@playfast/reform 1.2.1 → 1.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (416) hide show
  1. package/README.md +15 -6
  2. package/dist/boundary/boundary.d.ts +64 -0
  3. package/dist/boundary/boundary.d.ts.map +1 -0
  4. package/dist/boundary/boundary.js +102 -0
  5. package/dist/boundary/boundary.js.map +1 -0
  6. package/dist/calc/asyncCalc.d.ts +23 -0
  7. package/dist/calc/asyncCalc.d.ts.map +1 -0
  8. package/dist/calc/asyncCalc.js +100 -0
  9. package/dist/calc/asyncCalc.js.map +1 -0
  10. package/dist/calc/asyncCalcTypes.d.ts +80 -0
  11. package/dist/calc/asyncCalcTypes.d.ts.map +1 -0
  12. package/dist/calc/asyncCalcTypes.js +2 -0
  13. package/dist/calc/asyncCalcTypes.js.map +1 -0
  14. package/dist/calc/asyncData.d.ts +29 -0
  15. package/dist/calc/asyncData.d.ts.map +1 -0
  16. package/dist/calc/asyncData.js +21 -0
  17. package/dist/calc/asyncData.js.map +1 -0
  18. package/dist/calc/calc.d.ts +39 -0
  19. package/dist/calc/calc.d.ts.map +1 -0
  20. package/dist/calc/calc.js +48 -0
  21. package/dist/calc/calc.js.map +1 -0
  22. package/dist/calc/calcFamily.d.ts +40 -0
  23. package/dist/calc/calcFamily.d.ts.map +1 -0
  24. package/dist/calc/calcFamily.js +156 -0
  25. package/dist/calc/calcFamily.js.map +1 -0
  26. package/dist/calc/compose.d.ts +5 -0
  27. package/dist/calc/compose.d.ts.map +1 -0
  28. package/dist/calc/compose.js +5 -0
  29. package/dist/calc/compose.js.map +1 -0
  30. package/dist/calc/queryState.d.ts +14 -0
  31. package/dist/calc/queryState.d.ts.map +1 -0
  32. package/dist/calc/queryState.js +39 -0
  33. package/dist/calc/queryState.js.map +1 -0
  34. package/dist/channel/channel.d.ts +51 -0
  35. package/dist/channel/channel.d.ts.map +1 -0
  36. package/dist/channel/channel.js +91 -0
  37. package/dist/channel/channel.js.map +1 -0
  38. package/dist/channel/procedures.d.ts +24 -0
  39. package/dist/channel/procedures.d.ts.map +1 -0
  40. package/dist/channel/procedures.js +82 -0
  41. package/dist/channel/procedures.js.map +1 -0
  42. package/dist/compose/composition.d.ts +12 -0
  43. package/dist/compose/composition.d.ts.map +1 -0
  44. package/dist/compose/composition.js +62 -0
  45. package/dist/compose/composition.js.map +1 -0
  46. package/dist/compose/compositionTypes.d.ts +127 -0
  47. package/dist/compose/compositionTypes.d.ts.map +1 -0
  48. package/dist/compose/compositionTypes.js +2 -0
  49. package/dist/compose/compositionTypes.js.map +1 -0
  50. package/dist/compose/host.d.ts +6 -0
  51. package/dist/compose/host.d.ts.map +1 -0
  52. package/dist/compose/host.js +2 -0
  53. package/dist/compose/host.js.map +1 -0
  54. package/dist/compose/props.d.ts +10 -0
  55. package/dist/compose/props.d.ts.map +1 -0
  56. package/dist/compose/props.js +6 -0
  57. package/dist/compose/props.js.map +1 -0
  58. package/dist/compose/provide.d.ts +18 -0
  59. package/dist/compose/provide.d.ts.map +1 -0
  60. package/dist/compose/provide.js +49 -0
  61. package/dist/compose/provide.js.map +1 -0
  62. package/dist/compose/slot.d.ts +70 -0
  63. package/dist/compose/slot.d.ts.map +1 -0
  64. package/dist/compose/slot.js +26 -0
  65. package/dist/compose/slot.js.map +1 -0
  66. package/dist/compose/structure.d.ts +51 -0
  67. package/dist/compose/structure.d.ts.map +1 -0
  68. package/dist/compose/structure.js +117 -0
  69. package/dist/compose/structure.js.map +1 -0
  70. package/dist/compose/ui.d.ts +112 -0
  71. package/dist/compose/ui.d.ts.map +1 -0
  72. package/dist/compose/ui.js +58 -0
  73. package/dist/compose/ui.js.map +1 -0
  74. package/dist/definition/definition.d.ts +11 -0
  75. package/dist/definition/definition.d.ts.map +1 -0
  76. package/dist/definition/definition.js +35 -0
  77. package/dist/definition/definition.js.map +1 -0
  78. package/dist/event/event.d.ts +52 -0
  79. package/dist/event/event.d.ts.map +1 -0
  80. package/dist/event/event.js +88 -0
  81. package/dist/event/event.js.map +1 -0
  82. package/dist/event/eventGroup.d.ts +8 -0
  83. package/dist/event/eventGroup.d.ts.map +1 -0
  84. package/dist/event/eventGroup.js +3 -0
  85. package/dist/event/eventGroup.js.map +1 -0
  86. package/dist/feature/feature.d.ts +27 -0
  87. package/dist/feature/feature.d.ts.map +1 -0
  88. package/dist/feature/feature.js +20 -0
  89. package/dist/feature/feature.js.map +1 -0
  90. package/dist/feature/featureBinding.d.ts +85 -0
  91. package/dist/feature/featureBinding.d.ts.map +1 -0
  92. package/dist/feature/featureBinding.js +10 -0
  93. package/dist/feature/featureBinding.js.map +1 -0
  94. package/dist/feature/featureClass.d.ts +10 -0
  95. package/dist/feature/featureClass.d.ts.map +1 -0
  96. package/dist/feature/featureClass.js +2 -0
  97. package/dist/feature/featureClass.js.map +1 -0
  98. package/dist/feature/featureConfig.d.ts +86 -0
  99. package/dist/feature/featureConfig.d.ts.map +1 -0
  100. package/dist/feature/featureConfig.js +13 -0
  101. package/dist/feature/featureConfig.js.map +1 -0
  102. package/dist/feature/featureFactory.d.ts +15 -0
  103. package/dist/feature/featureFactory.d.ts.map +1 -0
  104. package/dist/feature/featureFactory.js +59 -0
  105. package/dist/feature/featureFactory.js.map +1 -0
  106. package/dist/feature/featureModule.d.ts +28 -0
  107. package/dist/feature/featureModule.d.ts.map +1 -0
  108. package/dist/feature/featureModule.js +33 -0
  109. package/dist/feature/featureModule.js.map +1 -0
  110. package/dist/feature/featureNativeTree.d.ts +26 -0
  111. package/dist/feature/featureNativeTree.d.ts.map +1 -0
  112. package/dist/feature/featureNativeTree.js +2 -0
  113. package/dist/feature/featureNativeTree.js.map +1 -0
  114. package/dist/feature/featureRequirement.d.ts +43 -0
  115. package/dist/feature/featureRequirement.d.ts.map +1 -0
  116. package/dist/feature/featureRequirement.js +6 -0
  117. package/dist/feature/featureRequirement.js.map +1 -0
  118. package/dist/feature/featureVariants.d.ts +9 -0
  119. package/dist/feature/featureVariants.d.ts.map +1 -0
  120. package/dist/feature/featureVariants.js +59 -0
  121. package/dist/feature/featureVariants.js.map +1 -0
  122. package/dist/feature/mountFeature.d.ts +10 -0
  123. package/dist/feature/mountFeature.d.ts.map +1 -0
  124. package/dist/feature/mountFeature.js +14 -0
  125. package/dist/feature/mountFeature.js.map +1 -0
  126. package/dist/graph/closure.d.ts +77 -0
  127. package/dist/graph/closure.d.ts.map +1 -0
  128. package/dist/graph/closure.js +4 -0
  129. package/dist/graph/closure.js.map +1 -0
  130. package/dist/graph/services.d.ts +30 -0
  131. package/dist/graph/services.d.ts.map +1 -0
  132. package/dist/graph/services.js +2 -0
  133. package/dist/graph/services.js.map +1 -0
  134. package/dist/graph/summary.d.ts +60 -0
  135. package/dist/graph/summary.d.ts.map +1 -0
  136. package/dist/graph/summary.js +7 -0
  137. package/dist/graph/summary.js.map +1 -0
  138. package/dist/index.d.ts +51 -0
  139. package/dist/index.d.ts.map +1 -0
  140. package/dist/index.js +34 -0
  141. package/dist/index.js.map +1 -0
  142. package/dist/internal/bucketCache.d.ts +19 -0
  143. package/dist/internal/bucketCache.d.ts.map +1 -0
  144. package/dist/internal/bucketCache.js +20 -0
  145. package/dist/internal/bucketCache.js.map +1 -0
  146. package/dist/internal/capture.d.ts +17 -0
  147. package/dist/internal/capture.d.ts.map +1 -0
  148. package/dist/internal/capture.js +5 -0
  149. package/dist/internal/capture.js.map +1 -0
  150. package/dist/internal/env.d.ts +2 -0
  151. package/dist/internal/env.d.ts.map +1 -0
  152. package/dist/internal/env.js +5 -0
  153. package/dist/internal/env.js.map +1 -0
  154. package/dist/internal/errors.d.ts +48 -0
  155. package/dist/internal/errors.d.ts.map +1 -0
  156. package/dist/internal/errors.js +69 -0
  157. package/dist/internal/errors.js.map +1 -0
  158. package/dist/internal/familyKeys.d.ts +16 -0
  159. package/dist/internal/familyKeys.d.ts.map +1 -0
  160. package/dist/internal/familyKeys.js +35 -0
  161. package/dist/internal/familyKeys.js.map +1 -0
  162. package/dist/internal/inspect.d.ts +5 -0
  163. package/dist/internal/inspect.d.ts.map +1 -0
  164. package/dist/internal/inspect.js +20 -0
  165. package/dist/internal/inspect.js.map +1 -0
  166. package/dist/internal/platform.d.ts +43 -0
  167. package/dist/internal/platform.d.ts.map +1 -0
  168. package/dist/internal/platform.js +68 -0
  169. package/dist/internal/platform.js.map +1 -0
  170. package/dist/internal/queryDriver.d.ts +7 -0
  171. package/dist/internal/queryDriver.d.ts.map +1 -0
  172. package/dist/internal/queryDriver.js +222 -0
  173. package/dist/internal/queryDriver.js.map +1 -0
  174. package/dist/internal/queryDriverConsume.d.ts +9 -0
  175. package/dist/internal/queryDriverConsume.d.ts.map +1 -0
  176. package/dist/internal/queryDriverConsume.js +16 -0
  177. package/dist/internal/queryDriverConsume.js.map +1 -0
  178. package/dist/internal/queryDriverHydrate.d.ts +17 -0
  179. package/dist/internal/queryDriverHydrate.d.ts.map +1 -0
  180. package/dist/internal/queryDriverHydrate.js +25 -0
  181. package/dist/internal/queryDriverHydrate.js.map +1 -0
  182. package/dist/internal/queryDriverPersist.d.ts +16 -0
  183. package/dist/internal/queryDriverPersist.d.ts.map +1 -0
  184. package/dist/internal/queryDriverPersist.js +15 -0
  185. package/dist/internal/queryDriverPersist.js.map +1 -0
  186. package/dist/internal/queryDriverPersistWarn.d.ts +12 -0
  187. package/dist/internal/queryDriverPersistWarn.d.ts.map +1 -0
  188. package/dist/internal/queryDriverPersistWarn.js +31 -0
  189. package/dist/internal/queryDriverPersistWarn.js.map +1 -0
  190. package/dist/internal/queryDriverSettle.d.ts +23 -0
  191. package/dist/internal/queryDriverSettle.d.ts.map +1 -0
  192. package/dist/internal/queryDriverSettle.js +52 -0
  193. package/dist/internal/queryDriverSettle.js.map +1 -0
  194. package/dist/internal/queryDriverStore.d.ts +28 -0
  195. package/dist/internal/queryDriverStore.d.ts.map +1 -0
  196. package/dist/internal/queryDriverStore.js +53 -0
  197. package/dist/internal/queryDriverStore.js.map +1 -0
  198. package/dist/internal/queryDriverTypes.d.ts +44 -0
  199. package/dist/internal/queryDriverTypes.d.ts.map +1 -0
  200. package/dist/internal/queryDriverTypes.js +9 -0
  201. package/dist/internal/queryDriverTypes.js.map +1 -0
  202. package/dist/internal/queryEvents.d.ts +12 -0
  203. package/dist/internal/queryEvents.d.ts.map +1 -0
  204. package/dist/internal/queryEvents.js +11 -0
  205. package/dist/internal/queryEvents.js.map +1 -0
  206. package/dist/internal/queryStore.d.ts +13 -0
  207. package/dist/internal/queryStore.d.ts.map +1 -0
  208. package/dist/internal/queryStore.js +11 -0
  209. package/dist/internal/queryStore.js.map +1 -0
  210. package/dist/internal/reuse.d.ts +2 -0
  211. package/dist/internal/reuse.d.ts.map +1 -0
  212. package/dist/internal/reuse.js +55 -0
  213. package/dist/internal/reuse.js.map +1 -0
  214. package/dist/internal/scheduler.d.ts +14 -0
  215. package/dist/internal/scheduler.d.ts.map +1 -0
  216. package/dist/internal/scheduler.js +101 -0
  217. package/dist/internal/scheduler.js.map +1 -0
  218. package/dist/internal/seeds.d.ts +3 -0
  219. package/dist/internal/seeds.d.ts.map +1 -0
  220. package/dist/internal/seeds.js +3 -0
  221. package/dist/internal/seeds.js.map +1 -0
  222. package/dist/internal/sources.d.ts +20 -0
  223. package/dist/internal/sources.d.ts.map +1 -0
  224. package/dist/internal/sources.js +39 -0
  225. package/dist/internal/sources.js.map +1 -0
  226. package/dist/internal/stateRegistry.d.ts +2 -0
  227. package/dist/internal/stateRegistry.d.ts.map +1 -0
  228. package/dist/internal/stateRegistry.js +15 -0
  229. package/dist/internal/stateRegistry.js.map +1 -0
  230. package/dist/internal/store.d.ts +15 -0
  231. package/dist/internal/store.d.ts.map +1 -0
  232. package/dist/internal/store.js +61 -0
  233. package/dist/internal/store.js.map +1 -0
  234. package/dist/internal/track.d.ts +15 -0
  235. package/dist/internal/track.d.ts.map +1 -0
  236. package/dist/internal/track.js +11 -0
  237. package/dist/internal/track.js.map +1 -0
  238. package/dist/internal/variance.d.ts +4 -0
  239. package/dist/internal/variance.d.ts.map +1 -0
  240. package/dist/internal/variance.js +2 -0
  241. package/dist/internal/variance.js.map +1 -0
  242. package/dist/internal.d.ts +43 -0
  243. package/dist/internal.d.ts.map +1 -0
  244. package/dist/internal.js +59 -0
  245. package/dist/internal.js.map +1 -0
  246. package/dist/namespace/namespace.d.ts +24 -0
  247. package/dist/namespace/namespace.d.ts.map +1 -0
  248. package/dist/namespace/namespace.js +20 -0
  249. package/dist/namespace/namespace.js.map +1 -0
  250. package/dist/procedure/procedure.d.ts +32 -0
  251. package/dist/procedure/procedure.d.ts.map +1 -0
  252. package/dist/procedure/procedure.js +34 -0
  253. package/dist/procedure/procedure.js.map +1 -0
  254. package/dist/reducer/reducer.d.ts +35 -0
  255. package/dist/reducer/reducer.d.ts.map +1 -0
  256. package/dist/reducer/reducer.js +73 -0
  257. package/dist/reducer/reducer.js.map +1 -0
  258. package/dist/remote/pendingQueue.d.ts +22 -0
  259. package/dist/remote/pendingQueue.d.ts.map +1 -0
  260. package/dist/remote/pendingQueue.js +129 -0
  261. package/dist/remote/pendingQueue.js.map +1 -0
  262. package/dist/remote/remoteState.d.ts +13 -0
  263. package/dist/remote/remoteState.d.ts.map +1 -0
  264. package/dist/remote/remoteState.js +189 -0
  265. package/dist/remote/remoteState.js.map +1 -0
  266. package/dist/remote/remoteStateMake.d.ts +8 -0
  267. package/dist/remote/remoteStateMake.d.ts.map +1 -0
  268. package/dist/remote/remoteStateMake.js +46 -0
  269. package/dist/remote/remoteStateMake.js.map +1 -0
  270. package/dist/remote/remoteStateSend.d.ts +43 -0
  271. package/dist/remote/remoteStateSend.d.ts.map +1 -0
  272. package/dist/remote/remoteStateSend.js +83 -0
  273. package/dist/remote/remoteStateSend.js.map +1 -0
  274. package/dist/remote/remoteStateTypes.d.ts +106 -0
  275. package/dist/remote/remoteStateTypes.d.ts.map +1 -0
  276. package/dist/remote/remoteStateTypes.js +4 -0
  277. package/dist/remote/remoteStateTypes.js.map +1 -0
  278. package/dist/runtime/appRuntime.d.ts +10 -0
  279. package/dist/runtime/appRuntime.d.ts.map +1 -0
  280. package/dist/runtime/appRuntime.js +6 -0
  281. package/dist/runtime/appRuntime.js.map +1 -0
  282. package/dist/runtime/bus.d.ts +22 -0
  283. package/dist/runtime/bus.d.ts.map +1 -0
  284. package/dist/runtime/bus.js +12 -0
  285. package/dist/runtime/bus.js.map +1 -0
  286. package/dist/runtime/capturedAppRuntime.d.ts +7 -0
  287. package/dist/runtime/capturedAppRuntime.d.ts.map +1 -0
  288. package/dist/runtime/capturedAppRuntime.js +137 -0
  289. package/dist/runtime/capturedAppRuntime.js.map +1 -0
  290. package/dist/runtime/eventBudget.d.ts +32 -0
  291. package/dist/runtime/eventBudget.d.ts.map +1 -0
  292. package/dist/runtime/eventBudget.js +68 -0
  293. package/dist/runtime/eventBudget.js.map +1 -0
  294. package/dist/runtime/featureMount.d.ts +48 -0
  295. package/dist/runtime/featureMount.d.ts.map +1 -0
  296. package/dist/runtime/featureMount.js +118 -0
  297. package/dist/runtime/featureMount.js.map +1 -0
  298. package/dist/runtime/instrumentation.d.ts +18 -0
  299. package/dist/runtime/instrumentation.d.ts.map +1 -0
  300. package/dist/runtime/instrumentation.js +16 -0
  301. package/dist/runtime/instrumentation.js.map +1 -0
  302. package/dist/runtime/loop.d.ts +44 -0
  303. package/dist/runtime/loop.d.ts.map +1 -0
  304. package/dist/runtime/loop.js +131 -0
  305. package/dist/runtime/loop.js.map +1 -0
  306. package/dist/runtime/queries.d.ts +20 -0
  307. package/dist/runtime/queries.d.ts.map +1 -0
  308. package/dist/runtime/queries.js +21 -0
  309. package/dist/runtime/queries.js.map +1 -0
  310. package/dist/runtime/runtimeHandle.d.ts +56 -0
  311. package/dist/runtime/runtimeHandle.d.ts.map +1 -0
  312. package/dist/runtime/runtimeHandle.js +2 -0
  313. package/dist/runtime/runtimeHandle.js.map +1 -0
  314. package/dist/scene/scene.d.ts +55 -0
  315. package/dist/scene/scene.d.ts.map +1 -0
  316. package/dist/scene/scene.js +74 -0
  317. package/dist/scene/scene.js.map +1 -0
  318. package/dist/state/state.d.ts +54 -0
  319. package/dist/state/state.d.ts.map +1 -0
  320. package/dist/state/state.js +41 -0
  321. package/dist/state/state.js.map +1 -0
  322. package/dist/state/stateFamily.d.ts +59 -0
  323. package/dist/state/stateFamily.d.ts.map +1 -0
  324. package/dist/state/stateFamily.js +139 -0
  325. package/dist/state/stateFamily.js.map +1 -0
  326. package/dist/state/stateGroup.d.ts +36 -0
  327. package/dist/state/stateGroup.d.ts.map +1 -0
  328. package/dist/state/stateGroup.js +33 -0
  329. package/dist/state/stateGroup.js.map +1 -0
  330. package/dist/state/token.d.ts +35 -0
  331. package/dist/state/token.d.ts.map +1 -0
  332. package/dist/state/token.js +18 -0
  333. package/dist/state/token.js.map +1 -0
  334. package/dist/synced/syncedStore.d.ts +44 -0
  335. package/dist/synced/syncedStore.d.ts.map +1 -0
  336. package/dist/synced/syncedStore.js +32 -0
  337. package/dist/synced/syncedStore.js.map +1 -0
  338. package/dist/testkit/flight.testkit.d.ts +18 -0
  339. package/dist/testkit/flight.testkit.d.ts.map +1 -0
  340. package/dist/testkit/flight.testkit.js +46 -0
  341. package/dist/testkit/flight.testkit.js.map +1 -0
  342. package/dist/ui/node.d.ts +3 -0
  343. package/dist/ui/node.d.ts.map +1 -0
  344. package/dist/ui/node.js +2 -0
  345. package/dist/ui/node.js.map +1 -0
  346. package/dist/ui/trigger.d.ts +2 -0
  347. package/dist/ui/trigger.d.ts.map +1 -0
  348. package/dist/ui/trigger.js +2 -0
  349. package/dist/ui/trigger.js.map +1 -0
  350. package/dist/wire/tree.d.ts +32 -0
  351. package/dist/wire/tree.d.ts.map +1 -0
  352. package/dist/wire/tree.js +64 -0
  353. package/dist/wire/tree.js.map +1 -0
  354. package/dist/wire/triggers.d.ts +25 -0
  355. package/dist/wire/triggers.d.ts.map +1 -0
  356. package/dist/wire/triggers.js +31 -0
  357. package/dist/wire/triggers.js.map +1 -0
  358. package/package.json +17 -5
  359. package/src/calc/asyncCalc-key-attribution.test.ts +182 -0
  360. package/src/calc/asyncCalc-registry-name.test.ts +82 -0
  361. package/src/calc/asyncCalc.superseded-commit.test.ts +162 -0
  362. package/src/calc/asyncCalc.test.ts +35 -7
  363. package/src/calc/asyncCalc.ts +33 -10
  364. package/src/calc/asyncCalcTypes.test.ts +64 -0
  365. package/src/calc/asyncCalcTypes.ts +8 -10
  366. package/src/calc/calcFamily.ts +81 -34
  367. package/src/calc/family-identity.test.ts +231 -0
  368. package/src/calc/queryState.ts +36 -5
  369. package/src/channel/channel-throttle-frames.test.ts +163 -0
  370. package/src/channel/channel.ts +5 -0
  371. package/src/compose/compose-identity.test.ts +175 -0
  372. package/src/compose/composition.ts +8 -4
  373. package/src/compose/structure.ts +45 -13
  374. package/src/compose/ui.ts +22 -5
  375. package/src/event/event.ts +31 -2
  376. package/src/event/syncTrigger.test.ts +66 -0
  377. package/src/internal/familyKeys.ts +41 -0
  378. package/src/internal/platform.test.ts +55 -0
  379. package/src/internal/platform.ts +72 -0
  380. package/src/internal/queryDriver-defect-attribution.test.ts +107 -0
  381. package/src/internal/queryDriver-persist-key.test.ts +118 -0
  382. package/src/internal/queryDriver.persistence-staleness.test.ts +209 -0
  383. package/src/internal/queryDriver.ts +92 -80
  384. package/src/internal/queryDriverConsume.ts +42 -0
  385. package/src/internal/queryDriverHydrate.ts +9 -3
  386. package/src/internal/queryDriverPersist.ts +42 -0
  387. package/src/internal/queryDriverPersistWarn.ts +61 -0
  388. package/src/internal/queryDriverSettle.ts +85 -0
  389. package/src/internal/queryDriverStore.ts +9 -74
  390. package/src/internal/queryDriverTypes.ts +10 -0
  391. package/src/internal/queryState-and-scheduler-audit.test.ts +170 -0
  392. package/src/internal/scheduler-multi-source-wake.test.ts +111 -0
  393. package/src/internal/scheduler.ts +80 -6
  394. package/src/internal.ts +1 -0
  395. package/src/reducer/reducer.ts +3 -3
  396. package/src/remote/pendingQueue.ts +35 -5
  397. package/src/remote/remoteState.intent-key-stamp.test.ts +207 -0
  398. package/src/remote/remoteState.key-attribution.test.ts +174 -0
  399. package/src/remote/remoteState.ts +27 -15
  400. package/src/remote/remoteStateSend.ts +29 -8
  401. package/src/remote/remoteStateTypes.ts +7 -0
  402. package/src/runtime/bus.ts +6 -1
  403. package/src/runtime/capturedAppRuntime.ts +134 -110
  404. package/src/runtime/eventBudget.ts +2 -1
  405. package/src/runtime/feature-mount-ownership.test.ts +539 -0
  406. package/src/runtime/featureMount.ts +110 -3
  407. package/src/runtime/loop.ts +29 -6
  408. package/src/runtime/mount-inflight-ownership.test.ts +361 -0
  409. package/src/runtime/mount-scope-hardening.test.ts +440 -0
  410. package/src/runtime/runtime.schema-identity.test.ts +188 -0
  411. package/src/state/state.ts +8 -4
  412. package/src/state/stateFamily.ts +86 -36
  413. package/src/state/stateGroup.ts +24 -6
  414. package/src/testkit/flight.testkit.ts +2 -1
  415. package/src/wire/tree-key-presence.test.ts +46 -0
  416. package/src/wire/tree.ts +9 -1
@@ -0,0 +1,82 @@
1
+ import { expect, it } from '@effect/vitest'
2
+ import { Effect, Layer, Schema as S } from 'effect'
3
+ import { AsyncCalc, Engine, State, StateGroup } from '../index'
4
+ import { Queries } from '../runtime/queries'
5
+ import { until } from '../testkit/flight.testkit'
6
+
7
+ // `AsyncCalc.invalidate`/`refetch` look the query up by `calc.name`. A class
8
+ // declaration defines its own `name` own-property, so `class ChatHistory extends
9
+ // AsyncCalc.make('chat/history', …)` reports 'ChatHistory' and shadows the
10
+ // registered 'chat/history'. The registry lookup misses and the call is silently
11
+ // swallowed by `?.` — the very naming the framework asks for (namespace the name
12
+ // per feature) is what breaks it.
13
+
14
+ class Seed extends State.make('seed', S.Number) {}
15
+ class Inputs extends StateGroup.make(Seed) {}
16
+ const seed = StateGroup.select(Inputs, 'seed')
17
+
18
+ const isStale = (name: string): Effect.Effect<boolean, never, Queries> =>
19
+ Effect.map(Queries, (queries) => queries.byName.get(name)?.snapshot().isStale ?? false)
20
+
21
+ it.live('refetch reaches a calc whose class binding differs from its registered name', () => {
22
+ const runs = { n: 0 }
23
+ class ChatHistory extends AsyncCalc.make('chat/history', {
24
+ inputs: [seed],
25
+ output: S.Number,
26
+ alwaysOn: true,
27
+ }) {}
28
+ const Live = AsyncCalc.live(ChatHistory, {
29
+ query: ({ seed: value }) =>
30
+ Effect.sync(() => {
31
+ runs.n += 1
32
+ return value
33
+ }),
34
+ })
35
+ const TestLayer = Live.pipe(
36
+ Layer.provideMerge(StateGroup.live(Inputs, { seed: 1 })),
37
+ Layer.provideMerge(Engine),
38
+ )
39
+
40
+ return Effect.gen(function* () {
41
+ const store = yield* ChatHistory.store
42
+ yield* until(
43
+ () => store.get(),
44
+ (v) => v._tag === 'Success',
45
+ )
46
+ expect(runs.n).toBe(1)
47
+
48
+ yield* AsyncCalc.refetch(ChatHistory)
49
+ yield* until(
50
+ () => runs.n,
51
+ (n) => n > 1,
52
+ 20,
53
+ )
54
+ expect(runs.n).toBe(2)
55
+ }).pipe(Effect.provide(TestLayer))
56
+ })
57
+
58
+ it.live('invalidate reaches a calc whose class binding differs from its registered name', () => {
59
+ class ChatBranches extends AsyncCalc.make('chat/branches', {
60
+ inputs: [seed],
61
+ output: S.Number,
62
+ alwaysOn: true,
63
+ }) {}
64
+ const Live = AsyncCalc.live(ChatBranches, {
65
+ query: ({ seed: value }) => Effect.succeed(value),
66
+ })
67
+ const TestLayer = Live.pipe(
68
+ Layer.provideMerge(StateGroup.live(Inputs, { seed: 1 })),
69
+ Layer.provideMerge(Engine),
70
+ )
71
+
72
+ return Effect.gen(function* () {
73
+ const store = yield* ChatBranches.store
74
+ yield* until(
75
+ () => store.get(),
76
+ (v) => v._tag === 'Success' && v.refetching === false,
77
+ )
78
+
79
+ yield* AsyncCalc.invalidate(ChatBranches)
80
+ expect(yield* isStale('chat/branches')).toBe(true)
81
+ }).pipe(Effect.provide(TestLayer))
82
+ })
@@ -0,0 +1,162 @@
1
+ import { expect, it } from '@effect/vitest'
2
+ import { Data, Effect, Layer, Schema as S } from 'effect'
3
+ import { AsyncCalc, Engine, State, StateGroup } from '../index'
4
+ import { makeFlightGate, until } from '../testkit/flight.testkit'
5
+
6
+ // Class bindings match their registered names on purpose: `AsyncCalc.invalidate` /
7
+ // `refetch` resolve a query by `calc.name`, and a class declaration installs its
8
+ // own `name` own-property that shadows the static the framework wrote. That
9
+ // divergence is a separate finding — these tests keep the two aligned so they
10
+ // isolate the driver, not the lookup.
11
+
12
+ class Torn extends Data.TaggedError('Torn')<{ readonly reason: string }> {}
13
+
14
+ // A negative wait: long enough that the missing transition would have landed,
15
+ // short enough that the assertion — not the timeout — reports the failure.
16
+ const settleRounds = 40
17
+
18
+ // `commitSuccess` asks only `disabledNow()` — is the query enabled *now* — before
19
+ // writing. It never asks whether the request it is committing survived the gate.
20
+ // A run that was in flight when `disabled` flipped is dropped from the queue's
21
+ // point of view and its state wiped to Idle, yet it still lands as the answer once
22
+ // the query is re-enabled under a different key. `coalesce: 'trailing'` makes this
23
+ // deterministic: that consumer never cancels the run it is holding.
24
+ it.live('a flight disabled away commits over the query the gate reset', () => {
25
+ class Count extends State.make('supersedeDisableCount', S.Number) {}
26
+ class Inputs extends StateGroup.make(Count) {}
27
+ class SupersedeDisableResurrect extends AsyncCalc.make('SupersedeDisableResurrect', {
28
+ inputs: [StateGroup.select(Inputs, 'supersedeDisableCount')],
29
+ output: S.Number,
30
+ }) {}
31
+ const firstGate = makeFlightGate()
32
+ const laterGate = makeFlightGate()
33
+ // Armed before the layer builds, so the very first flight parks at the gate.
34
+ Effect.runSync(firstGate.hold)
35
+ Effect.runSync(laterGate.hold)
36
+ const TestLayer = AsyncCalc.live(SupersedeDisableResurrect, {
37
+ query: ({ supersedeDisableCount: count }) =>
38
+ count === 1
39
+ ? firstGate.through(Effect.succeed(count * 2))
40
+ : laterGate.through(Effect.succeed(count * 2)),
41
+ disabled: ({ supersedeDisableCount: count }) => count < 0,
42
+ coalesce: 'trailing',
43
+ }).pipe(Layer.provideMerge(StateGroup.live(Inputs, { supersedeDisableCount: 1 })))
44
+
45
+ return Effect.gen(function* () {
46
+ const source = yield* StateGroup.select(Inputs, 'supersedeDisableCount').store
47
+ const store = yield* SupersedeDisableResurrect.store
48
+ yield* firstGate.awaitEntry(1)
49
+
50
+ // count=1's answer is held at the gate; the disable then wipes the query back
51
+ // to Idle and drops the key it was asked with.
52
+ source.set(-1)
53
+ yield* until(
54
+ () => store.get(),
55
+ (v) => v._tag === 'Idle',
56
+ )
57
+ expect(store.get()._tag).toBe('Idle')
58
+
59
+ // Re-enabled at a different key. The trailing consumer runs count=1 to
60
+ // completion first, then parks count=5's flight, so what the store reports in
61
+ // between is a stable state rather than a window to catch.
62
+ source.set(5)
63
+ yield* firstGate.release
64
+ yield* laterGate.awaitEntry(1)
65
+
66
+ // count=1's answer belongs to a key the gate already discarded: it is not an
67
+ // answer about count=5, and it is not permission to leave Idle behind.
68
+ expect(store.get()).toMatchObject({ _tag: 'Loading' })
69
+ }).pipe(Effect.provide(TestLayer))
70
+ })
71
+
72
+ // The same hole on the failure side: `settleError` sits behind the identical
73
+ // `!disabledNow()` guard, so a disabled-away run's failure is published as the
74
+ // current key's error.
75
+ it.live('a failure disabled away commits over the query the gate reset', () => {
76
+ class Count extends State.make('supersedeDisableErrCount', S.Number) {}
77
+ class Inputs extends StateGroup.make(Count) {}
78
+ class SupersedeDisableResurrectError extends AsyncCalc.make('SupersedeDisableResurrectError', {
79
+ inputs: [StateGroup.select(Inputs, 'supersedeDisableErrCount')],
80
+ output: S.Number,
81
+ error: S.String,
82
+ }) {}
83
+ const firstGate = makeFlightGate()
84
+ const laterGate = makeFlightGate()
85
+ Effect.runSync(firstGate.hold)
86
+ Effect.runSync(laterGate.hold)
87
+ const TestLayer = AsyncCalc.live(SupersedeDisableResurrectError, {
88
+ query: ({ supersedeDisableErrCount: count }): Effect.Effect<number, string> =>
89
+ count === 1
90
+ ? // Park first, then fail: the gate only wraps a success.
91
+ firstGate.through(Effect.void).pipe(Effect.zipRight(Effect.fail('boom-for-1')))
92
+ : laterGate.through(Effect.succeed(count * 2)),
93
+ disabled: ({ supersedeDisableErrCount: count }) => count < 0,
94
+ coalesce: 'trailing',
95
+ }).pipe(Layer.provideMerge(StateGroup.live(Inputs, { supersedeDisableErrCount: 1 })))
96
+
97
+ return Effect.gen(function* () {
98
+ const source = yield* StateGroup.select(Inputs, 'supersedeDisableErrCount').store
99
+ const store = yield* SupersedeDisableResurrectError.store
100
+ yield* firstGate.awaitEntry(1)
101
+
102
+ source.set(-1)
103
+ yield* until(
104
+ () => store.get(),
105
+ (v) => v._tag === 'Idle',
106
+ )
107
+ expect(store.get()._tag).toBe('Idle')
108
+
109
+ source.set(5)
110
+ yield* firstGate.release
111
+ yield* laterGate.awaitEntry(1)
112
+
113
+ // count=1's failure is not a failure of count=5.
114
+ expect(store.get()).toMatchObject({ _tag: 'Loading' })
115
+ }).pipe(Effect.provide(TestLayer))
116
+ })
117
+
118
+ // `runQuery` folds the whole cause: a typed failure reaches `settleError`, but a
119
+ // defect is only logged and then swallowed as `Option.none()`. Nothing ever clears
120
+ // the `isFetching` flag `enqueue` raised, so the run that died stays advertised as
121
+ // in flight for the rest of the query's life.
122
+ it.live('a query that dies leaves the view refetching forever', () => {
123
+ const runs = { n: 0 }
124
+ class RegressDefectQ extends AsyncCalc.make('RegressDefectQ', {
125
+ inputs: [],
126
+ output: S.Number,
127
+ alwaysOn: true,
128
+ }) {}
129
+ const TestLayer = AsyncCalc.live(RegressDefectQ, {
130
+ query: (): Effect.Effect<number> =>
131
+ Effect.suspend(() => {
132
+ runs.n += 1
133
+ // A defect, not a typed failure: an exception escaping the query body.
134
+ return runs.n === 1 ? Effect.succeed(1) : Effect.die(new Torn({ reason: 'kaboom' }))
135
+ }),
136
+ }).pipe(Layer.provideMerge(Engine))
137
+
138
+ return Effect.gen(function* () {
139
+ const store = yield* RegressDefectQ.store
140
+ yield* until(
141
+ () => store.get(),
142
+ (v) => v._tag === 'Success' && v.refetching === false,
143
+ )
144
+ expect(store.get()).toMatchObject({ _tag: 'Success', value: 1, refetching: false })
145
+
146
+ yield* AsyncCalc.refetch(RegressDefectQ)
147
+ yield* until(
148
+ () => runs.n,
149
+ (n) => n > 1,
150
+ )
151
+ expect(runs.n).toBe(2)
152
+
153
+ // The run is over — it died. Nothing is in flight and nothing is queued, so
154
+ // the view must stop advertising a refetch.
155
+ yield* until(
156
+ () => store.get(),
157
+ (v) => v._tag === 'Success' && v.refetching === false,
158
+ settleRounds,
159
+ )
160
+ expect(store.get()).toMatchObject({ _tag: 'Success', value: 1, refetching: false })
161
+ }).pipe(Effect.provide(TestLayer))
162
+ })
@@ -104,6 +104,10 @@ it.live('a dependency change re-fetches, keeping the last value with refetching:
104
104
  const gate = makeFlightGate()
105
105
  const DoubledLive = AsyncCalc.live(Doubled, {
106
106
  query: ({ count }) => gate.through(Effect.succeed(count * 2)),
107
+ // Opt-in: across an input-key change the previous key's payload is retained
108
+ // only under `keepPreviousData`. The default reads as Loading — see
109
+ // asyncCalc-key-attribution.test.ts.
110
+ keepPreviousData: true,
107
111
  })
108
112
  const TestLayer = DoubledLive.pipe(Layer.provideMerge(StateGroup.live(Inputs, { count: 5 })))
109
113
 
@@ -245,7 +249,10 @@ it.live('invalidateBy: an input change outside the key does not re-fetch', () =>
245
249
  // Holding the gate means any flight the status churn triggered would still
246
250
  // be marked fetching here, and could not have settled back out of view.
247
251
  yield* gate.hold
248
- yield* afterNotify(status, Effect.sync(() => status.set('b')))
252
+ yield* afterNotify(
253
+ status,
254
+ Effect.sync(() => status.set('b')),
255
+ )
249
256
  expect(store.get()).toMatchObject({ _tag: 'Success', refetching: false })
250
257
  expect(runs.n).toBe(1)
251
258
  yield* gate.release
@@ -338,8 +345,14 @@ it.live(
338
345
  yield* gate.hold
339
346
  source.set(2)
340
347
  yield* gate.awaitEntry(2)
341
- yield* afterNotify(source, Effect.sync(() => source.set(3)))
342
- yield* afterNotify(source, Effect.sync(() => source.set(4)))
348
+ yield* afterNotify(
349
+ source,
350
+ Effect.sync(() => source.set(3)),
351
+ )
352
+ yield* afterNotify(
353
+ source,
354
+ Effect.sync(() => source.set(4)),
355
+ )
343
356
  yield* gate.release
344
357
  const settled = yield* until(
345
358
  () => store.get(),
@@ -371,6 +384,9 @@ it.live(
371
384
  ? trailingGate.through(Effect.succeed(count * 2))
372
385
  : firstGate.through(Effect.succeed(count * 2)),
373
386
  coalesce: 'trailing',
387
+ // The stale settle belongs to a superseded key, so it is visible only under
388
+ // `keepPreviousData`; by default it reads as Loading.
389
+ keepPreviousData: true,
374
390
  })
375
391
  const TestLayer = DoubledLive.pipe(Layer.provideMerge(StateGroup.live(Inputs, { count: 1 })))
376
392
 
@@ -386,7 +402,10 @@ it.live(
386
402
  yield* trailingGate.hold
387
403
  source.set(2)
388
404
  yield* firstGate.awaitEntry(2)
389
- yield* afterNotify(source, Effect.sync(() => source.set(3)))
405
+ yield* afterNotify(
406
+ source,
407
+ Effect.sync(() => source.set(3)),
408
+ )
390
409
 
391
410
  yield* firstGate.release
392
411
  yield* trailingGate.awaitEntry(1)
@@ -437,8 +456,14 @@ it.live('coalesce trailing: a disable while a request waits in the slot skips th
437
456
  yield* gate.hold
438
457
  source.set(2)
439
458
  yield* gate.awaitEntry(2)
440
- yield* afterNotify(source, Effect.sync(() => source.set(3)))
441
- yield* afterNotify(source, Effect.sync(() => source.set(-1)))
459
+ yield* afterNotify(
460
+ source,
461
+ Effect.sync(() => source.set(3)),
462
+ )
463
+ yield* afterNotify(
464
+ source,
465
+ Effect.sync(() => source.set(-1)),
466
+ )
442
467
  yield* gate.release
443
468
  expect(store.get()._tag).toBe('Idle')
444
469
  expect(completed).toEqual([1, 2])
@@ -545,7 +570,10 @@ it.live(
545
570
  expect(runs.n).toBe(1)
546
571
 
547
572
  yield* gate.hold
548
- yield* afterNotify(status, Effect.sync(() => status.set('b')))
573
+ yield* afterNotify(
574
+ status,
575
+ Effect.sync(() => status.set('b')),
576
+ )
549
577
  expect(store.get()).toMatchObject({ _tag: 'Success', refetching: false })
550
578
  expect(runs.n).toBe(1)
551
579
  yield* gate.release
@@ -1,6 +1,7 @@
1
1
  import { Context, Effect, Layer, Match, Option, Schema } from 'effect'
2
2
  import { yieldableClass } from '../definition/definition'
3
3
  import { type AnyEvent } from '../event/event'
4
+ import { resolvePersistSlot } from '../internal/queryDriverPersistWarn'
4
5
  import { type InputStores } from '../internal/sources'
5
6
  import {
6
7
  bumpRevision,
@@ -15,7 +16,7 @@ import { type Store } from '../internal/store'
15
16
  import { readTracked } from '../internal/track'
16
17
  import * as Reducer from '../reducer/reducer'
17
18
  import { Reducers } from '../runtime/loop'
18
- import { Queries } from '../runtime/queries'
19
+ import { Queries, type QueryHandle } from '../runtime/queries'
19
20
  import * as State from '../state/state'
20
21
  import { type AnySource, type SourceCapture } from '../state/token'
21
22
  import { type AsyncData, narrowStore } from './asyncData'
@@ -44,7 +45,6 @@ export type {
44
45
  DefinedAsyncCalcClass,
45
46
  } from './asyncCalcTypes'
46
47
 
47
-
48
48
  export function make<
49
49
  const N extends string,
50
50
  const Inputs extends ReadonlyArray<AnySource>,
@@ -154,10 +154,12 @@ export function live<
154
154
  const cfg: AsyncCalcLiveView<Inputs, A, E, R> = config
155
155
 
156
156
  const persistOption = cfg.persist
157
- const persistKey =
158
- persistOption === true || persistOption === undefined || persistOption === false
159
- ? calc.manifest.name
160
- : (persistOption.key ?? calc.manifest.name)
157
+ const persistKey = yield* resolvePersistSlot({
158
+ label: 'AsyncCalc',
159
+ name: calc.manifest.name,
160
+ inputs: calc.inputs.length,
161
+ option: persistOption,
162
+ })
161
163
  const persistCodec: QueryCodec<A> = {
162
164
  encode: Schema.encode(calc.manifest.output),
163
165
  decode: Schema.decodeUnknown(calc.manifest.output),
@@ -188,6 +190,7 @@ export function live<
188
190
  disabled: cfg.disabled,
189
191
  coalesce: cfg.coalesce,
190
192
  reuse: cfg.reuse,
193
+ keepPreviousData: cfg.keepPreviousData,
191
194
  persist,
192
195
  extraKey,
193
196
  })
@@ -208,14 +211,34 @@ export function live<
208
211
  )
209
212
  }
210
213
 
214
+ // Resolved through `manifest`, never through `calc.name`: a `class X extends
215
+ // AsyncCalc.make('some/name')` declaration defines its own `name` own-property,
216
+ // which shadows the registered one — so a name-keyed lookup silently misses for
217
+ // exactly the namespaced names the framework asks authors to use.
211
218
  export interface NamedCalc {
212
- readonly name: string
219
+ readonly manifest: { readonly name: string }
213
220
  }
214
221
 
215
- export const invalidate = (calc: NamedCalc): Effect.Effect<void, never, Queries> =>
222
+ const onHandle = (
223
+ calc: NamedCalc,
224
+ verb: string,
225
+ act: (handle: QueryHandle) => void,
226
+ ): Effect.Effect<void, never, Queries> =>
216
227
  Effect.flatMap(Queries, (queries) =>
217
- Effect.sync(() => queries.byName.get(calc.name)?.invalidate()),
228
+ Effect.suspend(() =>
229
+ Option.match(Option.fromNullable(queries.byName.get(calc.manifest.name)), {
230
+ // Swallowing this is how a dead invalidation goes unnoticed for a release.
231
+ onNone: () =>
232
+ Effect.logWarning(
233
+ `reform: ${verb} skipped — no live query named '${calc.manifest.name}'`,
234
+ ),
235
+ onSome: (handle) => Effect.sync(() => act(handle)),
236
+ }),
237
+ ),
218
238
  )
219
239
 
240
+ export const invalidate = (calc: NamedCalc): Effect.Effect<void, never, Queries> =>
241
+ onHandle(calc, 'invalidate', (handle) => handle.invalidate())
242
+
220
243
  export const refetch = (calc: NamedCalc): Effect.Effect<void, never, Queries> =>
221
- Effect.flatMap(Queries, (queries) => Effect.sync(() => queries.byName.get(calc.name)?.refetch()))
244
+ onHandle(calc, 'refetch', (handle) => handle.refetch())
@@ -0,0 +1,64 @@
1
+ import { expect, it } from '@effect/vitest'
2
+ import type { Effect } from 'effect'
3
+ import { Schema as S } from 'effect'
4
+ import { AsyncCalc, Namespace, State, StateGroup } from '../index'
5
+ import type { AsyncData } from './asyncData'
6
+
7
+ // Pins the config -> value-type contract: a named schema in `output`/`error` must survive
8
+ // inference. The package publishes source, so consumers compile these signatures under their
9
+ // own tsconfig — keep `output`/`error` bare inference sites rather than wrapped in anything
10
+ // deferred. `Namespace.make` is the exposed path here because `AsyncCalc.make` can fall back
11
+ // to its second overload, which would mask a regression in `AsyncCalcConfig` itself.
12
+ type Equals<A, B> = (<T>() => T extends A ? 1 : 2) extends <T>() => T extends B ? 1 : 2 ? true
13
+ : false
14
+ const expectTypeEquality = <_Proof extends true>(): void => {}
15
+
16
+ const ns = Namespace.make('apps')
17
+
18
+ it('infers output and error schema types from named schemas', () => {
19
+ const Result = S.Struct({ id: S.String, count: S.Number })
20
+
21
+ class Count extends State.make('count', S.Number) {}
22
+ class Inputs extends StateGroup.make(Count) {}
23
+ class MyApps extends ns.AsyncCalc('MyApps', {
24
+ inputs: [StateGroup.select(Inputs, 'count')],
25
+ output: Result,
26
+ error: S.String,
27
+ alwaysOn: true,
28
+ }) {}
29
+
30
+ type Data = Effect.Effect.Success<typeof MyApps>
31
+ expectTypeEquality<
32
+ Equals<Data, AsyncData<{ readonly id: string; readonly count: number }, string, false>>
33
+ >()
34
+
35
+ // `MyApps.name` is the JS class binding's own name, which shadows the prefixed one.
36
+ expect(MyApps.manifest.name).toBe('apps.MyApps')
37
+ expect(MyApps.gated).toBe(false)
38
+ })
39
+
40
+ it('defaults the error type to never when `error` is omitted', () => {
41
+ class Loaded extends ns.AsyncCalc('Loaded', {
42
+ inputs: [],
43
+ output: S.Number,
44
+ }) {}
45
+
46
+ type Data = Effect.Effect.Success<typeof Loaded>
47
+ expectTypeEquality<Equals<Data, AsyncData<number, never, true>>>()
48
+
49
+ expect(Loaded.gated).toBe(true)
50
+ })
51
+
52
+ it('keeps `AsyncCalc.make` inference intact through its overloads', () => {
53
+ class Doubled extends AsyncCalc.make('Doubled', {
54
+ inputs: [],
55
+ output: S.Number,
56
+ error: S.String,
57
+ alwaysOn: true,
58
+ }) {}
59
+
60
+ type Data = Effect.Effect.Success<typeof Doubled>
61
+ expectTypeEquality<Equals<Data, AsyncData<number, string, false>>>()
62
+
63
+ expect(Doubled.gated).toBe(false)
64
+ })
@@ -70,14 +70,6 @@ export interface DefinedAsyncCalcClass<
70
70
  readonly capture: <Result>(visit: SourceCapture<Result>) => Result
71
71
  }
72
72
 
73
- type ConfigSchema<ValueOrSchema> = [ValueOrSchema] extends [Schema.Schema.AnyNoContext]
74
- ? ValueOrSchema
75
- : AnySchema<ValueOrSchema>
76
-
77
- type ConfigErrorSchema<ValueOrSchema> = [ValueOrSchema] extends [undefined]
78
- ? ValueOrSchema
79
- : ConfigSchema<ValueOrSchema>
80
-
81
73
  export interface AsyncCalcConfig<
82
74
  Inputs extends ReadonlyArray<AnySource>,
83
75
  OutputSchema,
@@ -85,9 +77,9 @@ export interface AsyncCalcConfig<
85
77
  AlwaysOn extends boolean,
86
78
  > {
87
79
  readonly inputs: Inputs
88
- readonly output: ConfigSchema<OutputSchema>
80
+ readonly output: OutputSchema
89
81
  // oxlint-disable-next-line reform-rules/no-optional-fields -- caller-omitted option built at the asyncCalc/remoteState construction site; Option<T> would change that call shape
90
- readonly error?: ConfigErrorSchema<ErrorSchema>
82
+ readonly error?: ErrorSchema
91
83
  // oxlint-disable-next-line reform-rules/no-optional-fields -- caller-omitted option built at the asyncCalc/remoteState construction site; Option<T> would change that call shape
92
84
  readonly alwaysOn?: AlwaysOn
93
85
  }
@@ -117,6 +109,11 @@ export type AsyncCalcLive<
117
109
  readonly coalesce?: 'switch' | 'trailing'
118
110
  // oxlint-disable-next-line reform-rules/no-optional-fields -- caller-omitted option built at the asyncCalc/remoteState construction site; Option<T> would change that call shape
119
111
  readonly reuse?: boolean
112
+ // Keep showing the previous key's result while the new key loads. Off by default:
113
+ // a retained payload is not an answer to the key being asked. Turn it on only when
114
+ // the payload is displayed, never when fields are read off it.
115
+ // oxlint-disable-next-line reform-rules/no-optional-fields -- caller-omitted option built at the asyncCalc/remoteState construction site; Option<T> would change that call shape
116
+ readonly keepPreviousData?: boolean
120
117
  // oxlint-disable-next-line reform-rules/no-optional-fields -- caller-omitted option built at the asyncCalc/remoteState construction site; Option<T> would change that call shape
121
118
  readonly persist?:
122
119
  | boolean
@@ -141,6 +138,7 @@ export interface AsyncCalcLiveView<Inputs extends ReadonlyArray<AnySource>, A, E
141
138
  readonly disabled?: (inputs: InputsObject<Inputs>) => boolean
142
139
  readonly coalesce?: 'switch' | 'trailing'
143
140
  readonly reuse?: boolean
141
+ readonly keepPreviousData?: boolean
144
142
  readonly persist?:
145
143
  | boolean
146
144
  | { readonly key?: string | ((inputs: InputsObject<Inputs>) => string) }