@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
@@ -14,6 +14,10 @@ export interface ReducerEntry {
14
14
  }
15
15
 
16
16
  export interface ReducerRegistry {
17
+ // Captured where the drain captures it, so the synchronous trigger path and the
18
+ // drain report to the same profiler even though triggers are built later, at mount,
19
+ // outside the scene's `Layer.locally(CurrentInstrumentation, ...)` scope.
20
+ readonly instrumentation: Instrumentation
17
21
  readonly entries: Array<ReducerEntry>
18
22
  readonly byName: Map<string, Set<ReducerEntry>>
19
23
  readonly byTag: Map<string, Set<ReducerEntry>>
@@ -38,7 +42,7 @@ const dropFromBuckets = <T>(map: Map<string, Set<T>>, key: string, target: T): v
38
42
  }
39
43
  }
40
44
 
41
- const makeReducerRegistry = (): ReducerRegistry => {
45
+ const makeReducerRegistry = (instrumentation: Instrumentation): ReducerRegistry => {
42
46
  const entries = new Set<ReducerEntry>()
43
47
  const byName = new Map<string, Set<ReducerEntry>>()
44
48
  const byTag = new Map<string, Set<ReducerEntry>>()
@@ -47,6 +51,7 @@ const makeReducerRegistry = (): ReducerRegistry => {
47
51
  value: Option.some([]),
48
52
  }
49
53
  return {
54
+ instrumentation,
50
55
  get entries() {
51
56
  return Option.getOrElse(snapshot.value, () => {
52
57
  const current = Array.from(entries)
@@ -108,7 +113,26 @@ const isolateApply = (
108
113
  )
109
114
  }
110
115
 
111
- export const reducersLayer: Layer.Layer<Reducers> = Layer.sync(Reducers, makeReducerRegistry)
116
+ export interface ApplyReducersInput {
117
+ readonly registry: ReducerRegistry
118
+ readonly event: Tagged
119
+ }
120
+
121
+ export interface ReducerFailure {
122
+ readonly event: Tagged
123
+ readonly error: unknown
124
+ }
125
+
126
+ /** Run every reducer registered for one event. A defect in one must not skip the rest. */
127
+ export const applyReducers = (input: ApplyReducersInput): ReadonlyArray<ReducerFailure> =>
128
+ Arr.filterMap(input.registry.tagged(input.event._tag), (reducer) =>
129
+ isolateApply(reducer, input.event, input.registry.instrumentation),
130
+ )
131
+
132
+ export const reducersLayer: Layer.Layer<Reducers> = Layer.effect(
133
+ Reducers,
134
+ Effect.map(resolveInstrumentation, makeReducerRegistry),
135
+ )
112
136
 
113
137
  // Partitioning keeps dispatch order stable within each priority.
114
138
  const rank = (priority: Envelope['priority']): number => (priority === 'High' ? 0 : 1)
@@ -145,10 +169,9 @@ const drain = Effect.gen(function* () {
145
169
  instrumentation.eventDispatched(envelope.event._tag, envelope.priority),
146
170
  )
147
171
  const collected = Arr.flatMap(ordered, (envelope) =>
148
- // A reducer defect must not abandon the remaining frame.
149
- Arr.filterMap(reducers.tagged(envelope.event._tag), (reducer) =>
150
- isolateApply(reducer, envelope.event, instrumentation),
151
- ),
172
+ envelope.reduced
173
+ ? []
174
+ : applyReducers({ registry: reducers, event: envelope.event }),
152
175
  )
153
176
  ordered.forEach((envelope) => {
154
177
  // channelsFor is distinct; per-procedure offers would double-run shared channels.
@@ -0,0 +1,361 @@
1
+ import { Context, Deferred, Effect, Layer, Option } from 'effect'
2
+ import { expect, it, test } from '@effect/vitest'
3
+ import * as Composition from '../compose/composition'
4
+ import { provide } from '../compose/provide'
5
+ import { mount } from '../compose/structure'
6
+ import * as Ui from '../compose/ui'
7
+ import { ui } from '../compose/ui'
8
+ import * as Feature from '../feature/feature'
9
+ import { featureModule } from '../feature/feature'
10
+ import { scene } from '../scene/scene'
11
+ import { makeScheduler } from '../internal/scheduler'
12
+ import { flush, until } from '../testkit/flight.testkit'
13
+ import { Engine } from './loop'
14
+ import { makeAppRuntime } from './appRuntime'
15
+
16
+ /**
17
+ * Round-5 adversarial audit — `reform`'s half of the mount-ownership /
18
+ * scheduler fix set.
19
+ *
20
+ * Under audit:
21
+ * * `runtime/featureMount.ts` `forkMountScope` / `startMount` — a mount whose
22
+ * owner scope has already closed is refused.
23
+ * * `runtime/capturedAppRuntime.ts` — every mount is forked from one parallel
24
+ * `rootScope`.
25
+ * * `internal/scheduler.ts` `isLive` — flush-time membership.
26
+ *
27
+ * Only findings that survived an attempt to refute them are filed. Each test
28
+ * states the behaviour the FIXED code is supposed to have, fails on the current
29
+ * working tree, and carries a passing CONTROL in the same body. Every handler is
30
+ * attached before anything is awaited; nothing waits a fixed duration.
31
+ */
32
+
33
+ // `until`'s poll budget, same as the round-4 hunt: 40 rounds x 10ms.
34
+ const ROUNDS = 40
35
+
36
+ // ---------------------------------------------------------------------------
37
+ // Shared scene / feature definitions
38
+ // ---------------------------------------------------------------------------
39
+
40
+ interface LifecycleProbeService {
41
+ readonly acquired: Array<string>
42
+ readonly released: Array<string>
43
+ }
44
+
45
+ const LifecycleProbeBase: Context.TagClass<
46
+ LifecycleProbe,
47
+ 'audit5.LifecycleProbe',
48
+ LifecycleProbeService
49
+ > = Context.Tag('audit5.LifecycleProbe')<LifecycleProbe, LifecycleProbeService>()
50
+ class LifecycleProbe extends LifecycleProbeBase {}
51
+
52
+ const makeProbe = (): LifecycleProbeService => ({ acquired: [], released: [] })
53
+
54
+ class AuditUi extends ui('audit5.AuditUi')<{ props: {} }>() {}
55
+ class AuditComp extends Composition.make('audit5.AuditComp', {
56
+ title: 'Audit',
57
+ ui: AuditUi,
58
+ })<AuditComp>() {}
59
+ const AuditLive = Composition.live(AuditComp, function* () {
60
+ return mount({ props: {}, slots: {} })
61
+ })
62
+
63
+ class AuditRootUi extends ui('audit5.AuditRootUi')<{ props: {} }>() {}
64
+ class AuditRootComp extends Composition.make('audit5.AuditRootComp', {
65
+ title: 'AuditRoot',
66
+ ui: AuditRootUi,
67
+ })<AuditRootComp>() {}
68
+ const AuditRootLive = Composition.live(AuditRootComp, function* () {
69
+ return mount({ props: {}, slots: {} })
70
+ })
71
+
72
+ const auditRootViews = AuditRootLive.pipe(
73
+ Layer.provideMerge(
74
+ provide(
75
+ AuditRootUi,
76
+ Ui.make(AuditRootUi, () => null),
77
+ ),
78
+ ),
79
+ )
80
+
81
+ const auditScene = (probe: LifecycleProbeService) =>
82
+ scene(AuditRootComp, {
83
+ provide: [
84
+ auditRootViews.pipe(
85
+ Layer.provideMerge(Layer.succeed(LifecycleProbe, probe)),
86
+ Layer.provideMerge(Engine),
87
+ ),
88
+ ],
89
+ })
90
+
91
+ // A scoped marker: `acquired` records that the layer built, `released` that its
92
+ // scope closed.
93
+ const scopedMark = (label: string): Layer.Layer<never, never, LifecycleProbe> =>
94
+ Layer.scopedDiscard(
95
+ Effect.gen(function* () {
96
+ const probe = yield* LifecycleProbe
97
+ yield* Effect.acquireRelease(
98
+ Effect.sync(() => probe.acquired.push(label)),
99
+ () => Effect.sync(() => probe.released.push(label)),
100
+ )
101
+ }),
102
+ )
103
+
104
+ // The same marker, but the module parks BEFORE it acquires anything: a module
105
+ // that awaits a socket, a config fetch, a lazy `import()`, a DB handshake. The
106
+ // mount is in flight and owns nothing yet.
107
+ const gatedMark = (
108
+ label: string,
109
+ gate: Deferred.Deferred<void>,
110
+ ): Layer.Layer<never, never, LifecycleProbe> =>
111
+ Layer.scopedDiscard(
112
+ Effect.gen(function* () {
113
+ const probe = yield* LifecycleProbe
114
+ yield* Deferred.await(gate)
115
+ yield* Effect.acquireRelease(
116
+ Effect.sync(() => probe.acquired.push(label)),
117
+ () => Effect.sync(() => probe.released.push(label)),
118
+ )
119
+ }),
120
+ )
121
+
122
+ const runOption = (candidate: Option.Option<() => void>): void =>
123
+ Option.match(candidate, { onNone: () => undefined, onSome: (run) => run() })
124
+
125
+ // ---------------------------------------------------------------------------
126
+ // FINDING 3 — `runtime/featureMount.ts:96-125`.
127
+ //
128
+ // `forkMountScope` refuses a mount whose owner is closed AT FORK TIME, and the
129
+ // doc comment states the contract absolutely: "A mount reported live over
130
+ // finalized services is worse than no mount, so that case is refused."
131
+ //
132
+ // The probe is a snapshot, and the thing it guards is not synchronous. The mount
133
+ // runs on `host.runFork`, which is `ManagedRuntime.runFork` — always deferred,
134
+ // because it must first await the runtime effect. Nothing on the mount path
135
+ // registers the mount fiber with the scope it was forked from, so once the fork
136
+ // has happened the owner closing NO LONGER stops the mount: the mount scope is
137
+ // closed underneath the running fiber, `Layer.build` against a closed scope
138
+ // still succeeds (`Scope.addFinalizer` on a closed scope runs the finalizer
139
+ // there and then), and `onLive` is called with a context whose finalizers have
140
+ // already run — the exact outcome the comment says is refused, reached by the
141
+ // other ordering of the same two events.
142
+ //
143
+ // The window is not a microtask: it is however long the mount takes. Any module
144
+ // that awaits anything — a lazy `import()`, a socket, a config read — holds it
145
+ // open across a whole route change or panel close.
146
+ //
147
+ // A fix that closes it: register the mount fiber with the scope it is forked
148
+ // from (`Scope.addFinalizer(scope, Fiber.interrupt(fiber))`) so closing the
149
+ // owner interrupts an in-flight mount. `forkFeatureMount` already treats
150
+ // `Cause.isInterruptedOnly` as cancellation, so that path stays silent — no
151
+ // `onLive`, no `onFailed`, and nothing acquired.
152
+ // ---------------------------------------------------------------------------
153
+
154
+ class OwnerFeature extends Feature.make('audit5.owner', {
155
+ composition: AuditComp,
156
+ module: featureModule([LifecycleProbe], Layer.merge(AuditLive, scopedMark('owner'))),
157
+ }) {}
158
+ const OwnerNative = Feature.tree<typeof OwnerFeature>(OwnerFeature.token)
159
+
160
+ class ControlChildFeature extends Feature.make('audit5.controlChild', {
161
+ composition: AuditComp,
162
+ module: featureModule([LifecycleProbe], Layer.merge(AuditLive, scopedMark('control-child'))),
163
+ }) {}
164
+ const ControlChildNative = Feature.tree<typeof ControlChildFeature>(ControlChildFeature.token)
165
+
166
+ const inFlightFeature = (gate: Deferred.Deferred<void>) =>
167
+ class InFlightChildFeature extends Feature.make('audit5.inFlightChild', {
168
+ composition: AuditComp,
169
+ module: featureModule(
170
+ [LifecycleProbe],
171
+ Layer.merge(AuditLive, gatedMark('in-flight-child', gate)),
172
+ ),
173
+ }) {}
174
+
175
+ it.scopedLive(
176
+ 'a feature mount whose owner scope closes while it is still in flight is not reported live',
177
+ () =>
178
+ Effect.gen(function* () {
179
+ const probe = makeProbe()
180
+ const app = makeAppRuntime(auditScene(probe))
181
+ yield* Effect.addFinalizer(() => Effect.sync(() => app.dispose()))
182
+
183
+ const gate = yield* Deferred.make<void>()
184
+ const InFlightChild = inFlightFeature(gate)
185
+ const InFlightChildNative = Feature.tree<typeof InFlightChild>(InFlightChild.token)
186
+
187
+ // Every handler attached before anything is awaited.
188
+ const owner = { live: 0, failed: 0 }
189
+ const control = { live: 0, failed: 0 }
190
+ const inFlight = { live: 0, failed: 0 }
191
+ const mounts = {
192
+ control: Option.none<() => void>(),
193
+ inFlight: Option.none<() => void>(),
194
+ }
195
+
196
+ const disposeOwner = app.mountFeature(OwnerNative.binding, {
197
+ onLive: (ownerRuntime) => {
198
+ owner.live += 1
199
+ mounts.control = Option.some(() => {
200
+ ownerRuntime.mountFeature(ControlChildNative.binding, {
201
+ onLive: () => {
202
+ control.live += 1
203
+ },
204
+ onFailed: () => {
205
+ control.failed += 1
206
+ },
207
+ })
208
+ })
209
+ mounts.inFlight = Option.some(() => {
210
+ ownerRuntime.mountFeature(InFlightChildNative.binding, {
211
+ onLive: () => {
212
+ inFlight.live += 1
213
+ },
214
+ onFailed: () => {
215
+ inFlight.failed += 1
216
+ },
217
+ })
218
+ })
219
+ },
220
+ onFailed: () => {
221
+ owner.failed += 1
222
+ },
223
+ })
224
+
225
+ yield* until(
226
+ () => owner.live + owner.failed,
227
+ (n) => n >= 1,
228
+ ROUNDS,
229
+ )
230
+ expect(owner).toEqual({ live: 1, failed: 0 })
231
+
232
+ // ---- CONTROL — the same handle, the same mount path, a module that does
233
+ // not park. It goes live, acquires, and holds its resource. This proves the
234
+ // scene, the probe, the child-mount path and the handle all work.
235
+ runOption(mounts.control)
236
+ yield* until(
237
+ () => control.live + control.failed,
238
+ (n) => n >= 1,
239
+ ROUNDS,
240
+ )
241
+ expect(control).toEqual({ live: 1, failed: 0 })
242
+ expect(probe.acquired).toContain('control-child')
243
+ expect(probe.released).toEqual([])
244
+
245
+ // ---- THE DEFECT — start a mount whose module parks before it acquires
246
+ // anything, so the mount is genuinely in flight and owns nothing yet...
247
+ runOption(mounts.inFlight)
248
+ yield* flush()
249
+ expect(probe.acquired).not.toContain('in-flight-child')
250
+
251
+ // ...then close the owner. Its scope, and every mount scope forked from it,
252
+ // is now closed — including the in-flight one.
253
+ disposeOwner()
254
+ yield* until(
255
+ () => probe.released.length,
256
+ (n) => n >= 2,
257
+ ROUNDS,
258
+ )
259
+ yield* flush()
260
+ expect(probe.released.slice().sort()).toEqual(['control-child', 'owner'])
261
+
262
+ // The module finishes what it was waiting for, after its owner is gone.
263
+ yield* Deferred.succeed(gate, undefined)
264
+ yield* until(
265
+ () => inFlight.live + inFlight.failed + probe.acquired.length,
266
+ (n) => n >= 3,
267
+ ROUNDS,
268
+ )
269
+ yield* flush()
270
+
271
+ // A mount the runtime can no longer own must not acquire resources it
272
+ // releases in the same breath...
273
+ // Actual today: 'in-flight-child' appears in BOTH lists — acquired against
274
+ // an already-closed scope, so its finalizer ran at the instant it was added.
275
+ expect(probe.acquired).not.toContain('in-flight-child')
276
+ // ...and must not be reported live over them.
277
+ // Actual today: 1.
278
+ expect(inFlight.live).toBe(0)
279
+ }),
280
+ )
281
+
282
+ // ---------------------------------------------------------------------------
283
+ // FINDING 4 — `internal/scheduler.ts:36-37`.
284
+ //
285
+ // const isLive = (queued, listener) =>
286
+ // Predicate.isSet(queued.source) ? queued.source.has(listener)
287
+ // : queued.members.includes(listener)
288
+ //
289
+ // Round-3 established the invariant the code still states one comment further
290
+ // down: "a listener that has since unsubscribed must not be woken". Round-4
291
+ // found that re-reading a ONE-SHOT iterable drops every wake, and round-5 fixed
292
+ // that by keeping a schedule-time snapshot. But the new answer for "anything
293
+ // that is not a Set" is the snapshot for ALL non-Set sources, including the ones
294
+ // that are perfectly re-readable — an `Array`, or any custom collection a
295
+ // first-party Source keeps its subscribers in.
296
+ //
297
+ // `Scheduler` is exported from `@playfast/reform/internal` as part of the
298
+ // primitive-authoring toolkit and its parameter is typed `Iterable<() => void>`,
299
+ // so an Array-backed subscriber list is a supported caller. For that caller the
300
+ // round-3 invariant silently no longer holds: an earlier listener in the same
301
+ // flush can tear a later one down — a scope closing, a family key forgotten, a
302
+ // composition unmounting — and the torn-down listener is woken anyway, reading a
303
+ // store whose finalizers have run.
304
+ //
305
+ // The distinction the code needs is re-readable vs one-shot, not Set vs
306
+ // everything else: `Predicate.isSet(source) || Array.isArray(source)` re-reads,
307
+ // everything else falls back to the snapshot.
308
+ //
309
+ // No in-repo caller passes a mutable non-Set today — `store.ts`, `pendingQueue`,
310
+ // `stateFamily` and `reform-profiler/recorder` all pass Sets, and `boundary`,
311
+ // `event` and `remoteState` pass single-element array LITERALS, which are never
312
+ // mutated and so behave identically either way. This is a latent SPI regression,
313
+ // filed at that severity.
314
+ // ---------------------------------------------------------------------------
315
+
316
+ const drainMicrotasks = async (turns = 5): Promise<void> => {
317
+ await Array.from({ length: turns }).reduce<Promise<void>>(
318
+ (chain) => chain.then(() => new Promise<void>((resolve) => queueMicrotask(resolve))),
319
+ Promise.resolve(),
320
+ )
321
+ }
322
+
323
+ test('scheduler: a listener dropped from a re-readable source mid-flush is not woken', async () => {
324
+ // ---- CONTROL — a Set source. `first` unsubscribes `second` while the flush is
325
+ // running and `second` is correctly dropped. Same scheduler, same two
326
+ // listeners, same mutation: only the container type differs.
327
+ const controlScheduler = makeScheduler()
328
+ const controlWoken: Array<string> = []
329
+ const controlSource = new Set<() => void>()
330
+ const controlSecond = (): void => {
331
+ controlWoken.push('second')
332
+ }
333
+ const controlFirst = (): void => {
334
+ controlWoken.push('first')
335
+ controlSource.delete(controlSecond)
336
+ }
337
+ controlSource.add(controlFirst)
338
+ controlSource.add(controlSecond)
339
+
340
+ // ---- DEFECT — an Array source, re-readable, mutated exactly the same way by
341
+ // exactly the same listener.
342
+ const scheduler = makeScheduler()
343
+ const woken: Array<string> = []
344
+ const source: Array<() => void> = []
345
+ const second = (): void => {
346
+ woken.push('second')
347
+ }
348
+ const first = (): void => {
349
+ woken.push('first')
350
+ source.splice(source.indexOf(second), 1)
351
+ }
352
+ source.push(first, second)
353
+
354
+ controlScheduler.schedule(controlSource)
355
+ scheduler.schedule(source)
356
+ await drainMicrotasks()
357
+
358
+ expect(controlWoken).toEqual(['first'])
359
+ // Actual today: ['first', 'second'] — the unsubscribed listener is woken.
360
+ expect(woken).toEqual(['first'])
361
+ })