@revisium/formula 0.2.1 → 0.4.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 +33 -1
- package/dist/{chunk-5NMNSRHH.cjs → chunk-AGBOCJGV.cjs} +341 -43
- package/dist/chunk-AGBOCJGV.cjs.map +1 -0
- package/dist/{chunk-FGRNVE53.js → chunk-FDIJPOVQ.js} +341 -44
- package/dist/chunk-FDIJPOVQ.js.map +1 -0
- package/dist/editor/index.cjs +6 -6
- package/dist/editor/index.d.cts +1 -1
- package/dist/editor/index.d.ts +1 -1
- package/dist/editor/index.js +1 -1
- package/dist/formula-spec.cjs +388 -8
- package/dist/formula-spec.cjs.map +1 -1
- package/dist/formula-spec.d.cts +16 -1
- package/dist/formula-spec.d.ts +16 -1
- package/dist/formula-spec.js +388 -8
- package/dist/formula-spec.js.map +1 -1
- package/dist/{index-DO2EZ7U6.d.cts → index-JZFJ9oT6.d.cts} +10 -1
- package/dist/{index-DO2EZ7U6.d.ts → index-JZFJ9oT6.d.ts} +10 -1
- package/dist/index.cjs +17 -13
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/package.json +1 -1
- package/dist/chunk-5NMNSRHH.cjs.map +0 -1
- package/dist/chunk-FGRNVE53.js.map +0 -1
|
@@ -294,6 +294,125 @@ token("*", 200, (a) => {
|
|
|
294
294
|
if (!a) return createWildcardLiteral();
|
|
295
295
|
return void 0;
|
|
296
296
|
});
|
|
297
|
+
token("/", 200, (a) => {
|
|
298
|
+
if (a) return;
|
|
299
|
+
const name = next2(isIdChar);
|
|
300
|
+
if (!name) return;
|
|
301
|
+
return "/" + name;
|
|
302
|
+
});
|
|
303
|
+
var isRelativePathChar = (c) => isIdChar(c) || c === 47 || c === 46 ? 1 : 0;
|
|
304
|
+
token(".", 200, (a) => {
|
|
305
|
+
if (a) return;
|
|
306
|
+
const second = next2((c) => c === 46 ? 1 : 0);
|
|
307
|
+
if (!second) return;
|
|
308
|
+
const rest = next2(isRelativePathChar);
|
|
309
|
+
if (!rest) return;
|
|
310
|
+
return ".." + rest;
|
|
311
|
+
});
|
|
312
|
+
var BUILTIN_FUNCTIONS = {
|
|
313
|
+
and: (a, b) => Boolean(a) && Boolean(b),
|
|
314
|
+
or: (a, b) => Boolean(a) || Boolean(b),
|
|
315
|
+
not: (a) => !a,
|
|
316
|
+
concat: (...args) => args.map(String).join(""),
|
|
317
|
+
upper: (s) => String(s).toUpperCase(),
|
|
318
|
+
lower: (s) => String(s).toLowerCase(),
|
|
319
|
+
trim: (s) => String(s).trim(),
|
|
320
|
+
left: (s, n) => {
|
|
321
|
+
const count = Math.max(0, Math.floor(Number(n)));
|
|
322
|
+
return String(s).slice(0, count);
|
|
323
|
+
},
|
|
324
|
+
right: (s, n) => {
|
|
325
|
+
const str = String(s);
|
|
326
|
+
const count = Math.max(0, Math.floor(Number(n)));
|
|
327
|
+
return count === 0 ? "" : str.slice(-count);
|
|
328
|
+
},
|
|
329
|
+
replace: (s, search, replacement) => String(s).replace(String(search), String(replacement)),
|
|
330
|
+
tostring: String,
|
|
331
|
+
length: (s) => {
|
|
332
|
+
if (Array.isArray(s)) return s.length;
|
|
333
|
+
if (typeof s === "string") return s.length;
|
|
334
|
+
if (s !== null && typeof s === "object") return Object.keys(s).length;
|
|
335
|
+
return String(s).length;
|
|
336
|
+
},
|
|
337
|
+
contains: (s, search) => String(s).includes(String(search)),
|
|
338
|
+
startswith: (s, search) => String(s).startsWith(String(search)),
|
|
339
|
+
endswith: (s, search) => String(s).endsWith(String(search)),
|
|
340
|
+
tonumber: Number,
|
|
341
|
+
toboolean: Boolean,
|
|
342
|
+
isnull: (v) => v === null || v === void 0,
|
|
343
|
+
coalesce: (...args) => args.find((v) => v !== null && v !== void 0) ?? null,
|
|
344
|
+
round: (n, decimals) => {
|
|
345
|
+
const num2 = Number(n);
|
|
346
|
+
const dec = decimals === void 0 ? 0 : Number(decimals);
|
|
347
|
+
const factor = 10 ** dec;
|
|
348
|
+
return Math.round(num2 * factor) / factor;
|
|
349
|
+
},
|
|
350
|
+
floor: (n) => Math.floor(Number(n)),
|
|
351
|
+
ceil: (n) => Math.ceil(Number(n)),
|
|
352
|
+
abs: (n) => Math.abs(Number(n)),
|
|
353
|
+
sqrt: (n) => Math.sqrt(Number(n)),
|
|
354
|
+
pow: (base, exp) => Math.pow(Number(base), Number(exp)),
|
|
355
|
+
min: (...args) => args.length === 0 ? Number.NaN : Math.min(...args.map(Number)),
|
|
356
|
+
max: (...args) => args.length === 0 ? Number.NaN : Math.max(...args.map(Number)),
|
|
357
|
+
log: (n) => Math.log(Number(n)),
|
|
358
|
+
log10: (n) => Math.log10(Number(n)),
|
|
359
|
+
exp: (n) => Math.exp(Number(n)),
|
|
360
|
+
sign: (n) => Math.sign(Number(n)),
|
|
361
|
+
sum: (arr) => Array.isArray(arr) ? arr.reduce((a, b) => a + Number(b), 0) : 0,
|
|
362
|
+
avg: (arr) => Array.isArray(arr) && arr.length > 0 ? arr.reduce((a, b) => a + Number(b), 0) / arr.length : 0,
|
|
363
|
+
count: (arr) => Array.isArray(arr) ? arr.length : 0,
|
|
364
|
+
first: (arr) => Array.isArray(arr) ? arr[0] : void 0,
|
|
365
|
+
last: (arr) => Array.isArray(arr) ? arr.at(-1) : void 0,
|
|
366
|
+
join: (arr, separator) => {
|
|
367
|
+
if (!Array.isArray(arr)) return "";
|
|
368
|
+
if (separator === void 0) return arr.join(",");
|
|
369
|
+
if (typeof separator === "string") return arr.join(separator);
|
|
370
|
+
if (typeof separator === "number") return arr.join(String(separator));
|
|
371
|
+
return arr.join(",");
|
|
372
|
+
},
|
|
373
|
+
includes: (arr, value) => Array.isArray(arr) ? arr.includes(value) : false,
|
|
374
|
+
if: (condition, ifTrue, ifFalse) => condition ? ifTrue : ifFalse
|
|
375
|
+
};
|
|
376
|
+
function extractCallArgs(argsNode) {
|
|
377
|
+
if (!argsNode) return [];
|
|
378
|
+
if (!Array.isArray(argsNode)) return [argsNode];
|
|
379
|
+
if (argsNode[0] === ",") {
|
|
380
|
+
return argsNode.slice(1);
|
|
381
|
+
}
|
|
382
|
+
return [argsNode];
|
|
383
|
+
}
|
|
384
|
+
function compileNode(node) {
|
|
385
|
+
return compile(node);
|
|
386
|
+
}
|
|
387
|
+
function createGroupingEvaluator(fn) {
|
|
388
|
+
const compiledExpr = compileNode(fn);
|
|
389
|
+
return (ctx) => compiledExpr(ctx);
|
|
390
|
+
}
|
|
391
|
+
function createFunctionCallEvaluator(fn, argsNode) {
|
|
392
|
+
const args = extractCallArgs(argsNode);
|
|
393
|
+
const compiledArgs = args.map((arg) => compileNode(arg));
|
|
394
|
+
return (ctx) => {
|
|
395
|
+
const argValues = compiledArgs.map((a) => a(ctx));
|
|
396
|
+
if (typeof fn === "string") {
|
|
397
|
+
const builtinFn = BUILTIN_FUNCTIONS[fn.toLowerCase()];
|
|
398
|
+
if (builtinFn) {
|
|
399
|
+
return builtinFn(...argValues);
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
const fnValue = compileNode(fn)(ctx);
|
|
403
|
+
if (typeof fnValue === "function") {
|
|
404
|
+
return fnValue(...argValues);
|
|
405
|
+
}
|
|
406
|
+
const fnName = typeof fn === "string" ? fn : String(fn);
|
|
407
|
+
throw new Error(`'${fnName}' is not a function`);
|
|
408
|
+
};
|
|
409
|
+
}
|
|
410
|
+
operator("()", (fn, argsNode) => {
|
|
411
|
+
if (argsNode === void 0) {
|
|
412
|
+
return createGroupingEvaluator(fn);
|
|
413
|
+
}
|
|
414
|
+
return createFunctionCallEvaluator(fn, argsNode);
|
|
415
|
+
});
|
|
297
416
|
var KEYWORDS = /* @__PURE__ */ new Set([
|
|
298
417
|
"true",
|
|
299
418
|
"false",
|
|
@@ -302,9 +421,6 @@ var KEYWORDS = /* @__PURE__ */ new Set([
|
|
|
302
421
|
"or",
|
|
303
422
|
"not",
|
|
304
423
|
"if",
|
|
305
|
-
"constructor",
|
|
306
|
-
"__proto__",
|
|
307
|
-
"prototype",
|
|
308
424
|
"round",
|
|
309
425
|
"floor",
|
|
310
426
|
"ceil",
|
|
@@ -339,8 +455,6 @@ var KEYWORDS = /* @__PURE__ */ new Set([
|
|
|
339
455
|
"first",
|
|
340
456
|
"last",
|
|
341
457
|
"join",
|
|
342
|
-
"filter",
|
|
343
|
-
"map",
|
|
344
458
|
"includes"
|
|
345
459
|
]);
|
|
346
460
|
var ARRAY_FUNCTIONS = /* @__PURE__ */ new Set([
|
|
@@ -350,8 +464,6 @@ var ARRAY_FUNCTIONS = /* @__PURE__ */ new Set([
|
|
|
350
464
|
"first",
|
|
351
465
|
"last",
|
|
352
466
|
"join",
|
|
353
|
-
"filter",
|
|
354
|
-
"map",
|
|
355
467
|
"includes"
|
|
356
468
|
]);
|
|
357
469
|
function isKeyword(name) {
|
|
@@ -363,6 +475,12 @@ function isArrayFunction(name) {
|
|
|
363
475
|
function isContextToken(name) {
|
|
364
476
|
return name.startsWith("@") || name.startsWith("#");
|
|
365
477
|
}
|
|
478
|
+
function isRootPath(name) {
|
|
479
|
+
return name.startsWith("/");
|
|
480
|
+
}
|
|
481
|
+
function isRelativePath(name) {
|
|
482
|
+
return name.startsWith("..");
|
|
483
|
+
}
|
|
366
484
|
function isValidIdentifierRoot(rootName) {
|
|
367
485
|
return !isKeyword(rootName) && !isContextToken(rootName);
|
|
368
486
|
}
|
|
@@ -375,6 +493,10 @@ function addPathIfValid(path, identifiers) {
|
|
|
375
493
|
}
|
|
376
494
|
}
|
|
377
495
|
function collectStringIdentifier(node, identifiers) {
|
|
496
|
+
if (isRootPath(node) || isRelativePath(node)) {
|
|
497
|
+
identifiers.add(node);
|
|
498
|
+
return;
|
|
499
|
+
}
|
|
378
500
|
if (!isContextToken(node) && !isKeyword(node)) {
|
|
379
501
|
identifiers.add(node);
|
|
380
502
|
}
|
|
@@ -407,6 +529,9 @@ function collectIdentifiers(node, identifiers) {
|
|
|
407
529
|
if (!Array.isArray(node)) {
|
|
408
530
|
return;
|
|
409
531
|
}
|
|
532
|
+
if (isLiteralArray(node)) {
|
|
533
|
+
return;
|
|
534
|
+
}
|
|
410
535
|
const [op, ...args] = node;
|
|
411
536
|
if (op === "." || op === "[]") {
|
|
412
537
|
collectPathOrFallback(node, identifiers);
|
|
@@ -504,16 +629,25 @@ function detectFunctionCallFeatures(funcName, features) {
|
|
|
504
629
|
features.add("array_function");
|
|
505
630
|
}
|
|
506
631
|
}
|
|
507
|
-
function
|
|
508
|
-
if (
|
|
509
|
-
|
|
510
|
-
|
|
632
|
+
function detectStringFeatures(node, features) {
|
|
633
|
+
if (isContextToken(node)) {
|
|
634
|
+
features.add("context_token");
|
|
635
|
+
}
|
|
636
|
+
if (isRootPath(node)) {
|
|
637
|
+
features.add("root_path");
|
|
638
|
+
if (node.includes(".")) {
|
|
639
|
+
features.add("nested_path");
|
|
511
640
|
}
|
|
512
|
-
return;
|
|
513
641
|
}
|
|
514
|
-
if (
|
|
515
|
-
|
|
642
|
+
if (isRelativePath(node)) {
|
|
643
|
+
features.add("relative_path");
|
|
644
|
+
const withoutPrefix = node.replace(/^(\.\.\/)+/, "");
|
|
645
|
+
if (withoutPrefix.includes(".")) {
|
|
646
|
+
features.add("nested_path");
|
|
647
|
+
}
|
|
516
648
|
}
|
|
649
|
+
}
|
|
650
|
+
function detectOperatorFeatures(node, features) {
|
|
517
651
|
const op = node[0];
|
|
518
652
|
if (op === ".") {
|
|
519
653
|
features.add("nested_path");
|
|
@@ -524,6 +658,16 @@ function detectFeatures(node, features) {
|
|
|
524
658
|
if (op === "()") {
|
|
525
659
|
detectFunctionCallFeatures(node[1], features);
|
|
526
660
|
}
|
|
661
|
+
}
|
|
662
|
+
function detectFeatures(node, features) {
|
|
663
|
+
if (typeof node === "string") {
|
|
664
|
+
detectStringFeatures(node, features);
|
|
665
|
+
return;
|
|
666
|
+
}
|
|
667
|
+
if (!Array.isArray(node) || isLiteralArray(node)) {
|
|
668
|
+
return;
|
|
669
|
+
}
|
|
670
|
+
detectOperatorFeatures(node, features);
|
|
527
671
|
for (let i = 1; i < node.length; i++) {
|
|
528
672
|
detectFeatures(node[i], features);
|
|
529
673
|
}
|
|
@@ -572,20 +716,63 @@ function evaluate(expression, context) {
|
|
|
572
716
|
throw new Error("Empty expression");
|
|
573
717
|
}
|
|
574
718
|
const fn = subscript_default(trimmed);
|
|
575
|
-
|
|
719
|
+
const safeContext = {};
|
|
720
|
+
for (const [key, value] of Object.entries(context)) {
|
|
721
|
+
if (typeof value !== "function") {
|
|
722
|
+
safeContext[key] = value;
|
|
723
|
+
}
|
|
724
|
+
}
|
|
725
|
+
return fn(safeContext);
|
|
726
|
+
}
|
|
727
|
+
function getValueByPath(data, path) {
|
|
728
|
+
const segments = path.split(".");
|
|
729
|
+
let current = data;
|
|
730
|
+
for (const segment of segments) {
|
|
731
|
+
if (current === null || current === void 0) {
|
|
732
|
+
return void 0;
|
|
733
|
+
}
|
|
734
|
+
if (typeof current !== "object") {
|
|
735
|
+
return void 0;
|
|
736
|
+
}
|
|
737
|
+
current = current[segment];
|
|
738
|
+
}
|
|
739
|
+
return current;
|
|
740
|
+
}
|
|
741
|
+
function buildPathReferences(rootData, dependencies) {
|
|
742
|
+
const refs = {};
|
|
743
|
+
for (const dep of dependencies) {
|
|
744
|
+
if (dep.startsWith("/")) {
|
|
745
|
+
const fieldPath = dep.slice(1);
|
|
746
|
+
const rootField = fieldPath.split(".")[0] ?? fieldPath;
|
|
747
|
+
const contextKey = "/" + rootField;
|
|
748
|
+
if (!(contextKey in refs)) {
|
|
749
|
+
refs[contextKey] = getValueByPath(rootData, rootField);
|
|
750
|
+
}
|
|
751
|
+
} else if (dep.startsWith("../")) {
|
|
752
|
+
const fieldPath = dep.slice(3);
|
|
753
|
+
refs[dep] = getValueByPath(rootData, fieldPath);
|
|
754
|
+
}
|
|
755
|
+
}
|
|
756
|
+
return refs;
|
|
757
|
+
}
|
|
758
|
+
function evaluateWithContext(expression, options) {
|
|
759
|
+
const { rootData, itemData } = options;
|
|
760
|
+
const trimmed = expression.trim();
|
|
761
|
+
if (!trimmed) {
|
|
762
|
+
throw new Error("Empty expression");
|
|
763
|
+
}
|
|
764
|
+
const parsed = parseFormula(trimmed);
|
|
765
|
+
const pathRefs = buildPathReferences(rootData, parsed.dependencies);
|
|
766
|
+
const context = {
|
|
767
|
+
...rootData,
|
|
768
|
+
...itemData ?? {},
|
|
769
|
+
...pathRefs
|
|
770
|
+
};
|
|
771
|
+
return evaluate(trimmed, context);
|
|
576
772
|
}
|
|
577
773
|
var ARITHMETIC_OPS = /* @__PURE__ */ new Set(["+", "-", "*", "/", "%"]);
|
|
578
|
-
var COMPARISON_OPS = /* @__PURE__ */ new Set([
|
|
579
|
-
|
|
580
|
-
">",
|
|
581
|
-
"<=",
|
|
582
|
-
">=",
|
|
583
|
-
"==",
|
|
584
|
-
"!=",
|
|
585
|
-
"===",
|
|
586
|
-
"!=="
|
|
587
|
-
]);
|
|
588
|
-
var LOGICAL_OPS = /* @__PURE__ */ new Set(["&&", "||", "!", "and", "or", "not"]);
|
|
774
|
+
var COMPARISON_OPS = /* @__PURE__ */ new Set(["<", ">", "<=", ">=", "==", "!="]);
|
|
775
|
+
var LOGICAL_OPS = /* @__PURE__ */ new Set(["&&", "||", "!"]);
|
|
589
776
|
var NUMERIC_FUNCTIONS = /* @__PURE__ */ new Set([
|
|
590
777
|
"round",
|
|
591
778
|
"floor",
|
|
@@ -602,7 +789,8 @@ var NUMERIC_FUNCTIONS = /* @__PURE__ */ new Set([
|
|
|
602
789
|
"sum",
|
|
603
790
|
"avg",
|
|
604
791
|
"count",
|
|
605
|
-
"tonumber"
|
|
792
|
+
"tonumber",
|
|
793
|
+
"length"
|
|
606
794
|
]);
|
|
607
795
|
var STRING_FUNCTIONS = /* @__PURE__ */ new Set([
|
|
608
796
|
"concat",
|
|
@@ -616,6 +804,9 @@ var STRING_FUNCTIONS = /* @__PURE__ */ new Set([
|
|
|
616
804
|
"join"
|
|
617
805
|
]);
|
|
618
806
|
var BOOLEAN_FUNCTIONS = /* @__PURE__ */ new Set([
|
|
807
|
+
"and",
|
|
808
|
+
"or",
|
|
809
|
+
"not",
|
|
619
810
|
"contains",
|
|
620
811
|
"startswith",
|
|
621
812
|
"endswith",
|
|
@@ -645,7 +836,12 @@ function inferLiteralArrayType(node) {
|
|
|
645
836
|
if (typeof val === "boolean") return "boolean";
|
|
646
837
|
return "unknown";
|
|
647
838
|
}
|
|
648
|
-
function inferOperatorType(op, argsLength) {
|
|
839
|
+
function inferOperatorType(op, argsLength, argTypes) {
|
|
840
|
+
if (op === "+" && argTypes) {
|
|
841
|
+
if (argTypes.includes("string")) return "string";
|
|
842
|
+
if (argTypes.includes("unknown")) return "unknown";
|
|
843
|
+
return "number";
|
|
844
|
+
}
|
|
649
845
|
if (ARITHMETIC_OPS.has(op)) return "number";
|
|
650
846
|
if (COMPARISON_OPS.has(op)) return "boolean";
|
|
651
847
|
if (LOGICAL_OPS.has(op)) return "boolean";
|
|
@@ -670,7 +866,8 @@ function inferTypeFromNode(node, fieldTypes) {
|
|
|
670
866
|
if (!Array.isArray(node)) return "unknown";
|
|
671
867
|
if (isLiteralArray(node)) return inferLiteralArrayType(node);
|
|
672
868
|
const [op, ...args] = node;
|
|
673
|
-
const
|
|
869
|
+
const argTypes = op === "+" ? args.map((arg) => inferTypeFromNode(arg, fieldTypes)) : void 0;
|
|
870
|
+
const operatorType = inferOperatorType(op, args.length, argTypes);
|
|
674
871
|
if (operatorType !== null) return operatorType;
|
|
675
872
|
if (op === "." || op === "[]") {
|
|
676
873
|
return inferPropertyAccessType(node, fieldTypes);
|
|
@@ -826,21 +1023,104 @@ function processQueue(queue, graph, inDegree) {
|
|
|
826
1023
|
// src/extract-schema.ts
|
|
827
1024
|
function extractSchemaFormulas(schema) {
|
|
828
1025
|
const formulas = [];
|
|
1026
|
+
extractFormulasRecursive(schema, "", formulas);
|
|
1027
|
+
return formulas;
|
|
1028
|
+
}
|
|
1029
|
+
function extractFormulasRecursive(schema, pathPrefix, formulas) {
|
|
1030
|
+
if (schema.type === "array" && schema.items) {
|
|
1031
|
+
extractFormulasRecursive(schema.items, `${pathPrefix}[]`, formulas);
|
|
1032
|
+
return;
|
|
1033
|
+
}
|
|
829
1034
|
const properties = schema.properties ?? {};
|
|
830
1035
|
for (const [fieldName, fieldSchema] of Object.entries(properties)) {
|
|
1036
|
+
const fullPath = pathPrefix ? `${pathPrefix}.${fieldName}` : fieldName;
|
|
831
1037
|
const xFormula = fieldSchema["x-formula"];
|
|
832
1038
|
if (xFormula) {
|
|
833
1039
|
formulas.push({
|
|
834
|
-
fieldName,
|
|
1040
|
+
fieldName: fullPath,
|
|
835
1041
|
expression: xFormula.expression,
|
|
836
1042
|
fieldType: fieldSchema.type ?? "string"
|
|
837
1043
|
});
|
|
838
1044
|
}
|
|
1045
|
+
if (fieldSchema.type === "object" && fieldSchema.properties) {
|
|
1046
|
+
extractFormulasRecursive(fieldSchema, fullPath, formulas);
|
|
1047
|
+
}
|
|
1048
|
+
if (fieldSchema.type === "array" && fieldSchema.items) {
|
|
1049
|
+
extractFormulasRecursive(fieldSchema.items, `${fullPath}[]`, formulas);
|
|
1050
|
+
}
|
|
839
1051
|
}
|
|
840
|
-
return formulas;
|
|
841
1052
|
}
|
|
842
1053
|
|
|
843
1054
|
// src/validate-schema.ts
|
|
1055
|
+
function resolveSubSchema(schema, fieldPath) {
|
|
1056
|
+
if (!fieldPath) {
|
|
1057
|
+
return schema;
|
|
1058
|
+
}
|
|
1059
|
+
const segments = parsePathSegments(fieldPath);
|
|
1060
|
+
let current = schema;
|
|
1061
|
+
for (const segment of segments) {
|
|
1062
|
+
if (segment === "[]") {
|
|
1063
|
+
if (current.type === "array" && current.items) {
|
|
1064
|
+
current = current.items;
|
|
1065
|
+
} else {
|
|
1066
|
+
return null;
|
|
1067
|
+
}
|
|
1068
|
+
} else {
|
|
1069
|
+
if (current.properties?.[segment]) {
|
|
1070
|
+
current = current.properties[segment];
|
|
1071
|
+
} else {
|
|
1072
|
+
return null;
|
|
1073
|
+
}
|
|
1074
|
+
}
|
|
1075
|
+
}
|
|
1076
|
+
return current;
|
|
1077
|
+
}
|
|
1078
|
+
function parsePathSegments(path) {
|
|
1079
|
+
const segments = [];
|
|
1080
|
+
let current = "";
|
|
1081
|
+
let inBracket = false;
|
|
1082
|
+
for (const char of path) {
|
|
1083
|
+
if (char === "[") {
|
|
1084
|
+
if (current) {
|
|
1085
|
+
segments.push(current);
|
|
1086
|
+
current = "";
|
|
1087
|
+
}
|
|
1088
|
+
inBracket = true;
|
|
1089
|
+
} else if (char === "]") {
|
|
1090
|
+
inBracket = false;
|
|
1091
|
+
segments.push("[]");
|
|
1092
|
+
} else if (char === "." && !inBracket) {
|
|
1093
|
+
if (current) {
|
|
1094
|
+
segments.push(current);
|
|
1095
|
+
current = "";
|
|
1096
|
+
}
|
|
1097
|
+
} else if (!inBracket) {
|
|
1098
|
+
current += char;
|
|
1099
|
+
}
|
|
1100
|
+
}
|
|
1101
|
+
if (current) {
|
|
1102
|
+
segments.push(current);
|
|
1103
|
+
}
|
|
1104
|
+
return segments;
|
|
1105
|
+
}
|
|
1106
|
+
function getParentPath(fieldPath) {
|
|
1107
|
+
const lastDotIndex = fieldPath.lastIndexOf(".");
|
|
1108
|
+
const lastBracketIndex = fieldPath.lastIndexOf("[");
|
|
1109
|
+
const splitIndex = Math.max(lastDotIndex, lastBracketIndex);
|
|
1110
|
+
if (splitIndex <= 0) {
|
|
1111
|
+
return "";
|
|
1112
|
+
}
|
|
1113
|
+
return fieldPath.substring(0, splitIndex);
|
|
1114
|
+
}
|
|
1115
|
+
function getFieldName(fieldPath) {
|
|
1116
|
+
const lastDotIndex = fieldPath.lastIndexOf(".");
|
|
1117
|
+
const lastBracketIndex = fieldPath.lastIndexOf("]");
|
|
1118
|
+
const splitIndex = Math.max(lastDotIndex, lastBracketIndex);
|
|
1119
|
+
if (splitIndex === -1) {
|
|
1120
|
+
return fieldPath;
|
|
1121
|
+
}
|
|
1122
|
+
return fieldPath.substring(splitIndex + 1);
|
|
1123
|
+
}
|
|
844
1124
|
function getSchemaFields(schema) {
|
|
845
1125
|
const fields = /* @__PURE__ */ new Set();
|
|
846
1126
|
const properties = schema.properties ?? {};
|
|
@@ -875,44 +1155,56 @@ function extractFieldRoot(dependency) {
|
|
|
875
1155
|
const root = dependency.split(".")[0]?.split("[")[0];
|
|
876
1156
|
return root || dependency;
|
|
877
1157
|
}
|
|
878
|
-
function
|
|
1158
|
+
function validateFormulaInContext(expression, fieldPath, rootSchema) {
|
|
879
1159
|
const syntaxResult = validateFormulaSyntax(expression);
|
|
880
1160
|
if (!syntaxResult.isValid) {
|
|
881
1161
|
return {
|
|
882
|
-
field:
|
|
1162
|
+
field: fieldPath,
|
|
883
1163
|
error: syntaxResult.error,
|
|
884
1164
|
position: syntaxResult.position
|
|
885
1165
|
};
|
|
886
1166
|
}
|
|
1167
|
+
const parentPath = getParentPath(fieldPath);
|
|
1168
|
+
const localFieldName = getFieldName(fieldPath);
|
|
1169
|
+
const contextSchema = resolveSubSchema(rootSchema, parentPath);
|
|
1170
|
+
if (!contextSchema) {
|
|
1171
|
+
return {
|
|
1172
|
+
field: fieldPath,
|
|
1173
|
+
error: `Cannot resolve schema context for path '${parentPath}'`
|
|
1174
|
+
};
|
|
1175
|
+
}
|
|
887
1176
|
const parseResult = parseExpression(expression);
|
|
888
|
-
const schemaFields = getSchemaFields(
|
|
1177
|
+
const schemaFields = getSchemaFields(contextSchema);
|
|
889
1178
|
for (const dep of parseResult.dependencies) {
|
|
890
1179
|
const rootField = extractFieldRoot(dep);
|
|
891
1180
|
if (!schemaFields.has(rootField)) {
|
|
892
1181
|
return {
|
|
893
|
-
field:
|
|
1182
|
+
field: fieldPath,
|
|
894
1183
|
error: `Unknown field '${rootField}' in formula`
|
|
895
1184
|
};
|
|
896
1185
|
}
|
|
897
1186
|
}
|
|
898
|
-
if (parseResult.dependencies.some((d) => extractFieldRoot(d) ===
|
|
1187
|
+
if (parseResult.dependencies.some((d) => extractFieldRoot(d) === localFieldName)) {
|
|
899
1188
|
return {
|
|
900
|
-
field:
|
|
1189
|
+
field: fieldPath,
|
|
901
1190
|
error: `Formula cannot reference itself`
|
|
902
1191
|
};
|
|
903
1192
|
}
|
|
904
|
-
const fieldSchema =
|
|
1193
|
+
const fieldSchema = contextSchema.properties?.[localFieldName];
|
|
905
1194
|
const expectedType = schemaTypeToInferred(fieldSchema?.type);
|
|
906
|
-
const fieldTypes = getSchemaFieldTypes(
|
|
1195
|
+
const fieldTypes = getSchemaFieldTypes(contextSchema);
|
|
907
1196
|
const inferredType = inferFormulaType(expression, fieldTypes);
|
|
908
1197
|
if (!isTypeCompatible(inferredType, expectedType)) {
|
|
909
1198
|
return {
|
|
910
|
-
field:
|
|
1199
|
+
field: fieldPath,
|
|
911
1200
|
error: `Type mismatch: formula returns '${inferredType}' but field expects '${expectedType}'`
|
|
912
1201
|
};
|
|
913
1202
|
}
|
|
914
1203
|
return null;
|
|
915
1204
|
}
|
|
1205
|
+
function validateFormulaAgainstSchema(expression, fieldName, schema) {
|
|
1206
|
+
return validateFormulaInContext(expression, fieldName, schema);
|
|
1207
|
+
}
|
|
916
1208
|
function validateSchemaFormulas(schema) {
|
|
917
1209
|
const errors = [];
|
|
918
1210
|
const formulas = extractSchemaFormulas(schema);
|
|
@@ -932,7 +1224,12 @@ function validateSchemaFormulas(schema) {
|
|
|
932
1224
|
const dependencies = {};
|
|
933
1225
|
for (const formula of formulas) {
|
|
934
1226
|
const parseResult = parseExpression(formula.expression);
|
|
935
|
-
|
|
1227
|
+
const parentPath = getParentPath(formula.fieldName);
|
|
1228
|
+
const prefix = parentPath ? `${parentPath}.` : "";
|
|
1229
|
+
dependencies[formula.fieldName] = parseResult.dependencies.map((dep) => {
|
|
1230
|
+
const rootField = extractFieldRoot(dep);
|
|
1231
|
+
return `${prefix}${rootField}`;
|
|
1232
|
+
});
|
|
936
1233
|
}
|
|
937
1234
|
const graph = buildDependencyGraph(dependencies);
|
|
938
1235
|
const circularCheck = detectCircularDependencies(graph);
|
|
@@ -950,6 +1247,6 @@ function validateSchemaFormulas(schema) {
|
|
|
950
1247
|
return { isValid: true, errors: [] };
|
|
951
1248
|
}
|
|
952
1249
|
|
|
953
|
-
export { buildDependencyGraph, detectCircularDependencies, evaluate, extractSchemaFormulas, getTopologicalOrder, inferFormulaType, parseExpression, parseFormula, validateFormulaAgainstSchema, validateFormulaSyntax, validateSchemaFormulas, validateSyntax };
|
|
954
|
-
//# sourceMappingURL=chunk-
|
|
955
|
-
//# sourceMappingURL=chunk-
|
|
1250
|
+
export { buildDependencyGraph, detectCircularDependencies, evaluate, evaluateWithContext, extractSchemaFormulas, getTopologicalOrder, inferFormulaType, parseExpression, parseFormula, validateFormulaAgainstSchema, validateFormulaSyntax, validateSchemaFormulas, validateSyntax };
|
|
1251
|
+
//# sourceMappingURL=chunk-FDIJPOVQ.js.map
|
|
1252
|
+
//# sourceMappingURL=chunk-FDIJPOVQ.js.map
|