@datarails-ai/dr-appspace-cli 0.12.28 → 0.12.30
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +390 -59
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -13,7 +13,7 @@ import { fileURLToPath as fileURLToPath6 } from "url";
|
|
|
13
13
|
// package.json
|
|
14
14
|
var package_default = {
|
|
15
15
|
name: "@datarails-ai/dr-appspace-cli",
|
|
16
|
-
version: "0.12.
|
|
16
|
+
version: "0.12.30",
|
|
17
17
|
description: "Datarails App Space CLI \u2014 build, try, push and publish App Space apps",
|
|
18
18
|
type: "module",
|
|
19
19
|
engines: {
|
|
@@ -57,7 +57,7 @@ var package_default = {
|
|
|
57
57
|
};
|
|
58
58
|
|
|
59
59
|
// src/buildInfo.ts
|
|
60
|
-
var BUILD_TIME = "2026-10-25T11:
|
|
60
|
+
var BUILD_TIME = "2026-10-25T11:56:43.845Z";
|
|
61
61
|
function formatBuildTime(iso) {
|
|
62
62
|
if (iso === "") return "";
|
|
63
63
|
const d = new Date(iso);
|
|
@@ -6327,15 +6327,15 @@ function inputTypeOf(node) {
|
|
|
6327
6327
|
}
|
|
6328
6328
|
function optionsOf(node, text3, tree) {
|
|
6329
6329
|
const options = [];
|
|
6330
|
-
const
|
|
6330
|
+
const walk5 = (index) => {
|
|
6331
6331
|
for (const child of tree.nodes[index].children) {
|
|
6332
6332
|
const candidate = tree.nodes[child];
|
|
6333
6333
|
if (candidate.tag === "option") {
|
|
6334
6334
|
if (!("disabled" in candidate.attrs)) options.push(candidate.attrs.value ?? textOf(text3, candidate));
|
|
6335
|
-
} else
|
|
6335
|
+
} else walk5(child);
|
|
6336
6336
|
}
|
|
6337
6337
|
};
|
|
6338
|
-
|
|
6338
|
+
walk5(node.index);
|
|
6339
6339
|
return options;
|
|
6340
6340
|
}
|
|
6341
6341
|
function splitList(value) {
|
|
@@ -6354,14 +6354,14 @@ function textOf(text3, node) {
|
|
|
6354
6354
|
function slotsOf(node, tree) {
|
|
6355
6355
|
if (!(WIDGET_ATTR in node.attrs)) return {};
|
|
6356
6356
|
const slots = {};
|
|
6357
|
-
const
|
|
6357
|
+
const walk5 = (index) => {
|
|
6358
6358
|
for (const child of tree.nodes[index].children) {
|
|
6359
6359
|
const kind = classify(tree.nodes[child]);
|
|
6360
6360
|
if (kind?.type === "slot") slots[kind.role] = kind.role === "body" ? classesOf(tree.nodes[child])[0] ?? true : true;
|
|
6361
|
-
else
|
|
6361
|
+
else walk5(child);
|
|
6362
6362
|
}
|
|
6363
6363
|
};
|
|
6364
|
-
|
|
6364
|
+
walk5(node.index);
|
|
6365
6365
|
return slots;
|
|
6366
6366
|
}
|
|
6367
6367
|
function bindingOf(entry, node, spec) {
|
|
@@ -7082,15 +7082,15 @@ function hasCycle(tables) {
|
|
|
7082
7082
|
}
|
|
7083
7083
|
}
|
|
7084
7084
|
const state = /* @__PURE__ */ new Map();
|
|
7085
|
-
const
|
|
7085
|
+
const walk5 = (name) => {
|
|
7086
7086
|
if (state.get(name) === "done") return false;
|
|
7087
7087
|
if (state.get(name) === "open") return true;
|
|
7088
7088
|
state.set(name, "open");
|
|
7089
|
-
for (const next of graph.get(name) ?? []) if (
|
|
7089
|
+
for (const next of graph.get(name) ?? []) if (walk5(next)) return true;
|
|
7090
7090
|
state.set(name, "done");
|
|
7091
7091
|
return false;
|
|
7092
7092
|
};
|
|
7093
|
-
return [...graph.keys()].some((name) =>
|
|
7093
|
+
return [...graph.keys()].some((name) => walk5(name));
|
|
7094
7094
|
}
|
|
7095
7095
|
function byProblem(a, b) {
|
|
7096
7096
|
return natural3(a.code, b.code) || natural3(a.where ?? "", b.where ?? "") || natural3(a.message, b.message);
|
|
@@ -7769,15 +7769,16 @@ var contract_schema_default = {
|
|
|
7769
7769
|
sources: {
|
|
7770
7770
|
$comment: "Present only for kind: app, and only when the contract declares any. A source some component names compiles to handlers/<name>.py; one only depended on is served in-process by the module that depends on it.",
|
|
7771
7771
|
type: "object",
|
|
7772
|
-
description: "one row per source: where its rows come from (from), the shape it answers in, the transforms/<name>.py that reshapes them, and the keys its read is narrowed by.",
|
|
7772
|
+
description: "one row per source: where its rows come from (from), the shape it answers in, the transforms/<name>.py that reshapes them or the expr the platform evaluates over them, and the keys its read is narrowed by.",
|
|
7773
7773
|
additionalProperties: {
|
|
7774
7774
|
type: "object",
|
|
7775
7775
|
required: ["from"],
|
|
7776
7776
|
properties: {
|
|
7777
7777
|
from: {
|
|
7778
|
-
description: 'one of four spellings: {"financeos": {...}} (the dashboard SPEC source vocabulary), {"table": "<app table>"}, {"call": "<dotted method>", "args": {...}}, or ["<source>", ...] to compose others',
|
|
7778
|
+
description: 'one of four spellings: {"financeos": {...}} (the dashboard SPEC source vocabulary), {"table": "<app table>"}, {"call": "<dotted method>", "args": {...}}, or ["<source>", ...] to compose others (with a transform, or with an expr the platform evaluates)',
|
|
7779
7779
|
oneOf: [{ type: "object" }, { type: "array", items: { type: "string" } }]
|
|
7780
7780
|
},
|
|
7781
|
+
expr: { type: "string", description: "OPTIONAL, beside from as a list and no transform (lineage tier 3, docs/appspace/design-computed-card-lineage.md): the source's figure as a formula over its from sources' figures, which the PLATFORM evaluates, so the figure is made of Datarails reads and never computed by the app. It may use numbers, the operands' names, + - * / and parentheses (/ is a safe divide: a zero or missing divisor is no figure, and any missing figure makes the result none), and abs(x), min(a, b, ...), max(a, b, ...), round(x) or round(x, decimals 0-6, half away from zero), and if(comparison, then, else), the one place a comparison (< <= > >= == !=) may stand. Shape kpi (the value, and the compare where every operand carries one), series (point by point: each series operand draws one line, and a kpi operand is one figure at every point) or breakdown (member by member: every breakdown operand states top: null, and a kpi operand is one figure for every member). Its figure comes from an operand on every path: each branch of if() and each argument of min()/max() reads an operand, so a number alone (870000, or a fallback 0) is never the figure; write actual / budget, not if(budget > 0, actual / budget, 0). Every operand is a FinanceOS table read or metric (from.financeos) or another expr source over them - never a transform's output, a composed source, a call or an app table (a KPI belongs in the Datarails semantic layer, not in app data) - and every operand is used; compare is not stated here (it is the formula over the operands' compares). The compiler refuses the rest (source_expr)." },
|
|
7781
7782
|
shape: { enum: ["rows", "series", "kpi", "breakdown", "table", "ohlc", "scatter", "narrative"] },
|
|
7782
7783
|
transform: { type: "string", pattern: "^transforms/[a-z][a-z0-9_]*\\.py$" },
|
|
7783
7784
|
period: { type: ["string", "object", "null"], description: `OPTIONAL. The window the source reads. "all": every period, whatever the page sends, honoured or not (not a metric). "ytd", "last12" or "quarter" (a FinanceOS source, table or metric): a rolling window resolved at each request as the page's period presets are, so it ends today; a source that honours period reads the page's window when the page sends one, and this one when it sends none. An object {start, end, grain}: a fixed window, read where the source does not honour period. Absent: year to date, or the page's window where the source honours period.` },
|
|
@@ -8500,16 +8501,16 @@ function payloadProducers(contract, { moduleSource = () => void 0, pageSource =
|
|
|
8500
8501
|
};
|
|
8501
8502
|
const fieldsUnder = (uid) => {
|
|
8502
8503
|
const fields = [];
|
|
8503
|
-
const
|
|
8504
|
+
const walk5 = (at) => {
|
|
8504
8505
|
for (const child of children.get(at) ?? []) {
|
|
8505
8506
|
const element = elements[child];
|
|
8506
8507
|
const field = fieldOf(element);
|
|
8507
8508
|
if (field !== null && !fields.includes(field)) fields.push(field);
|
|
8508
8509
|
if (element?.type === "form" || element?.type === "dialog") continue;
|
|
8509
|
-
|
|
8510
|
+
walk5(child);
|
|
8510
8511
|
}
|
|
8511
8512
|
};
|
|
8512
|
-
|
|
8513
|
+
walk5(uid);
|
|
8513
8514
|
return fields;
|
|
8514
8515
|
};
|
|
8515
8516
|
for (const uid of Object.keys(elements).sort()) {
|
|
@@ -9736,20 +9737,20 @@ function untitledCards(contract) {
|
|
|
9736
9737
|
if (page?.owned === true) continue;
|
|
9737
9738
|
const seen = /* @__PURE__ */ new Set();
|
|
9738
9739
|
const drawn2 = [];
|
|
9739
|
-
const
|
|
9740
|
+
const walk5 = (uids) => {
|
|
9740
9741
|
for (const uid of uids) {
|
|
9741
9742
|
const element = elements[uid];
|
|
9742
9743
|
if (element === void 0 || seen.has(uid)) continue;
|
|
9743
9744
|
seen.add(uid);
|
|
9744
9745
|
if (LAYOUT.has(element.type ?? "")) {
|
|
9745
9746
|
const children = element.children?.length ? element.children : Object.keys(elements).filter((other) => elements[other]?.parent === uid);
|
|
9746
|
-
|
|
9747
|
+
walk5(children);
|
|
9747
9748
|
} else {
|
|
9748
9749
|
drawn2.push(uid);
|
|
9749
9750
|
}
|
|
9750
9751
|
}
|
|
9751
9752
|
};
|
|
9752
|
-
|
|
9753
|
+
walk5(rootsOf(contract, page));
|
|
9753
9754
|
for (const uid of drawn2) {
|
|
9754
9755
|
const element = elements[uid];
|
|
9755
9756
|
const type = element.type ?? "";
|
|
@@ -11627,6 +11628,225 @@ function badgesCss() {
|
|
|
11627
11628
|
`;
|
|
11628
11629
|
}
|
|
11629
11630
|
|
|
11631
|
+
// ../flows/insights-builder/kit/contract/formula.mjs
|
|
11632
|
+
var FORMULA_FUNCTIONS = Object.freeze({
|
|
11633
|
+
abs: [1, 1],
|
|
11634
|
+
if: [3, 3],
|
|
11635
|
+
max: [2, null],
|
|
11636
|
+
min: [2, null],
|
|
11637
|
+
round: [1, 2]
|
|
11638
|
+
});
|
|
11639
|
+
var ROUND_DIGITS_MAX = 6;
|
|
11640
|
+
var FORMULA_NUMBERS_ALONE = "its figure can be a number it states rather than an operand's, and a number the app typed is no Datarails figure; build it from its operands (from) on every path: each branch of if() and each argument of min() and max() must come from an operand (if()'s condition only picks a branch), and / already gives no figure for a zero divisor, so no fallback number is needed";
|
|
11641
|
+
var COMPARISONS = ["<=", ">=", "==", "!=", "<", ">"];
|
|
11642
|
+
var FormulaSyntax = class extends Error {
|
|
11643
|
+
};
|
|
11644
|
+
function tokenize2(text3) {
|
|
11645
|
+
const tokens = [];
|
|
11646
|
+
let at = 0;
|
|
11647
|
+
const isDigit = (c) => c >= "0" && c <= "9";
|
|
11648
|
+
const isStart = (c) => /[\p{L}_]/u.test(c);
|
|
11649
|
+
const isPart5 = (c) => /[\p{L}\p{N}_]/u.test(c);
|
|
11650
|
+
while (at < text3.length) {
|
|
11651
|
+
const c = text3[at];
|
|
11652
|
+
if (/\s/.test(c)) {
|
|
11653
|
+
at += 1;
|
|
11654
|
+
} else if (COMPARISONS.slice(0, 4).includes(text3.slice(at, at + 2))) {
|
|
11655
|
+
tokens.push(["op", text3.slice(at, at + 2)]);
|
|
11656
|
+
at += 2;
|
|
11657
|
+
} else if ("+-*/(),<>".includes(c)) {
|
|
11658
|
+
tokens.push(["op", c]);
|
|
11659
|
+
at += 1;
|
|
11660
|
+
} else if (isDigit(c) || c === ".") {
|
|
11661
|
+
const start = at;
|
|
11662
|
+
while (at < text3.length && (isDigit(text3[at]) || text3[at] === ".")) at += 1;
|
|
11663
|
+
const spelled = text3.slice(start, at);
|
|
11664
|
+
if ((spelled.match(/\./g) ?? []).length > 1 || spelled === ".") throw new FormulaSyntax();
|
|
11665
|
+
tokens.push(["num", Number(spelled)]);
|
|
11666
|
+
} else if (isStart(c)) {
|
|
11667
|
+
const start = at;
|
|
11668
|
+
while (at < text3.length && isPart5(text3[at])) at += 1;
|
|
11669
|
+
tokens.push(["name", text3.slice(start, at)]);
|
|
11670
|
+
} else {
|
|
11671
|
+
throw new FormulaSyntax();
|
|
11672
|
+
}
|
|
11673
|
+
}
|
|
11674
|
+
return tokens;
|
|
11675
|
+
}
|
|
11676
|
+
function parseFormula(text3) {
|
|
11677
|
+
const tokens = tokenize2(String(text3));
|
|
11678
|
+
if (tokens.length === 0) throw new FormulaSyntax();
|
|
11679
|
+
const is = (at, op) => at < tokens.length && tokens[at][0] === "op" && tokens[at][1] === op;
|
|
11680
|
+
const isComparison = (at) => at < tokens.length && tokens[at][0] === "op" && COMPARISONS.includes(tokens[at][1]);
|
|
11681
|
+
function expr(at) {
|
|
11682
|
+
let [node2, next2] = term(at);
|
|
11683
|
+
while (is(next2, "+") || is(next2, "-")) {
|
|
11684
|
+
const op = tokens[next2][1];
|
|
11685
|
+
const [right, after] = term(next2 + 1);
|
|
11686
|
+
node2 = ["bin", op, node2, right];
|
|
11687
|
+
next2 = after;
|
|
11688
|
+
}
|
|
11689
|
+
return [node2, next2];
|
|
11690
|
+
}
|
|
11691
|
+
function term(at) {
|
|
11692
|
+
let [node2, next2] = factor(at);
|
|
11693
|
+
while (is(next2, "*") || is(next2, "/")) {
|
|
11694
|
+
const op = tokens[next2][1];
|
|
11695
|
+
const [right, after] = factor(next2 + 1);
|
|
11696
|
+
node2 = ["bin", op, node2, right];
|
|
11697
|
+
next2 = after;
|
|
11698
|
+
}
|
|
11699
|
+
return [node2, next2];
|
|
11700
|
+
}
|
|
11701
|
+
function factor(at) {
|
|
11702
|
+
if (is(at, "-")) {
|
|
11703
|
+
const [node2, next2] = factor(at + 1);
|
|
11704
|
+
return [["neg", node2], next2];
|
|
11705
|
+
}
|
|
11706
|
+
return atom(at);
|
|
11707
|
+
}
|
|
11708
|
+
function atom(at) {
|
|
11709
|
+
if (at >= tokens.length) throw new FormulaSyntax();
|
|
11710
|
+
const [kind, value] = tokens[at];
|
|
11711
|
+
if (kind === "num") return [["num", value], at + 1];
|
|
11712
|
+
if (kind === "name") {
|
|
11713
|
+
if (is(at + 1, "(")) return call(at + 2, value);
|
|
11714
|
+
return [["name", value], at + 1];
|
|
11715
|
+
}
|
|
11716
|
+
if (value === "(") {
|
|
11717
|
+
const [node2, next2] = expr(at + 1);
|
|
11718
|
+
if (!is(next2, ")")) throw new FormulaSyntax();
|
|
11719
|
+
return [node2, next2 + 1];
|
|
11720
|
+
}
|
|
11721
|
+
throw new FormulaSyntax();
|
|
11722
|
+
}
|
|
11723
|
+
function call(at, name) {
|
|
11724
|
+
const args = [];
|
|
11725
|
+
let next2 = at;
|
|
11726
|
+
for (; ; ) {
|
|
11727
|
+
let [node2, after] = expr(next2);
|
|
11728
|
+
if (isComparison(after)) {
|
|
11729
|
+
const op = tokens[after][1];
|
|
11730
|
+
const [right, past] = expr(after + 1);
|
|
11731
|
+
node2 = ["cmp", op, node2, right];
|
|
11732
|
+
after = past;
|
|
11733
|
+
}
|
|
11734
|
+
args.push(node2);
|
|
11735
|
+
if (is(after, ",")) {
|
|
11736
|
+
next2 = after + 1;
|
|
11737
|
+
continue;
|
|
11738
|
+
}
|
|
11739
|
+
if (is(after, ")")) return [["fn", name, args], after + 1];
|
|
11740
|
+
throw new FormulaSyntax();
|
|
11741
|
+
}
|
|
11742
|
+
}
|
|
11743
|
+
let [node, next] = expr(0);
|
|
11744
|
+
if (isComparison(next)) {
|
|
11745
|
+
const op = tokens[next][1];
|
|
11746
|
+
const [right, after] = expr(next + 1);
|
|
11747
|
+
node = ["cmp", op, node, right];
|
|
11748
|
+
next = after;
|
|
11749
|
+
}
|
|
11750
|
+
if (next !== tokens.length) throw new FormulaSyntax();
|
|
11751
|
+
return node;
|
|
11752
|
+
}
|
|
11753
|
+
function formulaNames(node) {
|
|
11754
|
+
const out = /* @__PURE__ */ new Set();
|
|
11755
|
+
const walk5 = (one) => {
|
|
11756
|
+
if (one[0] === "name") out.add(one[1]);
|
|
11757
|
+
else if (one[0] === "neg") walk5(one[1]);
|
|
11758
|
+
else if (one[0] === "bin" || one[0] === "cmp") {
|
|
11759
|
+
walk5(one[2]);
|
|
11760
|
+
walk5(one[3]);
|
|
11761
|
+
} else if (one[0] === "fn") one[2].forEach(walk5);
|
|
11762
|
+
};
|
|
11763
|
+
walk5(node);
|
|
11764
|
+
return out;
|
|
11765
|
+
}
|
|
11766
|
+
function takesAnOperand(node) {
|
|
11767
|
+
const kind = node[0];
|
|
11768
|
+
if (kind === "name") return true;
|
|
11769
|
+
if (kind === "neg") return takesAnOperand(node[1]);
|
|
11770
|
+
if (kind === "bin") return takesAnOperand(node[2]) || takesAnOperand(node[3]);
|
|
11771
|
+
if (kind === "fn") {
|
|
11772
|
+
const [, name, args] = node;
|
|
11773
|
+
let figures = args;
|
|
11774
|
+
if (name === "if") figures = args.slice(1);
|
|
11775
|
+
else if (name === "abs" || name === "round") figures = args.slice(0, 1);
|
|
11776
|
+
return figures.length > 0 && figures.every(takesAnOperand);
|
|
11777
|
+
}
|
|
11778
|
+
return false;
|
|
11779
|
+
}
|
|
11780
|
+
var ALLOWED = Object.keys(FORMULA_FUNCTIONS).sort().map((name) => `${name}()`).join(", ");
|
|
11781
|
+
function walk2(node, problems, condition) {
|
|
11782
|
+
const note = (sentence2) => {
|
|
11783
|
+
if (!problems.includes(sentence2)) problems.push(sentence2);
|
|
11784
|
+
};
|
|
11785
|
+
const kind = node[0];
|
|
11786
|
+
if (kind === "cmp") {
|
|
11787
|
+
if (!condition) note("a comparison (< <= > >= == !=) stands only as if()'s first argument, such as if(actual > budget, actual, budget)");
|
|
11788
|
+
walk2(node[2], problems, false);
|
|
11789
|
+
walk2(node[3], problems, false);
|
|
11790
|
+
return;
|
|
11791
|
+
}
|
|
11792
|
+
if (condition) note("if() takes a comparison first, such as if(actual > budget, actual, budget)");
|
|
11793
|
+
if (kind === "neg") walk2(node[1], problems, false);
|
|
11794
|
+
else if (kind === "bin") {
|
|
11795
|
+
walk2(node[2], problems, false);
|
|
11796
|
+
walk2(node[3], problems, false);
|
|
11797
|
+
} else if (kind === "fn") {
|
|
11798
|
+
const [, name, args] = node;
|
|
11799
|
+
callProblems(name, args, note);
|
|
11800
|
+
args.forEach((arg, index) => walk2(arg, problems, name === "if" && index === 0));
|
|
11801
|
+
}
|
|
11802
|
+
}
|
|
11803
|
+
function callProblems(name, args, note) {
|
|
11804
|
+
if (!Object.prototype.hasOwnProperty.call(FORMULA_FUNCTIONS, name)) {
|
|
11805
|
+
note(`${name}() is not a function a formula may call; it may call ${ALLOWED}`);
|
|
11806
|
+
return;
|
|
11807
|
+
}
|
|
11808
|
+
const [fewest, most] = FORMULA_FUNCTIONS[name];
|
|
11809
|
+
if (args.length < fewest || most !== null && args.length > most) {
|
|
11810
|
+
let said2;
|
|
11811
|
+
if (name === "if") said2 = "3 arguments: a comparison, the figure when it holds, and the figure when it does not";
|
|
11812
|
+
else if (most === null) said2 = `at least ${fewest} arguments`;
|
|
11813
|
+
else if (fewest === most) said2 = `${fewest} argument${fewest === 1 ? "" : "s"}`;
|
|
11814
|
+
else said2 = `${fewest} or ${most} arguments`;
|
|
11815
|
+
note(`${name}() takes ${said2}, and it is given ${args.length}`);
|
|
11816
|
+
return;
|
|
11817
|
+
}
|
|
11818
|
+
if (name === "round" && args.length === 2) {
|
|
11819
|
+
const digits = args[1];
|
|
11820
|
+
if (!(digits[0] === "num" && Number.isInteger(digits[1]) && digits[1] >= 0 && digits[1] <= ROUND_DIGITS_MAX)) {
|
|
11821
|
+
note(`round()'s second argument is how many decimals to keep: a whole number from 0 to ${ROUND_DIGITS_MAX}, written as a number`);
|
|
11822
|
+
}
|
|
11823
|
+
}
|
|
11824
|
+
}
|
|
11825
|
+
function formulaProblems(text3, operands) {
|
|
11826
|
+
if (typeof text3 !== "string") return { tree: null, problems: ["the formula is not text"] };
|
|
11827
|
+
let tree;
|
|
11828
|
+
try {
|
|
11829
|
+
tree = parseFormula(text3);
|
|
11830
|
+
} catch (error) {
|
|
11831
|
+
if (!(error instanceof FormulaSyntax)) throw error;
|
|
11832
|
+
return {
|
|
11833
|
+
tree: null,
|
|
11834
|
+
problems: [`it is not a formula the platform can read: it may use numbers, its operands' names, + - * /, parentheses, and the functions ${ALLOWED}`]
|
|
11835
|
+
};
|
|
11836
|
+
}
|
|
11837
|
+
const problems = [];
|
|
11838
|
+
walk2(tree, problems, false);
|
|
11839
|
+
const known = (Array.isArray(operands) ? operands : []).filter((one) => typeof one === "string");
|
|
11840
|
+
for (const name of [...formulaNames(tree)].sort()) {
|
|
11841
|
+
if (!known.includes(name)) {
|
|
11842
|
+
const sentence2 = `it reads ${name}, which is not one of its operands (from); its operands are: ${known.join(", ") || "none"}`;
|
|
11843
|
+
if (!problems.includes(sentence2)) problems.push(sentence2);
|
|
11844
|
+
}
|
|
11845
|
+
}
|
|
11846
|
+
if (!takesAnOperand(tree) && !problems.includes(FORMULA_NUMBERS_ALONE)) problems.push(FORMULA_NUMBERS_ALONE);
|
|
11847
|
+
return problems.length > 0 ? { tree: null, problems } : { tree, problems: [] };
|
|
11848
|
+
}
|
|
11849
|
+
|
|
11630
11850
|
// ../flows/insights-builder/kit/contract/compileApp.mjs
|
|
11631
11851
|
var RUNTIME = "dr-app-runtime@1.11.0";
|
|
11632
11852
|
var UI_KIT2 = "ui-kit/v4";
|
|
@@ -11637,7 +11857,7 @@ var WIDGETS = "dr-app-widgets@1.4.0";
|
|
|
11637
11857
|
var COMPONENTS = "dr-components@1.5.0";
|
|
11638
11858
|
var CONTROLS = "dr-components-controls@1.2.0";
|
|
11639
11859
|
var DASHBOARD_RUNTIME = "dr-app-runtime@1.13.1";
|
|
11640
|
-
var DASHBOARD_WIDGETS = "dr-app-widgets@1.5.
|
|
11860
|
+
var DASHBOARD_WIDGETS = "dr-app-widgets@1.5.1";
|
|
11641
11861
|
var DASHBOARD_BAR = "dr-app-dashboard@1.0.0";
|
|
11642
11862
|
var ASSISTANT = "dr-insights@1.4.0";
|
|
11643
11863
|
var MARKDOWN = "dr-md@1.0.0";
|
|
@@ -12084,7 +12304,7 @@ function sourceProblems(sources, app, handlers, extra) {
|
|
|
12084
12304
|
});
|
|
12085
12305
|
}
|
|
12086
12306
|
}
|
|
12087
|
-
if (row.transform === void 0 || row.transform === null) {
|
|
12307
|
+
if ((row.transform === void 0 || row.transform === null) && row.expr === void 0) {
|
|
12088
12308
|
problems.push({
|
|
12089
12309
|
code: "transform_missing",
|
|
12090
12310
|
pointer: at(name, "transform"),
|
|
@@ -12119,6 +12339,109 @@ function sourceProblems(sources, app, handlers, extra) {
|
|
|
12119
12339
|
}
|
|
12120
12340
|
return problems;
|
|
12121
12341
|
}
|
|
12342
|
+
var FORMULA_SHAPES = ["kpi", "series", "breakdown"];
|
|
12343
|
+
function exprProblems(sources) {
|
|
12344
|
+
const problems = [];
|
|
12345
|
+
const at = (name, key) => `/data/sources/${escapePointer3(name)}${key ? `/${key}` : ""}`;
|
|
12346
|
+
const isFormula = (row) => isPlainObject8(row) && row.expr !== void 0;
|
|
12347
|
+
const say = (pointer2, message) => problems.push({ code: "source_expr", pointer: pointer2, message });
|
|
12348
|
+
const cutOperands = /* @__PURE__ */ new Set();
|
|
12349
|
+
for (const name of Object.keys(sources).sort(natural3)) {
|
|
12350
|
+
const row = sources[name];
|
|
12351
|
+
if (!isFormula(row)) continue;
|
|
12352
|
+
const pointer2 = at(name, "expr");
|
|
12353
|
+
if (!Array.isArray(row.from)) {
|
|
12354
|
+
say(pointer2, `source "${name}" states expr, and a formula is evaluated over other sources' figures: write from as the list of sources it reads (["budget", "actual"])`);
|
|
12355
|
+
continue;
|
|
12356
|
+
}
|
|
12357
|
+
if (row.transform !== void 0 && row.transform !== null) {
|
|
12358
|
+
say(pointer2, `source "${name}" states expr and a transform; its figure is one or the other \u2014 drop the transform, and the platform evaluates expr`);
|
|
12359
|
+
continue;
|
|
12360
|
+
}
|
|
12361
|
+
const shape = row.shape ?? "rows";
|
|
12362
|
+
if (!FORMULA_SHAPES.includes(shape)) {
|
|
12363
|
+
say(at(name, "shape"), `source "${name}" states a formula and answers ${JSON.stringify(shape)}; a formula answers ${either3(FORMULA_SHAPES.map((one) => JSON.stringify(one)))} \u2014 state its shape`);
|
|
12364
|
+
}
|
|
12365
|
+
if (row.compare !== void 0) {
|
|
12366
|
+
say(at(name, "compare"), `source "${name}" is a formula and states compare; its compare is its formula over its operands' compares \u2014 state compare on the operands (compare_label still names it here)`);
|
|
12367
|
+
}
|
|
12368
|
+
const operands = row.from.filter(named5);
|
|
12369
|
+
const { tree, problems: wrong } = formulaProblems(row.expr, operands);
|
|
12370
|
+
for (const one of wrong) say(pointer2, `source "${name}"'s formula cannot be evaluated: ${one}`);
|
|
12371
|
+
if (tree === null) continue;
|
|
12372
|
+
const read = formulaNames(tree);
|
|
12373
|
+
row.from.forEach((dep, index) => {
|
|
12374
|
+
if (named5(dep) && !read.has(dep)) {
|
|
12375
|
+
say(
|
|
12376
|
+
at(name, `from/${index}`),
|
|
12377
|
+
`source "${name}"'s formula never reads "${dep}"; an operand it does not use would be listed as an input its figure was not built from \u2014 drop it from from, or use it in expr`
|
|
12378
|
+
);
|
|
12379
|
+
}
|
|
12380
|
+
});
|
|
12381
|
+
const allowed = shape === "kpi" ? ["kpi"] : [shape, "kpi"];
|
|
12382
|
+
let shaped = shape === "kpi";
|
|
12383
|
+
for (const dep of [...new Set(operands)]) {
|
|
12384
|
+
const operand = sources[dep];
|
|
12385
|
+
if (!isPlainObject8(operand)) continue;
|
|
12386
|
+
const origin = originOf(operand);
|
|
12387
|
+
if (operand.transform !== void 0 && operand.transform !== null || origin === "depends" && !isFormula(operand)) {
|
|
12388
|
+
say(
|
|
12389
|
+
pointer2,
|
|
12390
|
+
`source "${name}"'s formula reads "${dep}", which the app computes in its own code${named5(operand.transform) ? ` (${operand.transform})` : ""}; a formula's operands are FinanceOS table reads or metrics (from.financeos), or other formulas over them, so its figure never comes from app arithmetic`
|
|
12391
|
+
);
|
|
12392
|
+
continue;
|
|
12393
|
+
}
|
|
12394
|
+
if (origin === "call") {
|
|
12395
|
+
say(pointer2, `source "${name}"'s formula reads "${dep}", a call whose answer has no rows behind it to open; read the table or metric with from.financeos instead`);
|
|
12396
|
+
continue;
|
|
12397
|
+
}
|
|
12398
|
+
if (origin === "table") {
|
|
12399
|
+
say(
|
|
12400
|
+
pointer2,
|
|
12401
|
+
`source "${name}"'s formula reads "${dep}", one of the app's own tables; a formula's operands are FinanceOS table reads or metrics (from.financeos), or other formulas over them \u2014 a figure the page needs from app data is no formula`
|
|
12402
|
+
);
|
|
12403
|
+
continue;
|
|
12404
|
+
}
|
|
12405
|
+
const theirs = operand.shape ?? "rows";
|
|
12406
|
+
if (!allowed.includes(theirs)) {
|
|
12407
|
+
say(
|
|
12408
|
+
pointer2,
|
|
12409
|
+
`source "${name}" is a ${shape} formula, and its operand "${dep}" answers ${JSON.stringify(theirs)}; a ${shape} formula's operands answer ${allowed.map((one) => JSON.stringify(one)).join(" or ")} \u2014 give "${dep}" that shape`
|
|
12410
|
+
);
|
|
12411
|
+
continue;
|
|
12412
|
+
}
|
|
12413
|
+
if (theirs === shape) shaped = true;
|
|
12414
|
+
if (theirs === "breakdown" && operand.top !== null && !cutOperands.has(dep)) {
|
|
12415
|
+
cutOperands.add(dep);
|
|
12416
|
+
say(
|
|
12417
|
+
at(dep, "top"),
|
|
12418
|
+
`source "${dep}" is an operand of the breakdown formula "${name}", which needs every member of it; state "top": null on "${dep}" (a breakdown draws its top 5 by default, and the members past it would drop out of the formula)`
|
|
12419
|
+
);
|
|
12420
|
+
}
|
|
12421
|
+
}
|
|
12422
|
+
if (!shaped && FORMULA_SHAPES.includes(shape)) {
|
|
12423
|
+
say(pointer2, `source "${name}" is a ${shape} formula with no ${shape} operand; it takes its ${shape === "series" ? "periods" : "members"} from one \u2014 add a ${shape} source to from`);
|
|
12424
|
+
}
|
|
12425
|
+
}
|
|
12426
|
+
for (const name of Object.keys(sources).sort(natural3)) {
|
|
12427
|
+
if (!isFormula(sources[name]) || !Array.isArray(sources[name].from)) continue;
|
|
12428
|
+
for (const dep of sources[name].from.filter(named5)) {
|
|
12429
|
+
if (reaches2(sources, dep, name, /* @__PURE__ */ new Set(), isFormula)) {
|
|
12430
|
+
say(at(name, "expr"), `source "${name}"'s formula reads itself, through "${dep}"; a formula's operands are read before it, so none may read it back`);
|
|
12431
|
+
break;
|
|
12432
|
+
}
|
|
12433
|
+
}
|
|
12434
|
+
}
|
|
12435
|
+
return problems;
|
|
12436
|
+
}
|
|
12437
|
+
function reaches2(sources, start, target, seen, isFormula) {
|
|
12438
|
+
if (start === target) return true;
|
|
12439
|
+
if (seen.has(start)) return false;
|
|
12440
|
+
seen.add(start);
|
|
12441
|
+
const row = sources[start];
|
|
12442
|
+
if (!isFormula(row) || !Array.isArray(row.from)) return false;
|
|
12443
|
+
return row.from.filter(named5).some((one) => reaches2(sources, one, target, seen, isFormula));
|
|
12444
|
+
}
|
|
12122
12445
|
var SOURCE_ORDERS = ["source", "declared"];
|
|
12123
12446
|
function orderProblems(name, row, at) {
|
|
12124
12447
|
const problems = [];
|
|
@@ -12698,6 +13021,7 @@ function compileApp(contract, { kit = null, runtime = RUNTIME, extra = {} } = {}
|
|
|
12698
13021
|
const sourceFindings = sourceProblems(stated2, app, handlers, extra);
|
|
12699
13022
|
problems.push(...sourceFindings.filter((one) => !one.advisory));
|
|
12700
13023
|
problems.push(...compareProblems(stated2, model));
|
|
13024
|
+
problems.push(...exprProblems(stated2));
|
|
12701
13025
|
problems.push(...paramProblems(contract, stated2));
|
|
12702
13026
|
problems.push(...themeProblems(app));
|
|
12703
13027
|
problems.push(...shellProblems(contract, typed, handlers, stated2));
|
|
@@ -13723,8 +14047,8 @@ function numberPinProblems(elements, pins) {
|
|
|
13723
14047
|
const pin = pins[module];
|
|
13724
14048
|
if (typeof pin !== "string" || drawsPlatformNumbers(pin)) continue;
|
|
13725
14049
|
const need = typeof PLATFORM_NUMBERS2[module] === "string" ? `${module}@${PLATFORM_NUMBERS2[module]}` : `a ${module} release that draws it`;
|
|
13726
|
-
const
|
|
13727
|
-
if (Array.isArray(node)) return node.forEach((child, i) =>
|
|
14050
|
+
const walk5 = (node, at) => {
|
|
14051
|
+
if (Array.isArray(node)) return node.forEach((child, i) => walk5(child, `${at}/${i}`));
|
|
13728
14052
|
if (!isPlainObject8(node)) return;
|
|
13729
14053
|
for (const [key, value] of Object.entries(node)) {
|
|
13730
14054
|
const pointer2 = `${at}/${escapePointer3(key)}`;
|
|
@@ -13735,10 +14059,10 @@ function numberPinProblems(elements, pins) {
|
|
|
13735
14059
|
});
|
|
13736
14060
|
if (key === "format" && isPlatformFormat(value)) say(`format ${JSON.stringify(value)}`);
|
|
13737
14061
|
else if (key === "negative" && value !== void 0 && value !== null) say(`negative ${JSON.stringify(value)}`);
|
|
13738
|
-
else
|
|
14062
|
+
else walk5(value, pointer2);
|
|
13739
14063
|
}
|
|
13740
14064
|
};
|
|
13741
|
-
|
|
14065
|
+
walk5(element.props ?? {}, `/elements/${escapePointer3(uid)}/props`);
|
|
13742
14066
|
}
|
|
13743
14067
|
return problems;
|
|
13744
14068
|
}
|
|
@@ -16835,7 +17159,7 @@ function checkAppSources(doc, problems) {
|
|
|
16835
17159
|
fix: "Name a declared source, or declare it."
|
|
16836
17160
|
});
|
|
16837
17161
|
}
|
|
16838
|
-
if (row["transform"] === void 0 || row["transform"] === null) {
|
|
17162
|
+
if ((row["transform"] === void 0 || row["transform"] === null) && row["expr"] === void 0) {
|
|
16839
17163
|
problems.push({
|
|
16840
17164
|
code: "transform-missing",
|
|
16841
17165
|
pointer: pointer("data", "sources", name, "transform"),
|
|
@@ -17878,7 +18202,7 @@ function checkKitRules(doc, kind, problems, renderer = "widgets") {
|
|
|
17878
18202
|
fix: "Give it a source with the brief\u2019s metric_id or table_alias, or a compute."
|
|
17879
18203
|
});
|
|
17880
18204
|
}
|
|
17881
|
-
|
|
18205
|
+
walk3(row, ["data", "widgets", id], (value, path3, key) => {
|
|
17882
18206
|
if (isObject4(value) && typeof value["$callable"] === "string") {
|
|
17883
18207
|
if (known.size > 0 && !known.has(value["$callable"])) {
|
|
17884
18208
|
problems.push({
|
|
@@ -18293,17 +18617,17 @@ function inheritedEntity2(doc, uid, entityOf2) {
|
|
|
18293
18617
|
}
|
|
18294
18618
|
return null;
|
|
18295
18619
|
}
|
|
18296
|
-
function
|
|
18620
|
+
function walk3(node, path3, visit) {
|
|
18297
18621
|
const key = typeof path3[path3.length - 1] === "string" ? path3[path3.length - 1] : null;
|
|
18298
18622
|
if (!visit(node, path3, key)) return;
|
|
18299
18623
|
if (Array.isArray(node)) {
|
|
18300
18624
|
node.forEach((item, index) => {
|
|
18301
|
-
|
|
18625
|
+
walk3(item, [...path3, index], visit);
|
|
18302
18626
|
});
|
|
18303
18627
|
return;
|
|
18304
18628
|
}
|
|
18305
18629
|
if (isObject4(node)) {
|
|
18306
|
-
for (const [name, value] of Object.entries(node))
|
|
18630
|
+
for (const [name, value] of Object.entries(node)) walk3(value, [...path3, name], visit);
|
|
18307
18631
|
}
|
|
18308
18632
|
}
|
|
18309
18633
|
function dedupe(problems) {
|
|
@@ -19600,7 +19924,7 @@ async function readDirIfPresent(path3) {
|
|
|
19600
19924
|
}
|
|
19601
19925
|
async function readUiTree(uiDir) {
|
|
19602
19926
|
const out = [];
|
|
19603
|
-
const
|
|
19927
|
+
const walk5 = async (dir, prefix) => {
|
|
19604
19928
|
for (const name of (await readDirIfPresent(dir)).sort()) {
|
|
19605
19929
|
const full = join5(dir, name);
|
|
19606
19930
|
let entry;
|
|
@@ -19610,14 +19934,14 @@ async function readUiTree(uiDir) {
|
|
|
19610
19934
|
continue;
|
|
19611
19935
|
}
|
|
19612
19936
|
if (entry.isDirectory()) {
|
|
19613
|
-
await
|
|
19937
|
+
await walk5(full, `${prefix}${name}/`);
|
|
19614
19938
|
continue;
|
|
19615
19939
|
}
|
|
19616
19940
|
if (!entry.isFile()) continue;
|
|
19617
19941
|
out.push({ path: `${prefix}${name}`, bytes: await readFile3(full) });
|
|
19618
19942
|
}
|
|
19619
19943
|
};
|
|
19620
|
-
await
|
|
19944
|
+
await walk5(uiDir, "");
|
|
19621
19945
|
return out.sort((a, b) => a.path < b.path ? -1 : 1);
|
|
19622
19946
|
}
|
|
19623
19947
|
async function readManifest(dir) {
|
|
@@ -23114,7 +23438,7 @@ function loadApp2(dir) {
|
|
|
23114
23438
|
source: readHandlerSource2(dir, file)
|
|
23115
23439
|
};
|
|
23116
23440
|
});
|
|
23117
|
-
const ui =
|
|
23441
|
+
const ui = walk4(join13(dir, UI_PREFIX3)).map((path3) => ({ path: UI_PREFIX3 + relative2(join13(dir, UI_PREFIX3), path3).split("\\").join("/"), text: readText2(path3) })).sort((a, b) => a.path < b.path ? -1 : 1);
|
|
23118
23442
|
const pages = ui.filter((file) => PAGE_PATH4.test(file.path));
|
|
23119
23443
|
const index = pages.find((file) => file.path === "ui/index.html") ?? null;
|
|
23120
23444
|
return { manifest, dataModel, handlers, handlersSource, ui, pages, index };
|
|
@@ -23143,12 +23467,12 @@ function readText2(path3) {
|
|
|
23143
23467
|
const bytes = readFileSync4(path3);
|
|
23144
23468
|
return bytes.includes(0) ? "" : bytes.toString("utf8");
|
|
23145
23469
|
}
|
|
23146
|
-
function
|
|
23470
|
+
function walk4(dir) {
|
|
23147
23471
|
if (!existsSync2(dir)) return [];
|
|
23148
23472
|
const out = [];
|
|
23149
23473
|
for (const entry of readdirSync2(dir).sort()) {
|
|
23150
23474
|
const full = join13(dir, entry);
|
|
23151
|
-
if (statSync2(full).isDirectory()) out.push(...
|
|
23475
|
+
if (statSync2(full).isDirectory()) out.push(...walk4(full));
|
|
23152
23476
|
else out.push(full);
|
|
23153
23477
|
}
|
|
23154
23478
|
return out;
|
|
@@ -23379,9 +23703,9 @@ var semver2 = (version) => String(version ?? "").split(".").map((part) => Number
|
|
|
23379
23703
|
var APP_WIDGET_KINDS5 = APP_WIDGET_KINDS4;
|
|
23380
23704
|
var PLATFORM_FINANCE4 = PLATFORM_FINANCE3;
|
|
23381
23705
|
function drawsFinanceKinds2(pin) {
|
|
23382
|
-
return
|
|
23706
|
+
return reaches3(pin, PLATFORM_FINANCE4);
|
|
23383
23707
|
}
|
|
23384
|
-
function
|
|
23708
|
+
function reaches3(pin, table) {
|
|
23385
23709
|
const at = String(pin ?? "").lastIndexOf("@");
|
|
23386
23710
|
const module = at > 0 ? pin.slice(0, at) : String(pin ?? "");
|
|
23387
23711
|
const first = table[module];
|
|
@@ -23562,7 +23886,7 @@ var TERMINATORS2 = /* @__PURE__ */ new Set([",", "]", "}", ")", ":", ";"]);
|
|
|
23562
23886
|
var OPENERS3 = "([{";
|
|
23563
23887
|
var CLOSERS3 = ")]}";
|
|
23564
23888
|
var STRING_PREFIX2 = /^[rRbBuUfF]{0,3}$/;
|
|
23565
|
-
function
|
|
23889
|
+
function tokenize3(src, d) {
|
|
23566
23890
|
const tokens = [];
|
|
23567
23891
|
let i = 0;
|
|
23568
23892
|
let line2 = 1;
|
|
@@ -23720,7 +24044,7 @@ var Literals2 = class {
|
|
|
23720
24044
|
constructor(src, dialect = "py") {
|
|
23721
24045
|
this.src = src;
|
|
23722
24046
|
this.d = DIALECTS2[dialect] ?? PY2;
|
|
23723
|
-
this.tokens =
|
|
24047
|
+
this.tokens = tokenize3(src, this.d);
|
|
23724
24048
|
this.consts = /* @__PURE__ */ new Map();
|
|
23725
24049
|
this.defs = /* @__PURE__ */ new Set();
|
|
23726
24050
|
this.cache = /* @__PURE__ */ new Map();
|
|
@@ -24794,15 +25118,15 @@ function inputTypeOf2(node) {
|
|
|
24794
25118
|
}
|
|
24795
25119
|
function optionsOf2(node, text3, tree) {
|
|
24796
25120
|
const options = [];
|
|
24797
|
-
const
|
|
25121
|
+
const walk5 = (index) => {
|
|
24798
25122
|
for (const child of tree.nodes[index].children) {
|
|
24799
25123
|
const candidate = tree.nodes[child];
|
|
24800
25124
|
if (candidate.tag === "option") {
|
|
24801
25125
|
if (!("disabled" in candidate.attrs)) options.push(candidate.attrs.value ?? textOf2(text3, candidate));
|
|
24802
|
-
} else
|
|
25126
|
+
} else walk5(child);
|
|
24803
25127
|
}
|
|
24804
25128
|
};
|
|
24805
|
-
|
|
25129
|
+
walk5(node.index);
|
|
24806
25130
|
return options;
|
|
24807
25131
|
}
|
|
24808
25132
|
function splitList2(value) {
|
|
@@ -24821,14 +25145,14 @@ function textOf2(text3, node) {
|
|
|
24821
25145
|
function slotsOf2(node, tree) {
|
|
24822
25146
|
if (!(WIDGET_ATTR2 in node.attrs)) return {};
|
|
24823
25147
|
const slots = {};
|
|
24824
|
-
const
|
|
25148
|
+
const walk5 = (index) => {
|
|
24825
25149
|
for (const child of tree.nodes[index].children) {
|
|
24826
25150
|
const kind = classify2(tree.nodes[child]);
|
|
24827
25151
|
if (kind?.type === "slot") slots[kind.role] = kind.role === "body" ? classesOf3(tree.nodes[child])[0] ?? true : true;
|
|
24828
|
-
else
|
|
25152
|
+
else walk5(child);
|
|
24829
25153
|
}
|
|
24830
25154
|
};
|
|
24831
|
-
|
|
25155
|
+
walk5(node.index);
|
|
24832
25156
|
return slots;
|
|
24833
25157
|
}
|
|
24834
25158
|
function bindingOf2(entry, node, spec) {
|
|
@@ -25549,15 +25873,15 @@ function hasCycle2(tables) {
|
|
|
25549
25873
|
}
|
|
25550
25874
|
}
|
|
25551
25875
|
const state = /* @__PURE__ */ new Map();
|
|
25552
|
-
const
|
|
25876
|
+
const walk5 = (name) => {
|
|
25553
25877
|
if (state.get(name) === "done") return false;
|
|
25554
25878
|
if (state.get(name) === "open") return true;
|
|
25555
25879
|
state.set(name, "open");
|
|
25556
|
-
for (const next of graph.get(name) ?? []) if (
|
|
25880
|
+
for (const next of graph.get(name) ?? []) if (walk5(next)) return true;
|
|
25557
25881
|
state.set(name, "done");
|
|
25558
25882
|
return false;
|
|
25559
25883
|
};
|
|
25560
|
-
return [...graph.keys()].some((name) =>
|
|
25884
|
+
return [...graph.keys()].some((name) => walk5(name));
|
|
25561
25885
|
}
|
|
25562
25886
|
function byProblem3(a, b) {
|
|
25563
25887
|
return natural5(a.code, b.code) || natural5(a.where ?? "", b.where ?? "") || natural5(a.message, b.message);
|
|
@@ -26117,16 +26441,16 @@ function payloadProducers2(contract, { moduleSource = () => void 0, pageSource =
|
|
|
26117
26441
|
};
|
|
26118
26442
|
const fieldsUnder = (uid) => {
|
|
26119
26443
|
const fields = [];
|
|
26120
|
-
const
|
|
26444
|
+
const walk5 = (at) => {
|
|
26121
26445
|
for (const child of children.get(at) ?? []) {
|
|
26122
26446
|
const element = elements[child];
|
|
26123
26447
|
const field = fieldOf2(element);
|
|
26124
26448
|
if (field !== null && !fields.includes(field)) fields.push(field);
|
|
26125
26449
|
if (element?.type === "form" || element?.type === "dialog") continue;
|
|
26126
|
-
|
|
26450
|
+
walk5(child);
|
|
26127
26451
|
}
|
|
26128
26452
|
};
|
|
26129
|
-
|
|
26453
|
+
walk5(uid);
|
|
26130
26454
|
return fields;
|
|
26131
26455
|
};
|
|
26132
26456
|
for (const uid of Object.keys(elements).sort()) {
|
|
@@ -27001,20 +27325,20 @@ function untitledCards2(contract) {
|
|
|
27001
27325
|
if (page?.owned === true) continue;
|
|
27002
27326
|
const seen = /* @__PURE__ */ new Set();
|
|
27003
27327
|
const drawn2 = [];
|
|
27004
|
-
const
|
|
27328
|
+
const walk5 = (uids) => {
|
|
27005
27329
|
for (const uid of uids) {
|
|
27006
27330
|
const element = elements[uid];
|
|
27007
27331
|
if (element === void 0 || seen.has(uid)) continue;
|
|
27008
27332
|
seen.add(uid);
|
|
27009
27333
|
if (LAYOUT2.has(element.type ?? "")) {
|
|
27010
27334
|
const children = element.children?.length ? element.children : Object.keys(elements).filter((other) => elements[other]?.parent === uid);
|
|
27011
|
-
|
|
27335
|
+
walk5(children);
|
|
27012
27336
|
} else {
|
|
27013
27337
|
drawn2.push(uid);
|
|
27014
27338
|
}
|
|
27015
27339
|
}
|
|
27016
27340
|
};
|
|
27017
|
-
|
|
27341
|
+
walk5(rootsOf3(contract, page));
|
|
27018
27342
|
for (const uid of drawn2) {
|
|
27019
27343
|
const element = elements[uid];
|
|
27020
27344
|
const type = element.type ?? "";
|
|
@@ -28719,6 +29043,8 @@ var REFUSALS = /* @__PURE__ */ new Set([
|
|
|
28719
29043
|
"transform_forbidden",
|
|
28720
29044
|
// a KPI source's compare (E6)
|
|
28721
29045
|
"source_compare_field",
|
|
29046
|
+
// a source's formula, which the platform evaluates (lineage tier 3)
|
|
29047
|
+
"source_expr",
|
|
28722
29048
|
// dr-components parts (DC-4)
|
|
28723
29049
|
"part_props",
|
|
28724
29050
|
"part_source_shape",
|
|
@@ -28802,6 +29128,9 @@ var SOURCE_REFUSALS = {
|
|
|
28802
29128
|
var KPI_REFUSALS = {
|
|
28803
29129
|
source_compare_field: "a source's compare names a field its rows do not carry (where that is knowable: a FinanceOS aggregate's dimensions, measures and period column; an app table's columns), or is set on a source not shaped kpi \u2014 declare the measure beside the figure, or name one it carries"
|
|
28804
29130
|
};
|
|
29131
|
+
var EXPR_REFUSALS = {
|
|
29132
|
+
source_expr: "a source's formula (its expr) has a from that is no list, or sits beside a transform; answers a shape other than kpi, series or breakdown; states compare (its compare is its formula over its operands' compares); cannot be evaluated (it is not text, a syntax error, a function other than abs, min, max, round and if, a wrong number of arguments, round decimals other than a whole number from 0 to 6, an if whose first argument is no comparison, a comparison anywhere but if's condition, a name not in from); can be, on some path, a number it states rather than an operand's (numbers alone such as 870000, a typed if() branch such as if(actual > 0, 870000, actual), or a typed min() or max() bound such as max(actual, 0): each branch of if() and each argument of min() and max() must come from an operand, and / already gives no figure for a zero divisor, so no fallback number is needed); names an operand it never reads; reads an operand that is not a FinanceOS read or metric or a formula over them (a transform, a composed source, a call, one of the app's own tables); reads an operand of a shape it cannot take (a kpi formula takes kpis; a series or breakdown formula its own shape and kpis), or, as a series or breakdown formula, has no operand of its own shape; reads a breakdown operand that does not state top: null; or reads itself \u2014 the message names which"
|
|
29133
|
+
};
|
|
28805
29134
|
var TRANSFORM_REFUSALS = {
|
|
28806
29135
|
transform_row: 'a row names a transform and is not a dataset series row ("kind": "series", "data": "<dataset>", and no source, compute, depends, drill, window or by), or its dataset reads with the period pushed down (a period other than "all")',
|
|
28807
29136
|
transform_missing: "a row or source names a transform not spelled transforms/<name>.py, or that file is not beside contract.json",
|
|
@@ -29908,6 +30237,8 @@ function registerCompileCommand(parent) {
|
|
|
29908
30237
|
...Object.entries(SOURCE_REFUSALS).map(([code, what]) => ` ${code.padEnd(22)}${what}`),
|
|
29909
30238
|
"A KPI source (shape kpi: its period comes from the rows; compare sets the figure against another measure of the same row) is refused with:",
|
|
29910
30239
|
...Object.entries(KPI_REFUSALS).map(([code, what]) => ` ${code.padEnd(22)}${what}`),
|
|
30240
|
+
"A source that states its figure as a formula (expr over from, evaluated by the platform) is refused with:",
|
|
30241
|
+
...Object.entries(EXPR_REFUSALS).map(([code, what]) => ` ${code.padEnd(22)}${what}`),
|
|
29911
30242
|
"",
|
|
29912
30243
|
"A transform (a dashboard dataset series row's transforms/<name>.py, or an app source's) is refused with:",
|
|
29913
30244
|
...Object.entries(TRANSFORM_REFUSALS).map(([code, what]) => ` ${code.padEnd(22)}${what}`)
|
|
@@ -31612,19 +31943,19 @@ function figuresIn(text3) {
|
|
|
31612
31943
|
}
|
|
31613
31944
|
function seriesNumbers(data) {
|
|
31614
31945
|
const out = [];
|
|
31615
|
-
const
|
|
31946
|
+
const walk5 = (one) => {
|
|
31616
31947
|
if (Array.isArray(one)) {
|
|
31617
|
-
for (const item of one)
|
|
31948
|
+
for (const item of one) walk5(item);
|
|
31618
31949
|
return;
|
|
31619
31950
|
}
|
|
31620
31951
|
if (typeof one === "object" && one !== null) {
|
|
31621
|
-
|
|
31952
|
+
walk5(one.value);
|
|
31622
31953
|
return;
|
|
31623
31954
|
}
|
|
31624
31955
|
const n = numberOf(one);
|
|
31625
31956
|
if (n !== null) out.push(n);
|
|
31626
31957
|
};
|
|
31627
|
-
|
|
31958
|
+
walk5(data);
|
|
31628
31959
|
return out;
|
|
31629
31960
|
}
|
|
31630
31961
|
var INPUT_TAGS = /* @__PURE__ */ new Set(["input", "select", "textarea"]);
|