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.
Files changed (149) hide show
  1. package/README.md +81 -20
  2. package/board-live-cards-cli.js +37 -0
  3. package/browser/card-compute.js +132 -431
  4. package/browser/live-cards.js +41 -27
  5. package/browser/live-cards.schema.json +59 -77
  6. package/dist/card-compute/index.cjs +135 -415
  7. package/dist/card-compute/index.cjs.map +1 -1
  8. package/dist/card-compute/index.d.cts +52 -49
  9. package/dist/card-compute/index.d.ts +52 -49
  10. package/dist/card-compute/index.js +134 -415
  11. package/dist/card-compute/index.js.map +1 -1
  12. package/dist/cli/board-live-cards-cli.cjs +2379 -0
  13. package/dist/cli/board-live-cards-cli.cjs.map +1 -0
  14. package/dist/cli/board-live-cards-cli.d.cts +213 -0
  15. package/dist/cli/board-live-cards-cli.d.ts +213 -0
  16. package/dist/cli/board-live-cards-cli.js +2332 -0
  17. package/dist/cli/board-live-cards-cli.js.map +1 -0
  18. package/dist/{constants-B2zqu10b.d.ts → constants-DuzE5n03.d.ts} +2 -2
  19. package/dist/{constants-DJZU1pwJ.d.cts → constants-ozjf1Ejw.d.cts} +2 -2
  20. package/dist/continuous-event-graph/index.cjs +201 -448
  21. package/dist/continuous-event-graph/index.cjs.map +1 -1
  22. package/dist/continuous-event-graph/index.d.cts +16 -340
  23. package/dist/continuous-event-graph/index.d.ts +16 -340
  24. package/dist/continuous-event-graph/index.js +198 -448
  25. package/dist/continuous-event-graph/index.js.map +1 -1
  26. package/dist/event-graph/index.cjs +4 -4
  27. package/dist/event-graph/index.cjs.map +1 -1
  28. package/dist/event-graph/index.d.cts +5 -5
  29. package/dist/event-graph/index.d.ts +5 -5
  30. package/dist/event-graph/index.js +4 -4
  31. package/dist/event-graph/index.js.map +1 -1
  32. package/dist/index.cjs +278 -533
  33. package/dist/index.cjs.map +1 -1
  34. package/dist/index.d.cts +8 -7
  35. package/dist/index.d.ts +8 -7
  36. package/dist/index.js +278 -533
  37. package/dist/index.js.map +1 -1
  38. package/dist/inference/index.cjs +138 -19
  39. package/dist/inference/index.cjs.map +1 -1
  40. package/dist/inference/index.d.cts +2 -2
  41. package/dist/inference/index.d.ts +2 -2
  42. package/dist/inference/index.js +138 -19
  43. package/dist/inference/index.js.map +1 -1
  44. package/dist/journal-BJDjWb5Q.d.cts +343 -0
  45. package/dist/journal-B_2JnBMF.d.ts +343 -0
  46. package/dist/step-machine/index.cjs +18 -1
  47. package/dist/step-machine/index.cjs.map +1 -1
  48. package/dist/step-machine/index.d.cts +2 -2
  49. package/dist/step-machine/index.d.ts +2 -2
  50. package/dist/step-machine/index.js +18 -1
  51. package/dist/step-machine/index.js.map +1 -1
  52. package/dist/stores/file.d.cts +1 -1
  53. package/dist/stores/file.d.ts +1 -1
  54. package/dist/stores/index.d.cts +1 -1
  55. package/dist/stores/index.d.ts +1 -1
  56. package/dist/stores/localStorage.d.cts +1 -1
  57. package/dist/stores/localStorage.d.ts +1 -1
  58. package/dist/stores/memory.d.cts +1 -1
  59. package/dist/stores/memory.d.ts +1 -1
  60. package/dist/{types-BwvgvlOO.d.cts → types-BzLD8bjb.d.cts} +1 -1
  61. package/dist/{types-ClRA8hzC.d.ts → types-C2eJ7DAV.d.ts} +1 -1
  62. package/dist/{types-DEj7OakX.d.cts → types-CMFSIjpc.d.cts} +39 -4
  63. package/dist/{types-DEj7OakX.d.ts → types-CMFSIjpc.d.ts} +39 -4
  64. package/dist/{types-FZ_eyErS.d.cts → types-ycun84cq.d.cts} +1 -0
  65. package/dist/{types-FZ_eyErS.d.ts → types-ycun84cq.d.ts} +1 -0
  66. package/dist/{validate-DEZ2Ymdb.d.ts → validate-DJQTQ6bP.d.ts} +1 -1
  67. package/dist/{validate-DqKTZg_o.d.cts → validate-ke92Cleg.d.cts} +1 -1
  68. package/examples/browser/boards/portfolio-tracker/cards/holdings-table.json +22 -0
  69. package/examples/browser/boards/portfolio-tracker/cards/portfolio-form.json +16 -0
  70. package/examples/browser/boards/portfolio-tracker/cards/portfolio-value.json +15 -0
  71. package/examples/browser/boards/portfolio-tracker/cards/price-fetch.json +15 -0
  72. package/examples/browser/boards/portfolio-tracker/fetch-prices.js +43 -0
  73. package/examples/browser/boards/portfolio-tracker/portfolio-tracker.bat +7 -0
  74. package/examples/browser/boards/portfolio-tracker/portfolio-tracker.js +189 -0
  75. package/examples/browser/livecards-browser/index.html +688 -0
  76. package/examples/browser/step-machine-browser/index.html +367 -0
  77. package/examples/cli/step-machine-cli/portfolio-tracker/cards/holdings-table.json +22 -0
  78. package/examples/cli/step-machine-cli/portfolio-tracker/cards/portfolio-form.json +43 -0
  79. package/examples/cli/step-machine-cli/portfolio-tracker/cards/portfolio-value.json +15 -0
  80. package/examples/cli/step-machine-cli/portfolio-tracker/cards/price-fetch.json +15 -0
  81. package/examples/cli/step-machine-cli/portfolio-tracker/fetch-prices.js +48 -0
  82. package/examples/cli/step-machine-cli/portfolio-tracker/handlers/_board-cli.js +58 -0
  83. package/examples/cli/step-machine-cli/portfolio-tracker/handlers/add-cards-cli.js +27 -0
  84. package/examples/cli/step-machine-cli/portfolio-tracker/handlers/init-board-cli.js +25 -0
  85. package/examples/cli/step-machine-cli/portfolio-tracker/handlers/reset-board-dir-cli.js +29 -0
  86. package/examples/cli/step-machine-cli/portfolio-tracker/handlers/retrigger-cli.js +27 -0
  87. package/examples/cli/step-machine-cli/portfolio-tracker/handlers/status-cli.js +25 -0
  88. package/examples/cli/step-machine-cli/portfolio-tracker/handlers/update-holdings-cli.js +37 -0
  89. package/examples/cli/step-machine-cli/portfolio-tracker/handlers/wait-completed-cli.js +53 -0
  90. package/examples/cli/step-machine-cli/portfolio-tracker/handlers/write-prices-cli.js +35 -0
  91. package/examples/cli/step-machine-cli/portfolio-tracker/portfolio-tracker.flow.yaml +227 -0
  92. package/examples/cli/step-machine-cli/portfolio-tracker/portfolio-tracker.input.json +38 -0
  93. package/examples/cli/step-machine-cli/portfolio-tracker/run-portfolio-tracker.bat +29 -0
  94. package/examples/cli/step-machine-demo/jsonata-init-board-cli.js +36 -0
  95. package/examples/cli/step-machine-demo/jsonata-init-board.flow.yaml +30 -0
  96. package/examples/cli/step-machine-demo/one-step-cli-only.flow.yaml +19 -0
  97. package/examples/cli/step-machine-demo/step-cli-echo-y.js +15 -0
  98. package/examples/cli/step-machine-demo/step2-double-cli.js +39 -0
  99. package/examples/cli/step-machine-demo/two-step-math-handlers.js +32 -0
  100. package/examples/cli/step-machine-demo/two-step-math.flow.yaml +31 -0
  101. package/examples/cli/step-machine-demo/two-step-mixed-handlers.js +24 -0
  102. package/examples/cli/step-machine-demo/two-step-mixed.flow.yaml +35 -0
  103. package/examples/index.html +792 -0
  104. package/examples/ingest.js +733 -0
  105. package/examples/npm-libs/batch/batch-step-machine.ts +121 -0
  106. package/examples/npm-libs/continuous-event-graph/live-cards-board.ts +215 -0
  107. package/examples/npm-libs/continuous-event-graph/live-portfolio-dashboard.ts +555 -0
  108. package/examples/npm-libs/continuous-event-graph/portfolio-tracker.ts +287 -0
  109. package/examples/npm-libs/continuous-event-graph/reactive-monitoring.ts +265 -0
  110. package/examples/npm-libs/continuous-event-graph/reactive-pipeline.ts +168 -0
  111. package/examples/npm-libs/continuous-event-graph/soc-incident-board.ts +287 -0
  112. package/examples/npm-libs/continuous-event-graph/stock-dashboard.ts +229 -0
  113. package/examples/npm-libs/event-graph/ci-cd-pipeline.ts +243 -0
  114. package/examples/npm-libs/event-graph/executor-diamond.ts +165 -0
  115. package/examples/npm-libs/event-graph/executor-pipeline.ts +161 -0
  116. package/examples/npm-libs/event-graph/research-pipeline.ts +137 -0
  117. package/examples/npm-libs/flows/ai-conversation.yaml +116 -0
  118. package/examples/npm-libs/flows/order-processing.yaml +143 -0
  119. package/examples/npm-libs/flows/simple-greeting.yaml +54 -0
  120. package/examples/npm-libs/graph-of-graphs/multi-stage-etl.ts +307 -0
  121. package/examples/npm-libs/graph-of-graphs/url-processing-pipeline.ts +254 -0
  122. package/examples/npm-libs/inference/azure-deployment.ts +149 -0
  123. package/examples/npm-libs/inference/copilot-cli.ts +138 -0
  124. package/examples/npm-libs/inference/data-pipeline.ts +145 -0
  125. package/examples/npm-libs/inference/pluggable-adapters.ts +254 -0
  126. package/examples/npm-libs/node/ai-conversation.ts +195 -0
  127. package/examples/npm-libs/node/simple-greeting.ts +101 -0
  128. package/examples/step-machine-cli/portfolio-tracker/cards/holdings-table.json +22 -0
  129. package/examples/step-machine-cli/portfolio-tracker/cards/portfolio-form.json +43 -0
  130. package/examples/step-machine-cli/portfolio-tracker/cards/portfolio-value.json +15 -0
  131. package/examples/step-machine-cli/portfolio-tracker/cards/price-fetch.json +15 -0
  132. package/examples/step-machine-cli/portfolio-tracker/fetch-prices.js +48 -0
  133. package/examples/step-machine-cli/portfolio-tracker/handlers/_board-cli.js +58 -0
  134. package/examples/step-machine-cli/portfolio-tracker/handlers/add-cards-cli.js +27 -0
  135. package/examples/step-machine-cli/portfolio-tracker/handlers/init-board-cli.js +25 -0
  136. package/examples/step-machine-cli/portfolio-tracker/handlers/reset-board-dir-cli.js +29 -0
  137. package/examples/step-machine-cli/portfolio-tracker/handlers/retrigger-cli.js +27 -0
  138. package/examples/step-machine-cli/portfolio-tracker/handlers/status-cli.js +25 -0
  139. package/examples/step-machine-cli/portfolio-tracker/handlers/update-holdings-cli.js +37 -0
  140. package/examples/step-machine-cli/portfolio-tracker/handlers/wait-completed-cli.js +53 -0
  141. package/examples/step-machine-cli/portfolio-tracker/handlers/write-prices-cli.js +35 -0
  142. package/examples/step-machine-cli/portfolio-tracker/portfolio-tracker.flow.yaml +227 -0
  143. package/examples/step-machine-cli/portfolio-tracker/portfolio-tracker.input.json +38 -0
  144. package/examples/step-machine-cli/portfolio-tracker/run-portfolio-tracker.bat +29 -0
  145. package/package.json +14 -2
  146. package/schema/board-status.schema.json +118 -0
  147. package/schema/flow.schema.json +5 -0
  148. package/schema/live-cards.schema.json +59 -77
  149. package/step-machine-cli.js +674 -0
@@ -1,29 +1,28 @@
1
- // card_compute.js — Pure JSON expression evaluator for LiveCards nodes
1
+ // card-compute.js — JSONata-powered compute engine for LiveCards (browser build)
2
2
  //
3
- // Isomorphic: works in browser (global), Node.js (require), and ESM (import).
4
- // No DOM dependency. Usable on both client and server.
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) mutates node.state with computed values, returns node
8
- // CardCompute.eval(expr, node) evaluates a single compute_expr, returns value
9
- // CardCompute.resolve(node, path) → deep-get a state path like "state.foo.bar"
10
- // CardCompute.registerFunction(name, fn) add custom compute function
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 declarations (node.compute):
14
- // {
15
- // "total_value": { "fn": "sum", "input": "state.raw_quotes" },
16
- // "avg_price": { "fn": "avg", "input": "state.raw_quotes" },
17
- // "direction": { "fn": "if", "cond": { "fn": "gt", "input": ["state.latest", "state.prev"] }, "then": "up", "else": "down" }
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(); // Node / CommonJS
21
+ module.exports = factory(); // Node / CommonJS
23
22
  } else if (typeof define === 'function' && define.amd) {
24
- define(factory); // AMD
23
+ define(factory); // AMD
25
24
  } else {
26
- root.CardCompute = factory(); // Browser global
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 || !obj) return undefined;
37
- const parts = path.split('.');
38
- let cur = obj;
39
- for (let i = 0; i < parts.length; i++) {
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
- const parts = path.split('.');
48
- let cur = obj;
49
- for (let i = 0; i < parts.length - 1; i++) {
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
- // Built-in function registry
56
+ // run evaluate all compute steps on a node (async, returns Promise<node>)
63
57
  // ===========================================================================
64
58
 
65
- var _fns = {};
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
- // ---- Logic ----
151
-
152
- _fns.and = function (input) { var a = Array.isArray(input) ? input : []; return a.every(Boolean); };
153
- _fns.or = function (input) { var a = Array.isArray(input) ? input : []; return a.some(Boolean); };
154
- _fns.not = function (input) { return !input; };
155
- // "if" is handled specially in evalExpr
156
-
157
- // ---- String ----
158
-
159
- _fns.concat = function (input) {
160
- var a = Array.isArray(input) ? input : [];
161
- return a.map(function (v) { return v != null ? String(v) : ''; }).join('');
162
- };
163
-
164
- _fns.upper = function (input) { return String(input || '').toUpperCase(); };
165
- _fns.lower = function (input) { return String(input || '').toLowerCase(); };
166
-
167
- _fns.template = function (input, _eval, opts) {
168
- var t = String(opts.format || '');
169
- if (input && typeof input === 'object') {
170
- Object.keys(input).forEach(function (k) {
171
- t = t.split('{{' + k + '}}').join(input[k] != null ? String(input[k]) : '');
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
- _fns.flatten_keys = function (input) {
249
- // { a: [1,2], b: [3] } → [{ key: "a", value: 1 }, { key: "a", value: 2 }, { key: "b", value: 3 }]
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
- // Expression evaluator
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 (expr == null) return expr;
342
-
343
- // Literal values pass through
344
- if (typeof expr !== 'object' || Array.isArray(expr)) return expr;
345
-
346
- // Must have fn to be an expression
347
- if (!expr.fn) return expr;
348
-
349
- // Resolve input
350
- var input = expr.input;
351
- if (typeof input === 'string' && input.startsWith('state.')) {
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
- // runevaluate all compute declarations on a node
114
+ // resolvesynchronous deep-get from a node
401
115
  // ===========================================================================
402
116
 
403
- function run(node) {
404
- if (!node || !node.compute) return node;
405
- if (!node.state) node.state = {};
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 for LiveCards nodes
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 CARD_KEYS = ['id','type','meta','data','view','state','compute'];
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 (allowed.indexOf(k) === -1) errors.push('Unknown top-level key: "' + k + '"');
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)) errors.push('meta: must be an object');
467
- else {
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
- // data
474
- if (node.data != null) {
475
- if (typeof node.data !== 'object' || Array.isArray(node.data)) errors.push('data: must be an object');
476
- else {
477
- if (node.data.requires != null && !Array.isArray(node.data.requires)) errors.push('data.requires: must be an array');
478
- if (node.data.provides != null && (typeof node.data.provides !== 'object' || Array.isArray(node.data.provides))) errors.push('data.provides: must be an object');
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 (typeof node.compute !== 'object' || Array.isArray(node.compute)) errors.push('compute: must be an object');
485
- else {
486
- Object.keys(node.compute).forEach(function (key) {
487
- var expr = node.compute[key];
488
- if (!expr || typeof expr !== 'object' || Array.isArray(expr)) errors.push('compute.' + key + ': must be a compute expression object');
489
- else if (!expr.fn) errors.push('compute.' + key + ': missing required "fn" property');
490
- else if (!_fns[expr.fn] && !_customFns[expr.fn]) errors.push('compute.' + key + ': unknown function "' + expr.fn + '"');
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
- // Card-specific
496
- if (node.type === 'card') {
497
- if (node.source != null) errors.push('Card nodes must not have "source"');
498
- if (node.view == null || typeof node.view !== 'object' || Array.isArray(node.view)) {
499
- errors.push('view: required for card nodes, must be an object');
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') errors.push('view.elements[' + i + '].kind: required');
507
- else if (VALID_ELEMENT_KINDS.indexOf(elem.kind) === -1) errors.push('view.elements[' + i + '].kind: unknown "' + elem.kind + '"');
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
  }));