yaml-flow 3.1.0 → 4.0.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 +81 -20
- package/board-live-cards-cli.js +37 -0
- package/browser/card-compute.js +132 -431
- package/browser/live-cards.js +41 -27
- package/browser/live-cards.schema.json +59 -77
- package/dist/card-compute/index.cjs +135 -415
- package/dist/card-compute/index.cjs.map +1 -1
- package/dist/card-compute/index.d.cts +52 -49
- package/dist/card-compute/index.d.ts +52 -49
- package/dist/card-compute/index.js +134 -415
- package/dist/card-compute/index.js.map +1 -1
- package/dist/cli/board-live-cards-cli.cjs +2379 -0
- package/dist/cli/board-live-cards-cli.cjs.map +1 -0
- package/dist/cli/board-live-cards-cli.d.cts +213 -0
- package/dist/cli/board-live-cards-cli.d.ts +213 -0
- package/dist/cli/board-live-cards-cli.js +2332 -0
- package/dist/cli/board-live-cards-cli.js.map +1 -0
- package/dist/{constants-B2zqu10b.d.ts → constants-DuzE5n03.d.ts} +2 -2
- package/dist/{constants-DJZU1pwJ.d.cts → constants-ozjf1Ejw.d.cts} +2 -2
- package/dist/continuous-event-graph/index.cjs +201 -448
- package/dist/continuous-event-graph/index.cjs.map +1 -1
- package/dist/continuous-event-graph/index.d.cts +16 -340
- package/dist/continuous-event-graph/index.d.ts +16 -340
- package/dist/continuous-event-graph/index.js +198 -448
- package/dist/continuous-event-graph/index.js.map +1 -1
- package/dist/event-graph/index.cjs +4 -4
- package/dist/event-graph/index.cjs.map +1 -1
- package/dist/event-graph/index.d.cts +5 -5
- package/dist/event-graph/index.d.ts +5 -5
- package/dist/event-graph/index.js +4 -4
- package/dist/event-graph/index.js.map +1 -1
- package/dist/index.cjs +278 -533
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +8 -7
- package/dist/index.d.ts +8 -7
- package/dist/index.js +278 -533
- package/dist/index.js.map +1 -1
- package/dist/inference/index.cjs +138 -19
- package/dist/inference/index.cjs.map +1 -1
- package/dist/inference/index.d.cts +2 -2
- package/dist/inference/index.d.ts +2 -2
- package/dist/inference/index.js +138 -19
- package/dist/inference/index.js.map +1 -1
- package/dist/journal-BJDjWb5Q.d.cts +343 -0
- package/dist/journal-B_2JnBMF.d.ts +343 -0
- package/dist/step-machine/index.cjs +18 -1
- package/dist/step-machine/index.cjs.map +1 -1
- package/dist/step-machine/index.d.cts +2 -2
- package/dist/step-machine/index.d.ts +2 -2
- package/dist/step-machine/index.js +18 -1
- package/dist/step-machine/index.js.map +1 -1
- package/dist/stores/file.d.cts +1 -1
- package/dist/stores/file.d.ts +1 -1
- package/dist/stores/index.d.cts +1 -1
- package/dist/stores/index.d.ts +1 -1
- package/dist/stores/localStorage.d.cts +1 -1
- package/dist/stores/localStorage.d.ts +1 -1
- package/dist/stores/memory.d.cts +1 -1
- package/dist/stores/memory.d.ts +1 -1
- package/dist/{types-BwvgvlOO.d.cts → types-BzLD8bjb.d.cts} +1 -1
- package/dist/{types-ClRA8hzC.d.ts → types-C2eJ7DAV.d.ts} +1 -1
- package/dist/{types-DEj7OakX.d.cts → types-CMFSIjpc.d.cts} +39 -4
- package/dist/{types-DEj7OakX.d.ts → types-CMFSIjpc.d.ts} +39 -4
- package/dist/{types-FZ_eyErS.d.cts → types-ycun84cq.d.cts} +1 -0
- package/dist/{types-FZ_eyErS.d.ts → types-ycun84cq.d.ts} +1 -0
- package/dist/{validate-DEZ2Ymdb.d.ts → validate-DJQTQ6bP.d.ts} +1 -1
- package/dist/{validate-DqKTZg_o.d.cts → validate-ke92Cleg.d.cts} +1 -1
- package/examples/browser/boards/portfolio-tracker/cards/holdings-table.json +22 -0
- package/examples/browser/boards/portfolio-tracker/cards/portfolio-form.json +16 -0
- package/examples/browser/boards/portfolio-tracker/cards/portfolio-value.json +15 -0
- package/examples/browser/boards/portfolio-tracker/cards/price-fetch.json +15 -0
- package/examples/browser/boards/portfolio-tracker/fetch-prices.js +43 -0
- package/examples/browser/boards/portfolio-tracker/portfolio-tracker.bat +7 -0
- package/examples/browser/boards/portfolio-tracker/portfolio-tracker.js +189 -0
- package/examples/browser/livecards-browser/index.html +688 -0
- package/examples/browser/step-machine-browser/index.html +367 -0
- package/examples/cli/step-machine-cli/portfolio-tracker/cards/holdings-table.json +22 -0
- package/examples/cli/step-machine-cli/portfolio-tracker/cards/portfolio-form.json +43 -0
- package/examples/cli/step-machine-cli/portfolio-tracker/cards/portfolio-value.json +15 -0
- package/examples/cli/step-machine-cli/portfolio-tracker/cards/price-fetch.json +15 -0
- package/examples/cli/step-machine-cli/portfolio-tracker/fetch-prices.js +48 -0
- package/examples/cli/step-machine-cli/portfolio-tracker/handlers/_board-cli.js +58 -0
- package/examples/cli/step-machine-cli/portfolio-tracker/handlers/add-cards-cli.js +27 -0
- package/examples/cli/step-machine-cli/portfolio-tracker/handlers/init-board-cli.js +25 -0
- package/examples/cli/step-machine-cli/portfolio-tracker/handlers/reset-board-dir-cli.js +29 -0
- package/examples/cli/step-machine-cli/portfolio-tracker/handlers/retrigger-cli.js +27 -0
- package/examples/cli/step-machine-cli/portfolio-tracker/handlers/status-cli.js +25 -0
- package/examples/cli/step-machine-cli/portfolio-tracker/handlers/update-holdings-cli.js +37 -0
- package/examples/cli/step-machine-cli/portfolio-tracker/handlers/wait-completed-cli.js +53 -0
- package/examples/cli/step-machine-cli/portfolio-tracker/handlers/write-prices-cli.js +35 -0
- package/examples/cli/step-machine-cli/portfolio-tracker/portfolio-tracker.flow.yaml +227 -0
- package/examples/cli/step-machine-cli/portfolio-tracker/portfolio-tracker.input.json +38 -0
- package/examples/cli/step-machine-cli/portfolio-tracker/run-portfolio-tracker.bat +29 -0
- package/examples/cli/step-machine-demo/jsonata-init-board-cli.js +36 -0
- package/examples/cli/step-machine-demo/jsonata-init-board.flow.yaml +30 -0
- package/examples/cli/step-machine-demo/one-step-cli-only.flow.yaml +19 -0
- package/examples/cli/step-machine-demo/step-cli-echo-y.js +15 -0
- package/examples/cli/step-machine-demo/step2-double-cli.js +39 -0
- package/examples/cli/step-machine-demo/two-step-math-handlers.js +32 -0
- package/examples/cli/step-machine-demo/two-step-math.flow.yaml +31 -0
- package/examples/cli/step-machine-demo/two-step-mixed-handlers.js +24 -0
- package/examples/cli/step-machine-demo/two-step-mixed.flow.yaml +35 -0
- package/examples/index.html +792 -0
- package/examples/ingest.js +733 -0
- package/examples/npm-libs/batch/batch-step-machine.ts +121 -0
- package/examples/npm-libs/continuous-event-graph/live-cards-board.ts +215 -0
- package/examples/npm-libs/continuous-event-graph/live-portfolio-dashboard.ts +555 -0
- package/examples/npm-libs/continuous-event-graph/portfolio-tracker.ts +287 -0
- package/examples/npm-libs/continuous-event-graph/reactive-monitoring.ts +265 -0
- package/examples/npm-libs/continuous-event-graph/reactive-pipeline.ts +168 -0
- package/examples/npm-libs/continuous-event-graph/soc-incident-board.ts +287 -0
- package/examples/npm-libs/continuous-event-graph/stock-dashboard.ts +229 -0
- package/examples/npm-libs/event-graph/ci-cd-pipeline.ts +243 -0
- package/examples/npm-libs/event-graph/executor-diamond.ts +165 -0
- package/examples/npm-libs/event-graph/executor-pipeline.ts +161 -0
- package/examples/npm-libs/event-graph/research-pipeline.ts +137 -0
- package/examples/npm-libs/flows/ai-conversation.yaml +116 -0
- package/examples/npm-libs/flows/order-processing.yaml +143 -0
- package/examples/npm-libs/flows/simple-greeting.yaml +54 -0
- package/examples/npm-libs/graph-of-graphs/multi-stage-etl.ts +307 -0
- package/examples/npm-libs/graph-of-graphs/url-processing-pipeline.ts +254 -0
- package/examples/npm-libs/inference/azure-deployment.ts +149 -0
- package/examples/npm-libs/inference/copilot-cli.ts +138 -0
- package/examples/npm-libs/inference/data-pipeline.ts +145 -0
- package/examples/npm-libs/inference/pluggable-adapters.ts +254 -0
- package/examples/npm-libs/node/ai-conversation.ts +195 -0
- package/examples/npm-libs/node/simple-greeting.ts +101 -0
- package/examples/step-machine-cli/portfolio-tracker/cards/holdings-table.json +22 -0
- package/examples/step-machine-cli/portfolio-tracker/cards/portfolio-form.json +43 -0
- package/examples/step-machine-cli/portfolio-tracker/cards/portfolio-value.json +15 -0
- package/examples/step-machine-cli/portfolio-tracker/cards/price-fetch.json +15 -0
- package/examples/step-machine-cli/portfolio-tracker/fetch-prices.js +48 -0
- package/examples/step-machine-cli/portfolio-tracker/handlers/_board-cli.js +58 -0
- package/examples/step-machine-cli/portfolio-tracker/handlers/add-cards-cli.js +27 -0
- package/examples/step-machine-cli/portfolio-tracker/handlers/init-board-cli.js +25 -0
- package/examples/step-machine-cli/portfolio-tracker/handlers/reset-board-dir-cli.js +29 -0
- package/examples/step-machine-cli/portfolio-tracker/handlers/retrigger-cli.js +27 -0
- package/examples/step-machine-cli/portfolio-tracker/handlers/status-cli.js +25 -0
- package/examples/step-machine-cli/portfolio-tracker/handlers/update-holdings-cli.js +37 -0
- package/examples/step-machine-cli/portfolio-tracker/handlers/wait-completed-cli.js +53 -0
- package/examples/step-machine-cli/portfolio-tracker/handlers/write-prices-cli.js +35 -0
- package/examples/step-machine-cli/portfolio-tracker/portfolio-tracker.flow.yaml +227 -0
- package/examples/step-machine-cli/portfolio-tracker/portfolio-tracker.input.json +38 -0
- package/examples/step-machine-cli/portfolio-tracker/run-portfolio-tracker.bat +29 -0
- package/package.json +14 -2
- package/schema/board-status.schema.json +118 -0
- package/schema/flow.schema.json +5 -0
- package/schema/live-cards.schema.json +59 -77
- package/step-machine-cli.js +674 -0
package/browser/card-compute.js
CHANGED
|
@@ -1,29 +1,28 @@
|
|
|
1
|
-
//
|
|
1
|
+
// card-compute.js — JSONata-powered compute engine for LiveCards (browser build)
|
|
2
2
|
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
3
|
+
// Requires JSONata to be loaded first:
|
|
4
|
+
// <script src="https://cdn.jsdelivr.net/npm/jsonata/jsonata.min.js"></script>
|
|
5
5
|
//
|
|
6
|
-
// API:
|
|
7
|
-
// CardCompute.run(node)
|
|
8
|
-
// CardCompute.eval(expr, node)
|
|
9
|
-
// CardCompute.resolve(node, path)
|
|
10
|
-
// CardCompute.
|
|
11
|
-
// CardCompute.functions → read-only map of all registered functions
|
|
6
|
+
// API (all async where noted):
|
|
7
|
+
// CardCompute.run(node, options) → Promise<node> — eval all compute steps → computed_values
|
|
8
|
+
// CardCompute.eval(expr, node) → Promise<value> — eval single JSONata expression
|
|
9
|
+
// CardCompute.resolve(node, path) → value — sync deep-get "state.foo" or "sources.foo"
|
|
10
|
+
// CardCompute.validate(node) → { ok, errors } — sync structural validator
|
|
12
11
|
//
|
|
13
|
-
// Compute
|
|
14
|
-
// {
|
|
15
|
-
//
|
|
16
|
-
//
|
|
17
|
-
//
|
|
18
|
-
//
|
|
12
|
+
// Compute steps shape: { bindTo: string, expr: string }
|
|
13
|
+
// expr is a JSONata expression evaluated against { state, requires, sources, computed_values }
|
|
14
|
+
// computed_values and _sourcesData are ephemeral — reset on each run(), never persisted.
|
|
15
|
+
//
|
|
16
|
+
// Sequential steps: later steps see earlier results via computed_values.*
|
|
17
|
+
// options.sourcesData: pre-loaded { [bindTo]: data } map for the sources.* namespace
|
|
19
18
|
|
|
20
19
|
(function (root, factory) {
|
|
21
20
|
if (typeof module === 'object' && module.exports) {
|
|
22
|
-
module.exports = factory();
|
|
21
|
+
module.exports = factory(); // Node / CommonJS
|
|
23
22
|
} else if (typeof define === 'function' && define.amd) {
|
|
24
|
-
define(factory);
|
|
23
|
+
define(factory); // AMD
|
|
25
24
|
} else {
|
|
26
|
-
root.CardCompute = factory();
|
|
25
|
+
root.CardCompute = factory(); // Browser global
|
|
27
26
|
}
|
|
28
27
|
}(typeof globalThis !== 'undefined' ? globalThis : this, function () {
|
|
29
28
|
'use strict';
|
|
@@ -33,10 +32,10 @@
|
|
|
33
32
|
// ===========================================================================
|
|
34
33
|
|
|
35
34
|
function _deepGet(obj, path) {
|
|
36
|
-
if (!path ||
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
for (
|
|
35
|
+
if (!path || obj == null) return undefined;
|
|
36
|
+
var parts = path.split('.');
|
|
37
|
+
var cur = obj;
|
|
38
|
+
for (var i = 0; i < parts.length; i++) {
|
|
40
39
|
if (cur == null) return undefined;
|
|
41
40
|
cur = cur[parts[i]];
|
|
42
41
|
}
|
|
@@ -44,414 +43,103 @@
|
|
|
44
43
|
}
|
|
45
44
|
|
|
46
45
|
function _deepSet(obj, path, value) {
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
for (
|
|
46
|
+
var parts = path.split('.');
|
|
47
|
+
var cur = obj;
|
|
48
|
+
for (var i = 0; i < parts.length - 1; i++) {
|
|
50
49
|
if (cur[parts[i]] == null || typeof cur[parts[i]] !== 'object') cur[parts[i]] = {};
|
|
51
50
|
cur = cur[parts[i]];
|
|
52
51
|
}
|
|
53
52
|
cur[parts[parts.length - 1]] = value;
|
|
54
53
|
}
|
|
55
54
|
|
|
56
|
-
function resolve(node, path) {
|
|
57
|
-
if (!path) return undefined;
|
|
58
|
-
return _deepGet(node, path);
|
|
59
|
-
}
|
|
60
|
-
|
|
61
55
|
// ===========================================================================
|
|
62
|
-
//
|
|
56
|
+
// run — evaluate all compute steps on a node (async, returns Promise<node>)
|
|
63
57
|
// ===========================================================================
|
|
64
58
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
// ---- Aggregates ----
|
|
68
|
-
|
|
69
|
-
_fns.sum = function (input, _eval, opts) {
|
|
70
|
-
var a = Array.isArray(input) ? input : [];
|
|
71
|
-
return opts.field
|
|
72
|
-
? a.reduce(function (s, r) { return s + (Number(r[opts.field]) || 0); }, 0)
|
|
73
|
-
: a.reduce(function (s, v) { return s + (Number(v) || 0); }, 0);
|
|
74
|
-
};
|
|
75
|
-
|
|
76
|
-
_fns.avg = function (input, _eval, opts) {
|
|
77
|
-
var s = _fns.sum(input, _eval, opts);
|
|
78
|
-
var n = Array.isArray(input) ? input.length : 1;
|
|
79
|
-
return n ? s / n : 0;
|
|
80
|
-
};
|
|
81
|
-
|
|
82
|
-
_fns.min = function (input, _eval, opts) {
|
|
83
|
-
var a = Array.isArray(input) ? input : [];
|
|
84
|
-
var vals = opts.field ? a.map(function (r) { return Number(r[opts.field]); }) : a.map(Number);
|
|
85
|
-
return vals.length ? Math.min.apply(null, vals) : 0;
|
|
86
|
-
};
|
|
87
|
-
|
|
88
|
-
_fns.max = function (input, _eval, opts) {
|
|
89
|
-
var a = Array.isArray(input) ? input : [];
|
|
90
|
-
var vals = opts.field ? a.map(function (r) { return Number(r[opts.field]); }) : a.map(Number);
|
|
91
|
-
return vals.length ? Math.max.apply(null, vals) : 0;
|
|
92
|
-
};
|
|
93
|
-
|
|
94
|
-
_fns.count = function (input) {
|
|
95
|
-
return Array.isArray(input) ? input.length : (input != null ? 1 : 0);
|
|
96
|
-
};
|
|
97
|
-
|
|
98
|
-
_fns.first = function (input) {
|
|
99
|
-
return Array.isArray(input) ? input[0] : input;
|
|
100
|
-
};
|
|
101
|
-
|
|
102
|
-
_fns.last = function (input) {
|
|
103
|
-
return Array.isArray(input) ? input[input.length - 1] : input;
|
|
104
|
-
};
|
|
105
|
-
|
|
106
|
-
// ---- Math ----
|
|
107
|
-
|
|
108
|
-
_fns.add = function (input) {
|
|
109
|
-
var a = Array.isArray(input) ? input : [];
|
|
110
|
-
return a.reduce(function (s, v) { return s + Number(v); }, 0);
|
|
111
|
-
};
|
|
112
|
-
|
|
113
|
-
_fns.sub = function (input) {
|
|
114
|
-
var a = Array.isArray(input) ? input : [];
|
|
115
|
-
return a.length >= 2 ? Number(a[0]) - Number(a[1]) : 0;
|
|
116
|
-
};
|
|
117
|
-
|
|
118
|
-
_fns.mul = function (input) {
|
|
119
|
-
var a = Array.isArray(input) ? input : [];
|
|
120
|
-
return a.reduce(function (s, v) { return s * Number(v); }, 1);
|
|
121
|
-
};
|
|
122
|
-
|
|
123
|
-
_fns.div = function (input) {
|
|
124
|
-
var a = Array.isArray(input) ? input : [];
|
|
125
|
-
return a.length >= 2 && Number(a[1]) !== 0 ? Number(a[0]) / Number(a[1]) : 0;
|
|
126
|
-
};
|
|
127
|
-
|
|
128
|
-
_fns.round = function (input, _eval, opts) {
|
|
129
|
-
var decimals = opts.decimals != null ? opts.decimals : 0;
|
|
130
|
-
var factor = Math.pow(10, decimals);
|
|
131
|
-
return Math.round(Number(input) * factor) / factor;
|
|
132
|
-
};
|
|
133
|
-
|
|
134
|
-
_fns.abs = function (input) { return Math.abs(Number(input)); };
|
|
135
|
-
|
|
136
|
-
_fns.mod = function (input) {
|
|
137
|
-
var a = Array.isArray(input) ? input : [];
|
|
138
|
-
return a.length >= 2 ? Number(a[0]) % Number(a[1]) : 0;
|
|
139
|
-
};
|
|
140
|
-
|
|
141
|
-
// ---- Compare ----
|
|
142
|
-
|
|
143
|
-
_fns.gt = function (input) { var a = Array.isArray(input) ? input : []; return a.length >= 2 && Number(a[0]) > Number(a[1]); };
|
|
144
|
-
_fns.gte = function (input) { var a = Array.isArray(input) ? input : []; return a.length >= 2 && Number(a[0]) >= Number(a[1]); };
|
|
145
|
-
_fns.lt = function (input) { var a = Array.isArray(input) ? input : []; return a.length >= 2 && Number(a[0]) < Number(a[1]); };
|
|
146
|
-
_fns.lte = function (input) { var a = Array.isArray(input) ? input : []; return a.length >= 2 && Number(a[0]) <= Number(a[1]); };
|
|
147
|
-
_fns.eq = function (input) { var a = Array.isArray(input) ? input : []; return a.length >= 2 && a[0] === a[1]; };
|
|
148
|
-
_fns.neq = function (input) { var a = Array.isArray(input) ? input : []; return a.length >= 2 && a[0] !== a[1]; };
|
|
59
|
+
function run(node, options) {
|
|
60
|
+
if (!node || !node.compute || !node.compute.length) return Promise.resolve(node);
|
|
149
61
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
62
|
+
if (!node.state) node.state = {};
|
|
63
|
+
node.computed_values = {};
|
|
64
|
+
node._sourcesData = (options && options.sourcesData) || {};
|
|
65
|
+
|
|
66
|
+
if (typeof jsonata === 'undefined') {
|
|
67
|
+
console.error('CardCompute: JSONata not loaded. Add <script src="https://cdn.jsdelivr.net/npm/jsonata/jsonata.min.js"></script> before card-compute.js');
|
|
68
|
+
return Promise.resolve(node);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
var ctx = {
|
|
72
|
+
state: node.state,
|
|
73
|
+
requires: node.requires || {},
|
|
74
|
+
sources: node._sourcesData,
|
|
75
|
+
computed_values: node.computed_values,
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
var chain = Promise.resolve();
|
|
79
|
+
|
|
80
|
+
node.compute.forEach(function (step) {
|
|
81
|
+
chain = chain.then(function () {
|
|
82
|
+
if (!step || typeof step.expr !== 'string') return;
|
|
83
|
+
return jsonata(step.expr).evaluate(ctx).then(function (val) {
|
|
84
|
+
_deepSet(node.computed_values, step.bindTo, val);
|
|
85
|
+
ctx.computed_values = node.computed_values; // subsequent steps see earlier results
|
|
86
|
+
}).catch(function (e) {
|
|
87
|
+
console.error('CardCompute.run error on "' + (node.id || '?') + '.' + step.bindTo + '":', e);
|
|
88
|
+
});
|
|
172
89
|
});
|
|
173
|
-
}
|
|
174
|
-
return t;
|
|
175
|
-
};
|
|
176
|
-
|
|
177
|
-
_fns.join = function (input, _eval, opts) {
|
|
178
|
-
var a = Array.isArray(input) ? input : [];
|
|
179
|
-
var sep = opts.separator != null ? opts.separator : ', ';
|
|
180
|
-
return a.map(function (v) { return v != null ? String(v) : ''; }).join(sep);
|
|
181
|
-
};
|
|
182
|
-
|
|
183
|
-
_fns.split = function (input, _eval, opts) {
|
|
184
|
-
var sep = opts.separator != null ? opts.separator : ',';
|
|
185
|
-
return String(input || '').split(sep).map(function (s) { return s.trim(); });
|
|
186
|
-
};
|
|
187
|
-
|
|
188
|
-
_fns.trim = function (input) { return String(input || '').trim(); };
|
|
189
|
-
|
|
190
|
-
// ---- Collection ----
|
|
191
|
-
|
|
192
|
-
_fns.pluck = function (input, _eval, opts) {
|
|
193
|
-
return Array.isArray(input) ? input.map(function (r) { return r[opts.field]; }) : [];
|
|
194
|
-
};
|
|
195
|
-
|
|
196
|
-
_fns.filter = function (input, evalFn, opts) {
|
|
197
|
-
// Handled specially in evalExpr for the where clause; fallback for simple truthy filter
|
|
198
|
-
if (!Array.isArray(input)) return [];
|
|
199
|
-
if (opts.field) return input.filter(function (r) { return !!r[opts.field]; });
|
|
200
|
-
return input.filter(Boolean);
|
|
201
|
-
};
|
|
202
|
-
|
|
203
|
-
_fns.map = function (input) {
|
|
204
|
-
// Handled specially in evalExpr for the apply clause
|
|
205
|
-
return Array.isArray(input) ? input.slice() : [];
|
|
206
|
-
};
|
|
207
|
-
|
|
208
|
-
_fns.sort = function (input, _eval, opts) {
|
|
209
|
-
var a = Array.isArray(input) ? input.slice() : [];
|
|
210
|
-
var f = opts.field;
|
|
211
|
-
var dir = opts.direction === 'desc' ? -1 : 1;
|
|
212
|
-
if (f) return a.sort(function (x, y) { return x[f] > y[f] ? dir : x[f] < y[f] ? -dir : 0; });
|
|
213
|
-
return a.sort(function (x, y) { return x > y ? dir : x < y ? -dir : 0; });
|
|
214
|
-
};
|
|
215
|
-
|
|
216
|
-
_fns.slice = function (input, _eval, opts) {
|
|
217
|
-
return Array.isArray(input) ? input.slice(opts.start || 0, opts.end) : input;
|
|
218
|
-
};
|
|
219
|
-
|
|
220
|
-
_fns.flat = function (input, _eval, opts) {
|
|
221
|
-
var depth = opts.depth != null ? opts.depth : 1;
|
|
222
|
-
return Array.isArray(input) ? input.flat(depth) : [input];
|
|
223
|
-
};
|
|
224
|
-
|
|
225
|
-
_fns.unique = function (input) {
|
|
226
|
-
if (!Array.isArray(input)) return [input];
|
|
227
|
-
// For primitives, use Set. For objects, use JSON comparison.
|
|
228
|
-
var seen = new Set();
|
|
229
|
-
return input.filter(function (v) {
|
|
230
|
-
var key = typeof v === 'object' ? JSON.stringify(v) : v;
|
|
231
|
-
if (seen.has(key)) return false;
|
|
232
|
-
seen.add(key);
|
|
233
|
-
return true;
|
|
234
|
-
});
|
|
235
|
-
};
|
|
236
|
-
|
|
237
|
-
_fns.group = function (input, _eval, opts) {
|
|
238
|
-
var a = Array.isArray(input) ? input : [];
|
|
239
|
-
var g = {};
|
|
240
|
-
a.forEach(function (r) {
|
|
241
|
-
var k = String(r[opts.field] || '');
|
|
242
|
-
if (!g[k]) g[k] = [];
|
|
243
|
-
g[k].push(r);
|
|
244
90
|
});
|
|
245
|
-
return g;
|
|
246
|
-
};
|
|
247
91
|
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
if (!input || typeof input !== 'object' || Array.isArray(input)) return [];
|
|
251
|
-
var result = [];
|
|
252
|
-
Object.keys(input).forEach(function (k) {
|
|
253
|
-
var vals = Array.isArray(input[k]) ? input[k] : [input[k]];
|
|
254
|
-
vals.forEach(function (v) { result.push({ key: k, value: v }); });
|
|
255
|
-
});
|
|
256
|
-
return result;
|
|
257
|
-
};
|
|
258
|
-
|
|
259
|
-
_fns.entries = function (input) {
|
|
260
|
-
if (!input || typeof input !== 'object' || Array.isArray(input)) return [];
|
|
261
|
-
return Object.keys(input).map(function (k) { return { key: k, value: input[k] }; });
|
|
262
|
-
};
|
|
263
|
-
|
|
264
|
-
_fns.from_entries = function (input) {
|
|
265
|
-
if (!Array.isArray(input)) return {};
|
|
266
|
-
var obj = {};
|
|
267
|
-
input.forEach(function (item) { if (item.key != null) obj[item.key] = item.value; });
|
|
268
|
-
return obj;
|
|
269
|
-
};
|
|
270
|
-
|
|
271
|
-
_fns.length = function (input) {
|
|
272
|
-
if (Array.isArray(input)) return input.length;
|
|
273
|
-
if (typeof input === 'string') return input.length;
|
|
274
|
-
if (input && typeof input === 'object') return Object.keys(input).length;
|
|
275
|
-
return 0;
|
|
276
|
-
};
|
|
277
|
-
|
|
278
|
-
// ---- Lookup ----
|
|
279
|
-
|
|
280
|
-
_fns.get = function (input, _eval, opts) {
|
|
281
|
-
return _deepGet(input, opts.field || opts.path || '');
|
|
282
|
-
};
|
|
283
|
-
|
|
284
|
-
_fns.default = function (input, _eval, opts) {
|
|
285
|
-
return input != null ? input : opts.value;
|
|
286
|
-
};
|
|
287
|
-
|
|
288
|
-
_fns.coalesce = function (input) {
|
|
289
|
-
var a = Array.isArray(input) ? input : [];
|
|
290
|
-
for (var i = 0; i < a.length; i++) { if (a[i] != null) return a[i]; }
|
|
291
|
-
return null;
|
|
292
|
-
};
|
|
293
|
-
|
|
294
|
-
// ---- Date ----
|
|
295
|
-
|
|
296
|
-
_fns.now = function () { return new Date().toISOString(); };
|
|
297
|
-
|
|
298
|
-
_fns.diff_days = function (input) {
|
|
299
|
-
var a = Array.isArray(input) ? input : [];
|
|
300
|
-
return a.length >= 2 ? Math.floor((new Date(a[0]) - new Date(a[1])) / 86400000) : 0;
|
|
301
|
-
};
|
|
302
|
-
|
|
303
|
-
_fns.format_date = function (input, _eval, opts) {
|
|
304
|
-
try {
|
|
305
|
-
var d = new Date(input);
|
|
306
|
-
if (opts.format === 'iso') return d.toISOString();
|
|
307
|
-
if (opts.format === 'date') return d.toLocaleDateString();
|
|
308
|
-
if (opts.format === 'time') return d.toLocaleTimeString();
|
|
309
|
-
return d.toLocaleDateString();
|
|
310
|
-
} catch (e) {
|
|
311
|
-
return String(input);
|
|
312
|
-
}
|
|
313
|
-
};
|
|
314
|
-
|
|
315
|
-
_fns.parse_date = function (input) {
|
|
316
|
-
try { return new Date(input).toISOString(); } catch (e) { return null; }
|
|
317
|
-
};
|
|
318
|
-
|
|
319
|
-
// ---- Type ----
|
|
320
|
-
|
|
321
|
-
_fns.to_number = function (input) { return Number(input) || 0; };
|
|
322
|
-
_fns.to_string = function (input) { return input != null ? String(input) : ''; };
|
|
323
|
-
_fns.to_bool = function (input) { return !!input; };
|
|
324
|
-
_fns.type_of = function (input) { return Array.isArray(input) ? 'array' : typeof input; };
|
|
325
|
-
_fns.is_null = function (input) { return input == null; };
|
|
326
|
-
_fns.is_empty = function (input) {
|
|
327
|
-
if (input == null) return true;
|
|
328
|
-
if (Array.isArray(input)) return input.length === 0;
|
|
329
|
-
if (typeof input === 'string') return input.length === 0;
|
|
330
|
-
if (typeof input === 'object') return Object.keys(input).length === 0;
|
|
331
|
-
return false;
|
|
332
|
-
};
|
|
92
|
+
return chain.then(function () { return node; });
|
|
93
|
+
}
|
|
333
94
|
|
|
334
95
|
// ===========================================================================
|
|
335
|
-
//
|
|
96
|
+
// eval — evaluate a single JSONata expression (async, returns Promise<value>)
|
|
336
97
|
// ===========================================================================
|
|
337
98
|
|
|
338
|
-
var _customFns = {};
|
|
339
|
-
|
|
340
99
|
function evalExpr(expr, node) {
|
|
341
|
-
if (
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
input = resolve(node, input);
|
|
353
|
-
} else if (Array.isArray(input)) {
|
|
354
|
-
input = input.map(function (v) {
|
|
355
|
-
if (typeof v === 'string' && v.startsWith('state.')) return resolve(node, v);
|
|
356
|
-
if (v && typeof v === 'object' && v.fn) return evalExpr(v, node);
|
|
357
|
-
return v;
|
|
358
|
-
});
|
|
359
|
-
} else if (input && typeof input === 'object' && input.fn) {
|
|
360
|
-
input = evalExpr(input, node);
|
|
361
|
-
}
|
|
362
|
-
|
|
363
|
-
// Special: if
|
|
364
|
-
if (expr.fn === 'if') {
|
|
365
|
-
var cond = evalExpr(expr.cond, node);
|
|
366
|
-
if (cond) {
|
|
367
|
-
return (expr.then && typeof expr.then === 'object' && expr.then.fn) ? evalExpr(expr.then, node) : expr.then;
|
|
368
|
-
} else {
|
|
369
|
-
return (expr.else && typeof expr.else === 'object' && expr.else.fn) ? evalExpr(expr.else, node) : expr.else;
|
|
370
|
-
}
|
|
371
|
-
}
|
|
372
|
-
|
|
373
|
-
// Special: filter with where clause
|
|
374
|
-
if (expr.fn === 'filter' && Array.isArray(input) && expr.where) {
|
|
375
|
-
return input.filter(function (item) {
|
|
376
|
-
var tmp = { state: Object.assign({}, node.state, { $: item }) };
|
|
377
|
-
return evalExpr(expr.where, tmp);
|
|
378
|
-
});
|
|
379
|
-
}
|
|
380
|
-
|
|
381
|
-
// Special: map with apply clause
|
|
382
|
-
if (expr.fn === 'map' && Array.isArray(input) && expr.apply) {
|
|
383
|
-
return input.map(function (item) {
|
|
384
|
-
var tmp = { state: Object.assign({}, node.state, { $: item }) };
|
|
385
|
-
return evalExpr(expr.apply, tmp);
|
|
386
|
-
});
|
|
387
|
-
}
|
|
388
|
-
|
|
389
|
-
// Look up function
|
|
390
|
-
var fn = _customFns[expr.fn] || _fns[expr.fn];
|
|
391
|
-
if (!fn) {
|
|
392
|
-
console.warn('CardCompute: unknown function "' + expr.fn + '"');
|
|
393
|
-
return undefined;
|
|
394
|
-
}
|
|
395
|
-
|
|
396
|
-
return fn(input, evalExpr, expr);
|
|
100
|
+
if (typeof jsonata === 'undefined') {
|
|
101
|
+
console.error('CardCompute: JSONata not loaded.');
|
|
102
|
+
return Promise.resolve(undefined);
|
|
103
|
+
}
|
|
104
|
+
var ctx = {
|
|
105
|
+
state: (node && node.state) || {},
|
|
106
|
+
requires: (node && node.requires) || {},
|
|
107
|
+
sources: (node && node._sourcesData) || {},
|
|
108
|
+
computed_values: (node && node.computed_values) || {},
|
|
109
|
+
};
|
|
110
|
+
return jsonata(expr).evaluate(ctx);
|
|
397
111
|
}
|
|
398
112
|
|
|
399
113
|
// ===========================================================================
|
|
400
|
-
//
|
|
114
|
+
// resolve — synchronous deep-get from a node
|
|
401
115
|
// ===========================================================================
|
|
402
116
|
|
|
403
|
-
function
|
|
404
|
-
if (
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
var keys = Object.keys(node.compute);
|
|
408
|
-
for (var i = 0; i < keys.length; i++) {
|
|
409
|
-
var key = keys[i];
|
|
410
|
-
try {
|
|
411
|
-
var val = evalExpr(node.compute[key], node);
|
|
412
|
-
_deepSet(node.state, key, val);
|
|
413
|
-
} catch (e) {
|
|
414
|
-
console.error('CardCompute.run error on "' + (node.id || '?') + '.' + key + '":', e);
|
|
415
|
-
}
|
|
117
|
+
function resolve(node, path) {
|
|
118
|
+
if (path && path.indexOf('sources.') === 0) {
|
|
119
|
+
return _deepGet((node && node._sourcesData) || {}, path.slice('sources.'.length));
|
|
416
120
|
}
|
|
417
|
-
|
|
418
|
-
return node;
|
|
419
|
-
}
|
|
420
|
-
|
|
421
|
-
// ===========================================================================
|
|
422
|
-
// registerFunction — extend the vocabulary
|
|
423
|
-
// ===========================================================================
|
|
424
|
-
|
|
425
|
-
function registerFunction(name, fn) {
|
|
426
|
-
_customFns[name] = fn;
|
|
121
|
+
return _deepGet(node, path);
|
|
427
122
|
}
|
|
428
123
|
|
|
429
124
|
// ===========================================================================
|
|
430
|
-
// validate — lightweight structural validator
|
|
125
|
+
// validate — lightweight structural validator (sync)
|
|
431
126
|
// ===========================================================================
|
|
432
127
|
|
|
433
128
|
var VALID_ELEMENT_KINDS = ['metric','table','chart','form','filter','list','notes','todo','alert','narrative','badge','text','markdown','custom'];
|
|
434
|
-
var VALID_SOURCE_KINDS = ['api','websocket','static','llm'];
|
|
435
129
|
var VALID_STATUSES = ['fresh','stale','loading','error'];
|
|
436
|
-
var
|
|
437
|
-
var SOURCE_KEYS = ['id','type','meta','data','source','state','compute'];
|
|
130
|
+
var ALLOWED_KEYS = ['id','meta','requires','provides','view','state','compute','sources'];
|
|
438
131
|
|
|
439
132
|
function validateNode(node) {
|
|
440
133
|
var errors = [];
|
|
134
|
+
|
|
441
135
|
if (!node || typeof node !== 'object' || Array.isArray(node)) {
|
|
442
136
|
return { ok: false, errors: ['Node must be a non-null object'] };
|
|
443
137
|
}
|
|
444
138
|
|
|
445
139
|
if (typeof node.id !== 'string' || !node.id) errors.push('id: required, must be a non-empty string');
|
|
446
140
|
|
|
447
|
-
if (node.type !== 'card' && node.type !== 'source') {
|
|
448
|
-
errors.push('type: must be "card" or "source"');
|
|
449
|
-
return { ok: false, errors: errors };
|
|
450
|
-
}
|
|
451
|
-
|
|
452
|
-
var allowed = node.type === 'card' ? CARD_KEYS : SOURCE_KEYS;
|
|
453
141
|
Object.keys(node).forEach(function (k) {
|
|
454
|
-
if (
|
|
142
|
+
if (ALLOWED_KEYS.indexOf(k) === -1) errors.push('Unknown top-level key: "' + k + '"');
|
|
455
143
|
});
|
|
456
144
|
|
|
457
145
|
// state
|
|
@@ -463,65 +151,85 @@
|
|
|
463
151
|
|
|
464
152
|
// meta
|
|
465
153
|
if (node.meta != null) {
|
|
466
|
-
if (typeof node.meta !== 'object' || Array.isArray(node.meta))
|
|
467
|
-
|
|
154
|
+
if (typeof node.meta !== 'object' || Array.isArray(node.meta)) {
|
|
155
|
+
errors.push('meta: must be an object');
|
|
156
|
+
} else {
|
|
468
157
|
if (node.meta.title != null && typeof node.meta.title !== 'string') errors.push('meta.title: must be a string');
|
|
469
158
|
if (node.meta.tags != null && !Array.isArray(node.meta.tags)) errors.push('meta.tags: must be an array');
|
|
470
159
|
}
|
|
471
160
|
}
|
|
472
161
|
|
|
473
|
-
//
|
|
474
|
-
if (node.
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
162
|
+
// requires
|
|
163
|
+
if (node.requires != null && !Array.isArray(node.requires)) errors.push('requires: must be an array of strings');
|
|
164
|
+
|
|
165
|
+
// provides
|
|
166
|
+
if (node.provides != null) {
|
|
167
|
+
if (!Array.isArray(node.provides)) {
|
|
168
|
+
errors.push('provides: must be an array of { bindTo, src } bindings');
|
|
169
|
+
} else {
|
|
170
|
+
node.provides.forEach(function (p, i) {
|
|
171
|
+
if (!p || typeof p !== 'object' || Array.isArray(p)) {
|
|
172
|
+
errors.push('provides[' + i + ']: must be an object with bindTo and src');
|
|
173
|
+
} else {
|
|
174
|
+
if (typeof p.bindTo !== 'string' || !p.bindTo) errors.push('provides[' + i + ']: missing required "bindTo" string');
|
|
175
|
+
if (typeof p.src !== 'string' || !p.src) errors.push('provides[' + i + ']: missing required "src" string');
|
|
176
|
+
}
|
|
177
|
+
});
|
|
479
178
|
}
|
|
480
179
|
}
|
|
481
180
|
|
|
482
|
-
// compute
|
|
181
|
+
// compute — ordered array of { bindTo, expr } steps
|
|
483
182
|
if (node.compute != null) {
|
|
484
|
-
if (
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
if (!
|
|
489
|
-
|
|
490
|
-
else
|
|
183
|
+
if (!Array.isArray(node.compute)) {
|
|
184
|
+
errors.push('compute: must be an array of compute steps');
|
|
185
|
+
} else {
|
|
186
|
+
node.compute.forEach(function (step, i) {
|
|
187
|
+
if (!step || typeof step !== 'object' || Array.isArray(step)) {
|
|
188
|
+
errors.push('compute[' + i + ']: must be a compute step object');
|
|
189
|
+
} else {
|
|
190
|
+
if (typeof step.bindTo !== 'string' || !step.bindTo) errors.push('compute[' + i + ']: missing required "bindTo" property');
|
|
191
|
+
if (typeof step.expr !== 'string' || !step.expr) errors.push('compute[' + i + ']: missing required "expr" string (JSONata expression)');
|
|
192
|
+
}
|
|
491
193
|
});
|
|
492
194
|
}
|
|
493
195
|
}
|
|
494
196
|
|
|
495
|
-
//
|
|
496
|
-
if (node.
|
|
497
|
-
if (node.
|
|
498
|
-
|
|
499
|
-
|
|
197
|
+
// sources
|
|
198
|
+
if (node.sources != null) {
|
|
199
|
+
if (!Array.isArray(node.sources)) {
|
|
200
|
+
errors.push('sources: must be an array');
|
|
201
|
+
} else {
|
|
202
|
+
node.sources.forEach(function (src, i) {
|
|
203
|
+
if (!src || typeof src !== 'object' || Array.isArray(src)) errors.push('sources[' + i + ']: must be an object');
|
|
204
|
+
else if (typeof src.bindTo !== 'string' || !src.bindTo) errors.push('sources[' + i + ']: missing required "bindTo" property');
|
|
205
|
+
else {
|
|
206
|
+
if (src.outputFile != null && typeof src.outputFile !== 'string') errors.push('sources[' + i + ']: outputFile must be a string');
|
|
207
|
+
if (src.optional != null && typeof src.optional !== 'boolean') errors.push('sources[' + i + ']: optional must be a boolean');
|
|
208
|
+
}
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// view
|
|
214
|
+
if (node.view != null) {
|
|
215
|
+
if (typeof node.view !== 'object' || Array.isArray(node.view)) {
|
|
216
|
+
errors.push('view: must be an object');
|
|
500
217
|
} else {
|
|
501
218
|
if (!Array.isArray(node.view.elements) || node.view.elements.length === 0) {
|
|
502
219
|
errors.push('view.elements: required, must be a non-empty array');
|
|
503
220
|
} else {
|
|
504
221
|
node.view.elements.forEach(function (elem, i) {
|
|
505
222
|
if (!elem || typeof elem !== 'object') { errors.push('view.elements[' + i + ']: must be an object'); return; }
|
|
506
|
-
if (!elem.kind || typeof elem.kind !== 'string')
|
|
507
|
-
|
|
223
|
+
if (!elem.kind || typeof elem.kind !== 'string') {
|
|
224
|
+
errors.push('view.elements[' + i + '].kind: required, must be a string');
|
|
225
|
+
} else if (VALID_ELEMENT_KINDS.indexOf(elem.kind) === -1) {
|
|
226
|
+
errors.push('view.elements[' + i + '].kind: unknown kind "' + elem.kind + '". Valid: ' + VALID_ELEMENT_KINDS.join(', '));
|
|
227
|
+
}
|
|
508
228
|
});
|
|
509
229
|
}
|
|
510
230
|
}
|
|
511
231
|
}
|
|
512
232
|
|
|
513
|
-
// Source-specific
|
|
514
|
-
if (node.type === 'source') {
|
|
515
|
-
if (node.view != null) errors.push('Source nodes must not have "view"');
|
|
516
|
-
if (node.source == null || typeof node.source !== 'object' || Array.isArray(node.source)) {
|
|
517
|
-
errors.push('source: required for source nodes, must be an object');
|
|
518
|
-
} else {
|
|
519
|
-
if (!node.source.kind || VALID_SOURCE_KINDS.indexOf(node.source.kind) === -1) errors.push('source.kind: required, must be one of: ' + VALID_SOURCE_KINDS.join(', '));
|
|
520
|
-
if (typeof node.source.bindTo !== 'string' || !node.source.bindTo) errors.push('source.bindTo: required, must be a state path string');
|
|
521
|
-
else if (node.source.bindTo.indexOf('state.') !== 0) errors.push('source.bindTo: must start with "state."');
|
|
522
|
-
}
|
|
523
|
-
}
|
|
524
|
-
|
|
525
233
|
return { ok: errors.length === 0, errors: errors };
|
|
526
234
|
}
|
|
527
235
|
|
|
@@ -534,13 +242,6 @@
|
|
|
534
242
|
eval: evalExpr,
|
|
535
243
|
resolve: resolve,
|
|
536
244
|
validate: validateNode,
|
|
537
|
-
registerFunction: registerFunction,
|
|
538
|
-
get functions() {
|
|
539
|
-
var all = {};
|
|
540
|
-
Object.keys(_fns).forEach(function (k) { all[k] = _fns[k]; });
|
|
541
|
-
Object.keys(_customFns).forEach(function (k) { all[k] = _customFns[k]; });
|
|
542
|
-
return all;
|
|
543
|
-
}
|
|
544
245
|
};
|
|
545
246
|
|
|
546
247
|
}));
|