@lowdefy/operators 4.6.0 → 4.7.1
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/evaluateOperators.js +180 -0
- package/dist/index.js +2 -2
- package/package.json +3 -3
- package/dist/buildParser.js +0 -187
|
@@ -0,0 +1,180 @@
|
|
|
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 { ConfigError, OperatorError } from '@lowdefy/errors';
|
|
16
|
+
import { type } from '@lowdefy/helpers';
|
|
17
|
+
function setDynamicMarker(node) {
|
|
18
|
+
if (type.isObject(node) || type.isArray(node)) {
|
|
19
|
+
Object.defineProperty(node, '~dyn', {
|
|
20
|
+
value: true,
|
|
21
|
+
enumerable: false,
|
|
22
|
+
configurable: true
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
return node;
|
|
26
|
+
}
|
|
27
|
+
function hasDynChild(node) {
|
|
28
|
+
if (type.isArray(node)) {
|
|
29
|
+
return node.some((item)=>{
|
|
30
|
+
if (type.isArray(item) && item['~dyn'] === true) return true;
|
|
31
|
+
if (type.isObject(item) && item['~dyn'] === true) return true;
|
|
32
|
+
return false;
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
if (type.isObject(node)) {
|
|
36
|
+
return Object.values(node).some((item)=>{
|
|
37
|
+
if (type.isArray(item) && item['~dyn'] === true) return true;
|
|
38
|
+
if (type.isObject(item) && item['~dyn'] === true) return true;
|
|
39
|
+
return false;
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
return false;
|
|
43
|
+
}
|
|
44
|
+
function hasDynamicMarker(value) {
|
|
45
|
+
if ((type.isArray(value) || type.isObject(value)) && value['~dyn'] === true) return true;
|
|
46
|
+
return hasDynChild(value);
|
|
47
|
+
}
|
|
48
|
+
function evaluateOperators({ input, operators, operatorPrefix = '_', env, dynamicIdentifiers, typeNames, args }) {
|
|
49
|
+
if (type.isUndefined(input)) {
|
|
50
|
+
return {
|
|
51
|
+
output: input,
|
|
52
|
+
errors: []
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
if (args && !type.isArray(args)) {
|
|
56
|
+
throw new Error('Operator parser args must be an array.');
|
|
57
|
+
}
|
|
58
|
+
const resolvedDynamicIdentifiers = dynamicIdentifiers ?? new Set();
|
|
59
|
+
const resolvedTypeNames = typeNames ?? new Set();
|
|
60
|
+
const errors = [];
|
|
61
|
+
const parser = {
|
|
62
|
+
parse: ({ args: callArgs, input: callInput, operatorPrefix: callPrefix })=>evaluateOperators({
|
|
63
|
+
input: callInput,
|
|
64
|
+
operators,
|
|
65
|
+
operatorPrefix: callPrefix ?? operatorPrefix,
|
|
66
|
+
env,
|
|
67
|
+
dynamicIdentifiers: resolvedDynamicIdentifiers,
|
|
68
|
+
typeNames: resolvedTypeNames,
|
|
69
|
+
args: callArgs
|
|
70
|
+
})
|
|
71
|
+
};
|
|
72
|
+
function walk(node) {
|
|
73
|
+
// Primitives pass through
|
|
74
|
+
if (!type.isObject(node) && !type.isArray(node)) return node;
|
|
75
|
+
// Arrays: walk children, then bubble up
|
|
76
|
+
if (type.isArray(node)) {
|
|
77
|
+
for(let i = 0; i < node.length; i++){
|
|
78
|
+
node[i] = walk(node[i]);
|
|
79
|
+
}
|
|
80
|
+
if (hasDynamicMarker(node)) {
|
|
81
|
+
return setDynamicMarker(node);
|
|
82
|
+
}
|
|
83
|
+
return node;
|
|
84
|
+
}
|
|
85
|
+
// Object handling
|
|
86
|
+
// Walk children in-place (bottom-up)
|
|
87
|
+
const keys = Object.keys(node);
|
|
88
|
+
for (const k of keys){
|
|
89
|
+
node[k] = walk(node[k]);
|
|
90
|
+
}
|
|
91
|
+
// Operator detection (before type boundary and bubble-up, to match BuildParser order)
|
|
92
|
+
const nonTildeKeys = keys.filter((k)=>!k.startsWith('~'));
|
|
93
|
+
const isSingleKeyObject = nonTildeKeys.length === 1;
|
|
94
|
+
const key = isSingleKeyObject ? nonTildeKeys[0] : null;
|
|
95
|
+
const isOperatorObject = key && key.startsWith(operatorPrefix);
|
|
96
|
+
// Type boundary reset
|
|
97
|
+
const isTypeBoundary = type.isString(node.type) && resolvedTypeNames.has(node.type);
|
|
98
|
+
if (isTypeBoundary) {
|
|
99
|
+
delete node['~dyn'];
|
|
100
|
+
}
|
|
101
|
+
// Bubble up ~dyn from children (but not at type boundaries).
|
|
102
|
+
// _build.* operators always evaluate even with dynamic params, so skip bubble-up for them.
|
|
103
|
+
const isBuildOperator = isOperatorObject && operatorPrefix === '_build.';
|
|
104
|
+
if (!isTypeBoundary && !isBuildOperator && hasDynamicMarker(node)) {
|
|
105
|
+
return setDynamicMarker(node);
|
|
106
|
+
}
|
|
107
|
+
// Skip non-operator objects with ~r marker
|
|
108
|
+
if (type.isString(node['~r']) && !isOperatorObject) return node;
|
|
109
|
+
if (!isSingleKeyObject) return node;
|
|
110
|
+
if (!isOperatorObject) return node;
|
|
111
|
+
const [op, methodName] = `_${key.substring(operatorPrefix.length)}`.split('.');
|
|
112
|
+
// Dynamic identifier check — skip for _build.* operators
|
|
113
|
+
const fullIdentifier = methodName ? `${op}.${methodName}` : op;
|
|
114
|
+
if (operatorPrefix !== '_build.') {
|
|
115
|
+
if (resolvedDynamicIdentifiers.has(fullIdentifier) || resolvedDynamicIdentifiers.has(op)) {
|
|
116
|
+
return setDynamicMarker(node);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
// Unknown operator — mark as dynamic
|
|
120
|
+
if (type.isUndefined(operators[op])) {
|
|
121
|
+
return setDynamicMarker(node);
|
|
122
|
+
}
|
|
123
|
+
// Dynamic params check — skip for _build.* operators (they always evaluate)
|
|
124
|
+
if (operatorPrefix !== '_build.') {
|
|
125
|
+
if (hasDynamicMarker(node[key])) {
|
|
126
|
+
return setDynamicMarker(node);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
const configKey = node['~k'];
|
|
130
|
+
const lineNumber = node['~l'];
|
|
131
|
+
const refId = node['~r'];
|
|
132
|
+
const params = node[key];
|
|
133
|
+
try {
|
|
134
|
+
return operators[op]({
|
|
135
|
+
args,
|
|
136
|
+
arrayIndices: [],
|
|
137
|
+
env,
|
|
138
|
+
methodName,
|
|
139
|
+
operators,
|
|
140
|
+
params,
|
|
141
|
+
operatorPrefix,
|
|
142
|
+
parser,
|
|
143
|
+
runtime: 'node'
|
|
144
|
+
});
|
|
145
|
+
} catch (e) {
|
|
146
|
+
if (e instanceof ConfigError) {
|
|
147
|
+
if (!e.configKey) {
|
|
148
|
+
e.configKey = configKey;
|
|
149
|
+
}
|
|
150
|
+
if (!e.lineNumber) {
|
|
151
|
+
e.lineNumber = lineNumber;
|
|
152
|
+
}
|
|
153
|
+
if (!e.refId) {
|
|
154
|
+
e.refId = refId;
|
|
155
|
+
}
|
|
156
|
+
errors.push(e);
|
|
157
|
+
return null;
|
|
158
|
+
}
|
|
159
|
+
const operatorError = new OperatorError(e.message, {
|
|
160
|
+
cause: e,
|
|
161
|
+
typeName: op,
|
|
162
|
+
received: {
|
|
163
|
+
[key]: params
|
|
164
|
+
},
|
|
165
|
+
configKey: e.configKey ?? configKey
|
|
166
|
+
});
|
|
167
|
+
operatorError.lineNumber = lineNumber;
|
|
168
|
+
operatorError.refId = refId;
|
|
169
|
+
errors.push(operatorError);
|
|
170
|
+
return null;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
const output = walk(input);
|
|
174
|
+
return {
|
|
175
|
+
output,
|
|
176
|
+
errors
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
export default evaluateOperators;
|
|
180
|
+
export { hasDynamicMarker, hasDynChild };
|
package/dist/index.js
CHANGED
|
@@ -12,11 +12,11 @@
|
|
|
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
|
|
15
|
+
*/ import evaluateOperators, { hasDynamicMarker, hasDynChild } from './evaluateOperators.js';
|
|
16
16
|
import getFromArray from './getFromArray.js';
|
|
17
17
|
import getFromObject from './getFromObject.js';
|
|
18
18
|
import ServerParser from './serverParser.js';
|
|
19
19
|
import runClass from './runClass.js';
|
|
20
20
|
import runInstance from './runInstance.js';
|
|
21
21
|
import WebParser from './webParser.js';
|
|
22
|
-
export {
|
|
22
|
+
export { evaluateOperators, hasDynamicMarker, hasDynChild, getFromArray, getFromObject, ServerParser, runClass, runInstance, WebParser };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lowdefy/operators",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.7.1",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"description": "",
|
|
6
6
|
"homepage": "https://lowdefy.com",
|
|
@@ -34,8 +34,8 @@
|
|
|
34
34
|
"dist/*"
|
|
35
35
|
],
|
|
36
36
|
"dependencies": {
|
|
37
|
-
"@lowdefy/errors": "4.
|
|
38
|
-
"@lowdefy/helpers": "4.
|
|
37
|
+
"@lowdefy/errors": "4.7.1",
|
|
38
|
+
"@lowdefy/helpers": "4.7.1"
|
|
39
39
|
},
|
|
40
40
|
"devDependencies": {
|
|
41
41
|
"@jest/globals": "28.1.3",
|
package/dist/buildParser.js
DELETED
|
@@ -1,187 +0,0 @@
|
|
|
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 { ConfigError, OperatorError } from '@lowdefy/errors';
|
|
16
|
-
import { serializer, type } from '@lowdefy/helpers';
|
|
17
|
-
let BuildParser = class BuildParser {
|
|
18
|
-
// Check if value or its immediate children have the dynamic marker
|
|
19
|
-
// Note: Only checks immediate children because bubble-up happens bottom-up in reviver
|
|
20
|
-
static hasDynamicMarker(value) {
|
|
21
|
-
if (type.isArray(value) && value['~dyn'] === true) return true;
|
|
22
|
-
if (type.isObject(value) && value['~dyn'] === true) return true;
|
|
23
|
-
if (type.isArray(value)) {
|
|
24
|
-
return value.some((item)=>{
|
|
25
|
-
if (type.isArray(item) && item['~dyn'] === true) return true;
|
|
26
|
-
if (type.isObject(item) && item['~dyn'] === true) return true;
|
|
27
|
-
return false;
|
|
28
|
-
});
|
|
29
|
-
}
|
|
30
|
-
if (type.isObject(value)) {
|
|
31
|
-
return Object.values(value).some((item)=>{
|
|
32
|
-
if (type.isArray(item) && item['~dyn'] === true) return true;
|
|
33
|
-
if (type.isObject(item) && item['~dyn'] === true) return true;
|
|
34
|
-
return false;
|
|
35
|
-
});
|
|
36
|
-
}
|
|
37
|
-
return false;
|
|
38
|
-
}
|
|
39
|
-
// Set dynamic marker as non-enumerable property
|
|
40
|
-
static setDynamicMarker(value) {
|
|
41
|
-
if (type.isObject(value) || type.isArray(value)) {
|
|
42
|
-
Object.defineProperty(value, '~dyn', {
|
|
43
|
-
value: true,
|
|
44
|
-
enumerable: false
|
|
45
|
-
});
|
|
46
|
-
}
|
|
47
|
-
return value;
|
|
48
|
-
}
|
|
49
|
-
parse({ args, input, operatorPrefix = '_' }) {
|
|
50
|
-
if (type.isUndefined(input)) {
|
|
51
|
-
return {
|
|
52
|
-
output: input,
|
|
53
|
-
errors: []
|
|
54
|
-
};
|
|
55
|
-
}
|
|
56
|
-
if (args && !type.isArray(args)) {
|
|
57
|
-
throw new Error('Operator parser args must be an array.');
|
|
58
|
-
}
|
|
59
|
-
const errors = [];
|
|
60
|
-
const reviver = (_, value)=>{
|
|
61
|
-
// Handle arrays: bubble up dynamic marker if any element is dynamic
|
|
62
|
-
if (type.isArray(value)) {
|
|
63
|
-
if (BuildParser.hasDynamicMarker(value)) {
|
|
64
|
-
return BuildParser.setDynamicMarker(value);
|
|
65
|
-
}
|
|
66
|
-
return value;
|
|
67
|
-
}
|
|
68
|
-
if (!type.isObject(value)) return value;
|
|
69
|
-
// ~shallow placeholders are unresolved refs — mark as dynamic so operators
|
|
70
|
-
// wrapping them are preserved for evaluation after resolution (JIT builds).
|
|
71
|
-
if (value['~shallow'] === true) {
|
|
72
|
-
return BuildParser.setDynamicMarker(value);
|
|
73
|
-
}
|
|
74
|
-
// Check if this is an operator object BEFORE checking ~r
|
|
75
|
-
// Operators in vars have ~r set by copyVarValue (as enumerable), but should still be evaluated
|
|
76
|
-
// Filter out ~ prefixed keys (like ~r, ~k, ~l) when determining if single-key operator
|
|
77
|
-
const keys = Object.keys(value);
|
|
78
|
-
const nonTildeKeys = keys.filter((k)=>!k.startsWith('~'));
|
|
79
|
-
const isSingleKeyObject = nonTildeKeys.length === 1;
|
|
80
|
-
const key = isSingleKeyObject ? nonTildeKeys[0] : null;
|
|
81
|
-
const isOperatorObject = key && key.startsWith(operatorPrefix);
|
|
82
|
-
// Type boundary reset: if object has a 'type' key matching a registered type,
|
|
83
|
-
// delete the ~dyn marker and skip bubble-up to prevent propagation past this boundary
|
|
84
|
-
const isTypeBoundary = type.isString(value.type) && this.typeNames.has(value.type);
|
|
85
|
-
if (isTypeBoundary) {
|
|
86
|
-
delete value['~dyn'];
|
|
87
|
-
}
|
|
88
|
-
// Check if params contain dynamic content (bubble up), but not at type boundaries
|
|
89
|
-
// This must happen BEFORE the ~r check to allow dynamic markers to propagate
|
|
90
|
-
if (!isTypeBoundary && BuildParser.hasDynamicMarker(value)) {
|
|
91
|
-
return BuildParser.setDynamicMarker(value);
|
|
92
|
-
}
|
|
93
|
-
// Skip non-operator objects that have already been processed (have ~r marker)
|
|
94
|
-
// But allow operator objects to be evaluated even if they have ~r
|
|
95
|
-
if (type.isString(value['~r']) && !isOperatorObject) return value;
|
|
96
|
-
if (!isSingleKeyObject) return value;
|
|
97
|
-
if (!isOperatorObject) return value;
|
|
98
|
-
const [op, methodName] = `_${key.substring(operatorPrefix.length)}`.split('.');
|
|
99
|
-
// Check if this operator/method is dynamic
|
|
100
|
-
// Skip this check for _build.* operators (operatorPrefix === '_build.') because
|
|
101
|
-
// build operators should ALWAYS be evaluated at build time
|
|
102
|
-
const fullIdentifier = methodName ? `${op}.${methodName}` : op;
|
|
103
|
-
if (operatorPrefix !== '_build.') {
|
|
104
|
-
if (this.dynamicIdentifiers.has(fullIdentifier) || this.dynamicIdentifiers.has(op)) {
|
|
105
|
-
return BuildParser.setDynamicMarker(value);
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
// If operator is not in our operators map, it's a runtime-only operator
|
|
109
|
-
// Mark it as dynamic to preserve it for runtime evaluation
|
|
110
|
-
if (type.isUndefined(this.operators[op])) {
|
|
111
|
-
return BuildParser.setDynamicMarker(value);
|
|
112
|
-
}
|
|
113
|
-
// Check if params contain dynamic content before evaluating
|
|
114
|
-
if (BuildParser.hasDynamicMarker(value[key])) {
|
|
115
|
-
return BuildParser.setDynamicMarker(value);
|
|
116
|
-
}
|
|
117
|
-
const configKey = value['~k'];
|
|
118
|
-
const lineNumber = value['~l'];
|
|
119
|
-
const refId = value['~r'];
|
|
120
|
-
const params = value[key];
|
|
121
|
-
try {
|
|
122
|
-
const res = this.operators[op]({
|
|
123
|
-
args,
|
|
124
|
-
arrayIndices: [],
|
|
125
|
-
env: this.env,
|
|
126
|
-
methodName,
|
|
127
|
-
operators: this.operators,
|
|
128
|
-
params,
|
|
129
|
-
operatorPrefix,
|
|
130
|
-
parser: this,
|
|
131
|
-
payload: this.payload,
|
|
132
|
-
runtime: 'node',
|
|
133
|
-
secrets: this.secrets,
|
|
134
|
-
user: this.user
|
|
135
|
-
});
|
|
136
|
-
return res;
|
|
137
|
-
} catch (e) {
|
|
138
|
-
if (e instanceof ConfigError) {
|
|
139
|
-
if (!e.configKey) {
|
|
140
|
-
e.configKey = configKey;
|
|
141
|
-
}
|
|
142
|
-
if (!e.lineNumber) {
|
|
143
|
-
e.lineNumber = lineNumber;
|
|
144
|
-
}
|
|
145
|
-
if (!e.refId) {
|
|
146
|
-
e.refId = refId;
|
|
147
|
-
}
|
|
148
|
-
errors.push(e);
|
|
149
|
-
return null;
|
|
150
|
-
}
|
|
151
|
-
const operatorError = new OperatorError(e.message, {
|
|
152
|
-
cause: e,
|
|
153
|
-
typeName: op,
|
|
154
|
-
received: {
|
|
155
|
-
[key]: params
|
|
156
|
-
},
|
|
157
|
-
configKey: e.configKey ?? configKey
|
|
158
|
-
});
|
|
159
|
-
// lineNumber and refId needed by buildRefs consumers (evaluateBuildOperators,
|
|
160
|
-
// evaluateStaticOperators) which run before addKeys — no configKey
|
|
161
|
-
// exists yet, so they use filePath + lineNumber for resolution.
|
|
162
|
-
// refId (from ~r) identifies the source file in the refMap.
|
|
163
|
-
operatorError.lineNumber = lineNumber;
|
|
164
|
-
operatorError.refId = refId;
|
|
165
|
-
errors.push(operatorError);
|
|
166
|
-
return null;
|
|
167
|
-
}
|
|
168
|
-
};
|
|
169
|
-
return {
|
|
170
|
-
output: serializer.copy(input, {
|
|
171
|
-
reviver
|
|
172
|
-
}),
|
|
173
|
-
errors
|
|
174
|
-
};
|
|
175
|
-
}
|
|
176
|
-
constructor({ env, payload, secrets, user, operators, dynamicIdentifiers, typeNames }){
|
|
177
|
-
this.env = env;
|
|
178
|
-
this.operators = operators;
|
|
179
|
-
this.parse = this.parse.bind(this);
|
|
180
|
-
this.payload = payload;
|
|
181
|
-
this.secrets = secrets;
|
|
182
|
-
this.user = user;
|
|
183
|
-
this.dynamicIdentifiers = dynamicIdentifiers ?? new Set();
|
|
184
|
-
this.typeNames = typeNames ?? new Set();
|
|
185
|
-
}
|
|
186
|
-
};
|
|
187
|
-
export default BuildParser;
|