Skip to content

Commit 55229e7

Browse files
Made few improvement and added new functionality
1. Added UUID, RangeRight, Deep Merge transformations. 2. Added two new operators, equals ignore case, strict equals. 3. Enhanced the filterOn in forEach custom transform. 4. Added highlight js in home page. Will extend it to playground.
1 parent c7cf194 commit 55229e7

File tree

12 files changed

+223
-30
lines changed

12 files changed

+223
-30
lines changed

benchmark/profiler.js

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
import axios from "axios";
2+
import pLimit from "p-limit";
3+
4+
const apiUrl = "http://localhost:8000/api/process";
5+
6+
function generateInputs(count) {
7+
return Array.from({ length: count }, (_, i) => ({
8+
input: {
9+
name: {
10+
first: `NameFirst${i}`,
11+
last: `NameLast${i}`,
12+
},
13+
exes: [
14+
`Ex${i}A `,
15+
`Ex${i}B`,
16+
`Ex${i}C`,
17+
],
18+
lastEx: i,
19+
},
20+
transforms: [
21+
{
22+
"lastEx": "derived.lastEx + 5",
23+
},
24+
{
25+
"modified": "derived.lastEx",
26+
},
27+
{
28+
"original": "input.lastEx",
29+
},
30+
{
31+
"nameObject": [
32+
{
33+
"name":
34+
"input.name.first | trim + ' ' + input.name.last",
35+
},
36+
{
37+
"name": "derived.nameObject.name | capitalize",
38+
},
39+
{
40+
"age": "25",
41+
},
42+
{
43+
"ex1": "'=>' + input.exes[0] | rtrim",
44+
},
45+
],
46+
},
47+
{
48+
"isMinor": "derived.nameObject.age < 18",
49+
},
50+
{
51+
"nameLength": "derived.nameObject.name | length",
52+
},
53+
{
54+
"nameUpper": "derived.nameObject.name | upper",
55+
},
56+
],
57+
"settings": { "merge_method": "overwrite" },
58+
}));
59+
}
60+
61+
async function makeAPICall(apiUrl, payload) {
62+
try {
63+
const response = await axios.post(apiUrl, payload);
64+
return response.data;
65+
} catch (error) {
66+
console.error(
67+
`Error calling API for input ${
68+
JSON.stringify(payload.input)
69+
}: ${error.message}`,
70+
);
71+
return null;
72+
}
73+
}
74+
75+
76+
async function profileLimitedConcurrency(apiUrl, inputCount, concurrencyLimit) {
77+
const inputs = generateInputs(inputCount);
78+
const limit = pLimit(concurrencyLimit);
79+
80+
const startTime = performance.now();
81+
82+
const promises = inputs.map((input) =>
83+
limit(() => makeAPICall(apiUrl, input))
84+
);
85+
const results = await Promise.all(promises);
86+
87+
const endTime = performance.now();
88+
console.log(
89+
`Processed ${inputCount} API calls with concurrency limit ${concurrencyLimit} in ${
90+
(endTime - startTime).toFixed(2)
91+
} ms`,
92+
);
93+
}
94+
95+
// Run the profiler
96+
const inputCount = 10000;
97+
const concurrencyLimit = 10000;
98+
profileLimitedConcurrency(apiUrl, inputCount, concurrencyLimit);

core/constants.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,5 +14,6 @@ export enum Errors {
1414
InvalidTransform = "InvalidTransform",
1515
VariableNotInContext = "VariableNotInContext",
1616
InvalidMergeMethod = "InvalidMergeMethod",
17-
TransformError = "TransformError"
17+
TransformError = "TransformError",
18+
OperatorNotDefinedForType = "OperatorNotDefinedForType"
1819
};
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import { Errors } from "../../constants.ts";
2+
import { getType } from "../../utils.ts";
3+
4+
5+
export const EQUALS_IGNORE_CASE = (left: string, right: string) => {
6+
if (typeof left === "string" && typeof right === "string"){
7+
return left.toLowerCase() === right.toLocaleLowerCase();
8+
}
9+
return {
10+
[Errors.OperatorNotDefinedForType]: `${getType(left)} _= ${getType(right)} is an invalid operation. "_=" only supports string types.`
11+
};
12+
};
13+
14+
export const STRICT_EQUALS = (left: any, right: any) => {
15+
return left === right;
16+
};

core/lib/transforms/array_transforms.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,10 @@ export const RANGE = (_val: null = null, start: number, stop: number, step: numb
9494
return Array.from({ length: (stop - start) / step + 1 }, (_, i) => start + (i * step));
9595
};
9696

97+
export const RANGE_RIGHT = (_val: null = null, start: number, stop: number, step: number) => {
98+
return Array.from({ length: (start - stop) / step + 1 }, (_, i) => start - (i * Math.abs(step)));
99+
};
100+
97101
export const REMOVE_DUPLICATES = (val: Array<any>) => {
98102
const result: Array<any | ErrorObject> = structuredClone(val);
99103
if (typeof val === "object" && Array.isArray(val)) {

core/lib/transforms/misc_transforms.ts

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -52,12 +52,11 @@ export const FOREACH = async (
5252
Object.keys(context).forEach((field) => {
5353
if (result[field]) context[field] = result[field];
5454
});
55-
forEachResult.push(result);
56-
}
57-
if (filterOn !== null && filterOn !== "") {
58-
forEachResult = forEachResult.filter((record) => {
59-
return record[filterOn];
60-
});
55+
if (filterOn !== null && filterOn !== "") {
56+
if (result[filterOn]) {
57+
forEachResult.push(result);
58+
}
59+
}
6160
}
6261
return forEachResult;
6362
};
@@ -67,7 +66,7 @@ export const TYPE = (val: object) => {
6766
};
6867

6968
export const PARSE_JSON = (val: string) => {
70-
if(typeof val === "string") {
69+
if (typeof val === "string") {
7170
try {
7271
return JSON.parse(val);
7372
} catch (error) {
@@ -77,4 +76,8 @@ export const PARSE_JSON = (val: string) => {
7776
return {
7877
[Errors.MethodNotDefinedForType]: `The ${val} of type ${getType(val)} has no method 'parseJson'. <value> | parseJson is only supported for String`
7978
};
80-
}
79+
}
80+
81+
export const UUID = (_val: null = null) => {
82+
return crypto.randomUUID();
83+
};

core/lib/transforms/object_transforms.ts

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
import { Errors } from "../../constants.ts";
2-
import { getType, symmetricDifference } from "../../utils.ts";
2+
import { PlainObject } from "../../types.ts";
3+
import { getType, isObject, symmetricDifference } from "../../utils.ts";
34

4-
export const GET = (val: Record<string,any>, property : string) => {
5+
export const GET = (val: Record<string, any>, property: string) => {
56
if (typeof val === "object" && !Array.isArray(val)) {
67
return val?.[property] || null;
78
}
89
return {
9-
[Errors.MethodNotDefinedForType]: `The ${val} of type ${getType(val)} has no method 'keys'. <value> | keys is only supported for Object`
10+
[Errors.MethodNotDefinedForType]: `The ${val} of type ${getType(val)} has no method 'get'. <value> | get(<property>) is only supported for Object`
1011
};
1112
};
1213

@@ -67,4 +68,29 @@ export const STRINGIFY = (val: object) => {
6768
return {
6869
[Errors.MethodNotDefinedForType]: `The ${val} of type ${getType(val)} has no method 'stringify'. <value> | stringify is only supported for Object or Array`
6970
};
70-
};
71+
};
72+
73+
export const DEEP_MERGE = <T extends PlainObject, U extends PlainObject>(
74+
val: T,
75+
objectToMerge: U
76+
): T & U => {
77+
if (!isObject(val)) return objectToMerge as T & U;
78+
if (!isObject(objectToMerge)) return val as T & U;
79+
80+
const merged: PlainObject = { ...val };
81+
82+
for (const key in objectToMerge) {
83+
if (Object.prototype.hasOwnProperty.call(objectToMerge, key)) {
84+
const valProp = merged[key];
85+
const mergeProp = objectToMerge[key];
86+
87+
if (isObject(valProp) && isObject(mergeProp)) {
88+
merged[key] = DEEP_MERGE(valProp, mergeProp);
89+
} else {
90+
merged[key] = mergeProp;
91+
}
92+
}
93+
}
94+
95+
return merged as T & U;
96+
};

core/load_lib.ts

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,8 @@ import {
3232
FOREACH,
3333
JSONPATH,
3434
PARSE_JSON,
35-
TYPE
35+
TYPE,
36+
UUID
3637
} from "./lib/transforms/misc_transforms.ts";
3738
import {
3839
JOIN,
@@ -42,6 +43,7 @@ import {
4243
POP,
4344
PUSH,
4445
RANGE,
46+
RANGE_RIGHT,
4547
REMOVE_DUPLICATES,
4648
REVERSE_ARRAY,
4749
SIZE,
@@ -55,7 +57,8 @@ import {
5557
HAS,
5658
KEYS,
5759
STRINGIFY,
58-
VALUES
60+
VALUES,
61+
DEEP_MERGE
5962
} from "./lib/transforms/object_transforms.ts";
6063
import {
6164
ABS,
@@ -87,6 +90,15 @@ import {
8790
TO_UTC,
8891
UTC_NOW
8992
} from "./lib/transforms/date_transforms.ts";
93+
import { EQUALS_IGNORE_CASE, STRICT_EQUALS } from "./lib/operators/custom_operators.ts";
94+
import { Errors } from "./constants.ts";
95+
import { getType } from "./utils.ts";
96+
97+
98+
// Operators
99+
mozjexl.addBinaryOp("_=", 20, EQUALS_IGNORE_CASE)
100+
mozjexl.addBinaryOp("===", 20, STRICT_EQUALS)
101+
90102

91103
// String transforms
92104
mozjexl.addTransform("upper", UPPER);
@@ -118,6 +130,7 @@ mozjexl.addTransform("forEach", FOREACH);
118130
mozjexl.addTransform("jsonpath", JSONPATH);
119131
mozjexl.addTransform("type", TYPE);
120132
mozjexl.addTransform("parseJson", PARSE_JSON);
133+
mozjexl.addTransform("UUID", UUID);
121134

122135
// Array transforms
123136
mozjexl.addTransform("pluck", PLUCK);
@@ -129,6 +142,7 @@ mozjexl.addTransform("slice", SLICE);
129142
mozjexl.addTransform("reverseArray", REVERSE_ARRAY);
130143
mozjexl.addTransform("sortArray", SORT_ARRAY);
131144
mozjexl.addTransform("range", RANGE);
145+
mozjexl.addTransform("rangeRight", RANGE_RIGHT);
132146
mozjexl.addTransform("removeDuplicates", REMOVE_DUPLICATES);
133147
mozjexl.addTransform("max", MAX);
134148
mozjexl.addTransform("min", MIN);
@@ -141,6 +155,7 @@ mozjexl.addTransform("get", GET);
141155
mozjexl.addTransform("has", HAS);
142156
mozjexl.addTransform("delete", DELETE);
143157
mozjexl.addTransform("stringify", STRINGIFY);
158+
mozjexl.addTransform("deepMerge", DEEP_MERGE);
144159

145160
// Number transforms
146161
mozjexl.addTransform("abs", ABS);
@@ -172,4 +187,24 @@ mozjexl.addTransform("setDay", SET_DAY);
172187
mozjexl.addTransform("setMonth", SET_MONTH);
173188
mozjexl.addTransform("setYear", SET_YEAR);
174189

190+
191+
// This has to be loaded at last to mozjexl!... That is why placing it here.
192+
const EVALUATE_EXPRESSION = async (val: string, context: Record<any, any>) => {
193+
if (typeof val === "string") {
194+
const regex = /{{\s*(.+?)\s*}}/g;
195+
const parts = val.split(regex);
196+
for (let i = 1; i < parts.length; i += 2) {
197+
const result = await mozjexl.eval(parts[i], context);
198+
if (typeof result !== "string") parts[i] = JSON.stringify(await mozjexl.eval(parts[i], context));
199+
else parts[i] = await mozjexl.eval(parts[i], context);
200+
}
201+
return parts.join("");
202+
}
203+
return {
204+
[Errors.MethodNotDefinedForType]: `The ${val} of type ${getType(val)} has no method 'evaluateExpression'. <value> | evaluateExpression(context) is only supported for String`
205+
};
206+
};
207+
208+
mozjexl.addTransform("evaluateExpression", EVALUATE_EXPRESSION);
209+
175210
export default mozjexl;

core/types.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,4 +19,6 @@ export interface ErrorObject {
1919
[key: string]: string;
2020
}
2121

22-
export type DateTimeFormatType = 'ISO' | 'RFC2822' | 'SQL' | 'HTTP' | 'Millis';
22+
export type DateTimeFormatType = 'ISO' | 'RFC2822' | 'SQL' | 'HTTP' | 'Millis';
23+
24+
export type PlainObject = Record<string, any>;

core/utils.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
// Copyright (c) 2024-Present The Yak Shaving Devs, MIT License
22

33
import { Errors } from "./constants.ts";
4-
import { DateTimeFormatType, ErrorObject } from "./types.ts";
4+
import { DateTimeFormatType, ErrorObject, PlainObject } from "./types.ts";
55
import { DateTime } from 'luxon';
66

77
export const isRegExpExpression = (expression: string) => {
@@ -94,4 +94,8 @@ export const symmetricDifference = (setA: Set<string>, setB: Set<string>): Set<s
9494
}
9595
}
9696
return difference;
97-
};
97+
};
98+
99+
export const isObject = (value: unknown): value is PlainObject => {
100+
return value !== null && typeof value === "object" && !Array.isArray(value);
101+
};

server/routes/_app.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ export default function App({ Component }: PageProps) {
6363
<hr></hr>
6464
<footer class="container">
6565
<p class="text-center text-body-primary">
66-
© 2024 - 2025 Datadance,&nbsp;
66+
© {new Date().getFullYear()} Datadance,&nbsp;
6767
<a href="https://yakshavingdevs.org" style={{"color":"white","textDecoration":"none","font-weight":"bold"}}>Yak Shaving Devs</a>
6868
</p>
6969
</footer>

0 commit comments

Comments
 (0)