@develia/commons 0.4.25 → 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 +31 -11
- package/dist/collections/from.d.ts.map +1 -1
- package/dist/collections/from.js +73 -48
- 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 +11 -9
- package/dist/utilities.d.ts.map +1 -1
- package/dist/utilities.js +23 -23
- 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 +91 -49
- 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 +27 -29
- package/src/uuid.ts +153 -0
package/dist/uuid.js
ADDED
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
/** Versions 1 to 8 with the RFC variant, plus the Nil and Max UUIDs of RFC 9562 sections 5.9 and 5.10. */
|
|
2
|
+
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;
|
|
3
|
+
/** Largest Unix timestamp in milliseconds that fits in the 48 bits of a version 7 UUID. */
|
|
4
|
+
const MAX_V7_MILLISECONDS = 0xffffffffffff;
|
|
5
|
+
/**
|
|
6
|
+
* Immutable universally unique identifier (RFC 9562), kept in lowercase canonical form,
|
|
7
|
+
* such as `019a4f8b-4bae-7d2e-9f10-4a5b6c7d8e9f`.
|
|
8
|
+
*
|
|
9
|
+
* It serializes to its string with `JSON.stringify`, so DTOs carry it as text; read those
|
|
10
|
+
* strings back with {@link Uuid.parse} or {@link Uuid.parseOrThrow}.
|
|
11
|
+
*/
|
|
12
|
+
export default class Uuid {
|
|
13
|
+
constructor(value) {
|
|
14
|
+
this.value = value;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Generates a version 4 UUID from 122 random bits of a cryptographically secure source.
|
|
18
|
+
*
|
|
19
|
+
* Unlike `crypto.randomUUID()`, it also works in browser contexts that are not secure, such as
|
|
20
|
+
* a development site served over HTTP.
|
|
21
|
+
*/
|
|
22
|
+
static generateV4() {
|
|
23
|
+
const bytes = new Uint8Array(16);
|
|
24
|
+
globalThis.crypto.getRandomValues(bytes);
|
|
25
|
+
return Uuid.createWithVersion(bytes, 4);
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Generates a version 7 UUID: a 48-bit Unix timestamp in milliseconds followed by 74 random bits
|
|
29
|
+
* from a cryptographically secure source, so identifiers sort by creation time.
|
|
30
|
+
*
|
|
31
|
+
* Identifiers generated within the same millisecond keep no particular order among themselves.
|
|
32
|
+
*
|
|
33
|
+
* @param date Moment the UUID encodes; the current time when omitted.
|
|
34
|
+
* @throws {RangeError} If the date is invalid, before the Unix epoch or beyond the 48-bit range.
|
|
35
|
+
*/
|
|
36
|
+
static generateV7(date = new Date()) {
|
|
37
|
+
const milliseconds = date.getTime();
|
|
38
|
+
if (!Number.isInteger(milliseconds) || milliseconds < 0 || milliseconds > MAX_V7_MILLISECONDS) {
|
|
39
|
+
throw new RangeError('A version 7 UUID can only encode a valid date from the Unix epoch on.');
|
|
40
|
+
}
|
|
41
|
+
const bytes = new Uint8Array(16);
|
|
42
|
+
globalThis.crypto.getRandomValues(bytes);
|
|
43
|
+
let remaining = milliseconds;
|
|
44
|
+
for (let index = 5; index >= 0; index--) {
|
|
45
|
+
bytes[index] = remaining % 256;
|
|
46
|
+
remaining = Math.floor(remaining / 256);
|
|
47
|
+
}
|
|
48
|
+
return Uuid.createWithVersion(bytes, 7);
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Checks if a value is a UUID in canonical form, in any letter case: one of RFC 9562 versions 1 to 8,
|
|
52
|
+
* or its Nil UUID (all zeros) or Max UUID (all ones).
|
|
53
|
+
*
|
|
54
|
+
* It returns a plain boolean rather than a type guard: a string that is not a UUID is still a
|
|
55
|
+
* string, which a guard would narrow to `never`.
|
|
56
|
+
*/
|
|
57
|
+
static isValid(value) {
|
|
58
|
+
return typeof value === 'string' && PATTERN.test(value);
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Reads a UUID in canonical form, in any letter case, as {@link Uuid.isValid} accepts it.
|
|
62
|
+
*
|
|
63
|
+
* @returns The UUID, or `null` when the value is not one.
|
|
64
|
+
*/
|
|
65
|
+
static parse(value) {
|
|
66
|
+
if (typeof value !== 'string' || !Uuid.isValid(value)) {
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
69
|
+
return new Uuid(value.toLowerCase());
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Reads a UUID in canonical form, in any letter case.
|
|
73
|
+
*
|
|
74
|
+
* @throws {SyntaxError} If the value is not a UUID.
|
|
75
|
+
*/
|
|
76
|
+
static parseOrThrow(value) {
|
|
77
|
+
const uuid = Uuid.parse(value);
|
|
78
|
+
if (uuid === null) {
|
|
79
|
+
throw new SyntaxError(`Invalid UUID "${value}".`);
|
|
80
|
+
}
|
|
81
|
+
return uuid;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Returns the RFC 9562 version, from 1 to 8, or 0 for the Nil UUID and 15 for the Max UUID.
|
|
85
|
+
*/
|
|
86
|
+
getVersion() {
|
|
87
|
+
return Number.parseInt(this.value.charAt(14), 16);
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Returns the moment a version 7 UUID encodes, with millisecond precision.
|
|
91
|
+
*
|
|
92
|
+
* @returns The moment, or `null` for other versions.
|
|
93
|
+
*/
|
|
94
|
+
getDate() {
|
|
95
|
+
if (this.getVersion() !== 7) {
|
|
96
|
+
return null;
|
|
97
|
+
}
|
|
98
|
+
const milliseconds = Number.parseInt(this.value.slice(0, 8) + this.value.slice(9, 13), 16);
|
|
99
|
+
return new Date(milliseconds);
|
|
100
|
+
}
|
|
101
|
+
equals(other) {
|
|
102
|
+
return this.value === other.value;
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Returns the UUID in lowercase canonical form.
|
|
106
|
+
*/
|
|
107
|
+
toString() {
|
|
108
|
+
return this.value;
|
|
109
|
+
}
|
|
110
|
+
toJSON() {
|
|
111
|
+
return this.value;
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Sets the version and the RFC 9562 variant bits of 16 bytes and wraps them as a UUID.
|
|
115
|
+
*
|
|
116
|
+
* @param bytes Exactly 16 bytes; only the version and variant bits are overwritten.
|
|
117
|
+
* @param version From 1 to 8.
|
|
118
|
+
*/
|
|
119
|
+
static createWithVersion(bytes, version) {
|
|
120
|
+
bytes[6] = (bytes[6] & 0x0f) | (version << 4);
|
|
121
|
+
bytes[8] = (bytes[8] & 0x3f) | 0x80;
|
|
122
|
+
let hex = '';
|
|
123
|
+
for (const byte of bytes) {
|
|
124
|
+
hex += byte.toString(16).padStart(2, '0');
|
|
125
|
+
}
|
|
126
|
+
return new Uuid(`${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
//# sourceMappingURL=uuid.js.map
|
package/dist/uuid.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"uuid.js","sourceRoot":"","sources":["../src/uuid.ts"],"names":[],"mappings":"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","sourcesContent":["import {Nullable} from './types.js';\n\n/** Versions 1 to 8 with the RFC variant, plus the Nil and Max UUIDs of RFC 9562 sections 5.9 and 5.10. */\nconst 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;\n\n/** Largest Unix timestamp in milliseconds that fits in the 48 bits of a version 7 UUID. */\nconst MAX_V7_MILLISECONDS = 0xffffffffffff;\n\n/**\n * Immutable universally unique identifier (RFC 9562), kept in lowercase canonical form,\n * such as `019a4f8b-4bae-7d2e-9f10-4a5b6c7d8e9f`.\n *\n * It serializes to its string with `JSON.stringify`, so DTOs carry it as text; read those\n * strings back with {@link Uuid.parse} or {@link Uuid.parseOrThrow}.\n */\nexport default class Uuid {\n private readonly value: string;\n\n private constructor(value: string) {\n this.value = value;\n }\n\n /**\n * Generates a version 4 UUID from 122 random bits of a cryptographically secure source.\n *\n * Unlike `crypto.randomUUID()`, it also works in browser contexts that are not secure, such as\n * a development site served over HTTP.\n */\n static generateV4(): Uuid {\n const bytes = new Uint8Array(16);\n globalThis.crypto.getRandomValues(bytes);\n\n return Uuid.createWithVersion(bytes, 4);\n }\n\n /**\n * Generates a version 7 UUID: a 48-bit Unix timestamp in milliseconds followed by 74 random bits\n * from a cryptographically secure source, so identifiers sort by creation time.\n *\n * Identifiers generated within the same millisecond keep no particular order among themselves.\n *\n * @param date Moment the UUID encodes; the current time when omitted.\n * @throws {RangeError} If the date is invalid, before the Unix epoch or beyond the 48-bit range.\n */\n static generateV7(date: Date = new Date()): Uuid {\n const milliseconds = date.getTime();\n if (!Number.isInteger(milliseconds) || milliseconds < 0 || milliseconds > MAX_V7_MILLISECONDS) {\n throw new RangeError('A version 7 UUID can only encode a valid date from the Unix epoch on.');\n }\n\n const bytes = new Uint8Array(16);\n globalThis.crypto.getRandomValues(bytes);\n let remaining = milliseconds;\n for (let index = 5; index >= 0; index--) {\n bytes[index] = remaining % 256;\n remaining = Math.floor(remaining / 256);\n }\n\n return Uuid.createWithVersion(bytes, 7);\n }\n\n /**\n * Checks if a value is a UUID in canonical form, in any letter case: one of RFC 9562 versions 1 to 8,\n * or its Nil UUID (all zeros) or Max UUID (all ones).\n *\n * It returns a plain boolean rather than a type guard: a string that is not a UUID is still a\n * string, which a guard would narrow to `never`.\n */\n static isValid(value: unknown): boolean {\n return typeof value === 'string' && PATTERN.test(value);\n }\n\n /**\n * Reads a UUID in canonical form, in any letter case, as {@link Uuid.isValid} accepts it.\n *\n * @returns The UUID, or `null` when the value is not one.\n */\n static parse(value: unknown): Nullable<Uuid> {\n if (typeof value !== 'string' || !Uuid.isValid(value)) {\n return null;\n }\n\n return new Uuid(value.toLowerCase());\n }\n\n /**\n * Reads a UUID in canonical form, in any letter case.\n *\n * @throws {SyntaxError} If the value is not a UUID.\n */\n static parseOrThrow(value: string): Uuid {\n const uuid = Uuid.parse(value);\n if (uuid === null) {\n throw new SyntaxError(`Invalid UUID \"${value}\".`);\n }\n\n return uuid;\n }\n\n /**\n * Returns the RFC 9562 version, from 1 to 8, or 0 for the Nil UUID and 15 for the Max UUID.\n */\n getVersion(): number {\n return Number.parseInt(this.value.charAt(14), 16);\n }\n\n /**\n * Returns the moment a version 7 UUID encodes, with millisecond precision.\n *\n * @returns The moment, or `null` for other versions.\n */\n getDate(): Nullable<Date> {\n if (this.getVersion() !== 7) {\n return null;\n }\n\n const milliseconds = Number.parseInt(this.value.slice(0, 8) + this.value.slice(9, 13), 16);\n return new Date(milliseconds);\n }\n\n equals(other: Uuid): boolean {\n return this.value === other.value;\n }\n\n /**\n * Returns the UUID in lowercase canonical form.\n */\n toString(): string {\n return this.value;\n }\n\n toJSON(): string {\n return this.value;\n }\n\n /**\n * Sets the version and the RFC 9562 variant bits of 16 bytes and wraps them as a UUID.\n *\n * @param bytes Exactly 16 bytes; only the version and variant bits are overwritten.\n * @param version From 1 to 8.\n */\n private static createWithVersion(bytes: Uint8Array, version: number): Uuid {\n bytes[6] = (bytes[6] & 0x0f) | (version << 4);\n bytes[8] = (bytes[8] & 0x3f) | 0x80;\n\n let hex = '';\n for (const byte of bytes) {\n hex += byte.toString(16).padStart(2, '0');\n }\n\n return new Uuid(`${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`);\n }\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@develia/commons",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"author": {
|
|
5
5
|
"name": "Antonio Gil Espinosa",
|
|
6
6
|
"email": "antonio.gil.espinosa@gmail.com",
|
|
7
7
|
"url": "https://antoniogilespinosa.com"
|
|
8
8
|
},
|
|
9
9
|
"type": "module",
|
|
10
|
+
"engines": {
|
|
11
|
+
"node": ">=20"
|
|
12
|
+
},
|
|
10
13
|
"main": "./dist/index.js",
|
|
11
14
|
"types": "./dist/index.d.ts",
|
|
12
15
|
"exports": {
|
|
@@ -17,6 +20,7 @@
|
|
|
17
20
|
"./parse": "./dist/parse.js",
|
|
18
21
|
"./string": "./dist/string.js",
|
|
19
22
|
"./math": "./dist/math.js",
|
|
23
|
+
"./uuid": "./dist/uuid.js",
|
|
20
24
|
"./collections": "./dist/collections/index.js",
|
|
21
25
|
"./browser": "./dist/browser/index.js"
|
|
22
26
|
},
|
package/src/array.ts
CHANGED
|
@@ -49,6 +49,25 @@ export function chunk<T>(array: Array<T>, size: number): Array<Array<T>> {
|
|
|
49
49
|
return result;
|
|
50
50
|
}
|
|
51
51
|
|
|
52
|
+
/**
|
|
53
|
+
* A copy of the array with the element at one index moved to another, the others shifting to make room; the array
|
|
54
|
+
* stays as it was. An index out of the array leaves the copy in the same order.
|
|
55
|
+
* @template T
|
|
56
|
+
* @param {ReadonlyArray<T>} array - The array.
|
|
57
|
+
* @param {number} fromIndex - Index of the element to move.
|
|
58
|
+
* @param {number} toIndex - Index the element ends at.
|
|
59
|
+
* @return {Array<T>} The copy, in the new order.
|
|
60
|
+
*/
|
|
61
|
+
export function moveItem<T>(array: ReadonlyArray<T>, fromIndex: number, toIndex: number): Array<T> {
|
|
62
|
+
const result = [...array];
|
|
63
|
+
if (fromIndex < 0 || fromIndex >= result.length || toIndex < 0 || toIndex >= result.length) {
|
|
64
|
+
return result;
|
|
65
|
+
}
|
|
66
|
+
const [item] = result.splice(fromIndex, 1);
|
|
67
|
+
result.splice(toIndex, 0, item as T);
|
|
68
|
+
return result;
|
|
69
|
+
}
|
|
70
|
+
|
|
52
71
|
/**
|
|
53
72
|
* Removes all elements from the array, mutating it.
|
|
54
73
|
* @template T
|
|
@@ -7,11 +7,11 @@ type QueuedTask<T> = {
|
|
|
7
7
|
reject: (reason?: any) => void;
|
|
8
8
|
};
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
/** @throws {RangeError} When it is not a positive integer. */
|
|
11
|
+
function assertValidMaxConcurrency(value: number): void {
|
|
11
12
|
if (!Number.isInteger(value) || value < 1) {
|
|
12
13
|
throw new RangeError("maxConcurrency must be a positive integer");
|
|
13
14
|
}
|
|
14
|
-
return value;
|
|
15
15
|
}
|
|
16
16
|
|
|
17
17
|
function normalizeCheckInterval(value: number): number {
|
|
@@ -26,7 +26,8 @@ export default class AsyncPoolExecutor {
|
|
|
26
26
|
}
|
|
27
27
|
|
|
28
28
|
set maxConcurrency(value: number) {
|
|
29
|
-
|
|
29
|
+
assertValidMaxConcurrency(value);
|
|
30
|
+
this._maxConcurrency = value;
|
|
30
31
|
|
|
31
32
|
}
|
|
32
33
|
|
|
@@ -41,7 +42,8 @@ export default class AsyncPoolExecutor {
|
|
|
41
42
|
* @param {number} [checkInterval=50] - The interval, in milliseconds, to check for capacity to run new tasks. Defaults to 50 ms.
|
|
42
43
|
*/
|
|
43
44
|
constructor(maxConcurrency: number, checkInterval: number = 50) {
|
|
44
|
-
|
|
45
|
+
assertValidMaxConcurrency(maxConcurrency);
|
|
46
|
+
this._maxConcurrency = maxConcurrency;
|
|
45
47
|
this._checkIntervalMs = normalizeCheckInterval(checkInterval);
|
|
46
48
|
this._activeTasks = 0;
|
|
47
49
|
}
|
|
@@ -94,12 +96,10 @@ export default class AsyncPoolExecutor {
|
|
|
94
96
|
|
|
95
97
|
|
|
96
98
|
/**
|
|
97
|
-
* Waits until
|
|
98
|
-
*
|
|
99
|
-
*
|
|
100
|
-
* @return {Promise<void>} A promise that resolves when all tasks are complete.
|
|
99
|
+
* Waits until the queue is empty and no task is running, checking every check interval. It does not stop the
|
|
100
|
+
* executor: tasks submitted meanwhile are run and waited for too.
|
|
101
101
|
*/
|
|
102
|
-
public async
|
|
102
|
+
public async drain(): Promise<void> {
|
|
103
103
|
while (this._taskQueue.length > 0 || this._activeTasks > 0) {
|
|
104
104
|
await delay(this._checkIntervalMs);
|
|
105
105
|
}
|
|
@@ -125,13 +125,13 @@ function _appendFormDataValue(formData: FormData, key: string, value: any): void
|
|
|
125
125
|
_appendFormDataValue(formData, `${key}[${index}]`, value[index]);
|
|
126
126
|
}
|
|
127
127
|
} else if (value !== null && typeof value === 'object') {
|
|
128
|
-
|
|
128
|
+
_appendToFormData(value, formData, key);
|
|
129
129
|
} else {
|
|
130
130
|
formData.append(key, value);
|
|
131
131
|
}
|
|
132
132
|
}
|
|
133
133
|
|
|
134
|
-
function
|
|
134
|
+
function _appendToFormData(data: Record<string, any>, formData: FormData, parentKey: string = ''): FormData {
|
|
135
135
|
for (const key in data) {
|
|
136
136
|
if (Object.prototype.hasOwnProperty.call(data, key)) {
|
|
137
137
|
_appendFormDataValue(formData, parentKey ? `${parentKey}[${key}]` : key, data[key]);
|
|
@@ -149,34 +149,5 @@ function _objectToFormData(data: Record<string, any>, formData: FormData, parent
|
|
|
149
149
|
*/
|
|
150
150
|
export function objectToFormData(data: Record<string, any>): FormData {
|
|
151
151
|
let formData = new FormData();
|
|
152
|
-
return
|
|
152
|
+
return _appendToFormData(data, formData);
|
|
153
153
|
}
|
|
154
|
-
|
|
155
|
-
export function ajaxSubmission(
|
|
156
|
-
selectorOrElement: HTMLFormElement | string,
|
|
157
|
-
onSuccess?: Consumer<any>,
|
|
158
|
-
onFailure?: Consumer<any>
|
|
159
|
-
): void {
|
|
160
|
-
|
|
161
|
-
const forms: HTMLFormElement[] = typeof selectorOrElement === 'string'
|
|
162
|
-
? Array.from(document.querySelectorAll(selectorOrElement))
|
|
163
|
-
.filter(el => el instanceof HTMLFormElement) as HTMLFormElement[]
|
|
164
|
-
: [selectorOrElement];
|
|
165
|
-
|
|
166
|
-
for (const form of forms) {
|
|
167
|
-
form.addEventListener('submit', async (event) => {
|
|
168
|
-
event.preventDefault();
|
|
169
|
-
|
|
170
|
-
try {
|
|
171
|
-
let result = await ajaxSubmit(event.currentTarget as HTMLFormElement);
|
|
172
|
-
if (onSuccess)
|
|
173
|
-
onSuccess(result);
|
|
174
|
-
} catch (e) {
|
|
175
|
-
if (onFailure)
|
|
176
|
-
onFailure(e);
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
});
|
|
181
|
-
}
|
|
182
|
-
}
|
|
@@ -33,7 +33,7 @@ function positionToGeoLocation(position: GeolocationPosition): GeoLocation {
|
|
|
33
33
|
};
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
-
function
|
|
36
|
+
function toGeolocationError(error: GeolocationPositionError): Error {
|
|
37
37
|
switch (error.code) {
|
|
38
38
|
case error.PERMISSION_DENIED:
|
|
39
39
|
return new Error('Has denegado el permiso de ubicación.');
|
|
@@ -50,7 +50,7 @@ function requestPosition(options: PositionOptions): Promise<GeoLocation> {
|
|
|
50
50
|
return new Promise((resolve, reject) => {
|
|
51
51
|
navigator.geolocation.getCurrentPosition(
|
|
52
52
|
(position) => resolve(positionToGeoLocation(position)),
|
|
53
|
-
(error) => reject(
|
|
53
|
+
(error) => reject(toGeolocationError(error)),
|
|
54
54
|
options,
|
|
55
55
|
);
|
|
56
56
|
});
|
|
@@ -87,7 +87,7 @@ async function ensurePermission(): Promise<void> {
|
|
|
87
87
|
},
|
|
88
88
|
(error) => {
|
|
89
89
|
permission.onchange = null;
|
|
90
|
-
reject(
|
|
90
|
+
reject(toGeolocationError(error));
|
|
91
91
|
},
|
|
92
92
|
{timeout: 30000},
|
|
93
93
|
);
|
|
@@ -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
|
@@ -5,21 +5,30 @@ import Order from "../order.js";
|
|
|
5
5
|
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
|
-
* Creates a new From instance from an Iterable
|
|
8
|
+
* Creates a new From instance from an Iterable, a Supplier of an Iterable, or the entries of a plain object.
|
|
9
9
|
* @template T - The type of elements in the source.
|
|
10
10
|
* @param {Iterable<T> | Supplier<Iterable<T>>} source - The source data.
|
|
11
|
-
* @returns {From<T>
|
|
11
|
+
* @returns {From<T>} A new From instance.
|
|
12
12
|
* @throws {Error} If source is null or invalid.
|
|
13
13
|
*/
|
|
14
|
-
export default function from<T>(source: Iterable<T> | Supplier<Iterable<T>>): From<T
|
|
14
|
+
export default function from<T>(source: Iterable<T> | Supplier<Iterable<T>>): From<T>;
|
|
15
|
+
/**
|
|
16
|
+
* Creates a new From instance from the enumerable own and inherited properties of a plain object.
|
|
17
|
+
* @template TValue - The type of the property values.
|
|
18
|
+
* @param {Record<string, TValue>} source - The source object.
|
|
19
|
+
* @returns {From<KeyValuePair<string, TValue>>} A From instance of key-value pairs.
|
|
20
|
+
* @throws {Error} If source is null or invalid.
|
|
21
|
+
*/
|
|
22
|
+
export default function from<TValue>(source: Record<string, TValue>): From<KeyValuePair<string, TValue>>;
|
|
23
|
+
export default function from(source: Iterable<unknown> | Supplier<Iterable<unknown>> | Record<string, unknown>): From<unknown> {
|
|
15
24
|
if (source == null)
|
|
16
25
|
throw new Error("Source is null.");
|
|
17
26
|
if (typeof source === 'function')
|
|
18
|
-
return From.fn
|
|
19
|
-
if (typeof source[Symbol.iterator] === 'function') {
|
|
20
|
-
return From.iterable<
|
|
27
|
+
return From.fn(source);
|
|
28
|
+
if (Symbol.iterator in source && typeof source[Symbol.iterator] === 'function') {
|
|
29
|
+
return From.iterable(source as Iterable<unknown>);
|
|
21
30
|
} else if (typeof source === 'object') {
|
|
22
|
-
return From.object(source);
|
|
31
|
+
return From.object(source as Record<string, unknown>);
|
|
23
32
|
}
|
|
24
33
|
|
|
25
34
|
throw new Error("Invalid source.");
|
|
@@ -33,11 +42,12 @@ export class From<T> implements Iterable<T> {
|
|
|
33
42
|
|
|
34
43
|
/**
|
|
35
44
|
* Creates a From instance from an object's entries.
|
|
36
|
-
* @
|
|
37
|
-
* @
|
|
45
|
+
* @template TValue - The type of the property values.
|
|
46
|
+
* @param {Record<string, TValue>} obj - The object to convert to pairs.
|
|
47
|
+
* @returns {From<KeyValuePair<string, TValue>>} A From instance of key-value pairs.
|
|
38
48
|
*/
|
|
39
|
-
public static object(obj:
|
|
40
|
-
return new From<KeyValuePair<
|
|
49
|
+
public static object<TValue>(obj: Record<string, TValue>): From<KeyValuePair<string, TValue>> {
|
|
50
|
+
return new From<KeyValuePair<string, TValue>>(function () {
|
|
41
51
|
return toPairs(obj);
|
|
42
52
|
});
|
|
43
53
|
}
|
|
@@ -475,7 +485,7 @@ export class From<T> implements Iterable<T> {
|
|
|
475
485
|
array.sort((a, b) => {
|
|
476
486
|
const aValue = mapper(a);
|
|
477
487
|
const bValue = mapper(b);
|
|
478
|
-
return aValue > bValue ? 1 :
|
|
488
|
+
return aValue > bValue ? -1 : aValue < bValue ? 1 : 0;
|
|
479
489
|
});
|
|
480
490
|
} else {
|
|
481
491
|
throw new Error("invalid order argument");
|
|
@@ -703,11 +713,11 @@ export class From<T> implements Iterable<T> {
|
|
|
703
713
|
|
|
704
714
|
|
|
705
715
|
/**
|
|
706
|
-
*
|
|
716
|
+
* The elements of this sequence followed by those of the other, duplicates included, as LINQ's Concat.
|
|
707
717
|
* @param {Iterable<T>} other - The other sequence.
|
|
708
718
|
* @returns {From<T>} A new From instance.
|
|
709
719
|
*/
|
|
710
|
-
public
|
|
720
|
+
public concat(other: Iterable<T>): From<T> {
|
|
711
721
|
const self = this;
|
|
712
722
|
return From.fn<T>(function* () {
|
|
713
723
|
yield* self;
|
|
@@ -716,7 +726,19 @@ export class From<T> implements Iterable<T> {
|
|
|
716
726
|
}
|
|
717
727
|
|
|
718
728
|
/**
|
|
719
|
-
*
|
|
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.
|
|
720
742
|
* @template T2, R, R2
|
|
721
743
|
* @param {Iterable<T2>} other - The other sequence.
|
|
722
744
|
* @param {UnaryFunction<T, R>} thisKeySelector - Key selector for this sequence.
|
|
@@ -728,18 +750,20 @@ export class From<T> implements Iterable<T> {
|
|
|
728
750
|
|
|
729
751
|
const self = this;
|
|
730
752
|
return From.fn<R2>(() => {
|
|
731
|
-
const otherByKey =
|
|
732
|
-
|
|
733
|
-
|
|
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
|
+
}
|
|
734
759
|
}
|
|
735
|
-
return
|
|
736
|
-
.filter(item => otherByKey.has(thisKeySelector(item)))
|
|
737
|
-
.map(item => resultSelector(item, otherByKey.get(thisKeySelector(item))!));
|
|
760
|
+
return results;
|
|
738
761
|
});
|
|
739
762
|
}
|
|
740
763
|
|
|
741
764
|
/**
|
|
742
|
-
* 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.
|
|
743
767
|
* @template U, K, R
|
|
744
768
|
* @param {Iterable<U>} other - The other sequence.
|
|
745
769
|
* @param {UnaryFunction<T, K>} thisKeySelector - Key selector for this sequence.
|
|
@@ -751,11 +775,19 @@ export class From<T> implements Iterable<T> {
|
|
|
751
775
|
|
|
752
776
|
const self = this;
|
|
753
777
|
return From.fn<R>(() => {
|
|
754
|
-
const otherByKey =
|
|
755
|
-
|
|
756
|
-
|
|
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
|
+
}
|
|
757
789
|
}
|
|
758
|
-
return
|
|
790
|
+
return results;
|
|
759
791
|
});
|
|
760
792
|
}
|
|
761
793
|
|
|
@@ -773,15 +805,8 @@ export class From<T> implements Iterable<T> {
|
|
|
773
805
|
|
|
774
806
|
const self = this;
|
|
775
807
|
return From.fn<R>(() => {
|
|
776
|
-
const
|
|
777
|
-
|
|
778
|
-
const key = otherKeySelector(item);
|
|
779
|
-
if (!otherByKeys.has(key)) {
|
|
780
|
-
otherByKeys.set(key, []);
|
|
781
|
-
}
|
|
782
|
-
otherByKeys.get(key)!.push(item);
|
|
783
|
-
}
|
|
784
|
-
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)) ?? []));
|
|
785
810
|
});
|
|
786
811
|
}
|
|
787
812
|
|
|
@@ -798,21 +823,15 @@ export class From<T> implements Iterable<T> {
|
|
|
798
823
|
|
|
799
824
|
const self = this;
|
|
800
825
|
return From.fn<R>(() => {
|
|
801
|
-
const
|
|
802
|
-
|
|
803
|
-
const key = thisKeySelector(item);
|
|
804
|
-
if (!thisByKeys.has(key)) {
|
|
805
|
-
thisByKeys.set(key, []);
|
|
806
|
-
}
|
|
807
|
-
thisByKeys.get(key)!.push(item);
|
|
808
|
-
}
|
|
809
|
-
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));
|
|
810
828
|
});
|
|
811
829
|
}
|
|
812
830
|
|
|
813
831
|
|
|
814
832
|
/**
|
|
815
|
-
* 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.
|
|
816
835
|
* @template U, K, R
|
|
817
836
|
* @param {Iterable<U>} other - The other sequence.
|
|
818
837
|
* @param {UnaryFunction<T, K>} thisKeySelector - Key selector for this sequence.
|
|
@@ -824,16 +843,39 @@ export class From<T> implements Iterable<T> {
|
|
|
824
843
|
|
|
825
844
|
const self = this;
|
|
826
845
|
return From.fn<R>(() => {
|
|
827
|
-
const thisByKey =
|
|
828
|
-
|
|
829
|
-
|
|
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
|
+
}
|
|
830
857
|
}
|
|
831
|
-
return
|
|
858
|
+
return results;
|
|
832
859
|
});
|
|
833
860
|
}
|
|
834
861
|
|
|
835
862
|
}
|
|
836
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
|
+
|
|
837
879
|
/**
|
|
838
880
|
* Represents a group of elements that have a common key.
|
|
839
881
|
* @template TKey, T
|