@playfast/reform 1.2.0 → 1.3.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 (383) hide show
  1. package/dist/boundary/boundary.d.ts +64 -0
  2. package/dist/boundary/boundary.d.ts.map +1 -0
  3. package/dist/boundary/boundary.js +102 -0
  4. package/dist/boundary/boundary.js.map +1 -0
  5. package/dist/calc/asyncCalc.d.ts +21 -0
  6. package/dist/calc/asyncCalc.d.ts.map +1 -0
  7. package/dist/calc/asyncCalc.js +90 -0
  8. package/dist/calc/asyncCalc.js.map +1 -0
  9. package/dist/calc/asyncCalcTypes.d.ts +78 -0
  10. package/dist/calc/asyncCalcTypes.d.ts.map +1 -0
  11. package/dist/calc/asyncCalcTypes.js +2 -0
  12. package/dist/calc/asyncCalcTypes.js.map +1 -0
  13. package/dist/calc/asyncData.d.ts +29 -0
  14. package/dist/calc/asyncData.d.ts.map +1 -0
  15. package/dist/calc/asyncData.js +21 -0
  16. package/dist/calc/asyncData.js.map +1 -0
  17. package/dist/calc/calc.d.ts +39 -0
  18. package/dist/calc/calc.d.ts.map +1 -0
  19. package/dist/calc/calc.js +48 -0
  20. package/dist/calc/calc.js.map +1 -0
  21. package/dist/calc/calcFamily.d.ts +40 -0
  22. package/dist/calc/calcFamily.d.ts.map +1 -0
  23. package/dist/calc/calcFamily.js +113 -0
  24. package/dist/calc/calcFamily.js.map +1 -0
  25. package/dist/calc/compose.d.ts +5 -0
  26. package/dist/calc/compose.d.ts.map +1 -0
  27. package/dist/calc/compose.js +5 -0
  28. package/dist/calc/compose.js.map +1 -0
  29. package/dist/calc/queryState.d.ts +12 -0
  30. package/dist/calc/queryState.d.ts.map +1 -0
  31. package/dist/calc/queryState.js +17 -0
  32. package/dist/calc/queryState.js.map +1 -0
  33. package/dist/channel/channel.d.ts +51 -0
  34. package/dist/channel/channel.d.ts.map +1 -0
  35. package/dist/channel/channel.js +86 -0
  36. package/dist/channel/channel.js.map +1 -0
  37. package/dist/channel/procedures.d.ts +24 -0
  38. package/dist/channel/procedures.d.ts.map +1 -0
  39. package/dist/channel/procedures.js +82 -0
  40. package/dist/channel/procedures.js.map +1 -0
  41. package/dist/compose/composition.d.ts +12 -0
  42. package/dist/compose/composition.d.ts.map +1 -0
  43. package/dist/compose/composition.js +56 -0
  44. package/dist/compose/composition.js.map +1 -0
  45. package/dist/compose/compositionTypes.d.ts +127 -0
  46. package/dist/compose/compositionTypes.d.ts.map +1 -0
  47. package/dist/compose/compositionTypes.js +2 -0
  48. package/dist/compose/compositionTypes.js.map +1 -0
  49. package/dist/compose/host.d.ts +6 -0
  50. package/dist/compose/host.d.ts.map +1 -0
  51. package/dist/compose/host.js +2 -0
  52. package/dist/compose/host.js.map +1 -0
  53. package/dist/compose/props.d.ts +10 -0
  54. package/dist/compose/props.d.ts.map +1 -0
  55. package/dist/compose/props.js +6 -0
  56. package/dist/compose/props.js.map +1 -0
  57. package/dist/compose/provide.d.ts +18 -0
  58. package/dist/compose/provide.d.ts.map +1 -0
  59. package/dist/compose/provide.js +49 -0
  60. package/dist/compose/provide.js.map +1 -0
  61. package/dist/compose/slot.d.ts +70 -0
  62. package/dist/compose/slot.d.ts.map +1 -0
  63. package/dist/compose/slot.js +26 -0
  64. package/dist/compose/slot.js.map +1 -0
  65. package/dist/compose/structure.d.ts +51 -0
  66. package/dist/compose/structure.d.ts.map +1 -0
  67. package/dist/compose/structure.js +92 -0
  68. package/dist/compose/structure.js.map +1 -0
  69. package/dist/compose/ui.d.ts +112 -0
  70. package/dist/compose/ui.d.ts.map +1 -0
  71. package/dist/compose/ui.js +47 -0
  72. package/dist/compose/ui.js.map +1 -0
  73. package/dist/definition/definition.d.ts +11 -0
  74. package/dist/definition/definition.d.ts.map +1 -0
  75. package/dist/definition/definition.js +35 -0
  76. package/dist/definition/definition.js.map +1 -0
  77. package/dist/event/event.d.ts +52 -0
  78. package/dist/event/event.d.ts.map +1 -0
  79. package/dist/event/event.js +67 -0
  80. package/dist/event/event.js.map +1 -0
  81. package/dist/event/eventGroup.d.ts +8 -0
  82. package/dist/event/eventGroup.d.ts.map +1 -0
  83. package/dist/event/eventGroup.js +3 -0
  84. package/dist/event/eventGroup.js.map +1 -0
  85. package/dist/feature/feature.d.ts +27 -0
  86. package/dist/feature/feature.d.ts.map +1 -0
  87. package/dist/feature/feature.js +20 -0
  88. package/dist/feature/feature.js.map +1 -0
  89. package/dist/feature/featureBinding.d.ts +85 -0
  90. package/dist/feature/featureBinding.d.ts.map +1 -0
  91. package/dist/feature/featureBinding.js +10 -0
  92. package/dist/feature/featureBinding.js.map +1 -0
  93. package/dist/feature/featureClass.d.ts +10 -0
  94. package/dist/feature/featureClass.d.ts.map +1 -0
  95. package/dist/feature/featureClass.js +2 -0
  96. package/dist/feature/featureClass.js.map +1 -0
  97. package/dist/feature/featureConfig.d.ts +86 -0
  98. package/dist/feature/featureConfig.d.ts.map +1 -0
  99. package/dist/feature/featureConfig.js +13 -0
  100. package/dist/feature/featureConfig.js.map +1 -0
  101. package/dist/feature/featureFactory.d.ts +15 -0
  102. package/dist/feature/featureFactory.d.ts.map +1 -0
  103. package/dist/feature/featureFactory.js +59 -0
  104. package/dist/feature/featureFactory.js.map +1 -0
  105. package/dist/feature/featureModule.d.ts +28 -0
  106. package/dist/feature/featureModule.d.ts.map +1 -0
  107. package/dist/feature/featureModule.js +33 -0
  108. package/dist/feature/featureModule.js.map +1 -0
  109. package/dist/feature/featureNativeTree.d.ts +26 -0
  110. package/dist/feature/featureNativeTree.d.ts.map +1 -0
  111. package/dist/feature/featureNativeTree.js +2 -0
  112. package/dist/feature/featureNativeTree.js.map +1 -0
  113. package/dist/feature/featureRequirement.d.ts +43 -0
  114. package/dist/feature/featureRequirement.d.ts.map +1 -0
  115. package/dist/feature/featureRequirement.js +6 -0
  116. package/dist/feature/featureRequirement.js.map +1 -0
  117. package/dist/feature/featureVariants.d.ts +9 -0
  118. package/dist/feature/featureVariants.d.ts.map +1 -0
  119. package/dist/feature/featureVariants.js +59 -0
  120. package/dist/feature/featureVariants.js.map +1 -0
  121. package/dist/feature/mountFeature.d.ts +10 -0
  122. package/dist/feature/mountFeature.d.ts.map +1 -0
  123. package/dist/feature/mountFeature.js +14 -0
  124. package/dist/feature/mountFeature.js.map +1 -0
  125. package/dist/graph/closure.d.ts +77 -0
  126. package/dist/graph/closure.d.ts.map +1 -0
  127. package/dist/graph/closure.js +4 -0
  128. package/dist/graph/closure.js.map +1 -0
  129. package/dist/graph/services.d.ts +30 -0
  130. package/dist/graph/services.d.ts.map +1 -0
  131. package/dist/graph/services.js +2 -0
  132. package/dist/graph/services.js.map +1 -0
  133. package/dist/graph/summary.d.ts +60 -0
  134. package/dist/graph/summary.d.ts.map +1 -0
  135. package/dist/graph/summary.js +7 -0
  136. package/dist/graph/summary.js.map +1 -0
  137. package/dist/index.d.ts +51 -0
  138. package/dist/index.d.ts.map +1 -0
  139. package/dist/index.js +34 -0
  140. package/dist/index.js.map +1 -0
  141. package/dist/internal/bucketCache.d.ts +19 -0
  142. package/dist/internal/bucketCache.d.ts.map +1 -0
  143. package/dist/internal/bucketCache.js +20 -0
  144. package/dist/internal/bucketCache.js.map +1 -0
  145. package/dist/internal/capture.d.ts +17 -0
  146. package/dist/internal/capture.d.ts.map +1 -0
  147. package/dist/internal/capture.js +5 -0
  148. package/dist/internal/capture.js.map +1 -0
  149. package/dist/internal/env.d.ts +2 -0
  150. package/dist/internal/env.d.ts.map +1 -0
  151. package/dist/internal/env.js +5 -0
  152. package/dist/internal/env.js.map +1 -0
  153. package/dist/internal/errors.d.ts +48 -0
  154. package/dist/internal/errors.d.ts.map +1 -0
  155. package/dist/internal/errors.js +69 -0
  156. package/dist/internal/errors.js.map +1 -0
  157. package/dist/internal/inspect.d.ts +5 -0
  158. package/dist/internal/inspect.d.ts.map +1 -0
  159. package/dist/internal/inspect.js +20 -0
  160. package/dist/internal/inspect.js.map +1 -0
  161. package/dist/internal/queryDriver.d.ts +7 -0
  162. package/dist/internal/queryDriver.d.ts.map +1 -0
  163. package/dist/internal/queryDriver.js +198 -0
  164. package/dist/internal/queryDriver.js.map +1 -0
  165. package/dist/internal/queryDriverHydrate.d.ts +17 -0
  166. package/dist/internal/queryDriverHydrate.d.ts.map +1 -0
  167. package/dist/internal/queryDriverHydrate.js +19 -0
  168. package/dist/internal/queryDriverHydrate.js.map +1 -0
  169. package/dist/internal/queryDriverStore.d.ts +54 -0
  170. package/dist/internal/queryDriverStore.d.ts.map +1 -0
  171. package/dist/internal/queryDriverStore.js +77 -0
  172. package/dist/internal/queryDriverStore.js.map +1 -0
  173. package/dist/internal/queryDriverTypes.d.ts +41 -0
  174. package/dist/internal/queryDriverTypes.d.ts.map +1 -0
  175. package/dist/internal/queryDriverTypes.js +9 -0
  176. package/dist/internal/queryDriverTypes.js.map +1 -0
  177. package/dist/internal/queryEvents.d.ts +12 -0
  178. package/dist/internal/queryEvents.d.ts.map +1 -0
  179. package/dist/internal/queryEvents.js +11 -0
  180. package/dist/internal/queryEvents.js.map +1 -0
  181. package/dist/internal/queryStore.d.ts +13 -0
  182. package/dist/internal/queryStore.d.ts.map +1 -0
  183. package/dist/internal/queryStore.js +11 -0
  184. package/dist/internal/queryStore.js.map +1 -0
  185. package/dist/internal/reuse.d.ts +2 -0
  186. package/dist/internal/reuse.d.ts.map +1 -0
  187. package/dist/internal/reuse.js +55 -0
  188. package/dist/internal/reuse.js.map +1 -0
  189. package/dist/internal/scheduler.d.ts +13 -0
  190. package/dist/internal/scheduler.d.ts.map +1 -0
  191. package/dist/internal/scheduler.js +38 -0
  192. package/dist/internal/scheduler.js.map +1 -0
  193. package/dist/internal/seeds.d.ts +3 -0
  194. package/dist/internal/seeds.d.ts.map +1 -0
  195. package/dist/internal/seeds.js +3 -0
  196. package/dist/internal/seeds.js.map +1 -0
  197. package/dist/internal/sources.d.ts +20 -0
  198. package/dist/internal/sources.d.ts.map +1 -0
  199. package/dist/internal/sources.js +39 -0
  200. package/dist/internal/sources.js.map +1 -0
  201. package/dist/internal/stateRegistry.d.ts +2 -0
  202. package/dist/internal/stateRegistry.d.ts.map +1 -0
  203. package/dist/internal/stateRegistry.js +15 -0
  204. package/dist/internal/stateRegistry.js.map +1 -0
  205. package/dist/internal/store.d.ts +15 -0
  206. package/dist/internal/store.d.ts.map +1 -0
  207. package/dist/internal/store.js +61 -0
  208. package/dist/internal/store.js.map +1 -0
  209. package/dist/internal/track.d.ts +15 -0
  210. package/dist/internal/track.d.ts.map +1 -0
  211. package/dist/internal/track.js +11 -0
  212. package/dist/internal/track.js.map +1 -0
  213. package/dist/internal/variance.d.ts +4 -0
  214. package/dist/internal/variance.d.ts.map +1 -0
  215. package/dist/internal/variance.js +2 -0
  216. package/dist/internal/variance.js.map +1 -0
  217. package/dist/internal.d.ts +42 -0
  218. package/dist/internal.d.ts.map +1 -0
  219. package/dist/internal.js +58 -0
  220. package/dist/internal.js.map +1 -0
  221. package/dist/namespace/namespace.d.ts +24 -0
  222. package/dist/namespace/namespace.d.ts.map +1 -0
  223. package/dist/namespace/namespace.js +20 -0
  224. package/dist/namespace/namespace.js.map +1 -0
  225. package/dist/procedure/procedure.d.ts +32 -0
  226. package/dist/procedure/procedure.d.ts.map +1 -0
  227. package/dist/procedure/procedure.js +34 -0
  228. package/dist/procedure/procedure.js.map +1 -0
  229. package/dist/reducer/reducer.d.ts +35 -0
  230. package/dist/reducer/reducer.d.ts.map +1 -0
  231. package/dist/reducer/reducer.js +73 -0
  232. package/dist/reducer/reducer.js.map +1 -0
  233. package/dist/remote/pendingQueue.d.ts +20 -0
  234. package/dist/remote/pendingQueue.d.ts.map +1 -0
  235. package/dist/remote/pendingQueue.js +113 -0
  236. package/dist/remote/pendingQueue.js.map +1 -0
  237. package/dist/remote/remoteState.d.ts +13 -0
  238. package/dist/remote/remoteState.d.ts.map +1 -0
  239. package/dist/remote/remoteState.js +176 -0
  240. package/dist/remote/remoteState.js.map +1 -0
  241. package/dist/remote/remoteStateMake.d.ts +8 -0
  242. package/dist/remote/remoteStateMake.d.ts.map +1 -0
  243. package/dist/remote/remoteStateMake.js +46 -0
  244. package/dist/remote/remoteStateMake.js.map +1 -0
  245. package/dist/remote/remoteStateSend.d.ts +41 -0
  246. package/dist/remote/remoteStateSend.d.ts.map +1 -0
  247. package/dist/remote/remoteStateSend.js +67 -0
  248. package/dist/remote/remoteStateSend.js.map +1 -0
  249. package/dist/remote/remoteStateTypes.d.ts +103 -0
  250. package/dist/remote/remoteStateTypes.d.ts.map +1 -0
  251. package/dist/remote/remoteStateTypes.js +4 -0
  252. package/dist/remote/remoteStateTypes.js.map +1 -0
  253. package/dist/runtime/appRuntime.d.ts +10 -0
  254. package/dist/runtime/appRuntime.d.ts.map +1 -0
  255. package/dist/runtime/appRuntime.js +6 -0
  256. package/dist/runtime/appRuntime.js.map +1 -0
  257. package/dist/runtime/bus.d.ts +17 -0
  258. package/dist/runtime/bus.d.ts.map +1 -0
  259. package/dist/runtime/bus.js +12 -0
  260. package/dist/runtime/bus.js.map +1 -0
  261. package/dist/runtime/capturedAppRuntime.d.ts +7 -0
  262. package/dist/runtime/capturedAppRuntime.d.ts.map +1 -0
  263. package/dist/runtime/capturedAppRuntime.js +151 -0
  264. package/dist/runtime/capturedAppRuntime.js.map +1 -0
  265. package/dist/runtime/eventBudget.d.ts +32 -0
  266. package/dist/runtime/eventBudget.d.ts.map +1 -0
  267. package/dist/runtime/eventBudget.js +67 -0
  268. package/dist/runtime/eventBudget.js.map +1 -0
  269. package/dist/runtime/featureMount.d.ts +16 -0
  270. package/dist/runtime/featureMount.d.ts.map +1 -0
  271. package/dist/runtime/featureMount.js +54 -0
  272. package/dist/runtime/featureMount.js.map +1 -0
  273. package/dist/runtime/instrumentation.d.ts +18 -0
  274. package/dist/runtime/instrumentation.d.ts.map +1 -0
  275. package/dist/runtime/instrumentation.js +16 -0
  276. package/dist/runtime/instrumentation.js.map +1 -0
  277. package/dist/runtime/loop.d.ts +32 -0
  278. package/dist/runtime/loop.d.ts.map +1 -0
  279. package/dist/runtime/loop.js +128 -0
  280. package/dist/runtime/loop.js.map +1 -0
  281. package/dist/runtime/queries.d.ts +20 -0
  282. package/dist/runtime/queries.d.ts.map +1 -0
  283. package/dist/runtime/queries.js +21 -0
  284. package/dist/runtime/queries.js.map +1 -0
  285. package/dist/runtime/runtimeHandle.d.ts +56 -0
  286. package/dist/runtime/runtimeHandle.d.ts.map +1 -0
  287. package/dist/runtime/runtimeHandle.js +2 -0
  288. package/dist/runtime/runtimeHandle.js.map +1 -0
  289. package/dist/scene/scene.d.ts +55 -0
  290. package/dist/scene/scene.d.ts.map +1 -0
  291. package/dist/scene/scene.js +74 -0
  292. package/dist/scene/scene.js.map +1 -0
  293. package/dist/state/state.d.ts +54 -0
  294. package/dist/state/state.d.ts.map +1 -0
  295. package/dist/state/state.js +41 -0
  296. package/dist/state/state.js.map +1 -0
  297. package/dist/state/stateFamily.d.ts +59 -0
  298. package/dist/state/stateFamily.d.ts.map +1 -0
  299. package/dist/state/stateFamily.js +91 -0
  300. package/dist/state/stateFamily.js.map +1 -0
  301. package/dist/state/stateGroup.d.ts +36 -0
  302. package/dist/state/stateGroup.d.ts.map +1 -0
  303. package/dist/state/stateGroup.js +33 -0
  304. package/dist/state/stateGroup.js.map +1 -0
  305. package/dist/state/token.d.ts +35 -0
  306. package/dist/state/token.d.ts.map +1 -0
  307. package/dist/state/token.js +18 -0
  308. package/dist/state/token.js.map +1 -0
  309. package/dist/synced/syncedStore.d.ts +44 -0
  310. package/dist/synced/syncedStore.d.ts.map +1 -0
  311. package/dist/synced/syncedStore.js +32 -0
  312. package/dist/synced/syncedStore.js.map +1 -0
  313. package/dist/testkit/flight.testkit.d.ts +18 -0
  314. package/dist/testkit/flight.testkit.d.ts.map +1 -0
  315. package/dist/testkit/flight.testkit.js +45 -0
  316. package/dist/testkit/flight.testkit.js.map +1 -0
  317. package/dist/ui/node.d.ts +3 -0
  318. package/dist/ui/node.d.ts.map +1 -0
  319. package/dist/ui/node.js +2 -0
  320. package/dist/ui/node.js.map +1 -0
  321. package/dist/ui/trigger.d.ts +2 -0
  322. package/dist/ui/trigger.d.ts.map +1 -0
  323. package/dist/ui/trigger.js +2 -0
  324. package/dist/ui/trigger.js.map +1 -0
  325. package/dist/wire/tree.d.ts +32 -0
  326. package/dist/wire/tree.d.ts.map +1 -0
  327. package/dist/wire/tree.js +59 -0
  328. package/dist/wire/tree.js.map +1 -0
  329. package/dist/wire/triggers.d.ts +25 -0
  330. package/dist/wire/triggers.d.ts.map +1 -0
  331. package/dist/wire/triggers.js +31 -0
  332. package/dist/wire/triggers.js.map +1 -0
  333. package/package.json +17 -5
  334. package/src/boundary/boundary.test.ts +26 -23
  335. package/src/calc/asyncCalc.invalidate.test.ts +258 -55
  336. package/src/calc/asyncCalc.test.ts +225 -85
  337. package/src/calc/asyncCalc.ts +27 -141
  338. package/src/calc/asyncCalcTypes.test.ts +64 -0
  339. package/src/calc/asyncCalcTypes.ts +139 -0
  340. package/src/calc/calc.test.ts +10 -11
  341. package/src/calc/calcFamily.test.ts +12 -12
  342. package/src/channel/channel.ts +8 -106
  343. package/src/channel/procedures.ts +108 -0
  344. package/src/compose/composition.ts +49 -246
  345. package/src/compose/compositionTypes.ts +249 -0
  346. package/src/event/event.fromSource.test.ts +12 -12
  347. package/src/feature/feature.ts +50 -1210
  348. package/src/feature/feature.typecheck.ts +2 -61
  349. package/src/feature/featureBinding.ts +158 -0
  350. package/src/feature/featureClass.ts +162 -0
  351. package/src/feature/featureConfig.ts +192 -0
  352. package/src/feature/featureFactory.ts +163 -0
  353. package/src/feature/featureModule.ts +136 -0
  354. package/src/feature/featureModule.typecheck.ts +63 -0
  355. package/src/feature/featureNativeTree.ts +152 -0
  356. package/src/feature/featureRequirement.ts +82 -0
  357. package/src/feature/featureVariants.ts +234 -0
  358. package/src/feature/mountFeature.ts +54 -0
  359. package/src/graph/closure.ts +45 -226
  360. package/src/graph/services.ts +95 -0
  361. package/src/graph/summary.ts +134 -0
  362. package/src/internal/queryDriver.ts +20 -89
  363. package/src/internal/queryDriverHydrate.ts +45 -0
  364. package/src/internal/queryDriverTypes.ts +8 -10
  365. package/src/internal/store.test.ts +27 -6
  366. package/src/remote/pendingQueue.ts +145 -0
  367. package/src/remote/remoteState.test.ts +28 -22
  368. package/src/remote/remoteState.ts +63 -489
  369. package/src/remote/remoteStateMake.ts +97 -0
  370. package/src/remote/remoteStateSend.ts +122 -0
  371. package/src/remote/remoteStateTypes.ts +155 -0
  372. package/src/runtime/appRuntime.activation.test.ts +9 -5
  373. package/src/runtime/appRuntime.test.ts +40 -16
  374. package/src/runtime/appRuntime.ts +17 -472
  375. package/src/runtime/capturedAppRuntime.ts +274 -0
  376. package/src/runtime/eventBudget.test.ts +38 -8
  377. package/src/runtime/featureMount.ts +94 -0
  378. package/src/runtime/hardening.test.ts +18 -4
  379. package/src/runtime/instrumentation.test.ts +49 -12
  380. package/src/runtime/loop.test.ts +51 -10
  381. package/src/runtime/runtimeHandle.ts +124 -0
  382. package/src/state/stateFamily.test.ts +25 -8
  383. package/src/testkit/flight.testkit.ts +9 -0
@@ -1,6 +1,7 @@
1
1
  import { expect, it } from '@effect/vitest'
2
2
  import { Duration, Effect, Layer, Schema as S, TestClock } from 'effect'
3
3
  import { Channel, Engine, Event, Procedure, publish, Reducer, State } from '../index'
4
+ import { makeFlightGate, until } from '../testkit/flight.testkit'
4
5
 
5
6
  it.live('a frame of events drains in one pass with a single notification flush', () => {
6
7
  class Counter extends State.make('counter', S.Number) {}
@@ -22,7 +23,11 @@ it.live('a frame of events drains in one pass with a single notification flush',
22
23
  yield* publish('Normal', Event.construct(Bumped, { to: 1 }))
23
24
  yield* publish('Normal', Event.construct(Bumped, { to: 2 }))
24
25
  yield* publish('Normal', Event.construct(Bumped, { to: 3 }))
25
- yield* Effect.sleep(Duration.millis(20))
26
+ // The flush is the frame's last act, so one notification means the fold is done.
27
+ yield* until(
28
+ () => notifications.n,
29
+ (n) => n >= 1,
30
+ )
26
31
 
27
32
  expect(store.get()).toBe(3)
28
33
  expect(notifications.n).toBe(1)
@@ -34,11 +39,12 @@ it.live('an exclusive channel serializes procedures that share it', () => {
34
39
  class StartB extends Event.make('StartB', S.Struct({})) {}
35
40
  class Exclusive extends Channel.make('Exclusive', { policy: { _tag: 'exclusive' } }) {}
36
41
 
42
+ const gate = makeFlightGate()
37
43
  const concurrency = { active: 0, max: 0 }
38
44
  const guarded = function* () {
39
45
  concurrency.active += 1
40
46
  concurrency.max = Math.max(concurrency.max, concurrency.active)
41
- yield* Effect.sleep(Duration.millis(15))
47
+ yield* gate.through(Effect.void)
42
48
  concurrency.active -= 1
43
49
  }
44
50
 
@@ -52,9 +58,20 @@ it.live('an exclusive channel serializes procedures that share it', () => {
52
58
  )
53
59
 
54
60
  return Effect.gen(function* () {
61
+ // Parking the first run IS the overlap: a shared lane that is not exclusive
62
+ // would start the second procedure while the first sits at the gate.
63
+ yield* gate.hold
55
64
  yield* publish('Normal', Event.construct(StartA, {}))
56
65
  yield* publish('Normal', Event.construct(StartB, {}))
57
- yield* Effect.sleep(Duration.millis(60))
66
+ yield* gate.awaitEntry(1)
67
+ expect(gate.entered()).toBe(1)
68
+
69
+ yield* gate.release
70
+ yield* gate.awaitEntry(2)
71
+ yield* until(
72
+ () => concurrency.active,
73
+ (active) => active === 0,
74
+ )
58
75
  expect(concurrency.max).toBe(1)
59
76
  }).pipe(Effect.provide(TestLayer))
60
77
  })
@@ -79,7 +96,11 @@ it.live('two procedures sharing a channel each run once for one shared event', (
79
96
 
80
97
  return Effect.gen(function* () {
81
98
  yield* publish('High', Event.construct(Shared, {}))
82
- yield* Effect.sleep(Duration.millis(20))
99
+ yield* until(
100
+ () => runs.a + runs.b,
101
+ (total) => total >= 2,
102
+ )
103
+ // A duplicated offer would land in the same frame and double one of them.
83
104
  expect(runs.a).toBe(1)
84
105
  expect(runs.b).toBe(1)
85
106
  }).pipe(Effect.provide(TestLayer))
@@ -126,10 +147,12 @@ it.live('a reducer ignores events outside its declared set (no-op)', () => {
126
147
  return Effect.gen(function* () {
127
148
  const store = yield* Counter.store
128
149
  yield* publish('Normal', Event.construct(Unrelated, {}))
129
- yield* Effect.sleep(Duration.millis(10))
130
- expect(store.get()).toBe(0)
131
150
  yield* publish('Normal', Event.construct(Bump, {}))
132
- yield* Effect.sleep(Duration.millis(10))
151
+ yield* until(
152
+ () => store.get(),
153
+ (n) => n > 0,
154
+ )
155
+ // Both events fold in one frame: a reducer that also took Unrelated reads 2.
133
156
  expect(store.get()).toBe(1)
134
157
  }).pipe(Effect.provide(TestLayer))
135
158
  })
@@ -166,6 +189,7 @@ it.live('a fold returning the previous reference keeps the exact store value', (
166
189
  S.TaggedStruct('Open', { revision: S.Number }),
167
190
  )
168
191
  class ViewerState extends State.make('viewer', Viewer) {}
192
+ class Marker extends State.make('marker', S.Number) {}
169
193
  class Poked extends Event.make('Poked', S.Struct({})) {}
170
194
  class PokeReducer extends Reducer.make('PokeReducer', {
171
195
  states: [ViewerState],
@@ -175,20 +199,37 @@ it.live('a fold returning the previous reference keeps the exact store value', (
175
199
  const PokeReducerLive = Reducer.live(PokeReducer, (viewer) =>
176
200
  viewer._tag === 'Open' ? { ...viewer, revision: viewer.revision + 1 } : viewer,
177
201
  )
202
+ class MarkReducer extends Reducer.make('MarkReducer', {
203
+ states: [Marker],
204
+ events: [Poked],
205
+ }) {}
206
+ const MarkReducerLive = Reducer.live(MarkReducer, (n) => n + 1)
178
207
 
179
- const TestLayer = PokeReducerLive.pipe(
180
- Layer.provideMerge(Layer.mergeAll(State.live(ViewerState, { _tag: 'Closed' }), Engine)),
208
+ const TestLayer = Layer.mergeAll(PokeReducerLive, MarkReducerLive).pipe(
209
+ Layer.provideMerge(
210
+ Layer.mergeAll(State.live(ViewerState, { _tag: 'Closed' }), State.live(Marker, 0), Engine),
211
+ ),
181
212
  )
182
213
 
183
214
  return Effect.gen(function* () {
184
215
  const store = yield* ViewerState.store
216
+ const marker = yield* Marker.store
185
217
  const before = store.get()
186
218
  const notifications = { n: 0 }
219
+ const flushed = { n: 0 }
187
220
  store.subscribe(() => {
188
221
  notifications.n += 1
189
222
  })
223
+ marker.subscribe(() => {
224
+ flushed.n += 1
225
+ })
190
226
  yield* publish('Normal', Event.construct(Poked, {}))
191
- yield* Effect.sleep(Duration.millis(20))
227
+ // The marker folds on the same event and shares the scheduler's flush: once
228
+ // its listener fires, a viewer notification would have run in that batch too.
229
+ yield* until(
230
+ () => flushed.n,
231
+ (n) => n >= 1,
232
+ )
192
233
  // Reference identity is the repaint signal: an unchanged fold must not be
193
234
  // re-decoded into a fresh, structurally-equal value that wakes subscribers.
194
235
  expect(store.get()).toBe(before)
@@ -0,0 +1,124 @@
1
+ import type { Context, Effect, Option } from 'effect'
2
+ import type {
3
+ CompositionActivation,
4
+ CompositionId,
5
+ RequiredSlotIdentities,
6
+ } from '../compose/composition'
7
+ import type { UiContract } from '../compose/ui'
8
+ import type {
9
+ AnyFeatureBinding,
10
+ CapturedFeatureBinding,
11
+ FeatureBinding,
12
+ FeatureRequirement,
13
+ } from '../feature/feature'
14
+ import type {
15
+ AnyFeatureGraphSummary,
16
+ FeatureSummaryCompositionIdentity,
17
+ FeatureSummaryContract,
18
+ FeatureSummaryInput,
19
+ FeatureSummaryMountProps,
20
+ FeatureSummaryName,
21
+ FeatureSummaryOpenServices,
22
+ FeatureSummaryUsesInput,
23
+ } from '../graph/closure'
24
+ import type { FeatureLoadFailed } from '../internal/errors'
25
+ import type { Node } from '../ui/node'
26
+ import type { Priority, Tagged } from './bus'
27
+ import type { Instrumentation } from './instrumentation'
28
+ import type { EngineServices } from './loop'
29
+
30
+ export const UnspecifiedRuntimeServicesTypeId: unique symbol = Symbol.for(
31
+ 'reform/UnspecifiedRuntimeServices',
32
+ )
33
+ export type UnspecifiedRuntimeServicesTypeId = typeof UnspecifiedRuntimeServicesTypeId
34
+ export interface UnspecifiedRuntimeServices {
35
+ readonly [UnspecifiedRuntimeServicesTypeId]: UnspecifiedRuntimeServicesTypeId
36
+ }
37
+
38
+ export type FeatureMountHandlersExternalApi<UsesInput extends boolean, Input, Services> = {
39
+ readonly onLive: (runtime: RuntimeHandle<Services>) => void
40
+ readonly onFailed: (error: FeatureLoadFailed) => void
41
+ } & (UsesInput extends true ? { readonly props: Input } : { readonly props?: never })
42
+
43
+ export type FeatureMountHandlers<
44
+ UsesInput extends boolean,
45
+ Input,
46
+ Services,
47
+ > = FeatureMountHandlersExternalApi<UsesInput, Input, Services>
48
+
49
+ export type RuntimeFeatureAvailability<Services, OpenServices> = [
50
+ EngineServices | OpenServices,
51
+ ] extends [Services]
52
+ ? object
53
+ : {
54
+ readonly missingFeatureServices: Exclude<EngineServices | OpenServices, Services>
55
+ }
56
+
57
+ export interface AnyFeatureMountHandlers {
58
+ readonly props: unknown
59
+ readonly onLive: <Services>(runtime: RuntimeHandle<Services>) => void
60
+ readonly onFailed: (error: FeatureLoadFailed) => void
61
+ }
62
+
63
+ export interface CapturedFeatureMountHandlers<
64
+ Props,
65
+ HostServices,
66
+ FeatureServices,
67
+ C extends UiContract,
68
+ N extends string,
69
+ Identity,
70
+ > {
71
+ readonly props: Props
72
+ readonly onLive: (
73
+ runtime: RuntimeHandle<
74
+ HostServices | FeatureServices | CompositionId<N, Identity, RequiredSlotIdentities<C>>
75
+ >,
76
+ ) => void
77
+ readonly onFailed: (error: FeatureLoadFailed) => void
78
+ }
79
+
80
+ export interface RuntimeHandle<Services = UnspecifiedRuntimeServices> {
81
+ read<Identifier extends Services, Service>(tag: Context.Tag<Identifier, Service>): Service
82
+ readOption<Identifier, Service>(tag: Context.Tag<Identifier, Service>): Option.Option<Service>
83
+ runRender(effect: Effect.Effect<Node, never, never>): Node
84
+ dispatch(priority: Priority, event: Tagged): void
85
+ mountScoped(activation: CompositionActivation): () => void
86
+ mountFeature<
87
+ Summary extends AnyFeatureGraphSummary,
88
+ ROut,
89
+ const DirectRequires extends ReadonlyArray<FeatureRequirement>,
90
+ >(
91
+ binding: [EngineServices | FeatureSummaryOpenServices<Summary>] extends [Services]
92
+ ? FeatureBinding<Summary, ROut, DirectRequires>
93
+ : never,
94
+ handlers: FeatureMountHandlers<
95
+ FeatureSummaryUsesInput<Summary>,
96
+ FeatureSummaryInput<Summary>,
97
+ Services | ROut
98
+ >,
99
+ ): () => void
100
+ mountCapturedFeature<
101
+ Summary extends AnyFeatureGraphSummary,
102
+ ROut,
103
+ const DirectRequires extends ReadonlyArray<FeatureRequirement>,
104
+ >(
105
+ binding: CapturedFeatureBinding<Summary, ROut, DirectRequires> &
106
+ RuntimeFeatureAvailability<Services, FeatureSummaryOpenServices<Summary>>,
107
+ handlers: CapturedFeatureMountHandlers<
108
+ FeatureSummaryMountProps<Summary>,
109
+ Services,
110
+ ROut,
111
+ FeatureSummaryContract<Summary>,
112
+ FeatureSummaryName<Summary>,
113
+ FeatureSummaryCompositionIdentity<Summary>
114
+ >,
115
+ ): () => void
116
+ mountAnyFeature(binding: AnyFeatureBinding, handlers: AnyFeatureMountHandlers): () => void
117
+ readonly instrumentation: Instrumentation
118
+ }
119
+
120
+ export interface AppRuntime<Services = UnspecifiedRuntimeServices> extends RuntimeHandle<Services> {
121
+ boot(): void
122
+ dispose(): void
123
+ isDisposed(): boolean
124
+ }
@@ -1,8 +1,12 @@
1
1
  import { expect, it } from '@effect/vitest'
2
- import { Duration, Effect, Layer, Schema as S } from 'effect'
2
+ import { Effect, Layer, Schema as S } from 'effect'
3
3
  import { Engine, Event, publish, Reducer, StateFamily } from '../index'
4
+ import { until } from '../testkit/flight.testkit'
4
5
 
5
- const tick = Effect.sleep(Duration.millis(1))
6
+ // Eviction is queued with queueMicrotask; one turn of the queue provably drains it.
7
+ const drainMicrotasks = Effect.promise(
8
+ () => new Promise<void>((resolve) => queueMicrotask(resolve)),
9
+ )
6
10
 
7
11
  it.live('same-shaped families resolve their own stores', () => {
8
12
  class First extends StateFamily.make('family.nominal.first', S.String, S.Number) {}
@@ -36,7 +40,10 @@ it.live("writing one key does not notify another key's subscribers", () => {
36
40
  })
37
41
 
38
42
  a.set(1)
39
- yield* tick
43
+ yield* until(
44
+ () => aWakes.n,
45
+ (n) => n > 0,
46
+ )
40
47
  expect(aWakes.n).toBe(1)
41
48
  expect(bWakes.n).toBe(0)
42
49
  expect(b.get()).toBe(0)
@@ -81,12 +88,16 @@ it.live('evictWhenUnused drops a key once its last subscriber leaves', () => {
81
88
  const offB = store.subscribe(() => {})
82
89
  expect(family.size()).toBe(1)
83
90
 
91
+ // Releasing one of two subscribers queues no eviction at all — assert with no window.
84
92
  offA()
85
- yield* tick
93
+ yield* drainMicrotasks
86
94
  expect(family.size()).toBe(1)
87
95
 
88
96
  offB()
89
- yield* tick
97
+ yield* until(
98
+ () => family.size(),
99
+ (size) => size === 0,
100
+ )
90
101
  expect(family.size()).toBe(0)
91
102
  expect(family.at('k').get()).toBe(0)
92
103
  }).pipe(Effect.provide(TestLayer))
@@ -103,7 +114,7 @@ it.live('evictWhenUnused: a same-tick re-subscribe cancels eviction', () => {
103
114
  const off = store.subscribe(() => {})
104
115
  off()
105
116
  store.subscribe(() => {})
106
- yield* tick
117
+ yield* drainMicrotasks
107
118
  expect(family.size()).toBe(1)
108
119
  expect(family.at('k').get()).toBe(7)
109
120
  }).pipe(Effect.provide(TestLayer))
@@ -130,12 +141,18 @@ it.live('a family reducer fold returning Tombstone evicts the key', () => {
130
141
  const family = yield* Items.store
131
142
 
132
143
  yield* publish('High', Event.construct(Bumped, { id: 'a' }))
133
- yield* Effect.sleep(Duration.millis(5))
144
+ yield* until(
145
+ () => family.at('a').get(),
146
+ (value) => value === 1,
147
+ )
134
148
  const before = family.at('a')
135
149
  expect(before.get()).toBe(1)
136
150
 
137
151
  yield* publish('High', Event.construct(Removed, { id: 'a' }))
138
- yield* Effect.sleep(Duration.millis(5))
152
+ yield* until(
153
+ () => family.at('a'),
154
+ (store) => store !== before,
155
+ )
139
156
  const after = family.at('a')
140
157
  expect(after).not.toBe(before)
141
158
  expect(after.get()).toBe(0)
@@ -10,6 +10,15 @@ import { Deferred, Duration, Effect, MutableRef, Option } from 'effect'
10
10
 
11
11
  export const tick = (ms = 10): Effect.Effect<void> => Effect.sleep(Duration.millis(ms))
12
12
 
13
+ // The runtime notifies on the microtask queue (scheduler, stateFamily, calcFamily),
14
+ // so draining turns is exact where a duration is a guess: a microtask cannot be
15
+ // starved by host load the way a 10ms budget can. Use this to let the graph
16
+ // quiesce before asserting that something did NOT happen.
17
+ export const flush = (turns = 3): Effect.Effect<void> =>
18
+ Effect.promise(() => new Promise<void>((resolve) => queueMicrotask(resolve))).pipe(
19
+ Effect.repeatN(Math.max(turns - 1, 0)),
20
+ )
21
+
13
22
  export const until = <A>(
14
23
  read: () => A,
15
24
  predicate: (value: A) => boolean,