@valkyriestudios/utils 12.34.0 → 12.36.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/LICENSE.md CHANGED
@@ -1,6 +1,6 @@
1
- The MIT License (MIT)
1
+ MIT License
2
2
 
3
- Copyright (c) 2017 Valkyrie Studios (www.valkyriestudios.be)
3
+ Copyright (c) 2017 - present, Peter Vermeulen and @valkyriestudios/utils contributors
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
@@ -9,13 +9,13 @@ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
9
  copies of the Software, and to permit persons to whom the Software is
10
10
  furnished to do so, subject to the following conditions:
11
11
 
12
- The above copyright notice and this permission notice shall be included in all
13
- copies or substantial portions of the Software.
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
14
 
15
15
  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
16
  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
17
  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
18
  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
19
  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- SOFTWARE.
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
package/README.md CHANGED
@@ -1,8 +1,8 @@
1
1
  # @valkyriestudios/utils
2
2
 
3
+ [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
3
4
  [![CodeCov](https://codecov.io/gh/ValkyrieStudios/utils/branch/main/graph/badge.svg)](https://codecov.io/gh/ValkyrieStudios/utils)
4
- [![Test](https://github.com/ValkyrieStudios/utils/actions/workflows/test.yml/badge.svg?branch=main)](https://github.com/ValkyrieStudios/utils/actions/workflows/test.yml)
5
- [![Lint](https://github.com/ValkyrieStudios/utils/actions/workflows/lint.yml/badge.svg?branch=main)](https://github.com/ValkyrieStudios/utils/actions/workflows/lint.yml)
5
+ [![CI](https://github.com/ValkyrieStudios/utils/actions/workflows/ci.yml/badge.svg)](https://github.com/ValkyrieStudios/utils/actions/workflows/ci.yml)
6
6
  [![CodeQL](https://github.com/ValkyrieStudios/utils/actions/workflows/github-code-scanning/codeql/badge.svg?branch=main)](https://github.com/ValkyrieStudios/utils/actions/workflows/github-code-scanning/codeql)
7
7
  [![npm](https://img.shields.io/npm/v/@valkyriestudios/utils.svg)](https://www.npmjs.com/package/@valkyriestudios/utils)
8
8
  [![npm](https://img.shields.io/npm/dm/@valkyriestudios/utils.svg)](https://www.npmjs.com/package/@valkyriestudios/utils)
@@ -30,7 +30,7 @@ isNotEmptyArray([]); // FALSE
30
30
  isNotEmptyArray([0, 1, 2]); // TRUE
31
31
  ```
32
32
 
33
- ### array/mapKey(val:Record[], key:string, opts?:{merge?:boolean;filter_fn?:(el:T) => boolean})
33
+ ### array/mapKey(val:Record[], key:string, opts?:{merge?:boolean;filter_fn?:(el:T) => boolean;transform_fn?:(el:T) => U})
34
34
  Map a non-primitive object array into an object map by key.
35
35
 
36
36
  **Take Note**: The function `array/mapKeyAsMap` has the same behavior as the mapKey function with the sole difference being that it returns a
@@ -110,7 +110,7 @@ mapKey([
110
110
  {name: 'Alana', isActive: true},
111
111
  {uid: 87, name: 'Josh', isActive: false},
112
112
  {uid: 12, name: 'Farah', isActive: false},
113
- ], 'uid', {merge: true})
113
+ ], 'uid', {merge: true, filter_fn: el => el.isActive})
114
114
  /* Expected output: */
115
115
  {
116
116
  12: {uid: 12, name: 'Peter', isActive: true},
@@ -118,6 +118,24 @@ mapKey([
118
118
  }
119
119
  ```
120
120
 
121
+ also allows transforming objects at the same time with a custom transform_fn. Take Note that all of these operations are run in O(n):
122
+ ```typescript
123
+ import mapKey from '@valkyriestudios/utils/array/mapKey';
124
+ mapKey([
125
+ {uid: 12, name: 'Peter', isActive: true},
126
+ {uid: 15, name: 'Jonas', dob: '2022-02-07', isActive: true},
127
+ {uid: 15, name: 'Bob', isActive: false},
128
+ {name: 'Alana', isActive: true},
129
+ {uid: 87, name: 'Josh', isActive: false},
130
+ {uid: 12, name: 'Farah', isActive: false},
131
+ ], 'uid', {merge: true, filter_fn: el => el.isActive, transform_fn: el => pick(el, ["name"])})
132
+ /* Expected output: */
133
+ {
134
+ 12: {name: 'Peter'},
135
+ 15: {name: 'Jonas'},
136
+ }
137
+ ```
138
+
121
139
  ### array/mapKeyAsMap(val:Record[], key:string, opts?:{merge?:boolean;filter_fn?:(el:T) => boolean})
122
140
  Same behavior as mapKey but returns a Map instead of an object
123
141
 
@@ -560,6 +578,7 @@ Format a date according to a spec/locale and zone
560
578
  | `L` | Locale-Specific date | 15 jul 2024 |
561
579
  | `t` | Locale-specific short time | 10:28 AM |
562
580
  | `T` | Locale-specific time with seconds | 10:28:30 AM |
581
+ | `ISO` | Alias for `YYYY-MM-DD[T]HH:mm:ss.SSS[Z]` | 2025-03-16T14:24:59.010Z |
563
582
 
564
583
  **Additional**:
565
584
  Format has several additional functions defined which help usage inside of an ecosystem (eg: webapp) by **overriding the global defaults** used by format.
@@ -1111,6 +1130,182 @@ Both synchronous and asynchronous errors are logged via the custom logger (if pr
1111
1130
  - **Flexibility:**
1112
1131
  The storage behavior can be controlled globally through the constructor or overridden for individual publish calls.
1113
1132
 
1133
+ ### modules/Scheduler
1134
+ A Lightweight scheduler module that works with a cron-like syntax and can be easily configured/run at runtime.
1135
+
1136
+ **Take Note**: This does **not replace** cron, it is a module that runs at runtime and could be used to simulate cron-like behavior, but it is **not a replacement**.
1137
+
1138
+ ##### Usage
1139
+ Supports baseline operation:
1140
+ ```typescript
1141
+ import { Scheduler } from '@valkyriestudios/utils/modules/Scheduler';
1142
+ import * as Handlers from "..."; /* Example methods */
1143
+
1144
+ const mySchedule = new Scheduler({name: 'MyScheduler'});
1145
+
1146
+ mySchedule.add({schedule: '0 * * * *', name: 'send_emails', fn: Handlers.SendEmails});
1147
+ mySchedule.add({schedule: '0 */3 * * *', name: 'cleanup', fn: Handlers.Cleanup});
1148
+ mySchedule.add({schedule: '0,15,30,45 * * * *', name: 'synchronize', fn: Handlers.Synchronize});
1149
+ await mySchedule.run();
1150
+ ```
1151
+
1152
+ Let's say you need to send something out in different timezones:
1153
+ ```typescript
1154
+ import { Scheduler } from '@valkyriestudios/utils/modules/Scheduler';
1155
+ import * as Handlers from "..."; /* Example methods */
1156
+
1157
+ ...
1158
+
1159
+ const mySchedule = new Scheduler({name: 'MyScheduler'});
1160
+
1161
+ /* This is an example */
1162
+ for (const user of users) {
1163
+ mySchedule.add({
1164
+ schedule: '0 * * * *',
1165
+ name: 'send_emails',
1166
+ fn: Handlers.SendEmail,
1167
+ timeZone: user.timeZone, /* Given a user has a timezone */
1168
+ });
1169
+ }
1170
+
1171
+ await mySchedule.run();
1172
+ ```
1173
+
1174
+ Too much flooding! let's turn off parallelization and have it run them in linear fashion:
1175
+ ```typescript
1176
+ import { Scheduler } from '@valkyriestudios/utils/modules/Scheduler';
1177
+ import * as Handlers from "..."; /* Example methods */
1178
+
1179
+ ...
1180
+
1181
+ const mySchedule = new Scheduler({
1182
+ name: 'MyScheduler',
1183
+ parallel: false, /* By setting parallel to false we will ensure one at a time */
1184
+ });
1185
+
1186
+ /* This is an example */
1187
+ for (const user of users) {
1188
+ mySchedule.add({
1189
+ schedule: '0 * * * *',
1190
+ name: 'send_emails',
1191
+ fn: Handlers.SendEmail,
1192
+ timeZone: user.timeZone, /* Given a user has a timezone */
1193
+ });
1194
+ }
1195
+
1196
+ await mySchedule.run();
1197
+ ```
1198
+
1199
+ Okay we can actually send 3 at a time, let's set that up:
1200
+ ```typescript
1201
+ import { Scheduler } from '@valkyriestudios/utils/modules/Scheduler';
1202
+ import * as Handlers from "..."; /* Example methods */
1203
+
1204
+ ...
1205
+
1206
+ const mySchedule = new Scheduler({
1207
+ name: 'MyScheduler',
1208
+ parallel: 3, /* By setting parallel to a specific integer above 0 we will run X jobs in parallel at a time */
1209
+ });
1210
+
1211
+ /* This is an example */
1212
+ for (const user of users) {
1213
+ mySchedule.add({
1214
+ schedule: '0 * * * *',
1215
+ name: 'send_emails',
1216
+ fn: Handlers.SendEmail,
1217
+ timeZone: user.timeZone, /* Given a user has a timezone */
1218
+ });
1219
+ }
1220
+
1221
+ await mySchedule.run();
1222
+ ```
1223
+
1224
+ Oh no the emails aren't going out to the right user because we didn't pass our data:
1225
+ ```typescript
1226
+ import { Scheduler } from '@valkyriestudios/utils/modules/Scheduler';
1227
+ import * as Handlers from "..."; /* Example methods */
1228
+
1229
+ ...
1230
+
1231
+ const mySchedule = new Scheduler({
1232
+ name: 'MyScheduler',
1233
+ parallel: 3,
1234
+ });
1235
+
1236
+ /* This is an example */
1237
+ for (const user of users) {
1238
+ mySchedule.add({
1239
+ schedule: '0 * * * *',
1240
+ name: 'send_emails',
1241
+ fn: Handlers.SendEmail,
1242
+ timeZone: user.timeZone, /* Given a user has a timezone */
1243
+ data: user, /* You will have automatic type hinting on this with the first val of SendEmail handler */
1244
+ });
1245
+ }
1246
+
1247
+ await mySchedule.run();
1248
+ ```
1249
+
1250
+ I want this to run continuously so that I can just leave it running on a server:
1251
+ ```typescript
1252
+ import { Scheduler } from '@valkyriestudios/utils/modules/Scheduler';
1253
+ import * as Handlers from "..."; /* Example methods */
1254
+
1255
+ ...
1256
+
1257
+ const mySchedule = new Scheduler({
1258
+ name: 'MyScheduler',
1259
+ parallel: 3,
1260
+ auto: true, /* By enabling this the schedule will automatically check once per minute which ones it needs to run */
1261
+ });
1262
+
1263
+ /* This is an example */
1264
+ for (const user of users) {
1265
+ mySchedule.add({
1266
+ schedule: '0 * * * *',
1267
+ name: 'send_emails',
1268
+ fn: Handlers.SendEmail,
1269
+ timeZone: user.timeZone,
1270
+ data: user,
1271
+ });
1272
+ }
1273
+
1274
+ await mySchedule.run();
1275
+ ```
1276
+
1277
+ ##### API Overview
1278
+ - **new Scheduler(options?: { logger?: LogFn; name?: string; timeZone?: string|null; parallel?:boolean|number; auto?: boolean })**
1279
+ Creates a new Scheduler instance.
1280
+ -- **logger (optional)**: Custom logging function to capture errors.
1281
+ -- **name (optional)**: A non‑empty string to name the instance.
1282
+ -- **timeZone (optional)**: (default=local timezone) The default timeZone the scheduler is run in
1283
+ -- **parallel (optional)**: (default=true) Scheduler will run jobs that need to run in parallel, set to false to run linear, set to a number to run X jobs in parallel
1284
+ -- **auto (optional)**: (default=false) Set to true to automatically run the schedule every 60 seconds
1285
+ - **add(job: {name:string; schedule:string; fn: Function; timeZone?: string | null; data?: unknown}): boolean**
1286
+ Add a job to the schedule
1287
+ -- If a timeZone is passed we will automatically check against local time in that timeZone on whether or not the job needs to run.
1288
+ -- If a data object is passed we will pass this data object to the provided function when run.
1289
+ -- Schedule uses cron-like schedule and is compatible with most of the standard cron formats.
1290
+ - **remove(name:string|string[]):void**
1291
+ Removes one or multiple jobs by name from the schedule.
1292
+ - **run(): Promise<void>**
1293
+ Runs the schedule.
1294
+ - **stopAutomaticRun():void**
1295
+ Stops automatic running (if enabled)
1296
+ - **startAutomaticRun():void**
1297
+ Start automatic running (if not enabled)
1298
+ - **static isCronSchedule(val:string):boolean**
1299
+ Returns true if the provided value is a valid cron schedule, false if it isnt
1300
+ - **static cronShouldRun(val:string, timeZone: string|null):boolean**
1301
+ Given a cron schedule and an optional timeZone returns whether or not the cron schedule should be run now
1302
+
1303
+ ##### Notes:
1304
+ - **cron:**
1305
+ This is not a cron replacement but rather a module that can be used at runtime to handle cron-like schedules in a seamless way.
1306
+ - **auto:**
1307
+ If running this on a system that is replicated it **is highly advised** to not turn on automatic runs but rather build a way to trigger the schedule on a single instance into your networking.
1308
+
1114
1309
  ### number/is(val:unknown)
1115
1310
  Check if a variable is a number
1116
1311
  ```typescript
package/array/index.d.ts CHANGED
@@ -1,14 +1,14 @@
1
- import { dedupe } from './dedupe';
2
- import { isArray } from './is';
3
- import { isNotEmptyArray } from './isNotEmpty';
4
- import { join } from './join';
5
- import { mapFn } from './mapFn';
6
- import { mapFnAsMap } from './mapFnAsMap';
7
- import { mapKey } from './mapKey';
8
- import { mapKeyAsMap } from './mapKeyAsMap';
9
- import { mapPrimitive } from './mapPrimitive';
10
- import { groupBy } from './groupBy';
11
- import { shuffle } from './shuffle';
12
- import { split } from './split';
13
- import { sort } from './sort';
14
- export { dedupe, isArray, isArray as is, isNotEmptyArray, isNotEmptyArray as isNotEmpty, isNotEmptyArray as isNeArray, isNotEmptyArray as isNe, join, mapFn, mapFnAsMap, mapKey, mapKeyAsMap, mapPrimitive, groupBy, shuffle, split, sort };
1
+ export { dedupe } from './dedupe';
2
+ export { join } from './join';
3
+ export { mapFn } from './mapFn';
4
+ export { mapFnAsMap } from './mapFnAsMap';
5
+ export { mapKey } from './mapKey';
6
+ export { mapKeyAsMap } from './mapKeyAsMap';
7
+ export { mapPrimitive } from './mapPrimitive';
8
+ export { groupBy } from './groupBy';
9
+ export { shuffle } from './shuffle';
10
+ export { split } from './split';
11
+ export { sort } from './sort';
12
+ export { isArray } from './is';
13
+ export { isNotEmptyArray } from './isNotEmpty';
14
+ export { isNotEmptyArray as isNeArray } from './isNotEmpty';
package/array/index.js CHANGED
@@ -1,33 +1,31 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.sort = exports.split = exports.shuffle = exports.groupBy = exports.mapPrimitive = exports.mapKeyAsMap = exports.mapKey = exports.mapFnAsMap = exports.mapFn = exports.join = exports.isNe = exports.isNeArray = exports.isNotEmpty = exports.isNotEmptyArray = exports.is = exports.isArray = exports.dedupe = void 0;
4
- const dedupe_1 = require("./dedupe");
3
+ exports.isNeArray = exports.isNotEmptyArray = exports.isArray = exports.sort = exports.split = exports.shuffle = exports.groupBy = exports.mapPrimitive = exports.mapKeyAsMap = exports.mapKey = exports.mapFnAsMap = exports.mapFn = exports.join = exports.dedupe = void 0;
4
+ var dedupe_1 = require("./dedupe");
5
5
  Object.defineProperty(exports, "dedupe", { enumerable: true, get: function () { return dedupe_1.dedupe; } });
6
- const is_1 = require("./is");
7
- Object.defineProperty(exports, "isArray", { enumerable: true, get: function () { return is_1.isArray; } });
8
- Object.defineProperty(exports, "is", { enumerable: true, get: function () { return is_1.isArray; } });
9
- const isNotEmpty_1 = require("./isNotEmpty");
10
- Object.defineProperty(exports, "isNotEmptyArray", { enumerable: true, get: function () { return isNotEmpty_1.isNotEmptyArray; } });
11
- Object.defineProperty(exports, "isNotEmpty", { enumerable: true, get: function () { return isNotEmpty_1.isNotEmptyArray; } });
12
- Object.defineProperty(exports, "isNeArray", { enumerable: true, get: function () { return isNotEmpty_1.isNotEmptyArray; } });
13
- Object.defineProperty(exports, "isNe", { enumerable: true, get: function () { return isNotEmpty_1.isNotEmptyArray; } });
14
- const join_1 = require("./join");
6
+ var join_1 = require("./join");
15
7
  Object.defineProperty(exports, "join", { enumerable: true, get: function () { return join_1.join; } });
16
- const mapFn_1 = require("./mapFn");
8
+ var mapFn_1 = require("./mapFn");
17
9
  Object.defineProperty(exports, "mapFn", { enumerable: true, get: function () { return mapFn_1.mapFn; } });
18
- const mapFnAsMap_1 = require("./mapFnAsMap");
10
+ var mapFnAsMap_1 = require("./mapFnAsMap");
19
11
  Object.defineProperty(exports, "mapFnAsMap", { enumerable: true, get: function () { return mapFnAsMap_1.mapFnAsMap; } });
20
- const mapKey_1 = require("./mapKey");
12
+ var mapKey_1 = require("./mapKey");
21
13
  Object.defineProperty(exports, "mapKey", { enumerable: true, get: function () { return mapKey_1.mapKey; } });
22
- const mapKeyAsMap_1 = require("./mapKeyAsMap");
14
+ var mapKeyAsMap_1 = require("./mapKeyAsMap");
23
15
  Object.defineProperty(exports, "mapKeyAsMap", { enumerable: true, get: function () { return mapKeyAsMap_1.mapKeyAsMap; } });
24
- const mapPrimitive_1 = require("./mapPrimitive");
16
+ var mapPrimitive_1 = require("./mapPrimitive");
25
17
  Object.defineProperty(exports, "mapPrimitive", { enumerable: true, get: function () { return mapPrimitive_1.mapPrimitive; } });
26
- const groupBy_1 = require("./groupBy");
18
+ var groupBy_1 = require("./groupBy");
27
19
  Object.defineProperty(exports, "groupBy", { enumerable: true, get: function () { return groupBy_1.groupBy; } });
28
- const shuffle_1 = require("./shuffle");
20
+ var shuffle_1 = require("./shuffle");
29
21
  Object.defineProperty(exports, "shuffle", { enumerable: true, get: function () { return shuffle_1.shuffle; } });
30
- const split_1 = require("./split");
22
+ var split_1 = require("./split");
31
23
  Object.defineProperty(exports, "split", { enumerable: true, get: function () { return split_1.split; } });
32
- const sort_1 = require("./sort");
24
+ var sort_1 = require("./sort");
33
25
  Object.defineProperty(exports, "sort", { enumerable: true, get: function () { return sort_1.sort; } });
26
+ var is_1 = require("./is");
27
+ Object.defineProperty(exports, "isArray", { enumerable: true, get: function () { return is_1.isArray; } });
28
+ var isNotEmpty_1 = require("./isNotEmpty");
29
+ Object.defineProperty(exports, "isNotEmptyArray", { enumerable: true, get: function () { return isNotEmpty_1.isNotEmptyArray; } });
30
+ var isNotEmpty_2 = require("./isNotEmpty");
31
+ Object.defineProperty(exports, "isNeArray", { enumerable: true, get: function () { return isNotEmpty_2.isNotEmptyArray; } });
package/array/mapFn.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- type MapOptions = {
1
+ type MapOptions<T, U = T> = {
2
2
  /**
3
3
  * Allow merging existing keys or not, if not keys will be overriden if they exist
4
4
  * (default=false)
@@ -11,6 +11,11 @@ type MapOptions = {
11
11
  * {12: {uid: 12, b: 'ho'}}
12
12
  */
13
13
  merge?: boolean;
14
+ /**
15
+ * A custom transform function that is applied to each element before mapping.
16
+ * Its output type `U` becomes the value type of the resulting map.
17
+ */
18
+ transform_fn?: (el: T) => U;
14
19
  };
15
20
  type MapFn<T extends Record<string, any>> = (entry: T) => (string | number | boolean);
16
21
  /**
@@ -26,5 +31,5 @@ type MapFn<T extends Record<string, any>> = (entry: T) => (string | number | boo
26
31
  * @param {MapFn} fn - Handler function which is run for each of the objects and should return a string or number
27
32
  * @param {MapOptions?} opts - Options object to override built-in defaults
28
33
  */
29
- declare function mapFn<T extends Record<string, any>>(arr: T[], fn: MapFn<T>, opts?: MapOptions): Record<string, T>;
34
+ declare function mapFn<T extends Record<string, any>, U extends Record<string, any> = T>(arr: T[], fn: MapFn<T>, opts?: MapOptions<T, U>): Record<string, U>;
30
35
  export { mapFn, mapFn as default };
package/array/mapFn.js CHANGED
@@ -8,16 +8,17 @@ function mapFn(arr, fn, opts) {
8
8
  typeof fn !== 'function')
9
9
  return {};
10
10
  const MERGE = opts?.merge === true;
11
+ const TRANSFORM_FN = opts?.transform_fn;
11
12
  const map = {};
12
13
  for (let i = 0; i < arr.length; i++) {
13
14
  const el = arr[i];
14
15
  if (Object.prototype.toString.call(el) !== '[object Object]')
15
16
  continue;
16
17
  let hash = fn(el);
17
- if (Number.isFinite(hash) ||
18
- (typeof hash === 'string' && hash.length)) {
18
+ if (Number.isFinite(hash) || (typeof hash === 'string' && hash.length)) {
19
19
  hash = hash + '';
20
- map[hash] = MERGE && hash in map ? (0, merge_1.merge)(map[hash], el, { union: true }) : el;
20
+ const transformed = TRANSFORM_FN ? TRANSFORM_FN(el) : el;
21
+ map[hash] = MERGE && hash in map ? (0, merge_1.merge)(map[hash], transformed, { union: true }) : transformed;
21
22
  }
22
23
  }
23
24
  return map;
@@ -1,4 +1,4 @@
1
- type MapOptions = {
1
+ type MapOptions<T, U = T> = {
2
2
  /**
3
3
  * Allow merging existing keys or not, if not keys will be overriden if they exist
4
4
  * (default=false)
@@ -11,6 +11,11 @@ type MapOptions = {
11
11
  * {12: {uid: 12, b: 'ho'}}
12
12
  */
13
13
  merge?: boolean;
14
+ /**
15
+ * A custom transform function that is applied to each element before mapping.
16
+ * Its output type `U` becomes the value type of the resulting map.
17
+ */
18
+ transform_fn?: (el: T) => U;
14
19
  };
15
20
  type MapFn<T extends Record<string, any>> = (entry: T) => string | number | null;
16
21
  /**
@@ -29,5 +34,5 @@ type MapFn<T extends Record<string, any>> = (entry: T) => string | number | null
29
34
  * @param {MapFn} fn - Handler function which is run for each of the objects and should return a string or number
30
35
  * @param {MapOptions?} opts - Options object to override built-in defaults
31
36
  */
32
- declare function mapFnAsMap<T extends Record<string, any>, TFN extends MapFn<T>>(arr: T[], fn: TFN, opts?: MapOptions): Map<NonNullable<ReturnType<TFN>>, T>;
37
+ declare function mapFnAsMap<T extends Record<string, any>, TFN extends MapFn<T>, U extends Record<string, any> = T>(arr: T[], fn: TFN, opts?: MapOptions<T, U>): Map<NonNullable<ReturnType<TFN>>, U>;
33
38
  export { mapFnAsMap, mapFnAsMap as default };
@@ -8,15 +8,17 @@ function mapFnAsMap(arr, fn, opts) {
8
8
  typeof fn !== 'function')
9
9
  return new Map();
10
10
  const MERGE = opts?.merge === true;
11
+ const TRANSFORM_FN = opts?.transform_fn;
11
12
  const map = new Map();
12
13
  for (let i = 0; i < arr.length; i++) {
13
14
  const el = arr[i];
14
15
  if (Object.prototype.toString.call(el) !== '[object Object]')
15
16
  continue;
16
17
  const hash = fn(el);
17
- if (Number.isFinite(hash) ||
18
- (typeof hash === 'string' && hash.trim().length))
19
- map.set(hash, MERGE && map.has(hash) ? (0, merge_1.merge)(map.get(hash), el, { union: true }) : el);
18
+ if (Number.isFinite(hash) || (typeof hash === 'string' && hash.trim().length)) {
19
+ const transformed = TRANSFORM_FN ? TRANSFORM_FN(el) : el;
20
+ map.set(hash, MERGE && map.has(hash) ? (0, merge_1.merge)(map.get(hash), transformed, { union: true }) : transformed);
21
+ }
20
22
  }
21
23
  return map;
22
24
  }
package/array/mapKey.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- type MapOptions<T> = {
1
+ type MapOptions<T, U = T> = {
2
2
  /**
3
3
  * Allow merging existing keys or not, if not keys will be overriden if they exist
4
4
  * (default=false)
@@ -15,6 +15,11 @@ type MapOptions<T> = {
15
15
  * Pass a custom filter function which will be run in O(n) while iterating
16
16
  */
17
17
  filter_fn?: (el: T) => boolean;
18
+ /**
19
+ * A custom transformer function to modify each element before mapping.
20
+ * Its output type `U` becomes the value type of the resulting map.
21
+ */
22
+ transform_fn?: (el: T) => U;
18
23
  };
19
24
  /**
20
25
  * Map an object array into a kv-object by passing a common key that exists on the objects. Objects for
@@ -29,5 +34,5 @@ type MapOptions<T> = {
29
34
  * @param {string} key - Key to map by
30
35
  * @param {MapOptions?} opts - Options object to override built-in defaults
31
36
  */
32
- declare function mapKey<T extends Record<string, any>, TKey extends keyof T>(arr: T[], key: TKey, opts?: MapOptions<T>): Record<string, T>;
37
+ declare function mapKey<T extends Record<string, any>, U extends Record<string, any> = T, TKey extends keyof T = string>(arr: T[], key: TKey, opts?: MapOptions<T, U>): Record<string, U>;
33
38
  export { mapKey, mapKey as default };
package/array/mapKey.js CHANGED
@@ -11,13 +11,15 @@ function mapKey(arr, key, opts) {
11
11
  return {};
12
12
  const FILTER_FN = opts?.filter_fn;
13
13
  const MERGE = opts?.merge === true;
14
+ const TRANSFORMER = opts?.transform_fn;
14
15
  const map = {};
15
16
  for (let i = 0; i < arr.length; i++) {
16
17
  const el = arr[i];
17
18
  const el_key = el?.[key];
18
- if (el_key !== undefined &&
19
- (!FILTER_FN || FILTER_FN(el)))
20
- map[el_key] = (MERGE && el_key in map ? (0, merge_1.merge)(map[el_key], el, { union: true }) : el);
19
+ if (el_key !== undefined && (!FILTER_FN || FILTER_FN(el))) {
20
+ const transformed = TRANSFORMER ? TRANSFORMER(el) : el;
21
+ map[el_key] = MERGE && el_key in map ? (0, merge_1.merge)(map[el_key], transformed, { union: true }) : transformed;
22
+ }
21
23
  }
22
24
  return map;
23
25
  }
@@ -1,4 +1,4 @@
1
- type MapOptions<T> = {
1
+ type MapOptions<T, U = T> = {
2
2
  /**
3
3
  * Allow merging existing keys or not, if not keys will be overriden if they exist
4
4
  * (default=false)
@@ -15,6 +15,11 @@ type MapOptions<T> = {
15
15
  * Pass a custom filter function which will be run in O(n) while iterating
16
16
  */
17
17
  filter_fn?: (el: T) => boolean;
18
+ /**
19
+ * A custom transformer function to modify each element before mapping.
20
+ * Its output type `U` becomes the value type of the resulting map.
21
+ */
22
+ transform_fn?: (el: T) => U;
18
23
  };
19
24
  /**
20
25
  * Map an object array into a Map by passing a common key that exists on the objects. Objects for
@@ -32,5 +37,5 @@ type MapOptions<T> = {
32
37
  * @param {string} key - Key to map by
33
38
  * @param {MapOptions?} opts - Options object to override built-in defaults
34
39
  */
35
- declare function mapKeyAsMap<T extends Record<string, any>, TKey extends keyof T>(arr: T[], key: TKey, opts?: MapOptions<T>): Map<T[TKey], T>;
40
+ declare function mapKeyAsMap<T extends Record<string, any>, U extends Record<string, any> = T, TKey extends keyof T = string>(arr: T[], key: TKey, opts?: MapOptions<T, U>): Map<T[TKey], U>;
36
41
  export { mapKeyAsMap, mapKeyAsMap as default };
@@ -11,13 +11,15 @@ function mapKeyAsMap(arr, key, opts) {
11
11
  return new Map();
12
12
  const FILTER_FN = opts?.filter_fn;
13
13
  const MERGE = opts?.merge === true;
14
+ const TRANSFORMER = opts?.transform_fn;
14
15
  const map = new Map();
15
16
  for (let i = 0; i < arr.length; i++) {
16
17
  const el = arr[i];
17
18
  const el_key = el?.[key];
18
- if (el_key !== undefined &&
19
- (!FILTER_FN || FILTER_FN(el)))
20
- map.set(el_key, MERGE && map.has(el_key) ? (0, merge_1.merge)(map.get(el_key), el, { union: true }) : el);
19
+ if (el_key !== undefined && (!FILTER_FN || FILTER_FN(el))) {
20
+ const transformed = (TRANSFORMER ? TRANSFORMER(el) : el);
21
+ map.set(el_key, MERGE && map.has(el_key) ? (0, merge_1.merge)(map.get(el_key), transformed, { union: true }) : transformed);
22
+ }
21
23
  }
22
24
  return map;
23
25
  }