@revisium/formula 0.3.0 → 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 +32 -0
- package/dist/{chunk-JJ72EVIZ.cjs → chunk-AGBOCJGV.cjs} +320 -89
- package/dist/chunk-AGBOCJGV.cjs.map +1 -0
- package/dist/{chunk-4JNGUHMF.js → chunk-FDIJPOVQ.js} +320 -90
- 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 +91 -1
- package/dist/formula-spec.cjs.map +1 -1
- package/dist/formula-spec.js +91 -1
- 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-4JNGUHMF.js.map +0 -1
- package/dist/chunk-JJ72EVIZ.cjs.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",
|
|
@@ -359,6 +475,12 @@ function isArrayFunction(name) {
|
|
|
359
475
|
function isContextToken(name) {
|
|
360
476
|
return name.startsWith("@") || name.startsWith("#");
|
|
361
477
|
}
|
|
478
|
+
function isRootPath(name) {
|
|
479
|
+
return name.startsWith("/");
|
|
480
|
+
}
|
|
481
|
+
function isRelativePath(name) {
|
|
482
|
+
return name.startsWith("..");
|
|
483
|
+
}
|
|
362
484
|
function isValidIdentifierRoot(rootName) {
|
|
363
485
|
return !isKeyword(rootName) && !isContextToken(rootName);
|
|
364
486
|
}
|
|
@@ -371,6 +493,10 @@ function addPathIfValid(path, identifiers) {
|
|
|
371
493
|
}
|
|
372
494
|
}
|
|
373
495
|
function collectStringIdentifier(node, identifiers) {
|
|
496
|
+
if (isRootPath(node) || isRelativePath(node)) {
|
|
497
|
+
identifiers.add(node);
|
|
498
|
+
return;
|
|
499
|
+
}
|
|
374
500
|
if (!isContextToken(node) && !isKeyword(node)) {
|
|
375
501
|
identifiers.add(node);
|
|
376
502
|
}
|
|
@@ -403,6 +529,9 @@ function collectIdentifiers(node, identifiers) {
|
|
|
403
529
|
if (!Array.isArray(node)) {
|
|
404
530
|
return;
|
|
405
531
|
}
|
|
532
|
+
if (isLiteralArray(node)) {
|
|
533
|
+
return;
|
|
534
|
+
}
|
|
406
535
|
const [op, ...args] = node;
|
|
407
536
|
if (op === "." || op === "[]") {
|
|
408
537
|
collectPathOrFallback(node, identifiers);
|
|
@@ -500,16 +629,25 @@ function detectFunctionCallFeatures(funcName, features) {
|
|
|
500
629
|
features.add("array_function");
|
|
501
630
|
}
|
|
502
631
|
}
|
|
503
|
-
function
|
|
504
|
-
if (
|
|
505
|
-
|
|
506
|
-
|
|
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");
|
|
507
640
|
}
|
|
508
|
-
return;
|
|
509
641
|
}
|
|
510
|
-
if (
|
|
511
|
-
|
|
642
|
+
if (isRelativePath(node)) {
|
|
643
|
+
features.add("relative_path");
|
|
644
|
+
const withoutPrefix = node.replace(/^(\.\.\/)+/, "");
|
|
645
|
+
if (withoutPrefix.includes(".")) {
|
|
646
|
+
features.add("nested_path");
|
|
647
|
+
}
|
|
512
648
|
}
|
|
649
|
+
}
|
|
650
|
+
function detectOperatorFeatures(node, features) {
|
|
513
651
|
const op = node[0];
|
|
514
652
|
if (op === ".") {
|
|
515
653
|
features.add("nested_path");
|
|
@@ -520,6 +658,16 @@ function detectFeatures(node, features) {
|
|
|
520
658
|
if (op === "()") {
|
|
521
659
|
detectFunctionCallFeatures(node[1], features);
|
|
522
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);
|
|
523
671
|
for (let i = 1; i < node.length; i++) {
|
|
524
672
|
detectFeatures(node[i], features);
|
|
525
673
|
}
|
|
@@ -562,77 +710,13 @@ function validateSyntax(expression) {
|
|
|
562
710
|
};
|
|
563
711
|
}
|
|
564
712
|
}
|
|
565
|
-
var BUILTIN_FUNCTIONS = {
|
|
566
|
-
and: (a, b) => Boolean(a) && Boolean(b),
|
|
567
|
-
or: (a, b) => Boolean(a) || Boolean(b),
|
|
568
|
-
not: (a) => !a,
|
|
569
|
-
concat: (...args) => args.map(String).join(""),
|
|
570
|
-
upper: (s) => String(s).toUpperCase(),
|
|
571
|
-
lower: (s) => String(s).toLowerCase(),
|
|
572
|
-
trim: (s) => String(s).trim(),
|
|
573
|
-
left: (s, n) => {
|
|
574
|
-
const count = Math.max(0, Math.floor(Number(n)));
|
|
575
|
-
return String(s).slice(0, count);
|
|
576
|
-
},
|
|
577
|
-
right: (s, n) => {
|
|
578
|
-
const str = String(s);
|
|
579
|
-
const count = Math.max(0, Math.floor(Number(n)));
|
|
580
|
-
return count === 0 ? "" : str.slice(-count);
|
|
581
|
-
},
|
|
582
|
-
replace: (s, search, replacement) => String(s).replace(String(search), String(replacement)),
|
|
583
|
-
tostring: String,
|
|
584
|
-
length: (s) => {
|
|
585
|
-
if (Array.isArray(s)) return s.length;
|
|
586
|
-
if (typeof s === "string") return s.length;
|
|
587
|
-
if (s !== null && typeof s === "object") return Object.keys(s).length;
|
|
588
|
-
return String(s).length;
|
|
589
|
-
},
|
|
590
|
-
contains: (s, search) => String(s).includes(String(search)),
|
|
591
|
-
startswith: (s, search) => String(s).startsWith(String(search)),
|
|
592
|
-
endswith: (s, search) => String(s).endsWith(String(search)),
|
|
593
|
-
tonumber: Number,
|
|
594
|
-
toboolean: Boolean,
|
|
595
|
-
isnull: (v) => v === null || v === void 0,
|
|
596
|
-
coalesce: (...args) => args.find((v) => v !== null && v !== void 0) ?? null,
|
|
597
|
-
round: (n, decimals) => {
|
|
598
|
-
const num2 = Number(n);
|
|
599
|
-
const dec = decimals === void 0 ? 0 : Number(decimals);
|
|
600
|
-
const factor = 10 ** dec;
|
|
601
|
-
return Math.round(num2 * factor) / factor;
|
|
602
|
-
},
|
|
603
|
-
floor: (n) => Math.floor(Number(n)),
|
|
604
|
-
ceil: (n) => Math.ceil(Number(n)),
|
|
605
|
-
abs: (n) => Math.abs(Number(n)),
|
|
606
|
-
sqrt: (n) => Math.sqrt(Number(n)),
|
|
607
|
-
pow: (base, exp) => Math.pow(Number(base), Number(exp)),
|
|
608
|
-
min: (...args) => args.length === 0 ? Number.NaN : Math.min(...args.map(Number)),
|
|
609
|
-
max: (...args) => args.length === 0 ? Number.NaN : Math.max(...args.map(Number)),
|
|
610
|
-
log: (n) => Math.log(Number(n)),
|
|
611
|
-
log10: (n) => Math.log10(Number(n)),
|
|
612
|
-
exp: (n) => Math.exp(Number(n)),
|
|
613
|
-
sign: (n) => Math.sign(Number(n)),
|
|
614
|
-
sum: (arr) => Array.isArray(arr) ? arr.reduce((a, b) => a + Number(b), 0) : 0,
|
|
615
|
-
avg: (arr) => Array.isArray(arr) && arr.length > 0 ? arr.reduce((a, b) => a + Number(b), 0) / arr.length : 0,
|
|
616
|
-
count: (arr) => Array.isArray(arr) ? arr.length : 0,
|
|
617
|
-
first: (arr) => Array.isArray(arr) ? arr[0] : void 0,
|
|
618
|
-
last: (arr) => Array.isArray(arr) ? arr.at(-1) : void 0,
|
|
619
|
-
join: (arr, separator) => {
|
|
620
|
-
if (!Array.isArray(arr)) return "";
|
|
621
|
-
if (separator === void 0) return arr.join(",");
|
|
622
|
-
if (typeof separator === "string") return arr.join(separator);
|
|
623
|
-
if (typeof separator === "number") return arr.join(String(separator));
|
|
624
|
-
return arr.join(",");
|
|
625
|
-
},
|
|
626
|
-
includes: (arr, value) => Array.isArray(arr) ? arr.includes(value) : false,
|
|
627
|
-
if: (condition, ifTrue, ifFalse) => condition ? ifTrue : ifFalse
|
|
628
|
-
};
|
|
629
713
|
function evaluate(expression, context) {
|
|
630
714
|
const trimmed = expression.trim();
|
|
631
715
|
if (!trimmed) {
|
|
632
716
|
throw new Error("Empty expression");
|
|
633
717
|
}
|
|
634
718
|
const fn = subscript_default(trimmed);
|
|
635
|
-
const safeContext = {
|
|
719
|
+
const safeContext = {};
|
|
636
720
|
for (const [key, value] of Object.entries(context)) {
|
|
637
721
|
if (typeof value !== "function") {
|
|
638
722
|
safeContext[key] = value;
|
|
@@ -640,6 +724,52 @@ function evaluate(expression, context) {
|
|
|
640
724
|
}
|
|
641
725
|
return fn(safeContext);
|
|
642
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);
|
|
772
|
+
}
|
|
643
773
|
var ARITHMETIC_OPS = /* @__PURE__ */ new Set(["+", "-", "*", "/", "%"]);
|
|
644
774
|
var COMPARISON_OPS = /* @__PURE__ */ new Set(["<", ">", "<=", ">=", "==", "!="]);
|
|
645
775
|
var LOGICAL_OPS = /* @__PURE__ */ new Set(["&&", "||", "!"]);
|
|
@@ -893,21 +1023,104 @@ function processQueue(queue, graph, inDegree) {
|
|
|
893
1023
|
// src/extract-schema.ts
|
|
894
1024
|
function extractSchemaFormulas(schema) {
|
|
895
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
|
+
}
|
|
896
1034
|
const properties = schema.properties ?? {};
|
|
897
1035
|
for (const [fieldName, fieldSchema] of Object.entries(properties)) {
|
|
1036
|
+
const fullPath = pathPrefix ? `${pathPrefix}.${fieldName}` : fieldName;
|
|
898
1037
|
const xFormula = fieldSchema["x-formula"];
|
|
899
1038
|
if (xFormula) {
|
|
900
1039
|
formulas.push({
|
|
901
|
-
fieldName,
|
|
1040
|
+
fieldName: fullPath,
|
|
902
1041
|
expression: xFormula.expression,
|
|
903
1042
|
fieldType: fieldSchema.type ?? "string"
|
|
904
1043
|
});
|
|
905
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
|
+
}
|
|
906
1051
|
}
|
|
907
|
-
return formulas;
|
|
908
1052
|
}
|
|
909
1053
|
|
|
910
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
|
+
}
|
|
911
1124
|
function getSchemaFields(schema) {
|
|
912
1125
|
const fields = /* @__PURE__ */ new Set();
|
|
913
1126
|
const properties = schema.properties ?? {};
|
|
@@ -942,44 +1155,56 @@ function extractFieldRoot(dependency) {
|
|
|
942
1155
|
const root = dependency.split(".")[0]?.split("[")[0];
|
|
943
1156
|
return root || dependency;
|
|
944
1157
|
}
|
|
945
|
-
function
|
|
1158
|
+
function validateFormulaInContext(expression, fieldPath, rootSchema) {
|
|
946
1159
|
const syntaxResult = validateFormulaSyntax(expression);
|
|
947
1160
|
if (!syntaxResult.isValid) {
|
|
948
1161
|
return {
|
|
949
|
-
field:
|
|
1162
|
+
field: fieldPath,
|
|
950
1163
|
error: syntaxResult.error,
|
|
951
1164
|
position: syntaxResult.position
|
|
952
1165
|
};
|
|
953
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
|
+
}
|
|
954
1176
|
const parseResult = parseExpression(expression);
|
|
955
|
-
const schemaFields = getSchemaFields(
|
|
1177
|
+
const schemaFields = getSchemaFields(contextSchema);
|
|
956
1178
|
for (const dep of parseResult.dependencies) {
|
|
957
1179
|
const rootField = extractFieldRoot(dep);
|
|
958
1180
|
if (!schemaFields.has(rootField)) {
|
|
959
1181
|
return {
|
|
960
|
-
field:
|
|
1182
|
+
field: fieldPath,
|
|
961
1183
|
error: `Unknown field '${rootField}' in formula`
|
|
962
1184
|
};
|
|
963
1185
|
}
|
|
964
1186
|
}
|
|
965
|
-
if (parseResult.dependencies.some((d) => extractFieldRoot(d) ===
|
|
1187
|
+
if (parseResult.dependencies.some((d) => extractFieldRoot(d) === localFieldName)) {
|
|
966
1188
|
return {
|
|
967
|
-
field:
|
|
1189
|
+
field: fieldPath,
|
|
968
1190
|
error: `Formula cannot reference itself`
|
|
969
1191
|
};
|
|
970
1192
|
}
|
|
971
|
-
const fieldSchema =
|
|
1193
|
+
const fieldSchema = contextSchema.properties?.[localFieldName];
|
|
972
1194
|
const expectedType = schemaTypeToInferred(fieldSchema?.type);
|
|
973
|
-
const fieldTypes = getSchemaFieldTypes(
|
|
1195
|
+
const fieldTypes = getSchemaFieldTypes(contextSchema);
|
|
974
1196
|
const inferredType = inferFormulaType(expression, fieldTypes);
|
|
975
1197
|
if (!isTypeCompatible(inferredType, expectedType)) {
|
|
976
1198
|
return {
|
|
977
|
-
field:
|
|
1199
|
+
field: fieldPath,
|
|
978
1200
|
error: `Type mismatch: formula returns '${inferredType}' but field expects '${expectedType}'`
|
|
979
1201
|
};
|
|
980
1202
|
}
|
|
981
1203
|
return null;
|
|
982
1204
|
}
|
|
1205
|
+
function validateFormulaAgainstSchema(expression, fieldName, schema) {
|
|
1206
|
+
return validateFormulaInContext(expression, fieldName, schema);
|
|
1207
|
+
}
|
|
983
1208
|
function validateSchemaFormulas(schema) {
|
|
984
1209
|
const errors = [];
|
|
985
1210
|
const formulas = extractSchemaFormulas(schema);
|
|
@@ -999,7 +1224,12 @@ function validateSchemaFormulas(schema) {
|
|
|
999
1224
|
const dependencies = {};
|
|
1000
1225
|
for (const formula of formulas) {
|
|
1001
1226
|
const parseResult = parseExpression(formula.expression);
|
|
1002
|
-
|
|
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
|
+
});
|
|
1003
1233
|
}
|
|
1004
1234
|
const graph = buildDependencyGraph(dependencies);
|
|
1005
1235
|
const circularCheck = detectCircularDependencies(graph);
|
|
@@ -1017,6 +1247,6 @@ function validateSchemaFormulas(schema) {
|
|
|
1017
1247
|
return { isValid: true, errors: [] };
|
|
1018
1248
|
}
|
|
1019
1249
|
|
|
1020
|
-
export { buildDependencyGraph, detectCircularDependencies, evaluate, extractSchemaFormulas, getTopologicalOrder, inferFormulaType, parseExpression, parseFormula, validateFormulaAgainstSchema, validateFormulaSyntax, validateSchemaFormulas, validateSyntax };
|
|
1021
|
-
//# sourceMappingURL=chunk-
|
|
1022
|
-
//# 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
|