@1nkvi/utils 0.1.3 → 0.3.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 +23 -193
- package/build/artifacts/src/array/createArray.d.ts +9 -0
- package/build/artifacts/src/array/createArray.js +11 -0
- package/build/artifacts/src/array/createRangeArray.d.ts +9 -0
- package/build/artifacts/src/array/createRangeArray.js +11 -0
- package/build/artifacts/src/array/index.d.ts +3 -0
- package/build/artifacts/src/array/index.js +3 -0
- package/build/artifacts/src/guard/index.d.ts +2 -0
- package/build/artifacts/src/guard/index.js +2 -0
- package/build/artifacts/src/guard/triggerExhaustiveSwitch.d.ts +14 -0
- package/build/artifacts/src/guard/triggerExhaustiveSwitch.js +17 -0
- package/build/artifacts/src/guard/utils.d.ts +4 -0
- package/build/artifacts/src/guard/utils.js +14 -0
- package/build/artifacts/src/index.d.ts +7 -5
- package/build/artifacts/src/index.js +7 -5
- package/build/artifacts/src/map/index.d.ts +1 -0
- package/build/artifacts/src/map/index.js +1 -0
- package/build/artifacts/src/map/upsert.d.ts +15 -0
- package/build/artifacts/src/map/upsert.js +24 -0
- package/build/artifacts/src/merge/DeepPartial.d.ts +3 -0
- package/build/artifacts/src/merge/index.d.ts +3 -0
- package/build/artifacts/src/merge/index.js +3 -0
- package/build/artifacts/src/merge/isInvalidPrimitive.js +1 -1
- package/build/artifacts/src/merge/merge.d.ts +1 -1
- package/build/artifacts/src/merge/merge.js +2 -2
- package/build/artifacts/src/merge/mergeArrays.js +1 -1
- package/build/artifacts/src/object/index.d.ts +1 -0
- package/build/artifacts/src/object/index.js +1 -0
- package/build/artifacts/src/object/replace.d.ts +7 -0
- package/build/artifacts/src/object/replace.js +27 -0
- package/build/artifacts/src/random/index.d.ts +1 -0
- package/build/artifacts/src/random/index.js +1 -0
- package/build/artifacts/src/random/random.d.ts +63 -0
- package/build/artifacts/src/random/random.js +57 -0
- package/build/artifacts/src/types/DeepPartial.d.ts +6 -0
- package/build/artifacts/src/types/DeepPartial.js +1 -0
- package/build/artifacts/src/types/DotPaths.d.ts +14 -0
- package/build/artifacts/src/types/DotPaths.js +1 -0
- package/build/artifacts/src/types/DotPathsWithArrayIndex.d.ts +21 -0
- package/build/artifacts/src/types/DotPathsWithArrayIndex.js +1 -0
- package/build/artifacts/src/types/FlatObject.d.ts +58 -0
- package/build/artifacts/src/types/FlatObject.js +1 -0
- package/build/artifacts/src/types/IsPlainObject.d.ts +15 -0
- package/build/artifacts/src/types/IsPlainObject.js +1 -0
- package/build/artifacts/src/types/LeafDotPaths.d.ts +20 -0
- package/build/artifacts/src/types/LeafDotPaths.js +1 -0
- package/build/artifacts/src/types/index.d.ts +6 -0
- package/build/artifacts/src/types/index.js +6 -0
- package/package.json +1 -1
- /package/build/artifacts/src/{tuplify.d.ts → array/tuplify.d.ts} +0 -0
- /package/build/artifacts/src/{tuplify.js → array/tuplify.js} +0 -0
package/README.md
CHANGED
|
@@ -1,206 +1,36 @@
|
|
|
1
1
|
# @1nkvi/utils
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
and merging nested objects recursively — while filtering out "empty" values like `null`, `undefined` and `""`.
|
|
6
|
-
|
|
7
|
-
## Quick start
|
|
8
|
-
|
|
9
|
-
```ts
|
|
10
|
-
import { merge } from "@1nkvi/utils"
|
|
11
|
-
|
|
12
|
-
merge([{ id: 1 }, { name: "Ada" }, { name: "Ada Lovelace" }])
|
|
13
|
-
// → { id: 1, name: "Ada Lovelace" }
|
|
14
|
-
```
|
|
15
|
-
|
|
16
|
-
Every export is available from the package root:
|
|
17
|
-
|
|
18
|
-
```ts
|
|
19
|
-
import {
|
|
20
|
-
merge,
|
|
21
|
-
mergeArrays,
|
|
22
|
-
filterValidPrimitiveArrayValues,
|
|
23
|
-
tuplify,
|
|
24
|
-
isPlainObject,
|
|
25
|
-
isEmptyObject,
|
|
26
|
-
isEmptyArray,
|
|
27
|
-
isInvalidNumber,
|
|
28
|
-
} from "@1nkvi/utils"
|
|
29
|
-
```
|
|
30
|
-
|
|
31
|
-
---
|
|
32
|
-
|
|
33
|
-
## `merge(values, options?)`
|
|
34
|
-
|
|
35
|
-
Deeply merges an array of partial objects (left to right) into a single object.
|
|
36
|
-
|
|
37
|
-
```ts
|
|
38
|
-
merge<TData>(values: Array<Partial<TData>>, options?: MergeOptions): Partial<TData>
|
|
3
|
+
```shell
|
|
4
|
+
pnpm i @1nkvi/utils
|
|
39
5
|
```
|
|
40
6
|
|
|
41
|
-
|
|
42
|
-
|
|
7
|
+
A small collection of TypeScript helper functions and utility types. Its centerpiece is **deeply merging** partial
|
|
8
|
+
objects and arrays — composing a list of partials into one while overriding primitives, concatenating and de-duplicating
|
|
9
|
+
arrays, and merging nested objects recursively, all while filtering out "empty" values like `null`, `undefined` and
|
|
10
|
+
`""`. Alongside it are a handful of array, map, randomness, and type-guard utilities, plus reusable object/path utility
|
|
11
|
+
types.
|
|
43
12
|
|
|
44
|
-
|
|
13
|
+
## Changelog
|
|
45
14
|
|
|
46
|
-
|
|
47
|
-
skipped, so a later `""` or `null` won't clobber an earlier real value.
|
|
48
|
-
- **Arrays** — concatenated, then primitive values are de-duplicated. (Objects/arrays nested inside an array are kept
|
|
49
|
-
as-is.)
|
|
50
|
-
- **Objects** — merged recursively, to any depth.
|
|
51
|
-
- **Mixed primitive vs. array** for the same key — the array wins by default. With `enableSingleValueArrays`, the single
|
|
52
|
-
value is wrapped into the array instead so nothing is lost.
|
|
53
|
-
- Inputs are **never mutated**; a new object is returned.
|
|
15
|
+
[Changelog link](./CHANGELOG.md)
|
|
54
16
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
```ts
|
|
58
|
-
// Compose keys from several partials
|
|
59
|
-
merge([{ int: 1 }, { float: 2.3 }, { text: "Hello" }])
|
|
60
|
-
// → { int: 1, float: 2.3, text: "Hello" }
|
|
61
|
-
|
|
62
|
-
// Later valid primitives override earlier ones; "" is ignored
|
|
63
|
-
merge([{ int: 1, text: "Hello" }, { int: 2, text: "" }, { int: 3 }])
|
|
64
|
-
// → { int: 3, text: "Hello" }
|
|
65
|
-
|
|
66
|
-
// Arrays concatenate and de-duplicate
|
|
67
|
-
merge([{ nums: [1, 2, 3] }, { nums: [4] }])
|
|
68
|
-
// → { nums: [1, 2, 3, 4] }
|
|
69
|
-
|
|
70
|
-
// Primitive vs. array on the same key — the array wins
|
|
71
|
-
merge([{ text: "first" }, { text: ["second", "third"] }])
|
|
72
|
-
// → { text: ["second", "third"] }
|
|
73
|
-
|
|
74
|
-
// ...unless you opt into folding the single value in
|
|
75
|
-
merge([{ text: "first" }, { text: ["second", "third"] }], { enableSingleValueArrays: true })
|
|
76
|
-
// → { text: ["first", "second", "third"] }
|
|
77
|
-
|
|
78
|
-
// Nested objects and arrays of objects merge recursively
|
|
79
|
-
merge([{ node: [{ text: "A", children: [{ text: "A.1" }] }] }, { node: [{ int: 20 }] }])
|
|
80
|
-
// → { node: [{ text: "A", children: [{ text: "A.1" }] }, { int: 20 }] }
|
|
81
|
-
```
|
|
82
|
-
|
|
83
|
-
### `MergeOptions`
|
|
84
|
-
|
|
85
|
-
| Option | Default | Effect |
|
|
86
|
-
| -------------------------------- | ------- | ----------------------------------------------------------------------------------------------------------------------------- |
|
|
87
|
-
| `enableSingleValueArrays` | `false` | When a key mixes a single value and an array, wrap the single value into the array (instead of letting the array replace it). |
|
|
88
|
-
| `disableDistinctPrimitiveFilter` | `false` | Keep duplicate primitives in merged arrays instead of de-duplicating. |
|
|
89
|
-
| `nullIsValid` | `false` | Treat `null` as a valid value (don't filter it out). |
|
|
90
|
-
| `undefinedIsValid` | `false` | Treat `undefined` as a valid value. |
|
|
91
|
-
| `emptyStringIsValid` | `false` | Treat `""` as a valid value. |
|
|
92
|
-
| `validators` | `[]` | Extra predicates `(value) => boolean`; returning `true` marks a value **invalid** and filters it out. |
|
|
93
|
-
|
|
94
|
-
---
|
|
95
|
-
|
|
96
|
-
## `mergeArrays(arrays, options?)`
|
|
97
|
-
|
|
98
|
-
Flattens several arrays into one. By default, primitive values are de-duplicated and invalid primitives are dropped.
|
|
99
|
-
|
|
100
|
-
```ts
|
|
101
|
-
mergeArrays<TData>(arrays: TData[][], options?: MergeArraysOptions): TData[]
|
|
102
|
-
```
|
|
103
|
-
|
|
104
|
-
```ts
|
|
105
|
-
mergeArrays([["first"], ["second"], ["third", "fourth"]])
|
|
106
|
-
// → ["first", "second", "third", "fourth"]
|
|
107
|
-
|
|
108
|
-
// Duplicates removed
|
|
109
|
-
mergeArrays([["first"], ["first", "second"]])
|
|
110
|
-
// → ["first", "second"]
|
|
111
|
-
|
|
112
|
-
// Invalid primitives ("" here) are filtered out
|
|
113
|
-
mergeArrays([["first"], [""]])
|
|
114
|
-
// → ["first"]
|
|
115
|
-
|
|
116
|
-
// Keep everything as-is
|
|
117
|
-
mergeArrays([["first"], ["first", ""]], { disableDistinctPrimitiveFilter: true })
|
|
118
|
-
// → ["first", "first", ""]
|
|
119
|
-
```
|
|
120
|
-
|
|
121
|
-
### `MergeArraysOptions`
|
|
122
|
-
|
|
123
|
-
| Option | Default | Effect |
|
|
124
|
-
| -------------------------------- | ------- | -------------------------------------------------------------------- |
|
|
125
|
-
| `disableDistinctPrimitiveFilter` | `false` | Keep duplicates and invalid primitives instead of filtering. |
|
|
126
|
-
| `nullIsValid` | `false` | Treat `null` as valid. |
|
|
127
|
-
| `undefinedIsValid` | `false` | Treat `undefined` as valid. |
|
|
128
|
-
| `emptyStringIsValid` | `false` | Treat `""` as valid. |
|
|
129
|
-
| `validators` | `[]` | Extra `(value) => boolean` predicates; `true` marks a value invalid. |
|
|
130
|
-
|
|
131
|
-
---
|
|
132
|
-
|
|
133
|
-
## `filterValidPrimitiveArrayValues(flatArray, options?)`
|
|
134
|
-
|
|
135
|
-
Takes a single flat array, removes invalid primitives, and de-duplicates the rest.
|
|
136
|
-
|
|
137
|
-
```ts
|
|
138
|
-
filterValidPrimitiveArrayValues<TData>(flatArray: TData[], options?): TData[]
|
|
139
|
-
```
|
|
140
|
-
|
|
141
|
-
```ts
|
|
142
|
-
filterValidPrimitiveArrayValues(["first", "", null, "second", "second"])
|
|
143
|
-
// → ["first", "second"]
|
|
144
|
-
```
|
|
145
|
-
|
|
146
|
-
Accepts the same validity overrides as above (`nullIsValid`, `undefinedIsValid`, `emptyStringIsValid`).
|
|
147
|
-
|
|
148
|
-
---
|
|
149
|
-
|
|
150
|
-
## Invalid primitives
|
|
151
|
-
|
|
152
|
-
The merge/filter functions skip values considered "empty" or "meaningless". By default these are:
|
|
153
|
-
|
|
154
|
-
- `null`
|
|
155
|
-
- `undefined`
|
|
156
|
-
- empty string `""`
|
|
157
|
-
- `NaN`
|
|
158
|
-
- `Infinity` and `-Infinity`
|
|
159
|
-
- empty array `[]`
|
|
160
|
-
- empty object `{}`
|
|
161
|
-
|
|
162
|
-
Note that **`0` and `false` are valid** — they are kept. You can relax the defaults per call with `nullIsValid` /
|
|
163
|
-
`undefinedIsValid` / `emptyStringIsValid`, or add your own rules via `validators`:
|
|
17
|
+
## Quick start
|
|
164
18
|
|
|
165
19
|
```ts
|
|
166
|
-
|
|
167
|
-
merge([{ tag: "release" }, { tag: "#draft" }], {
|
|
168
|
-
validators: [(v) => typeof v === "string" && v.startsWith("#")],
|
|
169
|
-
})
|
|
170
|
-
// → { tag: "release" }
|
|
171
|
-
```
|
|
172
|
-
|
|
173
|
-
---
|
|
174
|
-
|
|
175
|
-
## `tuplify(value)`
|
|
176
|
-
|
|
177
|
-
Wraps a single value into a one-element array, and leaves an existing array untouched. Handy for normalizing a `T | T[]`
|
|
178
|
-
value into `T[]`.
|
|
20
|
+
import { merge } from "@1nkvi/utils"
|
|
179
21
|
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
tuplify(["a", "b"]) // → ["a", "b"]
|
|
183
|
-
tuplify([]) // → []
|
|
22
|
+
merge([{ id: 1 }, { name: "Ada" }, { name: "Ada Lovelace" }])
|
|
23
|
+
// → { id: 1, name: "Ada Lovelace" }
|
|
184
24
|
```
|
|
185
25
|
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
## Small value helpers
|
|
26
|
+
## Modules
|
|
189
27
|
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
|
193
|
-
|
|
|
194
|
-
|
|
|
195
|
-
|
|
|
196
|
-
|
|
|
197
|
-
|
|
|
198
|
-
|
|
199
|
-
```ts
|
|
200
|
-
isPlainObject({ a: 1 }) // → true
|
|
201
|
-
isPlainObject([]) // → false
|
|
202
|
-
isEmptyObject({}) // → true
|
|
203
|
-
isEmptyArray([]) // → true
|
|
204
|
-
isInvalidNumber(NaN) // → true
|
|
205
|
-
isInvalidNumber(42) // → false
|
|
206
|
-
```
|
|
28
|
+
| Module | What it does |
|
|
29
|
+
| -------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
30
|
+
| [array](./docs/array.md) | Create and normalize arrays: `tuplify` a value into an array, or build index/range arrays with `createArray` and `createRangeArray`. |
|
|
31
|
+
| [guard](./docs/guard.md) | Type guards and runtime checks: `isPlainObject`, `isEmptyObject`, `isEmptyArray`, `isInvalidNumber`, plus `triggerExhaustiveSwitch` for exhaustiveness. |
|
|
32
|
+
| [map](./docs/map.md) | `upsert` a `Map` entry — insert a value, or combine it with the existing one via a callback. |
|
|
33
|
+
| [merge](./docs/merge.md) | Deeply `merge` partial objects, `mergeArrays` with de-duplication, and `filterValidPrimitiveArrayValues`, all skipping "empty" values. |
|
|
34
|
+
| [object](./docs/object.md) | `replace` a single deeply nested value by dot-notation path — immutably and type-safely, without touching its siblings. |
|
|
35
|
+
| [random](./docs/random.md) | Random integers: `random` within inclusive/exclusive bounds, and `randomMarginalChange` to nudge a value by fixed and percentage margins. |
|
|
36
|
+
| [types](./docs/types.md) | Utility types for nested objects: `DeepPartial`, `DotPaths`, `LeafDotPaths`, `DotPathsWithArrayIndex`, `FlatObject`, `IsPlainObject`. |
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Creates array from `startIndex` to `length¨
|
|
3
|
+
*
|
|
4
|
+
* Useful for quickly creating skeleton placeholders
|
|
5
|
+
*
|
|
6
|
+
* @param length size of the array
|
|
7
|
+
* @param startIndex defaults to 0
|
|
8
|
+
*/
|
|
9
|
+
export declare function createArray(length: number, startIndex?: number): number[];
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Creates array from `startIndex` to `length¨
|
|
3
|
+
*
|
|
4
|
+
* Useful for quickly creating skeleton placeholders
|
|
5
|
+
*
|
|
6
|
+
* @param length size of the array
|
|
7
|
+
* @param startIndex defaults to 0
|
|
8
|
+
*/
|
|
9
|
+
export function createArray(length, startIndex = 0) {
|
|
10
|
+
return Array.from({ length }, (_, i) => i + startIndex);
|
|
11
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Returns array of numbers from interval `<min, max>`
|
|
3
|
+
* @param min min value (inclusive)
|
|
4
|
+
* @param max max value (inclusive)
|
|
5
|
+
* @example ```tsx
|
|
6
|
+
* createRangeArray(-2, 1) // [-2, -1, 0, 1]
|
|
7
|
+
* ```
|
|
8
|
+
*/
|
|
9
|
+
export declare function createRangeArray(min: number, max: number): number[];
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Returns array of numbers from interval `<min, max>`
|
|
3
|
+
* @param min min value (inclusive)
|
|
4
|
+
* @param max max value (inclusive)
|
|
5
|
+
* @example ```tsx
|
|
6
|
+
* createRangeArray(-2, 1) // [-2, -1, 0, 1]
|
|
7
|
+
* ```
|
|
8
|
+
*/
|
|
9
|
+
export function createRangeArray(min, max) {
|
|
10
|
+
return Array.from({ length: max - min + 1 }, (_, i) => min + i);
|
|
11
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Used for checking that all values are accounted for in a switch
|
|
3
|
+
* ```typescript
|
|
4
|
+
* type Test = "a"|"b"|"c"
|
|
5
|
+
* switch(test) {
|
|
6
|
+
* case "a": ...
|
|
7
|
+
* case "b": ...
|
|
8
|
+
* default: triggerExhaustiveSwitch(test, "My component name")
|
|
9
|
+
* }
|
|
10
|
+
* ```
|
|
11
|
+
* @param value value in switch
|
|
12
|
+
* @param componentName name used for debugging purposes. Usually name of the component where switch lives
|
|
13
|
+
*/
|
|
14
|
+
export declare function triggerExhaustiveSwitch(value: never, componentName: string): never;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Used for checking that all values are accounted for in a switch
|
|
3
|
+
* ```typescript
|
|
4
|
+
* type Test = "a"|"b"|"c"
|
|
5
|
+
* switch(test) {
|
|
6
|
+
* case "a": ...
|
|
7
|
+
* case "b": ...
|
|
8
|
+
* default: triggerExhaustiveSwitch(test, "My component name")
|
|
9
|
+
* }
|
|
10
|
+
* ```
|
|
11
|
+
* @param value value in switch
|
|
12
|
+
* @param componentName name used for debugging purposes. Usually name of the component where switch lives
|
|
13
|
+
*/
|
|
14
|
+
export function triggerExhaustiveSwitch(value, componentName) {
|
|
15
|
+
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
|
|
16
|
+
throw new Error(`Unhandled ${componentName} type for value [${value}]`);
|
|
17
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export declare function isPlainObject(v: unknown): v is Record<string, unknown>;
|
|
2
|
+
export declare function isEmptyObject(v: unknown): v is object;
|
|
3
|
+
export declare function isEmptyArray(v: unknown): v is Array<unknown>;
|
|
4
|
+
export declare function isInvalidNumber(v: unknown): boolean;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export function isPlainObject(v) {
|
|
2
|
+
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
3
|
+
}
|
|
4
|
+
export function isEmptyObject(v) {
|
|
5
|
+
if (!isPlainObject(v))
|
|
6
|
+
return false;
|
|
7
|
+
return Object.keys(v).length === 0;
|
|
8
|
+
}
|
|
9
|
+
export function isEmptyArray(v) {
|
|
10
|
+
return Array.isArray(v) && v.length === 0;
|
|
11
|
+
}
|
|
12
|
+
export function isInvalidNumber(v) {
|
|
13
|
+
return typeof v === "number" && (!Number.isFinite(v) || Number.isNaN(v));
|
|
14
|
+
}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
|
-
export * from "./
|
|
2
|
-
export * from "./merge
|
|
3
|
-
export * from "./
|
|
4
|
-
export * from "./
|
|
5
|
-
export * from "./
|
|
1
|
+
export * from "./array";
|
|
2
|
+
export * from "./merge";
|
|
3
|
+
export * from "./guard";
|
|
4
|
+
export * from "./map";
|
|
5
|
+
export * from "./object";
|
|
6
|
+
export * from "./random";
|
|
7
|
+
export * from "./types";
|
|
@@ -1,5 +1,7 @@
|
|
|
1
|
-
export * from "./
|
|
2
|
-
export * from "./merge
|
|
3
|
-
export * from "./
|
|
4
|
-
export * from "./
|
|
5
|
-
export * from "./
|
|
1
|
+
export * from "./array";
|
|
2
|
+
export * from "./merge";
|
|
3
|
+
export * from "./guard";
|
|
4
|
+
export * from "./map";
|
|
5
|
+
export * from "./object";
|
|
6
|
+
export * from "./random";
|
|
7
|
+
export * from "./types";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./upsert";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./upsert";
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Upserts existing map by `value`, following the `fn` behavior
|
|
3
|
+
* @param map existing map
|
|
4
|
+
* @param key key to be upserted
|
|
5
|
+
* @param value value to be upserted
|
|
6
|
+
* @param fn function used when value exists in the map
|
|
7
|
+
* @example ```tsx
|
|
8
|
+
* const map = new Map<string, string[]>()
|
|
9
|
+
* map.set("a", ["1", "2"])
|
|
10
|
+
* upsert(map, "a", ["3"], (prev, value) => [...prev, ...value])
|
|
11
|
+
* // output: ["1", "2", "3"]
|
|
12
|
+
* ```
|
|
13
|
+
* @remarks if key does not exist, sets the value
|
|
14
|
+
*/
|
|
15
|
+
export declare function upsert<TKey, TValue>(map: Map<TKey, TValue>, key: TKey, value: TValue, fn: (prev: TValue, value: TValue) => TValue): TValue;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Upserts existing map by `value`, following the `fn` behavior
|
|
3
|
+
* @param map existing map
|
|
4
|
+
* @param key key to be upserted
|
|
5
|
+
* @param value value to be upserted
|
|
6
|
+
* @param fn function used when value exists in the map
|
|
7
|
+
* @example ```tsx
|
|
8
|
+
* const map = new Map<string, string[]>()
|
|
9
|
+
* map.set("a", ["1", "2"])
|
|
10
|
+
* upsert(map, "a", ["3"], (prev, value) => [...prev, ...value])
|
|
11
|
+
* // output: ["1", "2", "3"]
|
|
12
|
+
* ```
|
|
13
|
+
* @remarks if key does not exist, sets the value
|
|
14
|
+
*/
|
|
15
|
+
export function upsert(map, key, value, fn) {
|
|
16
|
+
if (map.has(key)) {
|
|
17
|
+
const prev = map.get(key);
|
|
18
|
+
const next = fn(prev, value);
|
|
19
|
+
map.set(key, next);
|
|
20
|
+
return next;
|
|
21
|
+
}
|
|
22
|
+
map.set(key, value);
|
|
23
|
+
return value;
|
|
24
|
+
}
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Expects json object. Walks through the children and makes all keys optional
|
|
3
|
+
*/
|
|
1
4
|
export type DeepPartial<T> = T extends Array<infer U> ? Array<DeepPartial<U>> : T extends ReadonlyArray<infer U> ? ReadonlyArray<DeepPartial<U>> : T extends object ? {
|
|
2
5
|
[K in keyof T]?: DeepPartial<T[K]>;
|
|
3
6
|
} : T;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { isEmptyArray, isEmptyObject, isInvalidNumber } from "../
|
|
1
|
+
import { isEmptyArray, isEmptyObject, isInvalidNumber } from "../guard/utils";
|
|
2
2
|
export function isInvalidPrimitive(value, options) {
|
|
3
3
|
if (options?.validators) {
|
|
4
4
|
const customValidatorResult = options.validators?.some((validator) => validator(value));
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type MergeArraysOptions } from "../merge/mergeArrays";
|
|
2
|
-
import type { DeepPartial } from "../
|
|
2
|
+
import type { DeepPartial } from "../types/DeepPartial";
|
|
3
3
|
export type MergeOptions<TData> = Omit<MergeArraysOptions<TData>, "validators"> & {
|
|
4
4
|
/**
|
|
5
5
|
* If `true` - merging `TValue | TValue[]` results in `TValue[]`
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { isInvalidPrimitive } from "../merge/isInvalidPrimitive";
|
|
2
|
-
import { isPlainObject } from "../
|
|
3
|
-
import { tuplify } from "../tuplify";
|
|
2
|
+
import { isPlainObject } from "../guard/utils";
|
|
3
|
+
import { tuplify } from "../array/tuplify";
|
|
4
4
|
import { mergeArrays } from "../merge/mergeArrays";
|
|
5
5
|
/**
|
|
6
6
|
* Deeply merges inputs into new object. Generally goes through the following steps:
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { isPlainObject } from "../
|
|
1
|
+
import { isPlainObject } from "../guard/utils";
|
|
2
2
|
import { filterValidPrimitiveArrayValues } from "../merge/filterValidPrimitiveArrayValues";
|
|
3
3
|
export function mergeArrays(arrays, options) {
|
|
4
4
|
const merged = arrays.flat();
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./replace";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./replace";
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { FlatObject } from "../../src";
|
|
2
|
+
/**
|
|
3
|
+
* Replaces a single deeply nested value, addressed by a dot‑notation path, and
|
|
4
|
+
* returns a new object — the input is left untouched. The `value` type is
|
|
5
|
+
* derived from {@link FlatObject}, so it must match the type at `key`.
|
|
6
|
+
*/
|
|
7
|
+
export declare function replace<TObject, TKey extends keyof FlatObject<TObject> & string>(object: TObject, key: TKey, value: FlatObject<TObject>[TKey]): TObject;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Replaces a single deeply nested value, addressed by a dot‑notation path, and
|
|
3
|
+
* returns a new object — the input is left untouched. The `value` type is
|
|
4
|
+
* derived from {@link FlatObject}, so it must match the type at `key`.
|
|
5
|
+
*/
|
|
6
|
+
export function replace(object, key, value) {
|
|
7
|
+
return setDeep(object, key.split("."), value);
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Immutably sets `value` at the dot‑notation `path`, cloning every container
|
|
11
|
+
* along the way. Numeric segments create/clone arrays, everything else objects,
|
|
12
|
+
* so a single element or key can be replaced without touching its siblings.
|
|
13
|
+
*/
|
|
14
|
+
function setDeep(target, path, value) {
|
|
15
|
+
const [head, ...rest] = path;
|
|
16
|
+
const clone = Array.isArray(target)
|
|
17
|
+
? [...target]
|
|
18
|
+
: { ...target };
|
|
19
|
+
if (rest.length === 0) {
|
|
20
|
+
clone[head] = value;
|
|
21
|
+
return clone;
|
|
22
|
+
}
|
|
23
|
+
const child = target?.[head];
|
|
24
|
+
const nextSegmentIsIndex = /^\d+$/.test(rest[0]);
|
|
25
|
+
clone[head] = setDeep(child ?? (nextSegmentIsIndex ? [] : {}), rest, value);
|
|
26
|
+
return clone;
|
|
27
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "../random/random";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "../random/random";
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
export interface RandomOptions {
|
|
2
|
+
excludeMin?: true;
|
|
3
|
+
excludeMax?: true;
|
|
4
|
+
}
|
|
5
|
+
/**
|
|
6
|
+
* Returns random integer number between `min` and `max` based on the `options`
|
|
7
|
+
* @param min min value (inclusive by default). Must be less than `max`
|
|
8
|
+
* @param max max value (inclusive by default). Must be greater than `min`
|
|
9
|
+
* @param options further options for modifying generator behavior
|
|
10
|
+
*
|
|
11
|
+
* @example ```tsx
|
|
12
|
+
* random(1, 10)
|
|
13
|
+
* // returns single value from inclusive interval <1, 10>
|
|
14
|
+
* ```
|
|
15
|
+
*/
|
|
16
|
+
export declare function random(min: number, max: number, options?: RandomOptions): number;
|
|
17
|
+
export interface RandomMarginalChangeOptions {
|
|
18
|
+
/**
|
|
19
|
+
* Value added to the `min` in the random function
|
|
20
|
+
* @default 0
|
|
21
|
+
*/
|
|
22
|
+
minFixed?: number;
|
|
23
|
+
/**
|
|
24
|
+
* Value added to the `max` in the random function
|
|
25
|
+
* @default 0
|
|
26
|
+
*/
|
|
27
|
+
maxFixed?: number;
|
|
28
|
+
/**
|
|
29
|
+
* Percentage value (as decimal) multiplied the resulting `min` in the random function
|
|
30
|
+
* @default 1.00
|
|
31
|
+
*/
|
|
32
|
+
minPercentage?: number;
|
|
33
|
+
/**
|
|
34
|
+
* Percentage value (as decimal) multiplied the resulting `max` in the random function
|
|
35
|
+
* @default 1.00
|
|
36
|
+
*/
|
|
37
|
+
maxPercentage?: number;
|
|
38
|
+
/**
|
|
39
|
+
* Return this number if computed result is less than absolute minimum
|
|
40
|
+
*/
|
|
41
|
+
absoluteMin?: number;
|
|
42
|
+
/**
|
|
43
|
+
* Return this number if computed result is greater than absolute maximum
|
|
44
|
+
*/
|
|
45
|
+
absoluteMax?: number;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Takes initial value and creates marginal change based on options values.
|
|
49
|
+
*
|
|
50
|
+
* Insert percentages as decimals - 100% = 1.00; 1% = 0.01 calculated from initial value.
|
|
51
|
+
* @example
|
|
52
|
+
* ```typescript
|
|
53
|
+
* randomMarginalChange(100, {minFixed: -10, maxFixed: 10, minPercentage: 0.9, maxPercentage: 1.1})
|
|
54
|
+
* // results in interval min: (100 - 10) * 0.9; max: (100 + 10) * 1.1
|
|
55
|
+
* // where result will be <89; 121>
|
|
56
|
+
*
|
|
57
|
+
* ```
|
|
58
|
+
* @remark Returns floored random value
|
|
59
|
+
*
|
|
60
|
+
* @param value initial value to be derived
|
|
61
|
+
* @param options fixed values default to 0, percentage values defaults to 1
|
|
62
|
+
*/
|
|
63
|
+
export declare function randomMarginalChange(value: number, options: RandomMarginalChangeOptions): number;
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Returns random integer number between `min` and `max` based on the `options`
|
|
3
|
+
* @param min min value (inclusive by default). Must be less than `max`
|
|
4
|
+
* @param max max value (inclusive by default). Must be greater than `min`
|
|
5
|
+
* @param options further options for modifying generator behavior
|
|
6
|
+
*
|
|
7
|
+
* @example ```tsx
|
|
8
|
+
* random(1, 10)
|
|
9
|
+
* // returns single value from inclusive interval <1, 10>
|
|
10
|
+
* ```
|
|
11
|
+
*/
|
|
12
|
+
export function random(min, max, options) {
|
|
13
|
+
if (min === max) {
|
|
14
|
+
return Math.floor(min);
|
|
15
|
+
}
|
|
16
|
+
if (min > max || max < min) {
|
|
17
|
+
throw new Error(`Min must be greater than max. Got min: ${min}, max: ${max}`);
|
|
18
|
+
}
|
|
19
|
+
if (Math.abs(Math.floor(min) - Math.floor(max)) === 1 && options?.excludeMin && options?.excludeMax) {
|
|
20
|
+
throw new Error(`Cannot generate random non-inclusive value from interval (${min}; ${max})`);
|
|
21
|
+
}
|
|
22
|
+
if (options?.excludeMin) {
|
|
23
|
+
return Math.floor(Math.random() * (max - min)) + min + 1;
|
|
24
|
+
}
|
|
25
|
+
if (options?.excludeMax) {
|
|
26
|
+
return Math.floor(Math.random() * (max - min)) + min;
|
|
27
|
+
}
|
|
28
|
+
return Math.floor(Math.random() * (max - min + 1)) + min;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Takes initial value and creates marginal change based on options values.
|
|
32
|
+
*
|
|
33
|
+
* Insert percentages as decimals - 100% = 1.00; 1% = 0.01 calculated from initial value.
|
|
34
|
+
* @example
|
|
35
|
+
* ```typescript
|
|
36
|
+
* randomMarginalChange(100, {minFixed: -10, maxFixed: 10, minPercentage: 0.9, maxPercentage: 1.1})
|
|
37
|
+
* // results in interval min: (100 - 10) * 0.9; max: (100 + 10) * 1.1
|
|
38
|
+
* // where result will be <89; 121>
|
|
39
|
+
*
|
|
40
|
+
* ```
|
|
41
|
+
* @remark Returns floored random value
|
|
42
|
+
*
|
|
43
|
+
* @param value initial value to be derived
|
|
44
|
+
* @param options fixed values default to 0, percentage values defaults to 1
|
|
45
|
+
*/
|
|
46
|
+
export function randomMarginalChange(value, options) {
|
|
47
|
+
const min = (value + (options.minFixed ?? 0)) * (options.minPercentage ?? 1);
|
|
48
|
+
const max = (value + (options.maxFixed ?? 0)) * (options.maxPercentage ?? 1);
|
|
49
|
+
const computedResult = Math.round(random(min, max));
|
|
50
|
+
if (options.absoluteMin && computedResult < options.absoluteMin) {
|
|
51
|
+
return options.absoluteMin;
|
|
52
|
+
}
|
|
53
|
+
if (options.absoluteMax && computedResult > options.absoluteMax) {
|
|
54
|
+
return options.absoluteMax;
|
|
55
|
+
}
|
|
56
|
+
return computedResult;
|
|
57
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Expects json object. Walks through the children and makes all keys optional
|
|
3
|
+
*/
|
|
4
|
+
export type DeepPartial<T> = T extends Array<infer U> ? Array<DeepPartial<U>> : T extends ReadonlyArray<infer U> ? ReadonlyArray<DeepPartial<U>> : T extends object ? {
|
|
5
|
+
[K in keyof T]?: DeepPartial<T[K]>;
|
|
6
|
+
} : T;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { IsPlainObject } from "../types/IsPlainObject";
|
|
2
|
+
/**
|
|
3
|
+
* Recursively builds a union of all nested property paths in dot‑notation.
|
|
4
|
+
* Only plain objects are traversed; primitives, arrays, and functions
|
|
5
|
+
* produce their key directly without recursion.
|
|
6
|
+
*
|
|
7
|
+
* @example ```tsx
|
|
8
|
+
* { a: string, b: { c: { d: number } } }
|
|
9
|
+
* // "a" | "b.c.d"
|
|
10
|
+
* ```
|
|
11
|
+
*/
|
|
12
|
+
export type DotPaths<TObject> = {
|
|
13
|
+
[TKey in keyof TObject]: IsPlainObject<TObject[TKey]> extends true ? (TKey & string) | `${TKey & string}.${DotPaths<TObject[TKey]>}` : TKey & string;
|
|
14
|
+
}[keyof TObject];
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { IsPlainObject } from "../types/IsPlainObject";
|
|
2
|
+
/**
|
|
3
|
+
* Recursively builds dot‑notation paths, including numeric indices for arrays.
|
|
4
|
+
*
|
|
5
|
+
* @typeParam T - The object or array type to extract paths from.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```tsx
|
|
9
|
+
* type Example = {
|
|
10
|
+
* users: [
|
|
11
|
+
* { name: string }
|
|
12
|
+
* ];
|
|
13
|
+
* };
|
|
14
|
+
*
|
|
15
|
+
* type Paths = DotPathsWithArrayIndex<Example>;
|
|
16
|
+
* // "users" | "users.0" | "users.0.name"
|
|
17
|
+
* ```
|
|
18
|
+
*/
|
|
19
|
+
export type DotPathsWithArrayIndex<TObject> = TObject extends unknown[] ? TObject extends (infer TElement)[] ? TElement extends unknown[] ? `${number}` | `${number}.${DotPathsWithArrayIndex<TElement>}` : IsPlainObject<TElement> extends true ? `${number}` | `${number}.${DotPathsWithArrayIndex<TElement>}` : `${number}` : never : {
|
|
20
|
+
[TKey in keyof TObject]: TObject[TKey] extends unknown[] ? (TKey & string) | `${TKey & string}.${DotPathsWithArrayIndex<TObject[TKey]>}` : IsPlainObject<TObject[TKey]> extends true ? (TKey & string) | `${TKey & string}.${DotPathsWithArrayIndex<TObject[TKey]>}` : TKey & string;
|
|
21
|
+
}[keyof TObject];
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import type { IsPlainObject } from "../types/IsPlainObject";
|
|
2
|
+
/**
|
|
3
|
+
* Collapses a union of objects into a single intersected object so that the
|
|
4
|
+
* per‑key results below merge into one flat lookup map instead of a union.
|
|
5
|
+
*/
|
|
6
|
+
type UnionToIntersection<TUnion> = (TUnion extends unknown ? (arg: TUnion) => void : never) extends (arg: infer TIntersection) => void ? TIntersection : never;
|
|
7
|
+
/**
|
|
8
|
+
* Produces a flattened object type where keys are dot‑notation paths and
|
|
9
|
+
* values are the corresponding values. Both intermediate paths (objects and
|
|
10
|
+
* arrays) and leaf paths are emitted, so a whole nested object/array can be
|
|
11
|
+
* addressed just as safely as a leaf. Arrays are descended using a generic
|
|
12
|
+
* numeric index (`${number}`), so element paths remain type‑safe.
|
|
13
|
+
*
|
|
14
|
+
* @typeParam T - The object type to flatten.
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* ```tsx
|
|
18
|
+
* type Example = {
|
|
19
|
+
* a: string;
|
|
20
|
+
* b: { c: number };
|
|
21
|
+
* items: { id: string }[];
|
|
22
|
+
* };
|
|
23
|
+
*
|
|
24
|
+
* type Flat = FlatObject<Example>;
|
|
25
|
+
* // {
|
|
26
|
+
* // "a": string;
|
|
27
|
+
* // "b": { c: number };
|
|
28
|
+
* // "b.c": number;
|
|
29
|
+
* // "items": { id: string }[];
|
|
30
|
+
* // [k: `items.${number}`]: { id: string };
|
|
31
|
+
* // [k: `items.${number}.id`]: string;
|
|
32
|
+
* // }
|
|
33
|
+
* ```
|
|
34
|
+
*/
|
|
35
|
+
export type FlatObject<T> = T extends readonly (infer TElement)[] ? IsPlainObject<TElement> extends true ? {
|
|
36
|
+
[Index in `${number}`]: TElement;
|
|
37
|
+
} & {
|
|
38
|
+
[SubKey in keyof FlatObject<TElement> & string as `${number}.${SubKey}`]: FlatObject<TElement>[SubKey];
|
|
39
|
+
} : TElement extends readonly unknown[] ? {
|
|
40
|
+
[Index in `${number}`]: TElement;
|
|
41
|
+
} & {
|
|
42
|
+
[SubKey in keyof FlatObject<TElement> & string as `${number}.${SubKey}`]: FlatObject<TElement>[SubKey];
|
|
43
|
+
} : {
|
|
44
|
+
[Index in `${number}`]: TElement;
|
|
45
|
+
} : UnionToIntersection<{
|
|
46
|
+
[Key in keyof T & string]: IsPlainObject<T[Key]> extends true ? {
|
|
47
|
+
[FlatKey in Key]: T[Key];
|
|
48
|
+
} & {
|
|
49
|
+
[SubKey in keyof FlatObject<T[Key]> & string as `${Key}.${SubKey}`]: FlatObject<T[Key]>[SubKey];
|
|
50
|
+
} : T[Key] extends readonly unknown[] ? {
|
|
51
|
+
[FlatKey in Key]: T[Key];
|
|
52
|
+
} & {
|
|
53
|
+
[SubKey in keyof FlatObject<T[Key]> & string as `${Key}.${SubKey}`]: FlatObject<T[Key]>[SubKey];
|
|
54
|
+
} : {
|
|
55
|
+
[FlatKey in Key]: T[Key];
|
|
56
|
+
};
|
|
57
|
+
}[keyof T & string]>;
|
|
58
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Determines whether a type is a plain object suitable for recursive traversal.
|
|
3
|
+
*
|
|
4
|
+
* A "plain object" in this context means:
|
|
5
|
+
* - It is an object type
|
|
6
|
+
* - It is **not** an array
|
|
7
|
+
* - It is **not** a function
|
|
8
|
+
*
|
|
9
|
+
* This prevents recursion from descending into prototypes of arrays, functions,
|
|
10
|
+
* primitives, or built‑in objects, ensuring that only object literals are walked.
|
|
11
|
+
*
|
|
12
|
+
* @typeParam T - The type being checked.
|
|
13
|
+
* @returns `true` if `TObject` is a non‑array, non‑function object; otherwise `false`.
|
|
14
|
+
*/
|
|
15
|
+
export type IsPlainObject<TObject> = TObject extends object ? TObject extends unknown[] ? false : TObject extends (...args: unknown[]) => unknown ? false : true : false;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { IsPlainObject } from "../types/IsPlainObject";
|
|
2
|
+
/**
|
|
3
|
+
* Extracts only leaf property paths (deepest keys) in dot‑notation.
|
|
4
|
+
*
|
|
5
|
+
* @typeParam T - The object type whose leaf paths should be extracted.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```tsx
|
|
9
|
+
* type Example = {
|
|
10
|
+
* a: string;
|
|
11
|
+
* b: { c: { d: number } };
|
|
12
|
+
* };
|
|
13
|
+
*
|
|
14
|
+
* type Paths = LeafDotPaths<Example>;
|
|
15
|
+
* // "a" | "b.c.d"
|
|
16
|
+
* ```
|
|
17
|
+
*/
|
|
18
|
+
export type LeafDotPaths<TObject> = {
|
|
19
|
+
[TKey in keyof TObject]: IsPlainObject<TObject[TKey]> extends true ? `${TKey & string}.${LeafDotPaths<TObject[TKey]>}` : TKey & string;
|
|
20
|
+
}[keyof TObject];
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/package.json
CHANGED
|
File without changes
|
|
File without changes
|