@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.
Files changed (81) hide show
  1. package/dist/array.d.ts +10 -0
  2. package/dist/array.d.ts.map +1 -1
  3. package/dist/array.js +18 -0
  4. package/dist/array.js.map +1 -1
  5. package/dist/async-pool-executor.d.ts +3 -5
  6. package/dist/async-pool-executor.d.ts.map +1 -1
  7. package/dist/async-pool-executor.js +9 -9
  8. package/dist/async-pool-executor.js.map +1 -1
  9. package/dist/browser/form-utilities.d.ts +0 -2
  10. package/dist/browser/form-utilities.d.ts.map +1 -1
  11. package/dist/browser/form-utilities.js +3 -23
  12. package/dist/browser/form-utilities.js.map +1 -1
  13. package/dist/browser/geo-location.js +3 -3
  14. package/dist/browser/geo-location.js.map +1 -1
  15. package/dist/browser/native-bridge.d.ts +1 -1
  16. package/dist/browser/native-bridge.d.ts.map +1 -1
  17. package/dist/browser/native-bridge.js +2 -2
  18. package/dist/browser/native-bridge.js.map +1 -1
  19. package/dist/cancellable-promise.d.ts +8 -2
  20. package/dist/cancellable-promise.d.ts.map +1 -1
  21. package/dist/cancellable-promise.js +9 -3
  22. package/dist/cancellable-promise.js.map +1 -1
  23. package/dist/clean-url-search-params.js +3 -3
  24. package/dist/clean-url-search-params.js.map +1 -1
  25. package/dist/collections/from.d.ts +31 -11
  26. package/dist/collections/from.d.ts.map +1 -1
  27. package/dist/collections/from.js +73 -48
  28. package/dist/collections/from.js.map +1 -1
  29. package/dist/draft.d.ts +2 -2
  30. package/dist/draft.d.ts.map +1 -1
  31. package/dist/draft.js +2 -2
  32. package/dist/draft.js.map +1 -1
  33. package/dist/functions.d.ts +2 -2
  34. package/dist/functions.d.ts.map +1 -1
  35. package/dist/functions.js +1 -1
  36. package/dist/functions.js.map +1 -1
  37. package/dist/index.d.ts +2 -1
  38. package/dist/index.d.ts.map +1 -1
  39. package/dist/index.js +2 -1
  40. package/dist/index.js.map +1 -1
  41. package/dist/math.d.ts +14 -5
  42. package/dist/math.d.ts.map +1 -1
  43. package/dist/math.js +16 -5
  44. package/dist/math.js.map +1 -1
  45. package/dist/string.d.ts +5 -0
  46. package/dist/string.d.ts.map +1 -1
  47. package/dist/string.js +8 -3
  48. package/dist/string.js.map +1 -1
  49. package/dist/timespan.d.ts +5 -5
  50. package/dist/timespan.d.ts.map +1 -1
  51. package/dist/timespan.js +5 -5
  52. package/dist/timespan.js.map +1 -1
  53. package/dist/types.d.ts +0 -1
  54. package/dist/types.d.ts.map +1 -1
  55. package/dist/types.js.map +1 -1
  56. package/dist/utilities.d.ts +11 -9
  57. package/dist/utilities.d.ts.map +1 -1
  58. package/dist/utilities.js +23 -23
  59. package/dist/utilities.js.map +1 -1
  60. package/dist/uuid.d.ts +73 -0
  61. package/dist/uuid.d.ts.map +1 -0
  62. package/dist/uuid.js +129 -0
  63. package/dist/uuid.js.map +1 -0
  64. package/package.json +5 -1
  65. package/src/array.ts +19 -0
  66. package/src/async-pool-executor.ts +9 -9
  67. package/src/browser/form-utilities.ts +3 -32
  68. package/src/browser/geo-location.ts +3 -3
  69. package/src/browser/native-bridge.ts +2 -2
  70. package/src/cancellable-promise.ts +9 -3
  71. package/src/clean-url-search-params.ts +3 -3
  72. package/src/collections/from.ts +91 -49
  73. package/src/draft.ts +3 -3
  74. package/src/functions.ts +3 -3
  75. package/src/index.ts +2 -1
  76. package/src/math.ts +17 -5
  77. package/src/string.ts +9 -3
  78. package/src/timespan.ts +5 -5
  79. package/src/types.ts +0 -1
  80. package/src/utilities.ts +27 -29
  81. 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
@@ -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.4.25",
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
- function normalizeMaxConcurrency(value: number): number {
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
- this._maxConcurrency = normalizeMaxConcurrency(value);
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
- this._maxConcurrency = normalizeMaxConcurrency(maxConcurrency);
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 all tasks (both queued and active) are completed.
98
- * This method resolves only when there are no pending tasks.
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 shutdown(): Promise<void> {
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
- _objectToFormData(value, formData, key);
128
+ _appendToFormData(value, formData, key);
129
129
  } else {
130
130
  formData.append(key, value);
131
131
  }
132
132
  }
133
133
 
134
- function _objectToFormData(data: Record<string, any>, formData: FormData, parentKey: string = ''): FormData {
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 _objectToFormData(data, formData);
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 geolocationError(error: GeolocationPositionError): Error {
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(geolocationError(error)),
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(geolocationError(error));
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._callback(id, error, result);
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 _callback(id: number, error: any, result: any) {
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
- static async compete<T>(promises: CancellablePromise<T>[]): Promise<T> {
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 compete.");
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
- static async cooperate<T>(promises: CancellablePromise<T>[]): Promise<T[]> {
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 appendCleanValue(params: URLSearchParams, key: string, value: any): void {
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
- appendCleanValue(params, key, item);
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
- appendCleanValue(this, key, value);
34
+ appendNonNullishValues(this, key, value);
35
35
  }
36
36
  }
37
37
  }
@@ -5,21 +5,30 @@ import Order from "../order.js";
5
5
 
6
6
 
7
7
  /**
8
- * Creates a new From instance from an Iterable or a Supplier of 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> | From<KeyValuePair<any, any>>} A new From instance.
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> | From<KeyValuePair<any, any>> {
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<T>(source);
19
- if (typeof source[Symbol.iterator] === 'function') {
20
- return From.iterable<T>(source);
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
- * @param {any} obj - The object to convert to pairs.
37
- * @returns {From<KeyValuePair<any, any>>} A From instance of key-value pairs.
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: any): From<KeyValuePair<any, any>> {
40
- return new From<KeyValuePair<any, any>>(function () {
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 : bValue < aValue ? -1 : 0;
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
- * Concatenates two sequences.
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 union(other: Iterable<T>): From<T> {
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
- * Correlates the elements of two sequences based on matching keys.
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 = new Map<R, T2>();
732
- for (let item of other) {
733
- otherByKey.set(otherKeySelector(item), item);
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 Array.from(self)
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 = new Map<K, U>();
755
- for (let item of other) {
756
- otherByKey.set(otherKeySelector(item), item);
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 Array.from(self).map(item => resultSelector(item, otherByKey.get(thisKeySelector(item))));
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 otherByKeys = new Map<K, Array<U>>();
777
- for (let item of other) {
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 thisByKeys = new Map<K, Array<T>>();
802
- for (let item of self) {
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 = new Map<K, T>();
828
- for (let item of self) {
829
- thisByKey.set(thisKeySelector(item), item);
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 Array.from(other).map(item => resultSelector(thisByKey.get(otherKeySelector(item)), item));
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