@naturalcycles/js-lib 14.213.0 → 14.215.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/dist/array/array.util.d.ts +3 -0
  2. package/dist/array/array.util.js +3 -0
  3. package/dist/decorators/asyncMemo.decorator.d.ts +4 -14
  4. package/dist/decorators/asyncMemo.decorator.js +5 -11
  5. package/dist/decorators/memo.decorator.d.ts +0 -13
  6. package/dist/decorators/memo.decorator.js +1 -11
  7. package/dist/decorators/memoFn.js +1 -13
  8. package/dist/decorators/memoFnAsync.js +1 -13
  9. package/dist/decorators/memoSimple.decorator.d.ts +0 -3
  10. package/dist/decorators/memoSimple.decorator.js +1 -8
  11. package/dist/index.d.ts +2 -0
  12. package/dist/index.js +2 -0
  13. package/dist/object/deepEquals.d.ts +58 -6
  14. package/dist/object/deepEquals.js +146 -52
  15. package/dist/object/map2.d.ts +15 -0
  16. package/dist/object/map2.js +25 -0
  17. package/dist/object/object.util.d.ts +3 -1
  18. package/dist/object/set2.d.ts +11 -0
  19. package/dist/object/set2.js +19 -0
  20. package/dist/string/stringify.js +6 -0
  21. package/dist-esm/array/array.util.js +3 -0
  22. package/dist-esm/decorators/asyncMemo.decorator.js +6 -12
  23. package/dist-esm/decorators/memo.decorator.js +2 -12
  24. package/dist-esm/decorators/memoFn.js +1 -13
  25. package/dist-esm/decorators/memoFnAsync.js +1 -13
  26. package/dist-esm/decorators/memoSimple.decorator.js +2 -9
  27. package/dist-esm/index.js +2 -0
  28. package/dist-esm/object/deepEquals.js +142 -49
  29. package/dist-esm/object/map2.js +21 -0
  30. package/dist-esm/object/set2.js +15 -0
  31. package/dist-esm/string/stringify.js +6 -0
  32. package/package.json +1 -4
  33. package/src/array/array.util.ts +3 -0
  34. package/src/decorators/asyncMemo.decorator.ts +5 -42
  35. package/src/decorators/memo.decorator.ts +1 -37
  36. package/src/decorators/memoFn.ts +0 -18
  37. package/src/decorators/memoFnAsync.ts +0 -18
  38. package/src/decorators/memoSimple.decorator.ts +2 -20
  39. package/src/index.ts +2 -0
  40. package/src/object/deepEquals.ts +136 -45
  41. package/src/object/map2.ts +25 -0
  42. package/src/object/object.util.ts +3 -1
  43. package/src/object/set2.ts +18 -0
  44. package/src/string/stringify.ts +6 -0
  45. package/dist/lodash.types.d.ts +0 -4
  46. package/dist/lodash.types.js +0 -2
  47. package/dist-esm/lodash.types.js +0 -1
  48. package/src/lodash.types.ts +0 -6
@@ -1,7 +1,6 @@
1
1
  import type { CommonLogger } from '../log/commonLogger'
2
- import { _since } from '../time/time.util'
3
2
  import type { AnyObject } from '../types'
4
- import { _getArgsSignature, _getMethodSignature, _getTargetMethodSignature } from './decorator.util'
3
+ import { _getTargetMethodSignature } from './decorator.util'
5
4
  import type { AsyncMemoCache } from './memo.util'
6
5
  import { jsonMemoSerializer, MapMemoCache } from './memo.util'
7
6
 
@@ -28,23 +27,6 @@ export interface AsyncMemoOptions {
28
27
  */
29
28
  cacheRejections?: boolean
30
29
 
31
- /**
32
- * Default to false
33
- */
34
- logHit?: boolean
35
-
36
- /**
37
- * Default to false
38
- */
39
- logMiss?: boolean
40
-
41
- /**
42
- * Set to `false` to skip logging method arguments.
43
- *
44
- * Defaults to true.
45
- */
46
- logArgs?: boolean
47
-
48
30
  /**
49
31
  * Default to `console`
50
32
  */
@@ -54,6 +36,10 @@ export interface AsyncMemoOptions {
54
36
  /**
55
37
  * Like @_Memo, but allowing async MemoCache implementation.
56
38
  *
39
+ * Important: it awaits the method to return the result before caching it.
40
+ *
41
+ * todo: test for "swarm requests", it should return "the same promise" and not cause a swarm origin hit
42
+ *
57
43
  * Method CANNOT return `undefined`, as undefined will always be treated as cache MISS and retried.
58
44
  * Return `null` instead (it'll be cached).
59
45
  */
@@ -71,9 +57,6 @@ export const _AsyncMemo =
71
57
  const cache = new Map<AnyObject, AsyncMemoCache>()
72
58
 
73
59
  const {
74
- logHit = false,
75
- logMiss = false,
76
- logArgs = true,
77
60
  logger = console,
78
61
  cacheFactory = () => new MapMemoCache(),
79
62
  cacheKeyFn = jsonMemoSerializer,
@@ -105,15 +88,6 @@ export const _AsyncMemo =
105
88
 
106
89
  if (value !== undefined) {
107
90
  // hit!
108
- if (logHit) {
109
- logger.log(
110
- `${_getMethodSignature(ctx, keyStr)}(${_getArgsSignature(
111
- args,
112
- logArgs,
113
- )}) @_AsyncMemo hit`,
114
- )
115
- }
116
-
117
91
  if (value instanceof Error) {
118
92
  throw value
119
93
  }
@@ -122,8 +96,6 @@ export const _AsyncMemo =
122
96
  }
123
97
 
124
98
  // Here we know it's a MISS, let's execute the real method
125
- const started = Date.now()
126
-
127
99
  try {
128
100
  value = await originalFn.apply(ctx, args)
129
101
 
@@ -154,15 +126,6 @@ export const _AsyncMemo =
154
126
  }
155
127
 
156
128
  throw err
157
- } finally {
158
- if (logMiss) {
159
- logger.log(
160
- `${_getMethodSignature(ctx, keyStr)}(${_getArgsSignature(
161
- args,
162
- logArgs,
163
- )}) @_AsyncMemo miss (${_since(started)})`,
164
- )
165
- }
166
129
  }
167
130
  } as any
168
131
  ;(descriptor.value as any).dropCache = async () => {
@@ -1,7 +1,6 @@
1
1
  import type { CommonLogger } from '../log/commonLogger'
2
- import { _since } from '../time/time.util'
3
2
  import type { AnyObject } from '../types'
4
- import { _getArgsSignature, _getMethodSignature, _getTargetMethodSignature } from './decorator.util'
3
+ import { _getTargetMethodSignature } from './decorator.util'
5
4
  import type { MemoCache } from './memo.util'
6
5
  import { jsonMemoSerializer, MapMemoCache } from './memo.util'
7
6
 
@@ -27,21 +26,6 @@ export interface MemoOptions {
27
26
  */
28
27
  cacheErrors?: boolean
29
28
 
30
- /**
31
- * Default to false
32
- */
33
- logHit?: boolean
34
- /**
35
- * Default to false
36
- */
37
- logMiss?: boolean
38
-
39
- /**
40
- * Defaults to true.
41
- * Set to false to skip logging method arguments.
42
- */
43
- logArgs?: boolean
44
-
45
29
  /**
46
30
  * Default to `console`
47
31
  */
@@ -87,9 +71,6 @@ export const _Memo =
87
71
  const cache = new Map<AnyObject, MemoCache>()
88
72
 
89
73
  const {
90
- logHit = false,
91
- logMiss = false,
92
- logArgs = true,
93
74
  logger = console,
94
75
  cacheFactory = () => new MapMemoCache(),
95
76
  cacheKeyFn = jsonMemoSerializer,
@@ -107,12 +88,6 @@ export const _Memo =
107
88
  if (!cache.has(ctx)) {
108
89
  cache.set(ctx, cacheFactory())
109
90
  } else if (cache.get(ctx)!.has(cacheKey)) {
110
- if (logHit) {
111
- logger.log(
112
- `${_getMethodSignature(ctx, keyStr)}(${_getArgsSignature(args, logArgs)}) @_Memo hit`,
113
- )
114
- }
115
-
116
91
  value = cache.get(ctx)!.get(cacheKey)
117
92
 
118
93
  if (value instanceof Error) {
@@ -122,8 +97,6 @@ export const _Memo =
122
97
  return value
123
98
  }
124
99
 
125
- const started = Date.now()
126
-
127
100
  try {
128
101
  value = originalFn.apply(ctx, args)
129
102
 
@@ -144,15 +117,6 @@ export const _Memo =
144
117
  }
145
118
 
146
119
  throw err
147
- } finally {
148
- if (logMiss) {
149
- logger.log(
150
- `${_getMethodSignature(ctx, keyStr)}(${_getArgsSignature(
151
- args,
152
- logArgs,
153
- )}) @_Memo miss (${_since(started)})`,
154
- )
155
- }
156
120
  }
157
121
  } as any
158
122
  ;(descriptor.value as any).dropCache = () => {
@@ -1,5 +1,3 @@
1
- import { _since } from '../time/time.util'
2
- import { _getArgsSignature } from './decorator.util'
3
1
  import type { MemoOptions } from './memo.decorator'
4
2
  import type { MemoCache } from './memo.util'
5
3
  import { jsonMemoSerializer, MapMemoCache } from './memo.util'
@@ -18,9 +16,6 @@ export function _memoFn<T extends (...args: any[]) => any>(
18
16
  opt: MemoOptions = {},
19
17
  ): T & MemoizedFunction {
20
18
  const {
21
- logHit = false,
22
- logMiss = false,
23
- logArgs = true,
24
19
  logger = console,
25
20
  cacheErrors = true,
26
21
  cacheFactory = () => new MapMemoCache(),
@@ -28,7 +23,6 @@ export function _memoFn<T extends (...args: any[]) => any>(
28
23
  } = opt
29
24
 
30
25
  const cache = cacheFactory()
31
- const fnName = fn.name
32
26
 
33
27
  const memoizedFn = function (this: any, ...args: any[]): T {
34
28
  const ctx = this
@@ -36,10 +30,6 @@ export function _memoFn<T extends (...args: any[]) => any>(
36
30
  let value: any
37
31
 
38
32
  if (cache.has(cacheKey)) {
39
- if (logHit) {
40
- logger.log(`${fnName}(${_getArgsSignature(args, logArgs)}) memoFn hit`)
41
- }
42
-
43
33
  value = cache.get(cacheKey)
44
34
 
45
35
  if (value instanceof Error) {
@@ -49,8 +39,6 @@ export function _memoFn<T extends (...args: any[]) => any>(
49
39
  return value
50
40
  }
51
41
 
52
- const started = Date.now()
53
-
54
42
  try {
55
43
  value = fn.apply(ctx, args)
56
44
 
@@ -71,12 +59,6 @@ export function _memoFn<T extends (...args: any[]) => any>(
71
59
  }
72
60
 
73
61
  throw err
74
- } finally {
75
- if (logMiss) {
76
- logger.log(
77
- `${fnName}(${_getArgsSignature(args, logArgs)}) memoFn miss (${_since(started)})`,
78
- )
79
- }
80
62
  }
81
63
  }
82
64
 
@@ -1,6 +1,4 @@
1
- import { _since } from '../time/time.util'
2
1
  import type { AsyncMemoOptions } from './asyncMemo.decorator'
3
- import { _getArgsSignature } from './decorator.util'
4
2
  import type { AsyncMemoCache } from './memo.util'
5
3
  import { jsonMemoSerializer, MapMemoCache } from './memo.util'
6
4
 
@@ -17,9 +15,6 @@ export function _memoFnAsync<T extends (...args: any[]) => Promise<any>>(
17
15
  opt: AsyncMemoOptions = {},
18
16
  ): T & MemoizedAsyncFunction {
19
17
  const {
20
- logHit = false,
21
- logMiss = false,
22
- logArgs = true,
23
18
  logger = console,
24
19
  cacheRejections = true,
25
20
  cacheFactory = () => new MapMemoCache(),
@@ -27,7 +22,6 @@ export function _memoFnAsync<T extends (...args: any[]) => Promise<any>>(
27
22
  } = opt
28
23
 
29
24
  const cache = cacheFactory()
30
- const fnName = fn.name
31
25
 
32
26
  const memoizedFn = async function (this: any, ...args: any[]): Promise<any> {
33
27
  const ctx = this
@@ -41,10 +35,6 @@ export function _memoFnAsync<T extends (...args: any[]) => Promise<any>>(
41
35
  }
42
36
 
43
37
  if (value !== undefined) {
44
- if (logHit) {
45
- logger.log(`${fnName}(${_getArgsSignature(args, logArgs)}) memoFnAsync hit`)
46
- }
47
-
48
38
  if (value instanceof Error) {
49
39
  throw value
50
40
  }
@@ -52,8 +42,6 @@ export function _memoFnAsync<T extends (...args: any[]) => Promise<any>>(
52
42
  return value
53
43
  }
54
44
 
55
- const started = Date.now()
56
-
57
45
  try {
58
46
  value = await fn.apply(ctx, args)
59
47
 
@@ -78,12 +66,6 @@ export function _memoFnAsync<T extends (...args: any[]) => Promise<any>>(
78
66
  }
79
67
 
80
68
  throw err
81
- } finally {
82
- if (logMiss) {
83
- logger.log(
84
- `${fnName}(${_getArgsSignature(args, logArgs)}) memoFnAsync miss (${_since(started)})`,
85
- )
86
- }
87
69
  }
88
70
  }
89
71
 
@@ -12,14 +12,11 @@ Benchmark shows similar perf for ObjectCache and MapCache.
12
12
  */
13
13
 
14
14
  import type { CommonLogger } from '../log/commonLogger'
15
- import { _getArgsSignature, _getTargetMethodSignature } from './decorator.util'
15
+ import { _getTargetMethodSignature } from './decorator.util'
16
16
  import type { MemoCache } from './memo.util'
17
17
  import { jsonMemoSerializer, MapMemoCache } from './memo.util'
18
18
 
19
19
  export interface MemoOpts {
20
- logHit?: boolean
21
- logMiss?: boolean
22
- logArgs?: boolean
23
20
  logger?: CommonLogger
24
21
  }
25
22
 
@@ -58,7 +55,7 @@ export const memoSimple =
58
55
  */
59
56
  const cache: MemoCache = new MapMemoCache()
60
57
 
61
- const { logHit, logMiss, logArgs = true, logger = console } = opt
58
+ const { logger = console } = opt
62
59
  const keyStr = String(key)
63
60
  const methodSignature = _getTargetMethodSignature(target, keyStr)
64
61
 
@@ -67,26 +64,11 @@ export const memoSimple =
67
64
  const cacheKey = jsonMemoSerializer(args)
68
65
 
69
66
  if (cache.has(cacheKey)) {
70
- if (logHit) {
71
- logger.log(`${methodSignature}(${_getArgsSignature(args, logArgs)}) @memo hit`)
72
- }
73
67
  return cache.get(cacheKey)
74
68
  }
75
69
 
76
- const d = Date.now()
77
-
78
70
  const res: any = originalFn.apply(ctx, args)
79
-
80
- if (logMiss) {
81
- logger.log(
82
- `${methodSignature}(${_getArgsSignature(args, logArgs)}) @memo miss (${
83
- Date.now() - d
84
- } ms)`,
85
- )
86
- }
87
-
88
71
  cache.set(cacheKey, res)
89
-
90
72
  return res
91
73
  } as any
92
74
  ;(descriptor.value as any).dropCache = () => {
package/src/index.ts CHANGED
@@ -35,6 +35,8 @@ export * from './object/deepEquals'
35
35
  export * from './object/object.util'
36
36
  export * from './object/sortObject'
37
37
  export * from './object/sortObjectDeep'
38
+ export * from './object/map2'
39
+ export * from './object/set2'
38
40
  export * from './promise/pDefer'
39
41
  export * from './promise/pDelay'
40
42
  export * from './promise/pFilter'
@@ -1,68 +1,159 @@
1
- const isArray = Array.isArray
2
- const keyList = Object.keys
3
- const hasProp = Object.prototype.hasOwnProperty
1
+ // Heavily inspired by https://github.com/epoberezkin/fast-deep-equal
4
2
 
5
3
  /**
6
- * deepEquals, but after JSON stringify/parse
7
- * E.g if object A has extra properties with value `undefined` -
8
- * it won't be _deepEquals true, but will be _deepJsonEquals true.
9
- * (because JSON.stringify removes undefined properties).
10
- */
11
- export function _deepJsonEquals(a: any, b: any): boolean {
12
- if (a === b) return true
13
- const aj = JSON.stringify(a)
14
- const bj = JSON.stringify(b)
15
- if (aj === bj) return true
16
- return _deepEquals(JSON.parse(aj), JSON.parse(bj))
17
- }
18
-
19
- /**
20
- * Based on: https://github.com/epoberezkin/fast-deep-equal/
4
+ Returns true if a and b are deeply equal.
5
+
6
+ Equality is checked recursively, with the following rules/caveats:
7
+ - Primitive values are checked with ===
8
+ - NaN === NaN
9
+ - Array length should be the same, and every value should be equal
10
+ - Sets are checked similarly to arrays (but order doesn't matter in Sets)
11
+ - Objects and Maps are checked that all values match. Undefined values are treated the same as absent key (important!)
12
+ - Order of object/Map keys doesn't matter, unlike when comparing JSON.stringify(a) === JSON.stringify(b)
13
+ - Regex are compared by their source and flags
14
+ - Functions are compared by their `.toString`
15
+ - Any object that overrides `.toString()` is compared by that (e.g Function)
16
+ - Any object that overrides `.valueOf()` is compared by that (e.g Date)
17
+
18
+ What are the differences between various deep-equality functions?
19
+ There are:
20
+ - _deepEquals
21
+ - _deepJsonEquals
22
+ - _jsonEquals
23
+
24
+ _deepEquals uses "common sense" equality.
25
+ It tries to work "as you would expect it to".
26
+ With the important caveat that undefined values are treated the same as absent key.
27
+ So, _deepEquals should be the first choice.
28
+ It's also the most performant of 3.
29
+
30
+ _deepJsonEquals uses different logic, that's often not what you expect.
31
+ It should be used to compare objects of how they would look after "passing via JSON.stringify",
32
+ for example when you return it over the API to the Frontend,
33
+ or when you pass it to be saved to the Database.
34
+ If some object has custom .toJSON() implementation - it'll invoke that (similar to JSON.stringify).
35
+ For these cases - it can be better than _deepEquals.
36
+ And it's better than _jsonEquals, because it doesn't fail/depend on object key order.
37
+
38
+ _jsonEquals is simply JSON.stringify(a) === JSON.stringify(b).
39
+ It's the simplest implementation, but also the slowest of 3.
40
+
41
+ TLDR: _deepEquals should be useful in most of the cases, start there.
21
42
  */
22
43
  export function _deepEquals(a: any, b: any): boolean {
23
44
  if (a === b) return true
24
45
 
25
- if (a && b && typeof a === 'object' && typeof b === 'object') {
26
- const arrA = isArray(a)
27
- const arrB = isArray(b)
28
- let i: number
29
- let length: number
30
- let key: string
46
+ if (Number.isNaN(a)) {
47
+ return Number.isNaN(b)
48
+ }
31
49
 
32
- if (arrA !== arrB) return false
50
+ if (a && b && typeof a === 'object' && typeof b === 'object') {
51
+ if (a.constructor !== b.constructor) return false
33
52
 
34
- if (arrA && arrB) {
35
- length = a.length
53
+ if (Array.isArray(a)) {
54
+ const length = a.length
36
55
  if (length !== b.length) return false
37
- for (i = length; i-- !== 0; ) if (!_deepEquals(a[i], b[i])) return false
56
+ for (let i = length; i-- !== 0; ) {
57
+ if (!_deepEquals(a[i], b[i])) return false
58
+ }
59
+ return true
60
+ }
61
+
62
+ if (a instanceof Map && b instanceof Map) {
63
+ for (const key of new Set([...a.keys(), ...b.keys()])) {
64
+ if (!_deepEquals(a.get(key), b.get(key))) return false
65
+ }
38
66
  return true
39
67
  }
40
68
 
41
- const dateA = a instanceof Date
42
- const dateB = b instanceof Date
43
- if (dateA !== dateB) return false
44
- if (dateA && dateB) return a.getTime() === b.getTime()
69
+ if (a instanceof Set && b instanceof Set) {
70
+ if (a.size !== b.size) return false
71
+ for (const key of a) {
72
+ if (!b.has(key)) return false
73
+ }
74
+ return true
75
+ }
45
76
 
46
- const regexpA = a instanceof RegExp
47
- const regexpB = b instanceof RegExp
48
- if (regexpA !== regexpB) return false
49
- if (regexpA && regexpB) return a.toString() === b.toString()
77
+ if (a.constructor === RegExp) return a.source === b.source && a.flags === b.flags
78
+ if (a.valueOf !== Object.prototype.valueOf) return a.valueOf() === b.valueOf()
79
+ if (a.toString !== Object.prototype.toString) return a.toString() === b.toString()
50
80
 
51
- const keys = keyList(a)
52
- length = keys.length
81
+ for (const key of new Set([...Object.keys(a), ...Object.keys(b)])) {
82
+ if (!_deepEquals(a[key], b[key])) return false
83
+ }
53
84
 
54
- if (length !== keyList(b).length) return false
85
+ return true
86
+ }
55
87
 
56
- for (i = length; i-- !== 0; ) if (!hasProp.call(b, keys[i]!)) return false
88
+ return a === b
89
+ }
57
90
 
58
- for (i = length; i-- !== 0; ) {
59
- key = keys[i]!
60
- if (!_deepEquals(a[key], b[key])) return false
91
+ /**
92
+ Returns true if a and b are deeply equal.
93
+
94
+ Equality is checked in the same way as if both arguments are processed via
95
+ JSON.stringify and JSON.parse:
96
+ - undefined values are removed, undefined values in an array are turned into `null`, etc.
97
+ - Any Regex, Map, Set, Function stringifies to {}.
98
+ - Date stringifies to its IsoDateTimeString representation.
99
+ - Any object that implements toJSON is compared by the output of its toJSON().
100
+ - NaN stringifies to null
101
+ - Order of object keys does not matter, unlike when comparing JSON.stringify(a) === JSON.stringify(b)
102
+
103
+ See _deepEquals docs for more details and comparison.
104
+ */
105
+ export function _deepJsonEquals(a: any, b: any): boolean {
106
+ if (a === b) return true
107
+
108
+ if (Number.isNaN(a)) {
109
+ a = null
110
+ } else if (typeof a === 'function') {
111
+ a = undefined
112
+ } else if (a && typeof a === 'object') {
113
+ if (a instanceof Date) {
114
+ a = a.valueOf()
115
+ } else if ('toJSON' in a) {
116
+ a = a.toJSON()
117
+ }
118
+ }
119
+ if (Number.isNaN(b)) {
120
+ b = null
121
+ } else if (typeof b === 'function') {
122
+ b = undefined
123
+ } else if (b && typeof b === 'object') {
124
+ if (b instanceof Date) {
125
+ b = b.valueOf()
126
+ } else if ('toJSON' in b) {
127
+ b = b.toJSON()
128
+ }
129
+ }
130
+
131
+ if (a && b && typeof a === 'object' && typeof b === 'object') {
132
+ if (Array.isArray(a)) {
133
+ const length = a.length
134
+ if (length !== b.length) return false
135
+ for (let i = length; i-- !== 0; ) {
136
+ if (!_deepJsonEquals(a[i], b[i])) return false
137
+ }
138
+ return true
139
+ }
140
+
141
+ for (const key of new Set([...Object.keys(a), ...Object.keys(b)])) {
142
+ if (!_deepJsonEquals(a[key], b[key])) return false
61
143
  }
62
144
 
63
145
  return true
64
146
  }
65
147
 
66
- // eslint-disable-next-line no-self-compare
67
- return a !== a && b !== b
148
+ return a === b
149
+ }
150
+
151
+ /**
152
+ * Shortcut for JSON.stringify(a) === JSON.stringify(b)
153
+ *
154
+ * Simplest "deep equals" implementation, but also the slowest,
155
+ * and not robust, in the sense that it depends on the order of object keys.
156
+ */
157
+ export function _jsonEquals(a: any, b: any): boolean {
158
+ return JSON.stringify(a) === JSON.stringify(b)
68
159
  }
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Like Map, but serializes to JSON as an object.
3
+ *
4
+ * Fixes the "issue" of stock Map being json-serialized as `{}`.
5
+ *
6
+ * @experimental
7
+ */
8
+ export class Map2<K = any, V = any> extends Map<K, V> {
9
+ /**
10
+ * Convenience way to create Map2 from object.
11
+ */
12
+ static of<V>(obj: Record<any, V>): Map2<string, V> {
13
+ return new Map2(Object.entries(obj))
14
+ }
15
+
16
+ toObject(): Record<string, V> {
17
+ return Object.fromEntries(this)
18
+ }
19
+
20
+ toJSON(): Record<string, V> {
21
+ return Object.fromEntries(this)
22
+ }
23
+
24
+ // consider more helpful .toString() ?
25
+ }
@@ -1,5 +1,4 @@
1
1
  import { _isEmpty, _isObject } from '../is.util'
2
- import type { PropertyPath } from '../lodash.types'
3
2
  import { _objectEntries, KeyValueTuple, Reviver, SKIP } from '../types'
4
3
  import type { AnyObject, ObjectMapper, ObjectPredicate, ValueOf } from '../types'
5
4
 
@@ -328,6 +327,9 @@ export function _get<T extends AnyObject>(obj = {} as T, path = ''): unknown {
328
327
  .reduce((o, p) => o?.[p], obj)
329
328
  }
330
329
 
330
+ type Many<T> = T | readonly T[]
331
+ type PropertyPath = Many<PropertyKey>
332
+
331
333
  /**
332
334
  * Sets the value at path of object. If a portion of path doesn’t exist it’s created. Arrays are created for
333
335
  * missing index properties while objects are created for all other missing properties.
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Like Set, but serializes to JSON as an array.
3
+ *
4
+ * Fixes the "issue" of stock Set being json-serialized as `{}`.
5
+ *
6
+ * @experimental
7
+ */
8
+ export class Set2<T = any> extends Set<T> {
9
+ toArray(): T[] {
10
+ return [...this]
11
+ }
12
+
13
+ toJSON(): T[] {
14
+ return [...this]
15
+ }
16
+
17
+ // consider more helpful .toString() ?
18
+ }
@@ -158,6 +158,12 @@ export function _stringify(obj: any, opt: StringifyOptions = {}): string {
158
158
  //
159
159
  // Other
160
160
  //
161
+ if (obj instanceof Map) {
162
+ obj = Object.fromEntries(obj)
163
+ } else if (obj instanceof Set) {
164
+ obj = [...obj]
165
+ }
166
+
161
167
  try {
162
168
  const { stringifyFn = globalStringifyFunction } = opt
163
169
 
@@ -1,4 +0,0 @@
1
- export type PropertyName = string | number | symbol;
2
- export type RecursiveArray<T> = (T | RecursiveArray<T>)[];
3
- export type Many<T> = T | readonly T[];
4
- export type PropertyPath = Many<PropertyName>;
@@ -1,2 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
@@ -1 +0,0 @@
1
- export {};
@@ -1,6 +0,0 @@
1
- export type PropertyName = string | number | symbol
2
-
3
- export type RecursiveArray<T> = (T | RecursiveArray<T>)[]
4
-
5
- export type Many<T> = T | readonly T[]
6
- export type PropertyPath = Many<PropertyName>