@treenity/core 1.0.48 → 3.0.1

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 (807) hide show
  1. package/README.md +72 -12
  2. package/dist/chain.d.ts +19 -0
  3. package/dist/chain.d.ts.map +1 -0
  4. package/dist/chain.js +59 -0
  5. package/dist/chain.js.map +1 -0
  6. package/dist/client/handle.d.ts +13 -0
  7. package/dist/client/handle.d.ts.map +1 -0
  8. package/dist/client/handle.js +46 -0
  9. package/dist/client/handle.js.map +1 -0
  10. package/dist/client/index.d.ts +21 -0
  11. package/dist/client/index.d.ts.map +1 -0
  12. package/dist/client/index.js +5 -0
  13. package/dist/client/index.js.map +1 -0
  14. package/dist/client/trpc.d.ts +13 -0
  15. package/dist/client/trpc.d.ts.map +1 -0
  16. package/dist/client/trpc.js +79 -0
  17. package/dist/client/trpc.js.map +1 -0
  18. package/dist/client.d.ts +2 -0
  19. package/dist/client.d.ts.map +1 -0
  20. package/dist/client.js +2 -0
  21. package/dist/client.js.map +1 -0
  22. package/dist/comp/handle.d.ts +14 -0
  23. package/dist/comp/handle.d.ts.map +1 -0
  24. package/dist/comp/handle.js +21 -0
  25. package/dist/comp/handle.js.map +1 -0
  26. package/dist/comp/index.d.ts +41 -0
  27. package/dist/comp/index.d.ts.map +1 -0
  28. package/dist/comp/index.js +127 -0
  29. package/dist/comp/index.js.map +1 -0
  30. package/dist/comp/needs.d.ts +26 -0
  31. package/dist/comp/needs.d.ts.map +1 -0
  32. package/dist/comp/needs.js +101 -0
  33. package/dist/comp/needs.js.map +1 -0
  34. package/dist/comp/validate.d.ts +13 -0
  35. package/dist/comp/validate.d.ts.map +1 -0
  36. package/dist/comp/validate.js +117 -0
  37. package/dist/comp/validate.js.map +1 -0
  38. package/dist/comp.d.ts +2 -0
  39. package/dist/comp.d.ts.map +1 -0
  40. package/dist/comp.js +2 -0
  41. package/dist/comp.js.map +1 -0
  42. package/dist/contexts/schema/index.d.ts +7 -0
  43. package/dist/contexts/schema/index.d.ts.map +1 -0
  44. package/dist/contexts/schema/index.js +2 -0
  45. package/dist/contexts/schema/index.js.map +1 -0
  46. package/dist/contexts/schema.d.ts +2 -0
  47. package/dist/contexts/schema.d.ts.map +1 -0
  48. package/dist/contexts/schema.js +2 -0
  49. package/dist/contexts/schema.js.map +1 -0
  50. package/dist/contexts/service/index.d.ts +31 -0
  51. package/dist/contexts/service/index.d.ts.map +1 -0
  52. package/dist/contexts/service/index.js +16 -0
  53. package/dist/contexts/service/index.js.map +1 -0
  54. package/dist/contexts/service.d.ts +1 -14
  55. package/dist/contexts/service.d.ts.map +1 -1
  56. package/dist/contexts/service.js +2 -0
  57. package/dist/contexts/service.js.map +1 -0
  58. package/dist/contexts/telegram/index.d.ts +19 -0
  59. package/dist/contexts/telegram/index.d.ts.map +1 -0
  60. package/dist/contexts/telegram/index.js +89 -0
  61. package/dist/contexts/telegram/index.js.map +1 -0
  62. package/dist/contexts/text/index.d.ts +7 -0
  63. package/dist/contexts/text/index.d.ts.map +1 -0
  64. package/dist/contexts/text/index.js +9 -0
  65. package/dist/contexts/text/index.js.map +1 -0
  66. package/dist/contexts/text.d.ts +2 -0
  67. package/dist/contexts/text.d.ts.map +1 -0
  68. package/dist/contexts/text.js +2 -0
  69. package/dist/contexts/text.js.map +1 -0
  70. package/dist/core/component.d.ts +43 -0
  71. package/dist/core/component.d.ts.map +1 -0
  72. package/dist/core/component.js +101 -0
  73. package/dist/core/component.js.map +1 -0
  74. package/dist/core/context.d.ts +5 -0
  75. package/dist/core/context.d.ts.map +1 -0
  76. package/dist/core/context.js +3 -0
  77. package/dist/core/context.js.map +1 -0
  78. package/dist/core/index.d.ts +5 -0
  79. package/dist/core/index.d.ts.map +1 -0
  80. package/dist/core/index.js +8 -0
  81. package/dist/core/index.js.map +1 -0
  82. package/dist/core/path.d.ts +7 -0
  83. package/dist/core/path.d.ts.map +1 -0
  84. package/dist/core/path.js +41 -0
  85. package/dist/core/path.js.map +1 -0
  86. package/dist/core/registry.d.ts +17 -0
  87. package/dist/core/registry.d.ts.map +1 -0
  88. package/dist/core/registry.js +100 -0
  89. package/dist/core/registry.js.map +1 -0
  90. package/dist/core.d.ts +2 -0
  91. package/dist/core.d.ts.map +1 -0
  92. package/dist/core.js +2 -0
  93. package/dist/core.js.map +1 -0
  94. package/dist/index.d.ts +1 -12
  95. package/dist/index.d.ts.map +1 -1
  96. package/dist/index.js +2 -0
  97. package/dist/index.js.map +1 -0
  98. package/dist/log.d.ts +38 -0
  99. package/dist/log.d.ts.map +1 -0
  100. package/dist/log.js +150 -0
  101. package/dist/log.js.map +1 -0
  102. package/dist/mod/discover.d.ts +4 -0
  103. package/dist/mod/discover.d.ts.map +1 -0
  104. package/dist/mod/discover.js +103 -0
  105. package/dist/mod/discover.js.map +1 -0
  106. package/dist/mod/examples/ticker/seed.d.ts +3 -0
  107. package/dist/mod/examples/ticker/seed.d.ts.map +1 -0
  108. package/dist/mod/examples/ticker/seed.js +16 -0
  109. package/dist/mod/examples/ticker/seed.js.map +1 -0
  110. package/dist/mod/examples/ticker/service.d.ts +2 -0
  111. package/dist/mod/examples/ticker/service.d.ts.map +1 -0
  112. package/dist/mod/examples/ticker/service.js +17 -0
  113. package/dist/mod/examples/ticker/service.js.map +1 -0
  114. package/dist/mod/examples/ticker/types.d.ts +16 -0
  115. package/dist/mod/examples/ticker/types.d.ts.map +1 -0
  116. package/dist/mod/examples/ticker/types.js +20 -0
  117. package/dist/mod/examples/ticker/types.js.map +1 -0
  118. package/dist/mod/examples/ticker/view.d.ts +2 -0
  119. package/dist/mod/examples/ticker/view.d.ts.map +1 -0
  120. package/dist/mod/examples/ticker/view.js +10 -0
  121. package/dist/mod/examples/ticker/view.js.map +1 -0
  122. package/dist/mod/index.d.ts +11 -0
  123. package/dist/mod/index.d.ts.map +1 -0
  124. package/dist/mod/index.js +8 -0
  125. package/dist/mod/index.js.map +1 -0
  126. package/dist/mod/loader.d.ts +19 -0
  127. package/dist/mod/loader.d.ts.map +1 -0
  128. package/dist/mod/loader.js +180 -0
  129. package/dist/mod/loader.js.map +1 -0
  130. package/dist/mod/optimistic.d.ts +34 -0
  131. package/dist/mod/optimistic.d.ts.map +1 -0
  132. package/dist/mod/optimistic.js +176 -0
  133. package/dist/mod/optimistic.js.map +1 -0
  134. package/dist/mod/prefab.d.ts +18 -0
  135. package/dist/mod/prefab.d.ts.map +1 -0
  136. package/dist/mod/prefab.js +42 -0
  137. package/dist/mod/prefab.js.map +1 -0
  138. package/dist/mod/tracking.d.ts +8 -0
  139. package/dist/mod/tracking.d.ts.map +1 -0
  140. package/dist/mod/tracking.js +44 -0
  141. package/dist/mod/tracking.js.map +1 -0
  142. package/dist/mod/types.d.ts +31 -0
  143. package/dist/mod/types.d.ts.map +1 -0
  144. package/dist/mod/types.js +5 -0
  145. package/dist/mod/types.js.map +1 -0
  146. package/dist/mod.d.ts +2 -0
  147. package/dist/mod.d.ts.map +1 -0
  148. package/dist/mod.js +2 -0
  149. package/dist/mod.js.map +1 -0
  150. package/dist/mods/autostart/server.d.ts +2 -0
  151. package/dist/mods/autostart/server.d.ts.map +1 -0
  152. package/dist/mods/autostart/server.js +2 -0
  153. package/dist/mods/autostart/server.js.map +1 -0
  154. package/dist/mods/autostart/service.d.ts +14 -0
  155. package/dist/mods/autostart/service.d.ts.map +1 -0
  156. package/dist/mods/autostart/service.js +88 -0
  157. package/dist/mods/autostart/service.js.map +1 -0
  158. package/dist/mods/clients.d.ts +2 -0
  159. package/dist/mods/clients.d.ts.map +1 -0
  160. package/dist/mods/clients.js +3 -0
  161. package/dist/mods/clients.js.map +1 -0
  162. package/dist/mods/llm/index.d.ts +13 -0
  163. package/dist/mods/llm/index.d.ts.map +1 -0
  164. package/dist/mods/llm/index.js +43 -0
  165. package/dist/mods/llm/index.js.map +1 -0
  166. package/dist/mods/llm/server.d.ts +2 -0
  167. package/dist/mods/llm/server.d.ts.map +1 -0
  168. package/dist/mods/llm/server.js +2 -0
  169. package/dist/mods/llm/server.js.map +1 -0
  170. package/dist/mods/mcp/server.d.ts +2 -0
  171. package/dist/mods/mcp/server.js +2 -0
  172. package/dist/mods/mcp/service.d.ts +1 -0
  173. package/dist/mods/mcp/service.js +16 -0
  174. package/dist/mods/mcp/types.d.ts +4 -0
  175. package/dist/mods/mcp/types.js +6 -0
  176. package/dist/mods/servers.d.ts +4 -0
  177. package/dist/mods/servers.d.ts.map +1 -0
  178. package/dist/mods/servers.js +5 -0
  179. package/dist/mods/servers.js.map +1 -0
  180. package/dist/mods/treenity/agent-port.d.ts +2 -0
  181. package/dist/mods/treenity/agent-port.d.ts.map +1 -0
  182. package/dist/mods/treenity/agent-port.js +76 -0
  183. package/dist/mods/treenity/agent-port.js.map +1 -0
  184. package/dist/mods/treenity/builtins.d.ts +2 -0
  185. package/dist/mods/treenity/builtins.d.ts.map +1 -0
  186. package/dist/mods/treenity/builtins.js +18 -0
  187. package/dist/mods/treenity/builtins.js.map +1 -0
  188. package/dist/mods/treenity/groups.d.ts +2 -0
  189. package/dist/mods/treenity/groups.d.ts.map +1 -0
  190. package/dist/mods/treenity/groups.js +18 -0
  191. package/dist/mods/treenity/groups.js.map +1 -0
  192. package/dist/mods/treenity/logs.d.ts +18 -0
  193. package/dist/mods/treenity/logs.d.ts.map +1 -0
  194. package/dist/mods/treenity/logs.js +17 -0
  195. package/dist/mods/treenity/logs.js.map +1 -0
  196. package/dist/mods/treenity/mod-type.d.ts +2 -0
  197. package/dist/mods/treenity/mod-type.d.ts.map +1 -0
  198. package/dist/mods/treenity/mod-type.js +9 -0
  199. package/dist/mods/treenity/mod-type.js.map +1 -0
  200. package/dist/mods/treenity/seed.d.ts +2 -0
  201. package/dist/mods/treenity/seed.d.ts.map +1 -0
  202. package/dist/mods/treenity/seed.js +55 -0
  203. package/dist/mods/treenity/seed.js.map +1 -0
  204. package/dist/mods/treenity/server.d.ts +7 -0
  205. package/dist/mods/treenity/server.d.ts.map +1 -0
  206. package/dist/mods/treenity/server.js +7 -0
  207. package/dist/mods/treenity/server.js.map +1 -0
  208. package/dist/mods/treenity/system.d.ts +36 -0
  209. package/dist/mods/treenity/system.d.ts.map +1 -0
  210. package/dist/mods/treenity/system.js +61 -0
  211. package/dist/mods/treenity/system.js.map +1 -0
  212. package/dist/mods/uix/client.d.ts +3 -0
  213. package/dist/mods/uix/client.d.ts.map +1 -0
  214. package/dist/mods/uix/client.js +96 -0
  215. package/dist/mods/uix/client.js.map +1 -0
  216. package/dist/mods/uix/compile.d.ts +7 -0
  217. package/dist/mods/uix/compile.d.ts.map +1 -0
  218. package/dist/mods/uix/compile.js +96 -0
  219. package/dist/mods/uix/compile.js.map +1 -0
  220. package/dist/mods/uix/jsx-parser.d.ts +2 -0
  221. package/dist/mods/uix/jsx-parser.d.ts.map +1 -0
  222. package/dist/mods/uix/jsx-parser.js +565 -0
  223. package/dist/mods/uix/jsx-parser.js.map +1 -0
  224. package/dist/mods/uix/verify.d.ts +7 -0
  225. package/dist/mods/uix/verify.d.ts.map +1 -0
  226. package/dist/mods/uix/verify.js +59 -0
  227. package/dist/mods/uix/verify.js.map +1 -0
  228. package/dist/schema/_test-fixture.d.ts +11 -0
  229. package/dist/schema/_test-fixture.d.ts.map +1 -0
  230. package/dist/schema/_test-fixture.js +8 -0
  231. package/dist/schema/_test-fixture.js.map +1 -0
  232. package/dist/schema/catalog.d.ts +23 -0
  233. package/dist/schema/catalog.d.ts.map +1 -0
  234. package/dist/schema/catalog.js +82 -0
  235. package/dist/schema/catalog.js.map +1 -0
  236. package/dist/schema/extract-schemas.d.ts +5 -0
  237. package/dist/schema/extract-schemas.d.ts.map +1 -0
  238. package/dist/schema/extract-schemas.js +444 -0
  239. package/dist/schema/extract-schemas.js.map +1 -0
  240. package/dist/schema/load.d.ts +2 -0
  241. package/dist/schema/load.d.ts.map +1 -0
  242. package/dist/schema/load.js +23 -0
  243. package/dist/schema/load.js.map +1 -0
  244. package/dist/schema/test-fixture.types.d.ts +2 -0
  245. package/dist/schema/test-fixture.types.d.ts.map +1 -0
  246. package/dist/schema/test-fixture.types.js +19 -0
  247. package/dist/schema/test-fixture.types.js.map +1 -0
  248. package/dist/schema/types.d.ts +38 -0
  249. package/dist/schema/types.d.ts.map +1 -0
  250. package/dist/schema/types.js +3 -0
  251. package/dist/schema/types.js.map +1 -0
  252. package/dist/server/actions.d.ts +35 -0
  253. package/dist/server/actions.d.ts.map +1 -0
  254. package/dist/server/actions.js +170 -0
  255. package/dist/server/actions.js.map +1 -0
  256. package/dist/server/agent.d.ts +6 -0
  257. package/dist/server/agent.d.ts.map +1 -0
  258. package/dist/server/agent.js +15 -0
  259. package/dist/server/agent.js.map +1 -0
  260. package/dist/server/auth.d.ts +39 -0
  261. package/dist/server/auth.d.ts.map +1 -0
  262. package/dist/server/auth.js +336 -0
  263. package/dist/server/auth.js.map +1 -0
  264. package/dist/server/client.d.ts +190 -0
  265. package/dist/server/client.d.ts.map +1 -0
  266. package/dist/server/client.js +22 -0
  267. package/dist/server/client.js.map +1 -0
  268. package/dist/server/cookies.d.ts +5 -0
  269. package/dist/server/cookies.d.ts.map +1 -0
  270. package/dist/server/cookies.js +24 -0
  271. package/dist/server/cookies.js.map +1 -0
  272. package/dist/server/doc-index.d.ts +38 -0
  273. package/dist/server/doc-index.d.ts.map +1 -0
  274. package/dist/server/doc-index.js +245 -0
  275. package/dist/server/doc-index.js.map +1 -0
  276. package/dist/server/errors.d.ts +7 -0
  277. package/dist/server/errors.d.ts.map +1 -0
  278. package/dist/server/errors.js +11 -0
  279. package/dist/server/errors.js.map +1 -0
  280. package/dist/server/factory.d.ts +29 -0
  281. package/dist/server/factory.d.ts.map +1 -0
  282. package/dist/server/factory.js +89 -0
  283. package/dist/server/factory.js.map +1 -0
  284. package/dist/server/main.d.ts +2 -0
  285. package/dist/server/main.d.ts.map +1 -0
  286. package/dist/server/main.js +19 -0
  287. package/dist/server/main.js.map +1 -0
  288. package/dist/server/mcp.d.ts +4 -0
  289. package/dist/server/mcp.js +279 -0
  290. package/dist/server/migrate.d.ts +3 -0
  291. package/dist/server/migrate.d.ts.map +1 -0
  292. package/dist/server/migrate.js +56 -0
  293. package/dist/server/migrate.js.map +1 -0
  294. package/dist/server/mod-catalog.d.ts +14 -0
  295. package/dist/server/mod-catalog.d.ts.map +1 -0
  296. package/dist/server/mod-catalog.js +40 -0
  297. package/dist/server/mod-catalog.js.map +1 -0
  298. package/dist/server/mods-mount.d.ts +3 -0
  299. package/dist/server/mods-mount.d.ts.map +1 -0
  300. package/dist/server/mods-mount.js +168 -0
  301. package/dist/server/mods-mount.js.map +1 -0
  302. package/dist/server/mount-adapters.d.ts +9 -0
  303. package/dist/server/mount-adapters.d.ts.map +1 -0
  304. package/dist/server/mount-adapters.js +80 -0
  305. package/dist/server/mount-adapters.js.map +1 -0
  306. package/dist/server/mount.d.ts +3 -0
  307. package/dist/server/mount.d.ts.map +1 -0
  308. package/dist/server/mount.js +195 -0
  309. package/dist/server/mount.js.map +1 -0
  310. package/dist/server/prefab.d.ts +18 -0
  311. package/dist/server/prefab.d.ts.map +1 -0
  312. package/dist/server/prefab.js +72 -0
  313. package/dist/server/prefab.js.map +1 -0
  314. package/dist/server/refs.d.ts +3 -0
  315. package/dist/server/refs.d.ts.map +1 -0
  316. package/dist/server/refs.js +59 -0
  317. package/dist/server/refs.js.map +1 -0
  318. package/dist/server/seed/index.d.ts +6 -0
  319. package/dist/server/seed/index.d.ts.map +1 -0
  320. package/dist/server/seed/index.js +16 -0
  321. package/dist/server/seed/index.js.map +1 -0
  322. package/dist/server/server.d.ts +28 -0
  323. package/dist/server/server.d.ts.map +1 -0
  324. package/dist/server/server.js +127 -0
  325. package/dist/server/server.js.map +1 -0
  326. package/dist/server/sub.d.ts +34 -0
  327. package/dist/server/sub.d.ts.map +1 -0
  328. package/dist/server/sub.js +174 -0
  329. package/dist/server/sub.js.map +1 -0
  330. package/dist/server/trpc.d.ts +199 -0
  331. package/dist/server/trpc.d.ts.map +1 -0
  332. package/dist/server/trpc.js +323 -0
  333. package/dist/server/trpc.js.map +1 -0
  334. package/dist/server/types-mount.d.ts +3 -0
  335. package/dist/server/types-mount.d.ts.map +1 -0
  336. package/dist/server/types-mount.js +144 -0
  337. package/dist/server/types-mount.js.map +1 -0
  338. package/dist/server/validate.d.ts +3 -0
  339. package/dist/server/validate.d.ts.map +1 -0
  340. package/dist/server/validate.js +20 -0
  341. package/dist/server/validate.js.map +1 -0
  342. package/dist/server/volatile.d.ts +11 -0
  343. package/dist/server/volatile.d.ts.map +1 -0
  344. package/dist/server/volatile.js +26 -0
  345. package/dist/server/volatile.js.map +1 -0
  346. package/dist/server/watch.d.ts +23 -0
  347. package/dist/server/watch.d.ts.map +1 -0
  348. package/dist/server/watch.js +178 -0
  349. package/dist/server/watch.js.map +1 -0
  350. package/dist/tree/cache.d.ts +3 -0
  351. package/dist/tree/cache.d.ts.map +1 -0
  352. package/dist/tree/cache.js +48 -0
  353. package/dist/tree/cache.js.map +1 -0
  354. package/dist/tree/fs.d.ts +3 -0
  355. package/dist/tree/fs.d.ts.map +1 -0
  356. package/dist/tree/fs.js +276 -0
  357. package/dist/tree/fs.js.map +1 -0
  358. package/dist/tree/index.d.ts +38 -0
  359. package/dist/tree/index.d.ts.map +1 -0
  360. package/dist/tree/index.js +183 -0
  361. package/dist/tree/index.js.map +1 -0
  362. package/dist/tree/inflight.d.ts +2 -0
  363. package/dist/tree/inflight.d.ts.map +1 -0
  364. package/dist/tree/inflight.js +15 -0
  365. package/dist/tree/inflight.js.map +1 -0
  366. package/dist/tree/json-codec.d.ts +2 -0
  367. package/dist/tree/json-codec.d.ts.map +1 -0
  368. package/dist/tree/json-codec.js +13 -0
  369. package/dist/tree/json-codec.js.map +1 -0
  370. package/dist/tree/mimefs.d.ts +13 -0
  371. package/dist/tree/mimefs.d.ts.map +1 -0
  372. package/dist/tree/mimefs.js +124 -0
  373. package/dist/tree/mimefs.js.map +1 -0
  374. package/dist/tree/mongo.d.ts +5 -0
  375. package/dist/tree/mongo.d.ts.map +1 -0
  376. package/dist/tree/mongo.js +110 -0
  377. package/dist/tree/mongo.js.map +1 -0
  378. package/dist/tree/patch.d.ts +30 -0
  379. package/dist/tree/patch.d.ts.map +1 -0
  380. package/dist/tree/patch.js +112 -0
  381. package/dist/tree/patch.js.map +1 -0
  382. package/dist/tree/query.d.ts +12 -0
  383. package/dist/tree/query.d.ts.map +1 -0
  384. package/dist/tree/query.js +61 -0
  385. package/dist/tree/query.js.map +1 -0
  386. package/dist/tree/repath.d.ts +3 -0
  387. package/dist/tree/repath.d.ts.map +1 -0
  388. package/dist/tree/repath.js +38 -0
  389. package/dist/tree/repath.js.map +1 -0
  390. package/dist/tree-chain.d.ts +19 -0
  391. package/dist/tree-chain.d.ts.map +1 -0
  392. package/dist/tree-chain.js +111 -0
  393. package/dist/tree-chain.js.map +1 -0
  394. package/dist/tree.d.ts +2 -0
  395. package/dist/tree.d.ts.map +1 -0
  396. package/dist/tree.js +2 -0
  397. package/dist/tree.js.map +1 -0
  398. package/dist/uri.d.ts +11 -0
  399. package/dist/uri.d.ts.map +1 -0
  400. package/dist/uri.js +84 -0
  401. package/dist/uri.js.map +1 -0
  402. package/package.json +78 -37
  403. package/src/chain.test.ts +190 -0
  404. package/src/chain.ts +84 -0
  405. package/src/client/client.test.ts +192 -0
  406. package/src/client/handle.ts +53 -0
  407. package/src/client/index.ts +18 -0
  408. package/src/client/trpc.ts +92 -0
  409. package/src/client.ts +1 -0
  410. package/src/comp/CLAUDE.md +14 -0
  411. package/src/comp/handle.ts +36 -0
  412. package/src/comp/index.test.ts +165 -0
  413. package/src/comp/index.ts +186 -0
  414. package/src/comp/needs.test.ts +499 -0
  415. package/src/comp/needs.ts +113 -0
  416. package/src/comp/validate.test.ts +304 -0
  417. package/src/comp/validate.ts +125 -0
  418. package/src/comp.ts +1 -0
  419. package/src/contexts/schema/index.ts +7 -0
  420. package/src/contexts/schema.ts +1 -0
  421. package/src/contexts/service/index.test.ts +323 -0
  422. package/src/contexts/service/index.ts +43 -0
  423. package/src/contexts/service.ts +1 -0
  424. package/src/contexts/telegram/index.ts +115 -0
  425. package/src/contexts/text/index.test.ts +31 -0
  426. package/src/contexts/text/index.ts +18 -0
  427. package/src/contexts/text.ts +1 -0
  428. package/src/core/component.ts +153 -0
  429. package/src/core/context.ts +14 -0
  430. package/src/core/index.test.ts +224 -0
  431. package/src/core/index.ts +9 -0
  432. package/src/core/path.ts +38 -0
  433. package/src/core/registry.ts +112 -0
  434. package/src/core.ts +1 -0
  435. package/src/index.ts +1 -0
  436. package/src/log.test.ts +70 -0
  437. package/src/log.ts +199 -0
  438. package/src/mod/discover.test.ts +133 -0
  439. package/src/mod/discover.ts +100 -0
  440. package/src/mod/docs/00-index.md +19 -0
  441. package/src/mod/docs/01-primitives.md +38 -0
  442. package/src/mod/docs/02-core-api.md +68 -0
  443. package/src/mod/docs/03-registry.md +30 -0
  444. package/src/mod/docs/04-store.md +62 -0
  445. package/src/mod/docs/05-comp.md +111 -0
  446. package/src/mod/docs/06-actions.md +193 -0
  447. package/src/mod/docs/07-realtime.md +108 -0
  448. package/src/mod/docs/08-services.md +35 -0
  449. package/src/mod/docs/09-mounts.md +43 -0
  450. package/src/mod/docs/10-acl.md +60 -0
  451. package/src/mod/docs/11-server.md +62 -0
  452. package/src/mod/docs/12-conventions.md +108 -0
  453. package/src/mod/docs/13-example.md +142 -0
  454. package/src/mod/docs/14-mod-format.md +377 -0
  455. package/src/mod/docs/15-documenting-types.md +156 -0
  456. package/src/mod/examples/ticker/seed.ts +19 -0
  457. package/src/mod/examples/ticker/service.ts +19 -0
  458. package/src/mod/examples/ticker/ticker.test.ts +18 -0
  459. package/src/mod/examples/ticker/types.ts +22 -0
  460. package/src/mod/examples/ticker/view.tsx +19 -0
  461. package/src/mod/index.ts +12 -0
  462. package/src/mod/loader.test.ts +220 -0
  463. package/src/mod/loader.ts +207 -0
  464. package/src/mod/optimistic.test.ts +446 -0
  465. package/src/mod/optimistic.ts +210 -0
  466. package/src/mod/prefab.ts +62 -0
  467. package/src/mod/tracking.test.ts +59 -0
  468. package/src/mod/tracking.ts +51 -0
  469. package/src/mod/types.ts +40 -0
  470. package/src/mod.ts +1 -0
  471. package/src/mods/autostart/CLAUDE.md +6 -0
  472. package/src/mods/autostart/autostart.test.ts +85 -0
  473. package/src/mods/autostart/server.ts +1 -0
  474. package/src/mods/autostart/service.ts +98 -0
  475. package/src/mods/clients.ts +2 -0
  476. package/src/mods/llm/CLAUDE.md +6 -0
  477. package/src/mods/llm/index.ts +57 -0
  478. package/src/mods/llm/llm.test.ts +109 -0
  479. package/src/mods/llm/server.ts +1 -0
  480. package/src/mods/servers.ts +4 -0
  481. package/src/mods/treenity/agent-port.ts +93 -0
  482. package/src/mods/treenity/builtins.ts +21 -0
  483. package/src/mods/treenity/groups.ts +19 -0
  484. package/src/mods/treenity/logs.ts +26 -0
  485. package/src/mods/treenity/mod-type.ts +10 -0
  486. package/src/mods/treenity/seed.ts +58 -0
  487. package/src/mods/treenity/server.ts +6 -0
  488. package/src/mods/treenity/system.ts +70 -0
  489. package/src/mods/uix/CLAUDE.md +7 -0
  490. package/src/mods/uix/client.ts +117 -0
  491. package/src/mods/uix/compile.test.ts +228 -0
  492. package/src/mods/uix/compile.ts +112 -0
  493. package/src/mods/uix/jsx-parser.test.ts +554 -0
  494. package/src/mods/uix/jsx-parser.ts +489 -0
  495. package/src/mods/uix/lazy-load.test.ts +261 -0
  496. package/src/mods/uix/uix-repomix.md +3509 -0
  497. package/src/mods/uix/verify.ts +68 -0
  498. package/src/schema/CLAUDE.md +13 -0
  499. package/src/schema/_test-fixture.ts +12 -0
  500. package/src/schema/catalog.ts +101 -0
  501. package/src/schema/extract-schemas.test.ts +84 -0
  502. package/src/schema/extract-schemas.ts +462 -0
  503. package/src/schema/generated/ai.agent.json +133 -0
  504. package/src/schema/generated/ai.approval.json +105 -0
  505. package/src/schema/generated/ai.approvals.json +24 -0
  506. package/src/schema/generated/ai.assignment.json +28 -0
  507. package/src/schema/generated/ai.plan.json +84 -0
  508. package/src/schema/generated/ai.policy.json +105 -0
  509. package/src/schema/generated/ai.pool.json +37 -0
  510. package/src/schema/generated/ai.thread.json +64 -0
  511. package/src/schema/generated/autostart.json +44 -0
  512. package/src/schema/generated/board.column.json +25 -0
  513. package/src/schema/generated/board.kanban.json +7 -0
  514. package/src/schema/generated/board.task.json +147 -0
  515. package/src/schema/generated/brahman.action.back.json +12 -0
  516. package/src/schema/generated/brahman.action.broadcast.json +31 -0
  517. package/src/schema/generated/brahman.action.call.json +57 -0
  518. package/src/schema/generated/brahman.action.emittext.json +23 -0
  519. package/src/schema/generated/brahman.action.eval.json +23 -0
  520. package/src/schema/generated/brahman.action.file.json +28 -0
  521. package/src/schema/generated/brahman.action.forward.json +29 -0
  522. package/src/schema/generated/brahman.action.getvalue.json +28 -0
  523. package/src/schema/generated/brahman.action.ifelse.json +42 -0
  524. package/src/schema/generated/brahman.action.keywordselect.json +46 -0
  525. package/src/schema/generated/brahman.action.message.json +127 -0
  526. package/src/schema/generated/brahman.action.onerror.json +29 -0
  527. package/src/schema/generated/brahman.action.page.json +22 -0
  528. package/src/schema/generated/brahman.action.params.json +36 -0
  529. package/src/schema/generated/brahman.action.question.json +43 -0
  530. package/src/schema/generated/brahman.action.remove.json +12 -0
  531. package/src/schema/generated/brahman.action.resethistory.json +12 -0
  532. package/src/schema/generated/brahman.action.resetsession.json +12 -0
  533. package/src/schema/generated/brahman.action.selectlang.json +20 -0
  534. package/src/schema/generated/brahman.action.setvalue.json +27 -0
  535. package/src/schema/generated/brahman.action.tag.json +27 -0
  536. package/src/schema/generated/brahman.bot.json +68 -0
  537. package/src/schema/generated/brahman.page.json +25 -0
  538. package/src/schema/generated/brahman.session.json +29 -0
  539. package/src/schema/generated/brahman.user.json +58 -0
  540. package/src/schema/generated/cafe.contact.json +56 -0
  541. package/src/schema/generated/cafe.mail.json +29 -0
  542. package/src/schema/generated/canary.item.json +40 -0
  543. package/src/schema/generated/claude-search.json +20 -0
  544. package/src/schema/generated/craft.product.json +47 -0
  545. package/src/schema/generated/craft.shop.json +94 -0
  546. package/src/schema/generated/craft.subscription.json +27 -0
  547. package/src/schema/generated/default.json +15 -0
  548. package/src/schema/generated/doc.page.json +23 -0
  549. package/src/schema/generated/examples.demo.generator.json +16 -0
  550. package/src/schema/generated/examples.demo.sensor.json +35 -0
  551. package/src/schema/generated/examples.demo.sensor.reading.json +25 -0
  552. package/src/schema/generated/flow.node.action.json +61 -0
  553. package/src/schema/generated/flow.node.code.json +43 -0
  554. package/src/schema/generated/flow.node.condition.json +37 -0
  555. package/src/schema/generated/flow.node.end.json +35 -0
  556. package/src/schema/generated/flow.node.http.json +65 -0
  557. package/src/schema/generated/flow.node.llm.json +67 -0
  558. package/src/schema/generated/flow.node.loop.json +49 -0
  559. package/src/schema/generated/flow.node.start.json +39 -0
  560. package/src/schema/generated/flow.scenario.json +118 -0
  561. package/src/schema/generated/groups.json +20 -0
  562. package/src/schema/generated/grove.attempt.json +199 -0
  563. package/src/schema/generated/grove.path.json +93 -0
  564. package/src/schema/generated/grove.review.json +27 -0
  565. package/src/schema/generated/grove.task.json +164 -0
  566. package/src/schema/generated/intel.scenario.json +58 -0
  567. package/src/schema/generated/intel.signal.json +113 -0
  568. package/src/schema/generated/intel.world.json +15 -0
  569. package/src/schema/generated/landing.block.json +201 -0
  570. package/src/schema/generated/landing.page.json +84 -0
  571. package/src/schema/generated/launcher.json +91 -0
  572. package/src/schema/generated/mcp.server.json +15 -0
  573. package/src/schema/generated/metatron.config.json +119 -0
  574. package/src/schema/generated/metatron.permission.json +36 -0
  575. package/src/schema/generated/metatron.skill.json +36 -0
  576. package/src/schema/generated/metatron.task.json +114 -0
  577. package/src/schema/generated/metatron.template.json +26 -0
  578. package/src/schema/generated/metatron.workspace.json +60 -0
  579. package/src/schema/generated/mindmap.map.json +22 -0
  580. package/src/schema/generated/order.status.json +21 -0
  581. package/src/schema/generated/polyhope.backtest.json +161 -0
  582. package/src/schema/generated/polyhope.feed.json +33 -0
  583. package/src/schema/generated/polyhope.run.json +94 -0
  584. package/src/schema/generated/polyhope.strategy.json +152 -0
  585. package/src/schema/generated/polymax.activity.json +65 -0
  586. package/src/schema/generated/polymax.aggr-feed.json +28 -0
  587. package/src/schema/generated/polymax.aggr.json +20 -0
  588. package/src/schema/generated/polymax.alert.json +56 -0
  589. package/src/schema/generated/polymax.bot-config.json +14 -0
  590. package/src/schema/generated/polymax.bot-status.json +35 -0
  591. package/src/schema/generated/polymax.classification.json +55 -0
  592. package/src/schema/generated/polymax.deposits.json +45 -0
  593. package/src/schema/generated/polymax.holding.json +55 -0
  594. package/src/schema/generated/polymax.identity.json +71 -0
  595. package/src/schema/generated/polymax.lb-entry.json +75 -0
  596. package/src/schema/generated/polymax.leaderboard.json +37 -0
  597. package/src/schema/generated/polymax.market-ref.json +25 -0
  598. package/src/schema/generated/polymax.performance.json +65 -0
  599. package/src/schema/generated/polymax.profile.json +16 -0
  600. package/src/schema/generated/polymax.scan-result.json +50 -0
  601. package/src/schema/generated/polymax.status.json +40 -0
  602. package/src/schema/generated/polymax.tags.json +53 -0
  603. package/src/schema/generated/polymax.trader.json +16 -0
  604. package/src/schema/generated/polymax.wallet-market.json +50 -0
  605. package/src/schema/generated/polymax.wallet-pnl.json +35 -0
  606. package/src/schema/generated/pult.concept.json +53 -0
  607. package/src/schema/generated/pult.config.json +227 -0
  608. package/src/schema/generated/pult.connector.json +72 -0
  609. package/src/schema/generated/pult.market.json +113 -0
  610. package/src/schema/generated/pult.order.json +113 -0
  611. package/src/schema/generated/pult.rete.json +68 -0
  612. package/src/schema/generated/pult.sensor.json +74 -0
  613. package/src/schema/generated/pult.signal.json +54 -0
  614. package/src/schema/generated/pult.synapse.json +93 -0
  615. package/src/schema/generated/pult.trade.json +93 -0
  616. package/src/schema/generated/resim.config.json +34 -0
  617. package/src/schema/generated/resim.function.json +62 -0
  618. package/src/schema/generated/resim.goal.json +22 -0
  619. package/src/schema/generated/resim.resource.json +40 -0
  620. package/src/schema/generated/resim.state.json +48 -0
  621. package/src/schema/generated/resim.world.json +40 -0
  622. package/src/schema/generated/saveme.action.save.json +29 -0
  623. package/src/schema/generated/saveme.message.json +36 -0
  624. package/src/schema/generated/saveme.router.json +31 -0
  625. package/src/schema/generated/sim.agent.json +24 -0
  626. package/src/schema/generated/sim.ai.json +24 -0
  627. package/src/schema/generated/sim.config.json +38 -0
  628. package/src/schema/generated/sim.descriptive.json +26 -0
  629. package/src/schema/generated/sim.events.json +47 -0
  630. package/src/schema/generated/sim.item.json +20 -0
  631. package/src/schema/generated/sim.memory.json +17 -0
  632. package/src/schema/generated/sim.nearby.json +17 -0
  633. package/src/schema/generated/sim.position.json +25 -0
  634. package/src/schema/generated/sim.round.json +64 -0
  635. package/src/schema/generated/sim.world.json +32 -0
  636. package/src/schema/generated/t.agent.port.json +74 -0
  637. package/src/schema/generated/t.coolify.json +50 -0
  638. package/src/schema/generated/t.event.json +46 -0
  639. package/src/schema/generated/t.llm.json +20 -0
  640. package/src/schema/generated/t.logs.json +155 -0
  641. package/src/schema/generated/t.mod.json +27 -0
  642. package/src/schema/generated/t.note.json +31 -0
  643. package/src/schema/generated/t.person.json +36 -0
  644. package/src/schema/generated/t.tenant.json +57 -0
  645. package/src/schema/generated/t.tenant.status.json +42 -0
  646. package/src/schema/generated/t3d.animator.json +46 -0
  647. package/src/schema/generated/t3d.audio.json +58 -0
  648. package/src/schema/generated/t3d.camera.json +50 -0
  649. package/src/schema/generated/t3d.collider.json +84 -0
  650. package/src/schema/generated/t3d.light.json +90 -0
  651. package/src/schema/generated/t3d.line.json +47 -0
  652. package/src/schema/generated/t3d.lod.json +28 -0
  653. package/src/schema/generated/t3d.material.json +131 -0
  654. package/src/schema/generated/t3d.mesh.json +65 -0
  655. package/src/schema/generated/t3d.object.json +64 -0
  656. package/src/schema/generated/t3d.particles.json +109 -0
  657. package/src/schema/generated/t3d.rigidbody.json +81 -0
  658. package/src/schema/generated/t3d.scene.json +7 -0
  659. package/src/schema/generated/t3d.script.json +23 -0
  660. package/src/schema/generated/t3d.text.json +86 -0
  661. package/src/schema/generated/t3d.trail.json +45 -0
  662. package/src/schema/generated/tagger.config.json +115 -0
  663. package/src/schema/generated/tagger.result.json +57 -0
  664. package/src/schema/generated/tagger.tree.json +36 -0
  665. package/src/schema/generated/task.json +96 -0
  666. package/src/schema/generated/test.fixture.json +43 -0
  667. package/src/schema/generated/ticker.config.json +43 -0
  668. package/src/schema/generated/ticker.price.json +20 -0
  669. package/src/schema/generated/todo.item.json +25 -0
  670. package/src/schema/generated/todo.list.json +33 -0
  671. package/src/schema/generated/treenity.system.json +259 -0
  672. package/src/schema/generated/ui.table.json +46 -0
  673. package/src/schema/generated/whisper.audio.json +25 -0
  674. package/src/schema/generated/whisper.checklist.json +17 -0
  675. package/src/schema/generated/whisper.config.json +30 -0
  676. package/src/schema/generated/whisper.inbox.json +24 -0
  677. package/src/schema/generated/whisper.meta.json +35 -0
  678. package/src/schema/generated/whisper.text.json +16 -0
  679. package/src/schema/load.ts +24 -0
  680. package/src/schema/schema.test.ts +86 -0
  681. package/src/schema/test-fixture.types.ts +21 -0
  682. package/src/schema/types.ts +34 -0
  683. package/src/server/CLAUDE.md +26 -0
  684. package/src/server/actions.test.ts +272 -0
  685. package/src/server/actions.ts +274 -0
  686. package/src/server/agent-sub.test.ts +90 -0
  687. package/src/server/agent.test.ts +305 -0
  688. package/src/server/agent.ts +17 -0
  689. package/src/server/api.test.ts +463 -0
  690. package/src/server/auth.test.ts +441 -0
  691. package/src/server/auth.ts +387 -0
  692. package/src/server/client.ts +24 -0
  693. package/src/server/conditions.test.ts +128 -0
  694. package/src/server/cookies.ts +25 -0
  695. package/src/server/coverage.test.ts +827 -0
  696. package/src/server/doc-index.ts +287 -0
  697. package/src/server/e2e.test.ts +967 -0
  698. package/src/server/errors.ts +11 -0
  699. package/src/server/factory.ts +121 -0
  700. package/src/server/main.ts +24 -0
  701. package/src/server/migrate.test.ts +123 -0
  702. package/src/server/migrate.ts +62 -0
  703. package/src/server/mod-catalog.ts +59 -0
  704. package/src/server/mods-mount.ts +177 -0
  705. package/src/server/mount-adapters.ts +87 -0
  706. package/src/server/mount.parametrized.test.ts +52 -0
  707. package/src/server/mount.query.test.ts +127 -0
  708. package/src/server/mount.test.ts +565 -0
  709. package/src/server/mount.ts +208 -0
  710. package/src/server/prefab.test.ts +415 -0
  711. package/src/server/prefab.ts +105 -0
  712. package/src/server/refs.test.ts +82 -0
  713. package/src/server/refs.ts +64 -0
  714. package/src/server/seed/index.ts +24 -0
  715. package/src/server/server.ts +164 -0
  716. package/src/server/stress.test.ts +844 -0
  717. package/src/server/sub.test.ts +55 -0
  718. package/src/server/sub.ts +224 -0
  719. package/src/server/trpc.ts +375 -0
  720. package/src/server/types-mount.ts +142 -0
  721. package/src/server/validate.test.ts +91 -0
  722. package/src/server/validate.ts +22 -0
  723. package/src/server/volatile.test.ts +140 -0
  724. package/src/server/volatile.ts +32 -0
  725. package/src/server/watch.test.ts +594 -0
  726. package/src/server/watch.ts +202 -0
  727. package/src/server/workflow.test.ts +82 -0
  728. package/src/tree/CLAUDE.md +13 -0
  729. package/src/tree/cache.test.ts +213 -0
  730. package/src/tree/cache.ts +51 -0
  731. package/src/tree/fs.test.ts +247 -0
  732. package/src/tree/fs.ts +257 -0
  733. package/src/tree/index.test.ts +190 -0
  734. package/src/tree/index.ts +217 -0
  735. package/src/tree/inflight.ts +15 -0
  736. package/src/tree/json-codec.ts +16 -0
  737. package/src/tree/mimefs.test.ts +289 -0
  738. package/src/tree/mimefs.ts +142 -0
  739. package/src/tree/mongo.ts +125 -0
  740. package/src/tree/patch.test.ts +192 -0
  741. package/src/tree/patch.ts +133 -0
  742. package/src/tree/query.test.ts +110 -0
  743. package/src/tree/query.ts +70 -0
  744. package/src/tree/repath.test.ts +86 -0
  745. package/src/tree/repath.ts +53 -0
  746. package/src/tree-chain.test.ts +716 -0
  747. package/src/tree-chain.ts +150 -0
  748. package/src/tree.ts +1 -0
  749. package/src/uri.test.ts +113 -0
  750. package/src/uri.ts +84 -0
  751. package/CHANGELOG.md +0 -314
  752. package/dist/context.d.ts +0 -41
  753. package/dist/context.d.ts.map +0 -1
  754. package/dist/context.mjs +0 -81
  755. package/dist/context.mjs.map +0 -1
  756. package/dist/contexts/node-engine.d.ts +0 -12
  757. package/dist/contexts/node-engine.d.ts.map +0 -1
  758. package/dist/contexts/node-engine.mjs +0 -7
  759. package/dist/contexts/node-engine.mjs.map +0 -1
  760. package/dist/contexts/noflo/types.d.ts +0 -20
  761. package/dist/contexts/noflo/types.d.ts.map +0 -1
  762. package/dist/contexts/object.d.ts +0 -11
  763. package/dist/contexts/object.d.ts.map +0 -1
  764. package/dist/contexts/object.mjs +0 -15
  765. package/dist/contexts/object.mjs.map +0 -1
  766. package/dist/contexts/proto.d.ts +0 -11
  767. package/dist/contexts/proto.d.ts.map +0 -1
  768. package/dist/contexts/proto.mjs +0 -7
  769. package/dist/contexts/proto.mjs.map +0 -1
  770. package/dist/contexts/react-context.d.ts +0 -21
  771. package/dist/contexts/react-context.d.ts.map +0 -1
  772. package/dist/contexts/react-context.mjs +0 -24
  773. package/dist/contexts/react-context.mjs.map +0 -1
  774. package/dist/contexts/service.mjs +0 -7
  775. package/dist/contexts/service.mjs.map +0 -1
  776. package/dist/get-type-cache.d.ts +0 -2
  777. package/dist/get-type-cache.d.ts.map +0 -1
  778. package/dist/get-type-cache.mjs +0 -7
  779. package/dist/get-type-cache.mjs.map +0 -1
  780. package/dist/index.mjs +0 -10
  781. package/dist/index.mjs.map +0 -1
  782. package/dist/link/link.d.ts +0 -25
  783. package/dist/link/link.d.ts.map +0 -1
  784. package/dist/link/link.mjs +0 -72
  785. package/dist/link/link.mjs.map +0 -1
  786. package/dist/link/link.test.d.ts +0 -2
  787. package/dist/link/link.test.d.ts.map +0 -1
  788. package/dist/loading.d.ts +0 -9
  789. package/dist/loading.d.ts.map +0 -1
  790. package/dist/meta-type.d.ts +0 -58
  791. package/dist/meta-type.d.ts.map +0 -1
  792. package/dist/meta-type.mjs +0 -104
  793. package/dist/meta-type.mjs.map +0 -1
  794. package/dist/meta.d.ts +0 -20
  795. package/dist/meta.d.ts.map +0 -1
  796. package/dist/meta.mjs +0 -16
  797. package/dist/meta.mjs.map +0 -1
  798. package/dist/node/index.d.ts +0 -2
  799. package/dist/node/index.d.ts.map +0 -1
  800. package/dist/node/types.d.ts +0 -37
  801. package/dist/node/types.d.ts.map +0 -1
  802. package/dist/test/context.test.d.ts +0 -2
  803. package/dist/test/context.test.d.ts.map +0 -1
  804. package/dist/types.d.ts +0 -14
  805. package/dist/types.d.ts.map +0 -1
  806. package/dist/types.mjs +0 -16
  807. package/dist/types.mjs.map +0 -1
@@ -0,0 +1,967 @@
1
+ // End-to-end tests — real HTTP server + tRPC client over the wire.
2
+ // Tests transport serialization, auth headers, subscriptions, patch streaming,
3
+ // ACL security (no leaked data), and action return values.
4
+
5
+ import { registerType } from '#comp';
6
+ import { createNode, R, S, W, register } from '#core';
7
+ import { createMemoryTree } from '#tree';
8
+ import assert from 'node:assert/strict';
9
+ import type { Socket } from 'node:net';
10
+ import './mount-adapters';
11
+ import { afterEach, before, beforeEach, describe, it } from 'node:test';
12
+ import { createClient } from './client';
13
+ import { createTreenityServer, type TreenityServer } from './server';
14
+ import { type NodeEvent } from './sub';
15
+
16
+ // ── Test components ──
17
+
18
+ class OrderStatus {
19
+ status = 'new';
20
+ cook() { this.status = 'kitchen'; }
21
+ deliver() { this.status = 'delivered'; }
22
+ }
23
+
24
+ class Returner {
25
+ getObject() { return { x: 1, nested: { y: 'hello' } }; }
26
+ getArray() { return [1, 'two', { three: 3 }]; }
27
+ getNull() { return null; }
28
+ getNumber() { return 42; }
29
+ }
30
+
31
+ class Streamer {
32
+ async *count(data: { n: number }) {
33
+ for (let i = 1; i <= data.n; i++) yield { i, total: data.n };
34
+ }
35
+ async *objects() {
36
+ yield { type: 'start' };
37
+ yield { items: [1, 2, 3] };
38
+ yield { type: 'end', summary: 'done' };
39
+ }
40
+ }
41
+
42
+ class Secret {
43
+ publicField = 'visible';
44
+ secretField = 'hidden';
45
+ }
46
+
47
+ class Priority {
48
+ level = 'low';
49
+ escalate() { this.level = 'high'; }
50
+ deescalate() { this.level = 'low'; }
51
+ }
52
+
53
+ // ── Helpers ──
54
+
55
+ type DataEvent = Exclude<NodeEvent, { type: 'reconnect' }>;
56
+
57
+ function listen(server: import('node:http').Server): Promise<number> {
58
+ return new Promise((resolve) => {
59
+ server.listen(0, '127.0.0.1', () => {
60
+ const addr = server.address() as { port: number };
61
+ resolve(addr.port);
62
+ });
63
+ });
64
+ }
65
+
66
+ // Collect subscription events with timeout.
67
+ // Uses `any` for onData param to avoid mismatch between our NodeEvent and tRPC's inferred type.
68
+ function collectEvents<T>(
69
+ subscribe: (callbacks: { onData: (d: any) => void; onComplete: () => void; onError: (e: unknown) => void }) => { unsubscribe: () => void },
70
+ opts: { count?: number; timeoutMs?: number } = {},
71
+ ): Promise<T[]> {
72
+ const { count = Infinity, timeoutMs = 3000 } = opts;
73
+ return new Promise((resolve) => {
74
+ const items: T[] = [];
75
+ let timer: ReturnType<typeof setTimeout>;
76
+ const sub = subscribe({
77
+ onData(d: NodeEvent) {
78
+ if (d.type === 'reconnect') return; // skip protocol event
79
+ items.push(d as T);
80
+ if (items.length >= count) { clearTimeout(timer); sub.unsubscribe(); resolve(items); }
81
+ },
82
+ onComplete() { clearTimeout(timer); resolve(items); },
83
+ onError() { clearTimeout(timer); resolve(items); },
84
+ });
85
+ timer = setTimeout(() => { sub.unsubscribe(); resolve(items); }, timeoutMs);
86
+ });
87
+ }
88
+
89
+ describe('e2e: tRPC over HTTP', () => {
90
+ let ts: TreenityServer;
91
+ let url: string;
92
+ const sockets = new Set<Socket>();
93
+
94
+ before(async () => {
95
+ registerType('order.status', OrderStatus);
96
+ registerType('returner', Returner);
97
+ registerType('streamer', Streamer);
98
+ registerType('secret', Secret);
99
+ registerType('task.priority', Priority);
100
+ register('test.task', 'schema', () => ({ type: 'object', title: 'Test Task' }));
101
+ });
102
+
103
+ beforeEach(async () => {
104
+ const bootstrap = createMemoryTree();
105
+ await bootstrap.set({
106
+ ...createNode('/', 'root'),
107
+ $acl: [{ g: 'public', p: R | W | S }, { g: 'authenticated', p: R | W | S }],
108
+ });
109
+
110
+ ts = createTreenityServer(bootstrap);
111
+ ts.server.on('connection', (socket: Socket) => {
112
+ sockets.add(socket);
113
+ socket.on('close', () => sockets.delete(socket));
114
+ });
115
+ const port = await listen(ts.server);
116
+ url = `http://127.0.0.1:${port}/`;
117
+ });
118
+
119
+ afterEach(async () => {
120
+ for (const s of sockets) s.destroy();
121
+ sockets.clear();
122
+ await new Promise<void>((resolve) => ts.server.close(() => resolve()));
123
+ });
124
+
125
+ // ── CRUD over HTTP ──
126
+
127
+ describe('CRUD', () => {
128
+ it('set + get roundtrip', async () => {
129
+ const client = createClient(url);
130
+ await client.set.mutate({ node: { $path: '/a', $type: 'doc', title: 'Hello' } });
131
+ const node = await client.get.query({ path: '/a' });
132
+ assert.equal(node?.$type, 'doc');
133
+ assert.equal((node as any).title, 'Hello');
134
+ });
135
+
136
+ it('getChildren with pagination', async () => {
137
+ const client = createClient(url);
138
+ await client.set.mutate({ node: { $path: '/p', $type: 'folder' } });
139
+ for (let i = 0; i < 5; i++)
140
+ await client.set.mutate({ node: { $path: `/p/${i}`, $type: 'doc' } });
141
+
142
+ const page = await client.getChildren.query({ path: '/p', limit: 2 });
143
+ assert.equal(page.items.length, 2);
144
+ assert.equal(page.total, 5);
145
+ });
146
+
147
+ it('remove works', async () => {
148
+ const client = createClient(url);
149
+ await client.set.mutate({ node: { $path: '/del', $type: 'doc' } });
150
+ assert.ok(await client.get.query({ path: '/del' }));
151
+ await client.remove.mutate({ path: '/del' });
152
+ assert.equal(await client.get.query({ path: '/del' }), undefined);
153
+ });
154
+ });
155
+
156
+ // ── Auth over HTTP ──
157
+
158
+ describe('auth', () => {
159
+ it('register → login → me (full auth flow)', async () => {
160
+ const pub = createClient(url);
161
+ const reg = await pub.register.mutate({ userId: 'bob', password: 'secret' });
162
+ assert.ok(reg.token);
163
+
164
+ const login = await pub.login.mutate({ userId: 'bob', password: 'secret' });
165
+ assert.ok(login.token);
166
+
167
+ const authed = createClient(url, login.token);
168
+ const me = await authed.me.query();
169
+ assert.equal(me?.userId, 'bob');
170
+ });
171
+
172
+ it('wrong password returns UNAUTHORIZED', async () => {
173
+ const client = createClient(url);
174
+ await client.register.mutate({ userId: 'u1', password: 'correct' });
175
+ await assert.rejects(
176
+ () => client.login.mutate({ userId: 'u1', password: 'wrong' }),
177
+ (e: any) => e.data?.httpStatus === 401 || e.data?.code === 'UNAUTHORIZED',
178
+ );
179
+ });
180
+
181
+ it('me returns null for unauthenticated', async () => {
182
+ const client = createClient(url);
183
+ const me = await client.me.query();
184
+ assert.equal(me, null);
185
+ });
186
+
187
+ it('anonLogin returns anon user', async () => {
188
+ const client = createClient(url);
189
+ const res = await client.anonLogin.mutate();
190
+ assert.ok(res.userId.startsWith('anon:'));
191
+ assert.ok(res.token);
192
+ });
193
+ });
194
+
195
+ // ── Error codes over HTTP ──
196
+
197
+ describe('error codes', () => {
198
+ it('NOT_FOUND serialized correctly', async () => {
199
+ const client = createClient(url);
200
+ await assert.rejects(
201
+ () => client.execute.mutate({ path: '/nope', action: 'x' }),
202
+ (e: any) => e.data?.code === 'NOT_FOUND',
203
+ );
204
+ });
205
+
206
+ it('BAD_REQUEST for unknown action', async () => {
207
+ const client = createClient(url);
208
+ await client.set.mutate({ node: { $path: '/n', $type: 'page', ret: { $type: 'returner' } } });
209
+ await assert.rejects(
210
+ () => client.execute.mutate({ path: '/n', key: 'ret', action: 'nonexistent' }),
211
+ (e: any) => e.data?.code === 'BAD_REQUEST',
212
+ );
213
+ });
214
+
215
+ it('CONFLICT on stale $rev', async () => {
216
+ const client = createClient(url);
217
+ await client.set.mutate({ node: { $path: '/rev', $type: 'doc', x: { $type: 'returner' } } });
218
+ await assert.rejects(
219
+ () => client.setComponent.mutate({ path: '/rev', name: 'x', data: { $type: 'returner' }, rev: 99 }),
220
+ (e: any) => e.data?.code === 'CONFLICT',
221
+ );
222
+ });
223
+ });
224
+
225
+ // ── Action return values ──
226
+
227
+ describe('action return values', () => {
228
+ it('execute returns object', async () => {
229
+ const client = createClient(url);
230
+ await client.set.mutate({ node: { $path: '/r', $type: 'page', ret: { $type: 'returner' } } });
231
+ const result = await client.execute.mutate({ path: '/r', key: 'ret', action: 'getObject' });
232
+ assert.deepEqual(result, { x: 1, nested: { y: 'hello' } });
233
+ });
234
+
235
+ it('execute returns array', async () => {
236
+ const client = createClient(url);
237
+ await client.set.mutate({ node: { $path: '/r2', $type: 'page', ret: { $type: 'returner' } } });
238
+ const result = await client.execute.mutate({ path: '/r2', key: 'ret', action: 'getArray' });
239
+ assert.deepEqual(result, [1, 'two', { three: 3 }]);
240
+ });
241
+
242
+ it('execute returns null', async () => {
243
+ const client = createClient(url);
244
+ await client.set.mutate({ node: { $path: '/r3', $type: 'page', ret: { $type: 'returner' } } });
245
+ const result = await client.execute.mutate({ path: '/r3', key: 'ret', action: 'getNull' });
246
+ assert.equal(result, null);
247
+ });
248
+
249
+ it('execute returns number', async () => {
250
+ const client = createClient(url);
251
+ await client.set.mutate({ node: { $path: '/r4', $type: 'page', ret: { $type: 'returner' } } });
252
+ const result = await client.execute.mutate({ path: '/r4', key: 'ret', action: 'getNumber' });
253
+ assert.equal(result, 42);
254
+ });
255
+ });
256
+
257
+ // ── Subscriptions ──
258
+
259
+ describe('subscriptions', () => {
260
+ it('events stream delivers set/patch/remove', async () => {
261
+ const pub = createClient(url);
262
+ const reg = await pub.register.mutate({ userId: 'watcher', password: 'pass' });
263
+ const client = createClient(url, reg.token);
264
+
265
+ await pub.set.mutate({ node: { $path: '/w', $type: 'page', meta: { $type: 'order.status', status: 'new' } } });
266
+
267
+ // Start subscription first (triggers watcher.connect on server)
268
+ const events = collectEvents<DataEvent>(
269
+ (cbs) => client.events.subscribe(undefined, cbs),
270
+ { count: 2, timeoutMs: 3000 },
271
+ );
272
+ await new Promise(r => setTimeout(r, 300));
273
+
274
+ // Now register watches
275
+ await client.get.query({ path: '/w', watch: true });
276
+
277
+ // Trigger events
278
+ await pub.execute.mutate({ path: '/w', key: 'meta', action: 'cook' });
279
+ await pub.remove.mutate({ path: '/w' });
280
+
281
+ const received = await events;
282
+ assert.ok(received.length >= 1, `Expected >=1 events, got ${received.length}`);
283
+ assert.ok(received.some(e => e.path === '/w'));
284
+ });
285
+
286
+ it('patch events include Immer patches', async () => {
287
+ const pub = createClient(url);
288
+ const reg = await pub.register.mutate({ userId: 'patcher', password: 'pass' });
289
+ const client = createClient(url, reg.token);
290
+
291
+ await pub.set.mutate({ node: { $path: '/pe', $type: 'page', meta: { $type: 'order.status', status: 'new' } } });
292
+
293
+ const events = collectEvents<DataEvent>(
294
+ (cbs) => client.events.subscribe(undefined, cbs),
295
+ { count: 1, timeoutMs: 3000 },
296
+ );
297
+ await new Promise(r => setTimeout(r, 300));
298
+
299
+ await client.get.query({ path: '/pe', watch: true });
300
+ await pub.execute.mutate({ path: '/pe', key: 'meta', action: 'cook' });
301
+ const received = await events;
302
+
303
+ const patchEvent = received.find(e => e.path === '/pe' && e.type === 'patch');
304
+ assert.ok(patchEvent, 'Should receive patch event');
305
+ assert.ok('patches' in patchEvent! && Array.isArray(patchEvent.patches));
306
+ assert.ok(patchEvent.patches.length > 0, 'Patches should be non-empty');
307
+ });
308
+
309
+ it('streamAction streams generator results', async () => {
310
+ const pub = createClient(url);
311
+ const reg = await pub.register.mutate({ userId: 'streamer', password: 'pass' });
312
+ const client = createClient(url, reg.token);
313
+
314
+ await pub.set.mutate({ node: { $path: '/s', $type: 'page', str: { $type: 'streamer' } } });
315
+
316
+ const items = await collectEvents<unknown>(
317
+ (cbs) => client.streamAction.subscribe({ path: '/s', key: 'str', action: 'count', data: { n: 3 } }, cbs),
318
+ { count: 3, timeoutMs: 3000 },
319
+ );
320
+
321
+ assert.equal(items.length, 3);
322
+ assert.deepEqual(items, [
323
+ { i: 1, total: 3 },
324
+ { i: 2, total: 3 },
325
+ { i: 3, total: 3 },
326
+ ]);
327
+ });
328
+
329
+ it('streamAction streams objects', async () => {
330
+ const pub = createClient(url);
331
+ const reg = await pub.register.mutate({ userId: 'streamer2', password: 'pass' });
332
+ const client = createClient(url, reg.token);
333
+
334
+ await pub.set.mutate({ node: { $path: '/s2', $type: 'page', str: { $type: 'streamer' } } });
335
+
336
+ const items = await collectEvents<unknown>(
337
+ (cbs) => client.streamAction.subscribe({ path: '/s2', key: 'str', action: 'objects' }, cbs),
338
+ { count: 3, timeoutMs: 3000 },
339
+ );
340
+
341
+ assert.equal(items.length, 3);
342
+ assert.deepEqual(items[0], { type: 'start' });
343
+ assert.deepEqual(items[1], { items: [1, 2, 3] });
344
+ assert.deepEqual(items[2], { type: 'end', summary: 'done' });
345
+ });
346
+ });
347
+
348
+ // ── ACL security ──
349
+
350
+ describe('ACL security', () => {
351
+ it('unauthenticated cannot read private nodes', async () => {
352
+ const pub = createClient(url);
353
+ await ts.store.set({
354
+ ...createNode('/secret', 'folder'),
355
+ $acl: [{ g: 'admins', p: R | W }, { g: 'public', p: 0 }],
356
+ });
357
+ await ts.store.set(createNode('/secret/data', 'doc'));
358
+
359
+ const node = await pub.get.query({ path: '/secret/data' });
360
+ assert.equal(node, undefined, 'Public should not see private node');
361
+ });
362
+
363
+ it('unauthenticated cannot write to private paths', async () => {
364
+ const pub = createClient(url);
365
+ await ts.store.set({
366
+ ...createNode('/private', 'folder'),
367
+ $acl: [{ g: 'admins', p: R | W }, { g: 'public', p: 0 }],
368
+ });
369
+
370
+ await assert.rejects(
371
+ () => pub.set.mutate({ node: { $path: '/private/hack', $type: 'doc' } }),
372
+ (e: any) => e.data?.code === 'FORBIDDEN',
373
+ );
374
+ });
375
+
376
+ it('getChildren does not leak private children', async () => {
377
+ const pub = createClient(url);
378
+ await ts.store.set(createNode('/mix', 'folder'));
379
+ await ts.store.set(createNode('/mix/public', 'doc'));
380
+ await ts.store.set({
381
+ ...createNode('/mix/private', 'doc'),
382
+ $acl: [{ g: 'admins', p: R | W }, { g: 'public', p: 0 }],
383
+ });
384
+
385
+ const result = await pub.getChildren.query({ path: '/mix' });
386
+ const paths = result.items.map(n => n.$path);
387
+ assert.ok(paths.includes('/mix/public'), 'Public child should be visible');
388
+ assert.ok(!paths.includes('/mix/private'), 'Private child should be hidden');
389
+ });
390
+
391
+ it('events do not leak to unauthorized subscribers', async () => {
392
+ const pub = createClient(url);
393
+ await pub.register.mutate({ userId: 'alice-sec', password: 'pass' });
394
+ const bob = await pub.register.mutate({ userId: 'bob-sec', password: 'pass' });
395
+
396
+ // Make alice an admin
397
+ await ts.store.set({
398
+ ...createNode('/auth/groups/admins', 'group'),
399
+ members: { $type: 'members', list: ['alice-sec'] },
400
+ });
401
+
402
+ // Create a private node only admins can see
403
+ await ts.store.set({
404
+ ...createNode('/classified', 'doc'),
405
+ $acl: [{ g: 'admins', p: R | W | S }, { g: 'public', p: 0 }],
406
+ });
407
+
408
+ const bobClient = createClient(url, bob.token);
409
+
410
+ // Bob subscribes first
411
+ const bobEvents = collectEvents<DataEvent>(
412
+ (cbs) => bobClient.events.subscribe(undefined, cbs),
413
+ { timeoutMs: 1500 },
414
+ );
415
+ await new Promise(r => setTimeout(r, 300));
416
+
417
+ // Bob watches root (public)
418
+ await bobClient.get.query({ path: '/', watch: true });
419
+
420
+ // Mutate classified — bob should NOT see this
421
+ await ts.store.set({ ...createNode('/classified', 'doc'), updated: true });
422
+ await new Promise(r => setTimeout(r, 200));
423
+
424
+ const received = await bobEvents;
425
+ const leaked = received.filter(e => e.path === '/classified');
426
+ assert.equal(leaked.length, 0, 'Bob should NOT receive events for /classified');
427
+ });
428
+
429
+ it('$acl and $owner stripped for non-admin users', async () => {
430
+ const pub = createClient(url);
431
+ const reg = await pub.register.mutate({ userId: 'viewer', password: 'pass' });
432
+ const viewer = createClient(url, reg.token);
433
+
434
+ await ts.store.set({
435
+ ...createNode('/owned', 'doc'),
436
+ $owner: 'someadmin',
437
+ $acl: [{ g: 'authenticated', p: R | S }],
438
+ });
439
+
440
+ const node = await viewer.get.query({ path: '/owned' });
441
+ assert.ok(node, 'Node should be readable');
442
+ assert.equal((node as any).$acl, undefined, '$acl should be stripped');
443
+ assert.equal((node as any).$owner, undefined, '$owner should be stripped');
444
+ });
445
+ });
446
+
447
+ // ── CDC over HTTP ──
448
+
449
+ describe('CDC Matrix over HTTP', () => {
450
+ it('query mount + action transition works over wire', async () => {
451
+ const client = createClient(url);
452
+
453
+ await client.set.mutate({ node: { $path: '/orders', $type: 'folder' } });
454
+ await client.set.mutate({ node: { $path: '/orders/data', $type: 'folder' } });
455
+ await client.set.mutate({ node: {
456
+ $path: '/orders/data/1', $type: 'page',
457
+ status: { $type: 'order.status', status: 'new' },
458
+ } });
459
+ await client.set.mutate({ node: {
460
+ $path: '/orders/new', $type: 'folder',
461
+ mount: { $type: 't.mount.query' },
462
+ query: { $type: 'query', source: '/orders/data', match: { 'status.status': 'new' } },
463
+ } });
464
+ await client.set.mutate({ node: {
465
+ $path: '/orders/kitchen', $type: 'folder',
466
+ mount: { $type: 't.mount.query' },
467
+ query: { $type: 'query', source: '/orders/data', match: { 'status.status': 'kitchen' } },
468
+ } });
469
+
470
+ let newOrders = await client.getChildren.query({ path: '/orders/new' });
471
+ assert.equal(newOrders.items.length, 1);
472
+
473
+ await client.execute.mutate({ path: '/orders/data/1', key: 'status', action: 'cook' });
474
+
475
+ newOrders = await client.getChildren.query({ path: '/orders/new' });
476
+ const kitchen = await client.getChildren.query({ path: '/orders/kitchen' });
477
+ assert.equal(newOrders.items.length, 0, 'Left /orders/new');
478
+ assert.equal(kitchen.items.length, 1, 'Entered /orders/kitchen');
479
+ });
480
+ });
481
+
482
+ // ── Helper: set up a query mount ──
483
+
484
+ async function setupQueryMount(
485
+ client: ReturnType<typeof createClient>,
486
+ vpPath: string,
487
+ sourcePath: string,
488
+ match: Record<string, unknown>,
489
+ ) {
490
+ await client.set.mutate({ node: {
491
+ $path: vpPath, $type: 'folder',
492
+ mount: { $type: 't.mount.query' },
493
+ query: { $type: 'query', source: sourcePath, match },
494
+ } });
495
+ }
496
+
497
+ // ── CDC Matrix live events ──
498
+
499
+ describe('CDC Matrix live events', () => {
500
+ it('action transition emits addVps/rmVps in live event', async () => {
501
+ const pub = createClient(url);
502
+ const reg = await pub.register.mutate({ userId: 'cdc-live1', password: 'pass' });
503
+ const client = createClient(url, reg.token);
504
+
505
+ // Data
506
+ await pub.set.mutate({ node: { $path: '/d1', $type: 'folder' } });
507
+ await pub.set.mutate({ node: {
508
+ $path: '/d1/o1', $type: 'page',
509
+ status: { $type: 'order.status', status: 'new' },
510
+ } });
511
+
512
+ // Query mounts
513
+ await pub.set.mutate({ node: { $path: '/qm1', $type: 'folder' } });
514
+ await setupQueryMount(pub, '/qm1/new', '/d1', { 'status.status': 'new' });
515
+ await setupQueryMount(pub, '/qm1/kitchen', '/d1', { 'status.status': 'kitchen' });
516
+
517
+ // Subscribe + register watches
518
+ const events = collectEvents<DataEvent>(
519
+ (cbs) => client.events.subscribe(undefined, cbs),
520
+ { count: 1, timeoutMs: 3000 },
521
+ );
522
+ await new Promise(r => setTimeout(r, 300));
523
+
524
+ await client.getChildren.query({ path: '/qm1/new', watchNew: true, watch: true });
525
+ await client.getChildren.query({ path: '/qm1/kitchen', watchNew: true, watch: true });
526
+
527
+ // Trigger transition
528
+ await pub.execute.mutate({ path: '/d1/o1', key: 'status', action: 'cook' });
529
+
530
+ const received = await events;
531
+ assert.ok(received.length >= 1, `Expected >=1 events, got ${received.length}`);
532
+ const ev = received.find(e => e.path === '/d1/o1')!;
533
+ assert.ok(ev, 'Should receive event for /d1/o1');
534
+ assert.ok((ev as any).rmVps?.includes('/qm1/new'), 'rmVps should contain /qm1/new');
535
+ assert.ok((ev as any).addVps?.includes('/qm1/kitchen'), 'addVps should contain /qm1/kitchen');
536
+ });
537
+
538
+ it('new node creation triggers addVps event', async () => {
539
+ const pub = createClient(url);
540
+ const reg = await pub.register.mutate({ userId: 'cdc-live2', password: 'pass' });
541
+ const client = createClient(url, reg.token);
542
+
543
+ await pub.set.mutate({ node: { $path: '/items2', $type: 'folder' } });
544
+ await pub.set.mutate({ node: { $path: '/qm2', $type: 'folder' } });
545
+ await setupQueryMount(pub, '/qm2/hot', '/items2', { 'pri.level': 'high' });
546
+
547
+ const events = collectEvents<DataEvent>(
548
+ (cbs) => client.events.subscribe(undefined, cbs),
549
+ { count: 1, timeoutMs: 3000 },
550
+ );
551
+ await new Promise(r => setTimeout(r, 300));
552
+
553
+ await client.getChildren.query({ path: '/qm2/hot', watchNew: true, watch: true });
554
+
555
+ // Create matching node
556
+ await pub.set.mutate({ node: {
557
+ $path: '/items2/t1', $type: 'task',
558
+ pri: { $type: 'task.priority', level: 'high' },
559
+ } });
560
+
561
+ const received = await events;
562
+ assert.ok(received.length >= 1, `Expected >=1 events, got ${received.length}`);
563
+ const ev = received.find(e => e.path === '/items2/t1')!;
564
+ assert.ok(ev, 'Should receive event for /items2/t1');
565
+ assert.ok((ev as any).addVps?.includes('/qm2/hot'), 'addVps should contain /qm2/hot');
566
+ });
567
+
568
+ it('node deletion triggers rmVps event', async () => {
569
+ const pub = createClient(url);
570
+ const reg = await pub.register.mutate({ userId: 'cdc-live3', password: 'pass' });
571
+ const client = createClient(url, reg.token);
572
+
573
+ await pub.set.mutate({ node: { $path: '/items3', $type: 'folder' } });
574
+ await pub.set.mutate({ node: {
575
+ $path: '/items3/t1', $type: 'task',
576
+ pri: { $type: 'task.priority', level: 'high' },
577
+ } });
578
+ await pub.set.mutate({ node: { $path: '/qm3', $type: 'folder' } });
579
+ await setupQueryMount(pub, '/qm3/hot', '/items3', { 'pri.level': 'high' });
580
+
581
+ const events = collectEvents<DataEvent>(
582
+ (cbs) => client.events.subscribe(undefined, cbs),
583
+ { count: 1, timeoutMs: 3000 },
584
+ );
585
+ await new Promise(r => setTimeout(r, 300));
586
+
587
+ await client.getChildren.query({ path: '/qm3/hot', watchNew: true, watch: true });
588
+
589
+ // Delete the matching node
590
+ await pub.remove.mutate({ path: '/items3/t1' });
591
+
592
+ const received = await events;
593
+ assert.ok(received.length >= 1, `Expected >=1 events, got ${received.length}`);
594
+ const ev = received.find(e => e.path === '/items3/t1')!;
595
+ assert.ok(ev, 'Should receive remove event for /items3/t1');
596
+ assert.equal(ev.type, 'remove');
597
+ assert.ok((ev as any).rmVps?.includes('/qm3/hot'), 'rmVps should contain /qm3/hot');
598
+ });
599
+
600
+ it('non-matching mutation produces no VP event', async () => {
601
+ const pub = createClient(url);
602
+ const reg = await pub.register.mutate({ userId: 'cdc-live4', password: 'pass' });
603
+ const client = createClient(url, reg.token);
604
+
605
+ await pub.set.mutate({ node: { $path: '/items4', $type: 'folder' } });
606
+ await pub.set.mutate({ node: {
607
+ $path: '/items4/t1', $type: 'task',
608
+ pri: { $type: 'task.priority', level: 'low' },
609
+ } });
610
+ await pub.set.mutate({ node: { $path: '/qm4', $type: 'folder' } });
611
+ await setupQueryMount(pub, '/qm4/hot', '/items4', { 'pri.level': 'high' });
612
+
613
+ const events = collectEvents<DataEvent>(
614
+ (cbs) => client.events.subscribe(undefined, cbs),
615
+ { timeoutMs: 800 },
616
+ );
617
+ await new Promise(r => setTimeout(r, 300));
618
+
619
+ await client.getChildren.query({ path: '/qm4/hot', watchNew: true, watch: true });
620
+
621
+ // Update non-matching node (still low priority)
622
+ await pub.set.mutate({ node: {
623
+ $path: '/items4/t1', $type: 'task',
624
+ pri: { $type: 'task.priority', level: 'low' },
625
+ extra: 'data',
626
+ } });
627
+
628
+ const received = await events;
629
+ const vpEvents = received.filter(e => e.path === '/items4/t1');
630
+ assert.equal(vpEvents.length, 0, 'Should receive no events for non-matching mutation');
631
+ });
632
+ });
633
+
634
+ // ── Query mount virtual API ──
635
+
636
+ describe('query mount virtual API', () => {
637
+ it('getChildren through query mount returns only matching nodes', async () => {
638
+ const client = createClient(url);
639
+
640
+ await client.set.mutate({ node: { $path: '/src5', $type: 'folder' } });
641
+ await client.set.mutate({ node: {
642
+ $path: '/src5/a', $type: 'page',
643
+ status: { $type: 'order.status', status: 'new' },
644
+ } });
645
+ await client.set.mutate({ node: {
646
+ $path: '/src5/b', $type: 'page',
647
+ status: { $type: 'order.status', status: 'kitchen' },
648
+ } });
649
+ await client.set.mutate({ node: {
650
+ $path: '/src5/c', $type: 'page',
651
+ status: { $type: 'order.status', status: 'new' },
652
+ } });
653
+
654
+ await client.set.mutate({ node: { $path: '/vm5', $type: 'folder' } });
655
+ await setupQueryMount(client, '/vm5/new', '/src5', { 'status.status': 'new' });
656
+
657
+ const result = await client.getChildren.query({ path: '/vm5/new' });
658
+ assert.equal(result.items.length, 2, 'Only 2 of 3 match status=new');
659
+
660
+ // All items have real paths from /src5/
661
+ for (const item of result.items) {
662
+ assert.ok(item.$path.startsWith('/src5/'), `Real path expected, got ${item.$path}`);
663
+ assert.equal((item as any).status.status, 'new');
664
+ }
665
+ });
666
+
667
+ it('execute action on node discovered via query mount', async () => {
668
+ const client = createClient(url);
669
+
670
+ await client.set.mutate({ node: { $path: '/src6', $type: 'folder' } });
671
+ await client.set.mutate({ node: {
672
+ $path: '/src6/o1', $type: 'page',
673
+ status: { $type: 'order.status', status: 'new' },
674
+ } });
675
+
676
+ await client.set.mutate({ node: { $path: '/vm6', $type: 'folder' } });
677
+ await setupQueryMount(client, '/vm6/new', '/src6', { 'status.status': 'new' });
678
+ await setupQueryMount(client, '/vm6/kitchen', '/src6', { 'status.status': 'kitchen' });
679
+
680
+ // Discover via query mount
681
+ const newOrders = await client.getChildren.query({ path: '/vm6/new' });
682
+ assert.equal(newOrders.items.length, 1);
683
+ const orderPath = newOrders.items[0].$path;
684
+
685
+ // Execute action on discovered path
686
+ await client.execute.mutate({ path: orderPath, key: 'status', action: 'cook' });
687
+
688
+ // Verify transition
689
+ const afterNew = await client.getChildren.query({ path: '/vm6/new' });
690
+ const afterKitchen = await client.getChildren.query({ path: '/vm6/kitchen' });
691
+ assert.equal(afterNew.items.length, 0, 'Node left /vm6/new');
692
+ assert.equal(afterKitchen.items.length, 1, 'Node entered /vm6/kitchen');
693
+ assert.equal(afterKitchen.items[0].$path, orderPath);
694
+ });
695
+
696
+ it('execute works on query mount children', async () => {
697
+ const client = createClient(url);
698
+
699
+ await client.set.mutate({ node: { $path: '/src7', $type: 'folder' } });
700
+ await client.set.mutate({ node: {
701
+ $path: '/src7/n1', $type: 'returner',
702
+ status: { $type: 'order.status', status: 'new' },
703
+ } });
704
+
705
+ await client.set.mutate({ node: { $path: '/vm7', $type: 'folder' } });
706
+ await setupQueryMount(client, '/vm7/new', '/src7', { 'status.status': 'new' });
707
+
708
+ // Discover via mount
709
+ const items = await client.getChildren.query({ path: '/vm7/new' });
710
+ assert.equal(items.items.length, 1);
711
+
712
+ // execute on the real path
713
+ const result = await client.execute.mutate({ path: items.items[0].$path, action: 'getObject' });
714
+ assert.deepEqual(result, { x: 1, nested: { y: 'hello' } });
715
+ });
716
+ });
717
+
718
+ // ── Order lifecycle with live watchers ──
719
+
720
+ describe('order lifecycle with live watchers', () => {
721
+ it('full lifecycle: new → kitchen → delivered with live events', async () => {
722
+ const pub = createClient(url);
723
+ const reg = await pub.register.mutate({ userId: 'lifecycle1', password: 'pass' });
724
+ const client = createClient(url, reg.token);
725
+
726
+ // Data
727
+ await pub.set.mutate({ node: { $path: '/lc', $type: 'folder' } });
728
+ await pub.set.mutate({ node: { $path: '/lc/data', $type: 'folder' } });
729
+ await pub.set.mutate({ node: {
730
+ $path: '/lc/data/o1', $type: 'page',
731
+ status: { $type: 'order.status', status: 'new' },
732
+ } });
733
+
734
+ // 3 query mounts
735
+ await setupQueryMount(pub, '/lc/new', '/lc/data', { 'status.status': 'new' });
736
+ await setupQueryMount(pub, '/lc/kitchen', '/lc/data', { 'status.status': 'kitchen' });
737
+ await setupQueryMount(pub, '/lc/delivered', '/lc/data', { 'status.status': 'delivered' });
738
+
739
+ // Subscribe
740
+ const events = collectEvents<DataEvent>(
741
+ (cbs) => client.events.subscribe(undefined, cbs),
742
+ { count: 2, timeoutMs: 5000 },
743
+ );
744
+ await new Promise(r => setTimeout(r, 300));
745
+
746
+ // Watch all 3 VPs
747
+ await client.getChildren.query({ path: '/lc/new', watchNew: true, watch: true });
748
+ await client.getChildren.query({ path: '/lc/kitchen', watchNew: true, watch: true });
749
+ await client.getChildren.query({ path: '/lc/delivered', watchNew: true, watch: true });
750
+
751
+ // Transition 1: new → kitchen
752
+ await pub.execute.mutate({ path: '/lc/data/o1', key: 'status', action: 'cook' });
753
+ // Small delay so events arrive separately
754
+ await new Promise(r => setTimeout(r, 100));
755
+
756
+ // Transition 2: kitchen → delivered
757
+ await pub.execute.mutate({ path: '/lc/data/o1', key: 'status', action: 'deliver' });
758
+
759
+ const received = await events;
760
+ assert.ok(received.length >= 2, `Expected >=2 events, got ${received.length}`);
761
+
762
+ // First event: new→kitchen
763
+ const ev1 = received[0];
764
+ assert.equal(ev1.path, '/lc/data/o1');
765
+ assert.ok((ev1 as any).rmVps?.includes('/lc/new'), 'ev1 rmVps should have /lc/new');
766
+ assert.ok((ev1 as any).addVps?.includes('/lc/kitchen'), 'ev1 addVps should have /lc/kitchen');
767
+
768
+ // Second event: kitchen→delivered
769
+ const ev2 = received[1];
770
+ assert.equal(ev2.path, '/lc/data/o1');
771
+ assert.ok((ev2 as any).rmVps?.includes('/lc/kitchen'), 'ev2 rmVps should have /lc/kitchen');
772
+ assert.ok((ev2 as any).addVps?.includes('/lc/delivered'), 'ev2 addVps should have /lc/delivered');
773
+
774
+ // Final state check
775
+ const finalNew = await pub.getChildren.query({ path: '/lc/new' });
776
+ const finalKitchen = await pub.getChildren.query({ path: '/lc/kitchen' });
777
+ const finalDelivered = await pub.getChildren.query({ path: '/lc/delivered' });
778
+ assert.equal(finalNew.items.length, 0);
779
+ assert.equal(finalKitchen.items.length, 0);
780
+ assert.equal(finalDelivered.items.length, 1);
781
+ });
782
+ });
783
+
784
+ // ── CDC edge cases ──
785
+
786
+ describe('CDC edge cases', () => {
787
+ it('node matching two query mounts gets both VPs in event', async () => {
788
+ const pub = createClient(url);
789
+ const reg = await pub.register.mutate({ userId: 'cdc-edge1', password: 'pass' });
790
+ const client = createClient(url, reg.token);
791
+
792
+ await pub.set.mutate({ node: { $path: '/multi', $type: 'folder' } });
793
+ await pub.set.mutate({ node: { $path: '/qme', $type: 'folder' } });
794
+ // VP1: matches high priority
795
+ await setupQueryMount(pub, '/qme/high', '/multi', { 'pri.level': 'high' });
796
+ // VP2: matches any node with pri component (level exists)
797
+ await setupQueryMount(pub, '/qme/all', '/multi', { 'pri.level': { $exists: true } });
798
+
799
+ const events = collectEvents<DataEvent>(
800
+ (cbs) => client.events.subscribe(undefined, cbs),
801
+ { count: 1, timeoutMs: 3000 },
802
+ );
803
+ await new Promise(r => setTimeout(r, 300));
804
+
805
+ await client.getChildren.query({ path: '/qme/high', watchNew: true, watch: true });
806
+ await client.getChildren.query({ path: '/qme/all', watchNew: true, watch: true });
807
+
808
+ // Create node matching both
809
+ await pub.set.mutate({ node: {
810
+ $path: '/multi/t1', $type: 'task',
811
+ pri: { $type: 'task.priority', level: 'high' },
812
+ } });
813
+
814
+ const received = await events;
815
+ assert.ok(received.length >= 1);
816
+ const ev = received.find(e => e.path === '/multi/t1')!;
817
+ assert.ok(ev, 'Should receive event');
818
+ assert.ok((ev as any).addVps?.includes('/qme/high'), 'addVps should contain /qme/high');
819
+ assert.ok((ev as any).addVps?.includes('/qme/all'), 'addVps should contain /qme/all');
820
+ });
821
+
822
+ it('autoWatch: VP entry followed by exact-path update', async () => {
823
+ const pub = createClient(url);
824
+ const reg = await pub.register.mutate({ userId: 'cdc-edge2', password: 'pass' });
825
+ const client = createClient(url, reg.token);
826
+
827
+ await pub.set.mutate({ node: { $path: '/aw', $type: 'folder' } });
828
+ await pub.set.mutate({ node: { $path: '/qmaw', $type: 'folder' } });
829
+ await setupQueryMount(pub, '/qmaw/hot', '/aw', { 'pri.level': 'high' });
830
+
831
+ const events = collectEvents<DataEvent>(
832
+ (cbs) => client.events.subscribe(undefined, cbs),
833
+ { count: 2, timeoutMs: 4000 },
834
+ );
835
+ await new Promise(r => setTimeout(r, 300));
836
+
837
+ // watchNew + watch = autoWatch enabled
838
+ await client.getChildren.query({ path: '/qmaw/hot', watchNew: true, watch: true });
839
+
840
+ // Event 1: create matching node → addVps (autoWatch registers exact-path watch)
841
+ await pub.set.mutate({ node: {
842
+ $path: '/aw/t1', $type: 'task',
843
+ pri: { $type: 'task.priority', level: 'high' },
844
+ } });
845
+ await new Promise(r => setTimeout(r, 200));
846
+
847
+ // Event 2: update same node (still matches) → exact-path watch delivers
848
+ await pub.set.mutate({ node: {
849
+ $path: '/aw/t1', $type: 'task',
850
+ pri: { $type: 'task.priority', level: 'high' },
851
+ extra: 'updated',
852
+ } });
853
+
854
+ const received = await events;
855
+ assert.equal(received.length, 2, 'Should receive 2 events');
856
+
857
+ // First: VP entry
858
+ assert.ok((received[0] as any).addVps?.includes('/qmaw/hot'), 'First event should have addVps');
859
+
860
+ // Second: exact-path update (no VP change since still matches)
861
+ assert.equal(received[1].path, '/aw/t1');
862
+ });
863
+
864
+ it('two users watching same VP both receive events', async () => {
865
+ const pub = createClient(url);
866
+ const reg1 = await pub.register.mutate({ userId: 'multi-u1', password: 'pass' });
867
+ const reg2 = await pub.register.mutate({ userId: 'multi-u2', password: 'pass' });
868
+ const c1 = createClient(url, reg1.token);
869
+ const c2 = createClient(url, reg2.token);
870
+
871
+ await pub.set.mutate({ node: { $path: '/mu', $type: 'folder' } });
872
+ await pub.set.mutate({ node: { $path: '/qmu', $type: 'folder' } });
873
+ await setupQueryMount(pub, '/qmu/hot', '/mu', { 'pri.level': 'high' });
874
+
875
+ // Both users subscribe
876
+ const events1 = collectEvents<DataEvent>(
877
+ (cbs) => c1.events.subscribe(undefined, cbs),
878
+ { count: 1, timeoutMs: 3000 },
879
+ );
880
+ const events2 = collectEvents<DataEvent>(
881
+ (cbs) => c2.events.subscribe(undefined, cbs),
882
+ { count: 1, timeoutMs: 3000 },
883
+ );
884
+ await new Promise(r => setTimeout(r, 300));
885
+
886
+ // Both watch the same VP
887
+ await c1.getChildren.query({ path: '/qmu/hot', watchNew: true, watch: true });
888
+ await c2.getChildren.query({ path: '/qmu/hot', watchNew: true, watch: true });
889
+
890
+ // Create matching node
891
+ await pub.set.mutate({ node: {
892
+ $path: '/mu/t1', $type: 'task',
893
+ pri: { $type: 'task.priority', level: 'high' },
894
+ } });
895
+
896
+ const [r1, r2] = await Promise.all([events1, events2]);
897
+ assert.ok(r1.length >= 1, 'User 1 should receive event');
898
+ assert.ok(r2.length >= 1, 'User 2 should receive event');
899
+ assert.ok((r1[0] as any).addVps?.includes('/qmu/hot'));
900
+ assert.ok((r2[0] as any).addVps?.includes('/qmu/hot'));
901
+ });
902
+ });
903
+
904
+ // ── Regular children watch (non-query-mount) ──
905
+
906
+ describe('regular children watch', () => {
907
+ it('new child triggers event via prefix watch (agent pattern)', async () => {
908
+ const pub = createClient(url);
909
+ const reg = await pub.register.mutate({ userId: 'child-watcher', password: 'pass' });
910
+ const client = createClient(url, reg.token);
911
+
912
+ // Seed parent
913
+ await pub.set.mutate({ node: { $path: '/agent-test', $type: 'config' } });
914
+ await pub.set.mutate({ node: { $path: '/agent-test/tasks', $type: 'dir' } });
915
+
916
+ // Start SSE subscription
917
+ const events = collectEvents<DataEvent>(
918
+ (cbs) => client.events.subscribe(undefined, cbs),
919
+ { count: 1, timeoutMs: 3000 },
920
+ );
921
+ await new Promise(r => setTimeout(r, 300));
922
+
923
+ // Register prefix watch via getChildren (same as useChildren with watchNew)
924
+ await client.getChildren.query({ path: '/agent-test/tasks', watchNew: true, watch: true });
925
+
926
+ // Create new child (simulates action:task creating a task node)
927
+ await pub.set.mutate({ node: {
928
+ $path: '/agent-test/tasks/t-1', $type: 'test.task',
929
+ prompt: 'test task', status: 'pending', createdAt: 12345,
930
+ } });
931
+
932
+ const received = await events;
933
+ assert.equal(received.length, 1, 'Should receive exactly 1 event');
934
+ assert.equal(received[0].type, 'set', 'Event type should be "set"');
935
+ assert.equal(received[0].path, '/agent-test/tasks/t-1');
936
+ const node = (received[0] as any).node;
937
+ assert.equal(node.$type, 'test.task');
938
+ assert.equal(node.prompt, 'test task');
939
+ assert.equal(node.status, 'pending');
940
+ });
941
+
942
+ it('action creating child triggers event via prefix watch', async () => {
943
+ const pub = createClient(url);
944
+ const reg = await pub.register.mutate({ userId: 'action-watcher', password: 'pass' });
945
+ const client = createClient(url, reg.token);
946
+
947
+ // Seed parent with an action that creates children
948
+ await pub.set.mutate({ node: { $path: '/act-test', $type: 'dir' } });
949
+
950
+ // Start SSE + watch children
951
+ const events = collectEvents<DataEvent>(
952
+ (cbs) => client.events.subscribe(undefined, cbs),
953
+ { count: 1, timeoutMs: 3000 },
954
+ );
955
+ await new Promise(r => setTimeout(r, 300));
956
+
957
+ await client.getChildren.query({ path: '/act-test', watchNew: true, watch: true });
958
+
959
+ // Direct child creation
960
+ await pub.set.mutate({ node: { $path: '/act-test/child-1', $type: 'doc', title: 'hello' } });
961
+
962
+ const received = await events;
963
+ assert.equal(received.length, 1, 'Should receive event for new child');
964
+ assert.equal(received[0].path, '/act-test/child-1');
965
+ });
966
+ });
967
+ });