@1nkvi/utils 0.2.0 → 0.3.1
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/guard/index.d.ts +1 -0
- package/build/artifacts/src/guard/index.js +1 -0
- package/build/artifacts/src/index.d.ts +2 -0
- package/build/artifacts/src/index.js +2 -0
- package/build/artifacts/src/merge/index.d.ts +0 -1
- package/build/artifacts/src/merge/index.js +0 -1
- 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 +1 -1
- 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/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 +59 -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/{merge → guard}/utils.d.ts +0 -0
- /package/build/artifacts/src/{merge → guard}/utils.js +0 -0
- /package/build/artifacts/src/{merge → types}/DeepPartial.d.ts +0 -0
- /package/build/artifacts/src/{merge → types}/DeepPartial.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`. |
|
|
@@ -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,5 +1,5 @@
|
|
|
1
1
|
import { isInvalidPrimitive } from "../merge/isInvalidPrimitive";
|
|
2
|
-
import { isPlainObject } from "../
|
|
2
|
+
import { isPlainObject } from "../guard/utils";
|
|
3
3
|
import { tuplify } from "../array/tuplify";
|
|
4
4
|
import { mergeArrays } from "../merge/mergeArrays";
|
|
5
5
|
/**
|
|
@@ -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,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,59 @@
|
|
|
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
|
+
} : FlatObjectFromKeys<Required<T>>;
|
|
46
|
+
type FlatObjectFromKeys<T> = UnionToIntersection<{
|
|
47
|
+
[Key in keyof T & string]: IsPlainObject<T[Key]> extends true ? {
|
|
48
|
+
[FlatKey in Key]: T[Key];
|
|
49
|
+
} & {
|
|
50
|
+
[SubKey in keyof FlatObject<T[Key]> & string as `${Key}.${SubKey}`]: FlatObject<T[Key]>[SubKey];
|
|
51
|
+
} : T[Key] extends readonly unknown[] ? {
|
|
52
|
+
[FlatKey in Key]: T[Key];
|
|
53
|
+
} & {
|
|
54
|
+
[SubKey in keyof FlatObject<T[Key]> & string as `${Key}.${SubKey}`]: FlatObject<T[Key]>[SubKey];
|
|
55
|
+
} : {
|
|
56
|
+
[FlatKey in Key]: T[Key];
|
|
57
|
+
};
|
|
58
|
+
}[keyof T & string]>;
|
|
59
|
+
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
|
|
File without changes
|
|
File without changes
|