@routier/core 0.0.1-alpha.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/CHANGELOG.md +8 -0
- package/package.json +91 -0
- package/rspack.config.mjs +65 -0
- package/src/assertions/index.ts +37 -0
- package/src/codegen/SlotPath.ts +20 -0
- package/src/codegen/blocks.ts +578 -0
- package/src/codegen/handlers/CloneHandlerBuilder.ts +13 -0
- package/src/codegen/handlers/CompareHandlerBuilder.ts +13 -0
- package/src/codegen/handlers/DeserializeHandlerBuilder.ts +20 -0
- package/src/codegen/handlers/EnableChangeTrackingHandlerBuilder.ts +13 -0
- package/src/codegen/handlers/EnrichmentHandlerBuilder.ts +28 -0
- package/src/codegen/handlers/FreezeHandlerBuilder.ts +13 -0
- package/src/codegen/handlers/HashHandlerBuilder.ts +25 -0
- package/src/codegen/handlers/HashTypeHandlerBuilder.ts +11 -0
- package/src/codegen/handlers/IdSelectorHandlerBuilder.ts +10 -0
- package/src/codegen/handlers/MergeHandlerBuilder.ts +19 -0
- package/src/codegen/handlers/PrepareHandlerBuilder.ts +23 -0
- package/src/codegen/handlers/SerializeHandlerBuilder.ts +15 -0
- package/src/codegen/handlers/StripHandlerBuilder.ts +18 -0
- package/src/codegen/handlers/clone/CloneObjectHandler.ts +29 -0
- package/src/codegen/handlers/clone/CloneValueHandler.ts +33 -0
- package/src/codegen/handlers/compare/CompareObjectHandler.ts +15 -0
- package/src/codegen/handlers/compare/CompareValueHandler.ts +27 -0
- package/src/codegen/handlers/deserialize/DeserializeComputedValueHandler.ts +16 -0
- package/src/codegen/handlers/deserialize/DeserializeDateHandler.ts +45 -0
- package/src/codegen/handlers/deserialize/DeserializeFunctionHandler.ts +16 -0
- package/src/codegen/handlers/deserialize/DeserializeObjectHandler.ts +29 -0
- package/src/codegen/handlers/deserialize/DeserializeValueHandler.ts +33 -0
- package/src/codegen/handlers/enableChangeTracking/EnableChangeTrackingObjectHandler.ts +20 -0
- package/src/codegen/handlers/enableChangeTracking/EnableChangeTrackingPrimitiveValueHandler.ts +15 -0
- package/src/codegen/handlers/enrichment/EnrichmentComputedValueHandler.ts +37 -0
- package/src/codegen/handlers/enrichment/EnrichmentDefaultFunctionHandler.ts +50 -0
- package/src/codegen/handlers/enrichment/EnrichmentDefaultValueHandler.ts +18 -0
- package/src/codegen/handlers/enrichment/EnrichmentFunctionHandler.ts +38 -0
- package/src/codegen/handlers/enrichment/EnrichmentNullableObjectHandler.ts +44 -0
- package/src/codegen/handlers/enrichment/EnrichmentObjectHandler.ts +29 -0
- package/src/codegen/handlers/enrichment/EnrichmentObjectIdentityHandler.ts +16 -0
- package/src/codegen/handlers/enrichment/EnrichmentPrimitiveHandler.ts +13 -0
- package/src/codegen/handlers/enrichment/EnrichmentPrimitiveIdentityHandler.ts +21 -0
- package/src/codegen/handlers/freeze/FreezeObjectHandler.ts +20 -0
- package/src/codegen/handlers/freeze/FreezePrimitiveValueHandler.ts +15 -0
- package/src/codegen/handlers/hash/HashComputedValueHandler.ts +15 -0
- package/src/codegen/handlers/hash/HashDateHandler.ts +26 -0
- package/src/codegen/handlers/hash/HashFunctionHandler.ts +16 -0
- package/src/codegen/handlers/hash/HashIdentityHandler.ts +15 -0
- package/src/codegen/handlers/hash/HashKeyHandler.ts +26 -0
- package/src/codegen/handlers/hash/HashValueHandler.ts +26 -0
- package/src/codegen/handlers/hashType/HashTypeValueHandler.ts +20 -0
- package/src/codegen/handlers/idSelector/IdSelectorValueHandler.ts +28 -0
- package/src/codegen/handlers/index.ts +13 -0
- package/src/codegen/handlers/merge/MergeComputedValueHandler.ts +37 -0
- package/src/codegen/handlers/merge/MergeDefaultFunctionHandler.ts +40 -0
- package/src/codegen/handlers/merge/MergeDefaultValueHandler.ts +32 -0
- package/src/codegen/handlers/merge/MergeFunctionHandler.ts +16 -0
- package/src/codegen/handlers/merge/MergePrimitiveHandler.ts +21 -0
- package/src/codegen/handlers/prepare/PrepareComputedValueHandler.ts +16 -0
- package/src/codegen/handlers/prepare/PrepareFunctionHandler.ts +16 -0
- package/src/codegen/handlers/prepare/PrepareIdentityHandler.ts +21 -0
- package/src/codegen/handlers/prepare/PrepareKeyHandler.ts +21 -0
- package/src/codegen/handlers/prepare/PrepareObjectHandler.ts +38 -0
- package/src/codegen/handlers/prepare/PrepareValueHandler.ts +36 -0
- package/src/codegen/handlers/serialize/SerializeDateHandler.ts +45 -0
- package/src/codegen/handlers/serialize/SerializeObjectHandler.ts +28 -0
- package/src/codegen/handlers/serialize/SerializeValueHandler.ts +33 -0
- package/src/codegen/handlers/strip/StripIdentityHandler.ts +15 -0
- package/src/codegen/handlers/strip/StripKeyHandler.ts +15 -0
- package/src/codegen/handlers/strip/StripObjectHandler.ts +35 -0
- package/src/codegen/handlers/strip/StripValueHandler.ts +36 -0
- package/src/codegen/handlers/types.ts +119 -0
- package/src/codegen/index.ts +2 -0
- package/src/codegen/types.ts +2 -0
- package/src/codegen/utils.ts +74 -0
- package/src/collections/Changes.test.ts +337 -0
- package/src/collections/Changes.ts +177 -0
- package/src/collections/IdSet.ts +28 -0
- package/src/collections/MemoryDataCollection.test.ts +424 -0
- package/src/collections/MemoryDataCollection.ts +134 -0
- package/src/collections/SchemaCollection.ts +22 -0
- package/src/collections/TagCollection.test.ts +443 -0
- package/src/collections/TagCollection.ts +62 -0
- package/src/collections/index.ts +5 -0
- package/src/errors/SchemaError.ts +6 -0
- package/src/errors/index.ts +1 -0
- package/src/expressions/index.ts +3 -0
- package/src/expressions/parser.test.ts +913 -0
- package/src/expressions/parser.ts +661 -0
- package/src/expressions/types.ts +184 -0
- package/src/expressions/utils.test.ts +346 -0
- package/src/expressions/utils.ts +59 -0
- package/src/index.ts +12 -0
- package/src/performance/index.ts +26 -0
- package/src/pipeline/SyncronousQueue.test.ts +269 -0
- package/src/pipeline/SyncronousQueue.ts +30 -0
- package/src/pipeline/TrampolinePipeline.test.ts +374 -0
- package/src/pipeline/TrampolinePipeline.ts +437 -0
- package/src/pipeline/index.ts +2 -0
- package/src/plugins/EphemeralDataPlugin.ts +132 -0
- package/src/plugins/capabilities/DbPluginCapability.ts +111 -0
- package/src/plugins/capabilities/index.ts +2 -0
- package/src/plugins/capabilities/logging/DbPluginLoggingCapability.ts +261 -0
- package/src/plugins/capabilities/logging/index.ts +1 -0
- package/src/plugins/index.ts +6 -0
- package/src/plugins/query/Query.ts +67 -0
- package/src/plugins/query/QueryOptionsCollection.test.ts +86 -0
- package/src/plugins/query/QueryOptionsCollection.ts +154 -0
- package/src/plugins/query/index.ts +3 -0
- package/src/plugins/query/types.ts +46 -0
- package/src/plugins/replication/OptimisticReplicationDbPlugin.ts +202 -0
- package/src/plugins/replication/ReplicationDbPlugin.ts +137 -0
- package/src/plugins/replication/index.ts +3 -0
- package/src/plugins/replication/types.ts +5 -0
- package/src/plugins/translators/DataTranslator.ts +46 -0
- package/src/plugins/translators/JsonTranslator.test.ts +618 -0
- package/src/plugins/translators/JsonTranslator.ts +211 -0
- package/src/plugins/translators/SqlTranslator.ts +45 -0
- package/src/plugins/translators/index.ts +3 -0
- package/src/plugins/types.ts +114 -0
- package/src/results/Result.ts +91 -0
- package/src/results/index.ts +3 -0
- package/src/results/types.ts +17 -0
- package/src/results/utils.ts +15 -0
- package/src/schema/PropertyInfo.test.ts +479 -0
- package/src/schema/PropertyInfo.ts +374 -0
- package/src/schema/SchemaDefinition.ts +626 -0
- package/src/schema/builder.ts +18 -0
- package/src/schema/index.ts +7 -0
- package/src/schema/property/base/SchemaBase.ts +49 -0
- package/src/schema/property/base/index.ts +1 -0
- package/src/schema/property/modifiers/SchemaDefault.ts +29 -0
- package/src/schema/property/modifiers/SchemaDeserialize.ts +23 -0
- package/src/schema/property/modifiers/SchemaDistinct.ts +14 -0
- package/src/schema/property/modifiers/SchemaFrom.ts +54 -0
- package/src/schema/property/modifiers/SchemaIdentity.ts +13 -0
- package/src/schema/property/modifiers/SchemaIndex.ts +60 -0
- package/src/schema/property/modifiers/SchemaKey.ts +28 -0
- package/src/schema/property/modifiers/SchemaNullable.ts +18 -0
- package/src/schema/property/modifiers/SchemaOptional.ts +19 -0
- package/src/schema/property/modifiers/SchemaReadonly.ts +34 -0
- package/src/schema/property/modifiers/SchemaSerialize.ts +18 -0
- package/src/schema/property/modifiers/SchemaTracked.ts +14 -0
- package/src/schema/property/modifiers/index.ts +12 -0
- package/src/schema/property/types/SchemaArray.ts +50 -0
- package/src/schema/property/types/SchemaBoolean.ts +59 -0
- package/src/schema/property/types/SchemaDate.ts +58 -0
- package/src/schema/property/types/SchemaNumber.ts +69 -0
- package/src/schema/property/types/SchemaObject.ts +44 -0
- package/src/schema/property/types/SchemaString.ts +69 -0
- package/src/schema/property/types/index.ts +6 -0
- package/src/schema/table/SchemaComputed.ts +20 -0
- package/src/schema/table/SchemaFunction.ts +15 -0
- package/src/schema/table/index.ts +2 -0
- package/src/schema/types.ts +238 -0
- package/src/types/index.ts +5 -0
- package/src/utilities/arrays.test.ts +312 -0
- package/src/utilities/arrays.ts +12 -0
- package/src/utilities/dates.test.ts +388 -0
- package/src/utilities/dates.ts +11 -0
- package/src/utilities/dbPluginEventUtils.ts +44 -0
- package/src/utilities/index.ts +10 -0
- package/src/utilities/objects.ts +7 -0
- package/src/utilities/queryOptionsCollection.ts +16 -0
- package/src/utilities/replication.ts +23 -0
- package/src/utilities/runtime.ts +3 -0
- package/src/utilities/strings.ts +18 -0
- package/src/utilities/types.ts +1 -0
- package/src/utilities/uuid.ts +56 -0
- package/tsconfig.json +28 -0
- package/vitest.config.ts +11 -0
|
@@ -0,0 +1,661 @@
|
|
|
1
|
+
import { CompiledSchema, SchemaTypes } from "../schema";
|
|
2
|
+
import { Expression, OperatorExpression, ComparatorExpression, ValueExpression, PropertyExpression, Filter, ParamsFilter, Operator } from "./types";
|
|
3
|
+
|
|
4
|
+
// Pre-compiled regex patterns for better performance
|
|
5
|
+
const METHOD_REGEX = /([a-zA-Z0-9_.]+)\.(startsWith|endsWith|includes)\(([^)]+)\)(\s*(===|==|!==|!=)\s*(true|false))?/;
|
|
6
|
+
const TRANSFORM_METHOD_REGEX = /([a-zA-Z0-9_.]+)\.(toLowerCase|toUpperCase|toLocaleLowerCase|toLocaleUpperCase)\(\)\.(startsWith|endsWith|includes)\(((?:[^()]|\([^)]*\))*)\)(\s*(===|==|!==|!=)\s*(true|false))?/;
|
|
7
|
+
const EQUALITY_REGEX = /([^=!<>]+?)\s*(===|==|!==|!=)\s*(.+)/;
|
|
8
|
+
const COMPARISON_REGEX = /([^=!<>]+?)\s*(>=|<=|>|<)\s*(.+)/;
|
|
9
|
+
const PARAM_PATH_REGEX = /^([a-zA-Z0-9_.]+)/;
|
|
10
|
+
|
|
11
|
+
// Pre-compiled regex patterns for comment removal
|
|
12
|
+
const SINGLE_LINE_COMMENT_REGEX = /\/\/.*$/gm;
|
|
13
|
+
const MULTI_LINE_COMMENT_REGEX = /\/\*[\s\S]*?\*\//g;
|
|
14
|
+
const WHITESPACE_REGEX = /\s+/g;
|
|
15
|
+
|
|
16
|
+
// need to have negated + strict
|
|
17
|
+
const comparators: Record<string, ComparatorExpression> = {
|
|
18
|
+
startsWith: new ComparatorExpression({
|
|
19
|
+
comparator: "starts-with",
|
|
20
|
+
negated: false,
|
|
21
|
+
strict: false
|
|
22
|
+
}),
|
|
23
|
+
endsWith: new ComparatorExpression({
|
|
24
|
+
comparator: "ends-with",
|
|
25
|
+
negated: false,
|
|
26
|
+
strict: false
|
|
27
|
+
}),
|
|
28
|
+
includes: new ComparatorExpression({
|
|
29
|
+
comparator: "includes",
|
|
30
|
+
negated: false,
|
|
31
|
+
strict: false
|
|
32
|
+
}),
|
|
33
|
+
"==": new ComparatorExpression({
|
|
34
|
+
comparator: "equals",
|
|
35
|
+
negated: false,
|
|
36
|
+
strict: false
|
|
37
|
+
}),
|
|
38
|
+
"===": new ComparatorExpression({
|
|
39
|
+
comparator: "equals",
|
|
40
|
+
negated: false,
|
|
41
|
+
strict: true
|
|
42
|
+
}),
|
|
43
|
+
"!=": new ComparatorExpression({
|
|
44
|
+
comparator: "equals",
|
|
45
|
+
negated: true,
|
|
46
|
+
strict: false
|
|
47
|
+
}),
|
|
48
|
+
"!==": new ComparatorExpression({
|
|
49
|
+
comparator: "equals",
|
|
50
|
+
negated: true,
|
|
51
|
+
strict: true
|
|
52
|
+
}),
|
|
53
|
+
">=": new ComparatorExpression({
|
|
54
|
+
comparator: "greater-than-equals",
|
|
55
|
+
negated: false,
|
|
56
|
+
strict: false
|
|
57
|
+
}),
|
|
58
|
+
">==": new ComparatorExpression({
|
|
59
|
+
comparator: "greater-than-equals",
|
|
60
|
+
negated: false,
|
|
61
|
+
strict: true
|
|
62
|
+
}),
|
|
63
|
+
"<=": new ComparatorExpression({
|
|
64
|
+
comparator: "less-than-equals",
|
|
65
|
+
negated: false,
|
|
66
|
+
strict: false
|
|
67
|
+
}),
|
|
68
|
+
"<==": new ComparatorExpression({
|
|
69
|
+
comparator: "less-than-equals",
|
|
70
|
+
negated: false,
|
|
71
|
+
strict: true
|
|
72
|
+
}),
|
|
73
|
+
">": new ComparatorExpression({
|
|
74
|
+
comparator: "greater-than",
|
|
75
|
+
negated: false,
|
|
76
|
+
strict: false
|
|
77
|
+
}),
|
|
78
|
+
"<": new ComparatorExpression({
|
|
79
|
+
comparator: "less-than",
|
|
80
|
+
negated: false,
|
|
81
|
+
strict: false
|
|
82
|
+
})
|
|
83
|
+
} as const;
|
|
84
|
+
|
|
85
|
+
// Optimized comparator lookup using Map
|
|
86
|
+
const COMPARATOR_MAP = new Map(Object.entries(comparators));
|
|
87
|
+
|
|
88
|
+
// Error message constants
|
|
89
|
+
const ERROR_MESSAGES = {
|
|
90
|
+
COMPARATOR_NOT_FOUND: (value: string) => `Cannot find comparator: ${value}`,
|
|
91
|
+
PROPERTY_NOT_FOUND: (path: string) => `Error parsing query, could not find PropertyInfo for path: ${path}`,
|
|
92
|
+
PARAM_PATH_NOT_FOUND: (value: string, params: any) => `Cannot find path in params for .where(). Make sure parameters are not used inline.\r\nPath: ${value}, Params: ${JSON.stringify(params)}`
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
export const combineExpressions = (...expressions: Expression[]): Expression => {
|
|
96
|
+
|
|
97
|
+
if (expressions.length === 0) {
|
|
98
|
+
throw new Error("combineExpressions requires at least 1 expression");
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
if (expressions.length === 1) {
|
|
103
|
+
return expressions[0];
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Start with the first expression
|
|
107
|
+
let result = expressions[0];
|
|
108
|
+
|
|
109
|
+
// Loop through remaining expressions and combine them
|
|
110
|
+
for (let i = 1; i < expressions.length; i++) {
|
|
111
|
+
result = new OperatorExpression({
|
|
112
|
+
operator: "&&",
|
|
113
|
+
left: result,
|
|
114
|
+
right: expressions[i]
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
return result;
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
export const toExpression = <T extends any, P extends any>(schema: CompiledSchema<any>, fn: Filter<T> | ParamsFilter<T, P>, params?: P) => {
|
|
122
|
+
try {
|
|
123
|
+
|
|
124
|
+
const stringifiedFunction = fn.toString();
|
|
125
|
+
|
|
126
|
+
// Optimized string parsing
|
|
127
|
+
const arrowIndex = stringifiedFunction.indexOf('=>');
|
|
128
|
+
if (arrowIndex === -1) {
|
|
129
|
+
throw new Error("Invalid Function");
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const parameterNames = stringifiedFunction.substring(0, arrowIndex).trim();
|
|
133
|
+
let expression = stringifiedFunction.substring(arrowIndex + 2).trim();
|
|
134
|
+
|
|
135
|
+
// Remove JavaScript comments from the expression
|
|
136
|
+
expression = expression
|
|
137
|
+
.replace(SINGLE_LINE_COMMENT_REGEX, '') // Remove single-line comments
|
|
138
|
+
.replace(MULTI_LINE_COMMENT_REGEX, '') // Remove multi-line comments
|
|
139
|
+
.replace(WHITESPACE_REGEX, ' ') // Normalize whitespace
|
|
140
|
+
.trim();
|
|
141
|
+
|
|
142
|
+
let parameterData: { name: string, data: P } | undefined = undefined;
|
|
143
|
+
|
|
144
|
+
if (params != null) {
|
|
145
|
+
let name: string;
|
|
146
|
+
if (parameterNames.includes("[") && parameterNames.includes("]")) {
|
|
147
|
+
// Optimized parameter name extraction
|
|
148
|
+
const commaIndex = parameterNames.indexOf(",");
|
|
149
|
+
if (commaIndex !== -1) {
|
|
150
|
+
const bracketIndex = parameterNames.indexOf("]", commaIndex);
|
|
151
|
+
if (bracketIndex !== -1) {
|
|
152
|
+
name = parameterNames.substring(commaIndex + 1, bracketIndex).trim();
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
parameterData = {
|
|
158
|
+
name,
|
|
159
|
+
data: params
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
return parseExpressionToTree(schema, expression, parameterData);
|
|
164
|
+
} catch (e) {
|
|
165
|
+
console.warn("Error parsing expression", e);
|
|
166
|
+
return Expression.NOT_PARSABLE;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const parseExpressionToTree = <P extends any>(schema: CompiledSchema<any>, expression: string, params?: { name: string, data: P }) => {
|
|
171
|
+
|
|
172
|
+
const parse = (exp: string): Expression => {
|
|
173
|
+
// Remove any wrapping parentheses
|
|
174
|
+
exp = exp.trim();
|
|
175
|
+
if (exp.startsWith('(') && exp.endsWith(')')) {
|
|
176
|
+
let depth = 0;
|
|
177
|
+
let isWrapper = true;
|
|
178
|
+
for (let i = 0; i < exp.length; i++) {
|
|
179
|
+
const char = exp[i];
|
|
180
|
+
if (char === '(') {
|
|
181
|
+
depth++;
|
|
182
|
+
} else if (char === ')') {
|
|
183
|
+
depth--;
|
|
184
|
+
if (depth < 0) break; // Early exit for invalid parentheses
|
|
185
|
+
}
|
|
186
|
+
// Early exit if we find a closing parenthesis that's not the last character
|
|
187
|
+
if (depth === 0 && i !== exp.length - 1) {
|
|
188
|
+
isWrapper = false;
|
|
189
|
+
break; // Early exit
|
|
190
|
+
}
|
|
191
|
+
// Early exit if we go negative (invalid parentheses)
|
|
192
|
+
if (depth < 0) {
|
|
193
|
+
isWrapper = false;
|
|
194
|
+
break;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
if (isWrapper) {
|
|
198
|
+
exp = exp.slice(1, -1).trim();
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// Parse based on the operator precedence
|
|
203
|
+
let operator: Operator | null = null, splitIndex = -1, depth = 0;
|
|
204
|
+
|
|
205
|
+
for (let i = 0; i < exp.length - 1; i++) {
|
|
206
|
+
const char = exp[i];
|
|
207
|
+
if (char === '(') depth++;
|
|
208
|
+
else if (char === ')') depth--;
|
|
209
|
+
else if (depth === 0) {
|
|
210
|
+
// Optimized operator detection using character comparison
|
|
211
|
+
if (char === '&' && exp[i + 1] === '&') {
|
|
212
|
+
operator = '&&';
|
|
213
|
+
splitIndex = i;
|
|
214
|
+
break; // AND takes precedence over OR
|
|
215
|
+
} else if (operator === null && char === '|' && exp[i + 1] === '|') {
|
|
216
|
+
operator = '||';
|
|
217
|
+
splitIndex = i;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
if (operator) {
|
|
223
|
+
const left = exp.slice(0, splitIndex).trim();
|
|
224
|
+
const right = exp.slice(splitIndex + 2).trim();
|
|
225
|
+
|
|
226
|
+
return new OperatorExpression({
|
|
227
|
+
operator,
|
|
228
|
+
left: parse(left),
|
|
229
|
+
right: parse(right)
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// If no operator, try to parse as a condition
|
|
234
|
+
return parseCondition(schema, exp, params);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
return parse(expression);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
const convertAndAssignValue = (valueExpression: unknown, propertyPathExpression: unknown) => {
|
|
241
|
+
|
|
242
|
+
assertIsPropertyPathExpression(propertyPathExpression);
|
|
243
|
+
|
|
244
|
+
assertIsValueExpression(valueExpression)
|
|
245
|
+
|
|
246
|
+
const propertyType = propertyPathExpression.property.type;
|
|
247
|
+
|
|
248
|
+
valueExpression.value = converters[propertyType](valueExpression.value);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
const converters: Record<SchemaTypes, (value: unknown) => unknown> = {
|
|
252
|
+
Array: v => v,
|
|
253
|
+
Boolean: v => v == null ? v : Boolean(v),
|
|
254
|
+
Computed: v => v,
|
|
255
|
+
Date: v => v,
|
|
256
|
+
Definition: v => v,
|
|
257
|
+
Function: v => v,
|
|
258
|
+
Number: v => v == null ? v : Number(v),
|
|
259
|
+
Object: v => v,
|
|
260
|
+
String: v => v == null ? v : String(v)
|
|
261
|
+
};
|
|
262
|
+
|
|
263
|
+
const isExpression = (value: unknown): value is Expression => {
|
|
264
|
+
return typeof value === "object" && value !== null && "type" in value;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
function assertIsExpression(value: unknown): asserts value is Expression {
|
|
268
|
+
if (isExpression(value) === false) {
|
|
269
|
+
throw new Error("Assertion Failed: Value is not a Expression")
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
function assertIsPropertyPathExpression(value: unknown): asserts value is PropertyExpression {
|
|
274
|
+
|
|
275
|
+
assertIsExpression(value);
|
|
276
|
+
|
|
277
|
+
if (!("property" in value)) {
|
|
278
|
+
throw new Error("Assertion Failed: Value is not a PropertyPathExpression")
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
function assertIsValueExpression(value: unknown): asserts value is ValueExpression {
|
|
283
|
+
|
|
284
|
+
assertIsExpression(value);
|
|
285
|
+
|
|
286
|
+
if (!("value" in value)) {
|
|
287
|
+
throw new Error("Assertion Failed: Value is not a ValueExpression")
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
// Helper function to detect if a string is a property path
|
|
292
|
+
const isPropertyPath = (value: string): boolean => {
|
|
293
|
+
return value.includes('.') && value.match(/^[a-zA-Z0-9_.]+$/) !== null;
|
|
294
|
+
};
|
|
295
|
+
|
|
296
|
+
// Helper function to determine if we need to swap the operator for reversed comparisons
|
|
297
|
+
const getSwappedOperator = (operator: string): string => {
|
|
298
|
+
const swapMap: Record<string, string> = {
|
|
299
|
+
'>': '<',
|
|
300
|
+
'<': '>',
|
|
301
|
+
'>=': '<=',
|
|
302
|
+
'<=': '>='
|
|
303
|
+
};
|
|
304
|
+
return swapMap[operator] || operator;
|
|
305
|
+
};
|
|
306
|
+
|
|
307
|
+
const parseCondition = <P extends any>(schema: CompiledSchema<any>, expression: string, params?: { name: string, data: P }): Expression => {
|
|
308
|
+
|
|
309
|
+
// Optimized string operations - single trim operation
|
|
310
|
+
const trimmed = expression.trim();
|
|
311
|
+
const isNegation = trimmed.startsWith('!');
|
|
312
|
+
const finalExpression = isNegation ? trimmed.slice(1).trim() : trimmed;
|
|
313
|
+
|
|
314
|
+
// Enhanced pattern matching for complex expressions
|
|
315
|
+
const methodMatch = finalExpression.match(METHOD_REGEX);
|
|
316
|
+
const transformMethodMatch = finalExpression.match(TRANSFORM_METHOD_REGEX);
|
|
317
|
+
const equalityMatch = finalExpression.match(EQUALITY_REGEX);
|
|
318
|
+
const comparisonMatch = finalExpression.match(COMPARISON_REGEX);
|
|
319
|
+
|
|
320
|
+
if (methodMatch) {
|
|
321
|
+
// Handle .startsWith or .endsWith
|
|
322
|
+
const comparator = getComparator(methodMatch[2]);
|
|
323
|
+
|
|
324
|
+
if (isNegation) {
|
|
325
|
+
comparator.negated = isNegation;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
// Check if the left side is a parameter path
|
|
329
|
+
const leftSide = methodMatch[1];
|
|
330
|
+
const rightSide = methodMatch[3];
|
|
331
|
+
|
|
332
|
+
if (params && leftSide.startsWith(params.name)) {
|
|
333
|
+
// This is a parameter path on the left side (e.g., params.distinctPlayers.includes(entity.playerId))
|
|
334
|
+
// For includes method, we need to swap left and right sides
|
|
335
|
+
if (methodMatch[2] === 'includes') {
|
|
336
|
+
comparator.left = getValue(leftSide, params);
|
|
337
|
+
comparator.right = getProperty(schema, rightSide);
|
|
338
|
+
} else {
|
|
339
|
+
// For other methods, return NOT_PARSABLE for now
|
|
340
|
+
return Expression.NOT_PARSABLE;
|
|
341
|
+
}
|
|
342
|
+
} else {
|
|
343
|
+
// Normal case: property on left, value on right
|
|
344
|
+
comparator.left = getProperty(schema, leftSide);
|
|
345
|
+
comparator.right = getValue(rightSide, params);
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
// If the comparison is explicitly to false, mark it as negated
|
|
349
|
+
if (methodMatch[6] === "false") {
|
|
350
|
+
comparator.negated = true;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
return comparator;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
// Check for transformations on the value side (right side of comparison) - check this FIRST
|
|
357
|
+
const valueTransformMatch = finalExpression.match(/([^=!<>]+?)\s*(===|==|!==|!=)\s*([^)]+)\.(toLowerCase|toUpperCase|toLocaleLowerCase|toLocaleUpperCase)\(\)/);
|
|
358
|
+
if (valueTransformMatch) {
|
|
359
|
+
const comparator = getComparator(valueTransformMatch[2]);
|
|
360
|
+
|
|
361
|
+
if (isNegation) {
|
|
362
|
+
comparator.negated = isNegation;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
// Create the property expression for the left side (no transformer)
|
|
366
|
+
const propertyExpression = getProperty(schema, valueTransformMatch[1].trim());
|
|
367
|
+
|
|
368
|
+
// Create a ValueExpression for the right side with transformer
|
|
369
|
+
const valueExpression = getValue(valueTransformMatch[3], params);
|
|
370
|
+
|
|
371
|
+
// Set transformer and locale based on the method
|
|
372
|
+
const method = valueTransformMatch[4];
|
|
373
|
+
if (method === 'toLowerCase' || method === 'toLocaleLowerCase') {
|
|
374
|
+
valueExpression.transformer = 'to-lower-case';
|
|
375
|
+
if (method === 'toLocaleLowerCase') {
|
|
376
|
+
valueExpression.locale = 'en-US';
|
|
377
|
+
}
|
|
378
|
+
} else {
|
|
379
|
+
valueExpression.transformer = 'to-upper-case';
|
|
380
|
+
if (method === 'toLocaleUpperCase') {
|
|
381
|
+
valueExpression.locale = 'en-US';
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
comparator.left = propertyExpression;
|
|
386
|
+
comparator.right = valueExpression;
|
|
387
|
+
|
|
388
|
+
return comparator;
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
if (transformMethodMatch) {
|
|
392
|
+
// Handle .toLowerCase or .toUpperCase on the property side
|
|
393
|
+
const comparator = getComparator(transformMethodMatch[3]); // e.g., startsWith, endsWith, includes
|
|
394
|
+
|
|
395
|
+
if (isNegation) {
|
|
396
|
+
comparator.negated = isNegation;
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
// Create the property expression for the left side with transformer
|
|
400
|
+
const propertyExpression = getProperty(schema, transformMethodMatch[1]);
|
|
401
|
+
|
|
402
|
+
// Set transformer and locale based on the method
|
|
403
|
+
const method = transformMethodMatch[2];
|
|
404
|
+
if (method === 'toLowerCase' || method === 'toLocaleLowerCase') {
|
|
405
|
+
propertyExpression.transformer = 'to-lower-case';
|
|
406
|
+
if (method === 'toLocaleLowerCase') {
|
|
407
|
+
propertyExpression.locale = 'en-US'; // Default locale, could be extracted from method call
|
|
408
|
+
}
|
|
409
|
+
} else {
|
|
410
|
+
propertyExpression.transformer = 'to-upper-case';
|
|
411
|
+
if (method === 'toLocaleUpperCase') {
|
|
412
|
+
propertyExpression.locale = 'en-US'; // Default locale, could be extracted from method call
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
// Create a ValueExpression for the right side
|
|
417
|
+
let valueExpression = getValue(transformMethodMatch[4], params);
|
|
418
|
+
|
|
419
|
+
// Check if the value also has a transformation (but only if it's not already handled by value-side check)
|
|
420
|
+
const valueTransformMatch = transformMethodMatch[4].match(/^([^)]+)\.(toLowerCase|toUpperCase|toLocaleLowerCase|toLocaleUpperCase)\(\)$/);
|
|
421
|
+
|
|
422
|
+
if (valueTransformMatch) {
|
|
423
|
+
// Create a new ValueExpression with the base value and transformer
|
|
424
|
+
valueExpression = new ValueExpression({
|
|
425
|
+
value: valueTransformMatch[1].replace(/^["']|["']$/g, '') // Remove quotes
|
|
426
|
+
});
|
|
427
|
+
|
|
428
|
+
// Set transformer and locale based on the method
|
|
429
|
+
const valueMethod = valueTransformMatch[2];
|
|
430
|
+
if (valueMethod === 'toLowerCase' || valueMethod === 'toLocaleLowerCase') {
|
|
431
|
+
valueExpression.transformer = 'to-lower-case';
|
|
432
|
+
if (valueMethod === 'toLocaleLowerCase') {
|
|
433
|
+
valueExpression.locale = 'en-US'; // Default locale
|
|
434
|
+
}
|
|
435
|
+
} else {
|
|
436
|
+
valueExpression.transformer = 'to-upper-case';
|
|
437
|
+
if (valueMethod === 'toLocaleUpperCase') {
|
|
438
|
+
valueExpression.locale = 'en-US'; // Default locale
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
comparator.left = propertyExpression;
|
|
444
|
+
comparator.right = valueExpression;
|
|
445
|
+
|
|
446
|
+
// If the comparison is explicitly to false, mark it as negated
|
|
447
|
+
if (transformMethodMatch[7] === "false") {
|
|
448
|
+
comparator.negated = true;
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
return comparator;
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
if (equalityMatch) {
|
|
455
|
+
const left = equalityMatch[1].trim();
|
|
456
|
+
const operator = equalityMatch[2];
|
|
457
|
+
const right = equalityMatch[3].trim();
|
|
458
|
+
|
|
459
|
+
const leftIsProperty = isPropertyPath(left);
|
|
460
|
+
const rightIsProperty = isPropertyPath(right);
|
|
461
|
+
|
|
462
|
+
// Determine which side is the property and which is the value
|
|
463
|
+
let propertySide: string, valueSide: string, finalOperator: string;
|
|
464
|
+
|
|
465
|
+
if (leftIsProperty && !rightIsProperty) {
|
|
466
|
+
// Normal case: property === value
|
|
467
|
+
propertySide = left;
|
|
468
|
+
valueSide = right;
|
|
469
|
+
finalOperator = operator;
|
|
470
|
+
} else if (!leftIsProperty && rightIsProperty) {
|
|
471
|
+
// Reversed case: value === property
|
|
472
|
+
propertySide = right;
|
|
473
|
+
valueSide = left;
|
|
474
|
+
finalOperator = operator;
|
|
475
|
+
} else {
|
|
476
|
+
// Both sides look like properties or neither does - assume left is property
|
|
477
|
+
propertySide = left;
|
|
478
|
+
valueSide = right;
|
|
479
|
+
finalOperator = operator;
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
const comparator = getComparator(finalOperator);
|
|
483
|
+
|
|
484
|
+
if (isNegation) {
|
|
485
|
+
comparator.negated = isNegation;
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
comparator.left = getProperty(schema, propertySide);
|
|
489
|
+
comparator.right = getValue(valueSide, params);
|
|
490
|
+
|
|
491
|
+
convertAndAssignValue(comparator.right, comparator.left);
|
|
492
|
+
|
|
493
|
+
return comparator;
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
if (comparisonMatch) {
|
|
497
|
+
const left = comparisonMatch[1].trim();
|
|
498
|
+
const operator = comparisonMatch[2];
|
|
499
|
+
const right = comparisonMatch[3].trim();
|
|
500
|
+
|
|
501
|
+
const leftIsProperty = isPropertyPath(left);
|
|
502
|
+
const rightIsProperty = isPropertyPath(right);
|
|
503
|
+
|
|
504
|
+
// Determine which side is the property and which is the value
|
|
505
|
+
let propertySide: string, valueSide: string, finalOperator: string;
|
|
506
|
+
|
|
507
|
+
if (leftIsProperty && !rightIsProperty) {
|
|
508
|
+
// Normal case: property > value
|
|
509
|
+
propertySide = left;
|
|
510
|
+
valueSide = right;
|
|
511
|
+
finalOperator = operator;
|
|
512
|
+
} else if (!leftIsProperty && rightIsProperty) {
|
|
513
|
+
// Reversed case: value > property -> property < value
|
|
514
|
+
propertySide = right;
|
|
515
|
+
valueSide = left;
|
|
516
|
+
finalOperator = getSwappedOperator(operator);
|
|
517
|
+
} else {
|
|
518
|
+
// Both sides look like properties or neither does - assume left is property
|
|
519
|
+
propertySide = left;
|
|
520
|
+
valueSide = right;
|
|
521
|
+
finalOperator = operator;
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
const comparator = getComparator(finalOperator);
|
|
525
|
+
|
|
526
|
+
if (isNegation) {
|
|
527
|
+
comparator.negated = isNegation;
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
comparator.left = getProperty(schema, propertySide);
|
|
531
|
+
comparator.right = getValue(valueSide, params);
|
|
532
|
+
|
|
533
|
+
convertAndAssignValue(comparator.right, comparator.left);
|
|
534
|
+
|
|
535
|
+
return comparator;
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
// If we get here, the expression is too complex for the current parser
|
|
539
|
+
// This is expected for complex real-world expressions
|
|
540
|
+
throw new Error(`Unsupported expression format: ${finalExpression}`);
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
const getComparator = (value: string): ComparatorExpression => {
|
|
544
|
+
|
|
545
|
+
const comparator = COMPARATOR_MAP.get(value);
|
|
546
|
+
|
|
547
|
+
if (comparator == null) {
|
|
548
|
+
throw new Error(ERROR_MESSAGES.COMPARATOR_NOT_FOUND(value))
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
// Return a new instance to avoid modifying the cached one
|
|
552
|
+
return new ComparatorExpression({
|
|
553
|
+
comparator: comparator.comparator,
|
|
554
|
+
negated: comparator.negated,
|
|
555
|
+
strict: comparator.strict
|
|
556
|
+
});
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
const getValue = <P extends any>(value: string, params?: { name: string, data: P }): ValueExpression => {
|
|
560
|
+
|
|
561
|
+
// Early return for string literals
|
|
562
|
+
if (value.startsWith("\'") || value.startsWith("\"")) {
|
|
563
|
+
return new ValueExpression({
|
|
564
|
+
value: value.replace(/\"|\'/g, "")
|
|
565
|
+
});
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
// Early return for null/undefined
|
|
569
|
+
if (value === "null") {
|
|
570
|
+
return new ValueExpression({ value: null });
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
if (value === "undefined" || value === "void 0") {
|
|
574
|
+
return new ValueExpression({ value: undefined });
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
// Optimized number parsing with early return
|
|
578
|
+
const numValue = +value;
|
|
579
|
+
if (!isNaN(numValue) && isFinite(numValue)) {
|
|
580
|
+
return new ValueExpression({ value: numValue });
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
// Handle parameter paths if params are provided
|
|
584
|
+
if (params != null) {
|
|
585
|
+
// Check if this is a parameter path (starts with params.name)
|
|
586
|
+
if (value.startsWith(params.name)) {
|
|
587
|
+
return new ValueExpression({
|
|
588
|
+
value: getValueFromParams(value, params)
|
|
589
|
+
});
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
// Check if this is a parameter path without the params prefix
|
|
593
|
+
if (value.includes('.')) {
|
|
594
|
+
// Try to extract the parameter path
|
|
595
|
+
const paramMatch = value.match(PARAM_PATH_REGEX);
|
|
596
|
+
if (paramMatch) {
|
|
597
|
+
const potentialParamPath = paramMatch[1];
|
|
598
|
+
try {
|
|
599
|
+
const paramValue = getValueFromParams(`${params.name}.${potentialParamPath}`, params);
|
|
600
|
+
return new ValueExpression({ value: paramValue });
|
|
601
|
+
} catch (e) {
|
|
602
|
+
// Not a parameter path, continue with normal parsing
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
// Default case: return as-is
|
|
609
|
+
return new ValueExpression({ value });
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
const getValueFromParams = <P extends any>(value: string, params: { name: string, data: P }) => {
|
|
613
|
+
|
|
614
|
+
const split = value.split('.');
|
|
615
|
+
|
|
616
|
+
// Early exit for invalid paths
|
|
617
|
+
if (split.length === 1) {
|
|
618
|
+
throw new Error(ERROR_MESSAGES.PARAM_PATH_NOT_FOUND(value, params.data))
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
let result = params.data as any;
|
|
622
|
+
|
|
623
|
+
// For nested params - start from index 1 to skip the params prefix
|
|
624
|
+
for (let i = 1; i < split.length; i++) {
|
|
625
|
+
const name = split[i];
|
|
626
|
+
|
|
627
|
+
if (name in result) {
|
|
628
|
+
result = result[name];
|
|
629
|
+
continue;
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
throw new Error(ERROR_MESSAGES.PARAM_PATH_NOT_FOUND(value, params.data))
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
return result;
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
const getProperty = (schema: CompiledSchema<any>, value: string): PropertyExpression => {
|
|
639
|
+
// Optimized string splitting - only split if we have the expected pattern
|
|
640
|
+
if (!value.includes('.')) {
|
|
641
|
+
throw new Error(ERROR_MESSAGES.PROPERTY_NOT_FOUND(value));
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
const pathSplit = value.split(/[?!.]/g).slice(1);
|
|
645
|
+
const pathString = pathSplit.join(".");
|
|
646
|
+
|
|
647
|
+
// Early exit if no path found
|
|
648
|
+
if (!pathString) {
|
|
649
|
+
throw new Error(ERROR_MESSAGES.PROPERTY_NOT_FOUND(value));
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
const found = schema.properties.find(w => w.getAssignmentPath() == pathString);
|
|
653
|
+
|
|
654
|
+
if (found == null) {
|
|
655
|
+
throw new Error(ERROR_MESSAGES.PROPERTY_NOT_FOUND(value));
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
return new PropertyExpression({ property: found });
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
|