@lowdefy/helpers 5.5.1 → 6.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/dist/set.js CHANGED
@@ -1,4 +1,4 @@
1
- /* eslint-disable no-plusplus */ /* eslint-disable no-use-before-define */ /* eslint-disable no-param-reassign */ /*
1
+ /* eslint-disable no-param-reassign */ /*
2
2
  Copyright 2020-2026 Lowdefy, Inc
3
3
 
4
4
  Licensed under the Apache License, Version 2.0 (the "License");
@@ -33,89 +33,74 @@
33
33
  // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
34
34
  // THE SOFTWARE.
35
35
  import type from './type.js';
36
- function isValidKey(key) {
37
- return key !== '__proto__' && key !== 'constructor' && key !== 'prototype';
36
+ import splitPath from './splitPath.js';
37
+ import { isReserved, ReservedKeyError } from './ReservedKeyError.js';
38
+ // "May I step into this value to reach a child?" Kept local rather than shared with get/unset -
39
+ // a module imported by all three trips a Jest ESM module-cache bug in operators-js' mocked
40
+ // re-imports.
41
+ function isTraversable(value) {
42
+ return !type.isNone(value) && (typeof value === 'object' || typeof value === 'function');
38
43
  }
39
- function isObjectOrFunction(val) {
40
- return val !== null && (typeof val === 'object' || typeof val === 'function');
41
- }
42
- function result(target, path, value, merge) {
43
- if (merge && isObjectOrFunction(target[path]) && isObjectOrFunction(value)) {
44
- target[path] = merge({}, target[path], value);
45
- } else {
46
- target[path] = value;
44
+ // Only own properties count when deciding whether an intermediate needs to be
45
+ // autovivified, so inherited members like `toString` are never descended into.
46
+ function ownValue(target, prop) {
47
+ if (!Object.hasOwn(target, prop)) {
48
+ return undefined;
47
49
  }
50
+ return target[prop];
48
51
  }
49
- // eslint-disable-next-line no-use-before-define
50
- set.memo = {};
51
- function set(target, path, value, options) {
52
+ function set(target, path, value) {
52
53
  if (!type.isObject(target)) {
53
54
  return target;
54
55
  }
55
- const opts = options || {};
56
- if (!type.isArray(path) && !type.isString(path)) {
56
+ if (!type.isString(path)) {
57
57
  return target;
58
58
  }
59
- let { merge } = opts;
60
- if (merge && typeof merge !== 'function') {
61
- merge = Object.assign;
59
+ const keys = splitPath(path);
60
+ const reserved = keys.find(isReserved);
61
+ if (!type.isNone(reserved)) {
62
+ throw new ReservedKeyError(reserved);
62
63
  }
63
- const keys = (type.isArray(path) ? path : split(path, opts)).filter(isValidKey);
64
64
  const len = keys.length;
65
65
  const orig = target;
66
- if (!options && keys.length === 1) {
67
- result(target, keys[0], value, merge);
68
- return target;
69
- }
70
- for(let i = 0; i < len; i++){
71
- const prop = keys[i];
72
- const propUp = keys[i + 1]; // changed to set an array value where the array was undefined be assigning value
73
- if (!isObjectOrFunction(target[prop]) && !type.isInt(parseInt(propUp, 10))) {
74
- // changed
75
- target[prop] = {};
76
- } else if (!isObjectOrFunction(target[prop]) && type.isInt(parseInt(propUp, 10))) {
77
- // added
78
- target[prop] = []; // added
66
+ let idx = 0;
67
+ while(idx < len){
68
+ let prop = keys[idx];
69
+ let next = idx + 1;
70
+ // The strict segment wins if present. On a miss, grow the candidate one segment at a time
71
+ // looking for a literal dotted key already on the target - shortest-first, no backtracking,
72
+ // matching get. If nothing matches, keep the strict segment and autovivify as before.
73
+ if (!Object.hasOwn(target, prop)) {
74
+ let candidate = prop;
75
+ let n = next;
76
+ while(n < len){
77
+ candidate = `${candidate}.${keys[n]}`;
78
+ n += 1;
79
+ if (Object.hasOwn(target, candidate)) {
80
+ prop = candidate;
81
+ next = n;
82
+ break;
83
+ }
84
+ }
79
85
  }
80
- if (i === len - 1) {
81
- result(target, prop, value, merge);
86
+ // Every segment was already rejected by isReserved above, but the assignments
87
+ // below are the prototype-pollution sinks, so the check also sits beside them
88
+ // on the resolved key, where it is local to the write.
89
+ if (prop === '__proto__' || prop === 'constructor' || prop === 'prototype') {
90
+ throw new ReservedKeyError(prop);
91
+ }
92
+ if (next === len) {
93
+ target[prop] = value;
82
94
  break;
83
95
  }
96
+ // The next segment decides whether a missing intermediate becomes an array or an object.
97
+ const propUp = keys[next];
98
+ if (!isTraversable(ownValue(target, prop))) {
99
+ target[prop] = type.isInt(parseInt(propUp, 10)) ? [] : {};
100
+ }
84
101
  target = target[prop];
102
+ idx = next;
85
103
  }
86
104
  return orig;
87
105
  }
88
- function createKey(pattern, options) {
89
- let id = pattern;
90
- if (typeof options === 'undefined') {
91
- return `${id}`;
92
- }
93
- const keys = Object.keys(options);
94
- for(let i = 0; i < keys.length; i++){
95
- const key = keys[i];
96
- id += `;${key}=${String(options[key])}`;
97
- }
98
- return id;
99
- }
100
- export function split(path, options) {
101
- const id = createKey(path, options);
102
- if (set.memo[id]) return set.memo[id];
103
- const char = options && options.separator ? options.separator : '.';
104
- let keys = [];
105
- const res = [];
106
- if (options && typeof options.split === 'function') {
107
- keys = options.split(path);
108
- } else {
109
- keys = path.split(char);
110
- }
111
- for(let i = 0; i < keys.length; i++){
112
- let prop = keys[i];
113
- while(prop && prop.slice(-1) === '\\' && keys[i + 1]){
114
- prop = prop.slice(0, -1) + char + keys[++i];
115
- }
116
- res.push(prop);
117
- }
118
- set.memo[id] = res;
119
- return res;
120
- }
121
106
  export default set;
package/dist/setKey.js ADDED
@@ -0,0 +1,33 @@
1
+ /*
2
+ Copyright 2020-2026 Lowdefy, Inc
3
+
4
+ Licensed under the Apache License, Version 2.0 (the "License");
5
+ you may not use this file except in compliance with the License.
6
+ You may obtain a copy of the License at
7
+
8
+ http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+ Unless required by applicable law or agreed to in writing, software
11
+ distributed under the License is distributed on an "AS IS" BASIS,
12
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ See the License for the specific language governing permissions and
14
+ limitations under the License.
15
+ */ import type from './type.js';
16
+ import { isReserved, ReservedKeyError } from './ReservedKeyError.js';
17
+ // Assigns a single literal key on a plain object. Dots are not path separators.
18
+ function setKey(target, key, value) {
19
+ if (!type.isObject(target)) {
20
+ throw new TypeError('setKey: target must be a plain object');
21
+ }
22
+ if (!type.isString(key)) {
23
+ throw new TypeError('setKey: key must be a string');
24
+ }
25
+ // Rejected even on null-proto targets, where the write would be safe: a caller seeing
26
+ // ReservedKeyError always knows the key was rejected, regardless of target shape.
27
+ if (isReserved(key)) {
28
+ throw new ReservedKeyError(key);
29
+ }
30
+ target[key] = value;
31
+ return target;
32
+ }
33
+ export default setKey;
@@ -0,0 +1,51 @@
1
+ /*
2
+ Copyright 2020-2026 Lowdefy, Inc
3
+
4
+ Licensed under the Apache License, Version 2.0 (the "License");
5
+ you may not use this file except in compliance with the License.
6
+ You may obtain a copy of the License at
7
+
8
+ http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+ Unless required by applicable law or agreed to in writing, software
11
+ distributed under the License is distributed on an "AS IS" BASIS,
12
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ See the License for the specific language governing permissions and
14
+ limitations under the License.
15
+ */ import type from './type.js';
16
+ // Dot-path grammar: `.` separates segments, `\.` is a literal dot and `\\` a
17
+ // literal backslash. Any other backslash is an ordinary character, so keys such
18
+ // as 'a\b' keep working unescaped. joinPath is the inverse.
19
+ function splitPath(path) {
20
+ if (!type.isString(path)) {
21
+ throw new TypeError(`splitPath: path must be a string. Received ${JSON.stringify(path)}.`);
22
+ }
23
+ const segments = [];
24
+ let segment = '';
25
+ let i = 0;
26
+ while(i < path.length){
27
+ const char = path[i];
28
+ if (char === '\\') {
29
+ const next = path[i + 1];
30
+ if (next === '.' || next === '\\') {
31
+ segment += next;
32
+ i += 2;
33
+ continue;
34
+ }
35
+ segment += char;
36
+ i += 1;
37
+ continue;
38
+ }
39
+ if (char === '.') {
40
+ segments.push(segment);
41
+ segment = '';
42
+ i += 1;
43
+ continue;
44
+ }
45
+ segment += char;
46
+ i += 1;
47
+ }
48
+ segments.push(segment);
49
+ return segments;
50
+ }
51
+ export default splitPath;
package/dist/type.js CHANGED
@@ -1,4 +1,4 @@
1
- /* eslint-disable indent */ /* eslint-disable default-case */ /*
1
+ /*
2
2
  Copyright 2020-2026 Lowdefy, Inc
3
3
 
4
4
  Licensed under the Apache License, Version 2.0 (the "License");
@@ -12,129 +12,46 @@
12
12
  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
13
  See the License for the specific language governing permissions and
14
14
  limitations under the License.
15
- */ // Derived from source:
16
- // because both js typeof and instance of sucks! use this.
17
- // https://ultimatecourses.com/blog/understanding-javascript-types-and-reliable-type-checking
18
- const { toString } = Object.prototype;
15
+ */ // Includes `Buffer` so Node Buffers map to 'buffer'. Buffer extends Uint8Array but reports
16
+ // constructor.name 'Buffer', so matching the name here keeps the legacy kindOf return
17
+ // without a Node-only branch.
18
+ const TYPED_ARRAY_RE = /^(Int|Uint|Float)\d+(Clamped)?Array$|^Buffer$/;
19
19
  function ctorName(val) {
20
- return val.constructor && typeof val.constructor === 'function' ? val.constructor.name : null;
20
+ const ctor = val.constructor;
21
+ return typeof ctor === 'function' ? ctor.name : null;
21
22
  }
22
23
  function isArray(val) {
23
- if (Array.isArray) return Array.isArray(val);
24
- return val instanceof Array;
24
+ return Array.isArray(val);
25
25
  }
26
26
  function isError(val) {
27
- return !!val && (val instanceof Error || typeof val.message === 'string' && val.constructor && typeof val.constructor.stackTraceLimit === 'number');
27
+ return val instanceof Error;
28
28
  }
29
29
  function isDate(val) {
30
- if (val instanceof Date && !Number.isNaN(val.getTime())) return true;
31
- return val && typeof val.toDateString === 'function' && typeof val.getDate === 'function' && typeof val.setDate === 'function' && !Number.isNaN(val.getTime());
32
- }
33
- function isRegexp(val) {
34
- if (val instanceof RegExp) return true;
35
- return typeof val.flags === 'string' && typeof val.ignoreCase === 'boolean' && typeof val.multiline === 'boolean' && typeof val.global === 'boolean';
36
- }
37
- function isGeneratorFn(name) {
38
- return ctorName(name) === 'GeneratorFunction';
39
- }
40
- function isGeneratorObj(val) {
41
- return typeof val.throw === 'function' && typeof val.return === 'function' && typeof val.next === 'function';
42
- }
43
- function isArguments(val) {
44
- try {
45
- if (typeof val.length === 'number' && typeof val.callee === 'function') {
46
- return true;
47
- }
48
- } catch (err) {
49
- if (err.message.indexOf('callee') !== -1) {
50
- return true;
51
- }
52
- }
53
- return false;
54
- }
55
- /**
56
- * If you need to support Safari 5-7 (8-10 yr-old browser),
57
- * take a look at https://github.com/feross/is-buffer
58
- */ function isBuffer(val) {
59
- if (val.constructor && typeof val.constructor.isBuffer === 'function') {
60
- return val.constructor.isBuffer(val);
61
- }
62
- return false;
30
+ return val instanceof Date && !Number.isNaN(val.getTime());
63
31
  }
64
32
  function kindOf(val) {
65
- // eslint-disable-next-line no-void
66
- if (val === void 0) return 'undefined';
33
+ if (val === undefined) return 'undefined';
67
34
  if (val === null) return 'null';
68
- let type = typeof val;
69
- if (type === 'boolean') return 'boolean';
70
- if (type === 'string') return 'string';
71
- if (type === 'number') return 'number';
72
- if (type === 'symbol') return 'symbol';
73
- if (type === 'function') {
74
- return isGeneratorFn(val) ? 'generatorfunction' : 'function';
75
- }
76
- if (isArray(val)) return 'array';
77
- if (isDate(val)) return 'date';
78
- if (isError(val)) return 'error';
79
- if (isRegexp(val)) return 'regexp';
80
- if (isArguments(val)) return 'arguments';
81
- if (isBuffer(val)) return 'buffer';
82
- switch(ctorName(val)){
83
- case 'Symbol':
84
- return 'symbol';
85
- case 'Promise':
86
- return 'promise';
87
- // Set, Map, WeakSet, WeakMap
88
- case 'WeakMap':
89
- return 'weakmap';
90
- case 'WeakSet':
91
- return 'weakset';
92
- case 'Map':
93
- return 'map';
94
- case 'Set':
95
- return 'set';
96
- // 8-bit typed arrays
97
- case 'Int8Array':
98
- return 'int8array';
99
- case 'Uint8Array':
100
- return 'uint8array';
101
- case 'Uint8ClampedArray':
102
- return 'uint8clampedarray';
103
- // 16-bit typed arrays
104
- case 'Int16Array':
105
- return 'int16array';
106
- case 'Uint16Array':
107
- return 'uint16array';
108
- // 32-bit typed arrays
109
- case 'Int32Array':
110
- return 'int32array';
111
- case 'Uint32Array':
112
- return 'uint32array';
113
- case 'Float32Array':
114
- return 'float32array';
115
- case 'Float64Array':
116
- return 'float64array';
117
- }
118
- if (isGeneratorObj(val)) {
119
- return 'generator';
120
- }
121
- // Non-plain objects
122
- type = toString.call(val);
123
- switch(type){
124
- case '[object Object]':
125
- return 'object';
126
- // iterators
127
- case '[object Map Iterator]':
128
- return 'mapiterator';
129
- case '[object Set Iterator]':
130
- return 'setiterator';
131
- case '[object String Iterator]':
132
- return 'stringiterator';
133
- case '[object Array Iterator]':
134
- return 'arrayiterator';
135
- }
136
- // other
137
- return type.slice(8, -1).toLowerCase().replace(/\s/g, '');
35
+ const t = typeof val;
36
+ if (t !== 'object' && t !== 'function') return t;
37
+ if (Array.isArray(val)) return 'array';
38
+ if (val instanceof Date) return 'date';
39
+ if (val instanceof Error) return 'error';
40
+ if (val instanceof RegExp) return 'regexp';
41
+ if (val instanceof Map) return 'map';
42
+ if (val instanceof Set) return 'set';
43
+ if (val instanceof WeakMap) return 'weakmap';
44
+ if (val instanceof WeakSet) return 'weakset';
45
+ if (val instanceof Promise) return 'promise';
46
+ if (t === 'function') return 'function';
47
+ const name = ctorName(val);
48
+ if (name && TYPED_ARRAY_RE.test(name)) return name.toLowerCase();
49
+ // Tail check: only plain objects map to 'object'. Non-plain web platform objects
50
+ // (URL, Headers, URLSearchParams, etc.) and user classes with Symbol.toStringTag fall
51
+ // through to the constructor-name branch so type.isObject preserves its
52
+ // "plain object only" contract.
53
+ if (Object.prototype.toString.call(val) === '[object Object]') return 'object';
54
+ return name ? name.toLowerCase() : 'object';
138
55
  }
139
56
  const reISO = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}(?:\.\d*))(?:Z|(\+|-)([\d|:]*))?$/;
140
57
  function isDateString(val) {
@@ -158,8 +75,10 @@ type.isSet = (value)=>kindOf(value) === 'set';
158
75
  type.isNull = (value)=>kindOf(value) === 'null';
159
76
  type.isUndefined = (value)=>kindOf(value) === 'undefined';
160
77
  type.isNone = (value)=>kindOf(value) === 'undefined' || kindOf(value) === 'null';
161
- type.isPrimitive = (value)=>kindOf(value) === 'undefined' || // are we making undefined a primative ?
162
- kindOf(value) === 'null' || kindOf(value) === 'string' || kindOf(value) === 'number' || kindOf(value) === 'boolean' || kindOf(value) === 'date';
78
+ // Lowdefy convention: `date` is treated as primitive. Used by the `_type: primitive`
79
+ // operator and `enforceType('primitive', ...)`. Do not "fix" this without coordinating
80
+ // with the operators-js, blocks-antd selector family, and nunjucks consumers.
81
+ type.isPrimitive = (value)=>kindOf(value) === 'undefined' || kindOf(value) === 'null' || kindOf(value) === 'string' || kindOf(value) === 'number' || kindOf(value) === 'boolean' || kindOf(value) === 'date';
163
82
  type.isEmptyObject = (value)=>kindOf(value) === 'object' && Object.keys(value).length === 0;
164
83
  // Lowdefy operator types
165
84
  function isName(value) {
package/dist/unset.js CHANGED
@@ -1,4 +1,4 @@
1
- /* eslint-disable no-param-reassign */ /*
1
+ /*
2
2
  Copyright 2020-2026 Lowdefy, Inc
3
3
 
4
4
  Licensed under the Apache License, Version 2.0 (the "License");
@@ -33,71 +33,61 @@
33
33
  // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
34
34
  // THE SOFTWARE.
35
35
  import type from './type.js';
36
- import get from './get.js';
37
- const hasValues = (val)=>{
38
- switch(type.typeOf(val)){
39
- case 'boolean':
40
- case 'date':
41
- case 'function':
42
- case 'null':
43
- case 'number':
44
- return true;
45
- case 'undefined':
46
- return false;
47
- case 'regexp':
48
- return val.source !== '(?:)' && val.source !== '';
49
- case 'buffer':
50
- return val.toString() !== '';
51
- case 'error':
52
- return val.message !== '';
53
- case 'string':
54
- case 'arguments':
55
- return val.length !== 0;
56
- case 'file':
57
- case 'map':
58
- case 'set':
59
- return val.size !== 0;
60
- case 'array':
61
- case 'object':
62
- // eslint-disable-next-line no-restricted-syntax
63
- // CHANGED - we are assuming that an empty object and array is a value.
64
- // for (const key of Object.keys(val)) {
65
- // if (hasValues(val[key])) {
66
- // return true;
67
- // }
68
- // }
69
- // return false;
70
- return true;
71
- // everything else
72
- default:
73
- {
74
- return true;
75
- }
76
- }
77
- };
78
- const hasValue = (obj, path, options)=>{
79
- if (type.isObject(obj) && (type.isString(path) || type.isArray(path))) {
80
- return hasValues(get(obj, path, options));
81
- }
82
- return false;
83
- };
36
+ import splitPath from './splitPath.js';
37
+ import { isReserved, ReservedKeyError } from './ReservedKeyError.js';
38
+ // "May I step into this value to reach a child?" Kept local rather than shared with get/set -
39
+ // a module imported by all three trips a Jest ESM module-cache bug in operators-js' mocked
40
+ // re-imports.
41
+ function isTraversable(value) {
42
+ return !type.isNone(value) && (typeof value === 'object' || typeof value === 'function');
43
+ }
84
44
  const unset = (obj, prop)=>{
85
- // support array refence in the form a.0 , a.0.b or a[0] , a[0].b
45
+ // supports array references in the form a.0 or a.0.b
86
46
  if (!type.isObject(obj)) {
87
47
  throw new TypeError('expected an object.');
88
48
  }
89
- if (Object.prototype.hasOwnProperty.call(obj, prop)) {
90
- delete obj[prop];
49
+ if (!type.isString(prop)) {
91
50
  return true;
92
51
  }
93
- if (hasValue(obj, prop)) {
94
- const segs = prop.split('.');
95
- let last = segs.pop();
96
- while(segs.length && segs[segs.length - 1].slice(-1) === '\\'){
97
- last = `${segs.pop().slice(0, -1)}.${last}`;
52
+ const segs = splitPath(prop);
53
+ const reserved = segs.find(isReserved);
54
+ if (!type.isNone(reserved)) {
55
+ throw new ReservedKeyError(reserved);
56
+ }
57
+ const len = segs.length;
58
+ let target = obj;
59
+ let idx = 0;
60
+ while(idx < len){
61
+ let seg = segs[idx];
62
+ let next = idx + 1;
63
+ // The strict segment wins if present. On a miss, grow the candidate one segment at a time
64
+ // looking for a literal dotted key - shortest-first, no backtracking, matching get and set.
65
+ if (!Object.hasOwn(target, seg)) {
66
+ let candidate = seg;
67
+ let n = next;
68
+ while(n < len){
69
+ candidate = `${candidate}.${segs[n]}`;
70
+ n += 1;
71
+ if (Object.hasOwn(target, candidate)) {
72
+ seg = candidate;
73
+ next = n;
74
+ break;
75
+ }
76
+ }
77
+ }
78
+ // Nothing matches at this level, so there is nothing to unset.
79
+ if (!Object.hasOwn(target, seg)) {
80
+ return true;
81
+ }
82
+ if (next === len) {
83
+ return delete target[seg];
84
+ }
85
+ target = target[seg];
86
+ // A missing or primitive intermediate means there is nothing to unset.
87
+ if (!isTraversable(target)) {
88
+ return true;
98
89
  }
99
- while(segs.length)obj = obj[segs.shift()];
100
- return delete obj[last];
90
+ idx = next;
101
91
  }
102
92
  return true;
103
93
  };
@@ -0,0 +1,36 @@
1
+ /*
2
+ Copyright 2020-2026 Lowdefy, Inc
3
+
4
+ Licensed under the Apache License, Version 2.0 (the "License");
5
+ you may not use this file except in compliance with the License.
6
+ You may obtain a copy of the License at
7
+
8
+ http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+ Unless required by applicable law or agreed to in writing, software
11
+ distributed under the License is distributed on an "AS IS" BASIS,
12
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ See the License for the specific language governing permissions and
14
+ limitations under the License.
15
+ */ import type from './type.js';
16
+ import { isReserved, ReservedKeyError } from './ReservedKeyError.js';
17
+ // Deletes a single literal key off a plain object. Dots are not path separators.
18
+ function unsetKey(target, key) {
19
+ if (!type.isObject(target)) {
20
+ throw new TypeError('unsetKey: target must be a plain object');
21
+ }
22
+ if (!type.isString(key)) {
23
+ throw new TypeError('unsetKey: key must be a string');
24
+ }
25
+ // Rejected even on null-proto targets, where the delete would be safe: a caller seeing
26
+ // ReservedKeyError always knows the key was rejected, regardless of target shape.
27
+ if (isReserved(key)) {
28
+ throw new ReservedKeyError(key);
29
+ }
30
+ // Own-property check so deleting an absent key never touches the prototype chain.
31
+ if (Object.hasOwn(target, key)) {
32
+ delete target[key];
33
+ }
34
+ return target;
35
+ }
36
+ export default unsetKey;
package/dist/urlQuery.js CHANGED
@@ -12,17 +12,26 @@
12
12
  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
13
  See the License for the specific language governing permissions and
14
14
  limitations under the License.
15
- */ import serializer from './serializer.js';
15
+ */ // Imported from the source module, not index.js, to avoid a circular import.
16
+ import { isReserved } from './ReservedKeyError.js';
17
+ import serializer from './serializer.js';
18
+ import setKey from './setKey.js';
16
19
  import type from './type.js';
17
20
  const parse = (str)=>{
18
21
  const parsed = new URLSearchParams(str);
19
22
  const deserialized = {};
20
23
  parsed.forEach((value, key)=>{
24
+ // URL keys are user-controlled: skip reserved-named keys (?__proto__=...) and keep parsing.
25
+ if (isReserved(key)) {
26
+ return;
27
+ }
28
+ let resolved;
21
29
  try {
22
- deserialized[key] = serializer.deserializeFromString(value);
30
+ resolved = serializer.deserializeFromString(value);
23
31
  } catch (error) {
24
- deserialized[key] = value;
32
+ resolved = value;
25
33
  }
34
+ setKey(deserialized, key, resolved);
26
35
  });
27
36
  return deserialized;
28
37
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lowdefy/helpers",
3
- "version": "5.5.1",
3
+ "version": "6.0.0",
4
4
  "license": "Apache-2.0",
5
5
  "description": "",
6
6
  "homepage": "https://lowdefy.com",
@@ -31,14 +31,13 @@
31
31
  "dist/*"
32
32
  ],
33
33
  "dependencies": {
34
- "@lowdefy/errors": "5.5.1",
35
- "intl-messageformat": "11.2.7",
36
- "lodash.merge": "4.6.2"
34
+ "@lowdefy/errors": "6.0.0",
35
+ "intl-messageformat": "11.2.7"
37
36
  },
38
37
  "devDependencies": {
39
38
  "@jest/globals": "28.1.3",
40
- "@swc/cli": "0.8.0",
41
- "@swc/core": "1.15.18",
39
+ "@swc/cli": "0.8.1",
40
+ "@swc/core": "1.15.32",
42
41
  "@swc/jest": "0.2.39",
43
42
  "jest": "28.1.3",
44
43
  "jest-diff": "28.1.3"