@simplysm/core-common 13.0.69 → 13.0.71
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/README.md +66 -267
- package/dist/common.types.d.ts +14 -14
- package/dist/errors/argument-error.d.ts +10 -10
- package/dist/errors/argument-error.d.ts.map +1 -1
- package/dist/errors/argument-error.js +2 -2
- package/dist/errors/argument-error.js.map +1 -1
- package/dist/errors/not-implemented-error.d.ts +8 -8
- package/dist/errors/not-implemented-error.js +2 -2
- package/dist/errors/not-implemented-error.js.map +1 -1
- package/dist/errors/sd-error.d.ts +10 -10
- package/dist/errors/sd-error.d.ts.map +1 -1
- package/dist/errors/timeout-error.d.ts +10 -10
- package/dist/errors/timeout-error.js +3 -3
- package/dist/errors/timeout-error.js.map +1 -1
- package/dist/extensions/arr-ext.d.ts +2 -2
- package/dist/extensions/arr-ext.helpers.d.ts +8 -8
- package/dist/extensions/arr-ext.helpers.js +1 -1
- package/dist/extensions/arr-ext.helpers.js.map +1 -1
- package/dist/extensions/arr-ext.js +13 -13
- package/dist/extensions/arr-ext.js.map +1 -1
- package/dist/extensions/arr-ext.types.d.ts +57 -57
- package/dist/extensions/arr-ext.types.d.ts.map +1 -1
- package/dist/extensions/map-ext.d.ts +16 -16
- package/dist/extensions/set-ext.d.ts +11 -11
- package/dist/features/debounce-queue.d.ts +17 -15
- package/dist/features/debounce-queue.d.ts.map +1 -1
- package/dist/features/debounce-queue.js +6 -6
- package/dist/features/debounce-queue.js.map +1 -1
- package/dist/features/event-emitter.d.ts +20 -20
- package/dist/features/event-emitter.js +17 -17
- package/dist/features/serial-queue.d.ts +11 -11
- package/dist/features/serial-queue.js +5 -5
- package/dist/features/serial-queue.js.map +1 -1
- package/dist/globals.d.ts +4 -4
- package/dist/types/date-only.d.ts +64 -64
- package/dist/types/date-only.d.ts.map +1 -1
- package/dist/types/date-only.js +63 -63
- package/dist/types/date-time.d.ts +37 -37
- package/dist/types/date-time.d.ts.map +1 -1
- package/dist/types/date-time.js +54 -37
- package/dist/types/date-time.js.map +1 -1
- package/dist/types/lazy-gc-map.d.ts +26 -26
- package/dist/types/lazy-gc-map.d.ts.map +1 -1
- package/dist/types/lazy-gc-map.js +26 -26
- package/dist/types/lazy-gc-map.js.map +1 -1
- package/dist/types/time.d.ts +25 -25
- package/dist/types/time.d.ts.map +1 -1
- package/dist/types/time.js +25 -25
- package/dist/types/time.js.map +1 -1
- package/dist/types/uuid.d.ts +11 -11
- package/dist/types/uuid.d.ts.map +1 -1
- package/dist/types/uuid.js +12 -12
- package/dist/types/uuid.js.map +1 -1
- package/dist/utils/bytes.d.ts +17 -17
- package/dist/utils/bytes.js +4 -4
- package/dist/utils/bytes.js.map +1 -1
- package/dist/utils/date-format.d.ts +45 -45
- package/dist/utils/date-format.js +1 -1
- package/dist/utils/date-format.js.map +1 -1
- package/dist/utils/error.d.ts +4 -4
- package/dist/utils/json.d.ts +17 -17
- package/dist/utils/json.js +3 -3
- package/dist/utils/json.js.map +1 -1
- package/dist/utils/num.d.ts +23 -23
- package/dist/utils/obj.d.ts +111 -111
- package/dist/utils/obj.d.ts.map +1 -1
- package/dist/utils/obj.js +3 -3
- package/dist/utils/obj.js.map +1 -1
- package/dist/utils/path.d.ts +10 -10
- package/dist/utils/primitive.d.ts +5 -5
- package/dist/utils/primitive.js +1 -1
- package/dist/utils/primitive.js.map +1 -1
- package/dist/utils/str.d.ts +46 -46
- package/dist/utils/str.d.ts.map +1 -1
- package/dist/utils/str.js +5 -5
- package/dist/utils/str.js.map +1 -1
- package/dist/utils/template-strings.d.ts +26 -26
- package/dist/utils/transferable.d.ts +18 -18
- package/dist/utils/transferable.js +1 -1
- package/dist/utils/transferable.js.map +1 -1
- package/dist/utils/wait.d.ts +9 -9
- package/dist/utils/xml.d.ts +13 -13
- package/dist/utils/xml.d.ts.map +1 -1
- package/dist/utils/xml.js +1 -0
- package/dist/utils/xml.js.map +1 -1
- package/dist/zip/sd-zip.d.ts +22 -22
- package/dist/zip/sd-zip.js +16 -16
- package/package.json +4 -4
- package/src/common.types.ts +17 -17
- package/src/errors/argument-error.ts +15 -15
- package/src/errors/not-implemented-error.ts +9 -9
- package/src/errors/sd-error.ts +12 -12
- package/src/errors/timeout-error.ts +12 -12
- package/src/extensions/arr-ext.helpers.ts +10 -10
- package/src/extensions/arr-ext.ts +57 -57
- package/src/extensions/arr-ext.types.ts +59 -59
- package/src/extensions/map-ext.ts +16 -16
- package/src/extensions/set-ext.ts +11 -11
- package/src/features/debounce-queue.ts +21 -19
- package/src/features/event-emitter.ts +25 -25
- package/src/features/serial-queue.ts +13 -13
- package/src/globals.ts +4 -4
- package/src/index.ts +1 -1
- package/src/types/date-only.ts +83 -83
- package/src/types/date-time.ts +64 -44
- package/src/types/lazy-gc-map.ts +45 -45
- package/src/types/time.ts +34 -34
- package/src/types/uuid.ts +17 -17
- package/src/utils/bytes.ts +35 -35
- package/src/utils/date-format.ts +65 -65
- package/src/utils/error.ts +4 -4
- package/src/utils/json.ts +39 -39
- package/src/utils/num.ts +23 -23
- package/src/utils/obj.ts +138 -138
- package/src/utils/path.ts +10 -10
- package/src/utils/primitive.ts +6 -6
- package/src/utils/str.ts +260 -261
- package/src/utils/template-strings.ts +29 -29
- package/src/utils/transferable.ts +284 -284
- package/src/utils/wait.ts +10 -10
- package/src/utils/xml.ts +20 -19
- package/src/zip/sd-zip.ts +25 -25
- package/tests/errors/errors.spec.ts +80 -0
- package/tests/extensions/array-extension.spec.ts +796 -0
- package/tests/extensions/map-extension.spec.ts +147 -0
- package/tests/extensions/set-extension.spec.ts +74 -0
- package/tests/types/date-only.spec.ts +638 -0
- package/tests/types/date-time.spec.ts +391 -0
- package/tests/types/lazy-gc-map.spec.ts +692 -0
- package/tests/types/time.spec.ts +559 -0
- package/tests/types/uuid.spec.ts +74 -0
- package/tests/utils/bytes-utils.spec.ts +230 -0
- package/tests/utils/date-format.spec.ts +373 -0
- package/tests/utils/debounce-queue.spec.ts +272 -0
- package/tests/utils/json.spec.ts +486 -0
- package/tests/utils/number.spec.ts +157 -0
- package/tests/utils/object.spec.ts +829 -0
- package/tests/utils/path.spec.ts +78 -0
- package/tests/utils/primitive.spec.ts +43 -0
- package/tests/utils/sd-event-emitter.spec.ts +216 -0
- package/tests/utils/serial-queue.spec.ts +365 -0
- package/tests/utils/string.spec.ts +281 -0
- package/tests/utils/template-strings.spec.ts +57 -0
- package/tests/utils/transferable.spec.ts +703 -0
- package/tests/utils/wait.spec.ts +145 -0
- package/tests/utils/xml.spec.ts +146 -0
- package/tests/zip/sd-zip.spec.ts +238 -0
- package/docs/extensions.md +0 -503
- package/docs/features.md +0 -109
- package/docs/types.md +0 -486
- package/docs/utils.md +0 -780
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Array
|
|
2
|
+
* Array extension methods
|
|
3
3
|
*
|
|
4
|
-
* @remarks
|
|
4
|
+
* @remarks See type definition file (arr-ext.types.ts) for TSDoc of each method
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import "./map-ext";
|
|
@@ -22,13 +22,13 @@ import type {
|
|
|
22
22
|
TreeArray,
|
|
23
23
|
} from "./arr-ext.types";
|
|
24
24
|
|
|
25
|
-
//#region
|
|
25
|
+
//#region Implementation
|
|
26
26
|
|
|
27
27
|
const arrayReadonlyExtensions: ReadonlyArrayExt<any> & ThisType<any[]> = {
|
|
28
28
|
single<T>(predicate?: (item: T, index: number) => boolean): T | undefined {
|
|
29
29
|
const arr = predicate !== undefined ? this.filter(predicate) : this;
|
|
30
30
|
if (arr.length > 1) {
|
|
31
|
-
throw new ArgumentError("
|
|
31
|
+
throw new ArgumentError("Multiple results found.", { count: arr.length });
|
|
32
32
|
}
|
|
33
33
|
return arr[0];
|
|
34
34
|
},
|
|
@@ -66,7 +66,7 @@ const arrayReadonlyExtensions: ReadonlyArrayExt<any> & ThisType<any[]> = {
|
|
|
66
66
|
},
|
|
67
67
|
|
|
68
68
|
ofType<T, N extends T>(type: PrimitiveTypeStr | Type<N>): N[] {
|
|
69
|
-
// PrimitiveTypeStr
|
|
69
|
+
// PrimitiveTypeStr case
|
|
70
70
|
if (typeof type === "string") {
|
|
71
71
|
return this.filter((item) => {
|
|
72
72
|
switch (type) {
|
|
@@ -87,15 +87,15 @@ const arrayReadonlyExtensions: ReadonlyArrayExt<any> & ThisType<any[]> = {
|
|
|
87
87
|
case "Bytes":
|
|
88
88
|
return item instanceof Uint8Array;
|
|
89
89
|
default: {
|
|
90
|
-
// exhaustive check:
|
|
90
|
+
// exhaustive check: Compilation error when a new type is added to PrimitiveTypeStr
|
|
91
91
|
const _exhaustive: never = type;
|
|
92
|
-
throw new ArgumentError(
|
|
92
|
+
throw new ArgumentError(`Unsupported type: ${_exhaustive}`);
|
|
93
93
|
}
|
|
94
94
|
}
|
|
95
95
|
}) as N[];
|
|
96
96
|
}
|
|
97
97
|
|
|
98
|
-
// Type<N> (
|
|
98
|
+
// Type<N> (constructor) case
|
|
99
99
|
return this.filter((item) => item instanceof type || item?.constructor === type) as N[];
|
|
100
100
|
},
|
|
101
101
|
|
|
@@ -121,10 +121,10 @@ const arrayReadonlyExtensions: ReadonlyArrayExt<any> & ThisType<any[]> = {
|
|
|
121
121
|
return Promise.all(this.map(fn));
|
|
122
122
|
},
|
|
123
123
|
|
|
124
|
-
//
|
|
125
|
-
//
|
|
126
|
-
// - primitive
|
|
127
|
-
// -
|
|
124
|
+
// Group array by key
|
|
125
|
+
// Performance considerations:
|
|
126
|
+
// - primitive key (string, number, etc.): O(n) - Map-based
|
|
127
|
+
// - object key: O(n²) - objEqual comparison
|
|
128
128
|
groupBy<T, K, V>(
|
|
129
129
|
keySelector: (item: T, index: number) => K,
|
|
130
130
|
valueSelector?: (item: T, index: number) => V,
|
|
@@ -134,14 +134,14 @@ const arrayReadonlyExtensions: ReadonlyArrayExt<any> & ThisType<any[]> = {
|
|
|
134
134
|
}[] {
|
|
135
135
|
const result: { key: K; values: (V | T)[] }[] = [];
|
|
136
136
|
|
|
137
|
-
//
|
|
137
|
+
// Map for primitive key optimization (key string -> result index)
|
|
138
138
|
const primitiveKeyIndex = new Map<string, number>();
|
|
139
139
|
|
|
140
140
|
for (let i = 0; i < this.length; i++) {
|
|
141
141
|
const keyObj = keySelector(this[i], i);
|
|
142
142
|
const valueObj = valueSelector !== undefined ? valueSelector(this[i], i) : this[i];
|
|
143
143
|
|
|
144
|
-
// primitive
|
|
144
|
+
// primitive keys are processed in O(n) using Map
|
|
145
145
|
if (keyObj == null || typeof keyObj !== "object") {
|
|
146
146
|
const keyStr = typeof keyObj + ":" + String(keyObj);
|
|
147
147
|
const existingIndex = primitiveKeyIndex.get(keyStr);
|
|
@@ -154,7 +154,7 @@ const arrayReadonlyExtensions: ReadonlyArrayExt<any> & ThisType<any[]> = {
|
|
|
154
154
|
continue;
|
|
155
155
|
}
|
|
156
156
|
|
|
157
|
-
//
|
|
157
|
+
// Object keys use the existing approach O(n²)
|
|
158
158
|
const existsRecord = result.find((item) => objEqual(item.key, keyObj));
|
|
159
159
|
if (existsRecord !== undefined) {
|
|
160
160
|
existsRecord.values.push(valueObj);
|
|
@@ -179,7 +179,7 @@ const arrayReadonlyExtensions: ReadonlyArrayExt<any> & ThisType<any[]> = {
|
|
|
179
179
|
const valueObj = valueSelector !== undefined ? valueSelector(item, i) : item;
|
|
180
180
|
|
|
181
181
|
if (result.has(keyObj)) {
|
|
182
|
-
throw new ArgumentError("
|
|
182
|
+
throw new ArgumentError("Duplicated key.", { duplicatedKey: keyObj });
|
|
183
183
|
}
|
|
184
184
|
result.set(keyObj, valueObj);
|
|
185
185
|
}
|
|
@@ -200,7 +200,7 @@ const arrayReadonlyExtensions: ReadonlyArrayExt<any> & ThisType<any[]> = {
|
|
|
200
200
|
const valueObj = valueSelector !== undefined ? await valueSelector(item, i) : item;
|
|
201
201
|
|
|
202
202
|
if (result.has(keyObj)) {
|
|
203
|
-
throw new ArgumentError("
|
|
203
|
+
throw new ArgumentError("Duplicated key.", { duplicatedKey: keyObj });
|
|
204
204
|
}
|
|
205
205
|
result.set(keyObj, valueObj);
|
|
206
206
|
}
|
|
@@ -282,9 +282,9 @@ const arrayReadonlyExtensions: ReadonlyArrayExt<any> & ThisType<any[]> = {
|
|
|
282
282
|
const key = keySelector(item, i);
|
|
283
283
|
const valueObj = valueSelector !== undefined ? valueSelector(item, i) : item;
|
|
284
284
|
|
|
285
|
-
// undefined
|
|
285
|
+
// undefined values are treated as "none", allowing overwrite
|
|
286
286
|
if (result[key] !== undefined) {
|
|
287
|
-
throw new ArgumentError("
|
|
287
|
+
throw new ArgumentError("Duplicated key.", { duplicatedKey: key });
|
|
288
288
|
}
|
|
289
289
|
result[key] = valueObj;
|
|
290
290
|
}
|
|
@@ -293,7 +293,7 @@ const arrayReadonlyExtensions: ReadonlyArrayExt<any> & ThisType<any[]> = {
|
|
|
293
293
|
},
|
|
294
294
|
|
|
295
295
|
toTree<T, K extends keyof T, P extends keyof T>(key: K, parentKey: P): TreeArray<T>[] {
|
|
296
|
-
// O(n)
|
|
296
|
+
// O(n) optimization: Map-based indexing
|
|
297
297
|
const childrenMap = this.toArrayMap((item) => item[parentKey]);
|
|
298
298
|
|
|
299
299
|
const fn = (items: T[]): TreeArray<T>[] => {
|
|
@@ -310,13 +310,13 @@ const arrayReadonlyExtensions: ReadonlyArrayExt<any> & ThisType<any[]> = {
|
|
|
310
310
|
distinct<T>(
|
|
311
311
|
options?: boolean | { matchAddress?: boolean; keyFn?: (item: T) => string | number },
|
|
312
312
|
): T[] {
|
|
313
|
-
//
|
|
313
|
+
// Normalize options
|
|
314
314
|
const opts = typeof options === "boolean" ? { matchAddress: options } : (options ?? {});
|
|
315
315
|
|
|
316
|
-
// matchAddress: Set
|
|
316
|
+
// matchAddress: Set-based O(n)
|
|
317
317
|
if (opts.matchAddress === true) return [...new Set(this)];
|
|
318
318
|
|
|
319
|
-
// keyFn
|
|
319
|
+
// keyFn provided: custom key-based O(n)
|
|
320
320
|
if (opts.keyFn) {
|
|
321
321
|
const seen = new Set<string | number>();
|
|
322
322
|
const result: T[] = [];
|
|
@@ -330,17 +330,17 @@ const arrayReadonlyExtensions: ReadonlyArrayExt<any> & ThisType<any[]> = {
|
|
|
330
330
|
return result;
|
|
331
331
|
}
|
|
332
332
|
|
|
333
|
-
//
|
|
333
|
+
// Default: type-based processing
|
|
334
334
|
const seen = new Map<string, T>();
|
|
335
|
-
const seenRefs = new Set<symbol | ((...args: unknown[]) => unknown)>(); //
|
|
335
|
+
const seenRefs = new Set<symbol | ((...args: unknown[]) => unknown)>(); // O(n) processing for symbol/function
|
|
336
336
|
const result: T[] = [];
|
|
337
337
|
|
|
338
338
|
for (const item of this) {
|
|
339
|
-
// primitive
|
|
339
|
+
// primitive types take the fast path
|
|
340
340
|
if (item === null || typeof item !== "object") {
|
|
341
341
|
const type = typeof item;
|
|
342
342
|
|
|
343
|
-
// symbol, function
|
|
343
|
+
// symbol, function use Set for identity comparison (O(n))
|
|
344
344
|
if (type === "symbol" || type === "function") {
|
|
345
345
|
if (!seenRefs.has(item)) {
|
|
346
346
|
seenRefs.add(item);
|
|
@@ -349,7 +349,7 @@ const arrayReadonlyExtensions: ReadonlyArrayExt<any> & ThisType<any[]> = {
|
|
|
349
349
|
continue;
|
|
350
350
|
}
|
|
351
351
|
|
|
352
|
-
//
|
|
352
|
+
// Other primitives: type prefix + special case handling
|
|
353
353
|
let key = type + ":";
|
|
354
354
|
if (Object.is(item, -0)) {
|
|
355
355
|
key += "-0";
|
|
@@ -406,8 +406,8 @@ const arrayReadonlyExtensions: ReadonlyArrayExt<any> & ThisType<any[]> = {
|
|
|
406
406
|
const hasKeys = options?.keys !== undefined && options.keys.length > 0;
|
|
407
407
|
const excludeOpts = { topLevelExcludes: options?.excludes };
|
|
408
408
|
|
|
409
|
-
// keys
|
|
410
|
-
//
|
|
409
|
+
// If keys option is provided, pre-index target by keys in Map to improve O(n×m) → O(n+m)
|
|
410
|
+
// Multiple targets with the same key value can exist, so store as array
|
|
411
411
|
const keyIndexedTarget = hasKeys ? new Map<string, P[]>() : undefined;
|
|
412
412
|
|
|
413
413
|
if (keyIndexedTarget) {
|
|
@@ -425,11 +425,11 @@ const arrayReadonlyExtensions: ReadonlyArrayExt<any> & ThisType<any[]> = {
|
|
|
425
425
|
}
|
|
426
426
|
|
|
427
427
|
for (const sourceItem of this) {
|
|
428
|
-
//
|
|
428
|
+
// Prioritize full match (sameTarget), otherwise search for key match (sameKeyTarget)
|
|
429
429
|
let sameTarget: P | undefined;
|
|
430
430
|
let sameKeyTarget: P | undefined;
|
|
431
431
|
|
|
432
|
-
//
|
|
432
|
+
// Skip already matched items using Set-based skipping (avoid O(n) splice removal)
|
|
433
433
|
for (const targetItem of uncheckedTarget) {
|
|
434
434
|
if (!uncheckedTargetSet.has(targetItem)) continue;
|
|
435
435
|
if (objEqual(targetItem, sourceItem, excludeOpts)) {
|
|
@@ -438,14 +438,14 @@ const arrayReadonlyExtensions: ReadonlyArrayExt<any> & ThisType<any[]> = {
|
|
|
438
438
|
}
|
|
439
439
|
}
|
|
440
440
|
|
|
441
|
-
//
|
|
441
|
+
// If no full match and keys option exists, perform O(1) lookup in Map
|
|
442
442
|
if (sameTarget === undefined && keyIndexedTarget) {
|
|
443
443
|
const sourceKeyStr = JSON.stringify(
|
|
444
444
|
options!.keys!.map((k) => (sourceItem as Record<string, unknown>)[k]),
|
|
445
445
|
);
|
|
446
446
|
const candidates = keyIndexedTarget.get(sourceKeyStr);
|
|
447
447
|
if (candidates && candidates.length > 0) {
|
|
448
|
-
//
|
|
448
|
+
// Select first remaining item using O(1) lookup in uncheckedTargetSet
|
|
449
449
|
sameKeyTarget = candidates.find((c) => uncheckedTargetSet.has(c));
|
|
450
450
|
}
|
|
451
451
|
}
|
|
@@ -531,22 +531,22 @@ const arrayReadonlyExtensions: ReadonlyArrayExt<any> & ThisType<any[]> = {
|
|
|
531
531
|
|
|
532
532
|
const result: (T | P | (T & P))[] = objClone(this);
|
|
533
533
|
|
|
534
|
-
//
|
|
534
|
+
// Pre-calculate original index of source items to improve O(n) lookup to O(1)
|
|
535
535
|
const sourceIndexMap = new Map<T, number>();
|
|
536
536
|
for (let i = 0; i < this.length; i++) {
|
|
537
537
|
sourceIndexMap.set(this[i], i);
|
|
538
538
|
}
|
|
539
539
|
|
|
540
540
|
for (const diff of diffs) {
|
|
541
|
-
//
|
|
541
|
+
// When updating
|
|
542
542
|
if (diff.source !== undefined && diff.target !== undefined) {
|
|
543
543
|
const sourceIndex = sourceIndexMap.get(diff.source);
|
|
544
544
|
if (sourceIndex === undefined) {
|
|
545
|
-
throw new SdError("
|
|
545
|
+
throw new SdError("Unexpected error: source item not found in merge.");
|
|
546
546
|
}
|
|
547
547
|
result[sourceIndex] = objMerge(diff.source, diff.target);
|
|
548
548
|
}
|
|
549
|
-
//
|
|
549
|
+
// When adding
|
|
550
550
|
else if (diff.target !== undefined) {
|
|
551
551
|
result.push(diff.target);
|
|
552
552
|
}
|
|
@@ -560,7 +560,7 @@ const arrayReadonlyExtensions: ReadonlyArrayExt<any> & ThisType<any[]> = {
|
|
|
560
560
|
for (let i = 0; i < this.length; i++) {
|
|
561
561
|
const item = selector !== undefined ? selector(this[i], i) : this[i];
|
|
562
562
|
if (typeof item !== "number") {
|
|
563
|
-
throw new ArgumentError("sum
|
|
563
|
+
throw new ArgumentError("sum can only be used with numbers.", {
|
|
564
564
|
type: typeof item,
|
|
565
565
|
});
|
|
566
566
|
}
|
|
@@ -575,7 +575,7 @@ const arrayReadonlyExtensions: ReadonlyArrayExt<any> & ThisType<any[]> = {
|
|
|
575
575
|
for (let i = 0; i < this.length; i++) {
|
|
576
576
|
const item = selector !== undefined ? selector(this[i], i) : this[i];
|
|
577
577
|
if (typeof item !== "number" && typeof item !== "string") {
|
|
578
|
-
throw new ArgumentError("min
|
|
578
|
+
throw new ArgumentError("min can only be used with numbers/strings.", {
|
|
579
579
|
type: typeof item,
|
|
580
580
|
});
|
|
581
581
|
}
|
|
@@ -592,7 +592,7 @@ const arrayReadonlyExtensions: ReadonlyArrayExt<any> & ThisType<any[]> = {
|
|
|
592
592
|
for (let i = 0; i < this.length; i++) {
|
|
593
593
|
const item = selector !== undefined ? selector(this[i], i) : this[i];
|
|
594
594
|
if (typeof item !== "number" && typeof item !== "string") {
|
|
595
|
-
throw new ArgumentError("max
|
|
595
|
+
throw new ArgumentError("max can only be used with numbers/strings.", {
|
|
596
596
|
type: typeof item,
|
|
597
597
|
});
|
|
598
598
|
}
|
|
@@ -622,11 +622,11 @@ const arrayMutableExtensions: MutableArrayExt<any> & ThisType<any[]> = {
|
|
|
622
622
|
distinctThis<T>(
|
|
623
623
|
options?: boolean | { matchAddress?: boolean; keyFn?: (item: T) => string | number },
|
|
624
624
|
): T[] {
|
|
625
|
-
//
|
|
625
|
+
// Normalize options
|
|
626
626
|
const opts = typeof options === "boolean" ? { matchAddress: options } : (options ?? {});
|
|
627
627
|
|
|
628
|
-
// matchAddress: Set
|
|
629
|
-
//
|
|
628
|
+
// matchAddress: Set-based O(n)
|
|
629
|
+
// To preserve first occurrence, collect indices to remove after forward traversal
|
|
630
630
|
if (opts.matchAddress === true) {
|
|
631
631
|
const seen = new Set<T>();
|
|
632
632
|
const toRemove: number[] = [];
|
|
@@ -637,15 +637,15 @@ const arrayMutableExtensions: MutableArrayExt<any> & ThisType<any[]> = {
|
|
|
637
637
|
seen.add(this[i]);
|
|
638
638
|
}
|
|
639
639
|
}
|
|
640
|
-
//
|
|
640
|
+
// Remove in reverse order (prevent index changes)
|
|
641
641
|
for (let i = toRemove.length - 1; i >= 0; i--) {
|
|
642
642
|
this.splice(toRemove[i], 1);
|
|
643
643
|
}
|
|
644
644
|
return this;
|
|
645
645
|
}
|
|
646
646
|
|
|
647
|
-
// keyFn
|
|
648
|
-
//
|
|
647
|
+
// keyFn provided: custom key-based O(n)
|
|
648
|
+
// To preserve first occurrence, collect indices to remove after forward traversal
|
|
649
649
|
if (opts.keyFn) {
|
|
650
650
|
const seen = new Set<string | number>();
|
|
651
651
|
const toRemove: number[] = [];
|
|
@@ -657,14 +657,14 @@ const arrayMutableExtensions: MutableArrayExt<any> & ThisType<any[]> = {
|
|
|
657
657
|
seen.add(key);
|
|
658
658
|
}
|
|
659
659
|
}
|
|
660
|
-
//
|
|
660
|
+
// Remove in reverse order (prevent index changes)
|
|
661
661
|
for (let i = toRemove.length - 1; i >= 0; i--) {
|
|
662
662
|
this.splice(toRemove[i], 1);
|
|
663
663
|
}
|
|
664
664
|
return this;
|
|
665
665
|
}
|
|
666
666
|
|
|
667
|
-
//
|
|
667
|
+
// Default: type-based processing (primitive optimization)
|
|
668
668
|
const seen = new Map<string, T>();
|
|
669
669
|
const seenRefs = new Set<symbol | ((...args: unknown[]) => unknown)>();
|
|
670
670
|
const toRemoveSet = new Set<number>();
|
|
@@ -672,11 +672,11 @@ const arrayMutableExtensions: MutableArrayExt<any> & ThisType<any[]> = {
|
|
|
672
672
|
for (let i = 0; i < this.length; i++) {
|
|
673
673
|
const item = this[i];
|
|
674
674
|
|
|
675
|
-
// primitive
|
|
675
|
+
// primitive types take the fast path O(n)
|
|
676
676
|
if (item === null || typeof item !== "object") {
|
|
677
677
|
const type = typeof item;
|
|
678
678
|
|
|
679
|
-
// symbol, function
|
|
679
|
+
// symbol, function use Set for identity comparison
|
|
680
680
|
if (type === "symbol" || type === "function") {
|
|
681
681
|
if (seenRefs.has(item)) {
|
|
682
682
|
toRemoveSet.add(i);
|
|
@@ -686,7 +686,7 @@ const arrayMutableExtensions: MutableArrayExt<any> & ThisType<any[]> = {
|
|
|
686
686
|
continue;
|
|
687
687
|
}
|
|
688
688
|
|
|
689
|
-
//
|
|
689
|
+
// Other primitives: type prefix + special case handling
|
|
690
690
|
let key = type + ":";
|
|
691
691
|
if (Object.is(item, -0)) {
|
|
692
692
|
key += "-0";
|
|
@@ -702,10 +702,10 @@ const arrayMutableExtensions: MutableArrayExt<any> & ThisType<any[]> = {
|
|
|
702
702
|
continue;
|
|
703
703
|
}
|
|
704
704
|
|
|
705
|
-
//
|
|
705
|
+
// Objects: deep comparison O(n²) - compare with previous non-removed items
|
|
706
706
|
let hasDuplicateBefore = false;
|
|
707
707
|
for (let j = 0; j < i; j++) {
|
|
708
|
-
//
|
|
708
|
+
// Skip indices in toRemoveSet (O(1) lookup)
|
|
709
709
|
if (toRemoveSet.has(j)) continue;
|
|
710
710
|
if (objEqual(this[j], item)) {
|
|
711
711
|
hasDuplicateBefore = true;
|
|
@@ -717,7 +717,7 @@ const arrayMutableExtensions: MutableArrayExt<any> & ThisType<any[]> = {
|
|
|
717
717
|
}
|
|
718
718
|
}
|
|
719
719
|
|
|
720
|
-
//
|
|
720
|
+
// Remove in reverse order (prevent index changes)
|
|
721
721
|
const toRemoveArr = Array.from(toRemoveSet).sort((a, b) => b - a);
|
|
722
722
|
for (const idx of toRemoveArr) {
|
|
723
723
|
this.splice(idx, 1);
|
|
@@ -757,7 +757,7 @@ const arrayMutableExtensions: MutableArrayExt<any> & ThisType<any[]> = {
|
|
|
757
757
|
? (itemOrSelector as (item: T, index: number) => boolean)
|
|
758
758
|
: (item: T) => item === itemOrSelector;
|
|
759
759
|
|
|
760
|
-
//
|
|
760
|
+
// Reverse traversal to prevent index change issues (O(n) performance)
|
|
761
761
|
for (let i = this.length - 1; i >= 0; i--) {
|
|
762
762
|
if (shouldRemove(this[i], i)) {
|
|
763
763
|
this.splice(i, 1);
|
|
@@ -795,7 +795,7 @@ for (const [name, fn] of Object.entries({
|
|
|
795
795
|
|
|
796
796
|
//#endregion
|
|
797
797
|
|
|
798
|
-
//#region
|
|
798
|
+
//#region Type Declarations
|
|
799
799
|
|
|
800
800
|
declare global {
|
|
801
801
|
interface ReadonlyArray<T> extends ReadonlyArrayExt<T> {}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Array
|
|
2
|
+
* Array extension type definitions
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
import type { PrimitiveTypeMap, PrimitiveTypeStr, Type } from "../common.types";
|
|
@@ -7,71 +7,71 @@ import type { DateTime } from "../types/date-time";
|
|
|
7
7
|
import type { DateOnly } from "../types/date-only";
|
|
8
8
|
import type { Time } from "../types/time";
|
|
9
9
|
|
|
10
|
-
//#region
|
|
10
|
+
//#region Interfaces
|
|
11
11
|
|
|
12
12
|
export interface ReadonlyArrayExt<TItem> {
|
|
13
13
|
/**
|
|
14
|
-
*
|
|
15
|
-
* @param predicate
|
|
16
|
-
* @returns
|
|
17
|
-
* @throws ArgumentError
|
|
14
|
+
* Return single element matching condition
|
|
15
|
+
* @param predicate Filter condition (if omitted, entire array is target)
|
|
16
|
+
* @returns undefined if element does not exist
|
|
17
|
+
* @throws ArgumentError If 2 or more elements match condition
|
|
18
18
|
*/
|
|
19
19
|
single(predicate?: (item: TItem, index: number) => boolean): TItem | undefined;
|
|
20
20
|
|
|
21
21
|
/**
|
|
22
|
-
*
|
|
23
|
-
* @param predicate
|
|
24
|
-
* @returns
|
|
22
|
+
* Return first element
|
|
23
|
+
* @param predicate Filter condition (if omitted, returns first element)
|
|
24
|
+
* @returns undefined if element does not exist
|
|
25
25
|
*/
|
|
26
26
|
first(predicate?: (item: TItem, index: number) => boolean): TItem | undefined;
|
|
27
27
|
|
|
28
|
-
/**
|
|
28
|
+
/** Async filter (sequential execution) */
|
|
29
29
|
filterAsync(predicate: (item: TItem, index: number) => Promise<boolean>): Promise<TItem[]>;
|
|
30
30
|
|
|
31
31
|
/**
|
|
32
|
-
*
|
|
33
|
-
* @param predicate
|
|
34
|
-
* @returns
|
|
32
|
+
* Return last element
|
|
33
|
+
* @param predicate Filter condition (if omitted, returns last element)
|
|
34
|
+
* @returns undefined if element does not exist
|
|
35
35
|
*/
|
|
36
36
|
last(predicate?: (item: TItem, index: number) => boolean): TItem | undefined;
|
|
37
37
|
|
|
38
|
-
/** null/undefined
|
|
38
|
+
/** Remove null/undefined */
|
|
39
39
|
filterExists(): NonNullable<TItem>[];
|
|
40
40
|
|
|
41
|
-
/**
|
|
41
|
+
/** Filter only elements of specific type (PrimitiveTypeStr or constructor type) */
|
|
42
42
|
ofType<K extends PrimitiveTypeStr>(type: K): Extract<TItem, PrimitiveTypeMap[K]>[];
|
|
43
43
|
ofType<N extends TItem>(type: Type<N>): N[];
|
|
44
44
|
|
|
45
|
-
/**
|
|
45
|
+
/** Async mapping (sequential execution) */
|
|
46
46
|
mapAsync<R>(selector: (item: TItem, index: number) => Promise<R>): Promise<R[]>;
|
|
47
47
|
|
|
48
|
-
/**
|
|
48
|
+
/** Flatten nested array */
|
|
49
49
|
mapMany(): TItem extends readonly (infer U)[] ? U[] : TItem;
|
|
50
50
|
|
|
51
|
-
/**
|
|
51
|
+
/** Map and then flatten */
|
|
52
52
|
mapMany<R>(selector: (item: TItem, index: number) => R[]): R[];
|
|
53
53
|
|
|
54
|
-
/**
|
|
54
|
+
/** Async mapping and then flatten (sequential execution) */
|
|
55
55
|
mapManyAsync<R>(selector: (item: TItem, index: number) => Promise<R[]>): Promise<R[]>;
|
|
56
56
|
|
|
57
57
|
/**
|
|
58
|
-
*
|
|
59
|
-
* @note
|
|
58
|
+
* Async parallel processing (using Promise.all)
|
|
59
|
+
* @note If any rejects, entire operation fail-fast rejects (Promise.all behavior)
|
|
60
60
|
*/
|
|
61
61
|
parallelAsync<R>(fn: (item: TItem, index: number) => Promise<R>): Promise<R[]>;
|
|
62
62
|
|
|
63
63
|
/**
|
|
64
|
-
*
|
|
65
|
-
* @param keySelector
|
|
66
|
-
* @note O(n²)
|
|
64
|
+
* Group by key
|
|
65
|
+
* @param keySelector Key selection function for group
|
|
66
|
+
* @note O(n²) complexity (deep comparison for object key support). If only primitive keys are needed, toArrayMap() is more efficient at O(n)
|
|
67
67
|
*/
|
|
68
68
|
groupBy<K>(keySelector: (item: TItem, index: number) => K): { key: K; values: TItem[] }[];
|
|
69
69
|
|
|
70
70
|
/**
|
|
71
|
-
*
|
|
72
|
-
* @param keySelector
|
|
73
|
-
* @param valueSelector
|
|
74
|
-
* @note O(n²)
|
|
71
|
+
* Group by key (with value transformation)
|
|
72
|
+
* @param keySelector Key selection function for group
|
|
73
|
+
* @param valueSelector Value transformation function
|
|
74
|
+
* @note O(n²) complexity (deep comparison for object key support). If only primitive keys are needed, toArrayMap() is more efficient at O(n)
|
|
75
75
|
*/
|
|
76
76
|
groupBy<K, V>(
|
|
77
77
|
keySelector: (item: TItem, index: number) => K,
|
|
@@ -121,16 +121,16 @@ export interface ReadonlyArrayExt<TItem> {
|
|
|
121
121
|
): Record<string, V>;
|
|
122
122
|
|
|
123
123
|
/**
|
|
124
|
-
*
|
|
124
|
+
* Convert flat array to tree structure
|
|
125
125
|
*
|
|
126
|
-
* @param keyProp
|
|
127
|
-
* @param parentKey
|
|
128
|
-
* @returns
|
|
126
|
+
* @param keyProp Unique key property name of each item
|
|
127
|
+
* @param parentKey Property name referencing parent item's key
|
|
128
|
+
* @returns Array of root items (each item has children property added)
|
|
129
129
|
*
|
|
130
130
|
* @remarks
|
|
131
|
-
* -
|
|
132
|
-
* -
|
|
133
|
-
* -
|
|
131
|
+
* - Items with null/undefined parentKey value become roots
|
|
132
|
+
* - Internally uses toArrayMap for O(n) complexity
|
|
133
|
+
* - Original items are copied with children property added
|
|
134
134
|
*
|
|
135
135
|
* @example
|
|
136
136
|
* ```typescript
|
|
@@ -162,9 +162,9 @@ export interface ReadonlyArrayExt<TItem> {
|
|
|
162
162
|
): TreeArray<TItem>[];
|
|
163
163
|
|
|
164
164
|
/**
|
|
165
|
-
*
|
|
166
|
-
* @param options matchAddress:
|
|
167
|
-
* @note
|
|
165
|
+
* Remove duplicates
|
|
166
|
+
* @param options matchAddress: address comparison (true uses Set), keyFn: custom key function (O(n) performance)
|
|
167
|
+
* @note O(n²) complexity when used without keyFn on object arrays. Using keyFn is recommended for large data
|
|
168
168
|
*/
|
|
169
169
|
distinct(
|
|
170
170
|
options?: boolean | { matchAddress?: boolean; keyFn?: (item: TItem) => string | number },
|
|
@@ -179,10 +179,10 @@ export interface ReadonlyArrayExt<TItem> {
|
|
|
179
179
|
): TItem[];
|
|
180
180
|
|
|
181
181
|
/**
|
|
182
|
-
*
|
|
183
|
-
* @param target
|
|
184
|
-
* @param options keys:
|
|
185
|
-
* @note target
|
|
182
|
+
* Compare two arrays (INSERT/DELETE/UPDATE)
|
|
183
|
+
* @param target Array to compare with
|
|
184
|
+
* @param options keys: for key comparison, excludes: properties to exclude from comparison
|
|
185
|
+
* @note If target has duplicate keys, only first match is used
|
|
186
186
|
*/
|
|
187
187
|
diffs<TOtherItem>(
|
|
188
188
|
target: TOtherItem[],
|
|
@@ -205,9 +205,9 @@ export interface ReadonlyArrayExt<TItem> {
|
|
|
205
205
|
): (TItem | TOtherItem | (TItem & TOtherItem))[];
|
|
206
206
|
|
|
207
207
|
/**
|
|
208
|
-
*
|
|
209
|
-
* @param selector
|
|
210
|
-
* @returns
|
|
208
|
+
* Return sum of elements
|
|
209
|
+
* @param selector Value selection function (if omitted, element itself is used as number)
|
|
210
|
+
* @returns 0 if array is empty
|
|
211
211
|
*/
|
|
212
212
|
sum(selector?: (item: TItem, index: number) => number): number;
|
|
213
213
|
|
|
@@ -223,49 +223,49 @@ export interface ReadonlyArrayExt<TItem> {
|
|
|
223
223
|
}
|
|
224
224
|
|
|
225
225
|
/**
|
|
226
|
-
*
|
|
227
|
-
* @mutates
|
|
226
|
+
* Extension methods that mutate the original array
|
|
227
|
+
* @mutates All methods directly modify the original array
|
|
228
228
|
*/
|
|
229
229
|
export interface MutableArrayExt<TItem> {
|
|
230
230
|
/**
|
|
231
|
-
*
|
|
232
|
-
* @param options matchAddress:
|
|
233
|
-
* @note
|
|
231
|
+
* Remove duplicates from original array
|
|
232
|
+
* @param options matchAddress: address comparison (true uses Set), keyFn: custom key function (O(n) performance)
|
|
233
|
+
* @note O(n²) complexity when used without keyFn on object arrays. Using keyFn is recommended for large data
|
|
234
234
|
* @mutates
|
|
235
235
|
*/
|
|
236
236
|
distinctThis(
|
|
237
237
|
options?: boolean | { matchAddress?: boolean; keyFn?: (item: TItem) => string | number },
|
|
238
238
|
): TItem[];
|
|
239
239
|
|
|
240
|
-
/**
|
|
240
|
+
/** Sort original array in ascending order @mutates */
|
|
241
241
|
orderByThis(
|
|
242
242
|
selector?: (item: TItem) => string | number | DateOnly | DateTime | Time | undefined,
|
|
243
243
|
): TItem[];
|
|
244
244
|
|
|
245
|
-
/**
|
|
245
|
+
/** Sort original array in descending order @mutates */
|
|
246
246
|
orderByDescThis(
|
|
247
247
|
selector?: (item: TItem) => string | number | DateOnly | DateTime | Time | undefined,
|
|
248
248
|
): TItem[];
|
|
249
249
|
|
|
250
|
-
/**
|
|
250
|
+
/** Insert items into original array @mutates */
|
|
251
251
|
insert(index: number, ...items: TItem[]): this;
|
|
252
252
|
|
|
253
|
-
/**
|
|
253
|
+
/** Remove item from original array @mutates */
|
|
254
254
|
remove(item: TItem): this;
|
|
255
255
|
|
|
256
|
-
/**
|
|
256
|
+
/** Remove items matching condition from original array @mutates */
|
|
257
257
|
remove(selector: (item: TItem, index: number) => boolean): this;
|
|
258
258
|
|
|
259
|
-
/**
|
|
259
|
+
/** Toggle item in original array (remove if exists, add if not) @mutates */
|
|
260
260
|
toggle(item: TItem): this;
|
|
261
261
|
|
|
262
|
-
/**
|
|
262
|
+
/** Clear original array @mutates */
|
|
263
263
|
clear(): this;
|
|
264
264
|
}
|
|
265
265
|
|
|
266
266
|
//#endregion
|
|
267
267
|
|
|
268
|
-
//#region
|
|
268
|
+
//#region Export Types
|
|
269
269
|
|
|
270
270
|
export type ArrayDiffsResult<TOriginal, TOther> =
|
|
271
271
|
| { source: undefined; target: TOther } // INSERT
|
|
@@ -279,7 +279,7 @@ export type ArrayDiffs2Result<TItem> =
|
|
|
279
279
|
|
|
280
280
|
export type TreeArray<TNode> = TNode & { children: TreeArray<TNode>[] };
|
|
281
281
|
|
|
282
|
-
/**
|
|
282
|
+
/** Type that can be sorted/compared */
|
|
283
283
|
export type ComparableType = string | number | boolean | DateTime | DateOnly | Time | undefined;
|
|
284
284
|
|
|
285
285
|
//#endregion
|