maraj 2.0.0 → 2.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,102 +1,100 @@
1
1
 
2
2
  # Maraj
3
3
 
4
- A lightweight alternative for creating **immutable updates of objects and arrays** such as Immer or ImmutableJS.
4
+ A fast lightweight utility for creating **immutable updates**. Full typescript support. Only 600 bytes gziped. Includes usefull utilities for manipulating updates (like remove, select).
5
5
 
6
- **4 times faster** and **over 10 times lighter**.
6
+ ### Features
7
7
 
8
- Fully typed.
8
+ - Full typescript support.
9
+ - Correct type inference.
10
+ - Multiple updates at same time mantaining the order of execution.
11
+ - Functional updates.
9
12
 
10
- Only **400 bytes** gziped and additional **300 bytes** for 4 common utilities that you will most likelly need in every project.
13
+ **<u>For Typescript:</u>** in tsconfig.json set **"strictNullChecks"** and **"noUncheckedIndexedAccess"** to true for correct type inference of optional properties (so it can correctly differentiate between optional props and props with possible undefined value)
11
14
 
12
- Features:
15
+ ```ts
16
+ const originalValue = { name: 'one', lastname: 'two' modified: false, files: [{ filename: 'one' }] }
13
17
 
14
- - Can accept multiple updates while mantaining the order of execution.
15
- - Easy syntax for conditional updates.
16
- - Functional updates.
18
+ const updatedValue = updateImmutably(orignalValue, {
19
+ 'name': 'updated',
20
+ 'modified': (value, fullObject) => !value,
21
+ 'files.0.filename': 'zero',
22
+ })
17
23
 
18
- Set "strictNullChecks" and "noUncheckedIndexedAccess" to true in tsconfig.json for correct type inference in optional properties (so it can correctly differentiate between optional props and props with possible undefined value)
24
+ // updatedValue = { name: 'updated', lastname: 'two' modified: true, files: [{ filename: 'zero' }] }
19
25
 
20
- ## Creating an Immutable Update
26
+ originalValue.files[1] === updatedValue.files[1] // true
27
+ ```
21
28
 
22
- The **updateImmutably** function has two arguments:
29
+ ### Limitations
23
30
 
24
- 1. The **Original object**.
25
- 2. An **Update object** (key-value pairs representing the updates we want to make)
31
+ - If "addNonExistentPropsAndIndexes" is enabled, numbers will always create an array if the array/object doesnt exist beforehand.
26
32
 
27
- The **Update Object** has the following syntax:
33
+ ```ts
34
+ const updatedValue = updateImmutably({}, {
35
+ 'files.1.name': 'new name',
36
+ }, true)
37
+ // updatedValue = { files: [<empty>, { name: 'new name' }] }
38
+ ```
28
39
 
29
- ```js
30
- {
31
- 'my.path.separated.by.dots': 'The new value',
32
- 'another.path': 'new value',
33
- 'myArray.0.name': 'new name in object inside array at index 0',
34
- [false && 'path.conditionalUpdate']: true
40
+ - The type "UpdateDotObject" using computed properties ( [key] ) 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)
41
+
42
+ ```ts
43
+ const updateFieldStore = <TPath extends DotPaths<Store>>(
44
+ path: TPath,
45
+ newValue: UpdateDotPathValue<Store, TPath>
46
+ ) => {
47
+ const update: UpdateDotPathObject<Store> = {
48
+ // ^throws ts error even if the typing is correct
49
+ [path]: newValue
50
+ } // fix: coherce it with " as UpdateDotPathObject<Store>"
51
+ return update
35
52
  }
36
53
  ```
37
54
 
38
- Multiple key-value pairs that mantains the order of execution.
55
+ - Use the "set" utility if typescript doesn't distinguish properly _"an optional field"_ vs a _"field which can have 'undefined' as type"_. Usually happens in optional nested props inside arrays.
56
+
57
+ ```ts
58
+ const updatedValue = updateImmutably(data, {
59
+ 'files.0.optionalProperty': 'new name',
60
+ 'files.1.optionalProperty': undefined, //no ts error
61
+ 'files.0.optionalProperty': set('new name'),
62
+ 'files.1.optionalProperty': set(undefined), //throws ts error, undefined cant be a value.
63
+ })
64
+ ```
65
+
66
+ ## Creating an Immutable Update
39
67
 
40
- - **Keys:** path name of the fiels, separated by dots.
41
- - For arrays just use its index value
42
- - Can accept conditional statements. (If the key = false or undefined or null it will skip that field update.)
68
+ Arguments:
43
69
 
44
- - **Value:** new value, any type. If its a function it ll be called with the current value in that field.
70
+ - Original Object
71
+ - Update Object
72
+ - If it should add non-existent properties/indexes
45
73
 
46
74
  ## Example
47
75
 
48
76
  ```js
49
- import updateImmutably, { spread } from "maraj";
77
+ import { updateImmutably } from "maraj";
50
78
 
51
79
  const data = {
52
- person: { name: "Lukasz", works: ["soum", "fi"] },
53
- works: [
80
+ profile: { name: "Lukasz" },
81
+ works: ["soum", "fi"],
82
+ history: [
54
83
  { title: "soum", size: 90, available: true, info: ["info1"] },
55
84
  { title: "fi", size: 30, available: false, info: ["infoA"] },
56
85
  ]
57
86
  }
58
87
 
59
88
  const newUpdatedObject = updateImmutably(data, {
60
- "age": 28, //add new values
61
- "person.name": "Lucas", //update values
62
- "person.works.1": "gs", //nested array update
63
- "works.1.available": (currentValue) => (!currentValue), //functional update
64
- [false && "works.0.size"]: 120, //conditional field update
65
- "person.works": (v) => spread(v, "portfolio") //common utilities
89
+ "profile.age": 28, //add new properties if "addNonExistentPropsAndIndexes" is enabled
90
+ "profile.name": "Lucas", //update values
91
+ "works.1": "gs", //nested array update
92
+ "history.1.available": (currentValue) => (!currentValue), //functional update
93
+ "works": (v) => [...v, 'portfolio'] //add new value to array
66
94
  })
67
-
68
- /** Does not make a deep copy and "falsy" fields are ignored */
69
- newUpdatedObject.works[0] === data.works[0] //true
70
-
71
- /** New Updated Object
72
- {
73
- person: { name: "Lucas", works: ["soum", "gs", "portfolio"], age: 28 },
74
- works: [
75
- { title: "soum", size: 90, available: true, info: ["info1"] },
76
- { title: "fi", size: 30, available: true, info: ["infoA"] },
77
- ]
78
- }
79
- */
80
95
  ```
81
96
 
82
- ## Other Utilities
83
-
84
- ### spread
85
-
86
- **For Objects and Arrays: Spreads** its argument into the key provided in the updateImmutably.
87
- **For the rest: Sets** its argument into the key provided in the updateImmutably.
88
-
89
- ```js
90
- import {spread} from 'maraj'
91
-
92
- const newUpdatedObject = updateImmutably(data,{
93
- 'person': (value) => spread(value, {info1: 'new Info'}), //same as (newValue) => (value) => ({...value, ...newValue})
94
- 'works': (value) => spread(value, {title: 'new project', size: 20}) //same as (newValueToAdd) => (value) => ([...value, ...newValueToAdd])
95
- 'person.works': (value) => spread(value, "portfolio") //same as (newValue) => (value) => (newValue)
96
- })
97
- ```
98
-
99
- ### remove
97
+ ## Remove Utility
100
98
 
101
99
  Removes the **key of an object** or the **index of an array** in the key provided in the **updateImmutably.**
102
100
 
@@ -104,36 +102,35 @@ Removes the **key of an object** or the **index of an array** in the key provide
104
102
  import {remove} from 'maraj'
105
103
 
106
104
  const newUpdatedObject = updateImmutably(person,{
107
- 'person': (value) => remove(value, 'works'), // returns new value (in this case "person") without the provided key ("works")
108
- 'works': (value) => remove(value, 1) // returns new array (in this case "works") without the provided index (1)
105
+ 'profile': (value) => remove(value, 'name'), // returns new value without the "name" prop
106
+ 'works': (value) => remove(value, 1), // returns new array without the provided index (1)
107
+ 'works': (value) => remove(value, [1, "0"]), // returns new array without the provided index (0,1)
109
108
  })
110
109
  ```
111
110
 
112
- ### select
113
-
114
- **Creates a store selector** from provided string path.
111
+ ## Select Utility
115
112
 
116
- Usefull in cases like getting data from Redux, Zustand, and so on. stores.
113
+ Selects the value of an object/array from a string-dot-path. Usefull in state management libraries.
117
114
 
118
115
  ```js
119
116
  import {select} from 'maraj'
120
117
 
121
118
  const stringPath = 'works.1.size'
122
119
 
123
- const storeSelector = (state) => select(state, stringPath) // will behave as (store) => store[works][1][size]
120
+ const work1SizeSelector = (state) => select(state, stringPath) // will behave as (store) => store[works][1][size]
124
121
  ```
125
122
 
126
- ### splitPathAtLastKey
123
+ ## SplitPathAtLastKey Utility
127
124
 
128
- Usefull when you want to spread multiple values into objects dynamically
125
+ Usefull when you want to retrieve the last key (for example an id).
129
126
 
130
127
  Splits string path (example:'projects.1.data.size' ) into two:
131
128
 
132
- - id: the last key in the path (in this case 'size')
133
129
  - remainingPath: the remaining path (in this case 'projects.1.data')
130
+ - lastKey: the last key in the path (in this case 'size')
134
131
 
135
132
  ```js
136
- import {splitPathAtLastKey} from 'maraj'
133
+ import { splitPathAtLastKey } from 'maraj'
137
134
 
138
135
  const stringPath = 'works.1.size'
139
136
 
@@ -141,3 +138,16 @@ const [remainingPath, last] = splitPathAtLastKey(stringPath)
141
138
  // last = 'size'
142
139
  // remainingPath = 'works.1'
143
140
  ```
141
+
142
+ ## Set Utility
143
+
144
+ 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.
145
+
146
+ ```ts
147
+ const updatedValue = updateImmutably(data, {
148
+ 'files.0.optionalProperty': 'new name',
149
+ 'files.1.optionalProperty': undefined, //no ts error
150
+ 'files.0.optionalProperty': set('new name'),
151
+ 'files.1.optionalProperty': set(undefined), //throws ts error, undefined cant be a value.
152
+ })
153
+ ```
package/dist/index.d.mts CHANGED
@@ -1,25 +1,17 @@
1
- type Remove = <T extends object>(value: T, keys: T extends any[] ? (number | `${number}` | (number | `${number}`)[]) : (keyof T | (keyof T)[])) => T;
2
- /**
3
- * Removes properties or indexes, order doesn't matter.
4
- */
5
- declare const remove: Remove;
6
-
7
1
  /**
8
2
  * Paths implementation
9
3
  */
10
4
  type PrimitivesAndNativeObjects = null | undefined | string | number | boolean | symbol | bigint | Date | FileList | File;
11
5
  type IsTuple<T extends ReadonlyArray<any>> = number extends T['length'] ? false : true;
12
6
  type Index = '_INDEX_';
13
- type RootArrayPathEnum = `${number}` | Index;
14
- type ArrayPathEnum<TLength extends boolean> = TLength extends true ? RootArrayPathEnum | 'length' : RootArrayPathEnum;
15
- type ArrayPaths<TInnerType, TLength extends boolean> = `${ArrayPathEnum<TLength>}` | `${ArrayPathEnum<false>}.${RootFlatten<TInnerType, TLength>}`;
16
- type FilteredTuple<T, TLength> = TLength extends string ? Omit<T, Exclude<keyof any[], 'length'>> : Omit<T, keyof any[]>;
17
- type ObjectPaths<T, TLength extends boolean> = {
18
- [Key in keyof T & string]: `${Key}` | `${Key}.${RootFlatten<T[Key], TLength>}`;
7
+ type ArrayIndexPaths = `${number}` | Index;
8
+ type ArrayPaths<TInnerType> = `${ArrayIndexPaths}` | `${ArrayIndexPaths}.${Flatten<TInnerType>}`;
9
+ type ObjectPaths<T> = {
10
+ [Key in keyof T & string]: `${Key}` | `${Key}.${Flatten<T[Key]>}`;
19
11
  }[keyof T & string];
20
- type RootFlatten<T, TIncludeLength extends boolean> = T extends ReadonlyArray<infer U> ? IsTuple<T> extends true ? ObjectPaths<FilteredTuple<T, TIncludeLength>, TIncludeLength> : ArrayPaths<U, TIncludeLength> : T extends string ? TIncludeLength extends true ? 'length' : never : T extends PrimitivesAndNativeObjects ? never : ObjectPaths<T, TIncludeLength>;
21
- type DotPaths<T> = RootFlatten<T, false>;
22
- type DotPathsIncludingLength<T> = RootFlatten<T, true>;
12
+ type TuplePaths<T> = ObjectPaths<Omit<T, keyof any[]>>;
13
+ type Flatten<T> = T extends ReadonlyArray<infer U> ? IsTuple<T> extends true ? TuplePaths<T> : ArrayPaths<U> : T extends PrimitivesAndNativeObjects ? never : ObjectPaths<T>;
14
+ type DotPaths<T> = Flatten<T>;
23
15
  /**
24
16
  * Type to know when a path is indexing an array
25
17
  */
@@ -27,14 +19,31 @@ type PathIndexingArray<T extends string | number> = `${T}` | `${T}.${string}` |
27
19
  /**
28
20
  * Nested Property Type implementation
29
21
  */
30
- type NestedProperty<TObject, TPath extends string> = TPath extends '' ? TObject : TPath extends keyof TObject ? TObject[TPath] : TPath extends (`${number}` | Index) ? TObject extends Array<infer U> ? TPath extends `${number}.${infer K}` ? K extends keyof U ? NestedProperty<U, K> : never : U : never : TPath extends `${infer K}.${infer R}` ? K extends keyof TObject ? NestedProperty<Required<TObject>[K], R> : K extends `${number}` | Index ? TObject extends Array<infer U> ? NestedProperty<U, R> : never : never : never;
22
+ type NestedProperty<TObject, TPath> = TPath extends '' ? TObject : TPath extends keyof TObject ? TObject[TPath] : TPath extends (`${number}` | Index) ? TObject extends Array<infer U> ? TPath extends `${number}.${infer K}` ? K extends keyof U ? NestedProperty<U, K> : never : U : never : TPath extends `${infer K}.${infer R}` ? K extends keyof TObject ? NestedProperty<Required<TObject>[K], R> : K extends `${number}` | Index ? TObject extends Array<infer U> ? NestedProperty<U, R> : never : never : never;
31
23
  type ValueInDotPath<TObject, TPath extends DotPaths<TObject> | ''> = NestedProperty<TObject, TPath>;
24
+ type DotPathUpdateValue<TObject, TPath extends DotPaths<TObject>> = TPath extends PathIndexingArray<Index> ? [`PATH ERROR: replace _INDEX_ with a NUMBER`, ValueInDotPath<TObject, TPath>, ((fieldValue: ValueInDotPath<TObject, TPath>, fullObject: TObject) => ValueInDotPath<TObject, TPath>)] : ValueInDotPath<TObject, TPath> | ((fieldValue: ValueInDotPath<TObject, TPath>, fullObject: TObject) => ValueInDotPath<TObject, TPath>);
32
25
  /**
33
26
  * Update Object Type Implementation
34
27
  */
35
- type DotPathUpdateObject<T> = Partial<{
36
- [K in DotPaths<T>]: K extends PathIndexingArray<Index> ? [`TS PATH ERROR: replace _INDEX_ with {NUMBER}`, ValueInDotPath<T, K>, ((value: ValueInDotPath<T, K>) => ValueInDotPath<T, K>)] : ValueInDotPath<T, K> | ((value: ValueInDotPath<T, K>) => ValueInDotPath<T, K>);
37
- }>;
28
+ type DotPathUpdateObject<TObject> = {
29
+ [TKey in DotPaths<TObject>]?: DotPathUpdateValue<TObject, TKey>;
30
+ };
31
+ /**
32
+ * Optional and Required keys
33
+ */
34
+ type NonPartialKeys = string | number | boolean | symbol | bigint | null | object;
35
+ type OptionalKeys<T> = {
36
+ [K in keyof T & string]: T[K] extends NonPartialKeys ? never : K;
37
+ }[keyof T & string];
38
+ type RequiredKeys<T> = {
39
+ [K in keyof T & string]: T[K] extends NonPartialKeys ? K : never;
40
+ }[keyof T & string];
41
+
42
+ type Remove = <T extends object>(value: T, keys: T extends any[] ? (number | `${number}` | (number | `${number}`)[]) : (OptionalKeys<T> | (OptionalKeys<T>)[])) => T;
43
+ /**
44
+ * Removes properties or indexes, order doesn't matter.
45
+ */
46
+ declare const remove: Remove;
38
47
 
39
48
  type Select = <T extends object, TPath extends (DotPaths<T> | '')>(state: T, path: TPath) => TPath extends PathIndexingArray<Index> ? never : TPath extends PathIndexingArray<number> ? ValueInDotPath<T, TPath> | undefined : ValueInDotPath<T, TPath>;
40
49
  /**
@@ -43,47 +52,26 @@ type Select = <T extends object, TPath extends (DotPaths<T> | '')>(state: T, pat
43
52
  */
44
53
  declare const select: Select;
45
54
 
46
- type SplitDotPathAtLastKey = <T extends string>(dotPath: T) => [string, string];
55
+ type PathWithoutLastKey = string;
56
+ type LastKey = string;
57
+ type SplitDotPathAtLastKey = <T extends string>(dotPath: T) => [PathWithoutLastKey, LastKey];
47
58
  /**
48
59
  * Splits string dotPath into ID and RemainingPath
49
60
  */
50
61
  declare const splitPathAtLastKey: SplitDotPathAtLastKey;
51
62
 
52
- type Spread = <T extends object>(value: T, update: T extends Array<infer U> ? Array<U> : Partial<T>) => T;
53
- /**
54
- * Spreads if its an object or array to the target, if its not then returns same value
55
- */
56
- declare const spread: Spread;
57
-
58
63
  type Set = <T>(newValue: T) => (value: T) => T;
59
64
  /**
60
65
  * Allows to set strongly typed values (removing inacurate "undefined" inference) in nested values inside arrays
61
66
  */
62
67
  declare const set: Set;
63
68
 
64
- type SemiStrictMode = 'skip-non-existent-paths';
65
- type NonStrictMode = 'implement-non-existent-paths';
66
- type UpdateImmutably = {
67
- <T extends object>(state: T, updates: DotPathUpdateObject<T>): T;
68
- };
69
+ type UpdateImmutably = <T extends object>(state: T, updates: DotPathUpdateObject<T>, addNonExistentPropsAndIndexes?: true) => T;
69
70
  /**
70
71
  * Creates a new updated object/array applying the changes provided in the update object.
71
72
  * It doesnt modify the original input.
72
73
  * If any of the paths doesnt exists it returns
73
74
  */
74
75
  declare const updateImmutably: UpdateImmutably;
75
- type PosibleExpandedProps<T> = {
76
- [K in keyof T]?: T[K] extends object ? PosibleExpandedProps<T[K]> & Record<any, any> : T[K];
77
- };
78
- type UpdateImmutablyNonStrictly = {
79
- <T extends object>(state: T, updates: DotPathUpdateObject<T>, mode: SemiStrictMode): T;
80
- <T extends object>(state: T, updates: DotPathUpdateObject<T>, mode: NonStrictMode): PosibleExpandedProps<T>;
81
- };
82
- /**
83
- * Creates a new updated object/array applying the changes provided in the update object.
84
- * It doesnt modify the original input.
85
- * If any of the paths doesnt exists it adds it if possible.
86
- */
87
- declare const updateImmutablyAllowingNewProps: UpdateImmutablyNonStrictly;
88
76
 
89
- export { type DotPathUpdateObject, type DotPaths, type DotPathsIncludingLength, type ValueInDotPath, remove, select, set, splitPathAtLastKey, spread, updateImmutably, updateImmutablyAllowingNewProps };
77
+ export { type DotPathUpdateObject, type DotPathUpdateValue, type DotPaths, type OptionalKeys, type RequiredKeys, type ValueInDotPath, remove, select, set, splitPathAtLastKey, updateImmutably };
package/dist/index.d.ts CHANGED
@@ -1,25 +1,17 @@
1
- type Remove = <T extends object>(value: T, keys: T extends any[] ? (number | `${number}` | (number | `${number}`)[]) : (keyof T | (keyof T)[])) => T;
2
- /**
3
- * Removes properties or indexes, order doesn't matter.
4
- */
5
- declare const remove: Remove;
6
-
7
1
  /**
8
2
  * Paths implementation
9
3
  */
10
4
  type PrimitivesAndNativeObjects = null | undefined | string | number | boolean | symbol | bigint | Date | FileList | File;
11
5
  type IsTuple<T extends ReadonlyArray<any>> = number extends T['length'] ? false : true;
12
6
  type Index = '_INDEX_';
13
- type RootArrayPathEnum = `${number}` | Index;
14
- type ArrayPathEnum<TLength extends boolean> = TLength extends true ? RootArrayPathEnum | 'length' : RootArrayPathEnum;
15
- type ArrayPaths<TInnerType, TLength extends boolean> = `${ArrayPathEnum<TLength>}` | `${ArrayPathEnum<false>}.${RootFlatten<TInnerType, TLength>}`;
16
- type FilteredTuple<T, TLength> = TLength extends string ? Omit<T, Exclude<keyof any[], 'length'>> : Omit<T, keyof any[]>;
17
- type ObjectPaths<T, TLength extends boolean> = {
18
- [Key in keyof T & string]: `${Key}` | `${Key}.${RootFlatten<T[Key], TLength>}`;
7
+ type ArrayIndexPaths = `${number}` | Index;
8
+ type ArrayPaths<TInnerType> = `${ArrayIndexPaths}` | `${ArrayIndexPaths}.${Flatten<TInnerType>}`;
9
+ type ObjectPaths<T> = {
10
+ [Key in keyof T & string]: `${Key}` | `${Key}.${Flatten<T[Key]>}`;
19
11
  }[keyof T & string];
20
- type RootFlatten<T, TIncludeLength extends boolean> = T extends ReadonlyArray<infer U> ? IsTuple<T> extends true ? ObjectPaths<FilteredTuple<T, TIncludeLength>, TIncludeLength> : ArrayPaths<U, TIncludeLength> : T extends string ? TIncludeLength extends true ? 'length' : never : T extends PrimitivesAndNativeObjects ? never : ObjectPaths<T, TIncludeLength>;
21
- type DotPaths<T> = RootFlatten<T, false>;
22
- type DotPathsIncludingLength<T> = RootFlatten<T, true>;
12
+ type TuplePaths<T> = ObjectPaths<Omit<T, keyof any[]>>;
13
+ type Flatten<T> = T extends ReadonlyArray<infer U> ? IsTuple<T> extends true ? TuplePaths<T> : ArrayPaths<U> : T extends PrimitivesAndNativeObjects ? never : ObjectPaths<T>;
14
+ type DotPaths<T> = Flatten<T>;
23
15
  /**
24
16
  * Type to know when a path is indexing an array
25
17
  */
@@ -27,14 +19,31 @@ type PathIndexingArray<T extends string | number> = `${T}` | `${T}.${string}` |
27
19
  /**
28
20
  * Nested Property Type implementation
29
21
  */
30
- type NestedProperty<TObject, TPath extends string> = TPath extends '' ? TObject : TPath extends keyof TObject ? TObject[TPath] : TPath extends (`${number}` | Index) ? TObject extends Array<infer U> ? TPath extends `${number}.${infer K}` ? K extends keyof U ? NestedProperty<U, K> : never : U : never : TPath extends `${infer K}.${infer R}` ? K extends keyof TObject ? NestedProperty<Required<TObject>[K], R> : K extends `${number}` | Index ? TObject extends Array<infer U> ? NestedProperty<U, R> : never : never : never;
22
+ type NestedProperty<TObject, TPath> = TPath extends '' ? TObject : TPath extends keyof TObject ? TObject[TPath] : TPath extends (`${number}` | Index) ? TObject extends Array<infer U> ? TPath extends `${number}.${infer K}` ? K extends keyof U ? NestedProperty<U, K> : never : U : never : TPath extends `${infer K}.${infer R}` ? K extends keyof TObject ? NestedProperty<Required<TObject>[K], R> : K extends `${number}` | Index ? TObject extends Array<infer U> ? NestedProperty<U, R> : never : never : never;
31
23
  type ValueInDotPath<TObject, TPath extends DotPaths<TObject> | ''> = NestedProperty<TObject, TPath>;
24
+ type DotPathUpdateValue<TObject, TPath extends DotPaths<TObject>> = TPath extends PathIndexingArray<Index> ? [`PATH ERROR: replace _INDEX_ with a NUMBER`, ValueInDotPath<TObject, TPath>, ((fieldValue: ValueInDotPath<TObject, TPath>, fullObject: TObject) => ValueInDotPath<TObject, TPath>)] : ValueInDotPath<TObject, TPath> | ((fieldValue: ValueInDotPath<TObject, TPath>, fullObject: TObject) => ValueInDotPath<TObject, TPath>);
32
25
  /**
33
26
  * Update Object Type Implementation
34
27
  */
35
- type DotPathUpdateObject<T> = Partial<{
36
- [K in DotPaths<T>]: K extends PathIndexingArray<Index> ? [`TS PATH ERROR: replace _INDEX_ with {NUMBER}`, ValueInDotPath<T, K>, ((value: ValueInDotPath<T, K>) => ValueInDotPath<T, K>)] : ValueInDotPath<T, K> | ((value: ValueInDotPath<T, K>) => ValueInDotPath<T, K>);
37
- }>;
28
+ type DotPathUpdateObject<TObject> = {
29
+ [TKey in DotPaths<TObject>]?: DotPathUpdateValue<TObject, TKey>;
30
+ };
31
+ /**
32
+ * Optional and Required keys
33
+ */
34
+ type NonPartialKeys = string | number | boolean | symbol | bigint | null | object;
35
+ type OptionalKeys<T> = {
36
+ [K in keyof T & string]: T[K] extends NonPartialKeys ? never : K;
37
+ }[keyof T & string];
38
+ type RequiredKeys<T> = {
39
+ [K in keyof T & string]: T[K] extends NonPartialKeys ? K : never;
40
+ }[keyof T & string];
41
+
42
+ type Remove = <T extends object>(value: T, keys: T extends any[] ? (number | `${number}` | (number | `${number}`)[]) : (OptionalKeys<T> | (OptionalKeys<T>)[])) => T;
43
+ /**
44
+ * Removes properties or indexes, order doesn't matter.
45
+ */
46
+ declare const remove: Remove;
38
47
 
39
48
  type Select = <T extends object, TPath extends (DotPaths<T> | '')>(state: T, path: TPath) => TPath extends PathIndexingArray<Index> ? never : TPath extends PathIndexingArray<number> ? ValueInDotPath<T, TPath> | undefined : ValueInDotPath<T, TPath>;
40
49
  /**
@@ -43,47 +52,26 @@ type Select = <T extends object, TPath extends (DotPaths<T> | '')>(state: T, pat
43
52
  */
44
53
  declare const select: Select;
45
54
 
46
- type SplitDotPathAtLastKey = <T extends string>(dotPath: T) => [string, string];
55
+ type PathWithoutLastKey = string;
56
+ type LastKey = string;
57
+ type SplitDotPathAtLastKey = <T extends string>(dotPath: T) => [PathWithoutLastKey, LastKey];
47
58
  /**
48
59
  * Splits string dotPath into ID and RemainingPath
49
60
  */
50
61
  declare const splitPathAtLastKey: SplitDotPathAtLastKey;
51
62
 
52
- type Spread = <T extends object>(value: T, update: T extends Array<infer U> ? Array<U> : Partial<T>) => T;
53
- /**
54
- * Spreads if its an object or array to the target, if its not then returns same value
55
- */
56
- declare const spread: Spread;
57
-
58
63
  type Set = <T>(newValue: T) => (value: T) => T;
59
64
  /**
60
65
  * Allows to set strongly typed values (removing inacurate "undefined" inference) in nested values inside arrays
61
66
  */
62
67
  declare const set: Set;
63
68
 
64
- type SemiStrictMode = 'skip-non-existent-paths';
65
- type NonStrictMode = 'implement-non-existent-paths';
66
- type UpdateImmutably = {
67
- <T extends object>(state: T, updates: DotPathUpdateObject<T>): T;
68
- };
69
+ type UpdateImmutably = <T extends object>(state: T, updates: DotPathUpdateObject<T>, addNonExistentPropsAndIndexes?: true) => T;
69
70
  /**
70
71
  * Creates a new updated object/array applying the changes provided in the update object.
71
72
  * It doesnt modify the original input.
72
73
  * If any of the paths doesnt exists it returns
73
74
  */
74
75
  declare const updateImmutably: UpdateImmutably;
75
- type PosibleExpandedProps<T> = {
76
- [K in keyof T]?: T[K] extends object ? PosibleExpandedProps<T[K]> & Record<any, any> : T[K];
77
- };
78
- type UpdateImmutablyNonStrictly = {
79
- <T extends object>(state: T, updates: DotPathUpdateObject<T>, mode: SemiStrictMode): T;
80
- <T extends object>(state: T, updates: DotPathUpdateObject<T>, mode: NonStrictMode): PosibleExpandedProps<T>;
81
- };
82
- /**
83
- * Creates a new updated object/array applying the changes provided in the update object.
84
- * It doesnt modify the original input.
85
- * If any of the paths doesnt exists it adds it if possible.
86
- */
87
- declare const updateImmutablyAllowingNewProps: UpdateImmutablyNonStrictly;
88
76
 
89
- export { type DotPathUpdateObject, type DotPaths, type DotPathsIncludingLength, type ValueInDotPath, remove, select, set, splitPathAtLastKey, spread, updateImmutably, updateImmutablyAllowingNewProps };
77
+ export { type DotPathUpdateObject, type DotPathUpdateValue, type DotPaths, type OptionalKeys, type RequiredKeys, type ValueInDotPath, remove, select, set, splitPathAtLastKey, updateImmutably };
package/dist/index.js CHANGED
@@ -1 +1 @@
1
- "use strict";var u=Object.defineProperty;var I=Object.getOwnPropertyDescriptor;var S=Object.getOwnPropertyNames;var j=Object.prototype.hasOwnProperty;var D=(t,e)=>{for(var o in e)u(t,o,{get:e[o],enumerable:!0})},w=(t,e,o,n)=>{if(e&&typeof e=="object"||typeof e=="function")for(let a of S(e))!j.call(t,a)&&a!==o&&u(t,a,{get:()=>e[a],enumerable:!(n=I(e,a))||n.enumerable});return t};var K=t=>w(u({},"__esModule",{value:!0}),t);var g={};D(g,{remove:()=>h,select:()=>f,set:()=>x,splitPathAtLastKey:()=>m,spread:()=>T,updateImmutably:()=>P,updateImmutablyAllowingNewProps:()=>A});module.exports=K(g);var h=(t,e)=>{if(!Array.isArray(t)&&t?.constructor!==Object)throw'Wrong type provided in "remove" function';let o=Array.isArray(e)?e:[e];if(Array.isArray(t)){let a=Symbol(),p=[...t];return o.forEach(i=>{p[i]=a}),p.filter(i=>i!==a)}let n={...t};return o.forEach(a=>delete n[a]),n};var f=(t,e)=>!e||typeof e!="string"?t:e.split(".").reduce((o,n)=>{if(o?.hasOwnProperty(n))return o[n]},t);var m=t=>{if(typeof t!="string")throw new Error(`Wrong dotPath type: ${t}`);let e=t.lastIndexOf(".")+1,o=e?t.slice(0,e-1):"",n=t.slice(e);return[o,n]};var T=(t,e)=>t?.constructor===Object&&e?.constructor===Object?{...t,...e}:Array.isArray(t)&&Array.isArray(e)?[...t,...e]:t;var x=t=>()=>t;var b=(t,e,o="strict-paths")=>{if(t?.constructor!==Object&&!Array.isArray(t))throw new Error(`${t} is not an object/array so cant be updated immutably`);if(!e)return t;let n=Array.isArray(t)?[...t]:{...t},a={};for(let[p,l]of Object.entries(e)){if(!p||p==="false"||p==="undefined"||p==="null")continue;let i=p.split("."),y=i.pop(),c=a,s=n;for(let r of i){if(!s[r]||typeof s[r]!="object"||s[r].constructor!==Object&&!Array.isArray(s[r]))throw new Error(`Invalid path provided: "${r}" is not an object/array in "${p}"`);c[r]||(s[r]=Array.isArray(s[r])?[...s[r]]:{...s[r]},c[r]={}),s=s[r],c=c[r]}let d=s;if(o!=="implement-non-existent-paths"){let r=o==="skip-non-existent-paths";if(Array.isArray(d)&&!(y<d.length)){if(r)continue;throw new Error(`Use spread to add indexes in ${i.join(".")}, because index ${y} doesnt exist in ${y}`)}if(!d.hasOwnProperty(y)){if(r)continue;throw new Error(`Property ${y} doesnt exist in the path ${i.join(".")} because its current value is: ${d}`)}}d[y]=typeof l=="function"?l(d[y]):l,delete c[y]}return n},P=b,A=b;0&&(module.exports={remove,select,set,splitPathAtLastKey,spread,updateImmutably,updateImmutablyAllowingNewProps});
1
+ "use strict";var f=Object.defineProperty;var I=Object.getOwnPropertyDescriptor;var D=Object.getOwnPropertyNames;var w=Object.prototype.hasOwnProperty;var j=(t,e)=>{for(var r in e)f(t,r,{get:e[r],enumerable:!0})},O=(t,e,r,a)=>{if(e&&typeof e=="object"||typeof e=="function")for(let o of D(e))!w.call(t,o)&&o!==r&&f(t,o,{get:()=>e[o],enumerable:!(a=I(e,o))||a.enumerable});return t};var g=t=>O(f({},"__esModule",{value:!0}),t);var v={};j(v,{remove:()=>x,select:()=>T,set:()=>b,splitPathAtLastKey:()=>m,updateImmutably:()=>A});module.exports=g(v);var x=(t,e)=>{if(!Array.isArray(t)&&t?.constructor!==Object)throw'Wrong type provided in "remove" function';let r=Array.isArray(e)?e:[e];if(Array.isArray(t)){let o=Symbol(),y=[...t];return r.forEach(n=>{y[n]=o}),y.filter(n=>n!==o)}let a={...t};return r.forEach(o=>delete a[o]),a};var T=(t,e)=>!e||typeof e!="string"?t:e.split(".").reduce((r,a)=>{if(r?.hasOwnProperty(a))return r[a]},t);var m=t=>{if(typeof t!="string")throw new Error(`Wrong dotPath type: ${t}`);let e=t.lastIndexOf(".")+1,r=e?t.slice(0,e-1):"",a=t.slice(e);return[r,a]};var b=t=>()=>t;var A=(t,e,r)=>{if(t?.constructor!==Object&&!Array.isArray(t))throw new Error(`${t} is not an object/array`);if(!e)return t;let a=Array.isArray(t)?[...t]:{...t},o={};for(let[y,l]of Object.entries(e)){if(!y)continue;let n=y.split("."),s=n.pop(),c=o,d=a;for(let u=0;u<n.length;u++){let i=n[u],p=d[i],K=typeof p=="object"&&(p.constructor===Object||Array.isArray(p)),P=r&&p===void 0;if(!K&&!P)throw new Error(`Invalid path provided: "${i}" is not an object/array in "${y}"`);c[i]||(P?d[i]=isNaN(n[u+1]??s)?{}:[]:d[i]=Array.isArray(p)?[...p]:{...p},c[i]={}),d=d[i],c=c[i]}let h=d;if(!r){if(Array.isArray(h)&&!(s<h.length))throw new Error(`Index ${s} doesnt exist in the path ${n.join(".")} because its value is ${s}, use spread operator for adding new indexes`);if(!h.hasOwnProperty(s))throw new Error(`Property ${s} doesnt exist in the path ${n.join(".")} because its value is ${h}`)}h[s]=typeof l=="function"?l(h[s],a):l,delete c[s]}return a};0&&(module.exports={remove,select,set,splitPathAtLastKey,updateImmutably});
package/dist/index.mjs CHANGED
@@ -1 +1 @@
1
- var h=(t,e)=>{if(!Array.isArray(t)&&t?.constructor!==Object)throw'Wrong type provided in "remove" function';let n=Array.isArray(e)?e:[e];if(Array.isArray(t)){let y=Symbol(),s=[...t];return n.forEach(p=>{s[p]=y}),s.filter(p=>p!==y)}let a={...t};return n.forEach(y=>delete a[y]),a};var f=(t,e)=>!e||typeof e!="string"?t:e.split(".").reduce((n,a)=>{if(n?.hasOwnProperty(a))return n[a]},t);var m=t=>{if(typeof t!="string")throw new Error(`Wrong dotPath type: ${t}`);let e=t.lastIndexOf(".")+1,n=e?t.slice(0,e-1):"",a=t.slice(e);return[n,a]};var T=(t,e)=>t?.constructor===Object&&e?.constructor===Object?{...t,...e}:Array.isArray(t)&&Array.isArray(e)?[...t,...e]:t;var x=t=>()=>t;var u=(t,e,n="strict-paths")=>{if(t?.constructor!==Object&&!Array.isArray(t))throw new Error(`${t} is not an object/array so cant be updated immutably`);if(!e)return t;let a=Array.isArray(t)?[...t]:{...t},y={};for(let[s,l]of Object.entries(e)){if(!s||s==="false"||s==="undefined"||s==="null")continue;let p=s.split("."),i=p.pop(),c=y,o=a;for(let r of p){if(!o[r]||typeof o[r]!="object"||o[r].constructor!==Object&&!Array.isArray(o[r]))throw new Error(`Invalid path provided: "${r}" is not an object/array in "${s}"`);c[r]||(o[r]=Array.isArray(o[r])?[...o[r]]:{...o[r]},c[r]={}),o=o[r],c=c[r]}let d=o;if(n!=="implement-non-existent-paths"){let r=n==="skip-non-existent-paths";if(Array.isArray(d)&&!(i<d.length)){if(r)continue;throw new Error(`Use spread to add indexes in ${p.join(".")}, because index ${i} doesnt exist in ${i}`)}if(!d.hasOwnProperty(i)){if(r)continue;throw new Error(`Property ${i} doesnt exist in the path ${p.join(".")} because its current value is: ${d}`)}}d[i]=typeof l=="function"?l(d[i]):l,delete c[i]}return a},b=u,P=u;export{h as remove,f as select,x as set,m as splitPathAtLastKey,T as spread,b as updateImmutably,P as updateImmutablyAllowingNewProps};
1
+ var x=(t,e)=>{if(!Array.isArray(t)&&t?.constructor!==Object)throw'Wrong type provided in "remove" function';let a=Array.isArray(e)?e:[e];if(Array.isArray(t)){let p=Symbol(),y=[...t];return a.forEach(o=>{y[o]=p}),y.filter(o=>o!==p)}let r={...t};return a.forEach(p=>delete r[p]),r};var T=(t,e)=>!e||typeof e!="string"?t:e.split(".").reduce((a,r)=>{if(a?.hasOwnProperty(r))return a[r]},t);var m=t=>{if(typeof t!="string")throw new Error(`Wrong dotPath type: ${t}`);let e=t.lastIndexOf(".")+1,a=e?t.slice(0,e-1):"",r=t.slice(e);return[a,r]};var b=t=>()=>t;var A=(t,e,a)=>{if(t?.constructor!==Object&&!Array.isArray(t))throw new Error(`${t} is not an object/array`);if(!e)return t;let r=Array.isArray(t)?[...t]:{...t},p={};for(let[y,l]of Object.entries(e)){if(!y)continue;let o=y.split("."),n=o.pop(),c=p,d=r;for(let u=0;u<o.length;u++){let s=o[u],i=d[s],P=typeof i=="object"&&(i.constructor===Object||Array.isArray(i)),f=a&&i===void 0;if(!P&&!f)throw new Error(`Invalid path provided: "${s}" is not an object/array in "${y}"`);c[s]||(f?d[s]=isNaN(o[u+1]??n)?{}:[]:d[s]=Array.isArray(i)?[...i]:{...i},c[s]={}),d=d[s],c=c[s]}let h=d;if(!a){if(Array.isArray(h)&&!(n<h.length))throw new Error(`Index ${n} doesnt exist in the path ${o.join(".")} because its value is ${n}, use spread operator for adding new indexes`);if(!h.hasOwnProperty(n))throw new Error(`Property ${n} doesnt exist in the path ${o.join(".")} because its value is ${h}`)}h[n]=typeof l=="function"?l(h[n],r):l,delete c[n]}return r};export{x as remove,T as select,b as set,m as splitPathAtLastKey,A as updateImmutably};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "maraj",
3
- "version": "2.0.0",
3
+ "version": "2.2.0",
4
4
  "description": "A lightweight 500 bytes gzipped alternative for immutable updates while dealing with objects and arrays",
5
5
  "keywords": [
6
6
  "shallow",
@@ -38,7 +38,7 @@
38
38
  "@changesets/cli": "^2.27.1",
39
39
  "@microsoft/api-extractor": "^7.38.5",
40
40
  "tsup": "^8.0.1",
41
- "typescript": "^4.9.5",
41
+ "typescript": "^5.3.3",
42
42
  "vitest": "^0.28.4"
43
43
  }
44
44
  }