@develia/commons 0.4.26 → 0.5.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/array.d.ts +10 -0
- package/dist/array.d.ts.map +1 -1
- package/dist/array.js +18 -0
- package/dist/array.js.map +1 -1
- package/dist/async-pool-executor.d.ts +3 -5
- package/dist/async-pool-executor.d.ts.map +1 -1
- package/dist/async-pool-executor.js +9 -9
- package/dist/async-pool-executor.js.map +1 -1
- package/dist/browser/form-utilities.d.ts +0 -2
- package/dist/browser/form-utilities.d.ts.map +1 -1
- package/dist/browser/form-utilities.js +3 -23
- package/dist/browser/form-utilities.js.map +1 -1
- package/dist/browser/geo-location.js +3 -3
- package/dist/browser/geo-location.js.map +1 -1
- package/dist/browser/native-bridge.d.ts +1 -1
- package/dist/browser/native-bridge.d.ts.map +1 -1
- package/dist/browser/native-bridge.js +2 -2
- package/dist/browser/native-bridge.js.map +1 -1
- package/dist/cancellable-promise.d.ts +8 -2
- package/dist/cancellable-promise.d.ts.map +1 -1
- package/dist/cancellable-promise.js +9 -3
- package/dist/cancellable-promise.js.map +1 -1
- package/dist/clean-url-search-params.js +3 -3
- package/dist/clean-url-search-params.js.map +1 -1
- package/dist/collections/from.d.ts +16 -5
- package/dist/collections/from.d.ts.map +1 -1
- package/dist/collections/from.js +69 -38
- package/dist/collections/from.js.map +1 -1
- package/dist/draft.d.ts +2 -2
- package/dist/draft.d.ts.map +1 -1
- package/dist/draft.js +2 -2
- package/dist/draft.js.map +1 -1
- package/dist/functions.d.ts +2 -2
- package/dist/functions.d.ts.map +1 -1
- package/dist/functions.js +1 -1
- package/dist/functions.js.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/math.d.ts +14 -5
- package/dist/math.d.ts.map +1 -1
- package/dist/math.js +16 -5
- package/dist/math.js.map +1 -1
- package/dist/string.d.ts +5 -0
- package/dist/string.d.ts.map +1 -1
- package/dist/string.js +8 -3
- package/dist/string.js.map +1 -1
- package/dist/timespan.d.ts +5 -5
- package/dist/timespan.d.ts.map +1 -1
- package/dist/timespan.js +5 -5
- package/dist/timespan.js.map +1 -1
- package/dist/types.d.ts +0 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/dist/utilities.d.ts +7 -6
- package/dist/utilities.d.ts.map +1 -1
- package/dist/utilities.js +19 -20
- package/dist/utilities.js.map +1 -1
- package/dist/uuid.d.ts +73 -0
- package/dist/uuid.d.ts.map +1 -0
- package/dist/uuid.js +129 -0
- package/dist/uuid.js.map +1 -0
- package/package.json +5 -1
- package/src/array.ts +19 -0
- package/src/async-pool-executor.ts +9 -9
- package/src/browser/form-utilities.ts +3 -32
- package/src/browser/geo-location.ts +3 -3
- package/src/browser/native-bridge.ts +2 -2
- package/src/cancellable-promise.ts +9 -3
- package/src/clean-url-search-params.ts +3 -3
- package/src/collections/from.ts +70 -38
- package/src/draft.ts +3 -3
- package/src/functions.ts +3 -3
- package/src/index.ts +2 -1
- package/src/math.ts +17 -5
- package/src/string.ts +9 -3
- package/src/timespan.ts +5 -5
- package/src/types.ts +0 -1
- package/src/utilities.ts +22 -25
- package/src/uuid.ts +153 -0
|
@@ -34,7 +34,7 @@ export default class NativeBridge {
|
|
|
34
34
|
}
|
|
35
35
|
|
|
36
36
|
(window as any)[callbackName] = (id: number, error: any, result: any) => {
|
|
37
|
-
this.
|
|
37
|
+
this._settlePendingCall(id, error, result);
|
|
38
38
|
};
|
|
39
39
|
|
|
40
40
|
// Proxy: cualquier propiedad no existente se convierte en call()
|
|
@@ -49,7 +49,7 @@ export default class NativeBridge {
|
|
|
49
49
|
}
|
|
50
50
|
|
|
51
51
|
|
|
52
|
-
private
|
|
52
|
+
private _settlePendingCall(id: number, error: any, result: any) {
|
|
53
53
|
|
|
54
54
|
const pending = this._pending.get(id);
|
|
55
55
|
if (!pending) {
|
|
@@ -36,9 +36,14 @@ export default class CancellablePromise<T> extends Promise<T> {
|
|
|
36
36
|
}
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
-
|
|
39
|
+
/**
|
|
40
|
+
* Resolves or rejects as the first of the promises to settle (Promise.race), and cancels all the others, also when
|
|
41
|
+
* the first one fails.
|
|
42
|
+
* @throws {Error} When there are no promises.
|
|
43
|
+
*/
|
|
44
|
+
static async raceAndCancelOthers<T>(promises: CancellablePromise<T>[]): Promise<T> {
|
|
40
45
|
if (promises.length === 0) {
|
|
41
|
-
throw new Error("No promises to
|
|
46
|
+
throw new Error("No promises to race.");
|
|
42
47
|
}
|
|
43
48
|
|
|
44
49
|
const result = await Promise.race(
|
|
@@ -61,7 +66,8 @@ export default class CancellablePromise<T> extends Promise<T> {
|
|
|
61
66
|
return result.value;
|
|
62
67
|
}
|
|
63
68
|
|
|
64
|
-
|
|
69
|
+
/** Resolves with all the results (Promise.all); when one fails, cancels all of them and rejects with its error. */
|
|
70
|
+
static async allOrCancelAll<T>(promises: CancellablePromise<T>[]): Promise<T[]> {
|
|
65
71
|
try {
|
|
66
72
|
return await Promise.all(promises);
|
|
67
73
|
} catch (error) {
|
|
@@ -7,12 +7,12 @@ function isRecordParams(params: unknown): params is Record<string, any> {
|
|
|
7
7
|
typeof (params as any)[Symbol.iterator] !== 'function';
|
|
8
8
|
}
|
|
9
9
|
|
|
10
|
-
function
|
|
10
|
+
function appendNonNullishValues(params: URLSearchParams, key: string, value: any): void {
|
|
11
11
|
if (value === null || value === undefined) return;
|
|
12
12
|
|
|
13
13
|
if (Array.isArray(value)) {
|
|
14
14
|
for (const item of value) {
|
|
15
|
-
|
|
15
|
+
appendNonNullishValues(params, key, item);
|
|
16
16
|
}
|
|
17
17
|
return;
|
|
18
18
|
}
|
|
@@ -31,7 +31,7 @@ export default class CleanURLSearchParams extends URLSearchParams {
|
|
|
31
31
|
|
|
32
32
|
// Filtrar `null` y `undefined` antes de añadirlos a URLSearchParams
|
|
33
33
|
for (const [key, value] of Object.entries(params)) {
|
|
34
|
-
|
|
34
|
+
appendNonNullishValues(this, key, value);
|
|
35
35
|
}
|
|
36
36
|
}
|
|
37
37
|
}
|
package/src/collections/from.ts
CHANGED
|
@@ -485,7 +485,7 @@ export class From<T> implements Iterable<T> {
|
|
|
485
485
|
array.sort((a, b) => {
|
|
486
486
|
const aValue = mapper(a);
|
|
487
487
|
const bValue = mapper(b);
|
|
488
|
-
return aValue > bValue ? 1 :
|
|
488
|
+
return aValue > bValue ? -1 : aValue < bValue ? 1 : 0;
|
|
489
489
|
});
|
|
490
490
|
} else {
|
|
491
491
|
throw new Error("invalid order argument");
|
|
@@ -713,11 +713,11 @@ export class From<T> implements Iterable<T> {
|
|
|
713
713
|
|
|
714
714
|
|
|
715
715
|
/**
|
|
716
|
-
*
|
|
716
|
+
* The elements of this sequence followed by those of the other, duplicates included, as LINQ's Concat.
|
|
717
717
|
* @param {Iterable<T>} other - The other sequence.
|
|
718
718
|
* @returns {From<T>} A new From instance.
|
|
719
719
|
*/
|
|
720
|
-
public
|
|
720
|
+
public concat(other: Iterable<T>): From<T> {
|
|
721
721
|
const self = this;
|
|
722
722
|
return From.fn<T>(function* () {
|
|
723
723
|
yield* self;
|
|
@@ -726,7 +726,19 @@ export class From<T> implements Iterable<T> {
|
|
|
726
726
|
}
|
|
727
727
|
|
|
728
728
|
/**
|
|
729
|
-
*
|
|
729
|
+
* The set union, as LINQ's Union: the elements of this sequence and then those of the other, each one once (the
|
|
730
|
+
* first time it appears), compared as distinct() does.
|
|
731
|
+
* @param {Iterable<T>} other - The other sequence.
|
|
732
|
+
* @param {EqualityComparer<T>} [equalityComparer] - Optional equality comparer; shallowEqual when not given.
|
|
733
|
+
* @returns {From<T>} A new From instance.
|
|
734
|
+
*/
|
|
735
|
+
public union(other: Iterable<T>, equalityComparer?: EqualityComparer<T>): From<T> {
|
|
736
|
+
return this.concat(other).distinct(equalityComparer);
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
/**
|
|
740
|
+
* Correlates the elements of two sequences based on matching keys: one result per matching pair, so an element
|
|
741
|
+
* that matches several elements of the other sequence gives one result for each.
|
|
730
742
|
* @template T2, R, R2
|
|
731
743
|
* @param {Iterable<T2>} other - The other sequence.
|
|
732
744
|
* @param {UnaryFunction<T, R>} thisKeySelector - Key selector for this sequence.
|
|
@@ -738,18 +750,20 @@ export class From<T> implements Iterable<T> {
|
|
|
738
750
|
|
|
739
751
|
const self = this;
|
|
740
752
|
return From.fn<R2>(() => {
|
|
741
|
-
const otherByKey =
|
|
742
|
-
|
|
743
|
-
|
|
753
|
+
const otherByKey = groupItemsByKey(other, otherKeySelector);
|
|
754
|
+
const results: R2[] = [];
|
|
755
|
+
for (const item of self) {
|
|
756
|
+
for (const otherItem of otherByKey.get(thisKeySelector(item)) ?? []) {
|
|
757
|
+
results.push(resultSelector(item, otherItem));
|
|
758
|
+
}
|
|
744
759
|
}
|
|
745
|
-
return
|
|
746
|
-
.filter(item => otherByKey.has(thisKeySelector(item)))
|
|
747
|
-
.map(item => resultSelector(item, otherByKey.get(thisKeySelector(item))!));
|
|
760
|
+
return results;
|
|
748
761
|
});
|
|
749
762
|
}
|
|
750
763
|
|
|
751
764
|
/**
|
|
752
|
-
* Performs a left join on two sequences
|
|
765
|
+
* Performs a left join on two sequences: one result per matching pair, and one with `undefined` for each element of
|
|
766
|
+
* this sequence that matches none.
|
|
753
767
|
* @template U, K, R
|
|
754
768
|
* @param {Iterable<U>} other - The other sequence.
|
|
755
769
|
* @param {UnaryFunction<T, K>} thisKeySelector - Key selector for this sequence.
|
|
@@ -761,11 +775,19 @@ export class From<T> implements Iterable<T> {
|
|
|
761
775
|
|
|
762
776
|
const self = this;
|
|
763
777
|
return From.fn<R>(() => {
|
|
764
|
-
const otherByKey =
|
|
765
|
-
|
|
766
|
-
|
|
778
|
+
const otherByKey = groupItemsByKey(other, otherKeySelector);
|
|
779
|
+
const results: R[] = [];
|
|
780
|
+
for (const item of self) {
|
|
781
|
+
const matches = otherByKey.get(thisKeySelector(item));
|
|
782
|
+
if (matches === undefined) {
|
|
783
|
+
results.push(resultSelector(item, undefined));
|
|
784
|
+
continue;
|
|
785
|
+
}
|
|
786
|
+
for (const otherItem of matches) {
|
|
787
|
+
results.push(resultSelector(item, otherItem));
|
|
788
|
+
}
|
|
767
789
|
}
|
|
768
|
-
return
|
|
790
|
+
return results;
|
|
769
791
|
});
|
|
770
792
|
}
|
|
771
793
|
|
|
@@ -783,15 +805,8 @@ export class From<T> implements Iterable<T> {
|
|
|
783
805
|
|
|
784
806
|
const self = this;
|
|
785
807
|
return From.fn<R>(() => {
|
|
786
|
-
const
|
|
787
|
-
|
|
788
|
-
const key = otherKeySelector(item);
|
|
789
|
-
if (!otherByKeys.has(key)) {
|
|
790
|
-
otherByKeys.set(key, []);
|
|
791
|
-
}
|
|
792
|
-
otherByKeys.get(key)!.push(item);
|
|
793
|
-
}
|
|
794
|
-
return Array.from(self).map(item => resultSelector(item, otherByKeys.get(thisKeySelector(item)) || []));
|
|
808
|
+
const otherByKey = groupItemsByKey(other, otherKeySelector);
|
|
809
|
+
return Array.from(self).map(item => resultSelector(item, otherByKey.get(thisKeySelector(item)) ?? []));
|
|
795
810
|
});
|
|
796
811
|
}
|
|
797
812
|
|
|
@@ -808,21 +823,15 @@ export class From<T> implements Iterable<T> {
|
|
|
808
823
|
|
|
809
824
|
const self = this;
|
|
810
825
|
return From.fn<R>(() => {
|
|
811
|
-
const
|
|
812
|
-
|
|
813
|
-
const key = thisKeySelector(item);
|
|
814
|
-
if (!thisByKeys.has(key)) {
|
|
815
|
-
thisByKeys.set(key, []);
|
|
816
|
-
}
|
|
817
|
-
thisByKeys.get(key)!.push(item);
|
|
818
|
-
}
|
|
819
|
-
return Array.from(other).map(item => resultSelector(thisByKeys.get(otherKeySelector(item)) || [], item));
|
|
826
|
+
const thisByKey = groupItemsByKey(self, thisKeySelector);
|
|
827
|
+
return Array.from(other).map(item => resultSelector(thisByKey.get(otherKeySelector(item)) ?? [], item));
|
|
820
828
|
});
|
|
821
829
|
}
|
|
822
830
|
|
|
823
831
|
|
|
824
832
|
/**
|
|
825
|
-
* Performs a right join on two sequences
|
|
833
|
+
* Performs a right join on two sequences: one result per matching pair, and one with `undefined` for each element of
|
|
834
|
+
* the other sequence that matches none.
|
|
826
835
|
* @template U, K, R
|
|
827
836
|
* @param {Iterable<U>} other - The other sequence.
|
|
828
837
|
* @param {UnaryFunction<T, K>} thisKeySelector - Key selector for this sequence.
|
|
@@ -834,16 +843,39 @@ export class From<T> implements Iterable<T> {
|
|
|
834
843
|
|
|
835
844
|
const self = this;
|
|
836
845
|
return From.fn<R>(() => {
|
|
837
|
-
const thisByKey =
|
|
838
|
-
|
|
839
|
-
|
|
846
|
+
const thisByKey = groupItemsByKey(self, thisKeySelector);
|
|
847
|
+
const results: R[] = [];
|
|
848
|
+
for (const otherItem of other) {
|
|
849
|
+
const matches = thisByKey.get(otherKeySelector(otherItem));
|
|
850
|
+
if (matches === undefined) {
|
|
851
|
+
results.push(resultSelector(undefined, otherItem));
|
|
852
|
+
continue;
|
|
853
|
+
}
|
|
854
|
+
for (const item of matches) {
|
|
855
|
+
results.push(resultSelector(item, otherItem));
|
|
856
|
+
}
|
|
840
857
|
}
|
|
841
|
-
return
|
|
858
|
+
return results;
|
|
842
859
|
});
|
|
843
860
|
}
|
|
844
861
|
|
|
845
862
|
}
|
|
846
863
|
|
|
864
|
+
/** The items of a sequence in lists by their key, in their order, for the joins. */
|
|
865
|
+
function groupItemsByKey<TItem, TKey>(items: Iterable<TItem>, keySelector: UnaryFunction<TItem, TKey>): Map<TKey, TItem[]> {
|
|
866
|
+
const itemsByKey = new Map<TKey, TItem[]>();
|
|
867
|
+
for (const item of items) {
|
|
868
|
+
const key = keySelector(item);
|
|
869
|
+
const group = itemsByKey.get(key);
|
|
870
|
+
if (group === undefined) {
|
|
871
|
+
itemsByKey.set(key, [item]);
|
|
872
|
+
} else {
|
|
873
|
+
group.push(item);
|
|
874
|
+
}
|
|
875
|
+
}
|
|
876
|
+
return itemsByKey;
|
|
877
|
+
}
|
|
878
|
+
|
|
847
879
|
/**
|
|
848
880
|
* Represents a group of elements that have a common key.
|
|
849
881
|
* @template TKey, T
|
package/src/draft.ts
CHANGED
|
@@ -39,7 +39,7 @@ type DraftMethods<T> = {
|
|
|
39
39
|
*
|
|
40
40
|
* @returns Array of modified property keys (does not recurse into children).
|
|
41
41
|
*/
|
|
42
|
-
|
|
42
|
+
getModifiedProps(): (keyof T)[];
|
|
43
43
|
|
|
44
44
|
/**
|
|
45
45
|
* Manually mark (or unmark) a property as modified.
|
|
@@ -155,7 +155,7 @@ function deepDraft(value: any, parentLink?: ParentLink): any {
|
|
|
155
155
|
/**
|
|
156
156
|
* Create a draft proxy over `original`. The proxy exposes the same properties
|
|
157
157
|
* as the original (with nested objects wrapped as child drafts) plus the
|
|
158
|
-
* control methods `commit`, `rollback`, `isModified`, `
|
|
158
|
+
* control methods `commit`, `rollback`, `isModified`, `getModifiedProps` and
|
|
159
159
|
* `setModified`.
|
|
160
160
|
*
|
|
161
161
|
* Mutations on the draft do not affect the original until `commit()` is called.
|
|
@@ -249,7 +249,7 @@ export default function draft<T extends object>(
|
|
|
249
249
|
return key === undefined ? touched.size > 0 : touched.has(key);
|
|
250
250
|
},
|
|
251
251
|
|
|
252
|
-
|
|
252
|
+
getModifiedProps() {
|
|
253
253
|
return [...touched] as (keyof T)[];
|
|
254
254
|
},
|
|
255
255
|
|
package/src/functions.ts
CHANGED
|
@@ -17,7 +17,7 @@ export interface ThrottleOptions {
|
|
|
17
17
|
* const refresh = throttle(loadData, 250);
|
|
18
18
|
* refresh();
|
|
19
19
|
*
|
|
20
|
-
* if (refresh.
|
|
20
|
+
* if (refresh.isPending()) {
|
|
21
21
|
* refresh.flush();
|
|
22
22
|
* }
|
|
23
23
|
* ```
|
|
@@ -31,7 +31,7 @@ export type DeferredFunction<F extends AnyFunction> = ((
|
|
|
31
31
|
/** Execute the scheduled trailing call immediately, when one exists. */
|
|
32
32
|
flush(): ReturnType<F> | undefined;
|
|
33
33
|
/** Return true while a trailing call is scheduled. */
|
|
34
|
-
|
|
34
|
+
isPending(): boolean;
|
|
35
35
|
};
|
|
36
36
|
|
|
37
37
|
/**
|
|
@@ -300,7 +300,7 @@ export function throttle<F extends AnyFunction>(
|
|
|
300
300
|
return result;
|
|
301
301
|
};
|
|
302
302
|
|
|
303
|
-
throttled.
|
|
303
|
+
throttled.isPending = (): boolean => timer !== undefined;
|
|
304
304
|
|
|
305
305
|
return throttled;
|
|
306
306
|
}
|
package/src/index.ts
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
export * from './types.js';
|
|
5
5
|
export {default as Type} from './type.js';
|
|
6
6
|
export {default as TypeCode} from './type-code.js';
|
|
7
|
+
export {default as Uuid} from './uuid.js';
|
|
7
8
|
|
|
8
9
|
// ─── Utilities ───────────────────────────────────────
|
|
9
10
|
export * from './utilities.js';
|
|
@@ -41,7 +42,7 @@ export {default as draft} from './draft.js';
|
|
|
41
42
|
// ─── Web ─────────────────────────────────────────────
|
|
42
43
|
export {default as CleanURLSearchParams} from './clean-url-search-params.js';
|
|
43
44
|
/** @deprecated */
|
|
44
|
-
export {ajaxSubmit,
|
|
45
|
+
export {ajaxSubmit, submitForm, formToObject} from './browser/index.js';
|
|
45
46
|
|
|
46
47
|
|
|
47
48
|
|
package/src/math.ts
CHANGED
|
@@ -1,15 +1,27 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Linearly interpolates a
|
|
2
|
+
* Linearly interpolates between two numbers: `a` for `t` 0, `b` for `t` 1, and beyond them outside that range.
|
|
3
3
|
*
|
|
4
|
-
* @param {number}
|
|
4
|
+
* @param {number} a - The value at `t` 0.
|
|
5
|
+
* @param {number} b - The value at `t` 1.
|
|
6
|
+
* @param {number} t - The interpolation factor.
|
|
7
|
+
* @return {number} - The interpolated value.
|
|
8
|
+
*/
|
|
9
|
+
export function lerp(a: number, b: number, t: number): number {
|
|
10
|
+
return a + (b - a) * t;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Maps a number from one range to another, keeping its proportion (`remap(5, 0, 10, 0, 100)` is 50).
|
|
15
|
+
*
|
|
16
|
+
* @param {number} n - The number to map.
|
|
5
17
|
* @param {number} inMin - The minimum value of the input range.
|
|
6
18
|
* @param {number} inMax - The maximum value of the input range.
|
|
7
19
|
* @param {number} outMin - The minimum value of the output range.
|
|
8
20
|
* @param {number} outMax - The maximum value of the output range.
|
|
9
|
-
*
|
|
10
|
-
* @
|
|
21
|
+
* @return {number} - The number in the output range.
|
|
22
|
+
* @throws {Error} When the input range is empty (`inMin === inMax`).
|
|
11
23
|
*/
|
|
12
|
-
export function
|
|
24
|
+
export function remap(n: number, inMin: number, inMax: number, outMin: number, outMax: number): number {
|
|
13
25
|
if (inMax === inMin) {
|
|
14
26
|
throw new Error("Input range cannot be zero");
|
|
15
27
|
}
|
package/src/string.ts
CHANGED
|
@@ -26,14 +26,20 @@ export function ensureSuffix(str: string, suffix: string): string {
|
|
|
26
26
|
return str
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
+
/**
|
|
30
|
+
* Null for an empty string, as `Str::nullIfEmpty()` of develia/commons for PHP; any other string as it is. Compose it
|
|
31
|
+
* with `trim()` to turn a blank string into null: `nullIfEmpty(value.trim())`.
|
|
32
|
+
*/
|
|
33
|
+
export function nullIfEmpty(str: string | null | undefined): string | null {
|
|
34
|
+
return str === undefined || str === null || str === '' ? null : str;
|
|
35
|
+
}
|
|
36
|
+
|
|
29
37
|
/**
|
|
30
38
|
* Normalizes Unicode whitespaces into single ASCII spaces and removes leading
|
|
31
39
|
* and trailing whitespace.
|
|
32
40
|
*/
|
|
33
41
|
export function normalizeWhitespaces(str: string): string {
|
|
34
|
-
return str.
|
|
35
|
-
.replace(/[\s\u200B\uFEFF]+/gu, ' ')
|
|
36
|
-
.trim()
|
|
42
|
+
return str.replace(/[\s\u200B\uFEFF]+/gu, ' ').trim();
|
|
37
43
|
}
|
|
38
44
|
|
|
39
45
|
/**
|
package/src/timespan.ts
CHANGED
|
@@ -13,7 +13,7 @@ export default class TimeSpan {
|
|
|
13
13
|
* Gets the time interval in seconds.
|
|
14
14
|
* @returns {number} The duration in seconds.
|
|
15
15
|
*/
|
|
16
|
-
|
|
16
|
+
getTotalSeconds(): number {
|
|
17
17
|
return this.milliseconds / 1000;
|
|
18
18
|
}
|
|
19
19
|
|
|
@@ -21,7 +21,7 @@ export default class TimeSpan {
|
|
|
21
21
|
* Gets the time interval in minutes.
|
|
22
22
|
* @returns {number} The duration in minutes.
|
|
23
23
|
*/
|
|
24
|
-
|
|
24
|
+
getTotalMinutes(): number {
|
|
25
25
|
return this.milliseconds / (1000 * 60);
|
|
26
26
|
}
|
|
27
27
|
|
|
@@ -29,7 +29,7 @@ export default class TimeSpan {
|
|
|
29
29
|
* Gets the time interval in hours.
|
|
30
30
|
* @returns {number} The duration in hours.
|
|
31
31
|
*/
|
|
32
|
-
|
|
32
|
+
getTotalHours(): number {
|
|
33
33
|
return this.milliseconds / (1000 * 60 * 60);
|
|
34
34
|
}
|
|
35
35
|
|
|
@@ -37,7 +37,7 @@ export default class TimeSpan {
|
|
|
37
37
|
* Gets the time interval in days.
|
|
38
38
|
* @returns {number} The duration in days.
|
|
39
39
|
*/
|
|
40
|
-
|
|
40
|
+
getTotalDays(): number {
|
|
41
41
|
return this.milliseconds / (1000 * 60 * 60 * 24);
|
|
42
42
|
}
|
|
43
43
|
|
|
@@ -45,7 +45,7 @@ export default class TimeSpan {
|
|
|
45
45
|
* Gets the time interval in weeks.
|
|
46
46
|
* @returns {number} The duration in weeks.
|
|
47
47
|
*/
|
|
48
|
-
|
|
48
|
+
getTotalWeeks(): number {
|
|
49
49
|
return this.milliseconds / (1000 * 60 * 60 * 24 * 7);
|
|
50
50
|
}
|
|
51
51
|
|
package/src/types.ts
CHANGED
package/src/utilities.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
|
|
4
4
|
import KeyValuePair from "./key-value-pair.js";
|
|
5
|
-
import type {
|
|
5
|
+
import type {Constructor} from "./types.js";
|
|
6
6
|
import Type from "./type.js";
|
|
7
7
|
|
|
8
8
|
export function lazy<T extends object>(factory: () => T): T {
|
|
@@ -185,8 +185,8 @@ function _deepEqual(
|
|
|
185
185
|
}
|
|
186
186
|
|
|
187
187
|
const objB = b as object;
|
|
188
|
-
const keysA =
|
|
189
|
-
const keysB =
|
|
188
|
+
const keysA = _collectComparableKeys(objA, includeInherited);
|
|
189
|
+
const keysB = _collectComparableKeys(objB, includeInherited);
|
|
190
190
|
if (keysA.length !== keysB.length) return false;
|
|
191
191
|
|
|
192
192
|
for (const key of keysA) {
|
|
@@ -265,7 +265,7 @@ function _deepEqualSet(
|
|
|
265
265
|
return true;
|
|
266
266
|
}
|
|
267
267
|
|
|
268
|
-
function
|
|
268
|
+
function _collectComparableKeys(value: object, includeInherited: boolean): PropertyKey[] {
|
|
269
269
|
const keys: PropertyKey[] = [];
|
|
270
270
|
const seen = new Set<PropertyKey>();
|
|
271
271
|
const addKey = (key: PropertyKey) => {
|
|
@@ -529,29 +529,26 @@ export function pick<T extends object, K extends keyof T>(obj: T, props: K[]): P
|
|
|
529
529
|
}
|
|
530
530
|
|
|
531
531
|
/**
|
|
532
|
-
*
|
|
533
|
-
*
|
|
534
|
-
*
|
|
535
|
-
* @
|
|
532
|
+
* Turns a function that takes a Node-style callback as its last argument (`(error, result) => void`) into one that
|
|
533
|
+
* returns a promise, as Node's `util.promisify`: it resolves with the result, or rejects with the error the callback
|
|
534
|
+
* receives. The function keeps the `this` it is called with.
|
|
535
|
+
* @param fn - The function to wrap.
|
|
536
|
+
* @returns The function that returns a promise.
|
|
536
537
|
*/
|
|
537
|
-
export function promisify<
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
}
|
|
549
|
-
reject(error);
|
|
550
|
-
}
|
|
538
|
+
export function promisify<TArgs extends unknown[], TResult>(
|
|
539
|
+
fn: (...args: [...TArgs, (error: unknown, result: TResult) => void]) => void,
|
|
540
|
+
): (...args: TArgs) => Promise<TResult> {
|
|
541
|
+
return function (this: unknown, ...args: TArgs): Promise<TResult> {
|
|
542
|
+
return new Promise<TResult>((resolve, reject) => {
|
|
543
|
+
fn.call(this, ...args, (error: unknown, result: TResult) => {
|
|
544
|
+
if (error !== null && error !== undefined) {
|
|
545
|
+
reject(error);
|
|
546
|
+
} else {
|
|
547
|
+
resolve(result);
|
|
548
|
+
}
|
|
549
|
+
});
|
|
551
550
|
});
|
|
552
|
-
}
|
|
553
|
-
|
|
554
|
-
return Promise.resolve(thing);
|
|
551
|
+
};
|
|
555
552
|
}
|
|
556
553
|
|
|
557
554
|
|
package/src/uuid.ts
ADDED
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import {Nullable} from './types.js';
|
|
2
|
+
|
|
3
|
+
/** Versions 1 to 8 with the RFC variant, plus the Nil and Max UUIDs of RFC 9562 sections 5.9 and 5.10. */
|
|
4
|
+
const PATTERN = /^(?:[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}|0{8}-0{4}-0{4}-0{4}-0{12}|f{8}-f{4}-f{4}-f{4}-f{12})$/i;
|
|
5
|
+
|
|
6
|
+
/** Largest Unix timestamp in milliseconds that fits in the 48 bits of a version 7 UUID. */
|
|
7
|
+
const MAX_V7_MILLISECONDS = 0xffffffffffff;
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Immutable universally unique identifier (RFC 9562), kept in lowercase canonical form,
|
|
11
|
+
* such as `019a4f8b-4bae-7d2e-9f10-4a5b6c7d8e9f`.
|
|
12
|
+
*
|
|
13
|
+
* It serializes to its string with `JSON.stringify`, so DTOs carry it as text; read those
|
|
14
|
+
* strings back with {@link Uuid.parse} or {@link Uuid.parseOrThrow}.
|
|
15
|
+
*/
|
|
16
|
+
export default class Uuid {
|
|
17
|
+
private readonly value: string;
|
|
18
|
+
|
|
19
|
+
private constructor(value: string) {
|
|
20
|
+
this.value = value;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Generates a version 4 UUID from 122 random bits of a cryptographically secure source.
|
|
25
|
+
*
|
|
26
|
+
* Unlike `crypto.randomUUID()`, it also works in browser contexts that are not secure, such as
|
|
27
|
+
* a development site served over HTTP.
|
|
28
|
+
*/
|
|
29
|
+
static generateV4(): Uuid {
|
|
30
|
+
const bytes = new Uint8Array(16);
|
|
31
|
+
globalThis.crypto.getRandomValues(bytes);
|
|
32
|
+
|
|
33
|
+
return Uuid.createWithVersion(bytes, 4);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Generates a version 7 UUID: a 48-bit Unix timestamp in milliseconds followed by 74 random bits
|
|
38
|
+
* from a cryptographically secure source, so identifiers sort by creation time.
|
|
39
|
+
*
|
|
40
|
+
* Identifiers generated within the same millisecond keep no particular order among themselves.
|
|
41
|
+
*
|
|
42
|
+
* @param date Moment the UUID encodes; the current time when omitted.
|
|
43
|
+
* @throws {RangeError} If the date is invalid, before the Unix epoch or beyond the 48-bit range.
|
|
44
|
+
*/
|
|
45
|
+
static generateV7(date: Date = new Date()): Uuid {
|
|
46
|
+
const milliseconds = date.getTime();
|
|
47
|
+
if (!Number.isInteger(milliseconds) || milliseconds < 0 || milliseconds > MAX_V7_MILLISECONDS) {
|
|
48
|
+
throw new RangeError('A version 7 UUID can only encode a valid date from the Unix epoch on.');
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const bytes = new Uint8Array(16);
|
|
52
|
+
globalThis.crypto.getRandomValues(bytes);
|
|
53
|
+
let remaining = milliseconds;
|
|
54
|
+
for (let index = 5; index >= 0; index--) {
|
|
55
|
+
bytes[index] = remaining % 256;
|
|
56
|
+
remaining = Math.floor(remaining / 256);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return Uuid.createWithVersion(bytes, 7);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Checks if a value is a UUID in canonical form, in any letter case: one of RFC 9562 versions 1 to 8,
|
|
64
|
+
* or its Nil UUID (all zeros) or Max UUID (all ones).
|
|
65
|
+
*
|
|
66
|
+
* It returns a plain boolean rather than a type guard: a string that is not a UUID is still a
|
|
67
|
+
* string, which a guard would narrow to `never`.
|
|
68
|
+
*/
|
|
69
|
+
static isValid(value: unknown): boolean {
|
|
70
|
+
return typeof value === 'string' && PATTERN.test(value);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Reads a UUID in canonical form, in any letter case, as {@link Uuid.isValid} accepts it.
|
|
75
|
+
*
|
|
76
|
+
* @returns The UUID, or `null` when the value is not one.
|
|
77
|
+
*/
|
|
78
|
+
static parse(value: unknown): Nullable<Uuid> {
|
|
79
|
+
if (typeof value !== 'string' || !Uuid.isValid(value)) {
|
|
80
|
+
return null;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return new Uuid(value.toLowerCase());
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Reads a UUID in canonical form, in any letter case.
|
|
88
|
+
*
|
|
89
|
+
* @throws {SyntaxError} If the value is not a UUID.
|
|
90
|
+
*/
|
|
91
|
+
static parseOrThrow(value: string): Uuid {
|
|
92
|
+
const uuid = Uuid.parse(value);
|
|
93
|
+
if (uuid === null) {
|
|
94
|
+
throw new SyntaxError(`Invalid UUID "${value}".`);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return uuid;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Returns the RFC 9562 version, from 1 to 8, or 0 for the Nil UUID and 15 for the Max UUID.
|
|
102
|
+
*/
|
|
103
|
+
getVersion(): number {
|
|
104
|
+
return Number.parseInt(this.value.charAt(14), 16);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Returns the moment a version 7 UUID encodes, with millisecond precision.
|
|
109
|
+
*
|
|
110
|
+
* @returns The moment, or `null` for other versions.
|
|
111
|
+
*/
|
|
112
|
+
getDate(): Nullable<Date> {
|
|
113
|
+
if (this.getVersion() !== 7) {
|
|
114
|
+
return null;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const milliseconds = Number.parseInt(this.value.slice(0, 8) + this.value.slice(9, 13), 16);
|
|
118
|
+
return new Date(milliseconds);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
equals(other: Uuid): boolean {
|
|
122
|
+
return this.value === other.value;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Returns the UUID in lowercase canonical form.
|
|
127
|
+
*/
|
|
128
|
+
toString(): string {
|
|
129
|
+
return this.value;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
toJSON(): string {
|
|
133
|
+
return this.value;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Sets the version and the RFC 9562 variant bits of 16 bytes and wraps them as a UUID.
|
|
138
|
+
*
|
|
139
|
+
* @param bytes Exactly 16 bytes; only the version and variant bits are overwritten.
|
|
140
|
+
* @param version From 1 to 8.
|
|
141
|
+
*/
|
|
142
|
+
private static createWithVersion(bytes: Uint8Array, version: number): Uuid {
|
|
143
|
+
bytes[6] = (bytes[6] & 0x0f) | (version << 4);
|
|
144
|
+
bytes[8] = (bytes[8] & 0x3f) | 0x80;
|
|
145
|
+
|
|
146
|
+
let hex = '';
|
|
147
|
+
for (const byte of bytes) {
|
|
148
|
+
hex += byte.toString(16).padStart(2, '0');
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
return new Uuid(`${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`);
|
|
152
|
+
}
|
|
153
|
+
}
|