maraj 2.11.0 → 3.0.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,211 +1,249 @@
1
-
2
1
  # Maraj
3
2
 
4
- A fast lightweight utility for creating **immutable updates**. Fully typed. Only 600 bytes gziped. Includes usefull utilities for manipulating updates (like remove, select).
3
+ A small utility for **immutable updates** of objects and arrays with dot paths. Fully typed, about 1.2 KB gzipped. Includes helpers to work with updates (`remove`, `select`, `set`, ...).
5
4
 
6
5
  ### Features
7
6
 
8
- - Update and ExtendableUpdate.
9
- - Usefull utilities like "remove", "select".
10
- - Full typescript support.
11
- - Correct type inference.
12
- - Multiple and concurrent updates.
13
- - Nested functional updates.
7
+ - `update` with typed dot paths (`'files.0.name'`), checked by TypeScript.
8
+ - Several updates in one call, sharing the copies of common parents.
9
+ - Function updates that get the current value.
10
+ - Only the objects and arrays along each path are copied; everything else keeps its reference.
11
+ - Options to add, skip or throw on missing props and indexes.
12
+ - Helpers: `remove`, `select`, `set`, `splitPathAtLastKey` and a few type helpers.
14
13
 
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?: ... } is not equal to { key: ... | undefined } )
14
+ ```sh
15
+ npm install maraj
16
+ ```
16
17
 
17
18
  ```ts
18
- const originalValue = { name: 'one', lastname: 'two' modified: false, files: [{ filename: 'one' }] }
19
+ import { update } from 'maraj'
19
20
 
20
- const updatedValue = update(orignalValue, {
21
+ const original = { name: 'one', lastname: 'two', modified: false, files: [{ filename: 'one' }, { filename: 'two' }] }
22
+
23
+ const updated = update(original, {
21
24
  'name': 'updated',
22
- 'modified': (value, fullObject) => !value,
25
+ 'modified': (value) => !value,
23
26
  'files.0.filename': 'zero',
24
27
  })
25
- // updatedValue = { name: 'updated', lastname: 'two' modified: true, files: [{ filename: 'zero' }] }
26
- originalValue.files[1] === updatedValue.files[1] // true
27
-
28
- const newValue = extendableUpdate(orignalValue, {
29
- 'newProp': 'new',
30
- 'data.0.name': 'dataName'
31
- })
32
- // updatedValue = { ... , newProp: 'new', data: [{ name: 'dataName' }] }
33
- originalValue.files[1] === newValue.files[1] // true
28
+ // updated = { name: 'updated', lastname: 'two', modified: true, files: [{ filename: 'zero' }, { filename: 'two' }] }
29
+ original.files[1] === updated.files[1] // true, not copied
30
+ original.files === updated.files // false, copied because 'files.0' changed
34
31
  ```
35
32
 
36
- ### Limitations
33
+ <u>**For TypeScript:**</u> set **"strictNullChecks"** and **"noUncheckedIndexedAccess"** to **true** in tsconfig.json, so optional props and props that can be `undefined` get the right type (`{ key?: string }` is not the same as `{ key: string | undefined }`). With **"exactOptionalPropertyTypes"** too, setting `undefined` on a prop that isn't optional is an error.
34
+
35
+ ## update
36
+
37
+ ```ts
38
+ update(state, updateObject, options?)
39
+ ```
37
40
 
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: ... } } } }
41
+ - `state`: an object literal or array, anything else throws (`Date`, `Map`, class instances, `null`).
42
+ - `updateObject`: an object literal of `{ 'dot.path': newValue }`, applied in order.
43
+ - `options`: what to do with paths that don't exist yet (see [Options](#options)).
43
44
 
44
- It can still work with dot-paths keys containing an object or array, however the paths targeting nested properties in this key are forbiden. For example having the following object { a: { "b.c": { d: ... } } } you can use the path "a" and "a.b.c", but you cant use "a.b.c.d", this path will create a new property "b" inside "a", then "c" and lastly "d" resulting in the following object { a: { "b.c": { d: ... } }, b: { c: { d: ... } } }
45
+ Returns a new object with the updates; `state` is never changed.
45
46
 
46
- - 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.
47
+ - A **function** as value is called with the current value (`undefined` if the prop doesn't exist) and its result is set. To set a function as the value, use [`set`](#set).
48
+ - Missing parents are created: an array when the next key is an index (`'files.1.name'` on `{}` -> `{ files: [<empty>, { name }] }`), an object otherwise.
49
+ - A primitive or `null` in the middle of a path is replaced by a new object (`'a.b'` on `{ a: 1 }` -> `{ a: { b } }`).
50
+ - Only own props are read: `'toString.value'` creates a `toString` prop instead of reading `Object.prototype.toString`.
51
+ - A path with `'__proto__'` throws (it would change the prototype of the copy).
52
+ - An empty path (`''`) is skipped, and so are symbol keys.
47
53
 
48
54
  ```ts
49
- const updatedValue = extendableUpdate({}, {
50
- 'files.1.name': 'new name',
51
- })
52
- // The "files" prop is an array. updatedValue = { files: [<empty>, { name: 'new name' }] }
55
+ import { update } from 'maraj'
53
56
 
54
- // To get an object instead of an array create an object beforehand
55
- const updatedValue2 = extendableUpdate({}, {
56
- 'files': {}, // add an object first, so then i can add props as numbers after it
57
- 'files.1.name': 'new name',
57
+ const data = {
58
+ profile: { name: 'Lukasz' },
59
+ works: ['soum', 'fi'],
60
+ history: [
61
+ { title: 'soum', size: 90, available: true },
62
+ { title: 'fi', size: 30, available: false },
63
+ ],
64
+ }
65
+
66
+ const newData = update(data, {
67
+ 'profile.name': 'Lucas',
68
+ 'works.1': 'gs',
69
+ 'history.1.available': (available) => !available,
70
+ 'works': (works) => [...works, 'portfolio'], // runs after 'works.1', so it gets ['soum', 'gs']
58
71
  })
59
- // updatedValue2 = { files: { '1': { name: 'new name' } } }
60
72
  ```
61
73
 
62
- - The type "UpdateObject" (the type of the object provided containing the updates, used as second parameter in the update function) if used to create new update objects using computed properties in a inline syntax { [key]: value } will always throw a ts error even if the typing is correct, use the second syntax.
74
+ TypeScript only accepts paths that exist in the type of `state`, with values of the same type. Use [`experimental_extendableUpdate`](#experimental_extendableupdate) to add new props.
75
+
76
+ ### Options
77
+
78
+ Each option takes `'add'` (default), `'skip'` or `'error'`:
79
+
80
+ - `onNewProps`: a prop doesn't exist, in an object, or a string key in an array.
81
+ - `onNewIndexes`: an index doesn't exist in an array (`'works.5'` on a 2-item array). `'error'` makes sure no empty items are created.
82
+ - `onPathsIntersectedByPrimitiveValues`: a primitive or `null` is in the middle of the path (`'a.b'` on `{ a: 1 }`). `'add'` replaces it with an object.
83
+
84
+ `'skip'` skips only that path, at any depth, and still applies the rest. `'error'` throws an `Error` naming the key and the path.
63
85
 
64
86
  ```ts
65
- const updateFieldStore = <T, TPath extends DotPaths<T>>(
66
- state: T,
67
- path: TPath,
68
- newValue: UpdateValue<T, TPath>
69
- ) => {
70
- // ts error even if the typing is correct (typescript issue)
71
- const updateObject1: UpdateObject<T> = {
72
- [path]: newValue
73
- }
74
-
75
- // All good, no ts error
76
- const updateObject2: UpdateObject<T> = {}
77
- updateObject[path] = newValue
78
-
79
- }
87
+ update({ a: { d: 1 } }, { 'a.b.c': 1, 'a.d': 2 }, { onNewProps: 'skip' })
88
+ // { a: { d: 2 } }
89
+
90
+ update({ list: [1, 2] }, { 'list.5': 3 }, { onNewIndexes: 'error' })
91
+ // throws: Property/Index "5" doesnt exist in the path "list.5"...
80
92
  ```
81
93
 
82
- - 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 nested props inside arrays.
94
+ ## experimental_extendableUpdate
95
+
96
+ Same as `update`, but TypeScript also accepts new paths and the returned type includes them. It has no options: new props and indexes are always added.
83
97
 
84
98
  ```ts
85
- const updatedValue = update(data, {
86
- 'files.0.nonOptionalProperty': 'new name',
87
- 'files.1.nonOptionalProperty': undefined, //no ts error
88
- 'files.0.nonOptionalProperty': set('new name'),
89
- 'files.1.nonOptionalProperty': set(undefined), //throws ts error, undefined cant be a value.
99
+ import { experimental_extendableUpdate } from 'maraj'
100
+
101
+ const newData = experimental_extendableUpdate(data, {
102
+ 'profile.age': 28, // new prop
103
+ 'profile.name': 'Lucas',
104
+ 'works.10': 'gs', // new index, items 2 to 9 are left empty
90
105
  })
91
106
  ```
92
107
 
93
- ## Creating a Strict Immutable Update
108
+ A number in the path creates a mutable array when nothing exists there yet. To get an object instead, set it first:
109
+
110
+ ```ts
111
+ experimental_extendableUpdate({}, { 'files.1.name': 'new name' })
112
+ // { files: [<empty>, { name: 'new name' }] }
113
+
114
+ experimental_extendableUpdate({}, {
115
+ 'files': {},
116
+ 'files.1.name': 'new name',
117
+ })
118
+ // { files: { '1': { name: 'new name' } } }
119
+ ```
94
120
 
95
- Arguments:
121
+ ## set
96
122
 
97
- - Original Object
98
- - Update Object
99
- - Options (to change JS behaviour on non existing props and indexes)
100
- - 'add': Adds non existing property/index.
101
- - 'skip': Skips the update in the non-existing prop/index and still applies the rest of the possible updates.
102
- - '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.
123
+ Returns a function update that always returns the given value. Use it to:
103
124
 
104
- ## Example
125
+ - Set a function as a value (a plain function would be called as an update).
126
+ - Get a stricter type when TypeScript mixes up an optional prop with one that can be `undefined` (usually props of items inside arrays): `set(undefined)` is an error.
105
127
 
106
- ```js
107
- import { update } from "maraj";
128
+ ```ts
129
+ import { update, set } from 'maraj'
108
130
 
109
- const data = {
110
- profile: { name: "Lukasz" },
111
- works: ["soum", "fi"],
112
- history: [
113
- { title: "soum", size: 90, available: true, info: ["info1"] },
114
- { title: "fi", size: 30, available: false, info: ["infoA"] },
115
- ]
116
- }
131
+ const button = { label: 'Save', onClick: () => {}, files: [{ name: 'one' }] }
117
132
 
118
- const newUpdatedObject = update(data, {
119
- "profile.name": "Lucas", //update values
120
- "works.1": "gs", //nested array update
121
- "history.1.available": (currentValue) => (!currentValue), //functional update
122
- "works": (v) => [...v, 'portfolio'] //add new value to array
133
+ update(button, {
134
+ 'onClick': set(() => console.log('clicked')),
135
+ 'files.0.name': set('new name'),
123
136
  })
124
137
  ```
125
138
 
126
- ## Creating an Immutable Update allowing new props to be added
127
-
128
- Arguments:
139
+ ## remove
129
140
 
130
- - Original Object
131
- - Update Object
141
+ Returns a copy of an object without the given keys, or of an array without the given indexes. Use it inside a function update.
132
142
 
133
- ## Example
143
+ - Objects: only optional keys can be removed (TypeScript).
144
+ - Arrays: indexes as numbers or strings (`1`, `'1'`), in any order; repeated or out-of-range indexes are ignored.
145
+ - Anything other than an object literal or array throws.
134
146
 
135
- ```js
136
- import { extendableUpdate } from "maraj";
147
+ ```ts
148
+ import { update, remove } from 'maraj'
137
149
 
138
- const data = {
139
- profile: { name: "Lukasz" },
140
- works: ["soum", "fi"],
141
- history: [
142
- { title: "soum", size: 90, available: true, info: ["info1"] },
143
- { title: "fi", size: 30, available: false, info: ["infoA"] },
144
- ]
145
- }
150
+ type Person = { profile: { name: string, nickname?: string }, works: string[] }
151
+ const person: Person = { profile: { name: 'Lukasz', nickname: 'Luk' }, works: ['soum', 'fi', 'gs'] }
146
152
 
147
- const newUpdatedObject = extendableUpdate(data, {
148
- "profile.age": 28, //add new property
149
- "profile.name": "Lucas", //update values
150
- "works.10": "gs", //increases the array (filling it up with empty/undefined) and sets the value in the index of 10 to "gs"
153
+ update(person, {
154
+ 'profile': (profile) => remove(profile, 'nickname'), // removes "nickname"
155
+ 'works': (works) => remove(works, [1, '0']), // ['gs']
151
156
  })
152
157
  ```
153
158
 
154
- ## Remove Utility
159
+ ## select
155
160
 
156
- Removes the **key of an object** or the **index of an array** in the key provided in the **update.**
161
+ Returns the value in a dot path, or `undefined` if the path doesn't exist. `''` returns the whole state.
157
162
 
158
- ```js
159
- import {remove} from 'maraj'
163
+ - Only own props are read: `'profile.toString'` and `'tags.map'` give `undefined`, and so does `'size'` on a `Map`.
164
+ - `null` or `undefined` in the middle of the path gives `undefined`.
165
+ - A key with dots (`{ 'a.b': 1 }`) is found when it matches the whole rest of the path.
160
166
 
161
- const newUpdatedObject = update(person,{
162
- 'profile': (value) => remove(value, 'name'), // returns new value without the "name" prop
163
- 'profile': (value) => remove(value, ['name', 'lastname']), // returns new value without the "name" and "lastname" prop
164
- 'works': (value) => remove(value, 1), // returns new array without the provided index (1)
165
- 'works': (value) => remove(value, [1, "0"]), // returns new array without the provided index "0" and "1"
166
- })
167
+ ```ts
168
+ import { select } from 'maraj'
169
+
170
+ const work1SizeSelector = (state) => select(state, 'history.1.size') // state.history[1].size
167
171
  ```
168
172
 
169
- ## Select Utility
173
+ ## splitPathAtLastKey
170
174
 
171
- Selects the value of an object/array from a string-dot-path. Usefull in state management libraries.
175
+ Splits a dot path into the path before the last key and the last key (for example, to get an id).
172
176
 
173
- ```js
174
- import {select} from 'maraj'
177
+ ```ts
178
+ import { splitPathAtLastKey } from 'maraj'
175
179
 
176
- const stringPath = 'works.1.size'
180
+ const [remainingPath, lastKey] = splitPathAtLastKey('projects.1.data.size')
181
+ // remainingPath = 'projects.1.data', lastKey = 'size'
177
182
 
178
- const work1SizeSelector = (state) => select(state, stringPath) // will behave as (store) => store[works][1][size]
183
+ splitPathAtLastKey('size') // ['', 'size']
179
184
  ```
180
185
 
181
- ## SplitPathAtLastKey Utility
186
+ ## Type helpers
182
187
 
183
- Usefull when you want to retrieve the last key (for example an id).
188
+ - `strictType<T, U>(value)`: returns `value` as `U`, and TypeScript errors when `value` has props `U` doesn't have (plain assignment allows them).
189
+ - `strict(value)` (or `s`): returns `value` unchanged, typed as `Strict<T>`.
190
+ - `isObjectLiteral(value)`: `true` when the prototype is `Object.prototype` (`{}`, `{ a: 1 }`); `false` for arrays, `null`, `Object.create(null)`, `Date`, `Map`, class instances and objects from another realm (`node:vm`, iframes).
191
+ - `isStringIndex(key)`: `true` when `key` is an integer written as `parseInt` would write it (`'1'`); `false` for `'01'`, `'1.5'`, `''` and `'NaN'`. `parseStringIndex(key)` returns that number, or `null`.
192
+ - `forEachEntry(object, (key, value) => ...)`: `Object.keys(object).forEach` with typed keys and values.
184
193
 
185
- Splits string path (example:'projects.1.data.size' ) into two:
194
+ ```ts
195
+ import { strictType } from 'maraj'
186
196
 
187
- - remainingPath: the remaining path (in this case 'projects.1.data')
188
- - lastKey: the last key in the path (in this case 'size')
197
+ type User = { name: string, age?: number }
198
+ const saveUser = (user: User) => { /* ... */ }
189
199
 
190
- ```js
191
- import { splitPathAtLastKey } from 'maraj'
200
+ const user = { name: 'Ana', admin: true }
201
+ saveUser(user) // no error
202
+ saveUser(strictType<typeof user, User>(user)) // error: "admin" isn't in User
203
+ ```
192
204
 
193
- const stringPath = 'works.1.size'
205
+ ## Limitations
194
206
 
195
- const [remainingPath, lastKey] = splitPathAtLastKey(stringPath)
196
- // lastKey = 'size'
197
- // remainingPath = 'works.1'
198
- ```
207
+ - **Keys with dots** (`{ 'this.is.dotted.key': 123 }`) work only when they already exist and the path ends on them. The path `'a.b.c.d'` works on:
208
+ - `{ 'a.b.c.d': ... }`
209
+ - `{ a: { 'b.c.d': ... } }`
210
+ - `{ a: { b: { 'c.d': ... } } }`
211
+ - `{ a: { b: { c: { d: ... } } } }`
199
212
 
200
- ## Set Utility
213
+ Paths into the value of a dotted key don't: with `{ a: { 'b.c': { d: 1 } } }`, `'a.b.c'` works but `'a.b.c.d'` creates `a.b.c.d` next to `'b.c'`: `{ a: { 'b.c': { d: 1 }, b: { c: { d: ... } } } }`.
201
214
 
202
- 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.
215
+ - **`UpdateObject` with a computed key** in an object literal is a TypeScript error even when the types are right. Assign the key after creating the object:
203
216
 
204
217
  ```ts
205
- const updatedValue = update(data, {
206
- 'files.0.optionalProperty': 'new name',
207
- 'files.1.optionalProperty': undefined, //no ts error
208
- 'files.0.optionalProperty': set('new name'),
209
- 'files.1.optionalProperty': set(undefined), //throws ts error, undefined cant be a value.
210
- })
218
+ import type { DotPaths, UpdateObject, UpdateValue } from 'maraj'
219
+
220
+ const updateField = <T, TPath extends DotPaths<T>>(state: T, path: TPath, newValue: UpdateValue<T, TPath>) => {
221
+ // ts error, even if the typing is correct (TypeScript issue)
222
+ const updateObject1: UpdateObject<T> = { [path]: newValue }
223
+
224
+ // no ts error
225
+ const updateObject2: UpdateObject<T> = {}
226
+ updateObject2[path] = newValue
227
+ return update(state, updateObject2)
228
+ }
211
229
  ```
230
+
231
+ ## Benchmarks
232
+
233
+ <!-- bench:start -->
234
+ Median speed against maraj in 11 scenarios (higher is faster)
235
+
236
+ ```text
237
+ dot-prop-immutable ██████████████████████████████ 1.26x
238
+ maraj ████████████████████████ 1.00x
239
+ timm ██████████████████ 0.77x
240
+ object-path-immutable ██████████████ 0.58x
241
+ immer (no autofreeze) ████████████ 0.49x
242
+ immutability-helper ██████████ 0.41x
243
+ lodash/fp █████████ 0.38x
244
+ mutative ████████ 0.35x
245
+ immer ███████ 0.30x
246
+ ```
247
+ <!-- bench:end -->
248
+
249
+ `1.50x` means 1.5 times as many updates per second as maraj. The scenarios go from one top-level prop to 100 paths in a 1,000-item array: see [benchmarks/README.md](benchmarks/README.md) for each one, the machine and how to run them.
package/dist/index.js CHANGED
@@ -1 +1 @@
1
- Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=e=>!!e&&Object.getPrototypeOf(e)===Object.prototype,t=(t,n)=>{if(!Array.isArray(t)&&!e(t))throw`Wrong type provided in "remove" function`;let r=Array.isArray(n)?n:[n];if(Array.isArray(t)){let e=Symbol(),n=[...t];return r.forEach(t=>{n[t]=e}),n.filter(t=>t!==e)}let i={...t};return r.forEach(e=>delete i[e]),i},n=(e,t)=>{if(typeof t!=`string`)throw Error(`path is not a string`);if(!t)return e;let n=t.split(`.`),r=e;for(;n.length;){if(r==null)return;let e=n[0],t=n.join(`.`);if(Object.hasOwn(r,t))return r[t];if(!Object.hasOwn(r,e))return;r=r[e],n.shift()}return r},r=e=>{if(typeof e!=`string`)throw Error(`Wrong dotPath type: ${e}`);let t=e.lastIndexOf(`.`)+1;return[t?e.slice(0,t-1):``,e.slice(t)]},i=e=>()=>e,a=e=>{let t=parseInt(e);return t.toString()===e?t:null},o=e=>parseInt(e).toString()===e,s=e=>JSON.stringify(e),c=e=>Array.isArray(e),l=(t,n,r={})=>{if(!e(t)&&!c(t))throw Error(`"${s(t)}" is not an literalObject/array`);if(!e(n))throw Error(`wrong "updateObject" type provided`);let i=Array.isArray(t)?[...t]:{...t},a={};for(let[t,l]of Object.entries(n)){if(t===``)continue;let n=t.split(`.`),u=a,d=i;for(;n.length>1;){let i=n.join(`.`);if(Object.hasOwn(d,i)){n=[i];break}let a=n[0],s=d[a],l=e(s),f=c(s);if(!l&&!f&&s!==void 0){if(r.onPathsIntersectedByPrimitiveValues===`error`)throw Error(`Property "${a}" in "${t}" is a primitive`);if(r.onPathsIntersectedByPrimitiveValues===`skip`)continue}if(!u[a]){if(!l&&!f){let e=c(d)&&o(a),i=o(n[1]),s=e?r.onNewIndexes:r.onNewProps;if(s===`error`)throw Error(`${a} doesn't exist in "${t}"`);if(s===`skip`)continue;d[a]=i?[]:{}}else d[a]=f?[...s]:{...s};u[a]={}}d=d[a],u=u[a],n.shift()}let f=n[0],p=d;if(!Object.hasOwn(p,f)){let e=c(p)&&o(f)?r.onNewIndexes:r.onNewProps;if(e===`error`)throw Error(`Property/Index "${f}" doesnt exist in the path "${t}". Its value is: "${s(p)}"`);if(e===`skip`)continue}p[f]=typeof l==`function`?l(p[f]):l,delete u[f]}return i},u=(e,t,n)=>l(e,t,n),d=(e,t)=>l(e,t),f=(e,t)=>Object.keys(e).forEach(n=>t(n,e[n])),p=e=>e,m=p,h=e=>e;exports.experimental_extendableUpdate=d,exports.forEachEntry=f,exports.isObjectLiteral=e,exports.isStringIndex=o,exports.parseStringIndex=a,exports.remove=t,exports.s=m,exports.select=n,exports.set=i,exports.splitPathAtLastKey=r,exports.strict=p,exports.strictType=h,exports.update=u;
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=e=>!!e&&Object.getPrototypeOf(e)===Object.prototype,t=(t,n)=>{if(!Array.isArray(t)&&!e(t))throw`Wrong type provided in "remove" function`;let r=Array.isArray(n)?n:[n];if(Array.isArray(t)){let e=Symbol(),n=[...t];return r.forEach(t=>{n[t]=e}),n.filter(t=>t!==e)}let i={...t};return r.forEach(e=>delete i[e]),i},n=(e,t)=>{if(typeof t!=`string`)throw Error(`path is not a string`);if(!t)return e;let n=t.split(`.`),r=e;for(;n.length;){if(r==null)return;let e=n[0],t=n.join(`.`);if(Object.hasOwn(r,t))return r[t];if(!Object.hasOwn(r,e))return;r=r[e],n.shift()}return r},r=e=>{if(typeof e!=`string`)throw Error(`Wrong dotPath type: ${e}`);let t=e.lastIndexOf(`.`)+1;return[t?e.slice(0,t-1):``,e.slice(t)]},i=e=>()=>e,a=e=>{let t=parseInt(e);return e!==`NaN`&&t.toString()===e?t:null},o=e=>e!==`NaN`&&parseInt(e).toString()===e,s=e=>JSON.stringify(e),c=e=>Array.isArray(e),l=(t,n,r={})=>{if(!e(t)&&!c(t))throw Error(`"${s(t)}" is not an literalObject/array`);if(!e(n))throw Error(`wrong "updateObject" type provided`);let i=Array.isArray(t)?[...t]:{...t},a=Object.create(null),l=Object.entries(n),u=l.length===1;nextUpdate:for(let[t,n]of l){if(t===``)continue;let l=t.split(`.`),d=a;if(l.includes(`__proto__`))throw Error(`"__proto__" is not allowed in "${t}"`);let f=i,p=l[l.length-1],m=0;for(let n=0;n<l.length-1;n++){let i=t.slice(m);if(Object.hasOwn(f,i)){p=i;break}let a=l[n],s=Object.hasOwn(f,a)?f[a]:void 0;m+=a.length+1;let h=d[a];if(h){f=s,d=h;continue}let g=e(s),_=c(s);if(!g&&!_&&s!==void 0){if(r.onPathsIntersectedByPrimitiveValues===`error`)throw Error(`Property "${a}" in "${t}" is a primitive`);if(r.onPathsIntersectedByPrimitiveValues===`skip`)continue nextUpdate}if(!g&&!_){let e=c(f)&&o(a),i=o(l[n+1]),s=e?r.onNewIndexes:r.onNewProps;if(s===`error`)throw Error(`${a} doesn't exist in "${t}"`);if(s===`skip`)continue nextUpdate;f[a]=i?[]:{}}else f[a]=_?[...s]:{...s};f=f[a],!u&&(d[a]=Object.create(null),d=d[a])}let h=f,g=!Object.hasOwn(h,p);if(g){let e=c(h)&&o(p)?r.onNewIndexes:r.onNewProps;if(e===`error`)throw Error(`Property/Index "${p}" doesnt exist in the path "${t}". Its value is: "${s(h)}"`);if(e===`skip`)continue}let _=g?void 0:h[p];h[p]=typeof n==`function`?n(_):n,delete d[p]}return i},u=(e,t,n)=>l(e,t,n),d=(e,t)=>l(e,t),f=(e,t)=>Object.keys(e).forEach(n=>t(n,e[n])),p=e=>e,m=p,h=e=>e;exports.experimental_extendableUpdate=d,exports.forEachEntry=f,exports.isObjectLiteral=e,exports.isStringIndex=o,exports.parseStringIndex=a,exports.remove=t,exports.s=m,exports.select=n,exports.set=i,exports.splitPathAtLastKey=r,exports.strict=p,exports.strictType=h,exports.update=u;
package/dist/index.mjs CHANGED
@@ -1 +1 @@
1
- const e=e=>!!e&&Object.getPrototypeOf(e)===Object.prototype,t=(t,n)=>{if(!Array.isArray(t)&&!e(t))throw`Wrong type provided in "remove" function`;let r=Array.isArray(n)?n:[n];if(Array.isArray(t)){let e=Symbol(),n=[...t];return r.forEach(t=>{n[t]=e}),n.filter(t=>t!==e)}let i={...t};return r.forEach(e=>delete i[e]),i},n=(e,t)=>{if(typeof t!=`string`)throw Error(`path is not a string`);if(!t)return e;let n=t.split(`.`),r=e;for(;n.length;){if(r==null)return;let e=n[0],t=n.join(`.`);if(Object.hasOwn(r,t))return r[t];if(!Object.hasOwn(r,e))return;r=r[e],n.shift()}return r},r=e=>{if(typeof e!=`string`)throw Error(`Wrong dotPath type: ${e}`);let t=e.lastIndexOf(`.`)+1;return[t?e.slice(0,t-1):``,e.slice(t)]},i=e=>()=>e,a=e=>{let t=parseInt(e);return t.toString()===e?t:null},o=e=>parseInt(e).toString()===e,s=e=>JSON.stringify(e),c=e=>Array.isArray(e),l=(t,n,r={})=>{if(!e(t)&&!c(t))throw Error(`"${s(t)}" is not an literalObject/array`);if(!e(n))throw Error(`wrong "updateObject" type provided`);let i=Array.isArray(t)?[...t]:{...t},a={};for(let[t,l]of Object.entries(n)){if(t===``)continue;let n=t.split(`.`),u=a,d=i;for(;n.length>1;){let i=n.join(`.`);if(Object.hasOwn(d,i)){n=[i];break}let a=n[0],s=d[a],l=e(s),f=c(s);if(!l&&!f&&s!==void 0){if(r.onPathsIntersectedByPrimitiveValues===`error`)throw Error(`Property "${a}" in "${t}" is a primitive`);if(r.onPathsIntersectedByPrimitiveValues===`skip`)continue}if(!u[a]){if(!l&&!f){let e=c(d)&&o(a),i=o(n[1]),s=e?r.onNewIndexes:r.onNewProps;if(s===`error`)throw Error(`${a} doesn't exist in "${t}"`);if(s===`skip`)continue;d[a]=i?[]:{}}else d[a]=f?[...s]:{...s};u[a]={}}d=d[a],u=u[a],n.shift()}let f=n[0],p=d;if(!Object.hasOwn(p,f)){let e=c(p)&&o(f)?r.onNewIndexes:r.onNewProps;if(e===`error`)throw Error(`Property/Index "${f}" doesnt exist in the path "${t}". Its value is: "${s(p)}"`);if(e===`skip`)continue}p[f]=typeof l==`function`?l(p[f]):l,delete u[f]}return i},u=(e,t,n)=>l(e,t,n),d=(e,t)=>l(e,t),f=(e,t)=>Object.keys(e).forEach(n=>t(n,e[n])),p=e=>e,m=p,h=e=>e;export{d as experimental_extendableUpdate,f as forEachEntry,e as isObjectLiteral,o as isStringIndex,a as parseStringIndex,t as remove,m as s,n as select,i as set,r as splitPathAtLastKey,p as strict,h as strictType,u as update};
1
+ const e=e=>!!e&&Object.getPrototypeOf(e)===Object.prototype,t=(t,n)=>{if(!Array.isArray(t)&&!e(t))throw`Wrong type provided in "remove" function`;let r=Array.isArray(n)?n:[n];if(Array.isArray(t)){let e=Symbol(),n=[...t];return r.forEach(t=>{n[t]=e}),n.filter(t=>t!==e)}let i={...t};return r.forEach(e=>delete i[e]),i},n=(e,t)=>{if(typeof t!=`string`)throw Error(`path is not a string`);if(!t)return e;let n=t.split(`.`),r=e;for(;n.length;){if(r==null)return;let e=n[0],t=n.join(`.`);if(Object.hasOwn(r,t))return r[t];if(!Object.hasOwn(r,e))return;r=r[e],n.shift()}return r},r=e=>{if(typeof e!=`string`)throw Error(`Wrong dotPath type: ${e}`);let t=e.lastIndexOf(`.`)+1;return[t?e.slice(0,t-1):``,e.slice(t)]},i=e=>()=>e,a=e=>{let t=parseInt(e);return e!==`NaN`&&t.toString()===e?t:null},o=e=>e!==`NaN`&&parseInt(e).toString()===e,s=e=>JSON.stringify(e),c=e=>Array.isArray(e),l=(t,n,r={})=>{if(!e(t)&&!c(t))throw Error(`"${s(t)}" is not an literalObject/array`);if(!e(n))throw Error(`wrong "updateObject" type provided`);let i=Array.isArray(t)?[...t]:{...t},a=Object.create(null),l=Object.entries(n),u=l.length===1;nextUpdate:for(let[t,n]of l){if(t===``)continue;let l=t.split(`.`),d=a;if(l.includes(`__proto__`))throw Error(`"__proto__" is not allowed in "${t}"`);let f=i,p=l[l.length-1],m=0;for(let n=0;n<l.length-1;n++){let i=t.slice(m);if(Object.hasOwn(f,i)){p=i;break}let a=l[n],s=Object.hasOwn(f,a)?f[a]:void 0;m+=a.length+1;let h=d[a];if(h){f=s,d=h;continue}let g=e(s),_=c(s);if(!g&&!_&&s!==void 0){if(r.onPathsIntersectedByPrimitiveValues===`error`)throw Error(`Property "${a}" in "${t}" is a primitive`);if(r.onPathsIntersectedByPrimitiveValues===`skip`)continue nextUpdate}if(!g&&!_){let e=c(f)&&o(a),i=o(l[n+1]),s=e?r.onNewIndexes:r.onNewProps;if(s===`error`)throw Error(`${a} doesn't exist in "${t}"`);if(s===`skip`)continue nextUpdate;f[a]=i?[]:{}}else f[a]=_?[...s]:{...s};f=f[a],!u&&(d[a]=Object.create(null),d=d[a])}let h=f,g=!Object.hasOwn(h,p);if(g){let e=c(h)&&o(p)?r.onNewIndexes:r.onNewProps;if(e===`error`)throw Error(`Property/Index "${p}" doesnt exist in the path "${t}". Its value is: "${s(h)}"`);if(e===`skip`)continue}let _=g?void 0:h[p];h[p]=typeof n==`function`?n(_):n,delete d[p]}return i},u=(e,t,n)=>l(e,t,n),d=(e,t)=>l(e,t),f=(e,t)=>Object.keys(e).forEach(n=>t(n,e[n])),p=e=>e,m=p,h=e=>e;export{d as experimental_extendableUpdate,f as forEachEntry,e as isObjectLiteral,o as isStringIndex,a as parseStringIndex,t as remove,m as s,n as select,i as set,r as splitPathAtLastKey,p as strict,h as strictType,u as update};
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "maraj",
3
- "version": "2.11.0",
4
- "description": "A lightweight 600 bytes gzipped alternative for immutable updates while dealing with objects and arrays",
3
+ "version": "3.0.0",
4
+ "description": "A lightweight (~1.2 KB gzipped) alternative for immutable updates while dealing with objects and arrays",
5
5
  "keywords": [
6
6
  "shallow",
7
7
  "shallow copy",