@valkyriestudios/utils 12.39.0 → 12.41.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/README.md CHANGED
@@ -12,6 +12,11 @@ Zero-dependency collection of single-function utilities for common tasks
12
12
  ## Installation
13
13
  `npm install @valkyriestudios/utils`
14
14
 
15
+ ## 🧊 TriFrost is powered by utils
16
+ Many of the utilities in this package are optimized and tested as foundational building blocks of [TriFrost](https://www.trifrost.dev) — a high-performance, type-safe framework for building fullstack TypeScript applications.
17
+
18
+ If you're looking to go beyond utilities and build fully modular backends with deep type integration, check out TriFrost.
19
+
15
20
  ## Available Functions
16
21
  ### array/is(val:unknown)
17
22
  Check if a variable is of type Array
@@ -1498,7 +1503,7 @@ pick({a: 1, b: 2, c: 3}, ['a','b']); // {a: 1, b: 2}
1498
1503
  ### object/omit(obj:Object={}, keys:Array[string]=[])
1499
1504
  Returns an object with the keys provided in the keys array stripped from the provided object.
1500
1505
  ```typescript
1501
- import { omit } from "@valkyriestudios/utils/object"; /* Or @valkyriestudios/utils/object/omit; */
1506
+ import { omit } from "@valkyriestudios/utils/object";
1502
1507
  const redacted = omit({
1503
1508
  firstName: "Peter",
1504
1509
  lastName: "Vermeulen",
@@ -1519,6 +1524,89 @@ Its type will be
1519
1524
  */
1520
1525
  ```
1521
1526
 
1527
+ Also works with wildcards (take note, only as **prefix**):
1528
+ ```typescript
1529
+ import { omit } from "@valkyriestudios/utils/object";
1530
+ const redacted = omit({
1531
+ firstName: "Peter",
1532
+ lastName: "Vermeulen",
1533
+ age: 34,
1534
+ details: {
1535
+ phone: "...",
1536
+ email: "...",
1537
+ isActive: true,
1538
+ password: "...",
1539
+ },
1540
+ meta: {
1541
+ phone: "...",
1542
+ },
1543
+ }, ["age", "*.phone", "*.email", "*.password"]);
1544
+ /**
1545
+ Redacted here will be:
1546
+ {
1547
+ firstName: "Peter",
1548
+ lastName: "Vermeulen",
1549
+ details: {isActive: true},
1550
+ meta: {}
1551
+ }
1552
+ */
1553
+ ```
1554
+
1555
+ ### object/scramble(obj:Object={}, keys:Array[string]=[], options?:{replacement?:string})
1556
+ Returns an object with the keys provided in the keys array scrambled.
1557
+ ```typescript
1558
+ import { scramble } from "@valkyriestudios/utils/object";
1559
+ const redacted = scramble({
1560
+ firstName: "Peter",
1561
+ lastName: "Vermeulen",
1562
+ age: 34,
1563
+ details: {
1564
+ phone: "...",
1565
+ email: "...",
1566
+ isActive: true,
1567
+ password: "...",
1568
+ },
1569
+ }, ["age", "details.phone", "details.email", "details.password"]);
1570
+ /**
1571
+ Redacted here will be:
1572
+ {
1573
+ firstName: "Peter",
1574
+ lastName: "Vermeulen",
1575
+ age: "***",
1576
+ details: {phone: "***", email: "***", isActive: true, password: "***"}
1577
+ }
1578
+ */
1579
+ ```
1580
+
1581
+ Also works with wildcards (take note, only as **prefix**):
1582
+ ```typescript
1583
+ import { scramble } from "@valkyriestudios/utils/object";
1584
+ const redacted = scramble({
1585
+ firstName: "Peter",
1586
+ lastName: "Vermeulen",
1587
+ age: 34,
1588
+ details: {
1589
+ phone: "...",
1590
+ email: "...",
1591
+ isActive: true,
1592
+ password: "...",
1593
+ },
1594
+ meta: {
1595
+ phone: "...",
1596
+ },
1597
+ }, ["age", "*.phone", "*.email", "*.password"]);
1598
+ /**
1599
+ Redacted here will be:
1600
+ {
1601
+ firstName: "Peter",
1602
+ lastName: "Vermeulen",
1603
+ age: "***",
1604
+ details: {phone: "***", email: "***", isActive: true, password: "***"},
1605
+ meta: {phone: "***"}
1606
+ }
1607
+ */
1608
+ ```
1609
+
1522
1610
  ### object/merge(target:Object={},obj:Object|Object[]={}, opts?:{union?:boolean})
1523
1611
  Merges two objects together, with the preference over the second object.
1524
1612
  ```typescript
@@ -6,6 +6,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.format = format;
7
7
  exports.default = format;
8
8
  const convertToDate_1 = require("./convertToDate");
9
+ const isFormat_1 = require("./isFormat");
9
10
  const LRU_1 = __importDefault(require("../caching/LRU"));
10
11
  const WEEK_STARTS = {
11
12
  mon: 'mon',
@@ -45,7 +46,7 @@ function toZone(d, zone) {
45
46
  const month = d.getUTCMonth();
46
47
  const day = d.getUTCDate();
47
48
  const time = d.getTime();
48
- const daysInMonths = [31, (year % 4 === 0 && year % 100 !== 0) || (year % 400 === 0) ? 29 : 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
49
+ const daysInMonths = (year % 4 === 0 && year % 100 !== 0) || (year % 400 === 0) ? isFormat_1.MONTHS_LEAP : isFormat_1.MONTHS;
49
50
  let doy = day;
50
51
  for (let i = 0; i <= month; i++)
51
52
  doy += daysInMonths[i];
@@ -3,25 +3,26 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.MONTHS = exports.MONTHS_LEAP = void 0;
6
7
  exports.isDateFormat = isDateFormat;
7
8
  exports.default = isDateFormat;
8
9
  const LRU_1 = __importDefault(require("../caching/LRU"));
9
10
  const SPECIAL_CHARS = /[.*+?^${}()|[\]\\]/g;
10
11
  const TOKENS = [
11
12
  ['YYYY', /\d{4}/.source, (raw, context) => {
12
- context.year = parseInt(raw, 10);
13
+ context.year = raw | 0;
13
14
  return context.year > 0;
14
15
  }],
15
16
  ['MM', /(?:0[1-9]|1[0-2])/.source, (raw, context) => {
16
- context.month = parseInt(raw, 10);
17
+ context.month = raw | 0;
17
18
  return context.month >= 1 && context.month <= 12;
18
19
  }],
19
20
  ['DD', /(?:0[1-9]|[12][0-9]|3[01])/.source, (raw, context) => {
20
- context.day = parseInt(raw, 10);
21
+ context.day = raw | 0;
21
22
  return context.day >= 1 && context.day <= 31;
22
23
  }],
23
24
  ['HH', /(?:[01][0-9]|2[0-3])/.source, (raw, context) => {
24
- context.hour = parseInt(raw, 10);
25
+ context.hour = raw | 0;
25
26
  return context.hour >= 0 && context.hour <= 23;
26
27
  }],
27
28
  ['mm', /[0-5][0-9]/.source, () => true],
@@ -39,10 +40,10 @@ const TOKENS = [
39
40
  ['Z', /Z|[+-](?:0[0-9]|1[0-4]):[0-5][0-9]/.source, raw => {
40
41
  if (raw === 'Z')
41
42
  return true;
42
- let hour = parseInt(raw[1] + raw[2], 10);
43
+ let hour = (raw[1] + raw[2]) | 0;
43
44
  if (raw[0] === '-')
44
45
  hour = -hour;
45
- const minutes = parseInt(raw[4] + raw[5], 10);
46
+ const minutes = (raw[4] + raw[5]) | 0;
46
47
  if (hour === 14 || hour === -12)
47
48
  return minutes === 0;
48
49
  return hour >= -11 && hour < 14 && [0, 15, 30, 45].indexOf(minutes) >= 0;
@@ -52,6 +53,13 @@ const SPEC_ALIASES = {
52
53
  ISO: 'YYYY-MM-DDTHH:mm:ss{.SSS}Z',
53
54
  };
54
55
  const spec_pat_cache = new LRU_1.default({ max_size: 100 });
56
+ exports.MONTHS_LEAP = [31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
57
+ exports.MONTHS = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
58
+ function isValidDay(year, month, day) {
59
+ return year % 4 === 0 && (year % 100 !== 0 || year % 400 === 0)
60
+ ? day <= exports.MONTHS_LEAP[month - 1]
61
+ : day <= exports.MONTHS[month - 1];
62
+ }
55
63
  function compileSpec(spec, is_chunk = false) {
56
64
  let cached = spec_pat_cache.get(spec);
57
65
  if (cached !== undefined)
@@ -90,7 +98,7 @@ function compileSpec(spec, is_chunk = false) {
90
98
  }
91
99
  }
92
100
  }
93
- cached = { rgx: is_chunk ? RegExp(pat) : RegExp('^' + pat + '$'), tokens };
101
+ cached = { rgx: is_chunk ? new RegExp(pat) : new RegExp('^' + pat + '$'), tokens };
94
102
  spec_pat_cache.set(spec, cached);
95
103
  return cached;
96
104
  }
@@ -113,11 +121,8 @@ function isDateFormat(input, spec) {
113
121
  return false;
114
122
  }
115
123
  const { is12, day, month, year } = context;
116
- if (day && month) {
117
- const date = new Date(year || 2024, month - 1, day);
118
- if (date.getDate() !== day || date.getMonth() !== month - 1)
119
- return false;
120
- }
124
+ if (day && month && !isValidDay(year || 2024, month, day))
125
+ return false;
121
126
  if (is12 && 'hour' in context && context.hour > 11)
122
127
  return false;
123
128
  return true;
package/cjs/deep/get.js CHANGED
@@ -21,8 +21,9 @@ function deepGet(obj, path, get_parent = false) {
21
21
  if (!key)
22
22
  break;
23
23
  if (Array.isArray(node)) {
24
- if (!isNaN(Number(key))) {
25
- const ix = parseInt(key, 10);
24
+ let ix = Number(key);
25
+ if (!isNaN(ix)) {
26
+ ix = ix | 0;
26
27
  if (ix < 0 || ix > node.length - 1)
27
28
  return undefined;
28
29
  node = node[ix];
@@ -53,8 +54,9 @@ function deepGet(obj, path, get_parent = false) {
53
54
  }
54
55
  if (key) {
55
56
  if (Array.isArray(node)) {
56
- if (!isNaN(Number(key))) {
57
- const ix = parseInt(key, 10);
57
+ let ix = Number(key);
58
+ if (!isNaN(ix)) {
59
+ ix = ix | 0;
58
60
  if (ix < 0 || ix > node.length - 1)
59
61
  return undefined;
60
62
  node = node[ix];
@@ -19,7 +19,7 @@ function assign(acc, rawkey, value, single) {
19
19
  if (i < (keys_len - 1)) {
20
20
  const n_key = Array.isArray(cursor) ? Number(key) : key;
21
21
  if (!cursor[n_key])
22
- cursor[n_key] = Number.isInteger(+keys[i + 1]) ? [] : {};
22
+ cursor[n_key] = isNaN(Number(keys[i + 1])) ? {} : [];
23
23
  cursor = cursor[n_key];
24
24
  }
25
25
  else if (!(key in cursor) || (single && single.has(key))) {
@@ -50,12 +50,16 @@ function toObject(form, config) {
50
50
  const nNumber = config?.normalize_number !== false;
51
51
  const acc = {};
52
52
  if (set === null) {
53
- form.forEach((value, key) => assign(acc, key, value, single));
53
+ for (const [key, value] of form) {
54
+ assign(acc, key, value, single);
55
+ }
56
+ return acc;
54
57
  }
55
- else {
56
- form.forEach((value, key) => {
57
- if (set_guard && set.has(key))
58
- return assign(acc, key, value, single);
58
+ for (const [key, value] of form) {
59
+ if (set_guard && set.has(key)) {
60
+ assign(acc, key, value, single);
61
+ }
62
+ else {
59
63
  switch (value) {
60
64
  case 'true':
61
65
  case 'TRUE':
@@ -73,20 +77,23 @@ function toObject(form, config) {
73
77
  assign(acc, key, nNull ? null : value, single);
74
78
  break;
75
79
  default: {
76
- if (typeof value === 'string' && value.length) {
80
+ if (typeof value === 'string' && value) {
77
81
  if (nNumber) {
78
82
  const nVal = Number(value);
79
- if (!isNaN(nVal))
80
- return assign(acc, key, nVal, single);
83
+ if (!isNaN(nVal)) {
84
+ assign(acc, key, nVal, single);
85
+ continue;
86
+ }
87
+ }
88
+ if (nDate && (0, isFormat_1.isDateFormat)(value, 'ISO')) {
89
+ assign(acc, key, new Date(value), single);
90
+ continue;
81
91
  }
82
- if (nDate &&
83
- (0, isFormat_1.isDateFormat)(value, 'ISO'))
84
- return assign(acc, key, new Date(value), single);
85
92
  }
86
93
  assign(acc, key, value, single);
87
94
  }
88
95
  }
89
- });
96
+ }
90
97
  }
91
98
  return acc;
92
99
  }
@@ -36,7 +36,7 @@ function convertPart(part, min, max) {
36
36
  const set = new Set();
37
37
  if (part.indexOf('/') > -1) {
38
38
  const [base, raw_step] = part.split('/', 2);
39
- const step = parseInt(raw_step, 10);
39
+ const step = raw_step | 0;
40
40
  let start;
41
41
  let end = max;
42
42
  if (base === '*') {
@@ -44,29 +44,29 @@ function convertPart(part, min, max) {
44
44
  }
45
45
  else if (base.indexOf('-') > -1) {
46
46
  const chunks = base.split('-', 2);
47
- start = parseInt(chunks[0], 10);
48
- end = parseInt(chunks[1], 10);
47
+ start = chunks[0] | 0;
48
+ end = chunks[1] | 0;
49
49
  }
50
50
  else {
51
- start = parseInt(base, 10);
51
+ start = base | 0;
52
52
  }
53
53
  for (let i = start; i <= end; i += step)
54
54
  set.add(i);
55
55
  }
56
56
  else if (part.indexOf('-') > -1) {
57
57
  const chunks = part.split('-', 2);
58
- const start = parseInt(chunks[0], 10);
59
- const end = parseInt(chunks[1], 10);
58
+ const start = chunks[0] | 0;
59
+ const end = chunks[1] | 0;
60
60
  for (let i = start; i <= end; i++)
61
61
  set.add(i);
62
62
  }
63
63
  else if (part.indexOf(',') > -1) {
64
64
  const chunks = part.split(',');
65
65
  for (let i = 0; i < chunks.length; i++)
66
- set.add(parseInt(chunks[i], 10));
66
+ set.add(chunks[i] | 0);
67
67
  }
68
68
  else {
69
- set.add(parseInt(part, 10));
69
+ set.add(part | 0);
70
70
  }
71
71
  return set;
72
72
  }
@@ -2,33 +2,31 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.merge = merge;
4
4
  exports.default = merge;
5
- const PROTO_OBJ = '[object Object]';
6
5
  function innerMerge(target, source, UNION) {
7
6
  const origin = UNION ? source : target;
8
7
  for (const key in origin) {
9
- const t_key = target[key];
10
- const s_key = source[key];
11
- if (Object.prototype.toString.call(t_key) === PROTO_OBJ &&
12
- Object.prototype.toString.call(s_key) === PROTO_OBJ) {
13
- target[key] = innerMerge({ ...t_key }, s_key, UNION);
14
- }
15
- else {
16
- target[key] = s_key !== undefined ? s_key : t_key;
8
+ const t_val = target[key];
9
+ const s_val = source[key];
10
+ if (s_val !== undefined && t_val !== s_val) {
11
+ target[key] = Object.prototype.toString.call(t_val) === '[object Object]' &&
12
+ Object.prototype.toString.call(s_val) === '[object Object]'
13
+ ? innerMerge(t_val, s_val, UNION)
14
+ : s_val;
17
15
  }
18
16
  }
19
17
  return target;
20
18
  }
21
19
  function merge(target, source, opts = {}) {
22
- if (Object.prototype.toString.call(target) !== PROTO_OBJ)
20
+ if (Object.prototype.toString.call(target) !== '[object Object]')
23
21
  throw new Error('object/merge: Please ensure valid target/source is passed');
24
22
  const union = opts?.union === true;
25
23
  const sources = Array.isArray(source) ? source : [source];
26
24
  let acc = { ...target };
27
25
  for (let i = 0; i < sources.length; i++) {
28
26
  const el = sources[i];
29
- if (!el || Object.prototype.toString.call(el) !== PROTO_OBJ)
30
- continue;
31
- acc = innerMerge(acc, el, union);
27
+ if (el &&
28
+ Object.prototype.toString.call(el) === '[object Object]')
29
+ acc = innerMerge(acc, el, union);
32
30
  }
33
31
  return acc;
34
32
  }
@@ -2,32 +2,84 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.omit = omit;
4
4
  exports.default = omit;
5
- function innerOmit(obj, keys) {
6
- const result = { ...obj };
7
- const groups = {};
8
- for (let i = 0; i < keys.length; i++) {
9
- if (typeof keys[i] === 'string') {
10
- const [root, ...rest] = keys[i].split('.');
11
- if (rest.length) {
12
- if (!groups[root])
13
- groups[root] = [];
14
- groups[root].push(rest.join('.'));
5
+ function wildcardProp(target, source, prop) {
6
+ if (Array.isArray(target)) {
7
+ for (let i = 0; i < target.length; i++) {
8
+ const t = target[i];
9
+ const s = source[i];
10
+ if (typeof t === 'object' && t !== null && typeof s === 'object' && s !== null) {
11
+ if (t === s)
12
+ target[i] = { ...t };
13
+ wildcardProp(target[i], s, prop);
14
+ }
15
+ }
16
+ }
17
+ else if (Object.prototype.toString.call(target) === '[object Object]') {
18
+ for (const key in target) {
19
+ if (key === prop && key in source) {
20
+ delete target[key];
15
21
  }
16
22
  else {
17
- delete result[root];
23
+ const val = target[key];
24
+ const s_val = source?.[key];
25
+ if (typeof val === 'object' && typeof s_val === 'object' && val !== null && s_val !== null) {
26
+ if (val === s_val) {
27
+ target[key] = Array.isArray(val) ? [...val] : { ...val };
28
+ }
29
+ wildcardProp(target[key], s_val, prop);
30
+ }
31
+ }
32
+ }
33
+ }
34
+ }
35
+ function standardProp(target, source, path) {
36
+ const last = path.length - 1;
37
+ for (let i = 0; i < last; i++) {
38
+ const key = path[i];
39
+ const val = target[key];
40
+ const src_val = source?.[key];
41
+ if (Array.isArray(val)) {
42
+ target[key] = val.map((item, idx) => {
43
+ const src_item = src_val[idx];
44
+ if (Object.prototype.toString.call(item) === '[object Object]') {
45
+ const clone = { ...item };
46
+ standardProp(clone, src_item, path.slice(i + 1));
47
+ return clone;
48
+ }
49
+ return item;
50
+ });
51
+ return;
52
+ }
53
+ if (Object.prototype.toString.call(val) === '[object Object]') {
54
+ if (val === src_val) {
55
+ target[key] = { ...val };
18
56
  }
57
+ target = target[key];
58
+ source = src_val;
59
+ }
60
+ else {
61
+ return;
19
62
  }
20
63
  }
21
- for (const root in groups) {
22
- if (typeof result[root] === 'object' &&
23
- result[root] !== null)
24
- result[root] = innerOmit(result[root], groups[root]);
64
+ if (target && typeof target === 'object' && path[last] in source) {
65
+ delete target[path[last]];
25
66
  }
26
- return result;
27
67
  }
28
68
  function omit(obj, keys) {
29
69
  if (Object.prototype.toString.call(obj) !== '[object Object]' ||
30
70
  !Array.isArray(keys))
31
71
  throw new TypeError('Please pass an object to omit from and a keys array');
32
- return innerOmit(obj, keys);
72
+ const result = { ...obj };
73
+ for (let i = 0; i < keys.length; i++) {
74
+ const key = keys[i];
75
+ if (typeof key === 'string') {
76
+ if (key.length > 2 && key[0] === '*' && key[1] === '.') {
77
+ wildcardProp(result, obj, key.slice(2));
78
+ }
79
+ else {
80
+ standardProp(result, obj, key.split('.'));
81
+ }
82
+ }
83
+ }
84
+ return result;
33
85
  }
@@ -0,0 +1,85 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ function wildcardProp(target, source, prop, repl) {
4
+ if (Array.isArray(target)) {
5
+ for (let i = 0; i < target.length; i++) {
6
+ const t = target[i];
7
+ const s = source[i];
8
+ if (typeof t === 'object' && t !== null && typeof s === 'object' && s !== null) {
9
+ if (t === s)
10
+ target[i] = { ...t };
11
+ wildcardProp(target[i], s, prop, repl);
12
+ }
13
+ }
14
+ }
15
+ else if (Object.prototype.toString.call(target) === '[object Object]') {
16
+ for (const key in target) {
17
+ if (key === prop && key in source) {
18
+ target[key] = repl;
19
+ }
20
+ else {
21
+ const val = target[key];
22
+ const s_val = source?.[key];
23
+ if (typeof val === 'object' && typeof s_val === 'object' && val !== null && s_val !== null) {
24
+ if (val === s_val) {
25
+ target[key] = Array.isArray(val) ? [...val] : { ...val };
26
+ }
27
+ wildcardProp(target[key], s_val, prop, repl);
28
+ }
29
+ }
30
+ }
31
+ }
32
+ }
33
+ function standardProp(target, source, path, repl) {
34
+ const last = path.length - 1;
35
+ for (let i = 0; i < last; i++) {
36
+ const key = path[i];
37
+ const val = target[key];
38
+ const src_val = source?.[key];
39
+ if (Array.isArray(val)) {
40
+ target[key] = val.map((item, idx) => {
41
+ const src_item = src_val[idx];
42
+ if (Object.prototype.toString.call(item) === '[object Object]') {
43
+ const clone = { ...item };
44
+ standardProp(clone, src_item, path.slice(i + 1), repl);
45
+ return clone;
46
+ }
47
+ return item;
48
+ });
49
+ return;
50
+ }
51
+ else if (Object.prototype.toString.call(val) === '[object Object]') {
52
+ if (val === src_val) {
53
+ target[key] = { ...val };
54
+ }
55
+ target = target[key];
56
+ source = src_val;
57
+ }
58
+ else {
59
+ return;
60
+ }
61
+ }
62
+ if (target && typeof target === 'object' && path[last] in source) {
63
+ target[path[last]] = repl;
64
+ }
65
+ }
66
+ function scramble(obj, keys, options) {
67
+ if (Object.prototype.toString.call(obj) !== '[object Object]' ||
68
+ !Array.isArray(keys))
69
+ throw new TypeError('Please pass an object to scramble and a keys array');
70
+ const repl = typeof options?.replacement === 'string' ? options.replacement : '***';
71
+ const result = { ...obj };
72
+ for (let i = 0; i < keys.length; i++) {
73
+ const key = keys[i];
74
+ if (typeof key === 'string') {
75
+ if (key.length > 2 && key[0] === '*' && key[1] === '.') {
76
+ wildcardProp(result, obj, key.slice(2), repl);
77
+ }
78
+ else {
79
+ standardProp(result, obj, key.split('.'), repl);
80
+ }
81
+ }
82
+ }
83
+ return result;
84
+ }
85
+ exports.default = scramble;
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -1,3 +1,5 @@
1
+ export declare const MONTHS_LEAP: number[];
2
+ export declare const MONTHS: number[];
1
3
  /**
2
4
  * Checks if a given string is in a particular format
3
5
  * Eg:
@@ -1,4 +1,5 @@
1
1
  import { convertToDate } from './convertToDate';
2
+ import { MONTHS, MONTHS_LEAP } from './isFormat';
2
3
  import LRU from '../caching/LRU';
3
4
  const WEEK_STARTS = {
4
5
  mon: 'mon',
@@ -38,7 +39,7 @@ function toZone(d, zone) {
38
39
  const month = d.getUTCMonth();
39
40
  const day = d.getUTCDate();
40
41
  const time = d.getTime();
41
- const daysInMonths = [31, (year % 4 === 0 && year % 100 !== 0) || (year % 400 === 0) ? 29 : 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
42
+ const daysInMonths = (year % 4 === 0 && year % 100 !== 0) || (year % 400 === 0) ? MONTHS_LEAP : MONTHS;
42
43
  let doy = day;
43
44
  for (let i = 0; i <= month; i++)
44
45
  doy += daysInMonths[i];
@@ -2,19 +2,19 @@ import LRU from '../caching/LRU';
2
2
  const SPECIAL_CHARS = /[.*+?^${}()|[\]\\]/g;
3
3
  const TOKENS = [
4
4
  ['YYYY', /\d{4}/.source, (raw, context) => {
5
- context.year = parseInt(raw, 10);
5
+ context.year = raw | 0;
6
6
  return context.year > 0;
7
7
  }],
8
8
  ['MM', /(?:0[1-9]|1[0-2])/.source, (raw, context) => {
9
- context.month = parseInt(raw, 10);
9
+ context.month = raw | 0;
10
10
  return context.month >= 1 && context.month <= 12;
11
11
  }],
12
12
  ['DD', /(?:0[1-9]|[12][0-9]|3[01])/.source, (raw, context) => {
13
- context.day = parseInt(raw, 10);
13
+ context.day = raw | 0;
14
14
  return context.day >= 1 && context.day <= 31;
15
15
  }],
16
16
  ['HH', /(?:[01][0-9]|2[0-3])/.source, (raw, context) => {
17
- context.hour = parseInt(raw, 10);
17
+ context.hour = raw | 0;
18
18
  return context.hour >= 0 && context.hour <= 23;
19
19
  }],
20
20
  ['mm', /[0-5][0-9]/.source, () => true],
@@ -32,10 +32,10 @@ const TOKENS = [
32
32
  ['Z', /Z|[+-](?:0[0-9]|1[0-4]):[0-5][0-9]/.source, raw => {
33
33
  if (raw === 'Z')
34
34
  return true;
35
- let hour = parseInt(raw[1] + raw[2], 10);
35
+ let hour = (raw[1] + raw[2]) | 0;
36
36
  if (raw[0] === '-')
37
37
  hour = -hour;
38
- const minutes = parseInt(raw[4] + raw[5], 10);
38
+ const minutes = (raw[4] + raw[5]) | 0;
39
39
  if (hour === 14 || hour === -12)
40
40
  return minutes === 0;
41
41
  return hour >= -11 && hour < 14 && [0, 15, 30, 45].indexOf(minutes) >= 0;
@@ -45,6 +45,13 @@ const SPEC_ALIASES = {
45
45
  ISO: 'YYYY-MM-DDTHH:mm:ss{.SSS}Z',
46
46
  };
47
47
  const spec_pat_cache = new LRU({ max_size: 100 });
48
+ export const MONTHS_LEAP = [31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
49
+ export const MONTHS = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
50
+ function isValidDay(year, month, day) {
51
+ return year % 4 === 0 && (year % 100 !== 0 || year % 400 === 0)
52
+ ? day <= MONTHS_LEAP[month - 1]
53
+ : day <= MONTHS[month - 1];
54
+ }
48
55
  function compileSpec(spec, is_chunk = false) {
49
56
  let cached = spec_pat_cache.get(spec);
50
57
  if (cached !== undefined)
@@ -83,7 +90,7 @@ function compileSpec(spec, is_chunk = false) {
83
90
  }
84
91
  }
85
92
  }
86
- cached = { rgx: is_chunk ? RegExp(pat) : RegExp('^' + pat + '$'), tokens };
93
+ cached = { rgx: is_chunk ? new RegExp(pat) : new RegExp('^' + pat + '$'), tokens };
87
94
  spec_pat_cache.set(spec, cached);
88
95
  return cached;
89
96
  }
@@ -106,11 +113,8 @@ function isDateFormat(input, spec) {
106
113
  return false;
107
114
  }
108
115
  const { is12, day, month, year } = context;
109
- if (day && month) {
110
- const date = new Date(year || 2024, month - 1, day);
111
- if (date.getDate() !== day || date.getMonth() !== month - 1)
112
- return false;
113
- }
116
+ if (day && month && !isValidDay(year || 2024, month, day))
117
+ return false;
114
118
  if (is12 && 'hour' in context && context.hour > 11)
115
119
  return false;
116
120
  return true;
package/esm/deep/get.js CHANGED
@@ -17,8 +17,9 @@ function deepGet(obj, path, get_parent = false) {
17
17
  if (!key)
18
18
  break;
19
19
  if (Array.isArray(node)) {
20
- if (!isNaN(Number(key))) {
21
- const ix = parseInt(key, 10);
20
+ let ix = Number(key);
21
+ if (!isNaN(ix)) {
22
+ ix = ix | 0;
22
23
  if (ix < 0 || ix > node.length - 1)
23
24
  return undefined;
24
25
  node = node[ix];
@@ -49,8 +50,9 @@ function deepGet(obj, path, get_parent = false) {
49
50
  }
50
51
  if (key) {
51
52
  if (Array.isArray(node)) {
52
- if (!isNaN(Number(key))) {
53
- const ix = parseInt(key, 10);
53
+ let ix = Number(key);
54
+ if (!isNaN(ix)) {
55
+ ix = ix | 0;
54
56
  if (ix < 0 || ix > node.length - 1)
55
57
  return undefined;
56
58
  node = node[ix];
@@ -15,7 +15,7 @@ function assign(acc, rawkey, value, single) {
15
15
  if (i < (keys_len - 1)) {
16
16
  const n_key = Array.isArray(cursor) ? Number(key) : key;
17
17
  if (!cursor[n_key])
18
- cursor[n_key] = Number.isInteger(+keys[i + 1]) ? [] : {};
18
+ cursor[n_key] = isNaN(Number(keys[i + 1])) ? {} : [];
19
19
  cursor = cursor[n_key];
20
20
  }
21
21
  else if (!(key in cursor) || (single && single.has(key))) {
@@ -46,12 +46,16 @@ function toObject(form, config) {
46
46
  const nNumber = config?.normalize_number !== false;
47
47
  const acc = {};
48
48
  if (set === null) {
49
- form.forEach((value, key) => assign(acc, key, value, single));
49
+ for (const [key, value] of form) {
50
+ assign(acc, key, value, single);
51
+ }
52
+ return acc;
50
53
  }
51
- else {
52
- form.forEach((value, key) => {
53
- if (set_guard && set.has(key))
54
- return assign(acc, key, value, single);
54
+ for (const [key, value] of form) {
55
+ if (set_guard && set.has(key)) {
56
+ assign(acc, key, value, single);
57
+ }
58
+ else {
55
59
  switch (value) {
56
60
  case 'true':
57
61
  case 'TRUE':
@@ -69,20 +73,23 @@ function toObject(form, config) {
69
73
  assign(acc, key, nNull ? null : value, single);
70
74
  break;
71
75
  default: {
72
- if (typeof value === 'string' && value.length) {
76
+ if (typeof value === 'string' && value) {
73
77
  if (nNumber) {
74
78
  const nVal = Number(value);
75
- if (!isNaN(nVal))
76
- return assign(acc, key, nVal, single);
79
+ if (!isNaN(nVal)) {
80
+ assign(acc, key, nVal, single);
81
+ continue;
82
+ }
83
+ }
84
+ if (nDate && isDateFormat(value, 'ISO')) {
85
+ assign(acc, key, new Date(value), single);
86
+ continue;
77
87
  }
78
- if (nDate &&
79
- isDateFormat(value, 'ISO'))
80
- return assign(acc, key, new Date(value), single);
81
88
  }
82
89
  assign(acc, key, value, single);
83
90
  }
84
91
  }
85
- });
92
+ }
86
93
  }
87
94
  return acc;
88
95
  }
@@ -33,7 +33,7 @@ function convertPart(part, min, max) {
33
33
  const set = new Set();
34
34
  if (part.indexOf('/') > -1) {
35
35
  const [base, raw_step] = part.split('/', 2);
36
- const step = parseInt(raw_step, 10);
36
+ const step = raw_step | 0;
37
37
  let start;
38
38
  let end = max;
39
39
  if (base === '*') {
@@ -41,29 +41,29 @@ function convertPart(part, min, max) {
41
41
  }
42
42
  else if (base.indexOf('-') > -1) {
43
43
  const chunks = base.split('-', 2);
44
- start = parseInt(chunks[0], 10);
45
- end = parseInt(chunks[1], 10);
44
+ start = chunks[0] | 0;
45
+ end = chunks[1] | 0;
46
46
  }
47
47
  else {
48
- start = parseInt(base, 10);
48
+ start = base | 0;
49
49
  }
50
50
  for (let i = start; i <= end; i += step)
51
51
  set.add(i);
52
52
  }
53
53
  else if (part.indexOf('-') > -1) {
54
54
  const chunks = part.split('-', 2);
55
- const start = parseInt(chunks[0], 10);
56
- const end = parseInt(chunks[1], 10);
55
+ const start = chunks[0] | 0;
56
+ const end = chunks[1] | 0;
57
57
  for (let i = start; i <= end; i++)
58
58
  set.add(i);
59
59
  }
60
60
  else if (part.indexOf(',') > -1) {
61
61
  const chunks = part.split(',');
62
62
  for (let i = 0; i < chunks.length; i++)
63
- set.add(parseInt(chunks[i], 10));
63
+ set.add(chunks[i] | 0);
64
64
  }
65
65
  else {
66
- set.add(parseInt(part, 10));
66
+ set.add(part | 0);
67
67
  }
68
68
  return set;
69
69
  }
@@ -1,30 +1,28 @@
1
- const PROTO_OBJ = '[object Object]';
2
1
  function innerMerge(target, source, UNION) {
3
2
  const origin = UNION ? source : target;
4
3
  for (const key in origin) {
5
- const t_key = target[key];
6
- const s_key = source[key];
7
- if (Object.prototype.toString.call(t_key) === PROTO_OBJ &&
8
- Object.prototype.toString.call(s_key) === PROTO_OBJ) {
9
- target[key] = innerMerge({ ...t_key }, s_key, UNION);
10
- }
11
- else {
12
- target[key] = s_key !== undefined ? s_key : t_key;
4
+ const t_val = target[key];
5
+ const s_val = source[key];
6
+ if (s_val !== undefined && t_val !== s_val) {
7
+ target[key] = Object.prototype.toString.call(t_val) === '[object Object]' &&
8
+ Object.prototype.toString.call(s_val) === '[object Object]'
9
+ ? innerMerge(t_val, s_val, UNION)
10
+ : s_val;
13
11
  }
14
12
  }
15
13
  return target;
16
14
  }
17
15
  function merge(target, source, opts = {}) {
18
- if (Object.prototype.toString.call(target) !== PROTO_OBJ)
16
+ if (Object.prototype.toString.call(target) !== '[object Object]')
19
17
  throw new Error('object/merge: Please ensure valid target/source is passed');
20
18
  const union = opts?.union === true;
21
19
  const sources = Array.isArray(source) ? source : [source];
22
20
  let acc = { ...target };
23
21
  for (let i = 0; i < sources.length; i++) {
24
22
  const el = sources[i];
25
- if (!el || Object.prototype.toString.call(el) !== PROTO_OBJ)
26
- continue;
27
- acc = innerMerge(acc, el, union);
23
+ if (el &&
24
+ Object.prototype.toString.call(el) === '[object Object]')
25
+ acc = innerMerge(acc, el, union);
28
26
  }
29
27
  return acc;
30
28
  }
@@ -1,30 +1,82 @@
1
- function innerOmit(obj, keys) {
2
- const result = { ...obj };
3
- const groups = {};
4
- for (let i = 0; i < keys.length; i++) {
5
- if (typeof keys[i] === 'string') {
6
- const [root, ...rest] = keys[i].split('.');
7
- if (rest.length) {
8
- if (!groups[root])
9
- groups[root] = [];
10
- groups[root].push(rest.join('.'));
1
+ function wildcardProp(target, source, prop) {
2
+ if (Array.isArray(target)) {
3
+ for (let i = 0; i < target.length; i++) {
4
+ const t = target[i];
5
+ const s = source[i];
6
+ if (typeof t === 'object' && t !== null && typeof s === 'object' && s !== null) {
7
+ if (t === s)
8
+ target[i] = { ...t };
9
+ wildcardProp(target[i], s, prop);
10
+ }
11
+ }
12
+ }
13
+ else if (Object.prototype.toString.call(target) === '[object Object]') {
14
+ for (const key in target) {
15
+ if (key === prop && key in source) {
16
+ delete target[key];
11
17
  }
12
18
  else {
13
- delete result[root];
19
+ const val = target[key];
20
+ const s_val = source?.[key];
21
+ if (typeof val === 'object' && typeof s_val === 'object' && val !== null && s_val !== null) {
22
+ if (val === s_val) {
23
+ target[key] = Array.isArray(val) ? [...val] : { ...val };
24
+ }
25
+ wildcardProp(target[key], s_val, prop);
26
+ }
27
+ }
28
+ }
29
+ }
30
+ }
31
+ function standardProp(target, source, path) {
32
+ const last = path.length - 1;
33
+ for (let i = 0; i < last; i++) {
34
+ const key = path[i];
35
+ const val = target[key];
36
+ const src_val = source?.[key];
37
+ if (Array.isArray(val)) {
38
+ target[key] = val.map((item, idx) => {
39
+ const src_item = src_val[idx];
40
+ if (Object.prototype.toString.call(item) === '[object Object]') {
41
+ const clone = { ...item };
42
+ standardProp(clone, src_item, path.slice(i + 1));
43
+ return clone;
44
+ }
45
+ return item;
46
+ });
47
+ return;
48
+ }
49
+ if (Object.prototype.toString.call(val) === '[object Object]') {
50
+ if (val === src_val) {
51
+ target[key] = { ...val };
14
52
  }
53
+ target = target[key];
54
+ source = src_val;
55
+ }
56
+ else {
57
+ return;
15
58
  }
16
59
  }
17
- for (const root in groups) {
18
- if (typeof result[root] === 'object' &&
19
- result[root] !== null)
20
- result[root] = innerOmit(result[root], groups[root]);
60
+ if (target && typeof target === 'object' && path[last] in source) {
61
+ delete target[path[last]];
21
62
  }
22
- return result;
23
63
  }
24
64
  function omit(obj, keys) {
25
65
  if (Object.prototype.toString.call(obj) !== '[object Object]' ||
26
66
  !Array.isArray(keys))
27
67
  throw new TypeError('Please pass an object to omit from and a keys array');
28
- return innerOmit(obj, keys);
68
+ const result = { ...obj };
69
+ for (let i = 0; i < keys.length; i++) {
70
+ const key = keys[i];
71
+ if (typeof key === 'string') {
72
+ if (key.length > 2 && key[0] === '*' && key[1] === '.') {
73
+ wildcardProp(result, obj, key.slice(2));
74
+ }
75
+ else {
76
+ standardProp(result, obj, key.split('.'));
77
+ }
78
+ }
79
+ }
80
+ return result;
29
81
  }
30
82
  export { omit, omit as default };
@@ -0,0 +1,83 @@
1
+ function wildcardProp(target, source, prop, repl) {
2
+ if (Array.isArray(target)) {
3
+ for (let i = 0; i < target.length; i++) {
4
+ const t = target[i];
5
+ const s = source[i];
6
+ if (typeof t === 'object' && t !== null && typeof s === 'object' && s !== null) {
7
+ if (t === s)
8
+ target[i] = { ...t };
9
+ wildcardProp(target[i], s, prop, repl);
10
+ }
11
+ }
12
+ }
13
+ else if (Object.prototype.toString.call(target) === '[object Object]') {
14
+ for (const key in target) {
15
+ if (key === prop && key in source) {
16
+ target[key] = repl;
17
+ }
18
+ else {
19
+ const val = target[key];
20
+ const s_val = source?.[key];
21
+ if (typeof val === 'object' && typeof s_val === 'object' && val !== null && s_val !== null) {
22
+ if (val === s_val) {
23
+ target[key] = Array.isArray(val) ? [...val] : { ...val };
24
+ }
25
+ wildcardProp(target[key], s_val, prop, repl);
26
+ }
27
+ }
28
+ }
29
+ }
30
+ }
31
+ function standardProp(target, source, path, repl) {
32
+ const last = path.length - 1;
33
+ for (let i = 0; i < last; i++) {
34
+ const key = path[i];
35
+ const val = target[key];
36
+ const src_val = source?.[key];
37
+ if (Array.isArray(val)) {
38
+ target[key] = val.map((item, idx) => {
39
+ const src_item = src_val[idx];
40
+ if (Object.prototype.toString.call(item) === '[object Object]') {
41
+ const clone = { ...item };
42
+ standardProp(clone, src_item, path.slice(i + 1), repl);
43
+ return clone;
44
+ }
45
+ return item;
46
+ });
47
+ return;
48
+ }
49
+ else if (Object.prototype.toString.call(val) === '[object Object]') {
50
+ if (val === src_val) {
51
+ target[key] = { ...val };
52
+ }
53
+ target = target[key];
54
+ source = src_val;
55
+ }
56
+ else {
57
+ return;
58
+ }
59
+ }
60
+ if (target && typeof target === 'object' && path[last] in source) {
61
+ target[path[last]] = repl;
62
+ }
63
+ }
64
+ function scramble(obj, keys, options) {
65
+ if (Object.prototype.toString.call(obj) !== '[object Object]' ||
66
+ !Array.isArray(keys))
67
+ throw new TypeError('Please pass an object to scramble and a keys array');
68
+ const repl = typeof options?.replacement === 'string' ? options.replacement : '***';
69
+ const result = { ...obj };
70
+ for (let i = 0; i < keys.length; i++) {
71
+ const key = keys[i];
72
+ if (typeof key === 'string') {
73
+ if (key.length > 2 && key[0] === '*' && key[1] === '.') {
74
+ wildcardProp(result, obj, key.slice(2), repl);
75
+ }
76
+ else {
77
+ standardProp(result, obj, key.split('.'), repl);
78
+ }
79
+ }
80
+ }
81
+ return result;
82
+ }
83
+ export default scramble;
@@ -0,0 +1 @@
1
+ export {};
package/index.d.ts CHANGED
@@ -205,6 +205,12 @@ declare module "caching/LRU" {
205
205
  }
206
206
  export { LRUCache, LRUCache as default };
207
207
  }
208
+ declare module "date/isFormat" {
209
+ export const MONTHS_LEAP: number[];
210
+ export const MONTHS: number[];
211
+ function isDateFormat(input: unknown, spec: string): input is string;
212
+ export { isDateFormat, isDateFormat as default };
213
+ }
208
214
  declare module "date/format" {
209
215
  const WEEK_STARTS: {
210
216
  readonly mon: "mon";
@@ -262,10 +268,6 @@ declare module "date/toUTC" {
262
268
  function toUTC(val: Date): Date;
263
269
  export { toUTC, toUTC as default };
264
270
  }
265
- declare module "date/isFormat" {
266
- function isDateFormat(input: unknown, spec: string): input is string;
267
- export { isDateFormat, isDateFormat as default };
268
- }
269
271
  declare module "date/index" {
270
272
  export { addUTC } from "date/addUTC";
271
273
  export { convertToDate } from "date/convertToDate";
@@ -377,13 +379,25 @@ declare module "deep/get" {
377
379
  function deepGet<T extends ObjectType | ArrayType, P extends string>(obj: T, path: P, get_parent?: boolean): DeepGetResult<T, P> | undefined;
378
380
  export { deepGet, deepGet as default };
379
381
  }
382
+ declare module "object/types" {
383
+ export type DottedKeys<T> = (T extends Record<string, any> ? {
384
+ [K in keyof T & string]: T[K] extends Record<string, any> ? K | `${K}.${DottedKeys<T[K]>}` : K;
385
+ }[keyof T & string] : string) & string;
386
+ export type DottedKeysWithArray<T> = (T extends Record<string, any> ? {
387
+ [K in keyof T & string]: T[K] extends (infer U)[] ? U extends Record<string, any> ? K | `${K}.${DottedKeysWithArray<U>}` : K : T[K] extends Record<string, any> ? K | `${K}.${DottedKeysWithArray<T[K]>}` : K;
388
+ }[keyof T & string] : string) & string;
389
+ export type ExpandWildcard<T, P extends string> = P extends `*.${infer Key}` ? {
390
+ [K in keyof T]: T[K] extends Record<string, any> ? `${K & string}.${Key}` : never;
391
+ }[keyof T] : P;
392
+ export type ExpandWildcardWithArray<T, P extends string> = P extends `*.${infer Key}` ? {
393
+ [K in keyof T]: T[K] extends (infer U)[] ? U extends Record<string, any> ? `${K & string}.${Key}` : never : T[K] extends Record<string, any> ? `${K & string}.${Key}` : never;
394
+ }[keyof T] : P;
395
+ }
380
396
  declare module "object/pick" {
397
+ import { type DottedKeys } from "object/types";
381
398
  type ObjectType = {
382
399
  [key: string]: any;
383
400
  };
384
- type DottedKeys<T> = (T extends ObjectType ? {
385
- [K in keyof T & string]: T[K] extends ObjectType ? K | `${K}.${DottedKeys<T[K]>}` : K;
386
- }[keyof T & string] : string) & string;
387
401
  type PickFromObject<T, K extends string> = K extends `${infer Key}.${infer Rest}` ? Key extends keyof T ? T[Key] extends ObjectType ? {
388
402
  [P in Key]: PickFromObject<T[Key], Rest>;
389
403
  } : object : object : K extends keyof T ? {
@@ -394,16 +408,11 @@ declare module "object/pick" {
394
408
  export { pick, pick as default };
395
409
  }
396
410
  declare module "object/omit" {
397
- type ObjectType = {
398
- [key: string]: any;
399
- };
400
- type DottedKeys<T> = (T extends ObjectType ? {
401
- [K in keyof T & string]: T[K] extends ObjectType ? K | `${K}.${DottedKeys<T[K]>}` : K;
402
- }[keyof T & string] : string) & string;
403
- type OmitFromObject<T, K extends string> = K extends `${infer Key}.${infer Rest}` ? Key extends keyof T ? T[Key] extends ObjectType ? {
411
+ import { type DottedKeysWithArray, type ExpandWildcardWithArray } from "object/types";
412
+ type OmitFromObject<T, K extends string> = T extends (infer U)[] ? OmitFromObject<U, K>[] : T extends Record<string, any> ? K extends `${infer Key}.${infer Rest}` ? Key extends keyof T ? T[Key] extends Record<string, any> | any[] ? {
404
413
  [P in keyof T]: P extends Key ? OmitFromObject<T[Key], Rest> : T[P];
405
- } : T : T : Omit<T, K>;
406
- function omit<T extends Record<string, any>, K extends readonly DottedKeys<T>[]>(obj: T, keys: K): OmitFromObject<T, K[number]>;
414
+ } : T : T : Omit<T, K> : T;
415
+ function omit<T extends Record<string, any>, K extends readonly (DottedKeysWithArray<T> | `*.${string}`)[]>(obj: T, keys: K): OmitFromObject<T, ExpandWildcardWithArray<T, K[number]>>;
407
416
  export { omit, omit as default };
408
417
  }
409
418
  declare module "object/index" {
@@ -775,3 +784,10 @@ declare module "modules/index" {
775
784
  export { PubSub } from "modules/PubSub";
776
785
  export { Scheduler } from "modules/Scheduler";
777
786
  }
787
+ declare module "object/scramble" {
788
+ import { type DottedKeysWithArray } from "object/types";
789
+ function scramble<T extends Record<string, any>, K extends readonly (DottedKeysWithArray<T> | `*.${string}`)[]>(obj: T, keys: K, options?: {
790
+ replacement?: string;
791
+ }): T;
792
+ export default scramble;
793
+ }
package/object/omit.d.ts CHANGED
@@ -1,17 +1,16 @@
1
- type ObjectType = {
2
- [key: string]: any;
3
- };
4
- type DottedKeys<T> = (T extends ObjectType ? {
5
- [K in keyof T & string]: T[K] extends ObjectType ? K | `${K}.${DottedKeys<T[K]>}` : K;
6
- }[keyof T & string] : string) & string;
7
- type OmitFromObject<T, K extends string> = K extends `${infer Key}.${infer Rest}` ? Key extends keyof T ? T[Key] extends ObjectType ? {
1
+ import { type DottedKeysWithArray, type ExpandWildcardWithArray } from './types';
2
+ type OmitFromObject<T, K extends string> = T extends (infer U)[] ? OmitFromObject<U, K>[] : T extends Record<string, any> ? K extends `${infer Key}.${infer Rest}` ? Key extends keyof T ? T[Key] extends Record<string, any> | any[] ? {
8
3
  [P in keyof T]: P extends Key ? OmitFromObject<T[Key], Rest> : T[P];
9
- } : T : T : Omit<T, K>;
4
+ } : T : T : Omit<T, K> : T;
10
5
  /**
11
- * Returns a new object with the keys omitted from the passed object, handling nested keys recursively
6
+ * Returns a new object with the provided keys omitted
7
+ * Supports:
8
+ * - omitting keys in nested arrays
9
+ * - omitting wildcard patterns: '*.password'
10
+ * - omitting standard keys
12
11
  *
13
12
  * @param {Record<string, any>} obj - Object to omit from
14
13
  * @param {string[]} keys - Array of keys to omit from object
15
14
  */
16
- declare function omit<T extends Record<string, any>, K extends readonly DottedKeys<T>[]>(obj: T, keys: K): OmitFromObject<T, K[number]>;
15
+ declare function omit<T extends Record<string, any>, K extends readonly (DottedKeysWithArray<T> | `*.${string}`)[]>(obj: T, keys: K): OmitFromObject<T, ExpandWildcardWithArray<T, K[number]>>;
17
16
  export { omit, omit as default };
package/object/pick.d.ts CHANGED
@@ -1,9 +1,7 @@
1
+ import { type DottedKeys } from './types';
1
2
  type ObjectType = {
2
3
  [key: string]: any;
3
4
  };
4
- type DottedKeys<T> = (T extends ObjectType ? {
5
- [K in keyof T & string]: T[K] extends ObjectType ? K | `${K}.${DottedKeys<T[K]>}` : K;
6
- }[keyof T & string] : string) & string;
7
5
  type PickFromObject<T, K extends string> = K extends `${infer Key}.${infer Rest}` ? Key extends keyof T ? T[Key] extends ObjectType ? {
8
6
  [P in Key]: PickFromObject<T[Key], Rest>;
9
7
  } : object : object : K extends keyof T ? {
@@ -0,0 +1,15 @@
1
+ import { type DottedKeysWithArray } from './types';
2
+ /**
3
+ * Returns a new object with the provided keys obfuscated
4
+ * Supports:
5
+ * - scrambling keys in nested arrays
6
+ * - scrambling wildcard patterns: '*.password'
7
+ * - scrambling standard keys
8
+ *
9
+ * @param {Record<string, any>} obj - Object to omit from
10
+ * @param {string[]} keys - Array of keys to omit from object
11
+ */
12
+ declare function scramble<T extends Record<string, any>, K extends readonly (DottedKeysWithArray<T> | `*.${string}`)[]>(obj: T, keys: K, options?: {
13
+ replacement?: string;
14
+ }): T;
15
+ export default scramble;
@@ -0,0 +1,12 @@
1
+ export type DottedKeys<T> = (T extends Record<string, any> ? {
2
+ [K in keyof T & string]: T[K] extends Record<string, any> ? K | `${K}.${DottedKeys<T[K]>}` : K;
3
+ }[keyof T & string] : string) & string;
4
+ export type DottedKeysWithArray<T> = (T extends Record<string, any> ? {
5
+ [K in keyof T & string]: T[K] extends (infer U)[] ? U extends Record<string, any> ? K | `${K}.${DottedKeysWithArray<U>}` : K : T[K] extends Record<string, any> ? K | `${K}.${DottedKeysWithArray<T[K]>}` : K;
6
+ }[keyof T & string] : string) & string;
7
+ export type ExpandWildcard<T, P extends string> = P extends `*.${infer Key}` ? {
8
+ [K in keyof T]: T[K] extends Record<string, any> ? `${K & string}.${Key}` : never;
9
+ }[keyof T] : P;
10
+ export type ExpandWildcardWithArray<T, P extends string> = P extends `*.${infer Key}` ? {
11
+ [K in keyof T]: T[K] extends (infer U)[] ? U extends Record<string, any> ? `${K & string}.${Key}` : never : T[K] extends Record<string, any> ? `${K & string}.${Key}` : never;
12
+ }[keyof T] : P;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@valkyriestudios/utils",
3
- "version": "12.39.0",
3
+ "version": "12.41.0",
4
4
  "description": "A collection of single-function utilities for common tasks",
5
5
  "author": {
6
6
  "name": "Peter Vermeulen",
@@ -457,6 +457,11 @@
457
457
  "import": "./esm/object/pick.js",
458
458
  "require": "./cjs/object/pick.js"
459
459
  },
460
+ "./object/scramble": {
461
+ "types": "./object/scramble.d.ts",
462
+ "import": "./esm/object/scramble.js",
463
+ "require": "./cjs/object/scramble.js"
464
+ },
460
465
  "./regexp": {
461
466
  "types": "./regexp/index.d.ts",
462
467
  "import": "./esm/regexp/index.js",
@@ -509,12 +514,12 @@
509
514
  }
510
515
  },
511
516
  "devDependencies": {
512
- "@types/node": "^22.15.24",
513
- "@vitest/coverage-v8": "^3.1.4",
517
+ "@types/node": "^22.15.30",
518
+ "@vitest/coverage-v8": "^3.2.2",
514
519
  "esbuild-register": "^3.6.0",
515
- "eslint": "^9.27.0",
520
+ "eslint": "^9.28.0",
516
521
  "typescript": "^5.8.3",
517
- "typescript-eslint": "^8.33.0",
518
- "vitest": "^3.1.4"
522
+ "typescript-eslint": "^8.33.1",
523
+ "vitest": "^3.2.2"
519
524
  }
520
525
  }