maraj 2.5.1 → 2.7.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 +28 -18
- package/dist/index.d.mts +79 -40
- package/dist/index.d.ts +79 -40
- package/dist/index.js +1 -1
- package/dist/index.mjs +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -5,19 +5,19 @@ A fast lightweight utility for creating **immutable updates**. Fully typed. Only
|
|
|
5
5
|
|
|
6
6
|
### Features
|
|
7
7
|
|
|
8
|
-
-
|
|
8
|
+
- Update and ExtendableUpdate.
|
|
9
|
+
- Usefull utilities like "remove", "select".
|
|
9
10
|
- Full typescript support.
|
|
10
11
|
- Correct type inference.
|
|
11
12
|
- Multiple and concurrent updates.
|
|
12
|
-
-
|
|
13
|
-
- Includes usefull utilities like "remove", "select".
|
|
13
|
+
- Nested functional updates.
|
|
14
14
|
|
|
15
|
-
|
|
15
|
+
<u>**For Typescript:**</u> in tsconfig.json set **"strictNullChecks"** and **"noUncheckedIndexedAccess"** to **true** to be able to infer correctly the type of optional properties and properties with posible undefined value. ({key?: ''} !== {key: '' | undefined})
|
|
16
16
|
|
|
17
17
|
```ts
|
|
18
18
|
const originalValue = { name: 'one', lastname: 'two' modified: false, files: [{ filename: 'one' }] }
|
|
19
19
|
|
|
20
|
-
const updatedValue =
|
|
20
|
+
const updatedValue = update(orignalValue, {
|
|
21
21
|
'name': 'updated',
|
|
22
22
|
'modified': (value, fullObject) => !value,
|
|
23
23
|
'files.0.filename': 'zero',
|
|
@@ -25,7 +25,7 @@ const updatedValue = updateImmutably(orignalValue, {
|
|
|
25
25
|
// updatedValue = { name: 'updated', lastname: 'two' modified: true, files: [{ filename: 'zero' }] }
|
|
26
26
|
originalValue.files[1] === updatedValue.files[1] // true
|
|
27
27
|
|
|
28
|
-
const newValue =
|
|
28
|
+
const newValue = extendableUpdate(orignalValue, {
|
|
29
29
|
'newProp': 'new',
|
|
30
30
|
'data.0.name': 'dataName'
|
|
31
31
|
})
|
|
@@ -35,23 +35,29 @@ originalValue.files[1] === newValue.files[1] // true
|
|
|
35
35
|
|
|
36
36
|
### Limitations
|
|
37
37
|
|
|
38
|
-
-
|
|
38
|
+
- Can work with keys containing dots ({ "this.is.dotted.key": 123 }), however those keys (keys containing dots) must exist in the object beforehand (hasOwnProperty) and also must have a non-literal-object/array value to behave properly; like primitives, sets, maps, and so on. For example the path "a.b.c.d" will work properly in the following scenarios:
|
|
39
|
+
- { "a.b.c.d": ... }
|
|
40
|
+
- { a: { "b.c.d": ... } }
|
|
41
|
+
- { a: { b: {"c.d": ... } } }
|
|
42
|
+
- { a: { b: {"c": { d: ... } } } }
|
|
43
|
+
|
|
44
|
+
- In "extendableUpdate", **numbers** inside path will **always** create a mutable **array** if the array/object doesnt exist beforehand. If you want an object/tuple/readonlyArray instead of an array, add it beforehand as type.
|
|
39
45
|
|
|
40
46
|
```ts
|
|
41
|
-
const updatedValue =
|
|
47
|
+
const updatedValue = extendableUpdate({}, {
|
|
42
48
|
'files.1.name': 'new name',
|
|
43
49
|
})
|
|
44
50
|
// The "files" prop is an array. updatedValue = { files: [<empty>, { name: 'new name' }] }
|
|
45
51
|
|
|
46
52
|
// To get an object instead of an array create an object beforehand
|
|
47
|
-
const updatedValue2 =
|
|
53
|
+
const updatedValue2 = extendableUpdate({}, {
|
|
48
54
|
'files': {}, // add an object first, so then i can add props as numbers after it
|
|
49
55
|
'files.1.name': 'new name',
|
|
50
56
|
})
|
|
51
57
|
// updatedValue2 = { files: { '1': { name: 'new name' } } }
|
|
52
58
|
```
|
|
53
59
|
|
|
54
|
-
- The type "UpdateDotPathObject" (the type used as second parameter in the
|
|
60
|
+
- The type "UpdateDotPathObject" (the type of the object provided containing the updates, the one used as second parameter in the update function) if used to create new update objects using computed properties { [key]: value } (middlewares for example) will always throw a ts error even if the typing is correct. [Playground simple explanation](https://www.typescriptlang.org/play?ts=5.3.2#code/LAKALgngDgpgBAGQJYGcxwLxwOQENtwA+cABgCQDeAdgK4C2ARjAE4C+JooksiqYASjADGAe2YATTHApwA2gGk4SKrzQBdAPwAuOItacQoqmjhDmMXGBiDREqQB4AKnBgAPK1XEpVYAHwAKAGsYCB1HAEpMX2lQODijE3MhHWQ0GzFJLApYuNyAejy4AD0wbxZmMR0UZSF4ADcAFgA6AGYmgFYldAB3JAAbPrgwAAsK7rhcFXKxCfQhYeFA5QBzUxE6KBorSWCIb0nJOtw+mhhvGmqqVasNvst4PqQrZmOyupgVJAAzIYWh6BWSm8tiSYByuTkuzUOl24P0IFy5jANGYKiSoFYQA)
|
|
55
61
|
|
|
56
62
|
```ts
|
|
57
63
|
const updateFieldStore = <TPath extends DotPaths<Store>>(
|
|
@@ -69,7 +75,7 @@ const updateFieldStore = <TPath extends DotPaths<Store>>(
|
|
|
69
75
|
- Use the "set" utility if typescript doesn't infer properly _"an optional field"_ vs a _"field which can have 'undefined' as type"_. Usually happens in optional nested props inside arrays.
|
|
70
76
|
|
|
71
77
|
```ts
|
|
72
|
-
const updatedValue =
|
|
78
|
+
const updatedValue = update(data, {
|
|
73
79
|
'files.0.nonOptionalProperty': 'new name',
|
|
74
80
|
'files.1.nonOptionalProperty': undefined, //no ts error
|
|
75
81
|
'files.0.nonOptionalProperty': set('new name'),
|
|
@@ -83,11 +89,15 @@ Arguments:
|
|
|
83
89
|
|
|
84
90
|
- Original Object
|
|
85
91
|
- Update Object
|
|
92
|
+
- Options (to change JS behaviour on non existing props and indexes)
|
|
93
|
+
- 'add': Adds non existing property/index.
|
|
94
|
+
- 'skip': Skips the update in the non-existing prop/index and still applies the rest of the possible updates.
|
|
95
|
+
- 'throw': Throws a JS error, usefull working with indexes making sure there are no "empty" indexes created if a new index is added which is bigger than the length of the current array.
|
|
86
96
|
|
|
87
97
|
## Example
|
|
88
98
|
|
|
89
99
|
```js
|
|
90
|
-
import {
|
|
100
|
+
import { update } from "maraj";
|
|
91
101
|
|
|
92
102
|
const data = {
|
|
93
103
|
profile: { name: "Lukasz" },
|
|
@@ -98,7 +108,7 @@ const data = {
|
|
|
98
108
|
]
|
|
99
109
|
}
|
|
100
110
|
|
|
101
|
-
const newUpdatedObject =
|
|
111
|
+
const newUpdatedObject = update(data, {
|
|
102
112
|
"profile.name": "Lucas", //update values
|
|
103
113
|
"works.1": "gs", //nested array update
|
|
104
114
|
"history.1.available": (currentValue) => (!currentValue), //functional update
|
|
@@ -116,7 +126,7 @@ Arguments:
|
|
|
116
126
|
## Example
|
|
117
127
|
|
|
118
128
|
```js
|
|
119
|
-
import {
|
|
129
|
+
import { extendableUpdate } from "maraj";
|
|
120
130
|
|
|
121
131
|
const data = {
|
|
122
132
|
profile: { name: "Lukasz" },
|
|
@@ -127,7 +137,7 @@ const data = {
|
|
|
127
137
|
]
|
|
128
138
|
}
|
|
129
139
|
|
|
130
|
-
const newUpdatedObject =
|
|
140
|
+
const newUpdatedObject = extendableUpdate(data, {
|
|
131
141
|
"profile.age": 28, //add new property
|
|
132
142
|
"profile.name": "Lucas", //update values
|
|
133
143
|
"works.10": "gs", //increases the array (filling it up with empty/undefined) and sets the value in the index of 10 to "gs"
|
|
@@ -136,12 +146,12 @@ const newUpdatedObject = modifyImmutably(data, {
|
|
|
136
146
|
|
|
137
147
|
## Remove Utility
|
|
138
148
|
|
|
139
|
-
Removes the **key of an object** or the **index of an array** in the key provided in the **
|
|
149
|
+
Removes the **key of an object** or the **index of an array** in the key provided in the **update.**
|
|
140
150
|
|
|
141
151
|
```js
|
|
142
152
|
import {remove} from 'maraj'
|
|
143
153
|
|
|
144
|
-
const newUpdatedObject =
|
|
154
|
+
const newUpdatedObject = update(person,{
|
|
145
155
|
'profile': (value) => remove(value, 'name'), // returns new value without the "name" prop
|
|
146
156
|
'profile': (value) => remove(value, ['name', 'lastname']), // returns new value without the "name" and "lastname" prop
|
|
147
157
|
'works': (value) => remove(value, 1), // returns new array without the provided index (1)
|
|
@@ -185,7 +195,7 @@ const [remainingPath, lastKey] = splitPathAtLastKey(stringPath)
|
|
|
185
195
|
In some unusual cases (usually when updating an optional object prop inside an array) typescript infers incorrectly the optional key vs a key with undefined value. To fix this typing issue, use the "set" function so it correctly infers the type in the field.
|
|
186
196
|
|
|
187
197
|
```ts
|
|
188
|
-
const updatedValue =
|
|
198
|
+
const updatedValue = update(data, {
|
|
189
199
|
'files.0.optionalProperty': 'new name',
|
|
190
200
|
'files.1.optionalProperty': undefined, //no ts error
|
|
191
201
|
'files.0.optionalProperty': set('new name'),
|
package/dist/index.d.mts
CHANGED
|
@@ -1,24 +1,46 @@
|
|
|
1
|
+
/** Returns only the optional keys of an object. */
|
|
2
|
+
type OptionalKeys<T> = {
|
|
3
|
+
[K in keyof T]-?: {} extends Pick<T, K> ? K : never;
|
|
4
|
+
}[keyof T];
|
|
5
|
+
/** Removes properties or indexes, order doesn't matter. */
|
|
6
|
+
type Remove = <T extends object>(value: T, keys: T extends any[] ? (number | `${number}` | (number | `${number}`)[]) : OptionalKeys<T> | (OptionalKeys<T>)[]) => T;
|
|
7
|
+
/**
|
|
8
|
+
* Removes properties or indexes, order doesn't matter.
|
|
9
|
+
* @param value Object/array from where to remove keys/indexes shallowly.
|
|
10
|
+
* @param keys Keys to remove.
|
|
11
|
+
* @returns New object/array without the provided keys/indexes.
|
|
12
|
+
*/
|
|
13
|
+
declare const remove: Remove;
|
|
14
|
+
|
|
1
15
|
/** Index literal type to improve DX at managing updates. */
|
|
2
16
|
declare const LITERAL_INDEX = "${number}";
|
|
3
17
|
type LiteralIndex = typeof LITERAL_INDEX;
|
|
4
|
-
type
|
|
18
|
+
type OptionValues = 'error' | 'skip' | 'add';
|
|
19
|
+
type Options = {
|
|
20
|
+
onNewProps?: OptionValues;
|
|
21
|
+
onNewIndexes?: OptionValues;
|
|
22
|
+
};
|
|
23
|
+
type ImmutableUpdate = <TObject extends Record<PropertyKey, any>>(state: TObject, dotPathUpdateObject: UpdateObject<TObject>, options?: Options) => TObject;
|
|
5
24
|
/**
|
|
6
|
-
* Creates a new object applying
|
|
25
|
+
* Creates a new object applying immutable updates using dot-path syntax.
|
|
26
|
+
* Can add or skip new props or indexes depending on options.
|
|
27
|
+
* TYPESCRIPT: Dot-Path Update Object type must STRICTLY match the type of state.
|
|
7
28
|
* @param state Original State Object.
|
|
8
|
-
* @param
|
|
9
|
-
* @param
|
|
29
|
+
* @param dotPathUpdateObject Dot Path Update Object.
|
|
30
|
+
* @param options Options to change JS behaviour on non-existent fields.
|
|
10
31
|
* @returns New Updated Object.
|
|
11
32
|
*/
|
|
12
|
-
declare const
|
|
13
|
-
type
|
|
33
|
+
declare const update: ImmutableUpdate;
|
|
34
|
+
type ExtendableImmutableUpdate = <TObject extends Record<PropertyKey, any>, TUpdateObject extends UpdateObject<TObject>>(state: TObject, dotPathUpdateObject: TUpdateObject) => ExtendedUpdate<TObject, TUpdateObject>;
|
|
14
35
|
/**
|
|
15
|
-
* Creates a new object applying immutable
|
|
36
|
+
* Creates a new object applying immutable updates using dot-path syntax.
|
|
37
|
+
* It will always add non-existent props or indexes if provided.
|
|
38
|
+
* TYPESCRIPT: Dot-Path Update Object type extends from the type of state and you can add new non-existent properties.
|
|
16
39
|
* @param state Original State Object.
|
|
17
|
-
* @param
|
|
18
|
-
* @param addNonExistentPropsAndIndexes If it should or not create non-existent fields.
|
|
40
|
+
* @param dotPathUpdateObject Dot Path Update Object.
|
|
19
41
|
* @returns New Updated Object.
|
|
20
42
|
*/
|
|
21
|
-
declare const
|
|
43
|
+
declare const extendableUpdate: ExtendableImmutableUpdate;
|
|
22
44
|
|
|
23
45
|
/**
|
|
24
46
|
* --------------------------------------------
|
|
@@ -37,11 +59,11 @@ type IsTuple<T extends ReadonlyArray<any>> = number extends T['length'] ? false
|
|
|
37
59
|
* --------------------------------------------
|
|
38
60
|
*/
|
|
39
61
|
/** Dot-paths of an array. */
|
|
40
|
-
type ArrayPaths<TInnerType, TAllowedTypes> = TInnerType extends TAllowedTypes ? TInnerType extends PrimitivesAndNativeObjects ? `${ArrayIndexes}` : `${ArrayIndexes}` | `${ArrayIndexes}.${DotPaths<TInnerType, TAllowedTypes>}` : `${ArrayIndexes}.${DotPaths<TInnerType, TAllowedTypes>}`;
|
|
62
|
+
type ArrayPaths<TInnerType, TAllowedTypes> = TInnerType extends TAllowedTypes ? TInnerType extends PrimitivesAndNativeObjects ? number | `${ArrayIndexes}` : number | `${ArrayIndexes}` | `${ArrayIndexes}.${DotPaths<TInnerType, TAllowedTypes>}` : `${ArrayIndexes}.${DotPaths<TInnerType, TAllowedTypes>}`;
|
|
41
63
|
/** Dot-paths of an object. */
|
|
42
64
|
type ObjectPaths<T, TAllowedTypes> = {
|
|
43
|
-
[Key in keyof T & string]: T[Key] extends TAllowedTypes ? T[Key] extends PrimitivesAndNativeObjects ? `${Key}` : `${Key}` | `${Key}.${DotPaths<T[Key], TAllowedTypes>}` : `${Key}.${DotPaths<T[Key], TAllowedTypes>}`;
|
|
44
|
-
}[keyof T & string];
|
|
65
|
+
[Key in keyof T & (string | number)]: T[Key] extends TAllowedTypes ? T[Key] extends PrimitivesAndNativeObjects ? `${Key}` : `${Key}` | `${Key}.${DotPaths<T[Key], TAllowedTypes>}` : `${Key}.${DotPaths<T[Key], TAllowedTypes>}`;
|
|
66
|
+
}[keyof T & (string | number)];
|
|
45
67
|
/** Dot-paths of a tuple. */
|
|
46
68
|
type TuplePaths<T, TAllowedTypes> = ObjectPaths<Omit<T, keyof any[]>, TAllowedTypes>;
|
|
47
69
|
/** Literal union of all posible dot-paths which satisfy provided types as second argument (default: any). */
|
|
@@ -52,53 +74,70 @@ type DotPaths<T, TAllowedTypes = any> = T extends ReadonlyArray<infer U> ? IsTup
|
|
|
52
74
|
* --------------------------------------------
|
|
53
75
|
*/
|
|
54
76
|
/** Nested type implementation. */
|
|
55
|
-
type
|
|
56
|
-
/** Value of a nested property of a given dot-path. */
|
|
57
|
-
type ValueInDotPath<TObject, TPath extends DotPaths<TObject> | ''> = NestedType<TObject, TPath>;
|
|
77
|
+
type ValueInPath<TObject, TPath> = TPath extends '' ? TObject : TPath extends keyof TObject ? TObject[TPath] : TPath extends LiteralIndex ? never : TPath extends `${number}` ? TObject extends ReadonlyArray<infer U> ? U : never : TPath extends `${infer K}.${infer R}` ? K extends keyof TObject ? ValueInPath<TObject[K], R> : K extends LiteralIndex ? never : K extends `${number}` ? TObject extends ReadonlyArray<infer U> ? ValueInPath<U, R> : never : never : never;
|
|
58
78
|
/**
|
|
59
79
|
* --------------------------------------------
|
|
60
80
|
* RETURNED VALUE OF A NESTED PROPERTY PROVIDING A DOT PATH
|
|
61
81
|
* --------------------------------------------
|
|
62
82
|
*/
|
|
63
83
|
/** Returned nested type implementation. */
|
|
64
|
-
type
|
|
65
|
-
/** Value of a RETURNED nested property of a given dot-path (so when accessing arrays can be undefined). */
|
|
66
|
-
type ReturnedValueInDotPath<TObject, TPath extends DotPaths<TObject> | ''> = ReturnedNestedValue<TObject, TPath>;
|
|
84
|
+
type ReturnedValueInPath<TObject, TPath> = TPath extends '' ? TObject : TPath extends keyof TObject ? TPath extends number ? TObject extends ReadonlyArray<infer U> ? (U | undefined) : TObject[TPath] : TObject[TPath] : TPath extends `${number}` ? TObject extends ReadonlyArray<infer U> ? (U | undefined) : never : TPath extends LiteralIndex ? never : TPath extends `${infer K}.${infer R}` ? K extends keyof TObject ? ReturnedValueInPath<TObject[K], R> : K extends LiteralIndex ? never : K extends `${number}` ? TObject extends ReadonlyArray<infer U> ? (ReturnedValueInPath<U, R> | undefined) : never : never : never;
|
|
67
85
|
/**
|
|
68
86
|
* --------------------------------------------
|
|
69
87
|
* UPDATE OBJECT WITH DOT PATH IMPLEMENTATION
|
|
70
88
|
* --------------------------------------------
|
|
71
89
|
*/
|
|
72
90
|
/** Value of a dot-path update object, can be the value or a function. */
|
|
73
|
-
type
|
|
91
|
+
type UpdateValue<TObject, TPath> = ValueInPath<TObject, TPath> | ((fieldValue: ValueInPath<TObject, TPath>) => ValueInPath<TObject, TPath>);
|
|
74
92
|
/** Dot-path update object, used to give updates to the updateImmutably function. */
|
|
75
|
-
type
|
|
76
|
-
[TPath in DotPaths<TObject>]?:
|
|
93
|
+
type UpdateObject<TObject> = {
|
|
94
|
+
[TPath in DotPaths<TObject>]?: ValueInPath<TObject, TPath> | ((fieldValue: ValueInPath<TObject, TPath>) => ValueInPath<TObject, TPath>);
|
|
77
95
|
};
|
|
78
96
|
/**
|
|
79
97
|
* --------------------------------------------
|
|
80
|
-
*
|
|
98
|
+
* EXTRACT TYPE FROM DOT-PATH UPDATE OBJECT
|
|
81
99
|
* --------------------------------------------
|
|
82
100
|
*/
|
|
83
|
-
/**
|
|
84
|
-
|
|
85
|
-
|
|
101
|
+
/** Merging two objects */
|
|
102
|
+
/** Keys which arent dot-path keys */
|
|
103
|
+
type NonDotPathKeys<T> = {
|
|
104
|
+
[Key in keyof T]: Key extends `${infer K}.${string}` ? K : Key;
|
|
86
105
|
}[keyof T];
|
|
87
|
-
/**
|
|
88
|
-
type
|
|
89
|
-
[
|
|
106
|
+
/** Keys which are an index */
|
|
107
|
+
type KeysRepresentingAnIndex<T> = {
|
|
108
|
+
[Key in keyof T]: Key extends `${number}.${string}` ? never : Key;
|
|
90
109
|
}[keyof T];
|
|
110
|
+
/** Keys which start with an index */
|
|
111
|
+
type KeyPathsStartingWithAnIndex<T> = {
|
|
112
|
+
[Key in keyof T]: Key extends `${number}.${string}` ? Key : never;
|
|
113
|
+
}[keyof T];
|
|
114
|
+
/** Keys which are paths and its first key-path is removed */
|
|
115
|
+
type NestedPathKeys<T, K extends string | number> = {
|
|
116
|
+
[Key in keyof T]: Key extends `${K}.${infer U}` ? U : never;
|
|
117
|
+
}[keyof T];
|
|
118
|
+
/** Extract value if its a function */
|
|
119
|
+
type Value<T> = T extends (...args: any) => any ? ReturnType<T> : T;
|
|
120
|
+
/** To check if an empty object is provided */
|
|
121
|
+
type EmptyObject = Record<string | number | symbol, never>;
|
|
122
|
+
/** Used to extract the type from a dot-path update object. */
|
|
123
|
+
type ExtractTypeFromDotPathUpdateObject<T extends Record<string, any>> = T extends EmptyObject ? {} : keyof T extends LiteralIndex | number | `${number}` | `${number}.${infer U}` ? Value<T[KeyPathsStartingWithAnIndex<T>]> extends never ? Array<Value<T[KeysRepresentingAnIndex<T>]>> : Array<Value<T[KeysRepresentingAnIndex<T>]> | {
|
|
124
|
+
[Key2 in U]: Value<T[KeyPathsStartingWithAnIndex<T>]>;
|
|
125
|
+
}> : {
|
|
126
|
+
[Key1 in NonDotPathKeys<T> & string]: Key1 extends keyof T ? Value<T[Key1]> extends Array<infer U> ? ExtractTypeFromDotPathUpdateObject<{
|
|
127
|
+
[Key2 in NestedPathKeys<T, Key1>]: Value<T[`${Key1}.${Key2}`]>;
|
|
128
|
+
}> extends Array<infer W> ? Array<U | W> : Array<U> & ExtractTypeFromDotPathUpdateObject<{
|
|
129
|
+
[Key2 in NestedPathKeys<T, Key1>]: Value<T[`${Key1}.${Key2}`]>;
|
|
130
|
+
}> : Value<T[Key1]> : ExtractTypeFromDotPathUpdateObject<{
|
|
131
|
+
[Key2 in NestedPathKeys<T, Key1>]: Value<T[`${Key1}.${Key2}`]>;
|
|
132
|
+
}>;
|
|
133
|
+
} & unknown;
|
|
134
|
+
/** New expanded value after aplying updates including new properties */
|
|
135
|
+
type ExtendedUpdate<TObject extends Record<string | number | symbol, any>, TDotPathUpdateObject extends Record<string | number | symbol, any>> = TObject extends Array<infer U> ? ExtractTypeFromDotPathUpdateObject<TDotPathUpdateObject> extends Array<infer W> ? Array<U | W> : TObject & ExtractTypeFromDotPathUpdateObject<TDotPathUpdateObject> : ExtractTypeFromDotPathUpdateObject<TObject & TDotPathUpdateObject>;
|
|
91
136
|
|
|
92
|
-
type
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
* @param keys Keys to remove.
|
|
97
|
-
* @returns New object/array without the provided keys/indexes.
|
|
98
|
-
*/
|
|
99
|
-
declare const remove: Remove;
|
|
100
|
-
|
|
101
|
-
type Select = <TObject, TPath extends DotPaths<TObject> | ''>(state: TObject, path: TPath) => ReturnedValueInDotPath<TObject, TPath>;
|
|
137
|
+
type Select = {
|
|
138
|
+
<T, TPath extends DotPaths<T>>(state: T, path: TPath): ReturnedValueInPath<T, TPath>;
|
|
139
|
+
<T, TPath extends ''>(state: T, path: TPath): T;
|
|
140
|
+
};
|
|
102
141
|
/**
|
|
103
142
|
* Returns the value from a given dot path of an object/array or undefined if doesn't exist.
|
|
104
143
|
* @param state State from which retrieve the value.
|
|
@@ -131,4 +170,4 @@ declare const isObjectLiteral: (valueToTest: any) => boolean;
|
|
|
131
170
|
declare const parseStringIndex: (v: string) => number | null;
|
|
132
171
|
declare const isStringIndex: (v: string) => boolean;
|
|
133
172
|
|
|
134
|
-
export { type
|
|
173
|
+
export { type DotPaths, type LiteralIndex, type ReturnedValueInPath, type UpdateObject, type UpdateValue, type ValueInPath, extendableUpdate, isObjectLiteral, isStringIndex, parseStringIndex, remove, select, set, splitPathAtLastKey, update };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,24 +1,46 @@
|
|
|
1
|
+
/** Returns only the optional keys of an object. */
|
|
2
|
+
type OptionalKeys<T> = {
|
|
3
|
+
[K in keyof T]-?: {} extends Pick<T, K> ? K : never;
|
|
4
|
+
}[keyof T];
|
|
5
|
+
/** Removes properties or indexes, order doesn't matter. */
|
|
6
|
+
type Remove = <T extends object>(value: T, keys: T extends any[] ? (number | `${number}` | (number | `${number}`)[]) : OptionalKeys<T> | (OptionalKeys<T>)[]) => T;
|
|
7
|
+
/**
|
|
8
|
+
* Removes properties or indexes, order doesn't matter.
|
|
9
|
+
* @param value Object/array from where to remove keys/indexes shallowly.
|
|
10
|
+
* @param keys Keys to remove.
|
|
11
|
+
* @returns New object/array without the provided keys/indexes.
|
|
12
|
+
*/
|
|
13
|
+
declare const remove: Remove;
|
|
14
|
+
|
|
1
15
|
/** Index literal type to improve DX at managing updates. */
|
|
2
16
|
declare const LITERAL_INDEX = "${number}";
|
|
3
17
|
type LiteralIndex = typeof LITERAL_INDEX;
|
|
4
|
-
type
|
|
18
|
+
type OptionValues = 'error' | 'skip' | 'add';
|
|
19
|
+
type Options = {
|
|
20
|
+
onNewProps?: OptionValues;
|
|
21
|
+
onNewIndexes?: OptionValues;
|
|
22
|
+
};
|
|
23
|
+
type ImmutableUpdate = <TObject extends Record<PropertyKey, any>>(state: TObject, dotPathUpdateObject: UpdateObject<TObject>, options?: Options) => TObject;
|
|
5
24
|
/**
|
|
6
|
-
* Creates a new object applying
|
|
25
|
+
* Creates a new object applying immutable updates using dot-path syntax.
|
|
26
|
+
* Can add or skip new props or indexes depending on options.
|
|
27
|
+
* TYPESCRIPT: Dot-Path Update Object type must STRICTLY match the type of state.
|
|
7
28
|
* @param state Original State Object.
|
|
8
|
-
* @param
|
|
9
|
-
* @param
|
|
29
|
+
* @param dotPathUpdateObject Dot Path Update Object.
|
|
30
|
+
* @param options Options to change JS behaviour on non-existent fields.
|
|
10
31
|
* @returns New Updated Object.
|
|
11
32
|
*/
|
|
12
|
-
declare const
|
|
13
|
-
type
|
|
33
|
+
declare const update: ImmutableUpdate;
|
|
34
|
+
type ExtendableImmutableUpdate = <TObject extends Record<PropertyKey, any>, TUpdateObject extends UpdateObject<TObject>>(state: TObject, dotPathUpdateObject: TUpdateObject) => ExtendedUpdate<TObject, TUpdateObject>;
|
|
14
35
|
/**
|
|
15
|
-
* Creates a new object applying immutable
|
|
36
|
+
* Creates a new object applying immutable updates using dot-path syntax.
|
|
37
|
+
* It will always add non-existent props or indexes if provided.
|
|
38
|
+
* TYPESCRIPT: Dot-Path Update Object type extends from the type of state and you can add new non-existent properties.
|
|
16
39
|
* @param state Original State Object.
|
|
17
|
-
* @param
|
|
18
|
-
* @param addNonExistentPropsAndIndexes If it should or not create non-existent fields.
|
|
40
|
+
* @param dotPathUpdateObject Dot Path Update Object.
|
|
19
41
|
* @returns New Updated Object.
|
|
20
42
|
*/
|
|
21
|
-
declare const
|
|
43
|
+
declare const extendableUpdate: ExtendableImmutableUpdate;
|
|
22
44
|
|
|
23
45
|
/**
|
|
24
46
|
* --------------------------------------------
|
|
@@ -37,11 +59,11 @@ type IsTuple<T extends ReadonlyArray<any>> = number extends T['length'] ? false
|
|
|
37
59
|
* --------------------------------------------
|
|
38
60
|
*/
|
|
39
61
|
/** Dot-paths of an array. */
|
|
40
|
-
type ArrayPaths<TInnerType, TAllowedTypes> = TInnerType extends TAllowedTypes ? TInnerType extends PrimitivesAndNativeObjects ? `${ArrayIndexes}` : `${ArrayIndexes}` | `${ArrayIndexes}.${DotPaths<TInnerType, TAllowedTypes>}` : `${ArrayIndexes}.${DotPaths<TInnerType, TAllowedTypes>}`;
|
|
62
|
+
type ArrayPaths<TInnerType, TAllowedTypes> = TInnerType extends TAllowedTypes ? TInnerType extends PrimitivesAndNativeObjects ? number | `${ArrayIndexes}` : number | `${ArrayIndexes}` | `${ArrayIndexes}.${DotPaths<TInnerType, TAllowedTypes>}` : `${ArrayIndexes}.${DotPaths<TInnerType, TAllowedTypes>}`;
|
|
41
63
|
/** Dot-paths of an object. */
|
|
42
64
|
type ObjectPaths<T, TAllowedTypes> = {
|
|
43
|
-
[Key in keyof T & string]: T[Key] extends TAllowedTypes ? T[Key] extends PrimitivesAndNativeObjects ? `${Key}` : `${Key}` | `${Key}.${DotPaths<T[Key], TAllowedTypes>}` : `${Key}.${DotPaths<T[Key], TAllowedTypes>}`;
|
|
44
|
-
}[keyof T & string];
|
|
65
|
+
[Key in keyof T & (string | number)]: T[Key] extends TAllowedTypes ? T[Key] extends PrimitivesAndNativeObjects ? `${Key}` : `${Key}` | `${Key}.${DotPaths<T[Key], TAllowedTypes>}` : `${Key}.${DotPaths<T[Key], TAllowedTypes>}`;
|
|
66
|
+
}[keyof T & (string | number)];
|
|
45
67
|
/** Dot-paths of a tuple. */
|
|
46
68
|
type TuplePaths<T, TAllowedTypes> = ObjectPaths<Omit<T, keyof any[]>, TAllowedTypes>;
|
|
47
69
|
/** Literal union of all posible dot-paths which satisfy provided types as second argument (default: any). */
|
|
@@ -52,53 +74,70 @@ type DotPaths<T, TAllowedTypes = any> = T extends ReadonlyArray<infer U> ? IsTup
|
|
|
52
74
|
* --------------------------------------------
|
|
53
75
|
*/
|
|
54
76
|
/** Nested type implementation. */
|
|
55
|
-
type
|
|
56
|
-
/** Value of a nested property of a given dot-path. */
|
|
57
|
-
type ValueInDotPath<TObject, TPath extends DotPaths<TObject> | ''> = NestedType<TObject, TPath>;
|
|
77
|
+
type ValueInPath<TObject, TPath> = TPath extends '' ? TObject : TPath extends keyof TObject ? TObject[TPath] : TPath extends LiteralIndex ? never : TPath extends `${number}` ? TObject extends ReadonlyArray<infer U> ? U : never : TPath extends `${infer K}.${infer R}` ? K extends keyof TObject ? ValueInPath<TObject[K], R> : K extends LiteralIndex ? never : K extends `${number}` ? TObject extends ReadonlyArray<infer U> ? ValueInPath<U, R> : never : never : never;
|
|
58
78
|
/**
|
|
59
79
|
* --------------------------------------------
|
|
60
80
|
* RETURNED VALUE OF A NESTED PROPERTY PROVIDING A DOT PATH
|
|
61
81
|
* --------------------------------------------
|
|
62
82
|
*/
|
|
63
83
|
/** Returned nested type implementation. */
|
|
64
|
-
type
|
|
65
|
-
/** Value of a RETURNED nested property of a given dot-path (so when accessing arrays can be undefined). */
|
|
66
|
-
type ReturnedValueInDotPath<TObject, TPath extends DotPaths<TObject> | ''> = ReturnedNestedValue<TObject, TPath>;
|
|
84
|
+
type ReturnedValueInPath<TObject, TPath> = TPath extends '' ? TObject : TPath extends keyof TObject ? TPath extends number ? TObject extends ReadonlyArray<infer U> ? (U | undefined) : TObject[TPath] : TObject[TPath] : TPath extends `${number}` ? TObject extends ReadonlyArray<infer U> ? (U | undefined) : never : TPath extends LiteralIndex ? never : TPath extends `${infer K}.${infer R}` ? K extends keyof TObject ? ReturnedValueInPath<TObject[K], R> : K extends LiteralIndex ? never : K extends `${number}` ? TObject extends ReadonlyArray<infer U> ? (ReturnedValueInPath<U, R> | undefined) : never : never : never;
|
|
67
85
|
/**
|
|
68
86
|
* --------------------------------------------
|
|
69
87
|
* UPDATE OBJECT WITH DOT PATH IMPLEMENTATION
|
|
70
88
|
* --------------------------------------------
|
|
71
89
|
*/
|
|
72
90
|
/** Value of a dot-path update object, can be the value or a function. */
|
|
73
|
-
type
|
|
91
|
+
type UpdateValue<TObject, TPath> = ValueInPath<TObject, TPath> | ((fieldValue: ValueInPath<TObject, TPath>) => ValueInPath<TObject, TPath>);
|
|
74
92
|
/** Dot-path update object, used to give updates to the updateImmutably function. */
|
|
75
|
-
type
|
|
76
|
-
[TPath in DotPaths<TObject>]?:
|
|
93
|
+
type UpdateObject<TObject> = {
|
|
94
|
+
[TPath in DotPaths<TObject>]?: ValueInPath<TObject, TPath> | ((fieldValue: ValueInPath<TObject, TPath>) => ValueInPath<TObject, TPath>);
|
|
77
95
|
};
|
|
78
96
|
/**
|
|
79
97
|
* --------------------------------------------
|
|
80
|
-
*
|
|
98
|
+
* EXTRACT TYPE FROM DOT-PATH UPDATE OBJECT
|
|
81
99
|
* --------------------------------------------
|
|
82
100
|
*/
|
|
83
|
-
/**
|
|
84
|
-
|
|
85
|
-
|
|
101
|
+
/** Merging two objects */
|
|
102
|
+
/** Keys which arent dot-path keys */
|
|
103
|
+
type NonDotPathKeys<T> = {
|
|
104
|
+
[Key in keyof T]: Key extends `${infer K}.${string}` ? K : Key;
|
|
86
105
|
}[keyof T];
|
|
87
|
-
/**
|
|
88
|
-
type
|
|
89
|
-
[
|
|
106
|
+
/** Keys which are an index */
|
|
107
|
+
type KeysRepresentingAnIndex<T> = {
|
|
108
|
+
[Key in keyof T]: Key extends `${number}.${string}` ? never : Key;
|
|
90
109
|
}[keyof T];
|
|
110
|
+
/** Keys which start with an index */
|
|
111
|
+
type KeyPathsStartingWithAnIndex<T> = {
|
|
112
|
+
[Key in keyof T]: Key extends `${number}.${string}` ? Key : never;
|
|
113
|
+
}[keyof T];
|
|
114
|
+
/** Keys which are paths and its first key-path is removed */
|
|
115
|
+
type NestedPathKeys<T, K extends string | number> = {
|
|
116
|
+
[Key in keyof T]: Key extends `${K}.${infer U}` ? U : never;
|
|
117
|
+
}[keyof T];
|
|
118
|
+
/** Extract value if its a function */
|
|
119
|
+
type Value<T> = T extends (...args: any) => any ? ReturnType<T> : T;
|
|
120
|
+
/** To check if an empty object is provided */
|
|
121
|
+
type EmptyObject = Record<string | number | symbol, never>;
|
|
122
|
+
/** Used to extract the type from a dot-path update object. */
|
|
123
|
+
type ExtractTypeFromDotPathUpdateObject<T extends Record<string, any>> = T extends EmptyObject ? {} : keyof T extends LiteralIndex | number | `${number}` | `${number}.${infer U}` ? Value<T[KeyPathsStartingWithAnIndex<T>]> extends never ? Array<Value<T[KeysRepresentingAnIndex<T>]>> : Array<Value<T[KeysRepresentingAnIndex<T>]> | {
|
|
124
|
+
[Key2 in U]: Value<T[KeyPathsStartingWithAnIndex<T>]>;
|
|
125
|
+
}> : {
|
|
126
|
+
[Key1 in NonDotPathKeys<T> & string]: Key1 extends keyof T ? Value<T[Key1]> extends Array<infer U> ? ExtractTypeFromDotPathUpdateObject<{
|
|
127
|
+
[Key2 in NestedPathKeys<T, Key1>]: Value<T[`${Key1}.${Key2}`]>;
|
|
128
|
+
}> extends Array<infer W> ? Array<U | W> : Array<U> & ExtractTypeFromDotPathUpdateObject<{
|
|
129
|
+
[Key2 in NestedPathKeys<T, Key1>]: Value<T[`${Key1}.${Key2}`]>;
|
|
130
|
+
}> : Value<T[Key1]> : ExtractTypeFromDotPathUpdateObject<{
|
|
131
|
+
[Key2 in NestedPathKeys<T, Key1>]: Value<T[`${Key1}.${Key2}`]>;
|
|
132
|
+
}>;
|
|
133
|
+
} & unknown;
|
|
134
|
+
/** New expanded value after aplying updates including new properties */
|
|
135
|
+
type ExtendedUpdate<TObject extends Record<string | number | symbol, any>, TDotPathUpdateObject extends Record<string | number | symbol, any>> = TObject extends Array<infer U> ? ExtractTypeFromDotPathUpdateObject<TDotPathUpdateObject> extends Array<infer W> ? Array<U | W> : TObject & ExtractTypeFromDotPathUpdateObject<TDotPathUpdateObject> : ExtractTypeFromDotPathUpdateObject<TObject & TDotPathUpdateObject>;
|
|
91
136
|
|
|
92
|
-
type
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
* @param keys Keys to remove.
|
|
97
|
-
* @returns New object/array without the provided keys/indexes.
|
|
98
|
-
*/
|
|
99
|
-
declare const remove: Remove;
|
|
100
|
-
|
|
101
|
-
type Select = <TObject, TPath extends DotPaths<TObject> | ''>(state: TObject, path: TPath) => ReturnedValueInDotPath<TObject, TPath>;
|
|
137
|
+
type Select = {
|
|
138
|
+
<T, TPath extends DotPaths<T>>(state: T, path: TPath): ReturnedValueInPath<T, TPath>;
|
|
139
|
+
<T, TPath extends ''>(state: T, path: TPath): T;
|
|
140
|
+
};
|
|
102
141
|
/**
|
|
103
142
|
* Returns the value from a given dot path of an object/array or undefined if doesn't exist.
|
|
104
143
|
* @param state State from which retrieve the value.
|
|
@@ -131,4 +170,4 @@ declare const isObjectLiteral: (valueToTest: any) => boolean;
|
|
|
131
170
|
declare const parseStringIndex: (v: string) => number | null;
|
|
132
171
|
declare const isStringIndex: (v: string) => boolean;
|
|
133
172
|
|
|
134
|
-
export { type
|
|
173
|
+
export { type DotPaths, type LiteralIndex, type ReturnedValueInPath, type UpdateObject, type UpdateValue, type ValueInPath, extendableUpdate, isObjectLiteral, isStringIndex, parseStringIndex, remove, select, set, splitPathAtLastKey, update };
|
package/dist/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";var
|
|
1
|
+
"use strict";var m=Object.defineProperty;var R=Object.getOwnPropertyDescriptor;var $=Object.getOwnPropertyNames;var k=Object.prototype.hasOwnProperty;var v=(e,t)=>{for(var r in t)m(e,r,{get:t[r],enumerable:!0})},C=(e,t,r,n)=>{if(t&&typeof t=="object"||typeof t=="function")for(let o of $(t))!k.call(e,o)&&o!==r&&m(e,o,{get:()=>t[o],enumerable:!(n=R(t,o))||n.enumerable});return e};var N=e=>C(m({},"__esModule",{value:!0}),e);var W={};v(W,{extendableUpdate:()=>L,isObjectLiteral:()=>d,isStringIndex:()=>y,parseStringIndex:()=>U,remove:()=>I,select:()=>j,set:()=>K,splitPathAtLastKey:()=>w,update:()=>E});module.exports=N(W);var I=(e,t)=>{if(!Array.isArray(e)&&e?.constructor!==Object)throw'Wrong type provided in "remove" function';let r=Array.isArray(t)?t:[t];if(Array.isArray(e)){let o=Symbol(),p=[...e];return r.forEach(s=>{p[s]=o}),p.filter(s=>s!==o)}let n={...e};return r.forEach(o=>delete n[o]),n};var j=(e,t)=>{if(typeof t!="string")throw new Error("path is not a string");if(!t)return e;let r=t.split("."),n=e;for(;r.length;){let o=r.join(".");if(n?.hasOwnProperty(o))return n[o];if(!n?.hasOwnProperty(r[0]))return;n=n[r[0]],r.shift()}return n};var w=e=>{if(typeof e!="string")throw new Error(`Wrong dotPath type: ${e}`);let t=e.lastIndexOf(".")+1,r=t?e.slice(0,t-1):"",n=e.slice(t);return[r,n]};var K=e=>()=>e;var d=e=>!!e&&Object.getPrototypeOf(e)===Object.prototype;var U=e=>{let t=parseInt(e);return t.toString()===e?t:null},y=e=>parseInt(e).toString()===e;var A=e=>JSON.stringify(e),h=e=>Array.isArray(e),g=(e,t,r={})=>{if(!d(e)&&!h(e))throw new Error(`"${A(e)}" is not an literalObject/array`);if(!d(t))throw new Error('wrong "updateObject" type provided');let n=Array.isArray(e)?[...e]:{...e},o={};for(let[p,x]of Object.entries(t)){if(p==="")continue;let s=p.split("."),l=o,i=n;for(;s.length>1;){let b=s.join(".");if(Object.hasOwn(i,b)){s=[b];break}let a=s[0],f=i[a],S=d(f),T=h(f),O=f===void 0;if(!S&&!T&&!O)throw new Error(`Property "${a}" in "${p}" is a primitive`);if(!l[a]){if(O){let V=h(i)&&y(a),D=y(s[1]),P=V?r.onNewIndexes:r.onNewProps;if(P==="error")throw new Error(`${a} doesn't exist in "${p}"`);if(P==="skip")continue;i[a]=D?[]:{}}else i[a]=T?[...f]:{...f};l[a]={}}i=i[a],l=l[a],s.shift()}let c=s[0],u=i;if(!Object.hasOwn(u,c)){let a=h(u)&&y(c)?r.onNewIndexes:r.onNewProps;if(a==="error")throw new Error(`Property/Index "${c}" doesnt exist in the path "${p}". Its value is: "${A(u)}"`);if(a==="skip")continue}u[c]=typeof x=="function"?x(u[c]):x,delete l[c]}return n},E=(e,t,r)=>g(e,t,r),L=(e,t)=>g(e,t);0&&(module.exports={extendableUpdate,isObjectLiteral,isStringIndex,parseStringIndex,remove,select,set,splitPathAtLastKey,update});
|
package/dist/index.mjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
var
|
|
1
|
+
var U=(e,t)=>{if(!Array.isArray(e)&&e?.constructor!==Object)throw'Wrong type provided in "remove" function';let r=Array.isArray(t)?t:[t];if(Array.isArray(e)){let s=Symbol(),p=[...e];return r.forEach(a=>{p[a]=s}),p.filter(a=>a!==s)}let n={...e};return r.forEach(s=>delete n[s]),n};var A=(e,t)=>{if(typeof t!="string")throw new Error("path is not a string");if(!t)return e;let r=t.split("."),n=e;for(;r.length;){let s=r.join(".");if(n?.hasOwnProperty(s))return n[s];if(!n?.hasOwnProperty(r[0]))return;n=n[r[0]],r.shift()}return n};var g=e=>{if(typeof e!="string")throw new Error(`Wrong dotPath type: ${e}`);let t=e.lastIndexOf(".")+1,r=t?e.slice(0,t-1):"",n=e.slice(t);return[r,n]};var E=e=>()=>e;var u=e=>!!e&&Object.getPrototypeOf(e)===Object.prototype;var L=e=>{let t=parseInt(e);return t.toString()===e?t:null},f=e=>parseInt(e).toString()===e;var P=e=>JSON.stringify(e),h=e=>Array.isArray(e),I=(e,t,r={})=>{if(!u(e)&&!h(e))throw new Error(`"${P(e)}" is not an literalObject/array`);if(!u(t))throw new Error('wrong "updateObject" type provided');let n=Array.isArray(e)?[...e]:{...e},s={};for(let[p,x]of Object.entries(t)){if(p==="")continue;let a=p.split("."),d=s,i=n;for(;a.length>1;){let b=a.join(".");if(Object.hasOwn(i,b)){a=[b];break}let o=a[0],l=i[o],j=u(l),m=h(l),T=l===void 0;if(!j&&!m&&!T)throw new Error(`Property "${o}" in "${p}" is a primitive`);if(!d[o]){if(T){let w=h(i)&&f(o),K=f(a[1]),O=w?r.onNewIndexes:r.onNewProps;if(O==="error")throw new Error(`${o} doesn't exist in "${p}"`);if(O==="skip")continue;i[o]=K?[]:{}}else i[o]=m?[...l]:{...l};d[o]={}}i=i[o],d=d[o],a.shift()}let c=a[0],y=i;if(!Object.hasOwn(y,c)){let o=h(y)&&f(c)?r.onNewIndexes:r.onNewProps;if(o==="error")throw new Error(`Property/Index "${c}" doesnt exist in the path "${p}". Its value is: "${P(y)}"`);if(o==="skip")continue}y[c]=typeof x=="function"?x(y[c]):x,delete d[c]}return n},S=(e,t,r)=>I(e,t,r),V=(e,t)=>I(e,t);export{V as extendableUpdate,u as isObjectLiteral,f as isStringIndex,L as parseStringIndex,U as remove,A as select,E as set,g as splitPathAtLastKey,S as update};
|