bhooai-nexus 2.0.13 → 3.0.4

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 (198) hide show
  1. package/README.md +113 -43
  2. package/package.json +23 -14
  3. package/packages/nexus-admin/package.json +1 -1
  4. package/packages/nexus-admin/src/App.tsx +280 -37
  5. package/packages/nexus-admin/src/api.ts +56 -0
  6. package/packages/nexus-admin/src/index.css +10 -1
  7. package/packages/nexus-ads/package.json +1 -1
  8. package/packages/nexus-ai-client/package.json +22 -20
  9. package/packages/nexus-ai-client/src/client.ts +3 -0
  10. package/packages/nexus-auth/package.json +1 -1
  11. package/packages/nexus-cache/package.json +1 -1
  12. package/packages/nexus-cli/package.json +6 -3
  13. package/packages/nexus-cli/src/commands/add.ts +32 -10
  14. package/packages/nexus-cli/src/commands/dev.ts +206 -127
  15. package/packages/nexus-cli/src/commands/init.ts +45 -12
  16. package/packages/nexus-cli/src/commands/license.ts +174 -0
  17. package/packages/nexus-cli/src/devPanel.ts +1381 -990
  18. package/packages/nexus-cli/src/devServiceManager.ts +416 -40
  19. package/packages/nexus-cli/src/features.ts +58 -11
  20. package/packages/nexus-cli/src/fusion/server.ts +251 -0
  21. package/packages/nexus-cli/src/index.ts +2 -0
  22. package/packages/nexus-cli/src/launcher.ts +35 -3
  23. package/packages/nexus-cli/src/ports.ts +112 -61
  24. package/packages/nexus-cli/src/tui.ts +272 -15
  25. package/packages/nexus-cli/src/wizard.ts +210 -23
  26. package/packages/nexus-cli/templates/base/apps/admin/vite.config.ts.ejs +15 -9
  27. package/packages/nexus-cli/templates/base/apps/backend/package.json.ejs +1 -0
  28. package/packages/nexus-cli/templates/base/apps/backend/src/live/root.live.js.ejs +26 -0
  29. package/packages/nexus-cli/templates/base/apps/backend/src/main.ts +1 -1
  30. package/packages/nexus-cli/templates/base/apps/backend/src/routes/index.ts.ejs +2 -0
  31. package/packages/nexus-cli/templates/base/apps/backend/src/routes/upload.ts.ejs +31 -0
  32. package/packages/nexus-cli/templates/base/apps/frontend/index.html.ejs +1 -0
  33. package/packages/nexus-cli/templates/base/apps/frontend/src/App.tsx.ejs +7 -2
  34. package/packages/nexus-cli/templates/base/apps/frontend/src/NotFound.tsx.ejs +56 -0
  35. package/packages/nexus-cli/templates/base/apps/frontend/vite.config.ts.ejs +15 -8
  36. package/packages/nexus-cli/templates/base/apps/pyserver/main.py.ejs +19 -0
  37. package/packages/nexus-cli/templates/base/apps/pyserver/requirements.txt.ejs +20 -0
  38. package/packages/nexus-cli/templates/base/apps/pyserver/routers/.gitkeep +0 -0
  39. package/packages/nexus-cli/templates/base/apps/pyserver/scripts/chat.py.ejs +40 -0
  40. package/packages/nexus-cli/templates/base/apps/pyserver/scripts/hello.py.ejs +5 -0
  41. package/packages/nexus-cli/templates/base/dockerignore +0 -1
  42. package/packages/nexus-cli/templates/base/gitignore +4 -1
  43. package/packages/nexus-cli/templates/base/nexus.config.ts.ejs +37 -1
  44. package/packages/nexus-cli/templates/base/package.json.ejs +12 -11
  45. package/packages/nexus-core/package.json +3 -2
  46. package/packages/nexus-core/src/app/Storage.ts +616 -9
  47. package/packages/nexus-core/src/app/Upload.ts +114 -0
  48. package/packages/nexus-core/src/app/adminModule.ts +256 -52
  49. package/packages/nexus-core/src/app/authModule.ts +56 -30
  50. package/packages/nexus-core/src/app/createNexusApp.ts +171 -28
  51. package/packages/nexus-core/src/app/databaseAdminModule.ts +171 -2
  52. package/packages/nexus-core/src/app/discover.ts +4 -2
  53. package/packages/nexus-core/src/app/errorPages.ts +21 -5
  54. package/packages/nexus-core/src/app/fusionEngine.ts +53 -0
  55. package/packages/nexus-core/src/app/index.ts +3 -0
  56. package/packages/nexus-core/src/app/preflightModule.ts +41 -4
  57. package/packages/nexus-core/src/app/userStore.ts +321 -0
  58. package/packages/nexus-core/src/certs/generator.ts +224 -0
  59. package/packages/nexus-core/src/config/dbAccess.ts +26 -0
  60. package/packages/nexus-core/src/config/defaults.ts +34 -4
  61. package/packages/nexus-core/src/config/index.ts +1 -0
  62. package/packages/nexus-core/src/config/schema.ts +72 -6
  63. package/packages/nexus-core/src/config/types.ts +94 -1
  64. package/packages/nexus-core/src/http/Server.ts +27 -3
  65. package/packages/nexus-core/src/http/static.ts +1 -1
  66. package/packages/nexus-crypto/package.json +1 -1
  67. package/packages/nexus-crypto/src/index.ts +2 -1
  68. package/packages/nexus-crypto/src/license.ts +141 -0
  69. package/packages/nexus-data/package.json +1 -1
  70. package/packages/nexus-data/src/errors.ts +11 -3
  71. package/packages/nexus-data/src/model/Model.ts +25 -1
  72. package/packages/nexus-data/src/schema/Schema.ts +1 -1
  73. package/packages/nexus-email/package.json +1 -1
  74. package/packages/nexus-examples/examples/chat/apps/backend/src/ws/chat.room.ts +11 -0
  75. package/packages/nexus-examples/examples/emotion-garden/apps/backend/src/live/_nav.js +17 -0
  76. package/packages/nexus-examples/examples/emotion-garden/apps/backend/src/live/garden.live.js +105 -0
  77. package/packages/nexus-examples/examples/file-storage/README.md +22 -4
  78. package/packages/nexus-examples/examples/file-storage/apps/backend/src/graphql/app.graph.ts +60 -2
  79. package/packages/nexus-examples/examples/file-storage/apps/backend/src/handlers/file.handler.ts +164 -6
  80. package/packages/nexus-examples/examples/file-storage/apps/frontend/src/main.tsx +32 -7
  81. package/packages/nexus-examples/examples/fusion-tree/README.md +169 -0
  82. package/packages/nexus-examples/examples/fusion-tree/apps/backend/src/handlers/ai.handler.ts +368 -0
  83. package/packages/nexus-examples/examples/fusion-tree/apps/backend/src/handlers/cache.handler.ts +79 -0
  84. package/packages/nexus-examples/examples/fusion-tree/apps/backend/src/handlers/fusion.handler.ts +437 -0
  85. package/packages/nexus-examples/examples/fusion-tree/apps/backend/src/routes/cache.ts +21 -0
  86. package/packages/nexus-examples/examples/fusion-tree/apps/backend/src/routes/fusion.ts +40 -0
  87. package/packages/nexus-examples/examples/fusion-tree/apps/backend/src/services/AiService.ts +184 -0
  88. package/packages/nexus-examples/examples/fusion-tree/apps/backend/src/services/CacheService.ts +65 -0
  89. package/packages/nexus-examples/examples/fusion-tree/apps/backend/src/services/FusionService.ts +141 -0
  90. package/packages/nexus-examples/examples/fusion-tree/apps/backend/src/services/QueryCache.ts +169 -0
  91. package/packages/nexus-examples/examples/fusion-tree/apps/frontend/src/AiPanel.tsx +179 -0
  92. package/packages/nexus-examples/examples/fusion-tree/apps/frontend/src/CachePanel.tsx +114 -0
  93. package/packages/nexus-examples/examples/fusion-tree/apps/frontend/src/main.tsx +412 -0
  94. package/packages/nexus-examples/examples/future-more/apps/backend/src/live/_nav.js +29 -0
  95. package/packages/nexus-examples/examples/future-more/apps/backend/src/live/ai.live.js +84 -0
  96. package/packages/nexus-examples/examples/future-more/apps/backend/src/live/all-examples.live.js +42 -0
  97. package/packages/nexus-examples/examples/future-more/apps/backend/src/live/chat.live.js +32 -0
  98. package/packages/nexus-examples/examples/future-more/apps/backend/src/live/counter.live.js +24 -0
  99. package/packages/nexus-examples/examples/future-more/apps/backend/src/live/gallery.live.js +46 -0
  100. package/packages/nexus-examples/examples/future-more/apps/backend/src/live/posts.live.js +32 -0
  101. package/packages/nexus-examples/examples/future-more/apps/backend/src/live/presence.live.js +47 -0
  102. package/packages/nexus-examples/examples/future-more/apps/backend/src/live/stats.live.js +50 -0
  103. package/packages/nexus-examples/examples/image-lab/README.md +26 -0
  104. package/packages/nexus-examples/examples/image-lab/apps/backend/src/routes/images.ts +184 -0
  105. package/packages/nexus-examples/examples/image-lab/apps/frontend/src/App.tsx +235 -0
  106. package/packages/nexus-examples/examples/image-lab/apps/frontend/src/main.tsx +6 -0
  107. package/packages/nexus-examples/examples/mario-platformer/README.md +13 -0
  108. package/packages/nexus-examples/examples/mario-platformer/apps/backend/src/live/_nav.js +16 -0
  109. package/packages/nexus-examples/examples/mario-platformer/apps/backend/src/live/root.live.js +178 -0
  110. package/packages/nexus-examples/examples/morph-ui/apps/backend/src/live/_nav.js +19 -0
  111. package/packages/nexus-examples/examples/morph-ui/apps/backend/src/live/cards.live.js +58 -0
  112. package/packages/nexus-examples/examples/morph-ui/apps/backend/src/live/list.live.js +84 -0
  113. package/packages/nexus-examples/examples/morph-ui/apps/backend/src/live/morphui.live.js +189 -0
  114. package/packages/nexus-examples/examples/python-bridge/README.md +12 -0
  115. package/packages/nexus-examples/examples/python-bridge/_setup.mjs +27 -0
  116. package/packages/nexus-examples/examples/python-bridge/apps/backend/src/routes/python-bridge.ts +120 -0
  117. package/packages/nexus-examples/examples/python-bridge/apps/frontend/src/App.tsx +61 -0
  118. package/packages/nexus-examples/examples/python-bridge-future/README.md +10 -0
  119. package/packages/nexus-examples/examples/python-bridge-future/_setup.mjs +27 -0
  120. package/packages/nexus-examples/examples/python-bridge-future/apps/backend/src/live/_nav.js +16 -0
  121. package/packages/nexus-examples/examples/python-bridge-future/apps/backend/src/live/p.live.js +12 -0
  122. package/packages/nexus-examples/examples/python-bridge-future/apps/backend/src/live/p1.live.js +13 -0
  123. package/packages/nexus-examples/examples/python-bridge-future/apps/backend/src/live/root.live.js +53 -0
  124. package/packages/nexus-examples/examples/saas-starter/apps/backend/src/graphql/app.graph.ts +4 -3
  125. package/packages/nexus-examples/examples/saas-starter/apps/frontend/src/main.tsx +6 -3
  126. package/packages/nexus-examples/package.json +1 -1
  127. package/packages/nexus-examples/src/index.ts +84 -1
  128. package/packages/nexus-fusion/package.json +41 -0
  129. package/packages/nexus-fusion/src/adapters/MongoMirror.ts +115 -0
  130. package/packages/nexus-fusion/src/adapters/RedisBridge.ts +89 -0
  131. package/packages/nexus-fusion/src/adapters/index.ts +3 -0
  132. package/packages/nexus-fusion/src/cache/FusionCache.ts +422 -0
  133. package/packages/nexus-fusion/src/cache/index.ts +2 -0
  134. package/packages/nexus-fusion/src/firebase/offlineQueue.ts +89 -0
  135. package/packages/nexus-fusion/src/firebase/onSnapshot.ts +98 -0
  136. package/packages/nexus-fusion/src/firebase/refs.ts +422 -0
  137. package/packages/nexus-fusion/src/firebase/securityRules.ts +121 -0
  138. package/packages/nexus-fusion/src/firebase/transforms.ts +73 -0
  139. package/packages/nexus-fusion/src/firebase/types.ts +45 -0
  140. package/packages/nexus-fusion/src/index.ts +248 -0
  141. package/packages/nexus-fusion/src/native/index.d.ts +0 -0
  142. package/packages/nexus-fusion/src/native/loader.ts +168 -0
  143. package/packages/nexus-fusion/src/playground/playgroundHtml.ts +461 -0
  144. package/packages/nexus-fusion/src/realtime/Emitter.ts +30 -0
  145. package/packages/nexus-fusion/src/schemas/auth.ts +32 -0
  146. package/packages/nexus-fusion/src/schemas/cms.ts +40 -0
  147. package/packages/nexus-fusion/src/schemas/core.ts +155 -0
  148. package/packages/nexus-fusion/src/schemas/ecommerce.ts +46 -0
  149. package/packages/nexus-fusion/src/schemas/index.ts +19 -0
  150. package/packages/nexus-fusion-core/package.json +20 -0
  151. package/packages/nexus-fusion-core/src/cache.rs +196 -0
  152. package/packages/nexus-fusion-core/src/engine.rs +772 -0
  153. package/packages/nexus-fusion-core/src/errors.rs +51 -0
  154. package/packages/nexus-fusion-core/src/idgen.rs +100 -0
  155. package/packages/nexus-fusion-core/src/lib.rs +17 -0
  156. package/packages/nexus-fusion-core/src/napi_glue.rs +333 -0
  157. package/packages/nexus-fusion-core/src/query.rs +513 -0
  158. package/packages/nexus-fusion-core/src/tree.rs +282 -0
  159. package/packages/nexus-fusion-core/src/types.rs +147 -0
  160. package/packages/nexus-fusion-core/src/wal.rs +1877 -0
  161. package/packages/nexus-future/LICENSE +21 -0
  162. package/packages/nexus-future/package.json +63 -0
  163. package/packages/nexus-future/src/ai/autoui.js +229 -0
  164. package/packages/nexus-future/src/ai/crud.js +144 -0
  165. package/packages/nexus-future/src/ai/fields.js +141 -0
  166. package/packages/nexus-future/src/ai/http.js +180 -0
  167. package/packages/nexus-future/src/ai/index.js +132 -0
  168. package/packages/nexus-future/src/ai/mock.js +14 -0
  169. package/packages/nexus-future/src/html/index.js +22 -0
  170. package/packages/nexus-future/src/html/signals.js +191 -0
  171. package/packages/nexus-future/src/html/template.js +459 -0
  172. package/packages/nexus-future/src/index.js +50 -0
  173. package/packages/nexus-future/src/live/client.js +580 -0
  174. package/packages/nexus-future/src/live/index.js +5 -0
  175. package/packages/nexus-future/src/live/morph.js +198 -0
  176. package/packages/nexus-future/src/live/rpc.js +53 -0
  177. package/packages/nexus-future/src/live/server.js +464 -0
  178. package/packages/nexus-future/src/live/stream.js +104 -0
  179. package/packages/nexus-future/src/page/index.js +180 -0
  180. package/packages/nexus-future/src/server/mount.js +192 -0
  181. package/packages/nexus-future/src/ui/canvas.js +142 -0
  182. package/packages/nexus-future/src/ui/index.js +2 -0
  183. package/packages/nexus-future/src/ui/sense.js +233 -0
  184. package/packages/nexus-graphql/package.json +1 -1
  185. package/packages/nexus-payments/package.json +23 -22
  186. package/packages/nexus-payments/src/index.ts +4 -0
  187. package/packages/nexus-plugins/package.json +1 -1
  188. package/packages/nexus-postcss/package.json +1 -1
  189. package/packages/nexus-realtime/package.json +1 -1
  190. package/packages/nexus-safe-goto/package.json +1 -1
  191. package/packages/nexus-telemetry/package.json +1 -1
  192. package/src/index.js +34 -0
  193. package/docs/ARCHITECTURE.md +0 -169
  194. package/docs/CLI.md +0 -147
  195. package/docs/CONVENTIONS.md +0 -243
  196. package/docs/MULTI_APP.md +0 -107
  197. package/docs/STORAGE.md +0 -118
  198. package/src/index.ts +0 -53
@@ -0,0 +1,198 @@
1
+ /**
2
+ * morph.js — morphdom-lite.
3
+ *
4
+ * Diffs two HTML strings / node trees and patches a live DOM element in
5
+ * place: keyed children (data-key) move instead of rebuild, other children
6
+ * patch positionally, attributes update individually, text nodes update in
7
+ * place. Event handler attributes (data-fx*) are preserved — never diffed
8
+ * away — because the server re-issues fresh ids each render.
9
+ */
10
+
11
+ /**
12
+ * Patch `target` (a live DOM element) to match `html`.
13
+ * `html` is a full element string (e.g. '<div id="t">…</div>') whose root
14
+ * should correspond to `target` itself — attributes patch on target,
15
+ * children morph recursively.
16
+ * @param {Element} target
17
+ * @param {string} html
18
+ */
19
+ export function morph(target, html) {
20
+ const tpl = target.ownerDocument.createElement('template');
21
+ tpl.innerHTML = html;
22
+ const root = tpl.content.firstElementChild;
23
+ if (!root) return;
24
+ morphNode(target, root);
25
+ }
26
+
27
+ /**
28
+ * Patch the children of `target` to match the children of `source`.
29
+ * @param {Element | DocumentFragment} target
30
+ * @param {Element | DocumentFragment} source
31
+ */
32
+ export function morphChildren(target, source) {
33
+ const src = [...source.childNodes];
34
+ const dst = [...target.childNodes];
35
+
36
+ // Keyed pass: when both lists carry data-key children, reconcile by key —
37
+ // existing nodes are morphed in place and moved to their new position;
38
+ // missing keys are inserted; vanished keys are removed.
39
+ const srcKeyed = src.filter((n) => n instanceof Element && n.hasAttribute('data-key'));
40
+ const dstKeyed = dst.filter((n) => n instanceof Element && n.hasAttribute('data-key'));
41
+ if (srcKeyed.length > 0 && dstKeyed.length > 0) {
42
+ morphKeyed(target, src, dst);
43
+ return;
44
+ }
45
+
46
+ let srcIdx = 0;
47
+ let dstIdx = 0;
48
+
49
+ // Positional pass: pair up same-position children.
50
+ while (dstIdx < dst.length || srcIdx < src.length) {
51
+ const s = src[srcIdx];
52
+ const d = dst[dstIdx];
53
+
54
+ if (s === undefined && d === undefined) break;
55
+
56
+ if (s !== undefined && d !== undefined && sameType(s, d)) {
57
+ morphNode(/** @type {Node} */ (d), s);
58
+ srcIdx++;
59
+ dstIdx++;
60
+ continue;
61
+ }
62
+
63
+ if (s !== undefined && d === undefined) {
64
+ target.appendChild(cloneForMorph(s));
65
+ srcIdx++;
66
+ continue;
67
+ }
68
+ if (s === undefined && d !== undefined) {
69
+ target.removeChild(/** @type {Node} */ (d));
70
+ dstIdx++;
71
+ continue;
72
+ }
73
+ // Different types at same position: replace.
74
+ target.replaceChild(cloneForMorph(s), /** @type {Node} */ (d));
75
+ srcIdx++;
76
+ dstIdx++;
77
+ }
78
+ }
79
+
80
+ /**
81
+ * Keyed reconciliation for children lists where (a subset of) children have
82
+ * data-key attributes. Non-keyed children pair positionally at their spots.
83
+ * @param {Element | DocumentFragment} target
84
+ * @param {Node[]} src
85
+ * @param {Node[]} dst
86
+ */
87
+ function morphKeyed(target, src, dst) {
88
+ /** @type {Map<string, Element>} */
89
+ const dstByKey = new Map();
90
+ for (const n of dst) {
91
+ if (n instanceof Element && n.hasAttribute('data-key')) {
92
+ dstByKey.set(String(n.getAttribute('data-key')), n);
93
+ }
94
+ }
95
+
96
+ /** @type {Node | null} */
97
+ let cursor = null; // last placed node
98
+
99
+ for (const s of src) {
100
+ const key = s instanceof Element ? s.getAttribute('data-key') : null;
101
+ /** @type {Node} */
102
+ let node;
103
+ if (key) {
104
+ const existing = dstByKey.get(String(key));
105
+ if (existing) {
106
+ morphNode(existing, s);
107
+ node = existing;
108
+ dstByKey.delete(String(key));
109
+ } else {
110
+ node = cloneForMorph(s);
111
+ }
112
+ // Place at correct position (move if needed).
113
+ /** @type {Node | null} */
114
+ const expectedNext = cursor ? cursor.nextSibling : target.firstChild;
115
+ if (node !== expectedNext) {
116
+ target.insertBefore(node, /** @type {Node} */ (expectedNext));
117
+ }
118
+ } else {
119
+ // Non-keyed node: append in order after cursor.
120
+ node = cloneForMorph(s);
121
+ target.insertBefore(node, cursor ? cursor.nextSibling : null);
122
+ }
123
+ cursor = node;
124
+ }
125
+
126
+ // Remove dst leftovers not consumed by key matching.
127
+ for (const leftover of dstByKey.values()) {
128
+ if (leftover.parentNode) leftover.parentNode.removeChild(leftover);
129
+ }
130
+ // Remove non-keyed destination nodes beyond what source consumed.
131
+ for (const d of dst) {
132
+ if (d instanceof Element && d.hasAttribute('data-key')) continue;
133
+ if (d.parentNode && !src.includes(d) && !([...target.childNodes]).includes(/** @type {ChildNode} */ (d))) {
134
+ /** @type {ChildNode} */ (d).remove();
135
+ }
136
+ }
137
+ }
138
+
139
+ /**
140
+ * Do two nodes have compatible types (morphable into each other)?
141
+ * @param {Node} a — live node
142
+ * @param {Node} b — incoming node
143
+ */
144
+ function sameType(a, b) {
145
+ if (a.nodeType === 3 && b.nodeType === 3) return true; // text
146
+ if (a.nodeType === 8 && b.nodeType === 8) return true; // comment
147
+ if (a instanceof Element && b instanceof Element) {
148
+ return a.tagName === b.tagName;
149
+ }
150
+ return false;
151
+ }
152
+
153
+ /**
154
+ * Patch live node `a` to match incoming `b`. b stays untouched (owned by the
155
+ * parsed template); a's children recurse.
156
+ * @param {Node} a
157
+ * @param {Node} b
158
+ */
159
+ function morphNode(a, b) {
160
+ if (a.nodeType === 3) {
161
+ const next = String(/** @type {Text} */ (b).data);
162
+ if (/** @type {Text} */ (a).data !== next) /** @type {Text} */ (a).data = next;
163
+ return;
164
+ }
165
+ if (a.nodeType === 8) {
166
+ const next = String(/** @type {Comment} */ (b).data);
167
+ if (/** @type {Comment} */ (a).data !== next) /** @type {Comment} */ (a).data = next;
168
+ return;
169
+ }
170
+ if (!(a instanceof Element) || !(b instanceof Element)) return;
171
+
172
+ // Attributes: apply incoming, remove stale (except data-fx* which are
173
+ // server-issued per-render and replaced wholesale below).
174
+ /** @type {Set<string>} */
175
+ const incoming = new Set();
176
+ for (const attr of b.attributes) {
177
+ incoming.add(attr.name);
178
+ if (a.getAttribute(attr.name) !== attr.value) {
179
+ a.setAttribute(attr.name, attr.value);
180
+ }
181
+ }
182
+ for (const attr of [...a.attributes]) {
183
+ if (attr.name.startsWith('data-fx')) continue; // managed by handler binding
184
+ if (!incoming.has(attr.name)) a.removeAttribute(attr.name);
185
+ }
186
+
187
+ morphChildren(a, b);
188
+ }
189
+
190
+ /**
191
+ * Clone a template node into a live node. Elements are cloned deeply;
192
+ * data-fx attributes survive so the delegated listener finds fresh ids.
193
+ * @param {Node} node
194
+ * @returns {Node}
195
+ */
196
+ function cloneForMorph(node) {
197
+ return node.cloneNode(true);
198
+ }
@@ -0,0 +1,53 @@
1
+ /**
2
+ * rpc.js — live() RPC protocol.
3
+ *
4
+ * Server-side handlers are captured per-render with captureHandlers() and
5
+ * stored in the LiveServer's session. The browser runtime sends:
6
+ *
7
+ * { t: 'rpc', rid, id, args? }
8
+ *
9
+ * over the WS connection; the server invokes the handler, then pushes a
10
+ * patch (or patch set) produced by re-rendering the live view.
11
+ *
12
+ * The rid (render id) routes to the correct handler set: each page render
13
+ * creates a fresh set, and the browser always sends the rid it last
14
+ * received, so stale clicks after a re-render fail safely (no-op + warn).
15
+ */
16
+
17
+ import { captureHandlers } from '../html/index.js';
18
+
19
+ /**
20
+ * Run `render()` with handler capture. Returns the HTML string plus the
21
+ * captured handler map.
22
+ * @param {() => import('../html/template.js').HtmlResult} render
23
+ */
24
+ export function renderWithHandlers(render) {
25
+ /** @type {Map<string, Function>} */
26
+ const handlers = new Map();
27
+ const stop = captureHandlers(/** @type {any} */ (handlers));
28
+ let html = '';
29
+ try {
30
+ html = String(render());
31
+ } finally {
32
+ stop();
33
+ }
34
+ return { html, handlers };
35
+ }
36
+
37
+ /**
38
+ * Invoke a captured handler by id.
39
+ * @param {Map<string, Function>} handlers
40
+ * @param {string} id
41
+ * @param {unknown[]} args
42
+ * @returns {{ ok: boolean, error?: string }}
43
+ */
44
+ export function invokeHandler(handlers, id, args) {
45
+ const fn = handlers.get(id);
46
+ if (typeof fn !== 'function') return { ok: false, error: `unknown handler ${id}` };
47
+ try {
48
+ fn(...(Array.isArray(args) ? args : []));
49
+ return { ok: true };
50
+ } catch (err) {
51
+ return { ok: false, error: err instanceof Error ? err.message : String(err) };
52
+ }
53
+ }