@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.
- package/README.md +15 -6
- package/dist/boundary/boundary.d.ts +64 -0
- package/dist/boundary/boundary.d.ts.map +1 -0
- package/dist/boundary/boundary.js +102 -0
- package/dist/boundary/boundary.js.map +1 -0
- package/dist/calc/asyncCalc.d.ts +23 -0
- package/dist/calc/asyncCalc.d.ts.map +1 -0
- package/dist/calc/asyncCalc.js +100 -0
- package/dist/calc/asyncCalc.js.map +1 -0
- package/dist/calc/asyncCalcTypes.d.ts +80 -0
- package/dist/calc/asyncCalcTypes.d.ts.map +1 -0
- package/dist/calc/asyncCalcTypes.js +2 -0
- package/dist/calc/asyncCalcTypes.js.map +1 -0
- package/dist/calc/asyncData.d.ts +29 -0
- package/dist/calc/asyncData.d.ts.map +1 -0
- package/dist/calc/asyncData.js +21 -0
- package/dist/calc/asyncData.js.map +1 -0
- package/dist/calc/calc.d.ts +39 -0
- package/dist/calc/calc.d.ts.map +1 -0
- package/dist/calc/calc.js +48 -0
- package/dist/calc/calc.js.map +1 -0
- package/dist/calc/calcFamily.d.ts +40 -0
- package/dist/calc/calcFamily.d.ts.map +1 -0
- package/dist/calc/calcFamily.js +156 -0
- package/dist/calc/calcFamily.js.map +1 -0
- package/dist/calc/compose.d.ts +5 -0
- package/dist/calc/compose.d.ts.map +1 -0
- package/dist/calc/compose.js +5 -0
- package/dist/calc/compose.js.map +1 -0
- package/dist/calc/queryState.d.ts +14 -0
- package/dist/calc/queryState.d.ts.map +1 -0
- package/dist/calc/queryState.js +39 -0
- package/dist/calc/queryState.js.map +1 -0
- package/dist/channel/channel.d.ts +51 -0
- package/dist/channel/channel.d.ts.map +1 -0
- package/dist/channel/channel.js +91 -0
- package/dist/channel/channel.js.map +1 -0
- package/dist/channel/procedures.d.ts +24 -0
- package/dist/channel/procedures.d.ts.map +1 -0
- package/dist/channel/procedures.js +82 -0
- package/dist/channel/procedures.js.map +1 -0
- package/dist/compose/composition.d.ts +12 -0
- package/dist/compose/composition.d.ts.map +1 -0
- package/dist/compose/composition.js +62 -0
- package/dist/compose/composition.js.map +1 -0
- package/dist/compose/compositionTypes.d.ts +127 -0
- package/dist/compose/compositionTypes.d.ts.map +1 -0
- package/dist/compose/compositionTypes.js +2 -0
- package/dist/compose/compositionTypes.js.map +1 -0
- package/dist/compose/host.d.ts +6 -0
- package/dist/compose/host.d.ts.map +1 -0
- package/dist/compose/host.js +2 -0
- package/dist/compose/host.js.map +1 -0
- package/dist/compose/props.d.ts +10 -0
- package/dist/compose/props.d.ts.map +1 -0
- package/dist/compose/props.js +6 -0
- package/dist/compose/props.js.map +1 -0
- package/dist/compose/provide.d.ts +18 -0
- package/dist/compose/provide.d.ts.map +1 -0
- package/dist/compose/provide.js +49 -0
- package/dist/compose/provide.js.map +1 -0
- package/dist/compose/slot.d.ts +70 -0
- package/dist/compose/slot.d.ts.map +1 -0
- package/dist/compose/slot.js +26 -0
- package/dist/compose/slot.js.map +1 -0
- package/dist/compose/structure.d.ts +51 -0
- package/dist/compose/structure.d.ts.map +1 -0
- package/dist/compose/structure.js +117 -0
- package/dist/compose/structure.js.map +1 -0
- package/dist/compose/ui.d.ts +112 -0
- package/dist/compose/ui.d.ts.map +1 -0
- package/dist/compose/ui.js +58 -0
- package/dist/compose/ui.js.map +1 -0
- package/dist/definition/definition.d.ts +11 -0
- package/dist/definition/definition.d.ts.map +1 -0
- package/dist/definition/definition.js +35 -0
- package/dist/definition/definition.js.map +1 -0
- package/dist/event/event.d.ts +52 -0
- package/dist/event/event.d.ts.map +1 -0
- package/dist/event/event.js +88 -0
- package/dist/event/event.js.map +1 -0
- package/dist/event/eventGroup.d.ts +8 -0
- package/dist/event/eventGroup.d.ts.map +1 -0
- package/dist/event/eventGroup.js +3 -0
- package/dist/event/eventGroup.js.map +1 -0
- package/dist/feature/feature.d.ts +27 -0
- package/dist/feature/feature.d.ts.map +1 -0
- package/dist/feature/feature.js +20 -0
- package/dist/feature/feature.js.map +1 -0
- package/dist/feature/featureBinding.d.ts +85 -0
- package/dist/feature/featureBinding.d.ts.map +1 -0
- package/dist/feature/featureBinding.js +10 -0
- package/dist/feature/featureBinding.js.map +1 -0
- package/dist/feature/featureClass.d.ts +10 -0
- package/dist/feature/featureClass.d.ts.map +1 -0
- package/dist/feature/featureClass.js +2 -0
- package/dist/feature/featureClass.js.map +1 -0
- package/dist/feature/featureConfig.d.ts +86 -0
- package/dist/feature/featureConfig.d.ts.map +1 -0
- package/dist/feature/featureConfig.js +13 -0
- package/dist/feature/featureConfig.js.map +1 -0
- package/dist/feature/featureFactory.d.ts +15 -0
- package/dist/feature/featureFactory.d.ts.map +1 -0
- package/dist/feature/featureFactory.js +59 -0
- package/dist/feature/featureFactory.js.map +1 -0
- package/dist/feature/featureModule.d.ts +28 -0
- package/dist/feature/featureModule.d.ts.map +1 -0
- package/dist/feature/featureModule.js +33 -0
- package/dist/feature/featureModule.js.map +1 -0
- package/dist/feature/featureNativeTree.d.ts +26 -0
- package/dist/feature/featureNativeTree.d.ts.map +1 -0
- package/dist/feature/featureNativeTree.js +2 -0
- package/dist/feature/featureNativeTree.js.map +1 -0
- package/dist/feature/featureRequirement.d.ts +43 -0
- package/dist/feature/featureRequirement.d.ts.map +1 -0
- package/dist/feature/featureRequirement.js +6 -0
- package/dist/feature/featureRequirement.js.map +1 -0
- package/dist/feature/featureVariants.d.ts +9 -0
- package/dist/feature/featureVariants.d.ts.map +1 -0
- package/dist/feature/featureVariants.js +59 -0
- package/dist/feature/featureVariants.js.map +1 -0
- package/dist/feature/mountFeature.d.ts +10 -0
- package/dist/feature/mountFeature.d.ts.map +1 -0
- package/dist/feature/mountFeature.js +14 -0
- package/dist/feature/mountFeature.js.map +1 -0
- package/dist/graph/closure.d.ts +77 -0
- package/dist/graph/closure.d.ts.map +1 -0
- package/dist/graph/closure.js +4 -0
- package/dist/graph/closure.js.map +1 -0
- package/dist/graph/services.d.ts +30 -0
- package/dist/graph/services.d.ts.map +1 -0
- package/dist/graph/services.js +2 -0
- package/dist/graph/services.js.map +1 -0
- package/dist/graph/summary.d.ts +60 -0
- package/dist/graph/summary.d.ts.map +1 -0
- package/dist/graph/summary.js +7 -0
- package/dist/graph/summary.js.map +1 -0
- package/dist/index.d.ts +51 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +34 -0
- package/dist/index.js.map +1 -0
- package/dist/internal/bucketCache.d.ts +19 -0
- package/dist/internal/bucketCache.d.ts.map +1 -0
- package/dist/internal/bucketCache.js +20 -0
- package/dist/internal/bucketCache.js.map +1 -0
- package/dist/internal/capture.d.ts +17 -0
- package/dist/internal/capture.d.ts.map +1 -0
- package/dist/internal/capture.js +5 -0
- package/dist/internal/capture.js.map +1 -0
- package/dist/internal/env.d.ts +2 -0
- package/dist/internal/env.d.ts.map +1 -0
- package/dist/internal/env.js +5 -0
- package/dist/internal/env.js.map +1 -0
- package/dist/internal/errors.d.ts +48 -0
- package/dist/internal/errors.d.ts.map +1 -0
- package/dist/internal/errors.js +69 -0
- package/dist/internal/errors.js.map +1 -0
- package/dist/internal/familyKeys.d.ts +16 -0
- package/dist/internal/familyKeys.d.ts.map +1 -0
- package/dist/internal/familyKeys.js +35 -0
- package/dist/internal/familyKeys.js.map +1 -0
- package/dist/internal/inspect.d.ts +5 -0
- package/dist/internal/inspect.d.ts.map +1 -0
- package/dist/internal/inspect.js +20 -0
- package/dist/internal/inspect.js.map +1 -0
- package/dist/internal/platform.d.ts +43 -0
- package/dist/internal/platform.d.ts.map +1 -0
- package/dist/internal/platform.js +68 -0
- package/dist/internal/platform.js.map +1 -0
- package/dist/internal/queryDriver.d.ts +7 -0
- package/dist/internal/queryDriver.d.ts.map +1 -0
- package/dist/internal/queryDriver.js +222 -0
- package/dist/internal/queryDriver.js.map +1 -0
- package/dist/internal/queryDriverConsume.d.ts +9 -0
- package/dist/internal/queryDriverConsume.d.ts.map +1 -0
- package/dist/internal/queryDriverConsume.js +16 -0
- package/dist/internal/queryDriverConsume.js.map +1 -0
- package/dist/internal/queryDriverHydrate.d.ts +17 -0
- package/dist/internal/queryDriverHydrate.d.ts.map +1 -0
- package/dist/internal/queryDriverHydrate.js +25 -0
- package/dist/internal/queryDriverHydrate.js.map +1 -0
- package/dist/internal/queryDriverPersist.d.ts +16 -0
- package/dist/internal/queryDriverPersist.d.ts.map +1 -0
- package/dist/internal/queryDriverPersist.js +15 -0
- package/dist/internal/queryDriverPersist.js.map +1 -0
- package/dist/internal/queryDriverPersistWarn.d.ts +12 -0
- package/dist/internal/queryDriverPersistWarn.d.ts.map +1 -0
- package/dist/internal/queryDriverPersistWarn.js +31 -0
- package/dist/internal/queryDriverPersistWarn.js.map +1 -0
- package/dist/internal/queryDriverSettle.d.ts +23 -0
- package/dist/internal/queryDriverSettle.d.ts.map +1 -0
- package/dist/internal/queryDriverSettle.js +52 -0
- package/dist/internal/queryDriverSettle.js.map +1 -0
- package/dist/internal/queryDriverStore.d.ts +28 -0
- package/dist/internal/queryDriverStore.d.ts.map +1 -0
- package/dist/internal/queryDriverStore.js +53 -0
- package/dist/internal/queryDriverStore.js.map +1 -0
- package/dist/internal/queryDriverTypes.d.ts +44 -0
- package/dist/internal/queryDriverTypes.d.ts.map +1 -0
- package/dist/internal/queryDriverTypes.js +9 -0
- package/dist/internal/queryDriverTypes.js.map +1 -0
- package/dist/internal/queryEvents.d.ts +12 -0
- package/dist/internal/queryEvents.d.ts.map +1 -0
- package/dist/internal/queryEvents.js +11 -0
- package/dist/internal/queryEvents.js.map +1 -0
- package/dist/internal/queryStore.d.ts +13 -0
- package/dist/internal/queryStore.d.ts.map +1 -0
- package/dist/internal/queryStore.js +11 -0
- package/dist/internal/queryStore.js.map +1 -0
- package/dist/internal/reuse.d.ts +2 -0
- package/dist/internal/reuse.d.ts.map +1 -0
- package/dist/internal/reuse.js +55 -0
- package/dist/internal/reuse.js.map +1 -0
- package/dist/internal/scheduler.d.ts +14 -0
- package/dist/internal/scheduler.d.ts.map +1 -0
- package/dist/internal/scheduler.js +101 -0
- package/dist/internal/scheduler.js.map +1 -0
- package/dist/internal/seeds.d.ts +3 -0
- package/dist/internal/seeds.d.ts.map +1 -0
- package/dist/internal/seeds.js +3 -0
- package/dist/internal/seeds.js.map +1 -0
- package/dist/internal/sources.d.ts +20 -0
- package/dist/internal/sources.d.ts.map +1 -0
- package/dist/internal/sources.js +39 -0
- package/dist/internal/sources.js.map +1 -0
- package/dist/internal/stateRegistry.d.ts +2 -0
- package/dist/internal/stateRegistry.d.ts.map +1 -0
- package/dist/internal/stateRegistry.js +15 -0
- package/dist/internal/stateRegistry.js.map +1 -0
- package/dist/internal/store.d.ts +15 -0
- package/dist/internal/store.d.ts.map +1 -0
- package/dist/internal/store.js +61 -0
- package/dist/internal/store.js.map +1 -0
- package/dist/internal/track.d.ts +15 -0
- package/dist/internal/track.d.ts.map +1 -0
- package/dist/internal/track.js +11 -0
- package/dist/internal/track.js.map +1 -0
- package/dist/internal/variance.d.ts +4 -0
- package/dist/internal/variance.d.ts.map +1 -0
- package/dist/internal/variance.js +2 -0
- package/dist/internal/variance.js.map +1 -0
- package/dist/internal.d.ts +43 -0
- package/dist/internal.d.ts.map +1 -0
- package/dist/internal.js +59 -0
- package/dist/internal.js.map +1 -0
- package/dist/namespace/namespace.d.ts +24 -0
- package/dist/namespace/namespace.d.ts.map +1 -0
- package/dist/namespace/namespace.js +20 -0
- package/dist/namespace/namespace.js.map +1 -0
- package/dist/procedure/procedure.d.ts +32 -0
- package/dist/procedure/procedure.d.ts.map +1 -0
- package/dist/procedure/procedure.js +34 -0
- package/dist/procedure/procedure.js.map +1 -0
- package/dist/reducer/reducer.d.ts +35 -0
- package/dist/reducer/reducer.d.ts.map +1 -0
- package/dist/reducer/reducer.js +73 -0
- package/dist/reducer/reducer.js.map +1 -0
- package/dist/remote/pendingQueue.d.ts +22 -0
- package/dist/remote/pendingQueue.d.ts.map +1 -0
- package/dist/remote/pendingQueue.js +129 -0
- package/dist/remote/pendingQueue.js.map +1 -0
- package/dist/remote/remoteState.d.ts +13 -0
- package/dist/remote/remoteState.d.ts.map +1 -0
- package/dist/remote/remoteState.js +189 -0
- package/dist/remote/remoteState.js.map +1 -0
- package/dist/remote/remoteStateMake.d.ts +8 -0
- package/dist/remote/remoteStateMake.d.ts.map +1 -0
- package/dist/remote/remoteStateMake.js +46 -0
- package/dist/remote/remoteStateMake.js.map +1 -0
- package/dist/remote/remoteStateSend.d.ts +43 -0
- package/dist/remote/remoteStateSend.d.ts.map +1 -0
- package/dist/remote/remoteStateSend.js +83 -0
- package/dist/remote/remoteStateSend.js.map +1 -0
- package/dist/remote/remoteStateTypes.d.ts +106 -0
- package/dist/remote/remoteStateTypes.d.ts.map +1 -0
- package/dist/remote/remoteStateTypes.js +4 -0
- package/dist/remote/remoteStateTypes.js.map +1 -0
- package/dist/runtime/appRuntime.d.ts +10 -0
- package/dist/runtime/appRuntime.d.ts.map +1 -0
- package/dist/runtime/appRuntime.js +6 -0
- package/dist/runtime/appRuntime.js.map +1 -0
- package/dist/runtime/bus.d.ts +22 -0
- package/dist/runtime/bus.d.ts.map +1 -0
- package/dist/runtime/bus.js +12 -0
- package/dist/runtime/bus.js.map +1 -0
- package/dist/runtime/capturedAppRuntime.d.ts +7 -0
- package/dist/runtime/capturedAppRuntime.d.ts.map +1 -0
- package/dist/runtime/capturedAppRuntime.js +137 -0
- package/dist/runtime/capturedAppRuntime.js.map +1 -0
- package/dist/runtime/eventBudget.d.ts +32 -0
- package/dist/runtime/eventBudget.d.ts.map +1 -0
- package/dist/runtime/eventBudget.js +68 -0
- package/dist/runtime/eventBudget.js.map +1 -0
- package/dist/runtime/featureMount.d.ts +48 -0
- package/dist/runtime/featureMount.d.ts.map +1 -0
- package/dist/runtime/featureMount.js +118 -0
- package/dist/runtime/featureMount.js.map +1 -0
- package/dist/runtime/instrumentation.d.ts +18 -0
- package/dist/runtime/instrumentation.d.ts.map +1 -0
- package/dist/runtime/instrumentation.js +16 -0
- package/dist/runtime/instrumentation.js.map +1 -0
- package/dist/runtime/loop.d.ts +44 -0
- package/dist/runtime/loop.d.ts.map +1 -0
- package/dist/runtime/loop.js +131 -0
- package/dist/runtime/loop.js.map +1 -0
- package/dist/runtime/queries.d.ts +20 -0
- package/dist/runtime/queries.d.ts.map +1 -0
- package/dist/runtime/queries.js +21 -0
- package/dist/runtime/queries.js.map +1 -0
- package/dist/runtime/runtimeHandle.d.ts +56 -0
- package/dist/runtime/runtimeHandle.d.ts.map +1 -0
- package/dist/runtime/runtimeHandle.js +2 -0
- package/dist/runtime/runtimeHandle.js.map +1 -0
- package/dist/scene/scene.d.ts +55 -0
- package/dist/scene/scene.d.ts.map +1 -0
- package/dist/scene/scene.js +74 -0
- package/dist/scene/scene.js.map +1 -0
- package/dist/state/state.d.ts +54 -0
- package/dist/state/state.d.ts.map +1 -0
- package/dist/state/state.js +41 -0
- package/dist/state/state.js.map +1 -0
- package/dist/state/stateFamily.d.ts +59 -0
- package/dist/state/stateFamily.d.ts.map +1 -0
- package/dist/state/stateFamily.js +139 -0
- package/dist/state/stateFamily.js.map +1 -0
- package/dist/state/stateGroup.d.ts +36 -0
- package/dist/state/stateGroup.d.ts.map +1 -0
- package/dist/state/stateGroup.js +33 -0
- package/dist/state/stateGroup.js.map +1 -0
- package/dist/state/token.d.ts +35 -0
- package/dist/state/token.d.ts.map +1 -0
- package/dist/state/token.js +18 -0
- package/dist/state/token.js.map +1 -0
- package/dist/synced/syncedStore.d.ts +44 -0
- package/dist/synced/syncedStore.d.ts.map +1 -0
- package/dist/synced/syncedStore.js +32 -0
- package/dist/synced/syncedStore.js.map +1 -0
- package/dist/testkit/flight.testkit.d.ts +18 -0
- package/dist/testkit/flight.testkit.d.ts.map +1 -0
- package/dist/testkit/flight.testkit.js +46 -0
- package/dist/testkit/flight.testkit.js.map +1 -0
- package/dist/ui/node.d.ts +3 -0
- package/dist/ui/node.d.ts.map +1 -0
- package/dist/ui/node.js +2 -0
- package/dist/ui/node.js.map +1 -0
- package/dist/ui/trigger.d.ts +2 -0
- package/dist/ui/trigger.d.ts.map +1 -0
- package/dist/ui/trigger.js +2 -0
- package/dist/ui/trigger.js.map +1 -0
- package/dist/wire/tree.d.ts +32 -0
- package/dist/wire/tree.d.ts.map +1 -0
- package/dist/wire/tree.js +64 -0
- package/dist/wire/tree.js.map +1 -0
- package/dist/wire/triggers.d.ts +25 -0
- package/dist/wire/triggers.d.ts.map +1 -0
- package/dist/wire/triggers.js +31 -0
- package/dist/wire/triggers.js.map +1 -0
- package/package.json +17 -5
- package/src/calc/asyncCalc-key-attribution.test.ts +182 -0
- package/src/calc/asyncCalc-registry-name.test.ts +82 -0
- package/src/calc/asyncCalc.superseded-commit.test.ts +162 -0
- package/src/calc/asyncCalc.test.ts +35 -7
- package/src/calc/asyncCalc.ts +33 -10
- package/src/calc/asyncCalcTypes.test.ts +64 -0
- package/src/calc/asyncCalcTypes.ts +8 -10
- package/src/calc/calcFamily.ts +81 -34
- package/src/calc/family-identity.test.ts +231 -0
- package/src/calc/queryState.ts +36 -5
- package/src/channel/channel-throttle-frames.test.ts +163 -0
- package/src/channel/channel.ts +5 -0
- package/src/compose/compose-identity.test.ts +175 -0
- package/src/compose/composition.ts +8 -4
- package/src/compose/structure.ts +45 -13
- package/src/compose/ui.ts +22 -5
- package/src/event/event.ts +31 -2
- package/src/event/syncTrigger.test.ts +66 -0
- package/src/internal/familyKeys.ts +41 -0
- package/src/internal/platform.test.ts +55 -0
- package/src/internal/platform.ts +72 -0
- package/src/internal/queryDriver-defect-attribution.test.ts +107 -0
- package/src/internal/queryDriver-persist-key.test.ts +118 -0
- package/src/internal/queryDriver.persistence-staleness.test.ts +209 -0
- package/src/internal/queryDriver.ts +92 -80
- package/src/internal/queryDriverConsume.ts +42 -0
- package/src/internal/queryDriverHydrate.ts +9 -3
- package/src/internal/queryDriverPersist.ts +42 -0
- package/src/internal/queryDriverPersistWarn.ts +61 -0
- package/src/internal/queryDriverSettle.ts +85 -0
- package/src/internal/queryDriverStore.ts +9 -74
- package/src/internal/queryDriverTypes.ts +10 -0
- package/src/internal/queryState-and-scheduler-audit.test.ts +170 -0
- package/src/internal/scheduler-multi-source-wake.test.ts +111 -0
- package/src/internal/scheduler.ts +80 -6
- package/src/internal.ts +1 -0
- package/src/reducer/reducer.ts +3 -3
- package/src/remote/pendingQueue.ts +35 -5
- package/src/remote/remoteState.intent-key-stamp.test.ts +207 -0
- package/src/remote/remoteState.key-attribution.test.ts +174 -0
- package/src/remote/remoteState.ts +27 -15
- package/src/remote/remoteStateSend.ts +29 -8
- package/src/remote/remoteStateTypes.ts +7 -0
- package/src/runtime/bus.ts +6 -1
- package/src/runtime/capturedAppRuntime.ts +134 -110
- package/src/runtime/eventBudget.ts +2 -1
- package/src/runtime/feature-mount-ownership.test.ts +539 -0
- package/src/runtime/featureMount.ts +110 -3
- package/src/runtime/loop.ts +29 -6
- package/src/runtime/mount-inflight-ownership.test.ts +361 -0
- package/src/runtime/mount-scope-hardening.test.ts +440 -0
- package/src/runtime/runtime.schema-identity.test.ts +188 -0
- package/src/state/state.ts +8 -4
- package/src/state/stateFamily.ts +86 -36
- package/src/state/stateGroup.ts +24 -6
- package/src/testkit/flight.testkit.ts +2 -1
- package/src/wire/tree-key-presence.test.ts +46 -0
- 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(
|
|
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(
|
|
342
|
-
|
|
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(
|
|
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(
|
|
441
|
-
|
|
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(
|
|
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
|
package/src/calc/asyncCalc.ts
CHANGED
|
@@ -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
|
-
|
|
159
|
-
|
|
160
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
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:
|
|
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?:
|
|
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) }
|