yaml-flow 5.4.2 → 7.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 (252) hide show
  1. package/board-live-cards-cli.js +6 -6
  2. package/browser/asset-integrity.json +10 -0
  3. package/browser/board-livecards-client.js +2 -0
  4. package/browser/board-livecards-client.js.map +1 -0
  5. package/browser/board-livecards-localstorage.js +10 -0
  6. package/browser/board-livecards-localstorage.js.map +1 -0
  7. package/browser/board-livegraph-engine.js +2 -1676
  8. package/browser/board-livegraph-engine.js.map +1 -1
  9. package/browser/card-compute.js +28 -28
  10. package/browser/compute-jsonata.js +5 -0
  11. package/browser/compute-jsonata.js.map +1 -0
  12. package/browser/live-cards.js +561 -129
  13. package/browser/live-cards.schema.json +418 -132
  14. package/card-store.js +37 -0
  15. package/dist/batch/index.cjs +1 -108
  16. package/dist/batch/index.cjs.map +1 -1
  17. package/dist/batch/index.js +1 -106
  18. package/dist/batch/index.js.map +1 -1
  19. package/dist/board-live-cards-lib-Bg6EvCo5.d.cts +136 -0
  20. package/dist/board-live-cards-lib-jM2uYG1v.d.ts +136 -0
  21. package/dist/board-live-cards-public-CW5074xr.d.cts +318 -0
  22. package/dist/board-live-cards-public-hnZo0mAf.d.ts +318 -0
  23. package/dist/board-livegraph-runtime/index.cjs +2 -1671
  24. package/dist/board-livegraph-runtime/index.cjs.map +1 -1
  25. package/dist/board-livegraph-runtime/index.d.cts +12 -11
  26. package/dist/board-livegraph-runtime/index.d.ts +12 -11
  27. package/dist/board-livegraph-runtime/index.js +2 -1662
  28. package/dist/board-livegraph-runtime/index.js.map +1 -1
  29. package/dist/board-livegraph-runtime/jsonata-sync.cjs +7623 -0
  30. package/dist/card-compute/index.cjs +9 -7159
  31. package/dist/card-compute/index.cjs.map +1 -1
  32. package/dist/card-compute/index.d.cts +27 -1
  33. package/dist/card-compute/index.d.ts +27 -1
  34. package/dist/card-compute/index.js +9 -7145
  35. package/dist/card-compute/index.js.map +1 -1
  36. package/dist/card-compute/jsonata-sync.cjs +7623 -0
  37. package/dist/cli/browser-api/board-live-cards-browser-adapter.cjs +3 -0
  38. package/dist/cli/browser-api/board-live-cards-browser-adapter.cjs.map +1 -0
  39. package/dist/cli/browser-api/board-live-cards-browser-adapter.d.cts +37 -0
  40. package/dist/cli/browser-api/board-live-cards-browser-adapter.d.ts +37 -0
  41. package/dist/cli/browser-api/board-live-cards-browser-adapter.js +3 -0
  42. package/dist/cli/browser-api/board-live-cards-browser-adapter.js.map +1 -0
  43. package/dist/cli/browser-api/card-store-browser-api.cjs +2 -0
  44. package/dist/cli/browser-api/card-store-browser-api.cjs.map +1 -0
  45. package/dist/cli/browser-api/card-store-browser-api.d.cts +26 -0
  46. package/dist/cli/browser-api/card-store-browser-api.d.ts +26 -0
  47. package/dist/cli/browser-api/card-store-browser-api.js +2 -0
  48. package/dist/cli/browser-api/card-store-browser-api.js.map +1 -0
  49. package/dist/cli/browser-api/jsonata-sync.cjs +7623 -0
  50. package/dist/cli/node/artifacts-store-cli.cjs +11 -0
  51. package/dist/cli/node/artifacts-store-cli.cjs.map +1 -0
  52. package/dist/cli/node/artifacts-store-cli.d.cts +8 -0
  53. package/dist/cli/node/artifacts-store-cli.d.ts +8 -0
  54. package/dist/cli/node/artifacts-store-cli.js +11 -0
  55. package/dist/cli/node/artifacts-store-cli.js.map +1 -0
  56. package/dist/cli/node/board-live-cards-cli.cjs +15 -0
  57. package/dist/cli/node/board-live-cards-cli.cjs.map +1 -0
  58. package/dist/cli/node/board-live-cards-cli.d.cts +20 -0
  59. package/dist/cli/node/board-live-cards-cli.d.ts +20 -0
  60. package/dist/cli/node/board-live-cards-cli.js +15 -0
  61. package/dist/cli/node/board-live-cards-cli.js.map +1 -0
  62. package/dist/cli/node/card-store-cli.cjs +8 -0
  63. package/dist/cli/node/card-store-cli.cjs.map +1 -0
  64. package/dist/cli/node/card-store-cli.d.cts +15 -0
  65. package/dist/cli/node/card-store-cli.d.ts +15 -0
  66. package/dist/cli/node/card-store-cli.js +8 -0
  67. package/dist/cli/node/card-store-cli.js.map +1 -0
  68. package/dist/cli/node/execution-adapter.cjs +3 -0
  69. package/dist/cli/node/execution-adapter.cjs.map +1 -0
  70. package/dist/cli/node/execution-adapter.d.cts +174 -0
  71. package/dist/cli/node/execution-adapter.d.ts +174 -0
  72. package/dist/cli/node/execution-adapter.js +3 -0
  73. package/dist/cli/node/execution-adapter.js.map +1 -0
  74. package/dist/cli/node/fs-board-adapter.cjs +14 -0
  75. package/dist/cli/node/fs-board-adapter.cjs.map +1 -0
  76. package/dist/cli/node/fs-board-adapter.d.cts +204 -0
  77. package/dist/cli/node/fs-board-adapter.d.ts +204 -0
  78. package/dist/cli/node/fs-board-adapter.js +14 -0
  79. package/dist/cli/node/fs-board-adapter.js.map +1 -0
  80. package/dist/cli/node/jsonata-sync.cjs +7623 -0
  81. package/dist/cli/node/source-cli-task-executor.cjs +11 -0
  82. package/dist/cli/node/source-cli-task-executor.cjs.map +1 -0
  83. package/dist/cli/node/source-cli-task-executor.d.cts +1 -0
  84. package/dist/cli/node/source-cli-task-executor.d.ts +1 -0
  85. package/dist/cli/node/source-cli-task-executor.js +11 -0
  86. package/dist/cli/node/source-cli-task-executor.js.map +1 -0
  87. package/dist/config/index.cjs +1 -79
  88. package/dist/config/index.cjs.map +1 -1
  89. package/dist/config/index.js +1 -76
  90. package/dist/config/index.js.map +1 -1
  91. package/dist/continuous-event-graph/index.cjs +2 -2129
  92. package/dist/continuous-event-graph/index.cjs.map +1 -1
  93. package/dist/continuous-event-graph/index.d.cts +81 -5
  94. package/dist/continuous-event-graph/index.d.ts +81 -5
  95. package/dist/continuous-event-graph/index.js +2 -2088
  96. package/dist/continuous-event-graph/index.js.map +1 -1
  97. package/dist/continuous-event-graph/jsonata-sync.cjs +7623 -0
  98. package/dist/event-graph/index.cjs +22 -8292
  99. package/dist/event-graph/index.cjs.map +1 -1
  100. package/dist/event-graph/index.js +22 -8237
  101. package/dist/event-graph/index.js.map +1 -1
  102. package/dist/execution-refs.cjs +3 -0
  103. package/dist/execution-refs.cjs.map +1 -0
  104. package/dist/execution-refs.d.cts +260 -0
  105. package/dist/execution-refs.d.ts +260 -0
  106. package/dist/execution-refs.js +3 -0
  107. package/dist/execution-refs.js.map +1 -0
  108. package/dist/index.cjs +29 -13221
  109. package/dist/index.cjs.map +1 -1
  110. package/dist/index.d.cts +2 -4
  111. package/dist/index.d.ts +2 -4
  112. package/dist/index.js +29 -13112
  113. package/dist/index.js.map +1 -1
  114. package/dist/inference/index.cjs +5 -617
  115. package/dist/inference/index.cjs.map +1 -1
  116. package/dist/inference/index.js +5 -610
  117. package/dist/inference/index.js.map +1 -1
  118. package/dist/jsonata-sync.cjs +7623 -0
  119. package/dist/{live-cards-bridge-x5XREkXm.d.cts → live-cards-bridge-BXbVTsna.d.cts} +27 -4
  120. package/dist/{live-cards-bridge-EQjytzI_.d.ts → live-cards-bridge-Ds28XR15.d.ts} +27 -4
  121. package/dist/server-runtime/index.cjs +9 -0
  122. package/dist/server-runtime/index.cjs.map +1 -0
  123. package/dist/server-runtime/index.d.cts +31 -0
  124. package/dist/server-runtime/index.d.ts +31 -0
  125. package/dist/server-runtime/index.js +9 -0
  126. package/dist/server-runtime/index.js.map +1 -0
  127. package/dist/server-runtime/jsonata-sync.cjs +7623 -0
  128. package/dist/step-machine/index.cjs +11 -7129
  129. package/dist/step-machine/index.cjs.map +1 -1
  130. package/dist/step-machine/index.js +11 -7113
  131. package/dist/step-machine/index.js.map +1 -1
  132. package/dist/step-machine-public/index.cjs +2 -0
  133. package/dist/step-machine-public/index.cjs.map +1 -0
  134. package/dist/step-machine-public/index.d.cts +159 -0
  135. package/dist/step-machine-public/index.d.ts +159 -0
  136. package/dist/step-machine-public/index.js +2 -0
  137. package/dist/step-machine-public/index.js.map +1 -0
  138. package/dist/step-machine-public/jsonata-sync.cjs +7623 -0
  139. package/dist/storage-refs.cjs +10 -0
  140. package/dist/storage-refs.cjs.map +1 -0
  141. package/dist/storage-refs.d.cts +93 -0
  142. package/dist/storage-refs.d.ts +93 -0
  143. package/dist/storage-refs.js +10 -0
  144. package/dist/storage-refs.js.map +1 -0
  145. package/dist/stores/file.cjs +1 -114
  146. package/dist/stores/file.cjs.map +1 -1
  147. package/dist/stores/file.js +1 -112
  148. package/dist/stores/file.js.map +1 -1
  149. package/dist/stores/index.cjs +1 -231
  150. package/dist/stores/index.cjs.map +1 -1
  151. package/dist/stores/index.js +1 -227
  152. package/dist/stores/index.js.map +1 -1
  153. package/dist/stores/localStorage.cjs +1 -76
  154. package/dist/stores/localStorage.cjs.map +1 -1
  155. package/dist/stores/localStorage.js +1 -74
  156. package/dist/stores/localStorage.js.map +1 -1
  157. package/dist/stores/memory.cjs +1 -47
  158. package/dist/stores/memory.cjs.map +1 -1
  159. package/dist/stores/memory.js +1 -45
  160. package/dist/stores/memory.js.map +1 -1
  161. package/dist/types-B1ZRa4aI.d.ts +147 -0
  162. package/dist/types-BxEFcVK9.d.cts +147 -0
  163. package/examples/browser/boards/portfolio-tracker/portfolio-t4.js +291 -0
  164. package/examples/browser/boards/portfolio-tracker/portfolio-tracker-fetch-prices.js +218 -0
  165. package/examples/browser/boards/portfolio-tracker/portfolio-tracker-fetch-prices.py +201 -0
  166. package/examples/browser/boards/portfolio-tracker/portfolio-tracker-http-test.js +357 -0
  167. package/examples/browser/boards/portfolio-tracker/portfolio-tracker-inference-adapter.js +25 -16
  168. package/examples/browser/boards/portfolio-tracker/portfolio-tracker-public.js +552 -0
  169. package/examples/browser/boards/portfolio-tracker/portfolio-tracker-server.js +300 -0
  170. package/examples/browser/boards/portfolio-tracker/portfolio-tracker-server.py +617 -0
  171. package/examples/browser/boards/portfolio-tracker/portfolio-tracker-sse-worker.js +48 -0
  172. package/examples/browser/boards/portfolio-tracker/portfolio-tracker.py +366 -0
  173. package/examples/cli/step-machine-cli/portfolio-tracker/--base-ref/.runtime-out +1 -0
  174. package/examples/cli/step-machine-cli/portfolio-tracker/--base-ref/board-graph.json +32 -0
  175. package/examples/cli/step-machine-cli/portfolio-tracker/handlers/_board-cli.js +70 -3
  176. package/examples/cli/step-machine-cli/portfolio-tracker/handlers/add-cards-cli.js +16 -11
  177. package/examples/cli/step-machine-cli/portfolio-tracker/handlers/init-board-cli.js +9 -8
  178. package/examples/cli/step-machine-cli/portfolio-tracker/handlers/poll-status-cli.js +49 -0
  179. package/examples/cli/step-machine-cli/portfolio-tracker/handlers/reset-board-dir-cli.js +2 -6
  180. package/examples/cli/step-machine-cli/portfolio-tracker/handlers/retrigger-cli.js +4 -8
  181. package/examples/cli/step-machine-cli/portfolio-tracker/handlers/status-cli.js +3 -7
  182. package/examples/cli/step-machine-cli/portfolio-tracker/handlers/update-holdings-cli.js +9 -8
  183. package/examples/cli/step-machine-cli/portfolio-tracker/handlers/wait-completed-cli.js +12 -17
  184. package/examples/cli/step-machine-cli/portfolio-tracker/handlers/write-prices-cli.js +2 -6
  185. package/examples/cli/step-machine-cli/portfolio-tracker/handlers-py/_board_pycli.py +107 -0
  186. package/examples/cli/step-machine-cli/portfolio-tracker/handlers-py/add-cards.py +51 -0
  187. package/examples/cli/step-machine-cli/portfolio-tracker/handlers-py/init-board.py +45 -0
  188. package/examples/cli/step-machine-cli/portfolio-tracker/handlers-py/poll-status.py +71 -0
  189. package/examples/cli/step-machine-cli/portfolio-tracker/handlers-py/reset-board-dir.py +36 -0
  190. package/examples/cli/step-machine-cli/portfolio-tracker/inline-python-demo.flow.yaml +26 -0
  191. package/examples/cli/step-machine-cli/portfolio-tracker/inline-python-handlers.py +39 -0
  192. package/examples/cli/step-machine-cli/portfolio-tracker/portfolio-tracker-pycli.flow.yaml +80 -0
  193. package/examples/cli/step-machine-cli/portfolio-tracker/portfolio-tracker.flow.yaml +36 -187
  194. package/examples/cli/step-machine-cli/portfolio-tracker/portfolio-tracker.input.json +40 -34
  195. package/examples/cli/step-machine-cli/portfolio-tracker/run-inline-python-demo-pycli.py +43 -0
  196. package/examples/cli/step-machine-cli/portfolio-tracker/run-portfolio-tracker-pycli.py +77 -0
  197. package/examples/cli/step-machine-cli/portfolio-tracker/run-portfolio-tracker.bat +1 -2
  198. package/examples/cli/step-machine-demo/jsonata-init-board-cli.js +8 -13
  199. package/examples/cli/step-machine-demo/jsonata-init-board.flow.yaml +33 -9
  200. package/examples/cli/step-machine-demo/one-step-cli-only.flow.yaml +3 -1
  201. package/examples/cli/step-machine-demo/step2-double-cli.js +6 -12
  202. package/examples/cli/step-machine-demo/two-step-math.flow.yaml +66 -4
  203. package/examples/cli/step-machine-demo/two-step-mixed.flow.yaml +13 -5
  204. package/examples/example-board/agent-instructions.md +11 -5
  205. package/examples/example-board/cards/_index.json +47 -0
  206. package/examples/example-board/cards/card-market-prices.json +33 -9
  207. package/examples/example-board/cards/card-my-identity.json +30 -6
  208. package/examples/example-board/cards/card-portfolio-action.json +24 -6
  209. package/examples/example-board/cards/card-portfolio-intelligence.json +97 -0
  210. package/examples/example-board/cards/card-portfolio-risks.json +24 -6
  211. package/examples/example-board/cards/card-portfolio-value.json +38 -10
  212. package/examples/example-board/cards/card-portfolio.json +57 -13
  213. package/examples/example-board/cards/card-rebalance-impact.json +22 -6
  214. package/examples/example-board/cards/card-rebalance-sim.json +66 -15
  215. package/examples/example-board/demo-chat-handler.js +14 -4
  216. package/examples/example-board/demo-server-config.json +1 -0
  217. package/examples/example-board/demo-server.js +366 -68
  218. package/examples/example-board/demo-shell-localstorage.html +774 -0
  219. package/examples/example-board/demo-shell-with-server.html +20 -37
  220. package/examples/example-board/demo-shell.html +5 -4
  221. package/examples/example-board/demo-task-executor.js +273 -275
  222. package/examples/index.html +0 -14
  223. package/examples/step-machine-cli/portfolio-tracker/handlers/_board-cli.js +0 -1
  224. package/examples/step-machine-cli/portfolio-tracker/run-portfolio-tracker.bat +1 -2
  225. package/package.json +46 -8
  226. package/schema/live-cards.schema.json +418 -132
  227. package/step-machine-cli.js +43 -310
  228. package/board-livecards-server-runtime.js +0 -1574
  229. package/browser/board-livecards-runtime-client.js +0 -263
  230. package/dist/cli/board-live-cards-cli.cjs +0 -10650
  231. package/dist/cli/board-live-cards-cli.cjs.map +0 -1
  232. package/dist/cli/board-live-cards-cli.d.cts +0 -179
  233. package/dist/cli/board-live-cards-cli.d.ts +0 -179
  234. package/dist/cli/board-live-cards-cli.js +0 -10598
  235. package/dist/cli/board-live-cards-cli.js.map +0 -1
  236. package/dist/journal-9HEgs7dU.d.ts +0 -28
  237. package/dist/journal-B-JCfQnh.d.cts +0 -28
  238. package/dist/schedule-Cszq9LYY.d.ts +0 -21
  239. package/dist/schedule-qWNL0RQh.d.cts +0 -21
  240. package/examples/browser/boards/portfolio-tracker/cards/holdings-table.json +0 -22
  241. package/examples/browser/boards/portfolio-tracker/cards/portfolio-form.json +0 -16
  242. package/examples/browser/boards/portfolio-tracker/cards/portfolio-risk-assessment.json +0 -28
  243. package/examples/browser/boards/portfolio-tracker/cards/portfolio-value.json +0 -15
  244. package/examples/browser/boards/portfolio-tracker/cards/price-fetch.json +0 -15
  245. package/examples/browser/boards/portfolio-tracker/cards/rebalancing-strategy.json +0 -28
  246. package/examples/browser/boards/portfolio-tracker/fetch-prices.js +0 -43
  247. package/examples/browser/boards/portfolio-tracker/portfolio-tracker-task-executor.cjs +0 -96
  248. package/examples/browser/boards/portfolio-tracker/portfolio-tracker.bat +0 -7
  249. package/examples/browser/boards/portfolio-tracker/portfolio-tracker.js +0 -351
  250. package/examples/cli/step-machine-demo/two-step-math-handlers.js +0 -32
  251. package/examples/cli/step-machine-demo/two-step-mixed-handlers.js +0 -24
  252. package/examples/example-board/demo-shell-browser.html +0 -674
@@ -0,0 +1,552 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * portfolio-tracker-public.js
4
+ *
5
+ * Identical E2E logic to portfolio-tracker.py, implemented directly against
6
+ * the yaml-flow public Node.js libraries — no CLI subprocess spawning.
7
+ *
8
+ * Imports:
9
+ * yaml-flow/board-live-cards-node — createBoardLiveCardsPublic,
10
+ * createFsBoardPlatformAdapter,
11
+ * createCardStorePublic,
12
+ * createCardStore, parseRef, serializeRef
13
+ */
14
+
15
+ import path from 'node:path';
16
+ import fs from 'node:fs';
17
+ import os from 'node:os';
18
+ import net from 'node:net';
19
+ import { fileURLToPath, pathToFileURL } from 'node:url';
20
+
21
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
22
+ const _REPO_ROOT = path.resolve(__dirname, '..', '..', '..', '..');
23
+
24
+ // ── Library imports ────────────────────────────────────────────────────────────
25
+ const _adapterPath = path.join(_REPO_ROOT, 'dist', 'cli', 'node', 'fs-board-adapter.js');
26
+ const {
27
+ createBoardLiveCardsPublic,
28
+ createBoardLiveCardsNonCorePublic,
29
+ createFsBoardPlatformAdapter,
30
+ createFsBoardNonCorePlatformAdapter,
31
+ createCardStorePublic,
32
+ createCardStore,
33
+ parseRef,
34
+ serializeRef,
35
+ } = await import(pathToFileURL(_adapterPath).href);
36
+
37
+ const FETCH_PRICES_JS = path.join(__dirname, 'portfolio-tracker-fetch-prices.js');
38
+
39
+ // ── Runtime directories ────────────────────────────────────────────────────────
40
+ const _TMP_BASE = path.join(os.tmpdir(), 'experiment-js');
41
+ const CARDSTORE_DIR = path.join(_TMP_BASE, 'cardstore');
42
+ const BOARDRUNTIME_DIR = path.join(_TMP_BASE, 'boardruntime');
43
+ const OUTPUTS_DIR = path.join(_TMP_BASE, 'outputs');
44
+
45
+ const CARDSTORE_REF = serializeRef({ kind: 'fs-path', value: CARDSTORE_DIR });
46
+ const BOARDRUNTIME_REF = serializeRef({ kind: 'fs-path', value: BOARDRUNTIME_DIR });
47
+ const OUTPUTS_REF = serializeRef({ kind: 'fs-path', value: OUTPUTS_DIR });
48
+ const NOTIFY_CHANNEL = 'yaml-flow-board-notify-portfolio-tracker-public';
49
+
50
+ // ── Card definitions ───────────────────────────────────────────────────────────
51
+ const CARD_PORTFOLIO_FORM = {
52
+ id: 'portfolio-form',
53
+ meta: { title: 'Portfolio Holdings Form' },
54
+ provides: [{ bindTo: 'holdings', ref: 'card_data.holdings' }],
55
+ card_data: { holdings: [] },
56
+ view: {
57
+ elements: [
58
+ { kind: 'table', label: 'Holdings',
59
+ data: { bind: 'card_data.holdings', columns: ['symbol', 'qty'] } }
60
+ ]
61
+ }
62
+ };
63
+
64
+ const CARD_PRICE_FETCH = {
65
+ id: 'price-fetch',
66
+ meta: { title: 'Fetch Market Prices' },
67
+ requires: ['holdings'],
68
+ provides: [{ bindTo: 'prices', ref: 'computed_values.prices' }],
69
+ card_data: {},
70
+ compute: [{
71
+ bindTo: 'prices',
72
+ expr: '$merge($map(requires.holdings, function($h){ { $h.symbol: 100 } }))'
73
+ }],
74
+ view: {
75
+ elements: [
76
+ { kind: 'table', label: 'Market Prices',
77
+ data: { bind: 'computed_values.prices' } }
78
+ ]
79
+ }
80
+ };
81
+
82
+ const CARD_HOLDINGS_TABLE = {
83
+ id: 'holdings-table',
84
+ meta: { title: 'Holdings Table' },
85
+ requires: ['holdings', 'prices'],
86
+ provides: [{ bindTo: 'table', ref: 'computed_values.table' }],
87
+ card_data: {},
88
+ compute: [{
89
+ bindTo: 'table',
90
+ expr: '{ "rows": $map(requires.holdings, function($h) { { "symbol": $h.symbol, "qty": $h.qty, "price": $lookup(requires.prices, $h.symbol), "value": $h.qty * $lookup(requires.prices, $h.symbol) } }) }'
91
+ }],
92
+ view: {
93
+ elements: [
94
+ { kind: 'table', label: 'Portfolio Positions',
95
+ data: { bind: 'computed_values.table.rows', columns: ['symbol', 'qty', 'price', 'value'] } }
96
+ ]
97
+ }
98
+ };
99
+
100
+ const CARD_PORTFOLIO_VALUE = {
101
+ id: 'portfolio-value',
102
+ meta: { title: 'Portfolio Total Value' },
103
+ requires: ['table'],
104
+ provides: [{ bindTo: 'totalValue', ref: 'computed_values.totalValue' }],
105
+ card_data: {},
106
+ compute: [
107
+ { bindTo: 'totalValue', expr: '$sum(requires.table.rows.value)' }
108
+ ],
109
+ view: {
110
+ elements: [
111
+ { kind: 'metric', label: 'Total Portfolio Value',
112
+ data: { bind: 'computed_values.totalValue' } }
113
+ ]
114
+ }
115
+ };
116
+
117
+ // ── Helpers ────────────────────────────────────────────────────────────────────
118
+ function setHoldings(card, holdings) {
119
+ return {
120
+ ...card,
121
+ card_data: {
122
+ ...card.card_data,
123
+ holdings: Object.entries(holdings).map(([symbol, qty]) => ({ symbol, qty })),
124
+ },
125
+ };
126
+ }
127
+
128
+ function assert(condition, message) {
129
+ if (!condition) {
130
+ console.error(`[ASSERT FAILED] ${message}`);
131
+ process.exit(1);
132
+ }
133
+ }
134
+
135
+ function makeBoard() {
136
+ const br = parseRef(BOARDRUNTIME_REF);
137
+ return createBoardLiveCardsPublic(br, createFsBoardPlatformAdapter(br, _REPO_ROOT, {
138
+ onWarn: console.warn,
139
+ notifyChannel: NOTIFY_CHANNEL,
140
+ }));
141
+ }
142
+
143
+ function makeNonCoreBoard() {
144
+ const br = parseRef(BOARDRUNTIME_REF);
145
+ return createBoardLiveCardsNonCorePublic(br, createFsBoardNonCorePlatformAdapter(br, _REPO_ROOT, { onWarn: console.warn }));
146
+ }
147
+
148
+ function makeCardStore() {
149
+ const ref = parseRef(CARDSTORE_REF);
150
+ const adapter = createFsBoardPlatformAdapter(ref, _REPO_ROOT, { onWarn: console.warn });
151
+ const kv = adapter.kvStorageForRef(CARDSTORE_REF);
152
+ const cardAdapterObj = {
153
+ readIndex: () => kv.read('_index'),
154
+ writeIndex: (idx) => kv.write('_index', idx),
155
+ readCard: (id) => kv.read(id),
156
+ writeCard: (id, card) => { kv.write(id, card); return id; },
157
+ cardExists: (id) => kv.read(id) !== null,
158
+ defaultCardKey: (id) => id,
159
+ };
160
+ return createCardStorePublic(createCardStore(cardAdapterObj, console.warn));
161
+ }
162
+
163
+ // ── NS — notification state class (compact log + full payload map) ───────────
164
+ class NotificationState {
165
+ constructor() {
166
+ this.log = [];
167
+ this.statusGen = 0;
168
+ this.values = {
169
+ status: null,
170
+ computedValues: {},
171
+ dataObjects: {},
172
+ cards: {},
173
+ };
174
+ }
175
+
176
+ append(event) {
177
+ const at = new Date().toISOString();
178
+ if (event.kind === 'status') {
179
+ this.log.push({ at, type: event.kind, key: 'status' });
180
+ this.values.status = event.status;
181
+ this.statusGen++;
182
+ return;
183
+ }
184
+ if (event.kind === 'computed_values') {
185
+ this.log.push({ at, type: event.kind, key: event.cardId });
186
+ this.values.computedValues[event.cardId] = event.values;
187
+ return;
188
+ }
189
+ if (event.kind === 'data_object') {
190
+ this.log.push({ at, type: event.kind, key: event.key });
191
+ this.values.dataObjects[event.key] = event.payload;
192
+ return;
193
+ }
194
+ if (event.kind === 'card_refreshed') {
195
+ this.log.push({ at, type: event.kind, key: event.cardId });
196
+ this.values.cards[event.cardId] = event.card;
197
+ }
198
+ }
199
+
200
+ latestStatus() {
201
+ return this.values.status;
202
+ }
203
+
204
+ countByType(type) {
205
+ let count = 0;
206
+ for (const n of this.log) {
207
+ if (n.type === type) count++;
208
+ }
209
+ return count;
210
+ }
211
+
212
+ summary() {
213
+ const byType = {};
214
+ const keysByType = {
215
+ computed_values: new Set(),
216
+ data_object: new Set(),
217
+ card_refreshed: new Set(),
218
+ status: new Set(),
219
+ };
220
+
221
+ for (const n of this.log) {
222
+ byType[n.type] = (byType[n.type] ?? 0) + 1;
223
+ if (n.type in keysByType) keysByType[n.type].add(n.key);
224
+ }
225
+
226
+ return {
227
+ totalNotifications: this.log.length,
228
+ byType,
229
+ keysByType: {
230
+ computed_values: [...keysByType.computed_values].sort(),
231
+ data_object: [...keysByType.data_object].sort(),
232
+ card_refreshed: [...keysByType.card_refreshed].sort(),
233
+ status: [...keysByType.status].sort(),
234
+ },
235
+ latestStatusSummary: this.values.status?.summary ?? null,
236
+ };
237
+ }
238
+ }
239
+
240
+ const NS = new NotificationState();
241
+
242
+ function appendNS(event) {
243
+ NS.append(event);
244
+ }
245
+
246
+ function namedPipePath(pipeName) {
247
+ if (process.platform === 'win32') return `\\\\.\\pipe\\${pipeName}`;
248
+ return path.join(os.tmpdir(), `${pipeName}.sock`);
249
+ }
250
+
251
+ function startPipeConsumer(pipeName) {
252
+ return new Promise((resolve, reject) => {
253
+ const pipePath = namedPipePath(pipeName);
254
+ const sockets = new Set();
255
+ if (process.platform !== 'win32' && fs.existsSync(pipePath)) {
256
+ try { fs.rmSync(pipePath, { force: true }); } catch { /* best-effort */ }
257
+ }
258
+
259
+ const server = net.createServer((socket) => {
260
+ sockets.add(socket);
261
+ socket.on('close', () => sockets.delete(socket));
262
+ let buf = '';
263
+ socket.on('data', (chunk) => {
264
+ buf += chunk.toString('utf-8');
265
+ while (true) {
266
+ const i = buf.indexOf('\n');
267
+ if (i < 0) break;
268
+ const line = buf.slice(0, i).trim();
269
+ buf = buf.slice(i + 1);
270
+ if (!line) continue;
271
+ try {
272
+ const msg = JSON.parse(line);
273
+ const n = msg?.notification ?? msg;
274
+ if (n && typeof n.kind === 'string') appendNS(n);
275
+ } catch (e) {
276
+ console.warn(`[pipe-consumer] invalid notification line: ${e instanceof Error ? e.message : String(e)}`);
277
+ }
278
+ }
279
+ });
280
+ });
281
+
282
+ server.once('error', (e) => reject(e));
283
+ server.listen(pipePath, () => resolve({ server, pipePath, sockets }));
284
+ });
285
+ }
286
+
287
+ function getNS() { return NS; }
288
+
289
+ function checkResult(result, label) {
290
+ if (result.status !== 'success') {
291
+ console.error(`[ERROR] ${label}: ${result.status} — ${result.error}`);
292
+ process.exit(1);
293
+ }
294
+ return result.data;
295
+ }
296
+
297
+ async function waitForCompleted(label, expectedCardCount, timeoutMs = 90_000, pollMs = 500) {
298
+ const deadline = Date.now() + timeoutMs;
299
+ let pollCount = 0;
300
+ const startGen = getNS().statusGen;
301
+ while (Date.now() < deadline) {
302
+ await new Promise(r => setTimeout(r, pollMs));
303
+ pollCount++;
304
+
305
+ // Only consider status updates that arrived after this wait began
306
+ if (getNS().statusGen <= startGen) {
307
+ if (pollCount % 4 === 0) {
308
+ console.log(`[${label}] poll#${pollCount} waiting for new status notification via named pipe...`);
309
+ }
310
+ continue;
311
+ }
312
+
313
+ const nsStatus = getNS().latestStatus();
314
+ if (!nsStatus) continue;
315
+
316
+ const { card_count, completed, in_progress, pending, failed } = nsStatus.summary;
317
+
318
+ if (card_count >= expectedCardCount && completed === card_count) {
319
+ console.log(`[${label}] all ${card_count} card(s) completed (via named-pipe notification).`);
320
+ return nsStatus;
321
+ }
322
+ if (pollCount % 4 === 0) {
323
+ const notDone = nsStatus.cards.filter(c => c.status !== 'completed').map(c => `${c.name}:${c.status}`);
324
+ console.log(`[${label}] poll#${pollCount} summary: ${card_count} cards, completed=${completed}, in_progress=${in_progress}, pending=${pending}, failed=${failed} | stuck: ${notDone.join(', ')}`);
325
+ }
326
+ }
327
+ console.error(`[ERROR] ${label}: timed out waiting for all cards to complete.`);
328
+ process.exit(1);
329
+ }
330
+
331
+ function sortedKeys(obj) {
332
+ if (!obj || typeof obj !== 'object') return [];
333
+ return Object.keys(obj).sort();
334
+ }
335
+
336
+ function readOutputsDataObject(key) {
337
+ const result = makeBoard().getOutputsDataObject({ params: { key } });
338
+ return result.status === 'success' ? result.data : undefined;
339
+ }
340
+
341
+ function readOutputsComputedValues(key) {
342
+ const result = makeBoard().getOutputsComputedValues({ params: { key } });
343
+ return result.status === 'success' ? result.data : undefined;
344
+ }
345
+
346
+ async function waitForPortfolioOutputs(label, expectedHoldingsBySymbol, timeoutMs = 30_000, pollMs = 300) {
347
+ const deadline = Date.now() + timeoutMs;
348
+ const expectedSymbols = Object.keys(expectedHoldingsBySymbol).sort();
349
+ let pollCount = 0;
350
+
351
+ while (Date.now() < deadline) {
352
+ await new Promise(r => setTimeout(r, pollMs));
353
+ pollCount++;
354
+
355
+ const prices = readOutputsDataObject('prices');
356
+ const holdingsTable = readOutputsComputedValues('holdings-table');
357
+ const rowsRaw = holdingsTable?.table?.rows;
358
+ if (!prices || rowsRaw === undefined || rowsRaw === null) continue;
359
+ const rows = [].concat(rowsRaw);
360
+
361
+ const priceSymbols = sortedKeys(prices);
362
+ const rowsBySymbol = Object.fromEntries(rows.map(r => [r.symbol, r.qty]));
363
+ const rowSymbols = sortedKeys(rowsBySymbol);
364
+ const hasSymbols = JSON.stringify(priceSymbols) === JSON.stringify(expectedSymbols)
365
+ && JSON.stringify(rowSymbols) === JSON.stringify(expectedSymbols);
366
+
367
+ let qtyMatches = true;
368
+ for (const sym of expectedSymbols) {
369
+ if (rowsBySymbol[sym] !== expectedHoldingsBySymbol[sym]) {
370
+ qtyMatches = false;
371
+ break;
372
+ }
373
+ }
374
+
375
+ if (hasSymbols && qtyMatches) {
376
+ return { prices, holdingsTable, rowsBySymbol };
377
+ }
378
+
379
+ if (pollCount % 5 === 0) {
380
+ console.log(`[${label}] waiting for output convergence: symbols=${JSON.stringify(priceSymbols)} rows=${JSON.stringify(rowSymbols)}`);
381
+ }
382
+ }
383
+
384
+ console.error(`[ERROR] ${label}: timed out waiting for outputs to match expected holdings.`);
385
+ process.exit(1);
386
+ }
387
+
388
+ // ── T0a — Create runtime directories ──────────────────────────────────────────
389
+ console.log('\n=== T0a: Create runtime directories ===');
390
+ if (fs.existsSync(_TMP_BASE)) {
391
+ fs.rmSync(_TMP_BASE, { recursive: true, force: true });
392
+ console.log(` cleaned: ${_TMP_BASE} (including .tmp, .card-runtime, journal)`);
393
+ }
394
+ for (const d of [CARDSTORE_DIR, BOARDRUNTIME_DIR, OUTPUTS_DIR]) {
395
+ fs.mkdirSync(d, { recursive: true });
396
+ console.log(` created: ${d}`);
397
+ }
398
+
399
+ const pipeConsumer = await startPipeConsumer(NOTIFY_CHANNEL);
400
+
401
+ // ── T0b — Init board ───────────────────────────────────────────────────────────
402
+ console.log('\n=== T0b: Init board ===');
403
+ checkResult(
404
+ makeBoard().init({
405
+ params: { cardStoreRef: CARDSTORE_REF, outputsStoreRef: OUTPUTS_REF },
406
+ body: {
407
+ 'task-executor-ref': {
408
+ meta: 'task-executor',
409
+ howToRun: 'local-node',
410
+ whatToRun: serializeRef({ kind: 'fs-path', value: FETCH_PRICES_JS }),
411
+ },
412
+ },
413
+ }),
414
+ 'init'
415
+ );
416
+ console.log(JSON.stringify({ status: 'success' }, null, 2));
417
+
418
+ // ── T0c — Validate and set all cards into card store ─────────────────────────
419
+ console.log('\n=== T0c: Validate and set all cards into card store ===');
420
+ const cardStore = makeCardStore();
421
+ for (const card of [
422
+ setHoldings(CARD_PORTFOLIO_FORM, { NVDA: 100 }),
423
+ CARD_PRICE_FETCH,
424
+ CARD_HOLDINGS_TABLE,
425
+ CARD_PORTFOLIO_VALUE,
426
+ ]) {
427
+ const vr = makeNonCoreBoard().validateTmpCard({ body: card });
428
+ if (!vr.data?.isValid) {
429
+ console.error(`[VALIDATE FAILED] card ${card.id}:`, JSON.stringify(vr.data?.issues ?? vr.error));
430
+ process.exit(1);
431
+ }
432
+ console.log(` [validate] ${card.id}: ok`);
433
+ const r = checkResult(cardStore.set({ body: card }), `card-store set ${card.id}`);
434
+ console.error(`card-store set: wrote ${r.count} card(s)`);
435
+ }
436
+
437
+ // ── T0d — Upsert cards to board ────────────────────────────────────────────────
438
+ console.log('\n=== T0d: Upsert cards to board ===');
439
+ for (const cardId of ['portfolio-form', 'price-fetch', 'holdings-table', 'portfolio-value']) {
440
+ checkResult(makeBoard().upsertCard({ params: { cardId } }), `upsertCard ${cardId}`);
441
+ console.log(JSON.stringify({ status: 'success' }, null, 2));
442
+ }
443
+
444
+ // ── T1 — Wait for all cards completed ──────────────────────────────────────────
445
+ console.log('\n=== T1: Wait for all cards completed ===');
446
+ await waitForCompleted('T1', 4);
447
+
448
+ const { prices: pricesT1, holdingsTable: htCvT1, rowsBySymbol: rowsBySymbolT1 } = await waitForPortfolioOutputs('T1', { NVDA: 100 });
449
+ assert(typeof pricesT1 === 'object' && pricesT1 !== null && Object.keys(pricesT1).length > 0,
450
+ 'T1: prices data object is empty or not an object');
451
+ assert(JSON.stringify(Object.keys(pricesT1).sort()) === JSON.stringify(['NVDA']),
452
+ `T1: expected keys {NVDA}, got ${JSON.stringify(Object.keys(pricesT1))}`);
453
+ assert(Object.values(pricesT1).every(v => typeof v === 'number'),
454
+ 'T1: all price values must be numbers');
455
+ assert(rowsBySymbolT1['NVDA'] === 100,
456
+ `T1: expected NVDA qty=100, got ${rowsBySymbolT1['NVDA']}`);
457
+ console.log('[T1] assertion passed: prices has NVDA with numeric values, NVDA qty=100.');
458
+
459
+ // ── T2a — Update holdings (GOOG added) ────────────────────────────────────────
460
+ console.log('\n=== T2a: Update holdings (GOOG added) ===');
461
+ checkResult(
462
+ makeCardStore().set({ body: setHoldings(CARD_PORTFOLIO_FORM, { NVDA: 50, GOOG: 100 }) }),
463
+ 'card-store set portfolio-form'
464
+ );
465
+ console.error('card-store set: wrote 1 card(s)');
466
+
467
+ // ── T2b — Upsert portfolio-form with restart ───────────────────────────────────
468
+ console.log('\n=== T2b: Upsert portfolio-form --restart ===');
469
+ checkResult(
470
+ makeBoard().upsertCard({ params: { cardId: 'portfolio-form', restart: 'true' } }),
471
+ 'upsertCard portfolio-form restart'
472
+ );
473
+ console.log(JSON.stringify({ status: 'success' }, null, 2));
474
+
475
+ // ── T2c — Wait and assert ──────────────────────────────────────────────────────
476
+ console.log('\n=== T2c: Wait for all cards completed ===');
477
+ await waitForCompleted('T2c', 4);
478
+
479
+ const { prices: pricesT2c, holdingsTable: htCvT2c, rowsBySymbol: rowsBySymbolT2c } = await waitForPortfolioOutputs('T2c', { NVDA: 50, GOOG: 100 });
480
+ assert(JSON.stringify(Object.keys(pricesT2c).sort()) === JSON.stringify(['GOOG', 'NVDA']),
481
+ `T2c: expected keys {GOOG, NVDA}, got ${JSON.stringify(Object.keys(pricesT2c))}`);
482
+
483
+ assert(htCvT2c.table.rows.length === 2,
484
+ `T2c: expected 2 rows in holdings-table, got ${htCvT2c.table.rows.length}`);
485
+ assert(rowsBySymbolT2c['NVDA'] === 50,
486
+ `T2c: expected NVDA qty=50, got ${rowsBySymbolT2c['NVDA']}`);
487
+ assert(rowsBySymbolT2c['GOOG'] === 100,
488
+ `T2c: expected GOOG qty=100, got ${rowsBySymbolT2c['GOOG']}`);
489
+ console.log('[T2c] assertions passed: 2 tickers in prices, 2 rows in holdings-table, NVDA qty=50, GOOG qty=100.');
490
+
491
+ // ── T3 — Retrigger price-fetch ─────────────────────────────────────────────────
492
+ console.log('\n=== T3: Retrigger price-fetch ===');
493
+ checkResult(makeBoard().retrigger({ params: { id: 'price-fetch' } }), 'retrigger price-fetch');
494
+ console.log(JSON.stringify({ status: 'success' }, null, 2));
495
+ await waitForCompleted('T3', 4);
496
+
497
+ const { prices: pricesT3, rowsBySymbol: rowsBySymbolT3 } = await waitForPortfolioOutputs('T3', { NVDA: 50, GOOG: 100 });
498
+ assert(JSON.stringify(Object.keys(pricesT3).sort()) === JSON.stringify(['GOOG', 'NVDA']),
499
+ `T3: expected keys {GOOG, NVDA}, got ${JSON.stringify(Object.keys(pricesT3))}`);
500
+ assert(rowsBySymbolT3['NVDA'] === 50,
501
+ `T3: expected NVDA qty=50, got ${rowsBySymbolT3['NVDA']}`);
502
+ assert(rowsBySymbolT3['GOOG'] === 100,
503
+ `T3: expected GOOG qty=100, got ${rowsBySymbolT3['GOOG']}`);
504
+ const pvCvT3 = checkResult(makeBoard().getOutputsComputedValues({ params: { key: 'portfolio-value' } }), 'T3 getOutputsComputedValues portfolio-value');
505
+ const expectedTotalT3 = Math.round(
506
+ (rowsBySymbolT3['NVDA'] * pricesT3['NVDA'] + rowsBySymbolT3['GOOG'] * pricesT3['GOOG']) * 100
507
+ ) / 100;
508
+ assert(Math.round(pvCvT3.totalValue * 100) === Math.round(expectedTotalT3 * 100),
509
+ `T3: expected totalValue=${expectedTotalT3}, got ${pvCvT3.totalValue}`);
510
+ console.log(`[T3] assertions passed: 2 tickers, NVDA qty=50, GOOG qty=100, totalValue=${pvCvT3.totalValue}.`);
511
+
512
+ // ── T4 — Rapid 5× portfolio-form updates ──────────────────────────────────────
513
+ // console.log('\n=== T4: Rapid 5x portfolio-form updates ===');
514
+ // for (const holdings of [
515
+ // { AAPL: 50 },
516
+ // { AAPL: 45, MSFT: 30, },
517
+ // { AAPL: 45, MSFT: 30, GOOG: 110, },
518
+ // { AAPL: 40, MSFT: 35, GOOG: 120, TSLA: 70 },
519
+ // { AAPL: 45, MSFT: 30, GOOG: 110, AMZN: 140, TSLA: 60 },
520
+ // ]) {
521
+ // checkResult(makeCardStore().set({ body: setHoldings(CARD_PORTFOLIO_FORM, holdings) }),
522
+ // 'card-store set portfolio-form');
523
+ // console.error('card-store set: wrote 1 card(s)');
524
+ // checkResult(makeBoard().upsertCard({ params: { cardId: 'portfolio-form', restart: 'true' } }),
525
+ // 'upsertCard portfolio-form restart');
526
+ // console.log(JSON.stringify({ status: 'success' }, null, 2));
527
+ // await new Promise(r => setTimeout(r, 2000));
528
+ // }
529
+
530
+ // await waitForCompleted('T4');
531
+
532
+ console.log('\nFinal board status (from NS):');
533
+ const finalStatusData = getNS().latestStatus();
534
+ console.log(JSON.stringify(finalStatusData, null, 2));
535
+
536
+ console.log('\nNotification summary (NS):');
537
+ console.log(JSON.stringify(getNS().summary(), null, 2));
538
+
539
+ console.log('\n=== portfolio-tracker-public completed successfully ===');
540
+ console.log('\n--- Runtime directories ---');
541
+ console.log(' cardstore: ', CARDSTORE_DIR);
542
+ console.log(' boardruntime: ', BOARDRUNTIME_DIR);
543
+ console.log(' outputs: ', OUTPUTS_DIR);
544
+
545
+ for (const socket of pipeConsumer.sockets) {
546
+ try { socket.destroy(); } catch { /* best-effort */ }
547
+ }
548
+ await new Promise((resolve) => pipeConsumer.server.close(resolve));
549
+ if (process.platform !== 'win32' && fs.existsSync(pipeConsumer.pipePath)) {
550
+ try { fs.rmSync(pipeConsumer.pipePath, { force: true }); } catch { /* best-effort */ }
551
+ }
552
+ process.exit(0);