@synnaxlabs/client 0.56.2 → 0.57.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 (741) hide show
  1. package/dist/client-DDACDVZ-.js +14505 -0
  2. package/dist/client-DDACDVZ-.js.map +1 -0
  3. package/dist/client.js +1128 -14876
  4. package/dist/client.js.map +1 -0
  5. package/dist/src/access/client.d.ts +10 -1
  6. package/dist/src/access/client.d.ts.map +1 -1
  7. package/dist/src/access/enforce.d.ts.map +1 -1
  8. package/dist/src/access/external.d.ts +1 -0
  9. package/dist/src/access/external.d.ts.map +1 -1
  10. package/dist/src/access/granted.d.ts +39 -0
  11. package/dist/src/access/granted.d.ts.map +1 -0
  12. package/dist/src/access/policy/client.d.ts +136 -147
  13. package/dist/src/access/policy/client.d.ts.map +1 -1
  14. package/dist/src/access/policy/types.gen.d.ts +15 -68
  15. package/dist/src/access/policy/types.gen.d.ts.map +1 -1
  16. package/dist/src/access/role/client.d.ts +77 -58
  17. package/dist/src/access/role/client.d.ts.map +1 -1
  18. package/dist/src/access/role/types.gen.d.ts +8 -14
  19. package/dist/src/access/role/types.gen.d.ts.map +1 -1
  20. package/dist/src/actions/actions.d.ts +5 -6
  21. package/dist/src/actions/actions.d.ts.map +1 -1
  22. package/dist/src/actions/controller.d.ts +194 -0
  23. package/dist/src/actions/controller.d.ts.map +1 -0
  24. package/dist/src/actions/external.d.ts +1 -0
  25. package/dist/src/actions/external.d.ts.map +1 -1
  26. package/dist/src/arc/actions.d.ts +575 -0
  27. package/dist/src/arc/actions.d.ts.map +1 -0
  28. package/dist/src/arc/actions.gen.d.ts +1439 -0
  29. package/dist/src/arc/actions.gen.d.ts.map +1 -0
  30. package/dist/src/arc/client.d.ts +97 -13
  31. package/dist/src/arc/client.d.ts.map +1 -1
  32. package/dist/src/arc/compiler/types.gen.d.ts +3 -3
  33. package/dist/src/arc/compiler/types.gen.d.ts.map +1 -1
  34. package/dist/src/arc/external.d.ts +2 -0
  35. package/dist/src/arc/external.d.ts.map +1 -1
  36. package/dist/src/arc/graph/types.gen.d.ts +45 -597
  37. package/dist/src/arc/graph/types.gen.d.ts.map +1 -1
  38. package/dist/src/arc/ir/index.d.ts +1 -1
  39. package/dist/src/arc/ir/index.d.ts.map +1 -1
  40. package/dist/src/arc/ir/ir.d.ts +2 -0
  41. package/dist/src/arc/ir/ir.d.ts.map +1 -0
  42. package/dist/src/arc/ir/types.gen.d.ts +58 -2807
  43. package/dist/src/arc/ir/types.gen.d.ts.map +1 -1
  44. package/dist/src/arc/module/types.gen.d.ts +18 -1090
  45. package/dist/src/arc/module/types.gen.d.ts.map +1 -1
  46. package/dist/src/arc/program/types.gen.d.ts +18 -1090
  47. package/dist/src/arc/program/types.gen.d.ts.map +1 -1
  48. package/dist/src/arc/text/types.gen.d.ts +54 -1
  49. package/dist/src/arc/text/types.gen.d.ts.map +1 -1
  50. package/dist/src/arc/types/types.gen.d.ts +35 -153
  51. package/dist/src/arc/types/types.gen.d.ts.map +1 -1
  52. package/dist/src/arc/types.gen.d.ts +98 -2278
  53. package/dist/src/arc/types.gen.d.ts.map +1 -1
  54. package/dist/src/auth/auth.d.ts +14 -2
  55. package/dist/src/auth/auth.d.ts.map +1 -1
  56. package/dist/src/channel/client.d.ts +113 -75
  57. package/dist/src/channel/client.d.ts.map +1 -1
  58. package/dist/src/channel/external.d.ts +0 -1
  59. package/dist/src/channel/external.d.ts.map +1 -1
  60. package/dist/src/channel/payload.d.ts +65 -56
  61. package/dist/src/channel/payload.d.ts.map +1 -1
  62. package/dist/src/channel/types.gen.d.ts +26 -73
  63. package/dist/src/channel/types.gen.d.ts.map +1 -1
  64. package/dist/src/channel/writer.d.ts +1 -3
  65. package/dist/src/channel/writer.d.ts.map +1 -1
  66. package/dist/src/client.d.ts +55 -27
  67. package/dist/src/client.d.ts.map +1 -1
  68. package/dist/src/connection/client.d.ts +146 -0
  69. package/dist/src/connection/client.d.ts.map +1 -0
  70. package/dist/src/connection/external.d.ts +3 -0
  71. package/dist/src/connection/external.d.ts.map +1 -0
  72. package/dist/src/connection/index.d.ts +1 -1
  73. package/dist/src/connection/index.d.ts.map +1 -1
  74. package/dist/src/connection/status.d.ts +252 -0
  75. package/dist/src/connection/status.d.ts.map +1 -0
  76. package/dist/src/control/client.d.ts +31 -7
  77. package/dist/src/control/client.d.ts.map +1 -1
  78. package/dist/src/control/state.d.ts +47 -16
  79. package/dist/src/control/state.d.ts.map +1 -1
  80. package/dist/src/device/client.d.ts +59 -17
  81. package/dist/src/device/client.d.ts.map +1 -1
  82. package/dist/src/device/types.gen.d.ts +17 -67
  83. package/dist/src/device/types.gen.d.ts.map +1 -1
  84. package/dist/src/errors.d.ts +17 -20
  85. package/dist/src/errors.d.ts.map +1 -1
  86. package/dist/src/ethercat/external.d.ts +2 -0
  87. package/dist/src/ethercat/external.d.ts.map +1 -0
  88. package/dist/src/ethercat/index.d.ts +2 -0
  89. package/dist/src/ethercat/index.d.ts.map +1 -0
  90. package/dist/src/ethercat/types.gen.d.ts +243 -0
  91. package/dist/src/ethercat/types.gen.d.ts.map +1 -0
  92. package/dist/src/framer/adapter.d.ts +8 -5
  93. package/dist/src/framer/adapter.d.ts.map +1 -1
  94. package/dist/src/framer/benchutil.d.ts +19 -0
  95. package/dist/src/framer/benchutil.d.ts.map +1 -0
  96. package/dist/src/framer/cache/cache.d.ts +38 -0
  97. package/dist/src/framer/cache/cache.d.ts.map +1 -0
  98. package/dist/src/framer/cache/dynamic.d.ts +91 -0
  99. package/dist/src/framer/cache/dynamic.d.ts.map +1 -0
  100. package/dist/src/framer/cache/reader.d.ts +57 -0
  101. package/dist/src/framer/cache/reader.d.ts.map +1 -0
  102. package/dist/src/framer/cache/static.d.ts +66 -0
  103. package/dist/src/framer/cache/static.d.ts.map +1 -0
  104. package/dist/src/framer/cache/streamer.d.ts +86 -0
  105. package/dist/src/framer/cache/streamer.d.ts.map +1 -0
  106. package/dist/src/framer/cache/transform.d.ts +18 -0
  107. package/dist/src/framer/cache/transform.d.ts.map +1 -0
  108. package/dist/src/framer/cache/unary.d.ts +39 -0
  109. package/dist/src/framer/cache/unary.d.ts.map +1 -0
  110. package/dist/src/framer/client.d.ts +35 -14
  111. package/dist/src/framer/client.d.ts.map +1 -1
  112. package/dist/src/framer/codec.d.ts.map +1 -1
  113. package/dist/src/framer/deleter.d.ts +3 -2
  114. package/dist/src/framer/deleter.d.ts.map +1 -1
  115. package/dist/src/framer/external.d.ts +6 -0
  116. package/dist/src/framer/external.d.ts.map +1 -1
  117. package/dist/src/framer/feed.d.ts +40 -0
  118. package/dist/src/framer/feed.d.ts.map +1 -0
  119. package/dist/src/framer/frame.d.ts +16 -20
  120. package/dist/src/framer/frame.d.ts.map +1 -1
  121. package/dist/src/framer/hardened.d.ts +70 -0
  122. package/dist/src/framer/hardened.d.ts.map +1 -0
  123. package/dist/src/framer/iterator.d.ts +36 -43
  124. package/dist/src/framer/iterator.d.ts.map +1 -1
  125. package/dist/src/framer/reader.d.ts +3 -2
  126. package/dist/src/framer/reader.d.ts.map +1 -1
  127. package/dist/src/framer/streamer.d.ts +132 -205
  128. package/dist/src/framer/streamer.d.ts.map +1 -1
  129. package/dist/src/framer/writer.d.ts +130 -161
  130. package/dist/src/framer/writer.d.ts.map +1 -1
  131. package/dist/src/group/client.d.ts +66 -9
  132. package/dist/src/group/client.d.ts.map +1 -1
  133. package/dist/src/group/types.gen.d.ts +2 -2
  134. package/dist/src/http/external.d.ts +2 -0
  135. package/dist/src/http/external.d.ts.map +1 -0
  136. package/dist/src/http/index.d.ts +2 -0
  137. package/dist/src/http/index.d.ts.map +1 -0
  138. package/dist/src/http/types.gen.d.ts +451 -0
  139. package/dist/src/http/types.gen.d.ts.map +1 -0
  140. package/dist/src/imex/client.d.ts +88 -0
  141. package/dist/src/imex/client.d.ts.map +1 -0
  142. package/dist/src/imex/index.d.ts +2 -0
  143. package/dist/src/imex/index.d.ts.map +1 -0
  144. package/dist/src/index.d.ts +18 -5
  145. package/dist/src/index.d.ts.map +1 -1
  146. package/dist/src/label/client.d.ts +55 -36
  147. package/dist/src/label/client.d.ts.map +1 -1
  148. package/dist/src/label/external.d.ts +2 -0
  149. package/dist/src/label/external.d.ts.map +1 -1
  150. package/dist/src/label/payload.d.ts +4 -46
  151. package/dist/src/label/payload.d.ts.map +1 -1
  152. package/dist/src/label/store.d.ts +8 -0
  153. package/dist/src/label/store.d.ts.map +1 -0
  154. package/dist/src/label/types.gen.d.ts +45 -0
  155. package/dist/src/label/types.gen.d.ts.map +1 -0
  156. package/dist/src/labjack/external.d.ts +2 -0
  157. package/dist/src/labjack/external.d.ts.map +1 -0
  158. package/dist/src/labjack/index.d.ts +2 -0
  159. package/dist/src/labjack/index.d.ts.map +1 -0
  160. package/dist/src/labjack/types.gen.d.ts +442 -0
  161. package/dist/src/labjack/types.gen.d.ts.map +1 -0
  162. package/dist/src/lineplot/actions.d.ts +914 -0
  163. package/dist/src/lineplot/actions.d.ts.map +1 -0
  164. package/dist/src/lineplot/actions.gen.d.ts +4208 -0
  165. package/dist/src/lineplot/actions.gen.d.ts.map +1 -0
  166. package/dist/src/lineplot/client.d.ts +66 -16
  167. package/dist/src/lineplot/client.d.ts.map +1 -1
  168. package/dist/src/lineplot/external.d.ts +3 -0
  169. package/dist/src/lineplot/external.d.ts.map +1 -1
  170. package/dist/src/lineplot/line.d.ts +16 -0
  171. package/dist/src/lineplot/line.d.ts.map +1 -0
  172. package/dist/src/lineplot/types.gen.d.ts +837 -14
  173. package/dist/src/lineplot/types.gen.d.ts.map +1 -1
  174. package/dist/src/log/actions.d.ts +271 -0
  175. package/dist/src/log/actions.d.ts.map +1 -0
  176. package/dist/src/log/actions.gen.d.ts +1529 -0
  177. package/dist/src/log/actions.gen.d.ts.map +1 -0
  178. package/dist/src/log/client.d.ts +66 -16
  179. package/dist/src/log/client.d.ts.map +1 -1
  180. package/dist/src/log/external.d.ts +2 -0
  181. package/dist/src/log/external.d.ts.map +1 -1
  182. package/dist/src/log/types.gen.d.ts +139 -14
  183. package/dist/src/log/types.gen.d.ts.map +1 -1
  184. package/dist/src/modbus/external.d.ts +2 -0
  185. package/dist/src/modbus/external.d.ts.map +1 -0
  186. package/dist/src/modbus/index.d.ts +2 -0
  187. package/dist/src/modbus/index.d.ts.map +1 -0
  188. package/dist/src/modbus/types.gen.d.ts +280 -0
  189. package/dist/src/modbus/types.gen.d.ts.map +1 -0
  190. package/dist/src/ni/external.d.ts +2 -0
  191. package/dist/src/ni/external.d.ts.map +1 -0
  192. package/dist/src/ni/index.d.ts +2 -0
  193. package/dist/src/ni/index.d.ts.map +1 -0
  194. package/dist/src/ni/types.gen.d.ts +21029 -0
  195. package/dist/src/ni/types.gen.d.ts.map +1 -0
  196. package/dist/src/node/index.d.ts +2 -0
  197. package/dist/src/node/index.d.ts.map +1 -0
  198. package/dist/src/node/types.gen.d.ts +4 -0
  199. package/dist/src/node/types.gen.d.ts.map +1 -0
  200. package/dist/src/ontology/client.d.ts +314 -67
  201. package/dist/src/ontology/client.d.ts.map +1 -1
  202. package/dist/src/ontology/external.d.ts +1 -0
  203. package/dist/src/ontology/external.d.ts.map +1 -1
  204. package/dist/src/ontology/payload.d.ts +60 -18
  205. package/dist/src/ontology/payload.d.ts.map +1 -1
  206. package/dist/src/ontology/store.d.ts +32 -0
  207. package/dist/src/ontology/store.d.ts.map +1 -0
  208. package/dist/src/ontology/types.gen.d.ts +3 -2
  209. package/dist/src/ontology/types.gen.d.ts.map +1 -1
  210. package/dist/src/ontology/writer.d.ts +16 -10
  211. package/dist/src/ontology/writer.d.ts.map +1 -1
  212. package/dist/src/opcua/external.d.ts +2 -0
  213. package/dist/src/opcua/external.d.ts.map +1 -0
  214. package/dist/src/opcua/index.d.ts +2 -0
  215. package/dist/src/opcua/index.d.ts.map +1 -0
  216. package/dist/src/opcua/types.gen.d.ts +82 -0
  217. package/dist/src/opcua/types.gen.d.ts.map +1 -0
  218. package/dist/src/pagerduty/external.d.ts +2 -0
  219. package/dist/src/pagerduty/external.d.ts.map +1 -0
  220. package/dist/src/pagerduty/index.d.ts +2 -0
  221. package/dist/src/pagerduty/index.d.ts.map +1 -0
  222. package/dist/src/pagerduty/types.gen.d.ts +30 -0
  223. package/dist/src/pagerduty/types.gen.d.ts.map +1 -0
  224. package/dist/src/panel/actions.d.ts +273 -0
  225. package/dist/src/panel/actions.d.ts.map +1 -0
  226. package/dist/src/panel/actions.gen.d.ts +1255 -0
  227. package/dist/src/panel/actions.gen.d.ts.map +1 -0
  228. package/dist/src/panel/client.d.ts +148 -0
  229. package/dist/src/panel/client.d.ts.map +1 -0
  230. package/dist/src/panel/external.d.ts +6 -0
  231. package/dist/src/panel/external.d.ts.map +1 -0
  232. package/dist/src/panel/index.d.ts +2 -0
  233. package/dist/src/panel/index.d.ts.map +1 -0
  234. package/dist/src/panel/tree.d.ts +50 -0
  235. package/dist/src/panel/tree.d.ts.map +1 -0
  236. package/dist/src/panel/types.gen.d.ts +370 -0
  237. package/dist/src/panel/types.gen.d.ts.map +1 -0
  238. package/dist/src/project/client.d.ts +89 -0
  239. package/dist/src/project/client.d.ts.map +1 -0
  240. package/dist/src/project/external.d.ts.map +1 -0
  241. package/dist/src/project/index.d.ts +2 -0
  242. package/dist/src/project/index.d.ts.map +1 -0
  243. package/dist/src/project/types.gen.d.ts +27 -0
  244. package/dist/src/project/types.gen.d.ts.map +1 -0
  245. package/dist/src/query/cache.d.ts +132 -0
  246. package/dist/src/query/cache.d.ts.map +1 -0
  247. package/dist/src/query/deleted.d.ts +20 -0
  248. package/dist/src/query/deleted.d.ts.map +1 -0
  249. package/dist/src/query/derived.d.ts +33 -0
  250. package/dist/src/query/derived.d.ts.map +1 -0
  251. package/dist/src/query/external.d.ts +10 -0
  252. package/dist/src/query/external.d.ts.map +1 -0
  253. package/dist/src/query/index.d.ts +2 -0
  254. package/dist/src/query/index.d.ts.map +1 -0
  255. package/dist/src/query/query.d.ts +235 -0
  256. package/dist/src/query/query.d.ts.map +1 -0
  257. package/dist/src/query/retriever.d.ts +135 -0
  258. package/dist/src/query/retriever.d.ts.map +1 -0
  259. package/dist/src/query/streamer.d.ts +67 -0
  260. package/dist/src/query/streamer.d.ts.map +1 -0
  261. package/dist/src/query/table.d.ts +219 -0
  262. package/dist/src/query/table.d.ts.map +1 -0
  263. package/dist/src/query/types.d.ts +35 -0
  264. package/dist/src/query/types.d.ts.map +1 -0
  265. package/dist/src/query/write.d.ts +21 -0
  266. package/dist/src/query/write.d.ts.map +1 -0
  267. package/dist/src/rack/client.d.ts +58 -20
  268. package/dist/src/rack/client.d.ts.map +1 -1
  269. package/dist/src/rack/types.gen.d.ts +11 -12
  270. package/dist/src/rack/types.gen.d.ts.map +1 -1
  271. package/dist/src/range/alias/types.gen.d.ts +2 -3
  272. package/dist/src/range/alias/types.gen.d.ts.map +1 -1
  273. package/dist/src/ranger/client.d.ts +138 -27
  274. package/dist/src/ranger/client.d.ts.map +1 -1
  275. package/dist/src/ranger/kv/client.d.ts +7 -4
  276. package/dist/src/ranger/kv/client.d.ts.map +1 -1
  277. package/dist/src/ranger/payload.d.ts +3 -0
  278. package/dist/src/ranger/payload.d.ts.map +1 -1
  279. package/dist/src/ranger/types.gen.d.ts +45 -101
  280. package/dist/src/ranger/types.gen.d.ts.map +1 -1
  281. package/dist/src/ranger/writer.d.ts +2 -74
  282. package/dist/src/ranger/writer.d.ts.map +1 -1
  283. package/dist/src/schematic/actions.d.ts +90 -33
  284. package/dist/src/schematic/actions.d.ts.map +1 -1
  285. package/dist/src/schematic/actions.gen.d.ts +207 -114
  286. package/dist/src/schematic/actions.gen.d.ts.map +1 -1
  287. package/dist/src/schematic/client.d.ts +70 -68
  288. package/dist/src/schematic/client.d.ts.map +1 -1
  289. package/dist/src/schematic/symbol/client.d.ts +133 -27
  290. package/dist/src/schematic/symbol/client.d.ts.map +1 -1
  291. package/dist/src/schematic/symbol/types.gen.d.ts +258 -153
  292. package/dist/src/schematic/symbol/types.gen.d.ts.map +1 -1
  293. package/dist/src/schematic/types.gen.d.ts +17 -96
  294. package/dist/src/schematic/types.gen.d.ts.map +1 -1
  295. package/dist/src/status/client.d.ts +69 -14
  296. package/dist/src/status/client.d.ts.map +1 -1
  297. package/dist/src/status/external.d.ts +2 -0
  298. package/dist/src/status/external.d.ts.map +1 -1
  299. package/dist/src/status/payload.d.ts +2 -36
  300. package/dist/src/status/payload.d.ts.map +1 -1
  301. package/dist/src/status/status.d.ts +65 -0
  302. package/dist/src/status/status.d.ts.map +1 -0
  303. package/dist/src/status/types.gen.d.ts +58 -0
  304. package/dist/src/status/types.gen.d.ts.map +1 -0
  305. package/dist/src/table/actions.d.ts +72 -13
  306. package/dist/src/table/actions.d.ts.map +1 -1
  307. package/dist/src/table/actions.gen.d.ts +221 -137
  308. package/dist/src/table/actions.gen.d.ts.map +1 -1
  309. package/dist/src/table/client.d.ts +64 -40
  310. package/dist/src/table/client.d.ts.map +1 -1
  311. package/dist/src/table/types.gen.d.ts +20 -55
  312. package/dist/src/table/types.gen.d.ts.map +1 -1
  313. package/dist/src/task/client.d.ts +149 -28
  314. package/dist/src/task/client.d.ts.map +1 -1
  315. package/dist/src/task/types.gen.d.ts +71 -89
  316. package/dist/src/task/types.gen.d.ts.map +1 -1
  317. package/dist/src/testutil/access.d.ts +27 -1
  318. package/dist/src/testutil/access.d.ts.map +1 -1
  319. package/dist/src/testutil/channels.d.ts +2 -0
  320. package/dist/src/testutil/channels.d.ts.map +1 -1
  321. package/dist/src/testutil/client.d.ts +5 -0
  322. package/dist/src/testutil/client.d.ts.map +1 -1
  323. package/dist/src/testutil/connection.d.ts +10 -0
  324. package/dist/src/testutil/connection.d.ts.map +1 -0
  325. package/dist/src/testutil/index.d.ts +9 -0
  326. package/dist/src/testutil/index.d.ts.map +1 -0
  327. package/dist/src/testutil/panels.d.ts +8 -0
  328. package/dist/src/testutil/panels.d.ts.map +1 -0
  329. package/dist/src/testutil/proxy.d.ts +29 -0
  330. package/dist/src/testutil/proxy.d.ts.map +1 -0
  331. package/dist/src/testutil/query.d.ts +7 -0
  332. package/dist/src/testutil/query.d.ts.map +1 -0
  333. package/dist/src/testutil/telem.d.ts +4 -0
  334. package/dist/src/testutil/telem.d.ts.map +1 -1
  335. package/dist/src/transport.d.ts +10 -4
  336. package/dist/src/transport.d.ts.map +1 -1
  337. package/dist/src/user/client.d.ts +29 -28
  338. package/dist/src/user/client.d.ts.map +1 -1
  339. package/dist/src/user/external.d.ts +1 -1
  340. package/dist/src/user/external.d.ts.map +1 -1
  341. package/dist/src/user/types.gen.d.ts +4 -4
  342. package/dist/src/util/retrieve.d.ts +2 -2
  343. package/dist/src/util/retrieve.d.ts.map +1 -1
  344. package/dist/src/util/telem.d.ts.map +1 -1
  345. package/dist/src/view/client.d.ts +30 -14
  346. package/dist/src/view/client.d.ts.map +1 -1
  347. package/dist/src/view/types.gen.d.ts +7 -15
  348. package/dist/src/view/types.gen.d.ts.map +1 -1
  349. package/dist/testutil.js +189 -0
  350. package/dist/testutil.js.map +1 -0
  351. package/package.json +39 -16
  352. package/src/access/client.ts +14 -3
  353. package/src/access/enforce.ts +0 -3
  354. package/src/access/external.ts +1 -0
  355. package/src/access/granted.ts +114 -0
  356. package/src/access/policy/client.ts +135 -43
  357. package/src/access/policy/types.gen.ts +9 -11
  358. package/src/access/role/client.ts +160 -38
  359. package/src/access/role/types.gen.ts +8 -12
  360. package/src/actions/actions.ts +5 -6
  361. package/src/actions/controller.ts +626 -0
  362. package/src/actions/external.ts +11 -0
  363. package/src/arc/actions.gen.ts +313 -0
  364. package/src/arc/actions.ts +192 -0
  365. package/src/arc/client.ts +424 -41
  366. package/src/arc/compiler/types.gen.ts +5 -6
  367. package/src/arc/external.ts +2 -0
  368. package/src/arc/graph/types.gen.ts +24 -22
  369. package/src/arc/ir/index.ts +1 -1
  370. package/{eslint.config.ts → src/arc/ir/ir.ts} +1 -4
  371. package/src/arc/ir/types.gen.ts +42 -41
  372. package/src/arc/text/types.gen.ts +21 -2
  373. package/src/arc/types/types.gen.ts +40 -32
  374. package/src/arc/types.gen.ts +31 -29
  375. package/src/auth/auth.ts +40 -8
  376. package/src/channel/client.ts +529 -132
  377. package/src/channel/external.ts +0 -1
  378. package/src/channel/payload.ts +30 -3
  379. package/src/channel/types.gen.ts +37 -48
  380. package/src/channel/writer.ts +2 -11
  381. package/src/client.ts +215 -91
  382. package/src/connection/client.ts +495 -0
  383. package/src/connection/external.ts +34 -0
  384. package/src/connection/index.ts +1 -1
  385. package/src/connection/status.ts +442 -0
  386. package/src/control/client.ts +84 -12
  387. package/src/control/state.ts +17 -36
  388. package/src/device/client.ts +293 -41
  389. package/src/device/types.gen.ts +15 -26
  390. package/src/errors.ts +45 -22
  391. package/src/{workspace → ethercat}/external.ts +1 -2
  392. package/src/{workspace → ethercat}/index.ts +1 -1
  393. package/src/ethercat/types.gen.ts +151 -0
  394. package/src/framer/adapter.ts +60 -37
  395. package/src/framer/benchutil.ts +67 -0
  396. package/src/framer/cache/cache.ts +115 -0
  397. package/src/framer/cache/dynamic.ts +340 -0
  398. package/src/framer/cache/reader.ts +231 -0
  399. package/src/framer/cache/static.ts +215 -0
  400. package/src/framer/cache/streamer.ts +391 -0
  401. package/src/framer/cache/transform.ts +31 -0
  402. package/src/framer/cache/unary.ts +108 -0
  403. package/src/framer/client.ts +71 -43
  404. package/src/framer/codec.ts +55 -18
  405. package/src/framer/deleter.ts +4 -6
  406. package/src/framer/external.ts +10 -0
  407. package/src/framer/feed.ts +108 -0
  408. package/src/framer/frame.ts +13 -23
  409. package/src/framer/hardened.ts +283 -0
  410. package/src/framer/iterator.ts +31 -42
  411. package/src/framer/reader.ts +11 -11
  412. package/src/framer/streamer.ts +18 -178
  413. package/src/framer/writer.ts +39 -59
  414. package/src/group/client.ts +158 -23
  415. package/src/http/external.ts +10 -0
  416. package/src/{cluster → http}/index.ts +1 -1
  417. package/src/http/types.gen.ts +245 -0
  418. package/src/imex/client.ts +125 -0
  419. package/src/imex/index.ts +10 -0
  420. package/src/index.ts +19 -6
  421. package/src/label/client.ts +193 -54
  422. package/src/label/external.ts +2 -0
  423. package/src/label/payload.ts +4 -9
  424. package/src/label/store.ts +28 -0
  425. package/src/label/types.gen.ts +31 -0
  426. package/src/labjack/external.ts +10 -0
  427. package/src/labjack/index.ts +10 -0
  428. package/src/labjack/types.gen.ts +251 -0
  429. package/src/lineplot/actions.gen.ts +880 -0
  430. package/src/lineplot/actions.ts +535 -0
  431. package/src/lineplot/client.ts +224 -52
  432. package/src/lineplot/external.ts +3 -0
  433. package/src/lineplot/line.ts +80 -0
  434. package/src/lineplot/types.gen.ts +225 -12
  435. package/src/log/actions.gen.ts +474 -0
  436. package/src/log/actions.ts +242 -0
  437. package/src/log/client.ts +223 -52
  438. package/src/log/external.ts +2 -0
  439. package/src/log/types.gen.ts +45 -14
  440. package/src/modbus/external.ts +10 -0
  441. package/src/modbus/index.ts +10 -0
  442. package/src/modbus/types.gen.ts +185 -0
  443. package/src/ni/external.ts +10 -0
  444. package/src/ni/index.ts +10 -0
  445. package/src/ni/types.gen.ts +1632 -0
  446. package/src/node/index.ts +10 -0
  447. package/src/{cluster → node}/types.gen.ts +2 -2
  448. package/src/ontology/client.ts +408 -99
  449. package/src/ontology/external.ts +7 -0
  450. package/src/ontology/payload.ts +48 -7
  451. package/src/ontology/store.ts +83 -0
  452. package/src/ontology/types.gen.ts +2 -1
  453. package/src/ontology/writer.ts +1 -0
  454. package/src/opcua/external.ts +10 -0
  455. package/src/opcua/index.ts +10 -0
  456. package/src/opcua/types.gen.ts +65 -0
  457. package/src/pagerduty/external.ts +10 -0
  458. package/src/pagerduty/index.ts +10 -0
  459. package/src/pagerduty/types.gen.ts +41 -0
  460. package/src/panel/actions.gen.ts +267 -0
  461. package/src/panel/actions.ts +357 -0
  462. package/src/panel/client.ts +327 -0
  463. package/src/panel/external.ts +14 -0
  464. package/src/panel/index.ts +10 -0
  465. package/src/panel/tree.ts +163 -0
  466. package/src/panel/types.gen.ts +164 -0
  467. package/src/project/client.ts +273 -0
  468. package/src/project/external.ts +11 -0
  469. package/src/project/index.ts +10 -0
  470. package/src/project/types.gen.ts +40 -0
  471. package/src/query/cache.ts +326 -0
  472. package/src/query/deleted.ts +38 -0
  473. package/src/query/derived.ts +101 -0
  474. package/src/query/external.ts +51 -0
  475. package/src/query/index.ts +10 -0
  476. package/src/query/query.ts +865 -0
  477. package/src/query/retriever.ts +308 -0
  478. package/src/query/streamer.ts +207 -0
  479. package/src/query/table.ts +625 -0
  480. package/src/query/types.ts +54 -0
  481. package/src/query/write.ts +42 -0
  482. package/src/rack/client.ts +233 -45
  483. package/src/rack/types.gen.ts +15 -16
  484. package/src/range/alias/types.gen.ts +2 -3
  485. package/src/ranger/client.ts +581 -70
  486. package/src/ranger/kv/client.ts +52 -21
  487. package/src/ranger/payload.ts +3 -0
  488. package/src/ranger/types.gen.ts +21 -23
  489. package/src/ranger/writer.ts +4 -8
  490. package/src/schematic/actions.gen.ts +62 -53
  491. package/src/schematic/actions.ts +44 -42
  492. package/src/schematic/client.ts +226 -70
  493. package/src/schematic/symbol/client.ts +277 -48
  494. package/src/schematic/symbol/types.gen.ts +22 -34
  495. package/src/schematic/types.gen.ts +22 -29
  496. package/src/status/client.ts +321 -44
  497. package/src/status/external.ts +2 -0
  498. package/src/status/payload.ts +1 -23
  499. package/src/status/status.ts +241 -0
  500. package/src/status/types.gen.ts +108 -0
  501. package/src/table/actions.gen.ts +96 -63
  502. package/src/table/actions.ts +44 -19
  503. package/src/table/client.ts +220 -56
  504. package/src/table/types.gen.ts +24 -31
  505. package/src/task/client.ts +580 -101
  506. package/src/task/types.gen.ts +68 -75
  507. package/src/testutil/access.ts +102 -6
  508. package/src/testutil/channels.ts +29 -0
  509. package/src/testutil/client.ts +27 -2
  510. package/src/testutil/connection.ts +47 -0
  511. package/src/testutil/index.ts +17 -0
  512. package/src/testutil/panels.ts +26 -0
  513. package/src/testutil/proxy.ts +106 -0
  514. package/src/testutil/query.ts +28 -0
  515. package/src/testutil/telem.ts +17 -1
  516. package/src/transport.ts +18 -7
  517. package/src/user/client.ts +165 -64
  518. package/src/user/external.ts +1 -1
  519. package/src/user/types.gen.ts +5 -5
  520. package/src/util/retrieve.ts +4 -4
  521. package/src/util/telem.ts +0 -1
  522. package/src/view/client.ts +120 -37
  523. package/src/view/types.gen.ts +8 -11
  524. package/.turbo/turbo-build.log +0 -20
  525. package/.vscode/settings.json +0 -3
  526. package/CONTRIBUTING.md +0 -47
  527. package/dist/client.cjs +0 -118
  528. package/dist/eslint.config.d.ts +0 -4
  529. package/dist/eslint.config.d.ts.map +0 -1
  530. package/dist/src/access/enforce.spec.d.ts +0 -2
  531. package/dist/src/access/enforce.spec.d.ts.map +0 -1
  532. package/dist/src/access/policy/access.spec.d.ts +0 -2
  533. package/dist/src/access/policy/access.spec.d.ts.map +0 -1
  534. package/dist/src/access/role/role.spec.d.ts +0 -2
  535. package/dist/src/access/role/role.spec.d.ts.map +0 -1
  536. package/dist/src/actions/actions.spec.d.ts +0 -2
  537. package/dist/src/actions/actions.spec.d.ts.map +0 -1
  538. package/dist/src/arc/access.spec.d.ts +0 -2
  539. package/dist/src/arc/access.spec.d.ts.map +0 -1
  540. package/dist/src/arc/arc.spec.d.ts +0 -2
  541. package/dist/src/arc/arc.spec.d.ts.map +0 -1
  542. package/dist/src/arc/lsp.spec.d.ts +0 -2
  543. package/dist/src/arc/lsp.spec.d.ts.map +0 -1
  544. package/dist/src/auth/auth.spec.d.ts +0 -2
  545. package/dist/src/auth/auth.spec.d.ts.map +0 -1
  546. package/dist/src/channel/access.spec.d.ts +0 -2
  547. package/dist/src/channel/access.spec.d.ts.map +0 -1
  548. package/dist/src/channel/batchRetriever.spec.d.ts +0 -2
  549. package/dist/src/channel/batchRetriever.spec.d.ts.map +0 -1
  550. package/dist/src/channel/channel.spec.d.ts +0 -2
  551. package/dist/src/channel/channel.spec.d.ts.map +0 -1
  552. package/dist/src/channel/payload.spec.d.ts +0 -2
  553. package/dist/src/channel/payload.spec.d.ts.map +0 -1
  554. package/dist/src/channel/retriever.d.ts +0 -64
  555. package/dist/src/channel/retriever.d.ts.map +0 -1
  556. package/dist/src/cluster/index.d.ts +0 -2
  557. package/dist/src/cluster/index.d.ts.map +0 -1
  558. package/dist/src/cluster/types.gen.d.ts +0 -4
  559. package/dist/src/cluster/types.gen.d.ts.map +0 -1
  560. package/dist/src/connection/checker.d.ts +0 -86
  561. package/dist/src/connection/checker.d.ts.map +0 -1
  562. package/dist/src/connection/connection.spec.d.ts +0 -2
  563. package/dist/src/connection/connection.spec.d.ts.map +0 -1
  564. package/dist/src/connection.spec.d.ts +0 -2
  565. package/dist/src/connection.spec.d.ts.map +0 -1
  566. package/dist/src/control/state.spec.d.ts +0 -2
  567. package/dist/src/control/state.spec.d.ts.map +0 -1
  568. package/dist/src/device/access.spec.d.ts +0 -2
  569. package/dist/src/device/access.spec.d.ts.map +0 -1
  570. package/dist/src/device/device.spec.d.ts +0 -2
  571. package/dist/src/device/device.spec.d.ts.map +0 -1
  572. package/dist/src/device/payload.spec.d.ts +0 -2
  573. package/dist/src/device/payload.spec.d.ts.map +0 -1
  574. package/dist/src/errors.spec.d.ts +0 -2
  575. package/dist/src/errors.spec.d.ts.map +0 -1
  576. package/dist/src/framer/adapter.spec.d.ts +0 -2
  577. package/dist/src/framer/adapter.spec.d.ts.map +0 -1
  578. package/dist/src/framer/client.spec.d.ts +0 -2
  579. package/dist/src/framer/client.spec.d.ts.map +0 -1
  580. package/dist/src/framer/codec.spec.d.ts +0 -2
  581. package/dist/src/framer/codec.spec.d.ts.map +0 -1
  582. package/dist/src/framer/deleter.spec.d.ts +0 -2
  583. package/dist/src/framer/deleter.spec.d.ts.map +0 -1
  584. package/dist/src/framer/frame.spec.d.ts +0 -2
  585. package/dist/src/framer/frame.spec.d.ts.map +0 -1
  586. package/dist/src/framer/iterator.spec.d.ts +0 -2
  587. package/dist/src/framer/iterator.spec.d.ts.map +0 -1
  588. package/dist/src/framer/reader.spec.d.ts +0 -2
  589. package/dist/src/framer/reader.spec.d.ts.map +0 -1
  590. package/dist/src/framer/streamer.spec.d.ts +0 -2
  591. package/dist/src/framer/streamer.spec.d.ts.map +0 -1
  592. package/dist/src/framer/writer.spec.d.ts +0 -2
  593. package/dist/src/framer/writer.spec.d.ts.map +0 -1
  594. package/dist/src/group/access.spec.d.ts +0 -2
  595. package/dist/src/group/access.spec.d.ts.map +0 -1
  596. package/dist/src/group/group.spec.d.ts +0 -2
  597. package/dist/src/group/group.spec.d.ts.map +0 -1
  598. package/dist/src/label/access.spec.d.ts +0 -2
  599. package/dist/src/label/access.spec.d.ts.map +0 -1
  600. package/dist/src/label/label.spec.d.ts +0 -2
  601. package/dist/src/label/label.spec.d.ts.map +0 -1
  602. package/dist/src/lineplot/access.spec.d.ts +0 -2
  603. package/dist/src/lineplot/access.spec.d.ts.map +0 -1
  604. package/dist/src/lineplot/lineplot.spec.d.ts +0 -2
  605. package/dist/src/lineplot/lineplot.spec.d.ts.map +0 -1
  606. package/dist/src/log/access.spec.d.ts +0 -2
  607. package/dist/src/log/access.spec.d.ts.map +0 -1
  608. package/dist/src/log/log.spec.d.ts +0 -2
  609. package/dist/src/log/log.spec.d.ts.map +0 -1
  610. package/dist/src/ontology/ontology.spec.d.ts +0 -2
  611. package/dist/src/ontology/ontology.spec.d.ts.map +0 -1
  612. package/dist/src/rack/access.spec.d.ts +0 -2
  613. package/dist/src/rack/access.spec.d.ts.map +0 -1
  614. package/dist/src/rack/rack.spec.d.ts +0 -2
  615. package/dist/src/rack/rack.spec.d.ts.map +0 -1
  616. package/dist/src/ranger/access.spec.d.ts +0 -2
  617. package/dist/src/ranger/access.spec.d.ts.map +0 -1
  618. package/dist/src/ranger/alias/payload.spec.d.ts +0 -2
  619. package/dist/src/ranger/alias/payload.spec.d.ts.map +0 -1
  620. package/dist/src/ranger/ranger.spec.d.ts +0 -2
  621. package/dist/src/ranger/ranger.spec.d.ts.map +0 -1
  622. package/dist/src/schematic/access.spec.d.ts +0 -2
  623. package/dist/src/schematic/access.spec.d.ts.map +0 -1
  624. package/dist/src/schematic/actions.spec.d.ts +0 -2
  625. package/dist/src/schematic/actions.spec.d.ts.map +0 -1
  626. package/dist/src/schematic/schematic.spec.d.ts +0 -2
  627. package/dist/src/schematic/schematic.spec.d.ts.map +0 -1
  628. package/dist/src/schematic/symbol/access.spec.d.ts +0 -2
  629. package/dist/src/schematic/symbol/access.spec.d.ts.map +0 -1
  630. package/dist/src/schematic/symbol/client.spec.d.ts +0 -2
  631. package/dist/src/schematic/symbol/client.spec.d.ts.map +0 -1
  632. package/dist/src/status/access.spec.d.ts +0 -2
  633. package/dist/src/status/access.spec.d.ts.map +0 -1
  634. package/dist/src/status/status.spec.d.ts +0 -2
  635. package/dist/src/status/status.spec.d.ts.map +0 -1
  636. package/dist/src/table/access.spec.d.ts +0 -2
  637. package/dist/src/table/access.spec.d.ts.map +0 -1
  638. package/dist/src/table/table.spec.d.ts +0 -2
  639. package/dist/src/table/table.spec.d.ts.map +0 -1
  640. package/dist/src/task/access.spec.d.ts +0 -2
  641. package/dist/src/task/access.spec.d.ts.map +0 -1
  642. package/dist/src/task/payload.spec.d.ts +0 -2
  643. package/dist/src/task/payload.spec.d.ts.map +0 -1
  644. package/dist/src/task/task.spec.d.ts +0 -2
  645. package/dist/src/task/task.spec.d.ts.map +0 -1
  646. package/dist/src/user/access.spec.d.ts +0 -2
  647. package/dist/src/user/access.spec.d.ts.map +0 -1
  648. package/dist/src/user/user.spec.d.ts +0 -2
  649. package/dist/src/user/user.spec.d.ts.map +0 -1
  650. package/dist/src/util/retrieve.spec.d.ts +0 -2
  651. package/dist/src/util/retrieve.spec.d.ts.map +0 -1
  652. package/dist/src/view/view.spec.d.ts +0 -2
  653. package/dist/src/view/view.spec.d.ts.map +0 -1
  654. package/dist/src/workspace/access.spec.d.ts +0 -2
  655. package/dist/src/workspace/access.spec.d.ts.map +0 -1
  656. package/dist/src/workspace/client.d.ts +0 -36
  657. package/dist/src/workspace/client.d.ts.map +0 -1
  658. package/dist/src/workspace/external.d.ts.map +0 -1
  659. package/dist/src/workspace/index.d.ts +0 -2
  660. package/dist/src/workspace/index.d.ts.map +0 -1
  661. package/dist/src/workspace/types.gen.d.ts +0 -34
  662. package/dist/src/workspace/types.gen.d.ts.map +0 -1
  663. package/dist/src/workspace/workspace.spec.d.ts +0 -2
  664. package/dist/src/workspace/workspace.spec.d.ts.map +0 -1
  665. package/examples/node/basicReadWrite.js +0 -86
  666. package/examples/node/createChannels.js +0 -50
  667. package/examples/node/createRange.js +0 -67
  668. package/examples/node/package-lock.json +0 -133
  669. package/examples/node/package.json +0 -17
  670. package/examples/node/readLatest.js +0 -76
  671. package/examples/node/seriesAndFrames.js +0 -173
  672. package/examples/node/streamRead.js +0 -50
  673. package/examples/node/streamWrite.js +0 -87
  674. package/src/access/enforce.spec.ts +0 -188
  675. package/src/access/policy/access.spec.ts +0 -147
  676. package/src/access/role/role.spec.ts +0 -95
  677. package/src/actions/actions.spec.ts +0 -229
  678. package/src/arc/access.spec.ts +0 -149
  679. package/src/arc/arc.spec.ts +0 -44
  680. package/src/arc/lsp.spec.ts +0 -717
  681. package/src/auth/auth.spec.ts +0 -101
  682. package/src/channel/access.spec.ts +0 -116
  683. package/src/channel/batchRetriever.spec.ts +0 -115
  684. package/src/channel/channel.spec.ts +0 -352
  685. package/src/channel/payload.spec.ts +0 -117
  686. package/src/channel/retriever.ts +0 -252
  687. package/src/connection/checker.ts +0 -203
  688. package/src/connection/connection.spec.ts +0 -152
  689. package/src/connection.spec.ts +0 -145
  690. package/src/control/state.spec.ts +0 -22
  691. package/src/device/access.spec.ts +0 -159
  692. package/src/device/device.spec.ts +0 -461
  693. package/src/device/payload.spec.ts +0 -142
  694. package/src/errors.spec.ts +0 -71
  695. package/src/framer/adapter.spec.ts +0 -620
  696. package/src/framer/client.spec.ts +0 -180
  697. package/src/framer/codec.spec.ts +0 -383
  698. package/src/framer/deleter.spec.ts +0 -103
  699. package/src/framer/frame.spec.ts +0 -973
  700. package/src/framer/iterator.spec.ts +0 -169
  701. package/src/framer/reader.spec.ts +0 -865
  702. package/src/framer/streamer.spec.ts +0 -871
  703. package/src/framer/writer.spec.ts +0 -486
  704. package/src/group/access.spec.ts +0 -77
  705. package/src/group/group.spec.ts +0 -65
  706. package/src/label/access.spec.ts +0 -109
  707. package/src/label/label.spec.ts +0 -83
  708. package/src/lineplot/access.spec.ts +0 -134
  709. package/src/lineplot/lineplot.spec.ts +0 -120
  710. package/src/log/access.spec.ts +0 -132
  711. package/src/log/log.spec.ts +0 -106
  712. package/src/ontology/ontology.spec.ts +0 -521
  713. package/src/rack/access.spec.ts +0 -102
  714. package/src/rack/rack.spec.ts +0 -171
  715. package/src/ranger/access.spec.ts +0 -115
  716. package/src/ranger/alias/payload.spec.ts +0 -52
  717. package/src/ranger/ranger.spec.ts +0 -343
  718. package/src/schematic/access.spec.ts +0 -134
  719. package/src/schematic/actions.spec.ts +0 -699
  720. package/src/schematic/schematic.spec.ts +0 -340
  721. package/src/schematic/symbol/access.spec.ts +0 -172
  722. package/src/schematic/symbol/client.spec.ts +0 -160
  723. package/src/status/access.spec.ts +0 -129
  724. package/src/status/status.spec.ts +0 -448
  725. package/src/table/access.spec.ts +0 -128
  726. package/src/table/table.spec.ts +0 -651
  727. package/src/task/access.spec.ts +0 -131
  728. package/src/task/payload.spec.ts +0 -113
  729. package/src/task/task.spec.ts +0 -657
  730. package/src/user/access.spec.ts +0 -107
  731. package/src/user/user.spec.ts +0 -290
  732. package/src/util/retrieve.spec.ts +0 -49
  733. package/src/view/view.spec.ts +0 -165
  734. package/src/workspace/access.spec.ts +0 -108
  735. package/src/workspace/client.ts +0 -112
  736. package/src/workspace/types.gen.ts +0 -46
  737. package/src/workspace/workspace.spec.ts +0 -111
  738. package/tsconfig.json +0 -5
  739. package/tsconfig.vite.json +0 -4
  740. package/vite.config.ts +0 -29
  741. /package/dist/src/{workspace → project}/external.d.ts +0 -0
@@ -0,0 +1,865 @@
1
+ // Copyright 2026 Synnax Labs, Inc.
2
+ //
3
+ // Use of this software is governed by the Business Source License included in the file
4
+ // licenses/BSL.txt.
5
+ //
6
+ // As of the Change Date specified in that file, in accordance with the Business Source
7
+ // License, use of this software will be governed by the Apache License, Version 2.0,
8
+ // included in the file licenses/APL.txt.
9
+
10
+ import {
11
+ compare,
12
+ type destructor,
13
+ errors,
14
+ primitive,
15
+ type record,
16
+ type state,
17
+ TimeSpan,
18
+ TimeStamp,
19
+ } from "@synnaxlabs/x";
20
+
21
+ import { AccessDeniedError, NotFoundError } from "@/errors";
22
+ import { Deleted } from "@/query/deleted";
23
+ import { type Table, type TableEvent } from "@/query/table";
24
+ import { type Data, type FetchOptions, type Params } from "@/query/types";
25
+
26
+ const hashes = new WeakMap<object, string>();
27
+
28
+ /**
29
+ * Deterministically serializes query params to a stable string. Keys are sorted
30
+ * recursively so `{a: 1, b: 2}` and `{b: 2, a: 1}` collapse to the same key, and
31
+ * explicitly-undefined fields hash like absent ones (matching JSON semantics). Class
32
+ * instances implementing {@link primitive.Hashable} delegate to their `hash()` method;
33
+ * plain objects and arrays recurse structurally, memoized per object identity ({@link
34
+ * Params} is readonly, so an object's hash never changes).
35
+ */
36
+ export const hash = (params: Params): string => {
37
+ if (params === null) return "null";
38
+ if (params === undefined) return "undefined";
39
+ if (typeof params === "bigint") return `${params.toString()}n`;
40
+ if (typeof params !== "object") return JSON.stringify(params);
41
+ if (primitive.isHashable(params)) return params.hash();
42
+ const held = hashes.get(params);
43
+ if (held !== undefined) return held;
44
+ let result: string;
45
+ if (Array.isArray(params)) result = `[${params.map(hash).join(",")}]`;
46
+ else {
47
+ const fields = Object.entries(params)
48
+ .filter(([, v]) => v !== undefined)
49
+ .sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0));
50
+ result = `{${fields.map(([k, v]) => `${JSON.stringify(k)}:${hash(v)}`).join(",")}}`;
51
+ }
52
+ hashes.set(params, result);
53
+ return result;
54
+ };
55
+
56
+ /**
57
+ * A cached answer as delivered to consumers: the query's current data, or the
58
+ * {@link Deleted} corpse of a record that was deleted. A corpse is never
59
+ * delivered as bare data, so deletion cannot be mistaken for a live result.
60
+ * Narrow with {@link isLive}, or {@link Deleted.matches} to reach the corpse.
61
+ */
62
+ export type Cached<D extends Data> = D | Deleted<D>;
63
+
64
+ /** True when a cached answer is live: cached at all, and not a deletion. */
65
+ export const isLive = <D extends Data>(value: Cached<D> | undefined): value is D =>
66
+ value != null && !Deleted.matches<D>(value);
67
+
68
+ /**
69
+ * Returns the corpse of a deleted answer, for callers that need the record's
70
+ * last value (restoring it, say) and have no use for any other outcome.
71
+ * @throws {NotFoundError} if the answer is live or nothing is cached.
72
+ */
73
+ export const requireCorpse = <D extends Data>(value: Cached<D> | undefined): D => {
74
+ if (!Deleted.matches<D>(value))
75
+ throw new NotFoundError("the deleted record is no longer cached");
76
+ return value.corpse;
77
+ };
78
+
79
+ /**
80
+ * Receives the new cached answer for a query. `undefined` means the answer was
81
+ * invalidated; the next read refetches.
82
+ */
83
+ export interface ChangeHandler<D extends Data> {
84
+ (result: Cached<D> | undefined): void;
85
+ }
86
+
87
+ /**
88
+ * The read surface of one query space: fetch, subscribe, snapshot. The public
89
+ * face of every answer space, including the named spaces domain clients
90
+ * expose (children, parents, kv).
91
+ */
92
+ export interface Retrieves<P extends Params, D extends Data> {
93
+ retrieve: (params: P, options?: FetchOptions) => Promise<D>;
94
+ onChange: (params: P, handler: ChangeHandler<D>) => destructor.Destructor;
95
+ getCached: (params: P) => Cached<D> | undefined;
96
+ }
97
+
98
+ /**
99
+ * A foreign-table subscription for an answer space: projects that table's
100
+ * events onto the space's primary keys. Build with {@link watch}.
101
+ */
102
+ export interface Watch<P extends Params, K extends record.Key> {
103
+ attach: (
104
+ params: P,
105
+ onEvent: (result: K[] | "refetch") => void,
106
+ ) => destructor.Destructor;
107
+ }
108
+
109
+ /**
110
+ * Declares that events on the given table affect answers in this space.
111
+ * `affects` maps an event to the primary keys whose membership or content it
112
+ * touches, "refetch" to invalidate wholesale, or null when unaffected.
113
+ */
114
+ export const watch = <
115
+ P extends Params,
116
+ K extends record.Key,
117
+ ForeignKey extends record.Key,
118
+ ForeignValue extends state.State,
119
+ >(
120
+ table: Table<ForeignKey, ForeignValue>,
121
+ affects: (
122
+ event: TableEvent<ForeignKey, ForeignValue>,
123
+ params: P,
124
+ ) => K[] | "refetch" | null,
125
+ ): Watch<P, K> => ({
126
+ attach: (params, onEvent) =>
127
+ // Batched so one foreign write fires onEvent once: a refetch verdict for any event
128
+ // supersedes the batch's keys, which otherwise union.
129
+ table.subscribeBatch((events) => {
130
+ let keys: K[] = [];
131
+ for (const event of events) {
132
+ const result = affects(event, params);
133
+ if (result == null) continue;
134
+ if (result === "refetch") return onEvent("refetch");
135
+ keys = keys.concat(result);
136
+ }
137
+ if (keys.length > 0) onEvent(keys);
138
+ }),
139
+ });
140
+
141
+ /**
142
+ * Declaration of one query space: which table owns its records, how to fetch
143
+ * an answer's keys, how to assemble content, and how membership is maintained.
144
+ *
145
+ * Maintenance is chosen per query instance:
146
+ * 1. exact-key — `keyOf` returns a key: track that entry; deletion flips the
147
+ * answer to deleted.
148
+ * 2. client-checkable — `matches` compares a record against the query:
149
+ * admit/evict exactly, no network.
150
+ * 3. server-computed — any field named in `serverFields` is set on the query
151
+ * (or neither `keyOf` nor `matches` applies): debounced wholesale refetch.
152
+ */
153
+ export interface SpaceConfig<
154
+ P extends Params,
155
+ D extends Data,
156
+ K extends record.Key = record.Key,
157
+ V extends state.State = state.State,
158
+ > {
159
+ /** Resource name used in error messages, e.g. "range". */
160
+ name: string;
161
+ /** The table owning this space's record content. */
162
+ table: Table<K, V>;
163
+ /**
164
+ * Fetches the answer from the cluster: writes fetched records into their
165
+ * tables and returns the member keys.
166
+ */
167
+ fetch: (params: P, options?: FetchOptions) => Promise<K[]>;
168
+ /** Assembles the answer from resolved member records at read time. */
169
+ compose: (records: V[], params: P) => D;
170
+ /** Rule 1: the single key a query addresses, or null when it doesn't. */
171
+ keyOf?: (params: P) => K | null;
172
+ /**
173
+ * The exact key set a keys-only query addresses, or null when it doesn't. Lets
174
+ * getCached compose the answer straight from the table (params order, deduped,
175
+ * tombstoned members omitted) and lets maintenance seed a ready answer on
176
+ * subscribe, so a fully cached key set never fetches.
177
+ */
178
+ keysOf?: (params: P) => K[] | null;
179
+ /** Rule 2: whether a record satisfies the query. Pure; no network. */
180
+ matches?: (record: V, params: P) => boolean;
181
+ /**
182
+ * Query fields only the server can evaluate (searchTerm, limit, offset).
183
+ * A query instance with any of them set is maintained by rule 3.
184
+ */
185
+ serverFields?: readonly string[];
186
+ /** Foreign tables whose events affect this space's answers. */
187
+ watch?: Watch<P, K>[];
188
+ /**
189
+ * Marks a space whose answer addresses one record and cannot compose an
190
+ * empty membership: rule-2 eviction of the last member flips the query to
191
+ * deleted (when the entry tombstoned) or invalidates it, instead of
192
+ * composing []. Leave unset for spaces where [] is a valid answer.
193
+ */
194
+ single?: boolean;
195
+ }
196
+
197
+ /** Wiring a {@link Space} receives from the cache that owns it. */
198
+ export interface SpaceHooks {
199
+ /** Started (not awaited) on reads and subscriptions to open change delivery. */
200
+ ensureStreaming?: () => Promise<void>;
201
+ /** Subscribes to connection-epoch changes; maintained answers refetch on bump. */
202
+ onEpoch?: (callback: (epoch: number) => void) => destructor.Destructor;
203
+ /**
204
+ * Whether change delivery keeps subscribed answers current. False when the
205
+ * cache is detached or the cluster refused the stream. Defaults to true.
206
+ */
207
+ maintained?: () => boolean;
208
+ /** Reports maintenance errors that have no caller to throw to. Defaults to
209
+ * console logging. */
210
+ onError?: (error: Error) => void;
211
+ /**
212
+ * How long maintenance survives the last unsubscribe, so a quick remount finds
213
+ * the answer still maintained and skips the reconfirm refetch. Zero tears down
214
+ * synchronously. Defaults to 5 seconds.
215
+ */
216
+ teardownGrace?: TimeSpan;
217
+ }
218
+
219
+ const DEFAULT_DEBOUNCE = TimeSpan.milliseconds(100);
220
+ const DEFAULT_TEARDOWN_GRACE = TimeSpan.seconds(5);
221
+
222
+ type QueryState<K extends record.Key, D extends Data> =
223
+ | { variant: "unfetched" }
224
+ | { variant: "loading"; promise: Promise<D> }
225
+ | { variant: "ready"; keys: K[] }
226
+ | { variant: "error"; error: Error }
227
+ | { variant: "deleted"; key: K };
228
+
229
+ interface Query<
230
+ P extends Params,
231
+ K extends record.Key,
232
+ D extends Data,
233
+ V extends state.State,
234
+ > {
235
+ params: P;
236
+ state: QueryState<K, D>;
237
+ handlers: Set<ChangeHandler<D>>;
238
+ /** Rule teardown; present exactly while the query is maintained. */
239
+ teardown?: destructor.Destructor[];
240
+ /** Set when maintenance ends: changes since then went unobserved. */
241
+ unmaintained?: boolean;
242
+ /** Keys whose membership a fetch in flight deferred; drained on settle. */
243
+ pendingRechecks?: Set<K>;
244
+ /** Set when a fetch in flight deferred a refetch; honored on settle. */
245
+ refetchOnSettle?: boolean;
246
+ refetchTimer?: ReturnType<typeof setTimeout>;
247
+ /** When the last subscriber left; the sweep tears the query down once this
248
+ * outlives the grace window. Cleared on resubscribe. */
249
+ idleSince?: TimeStamp;
250
+ /** Answer interned against the records it was composed from, so repeated
251
+ * reads stay referentially stable for useSyncExternalStore consumers. A
252
+ * table replaces an entry only when its content changes, so a surviving entry
253
+ * set means a surviving answer. */
254
+ composed?: { records: V[]; value: D };
255
+ }
256
+
257
+ /**
258
+ * Cached read path for one query space of a domain client: deduped fetches, per-query
259
+ * lifecycle, subscriptions, and deletion delivery. Answers hold only key lists; record
260
+ * content lives in tables and is assembled at read time, so answer content cannot
261
+ * diverge from the record cache. Answers are maintained only while subscribed. An
262
+ * unsubscribed read refetches; an unsubscribed getCached serves the retained (possibly
263
+ * stale) answer, recomposed against live tables.
264
+ */
265
+ export class Space<
266
+ P extends Params,
267
+ D extends Data,
268
+ K extends record.Key = record.Key,
269
+ V extends state.State = state.State,
270
+ > {
271
+ private readonly queries = new Map<string, Query<P, K, D, V>>();
272
+ /** Composed corpses interned per tombstone and params, so deleted answers
273
+ * stay referentially stable. Identity composes reuse the tombstone. */
274
+ private readonly corpses = new WeakMap<Deleted<V>, Map<string, Deleted<D>>>();
275
+ /** Answers for reads with no live query, interned per entry and params. */
276
+ private readonly entryAnswers = new WeakMap<object, Map<string, D>>();
277
+ private readonly config: SpaceConfig<P, D, K, V>;
278
+ private readonly hooks: SpaceHooks & {
279
+ onError: NonNullable<SpaceHooks["onError"]>;
280
+ };
281
+ private readonly detachEpoch?: destructor.Destructor;
282
+ private readonly grace: TimeSpan;
283
+ private sweepTimer?: ReturnType<typeof setTimeout>;
284
+
285
+ constructor(config: SpaceConfig<P, D, K, V>, hooks: SpaceHooks = {}) {
286
+ this.config = config;
287
+ this.hooks = { ...hooks, onError: hooks.onError ?? console.error };
288
+ this.grace = hooks.teardownGrace ?? DEFAULT_TEARDOWN_GRACE;
289
+ this.detachEpoch = hooks.onEpoch?.((epoch) => {
290
+ // 0 is a return to cold (cluster replacement): the fresh stream's own
291
+ // epoch bump refetches, not the reset itself.
292
+ if (epoch === 0) return;
293
+ this.queries.forEach((query) => {
294
+ if (query.teardown != null) void this.refetch(query);
295
+ });
296
+ });
297
+ }
298
+
299
+ /**
300
+ * Returns the answer to the query: instantly when cached and subscribed,
301
+ * joining the in-flight fetch when one exists, fetching otherwise. Settled
302
+ * answers are kept fresh only while maintained (subscribed, or within the teardown
303
+ * grace window after the last unsubscribe, and only while change delivery is on),
304
+ * so an unmaintained read always refetches. A previously failed query refetches.
305
+ * @throws {NotFoundError} if the queried record was deleted.
306
+ */
307
+ retrieve(params: P, options?: FetchOptions): Promise<D> {
308
+ const query = this.ensure(params);
309
+ const { state } = query;
310
+ if (state.variant === "loading") return state.promise;
311
+ if (query.teardown != null && this.hooks.maintained?.() !== false)
312
+ switch (state.variant) {
313
+ case "ready":
314
+ return Promise.resolve(this.composedOf(query, state.keys));
315
+ case "deleted":
316
+ return Promise.reject(new NotFoundError(`${this.config.name} was deleted`));
317
+ }
318
+ void this.startStreaming();
319
+ return this.fetch(query, options);
320
+ }
321
+
322
+ /**
323
+ * Returns the cached answer without touching the network: the current data,
324
+ * the corpse of a deleted record, or undefined when nothing is cached.
325
+ * Exact-key queries also resolve from a table entry cached by any other
326
+ * query. May be stale for queries nothing subscribes to.
327
+ */
328
+ getCached(params: P): Cached<D> | undefined {
329
+ const query = this.queries.get(hash(params));
330
+ if (query != null) {
331
+ const cached = this.cachedOf(query);
332
+ if (cached != null) return cached;
333
+ }
334
+ // Rule-1 queries read straight through to the table: an entry (or tombstone)
335
+ // answers the query even before any fetch or subscription exists.
336
+ const key = this.config.keyOf?.(params);
337
+ if (key != null) {
338
+ const { table } = this.config;
339
+ const entry = table.get(key);
340
+ if (entry != null) return this.entryAnswerOf(entry, params);
341
+ const tombstone = table.getTombstone(key);
342
+ if (tombstone == null) return undefined;
343
+ return this.deletedOf(tombstone, params);
344
+ }
345
+ // Keys-only queries compose from the table the same way, when every key resolves.
346
+ // The answer matches what the fetch would return.
347
+ const keys = this.config.keysOf?.(params);
348
+ if (keys == null) return undefined;
349
+ const members = this.composableMembersOf(keys);
350
+ if (members == null) return undefined;
351
+ return this.composedOf(this.ensure(params), members);
352
+ }
353
+
354
+ /**
355
+ * Resolves a keys-only query's key list against the table: deduped, in params order,
356
+ * tombstoned keys omitted to match the fetch answer. Returns null when any key is
357
+ * unknown, since only a fetch can tell an uncached record from a nonexistent one.
358
+ */
359
+ private composableMembersOf(keys: K[]): K[] | null {
360
+ const { table } = this.config;
361
+ const members: K[] = [];
362
+ const seen = new Set<K>();
363
+ for (const key of keys) {
364
+ if (seen.has(key)) continue;
365
+ seen.add(key);
366
+ const status = table.status(key);
367
+ if (status === "unknown") return null;
368
+ if (status === "present") members.push(key);
369
+ }
370
+ return members;
371
+ }
372
+
373
+ /**
374
+ * Subscribes to changes in the query's cached answer. The handler fires with the new
375
+ * answer on every change or deletion. Maintenance for the query runs while at least
376
+ * one subscriber exists and survives the last unsubscribe by the grace window, so a
377
+ * quick remount skips the reconfirm refetch. Returns a destructor that unsubscribes.
378
+ */
379
+ onChange(params: P, handler: ChangeHandler<D>): destructor.Destructor {
380
+ void this.startStreaming();
381
+ const query = this.ensure(params);
382
+ query.handlers.add(handler);
383
+ query.idleSince = undefined;
384
+ if (query.teardown == null) this.maintain(query);
385
+ return () => {
386
+ query.handlers.delete(handler);
387
+ if (query.handlers.size > 0) return;
388
+ if (this.grace.isZero) return this.teardownQuery(query);
389
+ query.idleSince = TimeStamp.now();
390
+ this.armSweep();
391
+ };
392
+ }
393
+
394
+ /**
395
+ * Resets every answer to unfetched and notifies subscribers that their answer was
396
+ * invalidated, so the next read refetches. Maintenance subscriptions stay mounted.
397
+ * Called by the cache when the cluster behind the connection is replaced.
398
+ */
399
+ reset(): void {
400
+ this.queries.forEach((query) => {
401
+ if (query.refetchTimer != null) {
402
+ clearTimeout(query.refetchTimer);
403
+ query.refetchTimer = undefined;
404
+ }
405
+ query.state = { variant: "unfetched" };
406
+ query.handlers.forEach((handler) => {
407
+ try {
408
+ handler(undefined);
409
+ } catch (exc) {
410
+ this.report(exc, `failed to notify ${this.config.name} answer subscriber`);
411
+ }
412
+ });
413
+ });
414
+ }
415
+
416
+ /** Detaches the epoch subscription. Queries and handlers are dropped. */
417
+ close(): void {
418
+ this.detachEpoch?.();
419
+ if (this.sweepTimer != null) {
420
+ clearTimeout(this.sweepTimer);
421
+ this.sweepTimer = undefined;
422
+ }
423
+ this.queries.forEach((query) => {
424
+ query.teardown?.forEach((d) => d());
425
+ if (query.refetchTimer != null) clearTimeout(query.refetchTimer);
426
+ });
427
+ this.queries.clear();
428
+ }
429
+
430
+ private teardownQuery(query: Query<P, K, D, V>): void {
431
+ query.idleSince = undefined;
432
+ query.teardown?.forEach((d) => d());
433
+ query.teardown = undefined;
434
+ query.unmaintained = true;
435
+ query.pendingRechecks = undefined;
436
+ query.refetchOnSettle = false;
437
+ if (query.refetchTimer != null) {
438
+ clearTimeout(query.refetchTimer);
439
+ query.refetchTimer = undefined;
440
+ }
441
+ }
442
+
443
+ /** Arms the sweep while any query sits in the grace window. One timer per
444
+ * space, no matter how many queries go idle at once. */
445
+ private armSweep(): void {
446
+ this.sweepTimer ??= setTimeout(() => {
447
+ this.sweepTimer = undefined;
448
+ this.sweep();
449
+ }, this.grace.milliseconds / 2);
450
+ }
451
+
452
+ private sweep(): void {
453
+ let anyIdle = false;
454
+ const now = TimeStamp.now();
455
+ this.queries.forEach((query) => {
456
+ if (query.idleSince == null) return;
457
+ if (query.handlers.size > 0) {
458
+ query.idleSince = undefined;
459
+ return;
460
+ }
461
+ if (now.span(query.idleSince).lessThan(this.grace)) {
462
+ anyIdle = true;
463
+ return;
464
+ }
465
+ this.teardownQuery(query);
466
+ });
467
+ if (anyIdle) this.armSweep();
468
+ }
469
+
470
+ private ensure(params: P): Query<P, K, D, V> {
471
+ const hashed = hash(params);
472
+ let query = this.queries.get(hashed);
473
+ if (query == null) {
474
+ query = { params, state: { variant: "unfetched" }, handlers: new Set() };
475
+ this.queries.set(hashed, query);
476
+ }
477
+ return query;
478
+ }
479
+
480
+ // Streaming failure must never block reads, so the change stream opens in the
481
+ // background rather than being awaited. A denial belongs to the connection, which
482
+ // reports it once, not to every query that reads. Never rejects.
483
+ private async startStreaming(): Promise<void> {
484
+ try {
485
+ await this.hooks.ensureStreaming?.();
486
+ } catch (exc) {
487
+ if (AccessDeniedError.matches(exc)) return;
488
+ this.report(exc);
489
+ }
490
+ }
491
+
492
+ private report(
493
+ exc: unknown,
494
+ message = `failed to maintain ${this.config.name} answers`,
495
+ ): void {
496
+ this.hooks.onError(new Error(message, { cause: exc }));
497
+ }
498
+
499
+ /** Composes a ready answer, interned against the records it was built from. */
500
+ private composedOf(query: Query<P, K, D, V>, keys: K[]): D {
501
+ const records = this.config.table.get(keys);
502
+ const { composed } = query;
503
+ if (composed != null && compare.arraysEqual(composed.records, records))
504
+ return composed.value;
505
+ const value = this.config.compose(records, query.params);
506
+ query.composed = { records, value };
507
+ return value;
508
+ }
509
+
510
+ private cachedOf(query: Query<P, K, D, V>): Cached<D> | undefined {
511
+ const { state } = query;
512
+ if (state.variant === "ready") return this.composedOf(query, state.keys);
513
+ if (state.variant === "deleted") {
514
+ const tombstone = this.config.table.getTombstone(state.key);
515
+ if (tombstone == null) return undefined;
516
+ return this.deletedOf(tombstone, query.params);
517
+ }
518
+ return undefined;
519
+ }
520
+
521
+ /** Composes a rule-1 answer for a read with no live query, interned per entry
522
+ * and params. The memo dies with the entry it is keyed on, which a table
523
+ * replaces on every content change. */
524
+ private entryAnswerOf(entry: V, params: P): D {
525
+ if (typeof entry !== "object" || entry === null)
526
+ return this.config.compose([entry], params);
527
+ const hashed = hash(params);
528
+ let perEntry = this.entryAnswers.get(entry);
529
+ if (perEntry == null) {
530
+ perEntry = new Map();
531
+ this.entryAnswers.set(entry, perEntry);
532
+ }
533
+ if (perEntry.has(hashed)) return perEntry.get(hashed) as D;
534
+ const value = this.config.compose([entry], params);
535
+ perEntry.set(hashed, value);
536
+ return value;
537
+ }
538
+
539
+ private deletedOf(tombstone: Deleted<V>, params: P): Deleted<D> {
540
+ const hashed = hash(params);
541
+ let perTombstone = this.corpses.get(tombstone);
542
+ if (perTombstone == null) {
543
+ perTombstone = new Map();
544
+ this.corpses.set(tombstone, perTombstone);
545
+ }
546
+ let deleted = perTombstone.get(hashed);
547
+ if (deleted == null) {
548
+ deleted = new Deleted(
549
+ this.config.compose([tombstone.corpse], params),
550
+ tombstone.deletedAt,
551
+ );
552
+ perTombstone.set(hashed, deleted);
553
+ }
554
+ return deleted;
555
+ }
556
+
557
+ /** Notifies subscribers when any of the keys is a member of the ready
558
+ * answer; otherwise just drops the memo so the next read recomposes. */
559
+ private touchMembers(query: Query<P, K, D, V>, keys: K[]): void {
560
+ query.composed = undefined;
561
+ if (query.state.variant !== "ready") return;
562
+ const members = new Set(query.state.keys);
563
+ if (keys.some((key) => members.has(key))) this.touch(query);
564
+ }
565
+
566
+ /** Notifies every subscriber with the query's current answer. */
567
+ private touch(query: Query<P, K, D, V>): void {
568
+ // A watched foreign table is an input the records do not carry, so an
569
+ // event is the only signal that a composed answer changed.
570
+ query.composed = undefined;
571
+ if (query.handlers.size === 0) return;
572
+ const result = this.cachedOf(query);
573
+ query.handlers.forEach((handler) => {
574
+ try {
575
+ handler(result);
576
+ } catch (exc) {
577
+ this.report(exc, `failed to notify ${this.config.name} answer subscriber`);
578
+ }
579
+ });
580
+ }
581
+
582
+ private settle(
583
+ query: Query<P, K, D, V>,
584
+ expected: QueryState<K, D>,
585
+ next: QueryState<K, D>,
586
+ ): void {
587
+ // A late promise resolution must not clobber a maintenance update.
588
+ if (query.state !== expected) return;
589
+ query.state = next;
590
+ this.drainRechecks(query);
591
+ if (next.variant === "error") {
592
+ query.refetchOnSettle = false;
593
+ return;
594
+ }
595
+ if (query.refetchOnSettle === true) {
596
+ query.refetchOnSettle = false;
597
+ if (query.teardown != null) this.scheduleRefetch(query);
598
+ }
599
+ this.touch(query);
600
+ }
601
+
602
+ /**
603
+ * Replays membership changes a fetch in flight deferred. The fetch answers the query
604
+ * as of when it ran, so a change that raced it would otherwise be lost under the keys
605
+ * it publishes. Applies quietly: the settle that calls this notifies once for the
606
+ * whole answer.
607
+ */
608
+ private drainRechecks(query: Query<P, K, D, V>): void {
609
+ const pending = query.pendingRechecks;
610
+ if (pending == null) return;
611
+ query.pendingRechecks = undefined;
612
+ this.applyRechecks(query, pending);
613
+ }
614
+
615
+ private fetch(query: Query<P, K, D, V>, options?: FetchOptions): Promise<D> {
616
+ const promise = this.config.fetch(query.params, options).then(
617
+ (keys) => {
618
+ this.settle(query, loading, { variant: "ready", keys });
619
+ // The settle drains membership changes that raced the fetch, so the answer
620
+ // comes off the query: the keys the fetch returned are already stale.
621
+ const { state } = query;
622
+ return this.composedOf(query, state.variant === "ready" ? state.keys : keys);
623
+ },
624
+ (reason: unknown) => {
625
+ const error = errors.fromUnknown(reason);
626
+ this.settle(query, loading, { variant: "error", error });
627
+ throw error;
628
+ },
629
+ );
630
+ const loading: QueryState<K, D> = { variant: "loading", promise };
631
+ query.state = loading;
632
+ return promise;
633
+ }
634
+
635
+ /** Never rejects: a failed refetch leaves the previous answer in place. */
636
+ private async refetch(query: Query<P, K, D, V>): Promise<void> {
637
+ if (query.refetchTimer != null) {
638
+ clearTimeout(query.refetchTimer);
639
+ query.refetchTimer = undefined;
640
+ }
641
+ const before = query.state;
642
+ let keys: K[];
643
+ try {
644
+ keys = await this.config.fetch(query.params);
645
+ } catch (reason) {
646
+ if (query.state !== before) return;
647
+ // A refetch revalidates an answer the caller already has. Failing to
648
+ // confirm it is not grounds for taking it away.
649
+ if (before.variant === "ready") return;
650
+ query.state = { variant: "error", error: errors.fromUnknown(reason) };
651
+ return;
652
+ }
653
+ // Only apply if maintenance hasn't been torn down and no newer fetch
654
+ // superseded this one.
655
+ if (query.state !== before) return;
656
+ query.state = { variant: "ready", keys };
657
+ this.touch(query);
658
+ }
659
+
660
+ private scheduleRefetch(query: Query<P, K, D, V>): void {
661
+ // A fetch in flight publishes its own keys on settle, and a refetch racing
662
+ // it resolves into a state its result no longer matches, so it is dropped.
663
+ if (query.state.variant === "loading") {
664
+ query.refetchOnSettle = true;
665
+ return;
666
+ }
667
+ if (query.refetchTimer != null) clearTimeout(query.refetchTimer);
668
+ const wait = DEFAULT_DEBOUNCE.milliseconds;
669
+ query.refetchTimer = setTimeout(() => {
670
+ query.refetchTimer = undefined;
671
+ void this.refetch(query);
672
+ }, wait);
673
+ }
674
+
675
+ private isServerComputed(params: P): boolean {
676
+ const { serverFields } = this.config;
677
+ if (serverFields == null || serverFields.length === 0) return false;
678
+ if (typeof params !== "object" || params === null) return false;
679
+ return Object.entries(params).some(
680
+ ([field, value]) => value != null && serverFields.includes(field),
681
+ );
682
+ }
683
+
684
+ private maintain(query: Query<P, K, D, V>): void {
685
+ const teardown: destructor.Destructor[] = [];
686
+ query.teardown = teardown;
687
+ const { table, keyOf, matches, watch: watches } = this.config;
688
+ const { params } = query;
689
+ // Rules 2 and 3 build membership from events, and an unmaintained query
690
+ // received none, so its answer must be reconfirmed. Rule 1 re-seeds from the
691
+ // table below and needs no network.
692
+ if (
693
+ query.unmaintained === true &&
694
+ query.state.variant === "ready" &&
695
+ keyOf?.(params) == null
696
+ )
697
+ this.scheduleRefetch(query);
698
+ query.unmaintained = false;
699
+
700
+ // A keys-only query whose members the table already holds seeds a ready answer:
701
+ // rules 2 and 3 gate notifications on a ready state, so without the seed an
702
+ // answer getCached composes would look live while never updating.
703
+ const composableKeys = this.config.keysOf?.(params);
704
+ if (
705
+ composableKeys != null &&
706
+ query.state.variant !== "loading" &&
707
+ query.state.variant !== "ready"
708
+ ) {
709
+ const members = this.composableMembersOf(composableKeys);
710
+ if (members != null) {
711
+ query.state = { variant: "ready", keys: members };
712
+ this.touch(query);
713
+ }
714
+ }
715
+
716
+ if (this.isServerComputed(params) || (keyOf?.(params) == null && matches == null)) {
717
+ // Rule 3: server-computed — any relevant event triggers a debounced
718
+ // wholesale refetch; membership is never patched locally. A change to a
719
+ // current member's entry notifies immediately, so optimistic writes render
720
+ // without waiting on the refetch.
721
+ teardown.push(
722
+ table.subscribeBatch((events) => {
723
+ this.scheduleRefetch(query);
724
+ this.touchMembers(
725
+ query,
726
+ events.map((event) => event.key),
727
+ );
728
+ }),
729
+ );
730
+ watches?.forEach((w) =>
731
+ teardown.push(
732
+ w.attach(params, (result) => {
733
+ this.scheduleRefetch(query);
734
+ if (result !== "refetch") this.touchMembers(query, result);
735
+ }),
736
+ ),
737
+ );
738
+ return;
739
+ }
740
+
741
+ const key = keyOf?.(params);
742
+ if (key != null) {
743
+ // Rule 1: exact-key — track one entry; deletion flips the answer. An entry
744
+ // already in the table answers the query without a fetch. The state is a
745
+ // cache of the table's presence for this key and goes stale while
746
+ // unsubscribed (a delete observed, then a recreate), so maintenance
747
+ // re-seeds from the table instead of trusting the last answer. A loading
748
+ // query owns an in-flight promise its own settle will resolve.
749
+ if (query.state.variant !== "loading") {
750
+ const status = table.status(key);
751
+ if (status === "present" && query.state.variant !== "ready") {
752
+ query.state = { variant: "ready", keys: [key] };
753
+ this.touch(query);
754
+ } else if (status === "tombstoned" && query.state.variant !== "deleted") {
755
+ query.state = { variant: "deleted", key };
756
+ this.touch(query);
757
+ }
758
+ }
759
+ teardown.push(
760
+ table.subscribe((event) => {
761
+ if (event.variant === "set") query.state = { variant: "ready", keys: [key] };
762
+ else
763
+ query.state =
764
+ table.status(key) === "tombstoned"
765
+ ? { variant: "deleted", key }
766
+ : { variant: "unfetched" };
767
+ this.touch(query);
768
+ }, key),
769
+ );
770
+ watches?.forEach((w) =>
771
+ teardown.push(
772
+ w.attach(params, (result) => {
773
+ if (result === "refetch") return this.scheduleRefetch(query);
774
+ if (result.includes(key) && query.state.variant === "ready")
775
+ this.touch(query);
776
+ }),
777
+ ),
778
+ );
779
+ return;
780
+ }
781
+
782
+ // Rule 2: client-checkable — admit/evict exactly against `matches`.
783
+ teardown.push(
784
+ table.subscribeBatch((events) =>
785
+ this.recheckKeys(
786
+ query,
787
+ events.map((event) => event.key),
788
+ ),
789
+ ),
790
+ );
791
+ watches?.forEach((w) =>
792
+ teardown.push(
793
+ w.attach(params, (result) => {
794
+ if (result === "refetch") return this.scheduleRefetch(query);
795
+ void this.recheckMany(query, result);
796
+ }),
797
+ ),
798
+ );
799
+ }
800
+
801
+ /**
802
+ * Applies membership rechecks for the given keys against the table's current entries,
803
+ * without notifying. Admissions append in iteration order; evicting a single space's
804
+ * last member flips the query to deleted or unfetched. Returns whether the answer
805
+ * changed: membership moved, or a member's content was touched.
806
+ */
807
+ private applyRechecks(query: Query<P, K, D, V>, keys: Iterable<K>): boolean {
808
+ if (query.state.variant === "loading") {
809
+ const pending = (query.pendingRechecks ??= new Set<K>());
810
+ for (const key of keys) pending.add(key);
811
+ return false;
812
+ }
813
+ if (query.state.variant !== "ready") return false;
814
+ const { table, matches, single } = this.config;
815
+ const memberSet = new Set(query.state.keys);
816
+ const admitted: K[] = [];
817
+ let lastEvicted: K | null = null;
818
+ let touched = false;
819
+ for (const key of new Set(keys)) {
820
+ const entry = table.get(key);
821
+ const matched = entry != null && matches!(entry, query.params);
822
+ if (matched === memberSet.has(key)) {
823
+ touched ||= matched;
824
+ continue;
825
+ }
826
+ if (matched) {
827
+ memberSet.add(key);
828
+ admitted.push(key);
829
+ } else {
830
+ memberSet.delete(key);
831
+ lastEvicted = key;
832
+ }
833
+ }
834
+ if (admitted.length === 0 && lastEvicted == null) return touched;
835
+ const next = [...query.state.keys.filter((k) => memberSet.has(k)), ...admitted];
836
+ if (single === true && next.length === 0 && lastEvicted != null)
837
+ query.state =
838
+ table.status(lastEvicted) === "tombstoned"
839
+ ? { variant: "deleted", key: lastEvicted }
840
+ : { variant: "unfetched" };
841
+ else query.state = { variant: "ready", keys: next };
842
+ return true;
843
+ }
844
+
845
+ /** Applies rechecks for the keys, then notifies once when anything changed. */
846
+ private recheckKeys(query: Query<P, K, D, V>, keys: Iterable<K>): void {
847
+ if (this.applyRechecks(query, keys)) this.touch(query);
848
+ }
849
+
850
+ /** Never rejects: a failed backfill is reported and the recheck is skipped. */
851
+ private async recheckMany(query: Query<P, K, D, V>, keys: K[]): Promise<void> {
852
+ const { table } = this.config;
853
+ this.recheckKeys(query, keys);
854
+ const missing = keys.filter((key) => table.status(key) === "unknown");
855
+ if (missing.length === 0) return;
856
+ // Backfill through the table's fetch so membership can be rechecked;
857
+ // fetch-less tables serve cached entries only and the recheck is a no-op.
858
+ try {
859
+ await table.retrieve(missing);
860
+ this.recheckKeys(query, missing);
861
+ } catch (exc) {
862
+ this.report(exc, `failed to hydrate ${this.config.name} answers`);
863
+ }
864
+ }
865
+ }