@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/README.md +404 -13
- package/dist/ReservedKeyError.js +38 -0
- package/dist/extractErrorProps.js +84 -25
- package/dist/get.js +61 -70
- package/dist/getKey.js +36 -0
- package/dist/getOperatorType.js +35 -0
- package/dist/index.js +8 -1
- package/dist/joinPath.js +24 -0
- package/dist/mergeObjects.js +55 -7
- package/dist/serializer.js +28 -8
- package/dist/set.js +53 -68
- package/dist/setKey.js +33 -0
- package/dist/splitPath.js +51 -0
- package/dist/type.js +35 -116
- package/dist/unset.js +49 -59
- package/dist/unsetKey.js +36 -0
- package/dist/urlQuery.js +12 -3
- package/package.json +5 -6
package/dist/set.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/* eslint-disable no-
|
|
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
|
-
|
|
37
|
-
|
|
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
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
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
|
-
|
|
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
|
-
|
|
56
|
-
if (!type.isArray(path) && !type.isString(path)) {
|
|
56
|
+
if (!type.isString(path)) {
|
|
57
57
|
return target;
|
|
58
58
|
}
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
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
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
if (!
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
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
|
-
|
|
81
|
-
|
|
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
|
-
/*
|
|
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
|
-
*/ //
|
|
16
|
-
//
|
|
17
|
-
//
|
|
18
|
-
const
|
|
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
|
-
|
|
20
|
+
const ctor = val.constructor;
|
|
21
|
+
return typeof ctor === 'function' ? ctor.name : null;
|
|
21
22
|
}
|
|
22
23
|
function isArray(val) {
|
|
23
|
-
|
|
24
|
-
return val instanceof Array;
|
|
24
|
+
return Array.isArray(val);
|
|
25
25
|
}
|
|
26
26
|
function isError(val) {
|
|
27
|
-
return
|
|
27
|
+
return val instanceof Error;
|
|
28
28
|
}
|
|
29
29
|
function isDate(val) {
|
|
30
|
-
|
|
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
|
-
|
|
66
|
-
if (val === void 0) return 'undefined';
|
|
33
|
+
if (val === undefined) return 'undefined';
|
|
67
34
|
if (val === null) return 'null';
|
|
68
|
-
|
|
69
|
-
if (
|
|
70
|
-
if (
|
|
71
|
-
if (
|
|
72
|
-
if (
|
|
73
|
-
if (
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
if (
|
|
77
|
-
if (
|
|
78
|
-
if (
|
|
79
|
-
if (
|
|
80
|
-
|
|
81
|
-
if (
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
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
|
-
|
|
162
|
-
|
|
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
|
-
/*
|
|
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
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
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
|
-
//
|
|
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 (
|
|
90
|
-
delete obj[prop];
|
|
49
|
+
if (!type.isString(prop)) {
|
|
91
50
|
return true;
|
|
92
51
|
}
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
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
|
-
|
|
100
|
-
return delete obj[last];
|
|
90
|
+
idx = next;
|
|
101
91
|
}
|
|
102
92
|
return true;
|
|
103
93
|
};
|
package/dist/unsetKey.js
ADDED
|
@@ -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
|
-
*/
|
|
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
|
-
|
|
30
|
+
resolved = serializer.deserializeFromString(value);
|
|
23
31
|
} catch (error) {
|
|
24
|
-
|
|
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": "
|
|
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": "
|
|
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.
|
|
41
|
-
"@swc/core": "1.15.
|
|
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"
|