@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.
- 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 +21 -0
- package/dist/calc/asyncCalc.d.ts.map +1 -0
- package/dist/calc/asyncCalc.js +90 -0
- package/dist/calc/asyncCalc.js.map +1 -0
- package/dist/calc/asyncCalcTypes.d.ts +78 -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 +113 -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 +12 -0
- package/dist/calc/queryState.d.ts.map +1 -0
- package/dist/calc/queryState.js +17 -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 +86 -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 +56 -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 +92 -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 +47 -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 +67 -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/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/queryDriver.d.ts +7 -0
- package/dist/internal/queryDriver.d.ts.map +1 -0
- package/dist/internal/queryDriver.js +198 -0
- package/dist/internal/queryDriver.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 +19 -0
- package/dist/internal/queryDriverHydrate.js.map +1 -0
- package/dist/internal/queryDriverStore.d.ts +54 -0
- package/dist/internal/queryDriverStore.d.ts.map +1 -0
- package/dist/internal/queryDriverStore.js +77 -0
- package/dist/internal/queryDriverStore.js.map +1 -0
- package/dist/internal/queryDriverTypes.d.ts +41 -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 +13 -0
- package/dist/internal/scheduler.d.ts.map +1 -0
- package/dist/internal/scheduler.js +38 -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 +42 -0
- package/dist/internal.d.ts.map +1 -0
- package/dist/internal.js +58 -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 +20 -0
- package/dist/remote/pendingQueue.d.ts.map +1 -0
- package/dist/remote/pendingQueue.js +113 -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 +176 -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 +41 -0
- package/dist/remote/remoteStateSend.d.ts.map +1 -0
- package/dist/remote/remoteStateSend.js +67 -0
- package/dist/remote/remoteStateSend.js.map +1 -0
- package/dist/remote/remoteStateTypes.d.ts +103 -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 +17 -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 +151 -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 +67 -0
- package/dist/runtime/eventBudget.js.map +1 -0
- package/dist/runtime/featureMount.d.ts +16 -0
- package/dist/runtime/featureMount.d.ts.map +1 -0
- package/dist/runtime/featureMount.js +54 -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 +32 -0
- package/dist/runtime/loop.d.ts.map +1 -0
- package/dist/runtime/loop.js +128 -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 +91 -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 +45 -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 +59 -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/boundary/boundary.test.ts +26 -23
- package/src/calc/asyncCalc.invalidate.test.ts +258 -55
- package/src/calc/asyncCalc.test.ts +225 -85
- package/src/calc/asyncCalc.ts +27 -141
- package/src/calc/asyncCalcTypes.test.ts +64 -0
- package/src/calc/asyncCalcTypes.ts +139 -0
- package/src/calc/calc.test.ts +10 -11
- package/src/calc/calcFamily.test.ts +12 -12
- package/src/channel/channel.ts +8 -106
- package/src/channel/procedures.ts +108 -0
- package/src/compose/composition.ts +49 -246
- package/src/compose/compositionTypes.ts +249 -0
- package/src/event/event.fromSource.test.ts +12 -12
- package/src/feature/feature.ts +50 -1210
- package/src/feature/feature.typecheck.ts +2 -61
- package/src/feature/featureBinding.ts +158 -0
- package/src/feature/featureClass.ts +162 -0
- package/src/feature/featureConfig.ts +192 -0
- package/src/feature/featureFactory.ts +163 -0
- package/src/feature/featureModule.ts +136 -0
- package/src/feature/featureModule.typecheck.ts +63 -0
- package/src/feature/featureNativeTree.ts +152 -0
- package/src/feature/featureRequirement.ts +82 -0
- package/src/feature/featureVariants.ts +234 -0
- package/src/feature/mountFeature.ts +54 -0
- package/src/graph/closure.ts +45 -226
- package/src/graph/services.ts +95 -0
- package/src/graph/summary.ts +134 -0
- package/src/internal/queryDriver.ts +20 -89
- package/src/internal/queryDriverHydrate.ts +45 -0
- package/src/internal/queryDriverTypes.ts +8 -10
- package/src/internal/store.test.ts +27 -6
- package/src/remote/pendingQueue.ts +145 -0
- package/src/remote/remoteState.test.ts +28 -22
- package/src/remote/remoteState.ts +63 -489
- package/src/remote/remoteStateMake.ts +97 -0
- package/src/remote/remoteStateSend.ts +122 -0
- package/src/remote/remoteStateTypes.ts +155 -0
- package/src/runtime/appRuntime.activation.test.ts +9 -5
- package/src/runtime/appRuntime.test.ts +40 -16
- package/src/runtime/appRuntime.ts +17 -472
- package/src/runtime/capturedAppRuntime.ts +274 -0
- package/src/runtime/eventBudget.test.ts +38 -8
- package/src/runtime/featureMount.ts +94 -0
- package/src/runtime/hardening.test.ts +18 -4
- package/src/runtime/instrumentation.test.ts +49 -12
- package/src/runtime/loop.test.ts +51 -10
- package/src/runtime/runtimeHandle.ts +124 -0
- package/src/state/stateFamily.test.ts +25 -8
- package/src/testkit/flight.testkit.ts +9 -0
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import type { UiContract } from '../compose/ui'
|
|
2
|
+
|
|
3
|
+
export const FeatureGraphValidTypeId: unique symbol = Symbol.for('reform/FeatureGraphValid')
|
|
4
|
+
export type FeatureGraphValidTypeId = typeof FeatureGraphValidTypeId
|
|
5
|
+
|
|
6
|
+
export const FeatureModuleGraphValidTypeId: unique symbol = Symbol.for(
|
|
7
|
+
'reform/FeatureModuleGraphValid',
|
|
8
|
+
)
|
|
9
|
+
export type FeatureModuleGraphValidTypeId = typeof FeatureModuleGraphValidTypeId
|
|
10
|
+
|
|
11
|
+
export const FeatureChildSummaryTypeId: unique symbol = Symbol.for('reform/FeatureChildSummary')
|
|
12
|
+
export type FeatureChildSummaryTypeId = typeof FeatureChildSummaryTypeId
|
|
13
|
+
|
|
14
|
+
export const FeatureGraphSummaryTypeId: unique symbol = Symbol.for('reform/FeatureGraphSummary')
|
|
15
|
+
export type FeatureGraphSummaryTypeId = typeof FeatureGraphSummaryTypeId
|
|
16
|
+
|
|
17
|
+
export const SlotBindingSummaryTypeId: unique symbol = Symbol.for('reform/SlotBindingSummary')
|
|
18
|
+
export type SlotBindingSummaryTypeId = typeof SlotBindingSummaryTypeId
|
|
19
|
+
|
|
20
|
+
export interface SlotBindingSummary {
|
|
21
|
+
readonly [SlotBindingSummaryTypeId]: SlotBindingSummaryTypeId
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* The constant-size proof published after a Feature's current module has passed
|
|
26
|
+
* graph validation. Type-only facts use empty-or-singleton tuples so the runtime
|
|
27
|
+
* summary stays compact while direct indexed access recovers their exact types.
|
|
28
|
+
*/
|
|
29
|
+
export interface FeatureGraphSummary<
|
|
30
|
+
out FeatureIdentity,
|
|
31
|
+
out MountProps,
|
|
32
|
+
out CompositionProps,
|
|
33
|
+
out C extends UiContract,
|
|
34
|
+
out S extends ReadonlyArray<unknown>,
|
|
35
|
+
out N extends string,
|
|
36
|
+
out CompositionIdentity,
|
|
37
|
+
out OpenServices,
|
|
38
|
+
out SupportOpenServices,
|
|
39
|
+
out UsesInput extends boolean,
|
|
40
|
+
out Input,
|
|
41
|
+
> extends SlotBindingSummary {
|
|
42
|
+
readonly [FeatureGraphSummaryTypeId]: {
|
|
43
|
+
readonly featureIdentity: readonly [] | readonly [FeatureIdentity]
|
|
44
|
+
readonly mountProps: readonly [] | readonly [MountProps]
|
|
45
|
+
readonly compositionProps: readonly [] | readonly [CompositionProps]
|
|
46
|
+
readonly contract: readonly [] | readonly [C]
|
|
47
|
+
readonly states: readonly [] | readonly [S]
|
|
48
|
+
readonly name: N
|
|
49
|
+
readonly compositionIdentity: readonly [] | readonly [CompositionIdentity]
|
|
50
|
+
readonly openServices: readonly [] | readonly [OpenServices]
|
|
51
|
+
readonly supportOpenServices: readonly [] | readonly [SupportOpenServices]
|
|
52
|
+
readonly usesInput: readonly [] | readonly [UsesInput]
|
|
53
|
+
readonly input: readonly [] | readonly [Input]
|
|
54
|
+
readonly validated: FeatureModuleGraphValidTypeId
|
|
55
|
+
}
|
|
56
|
+
readonly [FeatureChildSummaryTypeId]: {
|
|
57
|
+
readonly featureIdentity: readonly [] | readonly [FeatureIdentity]
|
|
58
|
+
readonly compositionIdentity: readonly [] | readonly [CompositionIdentity]
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export type FeatureChildSummary<
|
|
63
|
+
FeatureIdentity,
|
|
64
|
+
MountProps,
|
|
65
|
+
CompositionProps,
|
|
66
|
+
C extends UiContract,
|
|
67
|
+
S extends ReadonlyArray<unknown>,
|
|
68
|
+
N extends string,
|
|
69
|
+
CompositionIdentity,
|
|
70
|
+
OpenServices,
|
|
71
|
+
SupportOpenServices,
|
|
72
|
+
UsesInput extends boolean,
|
|
73
|
+
Input,
|
|
74
|
+
> = FeatureGraphSummary<
|
|
75
|
+
FeatureIdentity,
|
|
76
|
+
MountProps,
|
|
77
|
+
CompositionProps,
|
|
78
|
+
C,
|
|
79
|
+
S,
|
|
80
|
+
N,
|
|
81
|
+
CompositionIdentity,
|
|
82
|
+
OpenServices,
|
|
83
|
+
SupportOpenServices,
|
|
84
|
+
UsesInput,
|
|
85
|
+
Input
|
|
86
|
+
>
|
|
87
|
+
|
|
88
|
+
export type AnyFeatureGraphSummary = FeatureGraphSummary<
|
|
89
|
+
unknown,
|
|
90
|
+
unknown,
|
|
91
|
+
unknown,
|
|
92
|
+
UiContract,
|
|
93
|
+
ReadonlyArray<unknown>,
|
|
94
|
+
string,
|
|
95
|
+
unknown,
|
|
96
|
+
unknown,
|
|
97
|
+
unknown,
|
|
98
|
+
boolean,
|
|
99
|
+
unknown
|
|
100
|
+
>
|
|
101
|
+
|
|
102
|
+
export type FeatureSummaryIdentity<Summary extends AnyFeatureGraphSummary> =
|
|
103
|
+
Summary[FeatureGraphSummaryTypeId]['featureIdentity'][number]
|
|
104
|
+
export type FeatureSummaryMountProps<Summary extends AnyFeatureGraphSummary> =
|
|
105
|
+
Summary[FeatureGraphSummaryTypeId]['mountProps'][number]
|
|
106
|
+
export type FeatureSummaryCompositionProps<Summary extends AnyFeatureGraphSummary> =
|
|
107
|
+
Summary[FeatureGraphSummaryTypeId]['compositionProps'][number]
|
|
108
|
+
export type FeatureSummaryContract<Summary extends AnyFeatureGraphSummary> =
|
|
109
|
+
Summary[FeatureGraphSummaryTypeId]['contract'][number]
|
|
110
|
+
export type FeatureSummaryStates<Summary extends AnyFeatureGraphSummary> =
|
|
111
|
+
Summary[FeatureGraphSummaryTypeId]['states'][number]
|
|
112
|
+
export type FeatureSummaryName<Summary extends AnyFeatureGraphSummary> =
|
|
113
|
+
Summary[FeatureGraphSummaryTypeId]['name']
|
|
114
|
+
export type FeatureSummaryCompositionIdentity<Summary extends AnyFeatureGraphSummary> =
|
|
115
|
+
Summary[FeatureGraphSummaryTypeId]['compositionIdentity'][number]
|
|
116
|
+
export type FeatureSummaryOpenServices<Summary extends AnyFeatureGraphSummary> =
|
|
117
|
+
Summary[FeatureGraphSummaryTypeId]['openServices'][number]
|
|
118
|
+
export type FeatureSummarySupportOpenServices<Summary extends AnyFeatureGraphSummary> =
|
|
119
|
+
Summary[FeatureGraphSummaryTypeId]['supportOpenServices'][number]
|
|
120
|
+
export type FeatureSummaryUsesInput<Summary extends AnyFeatureGraphSummary> =
|
|
121
|
+
Summary[FeatureGraphSummaryTypeId]['usesInput'][number]
|
|
122
|
+
export type FeatureSummaryInput<Summary extends AnyFeatureGraphSummary> =
|
|
123
|
+
Summary[FeatureGraphSummaryTypeId]['input'][number]
|
|
124
|
+
|
|
125
|
+
export const CompositionChildSummaryTypeId: unique symbol = Symbol.for(
|
|
126
|
+
'reform/CompositionChildSummary',
|
|
127
|
+
)
|
|
128
|
+
export type CompositionChildSummaryTypeId = typeof CompositionChildSummaryTypeId
|
|
129
|
+
|
|
130
|
+
export interface CompositionChildSummary<out CompositionIdentity> extends SlotBindingSummary {
|
|
131
|
+
readonly [CompositionChildSummaryTypeId]: {
|
|
132
|
+
readonly compositionIdentity: CompositionIdentity
|
|
133
|
+
}
|
|
134
|
+
}
|
|
@@ -1,21 +1,10 @@
|
|
|
1
|
-
import {
|
|
2
|
-
Cause,
|
|
3
|
-
Chunk,
|
|
4
|
-
Effect,
|
|
5
|
-
MutableRef,
|
|
6
|
-
Option,
|
|
7
|
-
type ParseResult,
|
|
8
|
-
Queue,
|
|
9
|
-
Runtime,
|
|
10
|
-
type Scope,
|
|
11
|
-
Stream,
|
|
12
|
-
} from 'effect'
|
|
13
|
-
import { type AnyAsyncData } from '../calc/asyncData'
|
|
1
|
+
import { Cause, Chunk, Effect, MutableRef, Option, Queue, Runtime, type Scope, Stream } from 'effect'
|
|
14
2
|
import { empty, type QueryState } from '../calc/queryState'
|
|
15
3
|
import { Queries, type QueryHandle } from '../runtime/queries'
|
|
16
4
|
import { reuse } from './reuse'
|
|
17
5
|
import { resolveInstrumentation } from '../runtime/instrumentation'
|
|
18
6
|
import { resolveQueryStore } from './queryStore'
|
|
7
|
+
import { hydratePersistedQuery } from './queryDriverHydrate'
|
|
19
8
|
import {
|
|
20
9
|
incrementGeneration,
|
|
21
10
|
makeQueryProjection,
|
|
@@ -23,62 +12,24 @@ import {
|
|
|
23
12
|
settleQueryError,
|
|
24
13
|
withReaderTracking,
|
|
25
14
|
} from './queryDriverStore'
|
|
15
|
+
import type { QueryDriver, QueryDriverOptions } from './queryDriverTypes'
|
|
26
16
|
import { resolveScheduler } from './scheduler'
|
|
27
|
-
import {
|
|
28
|
-
|
|
29
|
-
type InputStores,
|
|
30
|
-
type InvalidateBy,
|
|
31
|
-
sameKey,
|
|
32
|
-
wireSources,
|
|
33
|
-
} from './sources'
|
|
34
|
-
import { makeStore, type Store } from './store'
|
|
17
|
+
import { type InputsObject, type InputStores, sameKey, wireSources } from './sources'
|
|
18
|
+
import { makeStore } from './store'
|
|
35
19
|
import { type AnySource } from '../state/token'
|
|
36
20
|
|
|
37
21
|
export {
|
|
38
22
|
bumpRevision,
|
|
39
23
|
gatedFlag,
|
|
40
24
|
type GatedOf,
|
|
25
|
+
type QueryCodec,
|
|
26
|
+
type QueryDriver,
|
|
27
|
+
type QueryDriverOptions,
|
|
41
28
|
Revision,
|
|
42
29
|
RevisionSchema,
|
|
43
30
|
revisionZero,
|
|
44
31
|
} from './queryDriverTypes'
|
|
45
32
|
|
|
46
|
-
// Keep schema invariance behind a covariant persistence boundary.
|
|
47
|
-
export interface QueryCodec<A> {
|
|
48
|
-
readonly encode: (value: A) => Effect.Effect<unknown, ParseResult.ParseError, never>
|
|
49
|
-
readonly decode: (input: unknown) => Effect.Effect<A, ParseResult.ParseError, never>
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
export interface QueryDriverOptions<Inputs extends ReadonlyArray<AnySource>, A, E, R> {
|
|
53
|
-
readonly name: string
|
|
54
|
-
readonly label: string
|
|
55
|
-
readonly gated: boolean
|
|
56
|
-
readonly inputs: Inputs
|
|
57
|
-
readonly query: (inputs: InputsObject<Inputs>) => Effect.Effect<A, E, R>
|
|
58
|
-
readonly invalidateBy?: InvalidateBy<Inputs> | undefined
|
|
59
|
-
readonly disabled?: ((inputs: InputsObject<Inputs>) => boolean) | undefined
|
|
60
|
-
readonly coalesce?: 'switch' | 'trailing' | undefined
|
|
61
|
-
readonly reuse?: boolean | undefined
|
|
62
|
-
readonly extraKey?:
|
|
63
|
-
| {
|
|
64
|
-
readonly read: () => unknown
|
|
65
|
-
readonly subscribe: (listener: () => void) => () => void
|
|
66
|
-
}
|
|
67
|
-
| undefined
|
|
68
|
-
readonly persist?:
|
|
69
|
-
| {
|
|
70
|
-
readonly key: string | ((inputs: InputsObject<Inputs>) => string)
|
|
71
|
-
readonly codec: QueryCodec<A>
|
|
72
|
-
}
|
|
73
|
-
| undefined
|
|
74
|
-
readonly onSettled?: ((generation: number) => void) | undefined
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
export interface QueryDriver<A, E> {
|
|
78
|
-
readonly store: Store<AnyAsyncData<A, E>>
|
|
79
|
-
readonly requested: () => number
|
|
80
|
-
}
|
|
81
|
-
|
|
82
33
|
// oxlint-disable-next-line reform-rules/prefer-effect-fn -- generic export: Effect.fn's inferred type isn't portable under isolatedDeclarations
|
|
83
34
|
export const makeQueryDriver = <Inputs extends ReadonlyArray<AnySource>, A, E, R>(
|
|
84
35
|
options: QueryDriverOptions<Inputs, A, E, R>,
|
|
@@ -268,42 +219,22 @@ export const makeQueryDriver = <Inputs extends ReadonlyArray<AnySource>, A, E, R
|
|
|
268
219
|
yield* Effect.addFinalizer(() => Effect.sync(unsubscribe))
|
|
269
220
|
|
|
270
221
|
// Fork hydration so an asynchronous store never blocks layer acquisition.
|
|
271
|
-
// Epoch, key, and settled-state guards prevent a late cache from replacing newer truth.
|
|
272
222
|
if (persist !== undefined && !disabledNow()) {
|
|
273
223
|
const hydrateArgs = sources.snapshot()
|
|
274
224
|
const hydrateSourceKey = keyOf(hydrateArgs)
|
|
275
225
|
const hydrateSourceEpoch = MutableRef.get(sourceEpoch)
|
|
276
|
-
const
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
hydrateSourceEpoch !== MutableRef.get(sourceEpoch) ||
|
|
289
|
-
!sameKey(hydrateSourceKey, keyOf(sources.snapshot())) ||
|
|
290
|
-
Option.isSome(state.data) ||
|
|
291
|
-
Option.isSome(state.error)
|
|
292
|
-
) {
|
|
293
|
-
return
|
|
294
|
-
}
|
|
295
|
-
MutableRef.update(staleAtGeneration, (current) =>
|
|
296
|
-
Math.max(current, hydrateStaleAtGeneration),
|
|
297
|
-
)
|
|
298
|
-
stateStore.set({ ...state, data: Option.some(decoded), isStale: true })
|
|
299
|
-
}),
|
|
300
|
-
),
|
|
301
|
-
),
|
|
302
|
-
}),
|
|
303
|
-
),
|
|
304
|
-
// Corrupt/incompatible cache: ignore and fetch fresh.
|
|
305
|
-
Effect.ignore,
|
|
306
|
-
)
|
|
226
|
+
const hydrate = hydratePersistedQuery({
|
|
227
|
+
queryStore,
|
|
228
|
+
cacheKey: persistKey(persist.key, hydrateArgs),
|
|
229
|
+
codec: persist.codec,
|
|
230
|
+
stateStore,
|
|
231
|
+
staleAtGeneration,
|
|
232
|
+
capturedGeneration: MutableRef.get(generation),
|
|
233
|
+
isSuperseded: () =>
|
|
234
|
+
disabledNow() ||
|
|
235
|
+
hydrateSourceEpoch !== MutableRef.get(sourceEpoch) ||
|
|
236
|
+
!sameKey(hydrateSourceKey, keyOf(sources.snapshot())),
|
|
237
|
+
})
|
|
307
238
|
const runtime = yield* Effect.runtime<never>()
|
|
308
239
|
const scope = yield* Effect.scope
|
|
309
240
|
Runtime.runFork(runtime)(hydrate, { immediate: true, scope })
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { Effect, MutableRef, Option } from 'effect'
|
|
2
|
+
import type { QueryState } from '../calc/queryState'
|
|
3
|
+
import type { QueryStoreApi } from './queryStore'
|
|
4
|
+
import type { QueryCodec } from './queryDriverTypes'
|
|
5
|
+
import type { Store } from './store'
|
|
6
|
+
|
|
7
|
+
interface HydratePersistedOptions<A, E> {
|
|
8
|
+
readonly queryStore: QueryStoreApi
|
|
9
|
+
readonly cacheKey: string
|
|
10
|
+
readonly codec: QueryCodec<A>
|
|
11
|
+
readonly stateStore: Store<QueryState<A, E>>
|
|
12
|
+
readonly staleAtGeneration: MutableRef.MutableRef<number>
|
|
13
|
+
readonly capturedGeneration: number
|
|
14
|
+
// True once the sources moved past the snapshot this hydration was captured for.
|
|
15
|
+
readonly isSuperseded: () => boolean
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const paint = <A, E>(options: HydratePersistedOptions<A, E>, decoded: A): void => {
|
|
19
|
+
const state = options.stateStore.get()
|
|
20
|
+
if (options.isSuperseded() || Option.isSome(state.data) || Option.isSome(state.error)) {
|
|
21
|
+
return
|
|
22
|
+
}
|
|
23
|
+
MutableRef.update(options.staleAtGeneration, (current) =>
|
|
24
|
+
Math.max(current, options.capturedGeneration),
|
|
25
|
+
)
|
|
26
|
+
options.stateStore.set({ ...state, data: Option.some(decoded), isStale: true })
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Epoch, key, and settled-state guards prevent a late cache from replacing newer truth.
|
|
30
|
+
export const hydratePersistedQuery = <A, E>(
|
|
31
|
+
options: HydratePersistedOptions<A, E>,
|
|
32
|
+
): Effect.Effect<void> =>
|
|
33
|
+
options.queryStore.get(options.cacheKey).pipe(
|
|
34
|
+
Effect.flatMap((cached) =>
|
|
35
|
+
Option.match(cached, {
|
|
36
|
+
onNone: () => Effect.void,
|
|
37
|
+
onSome: (raw) =>
|
|
38
|
+
options.codec
|
|
39
|
+
.decode(raw)
|
|
40
|
+
.pipe(Effect.tap((decoded) => Effect.sync(() => paint(options, decoded)))),
|
|
41
|
+
}),
|
|
42
|
+
),
|
|
43
|
+
// Corrupt/incompatible cache: ignore and fetch fresh.
|
|
44
|
+
Effect.ignore,
|
|
45
|
+
)
|
|
@@ -1,9 +1,8 @@
|
|
|
1
|
-
import { Brand, Effect, Schema } from 'effect'
|
|
1
|
+
import { Brand, Effect, type ParseResult, Schema } from 'effect'
|
|
2
2
|
import type { AnyAsyncData } from '../calc/asyncData'
|
|
3
3
|
import type { InputsObject, InvalidateBy } from './sources'
|
|
4
4
|
import type { Store } from './store'
|
|
5
5
|
import type { AnySource } from '../state/token'
|
|
6
|
-
import type { AnySchema } from './variance'
|
|
7
6
|
|
|
8
7
|
export type GatedOf<AlwaysOn extends boolean> = AlwaysOn extends true ? false : true
|
|
9
8
|
|
|
@@ -21,35 +20,34 @@ export const RevisionSchema: Schema.Schema<Revision, number> = Schema.Number.pip
|
|
|
21
20
|
export const revisionZero: Revision = Revision(0)
|
|
22
21
|
export const bumpRevision = (revision: Revision): Revision => Revision(revision + 1)
|
|
23
22
|
|
|
23
|
+
// Keep schema invariance behind a covariant persistence boundary.
|
|
24
|
+
export interface QueryCodec<A> {
|
|
25
|
+
readonly encode: (value: A) => Effect.Effect<unknown, ParseResult.ParseError, never>
|
|
26
|
+
readonly decode: (input: unknown) => Effect.Effect<A, ParseResult.ParseError, never>
|
|
27
|
+
}
|
|
28
|
+
|
|
24
29
|
export interface QueryDriverOptions<Inputs extends ReadonlyArray<AnySource>, A, E, R> {
|
|
25
30
|
readonly name: string
|
|
26
31
|
readonly label: string
|
|
27
32
|
readonly gated: boolean
|
|
28
33
|
readonly inputs: Inputs
|
|
29
34
|
readonly query: (inputs: InputsObject<Inputs>) => Effect.Effect<A, E, R>
|
|
30
|
-
// 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
|
|
31
35
|
readonly invalidateBy?: InvalidateBy<Inputs> | undefined
|
|
32
|
-
// 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
|
|
33
36
|
readonly disabled?: ((inputs: InputsObject<Inputs>) => boolean) | undefined
|
|
34
|
-
// 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
|
|
35
37
|
readonly coalesce?: 'switch' | 'trailing' | undefined
|
|
36
|
-
// 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
|
|
37
38
|
readonly reuse?: boolean | undefined
|
|
38
|
-
// 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
|
|
39
39
|
readonly extraKey?:
|
|
40
40
|
| {
|
|
41
41
|
readonly read: () => unknown
|
|
42
42
|
readonly subscribe: (listener: () => void) => () => void
|
|
43
43
|
}
|
|
44
44
|
| undefined
|
|
45
|
-
// 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
|
|
46
45
|
readonly persist?:
|
|
47
46
|
| {
|
|
48
47
|
readonly key: string | ((inputs: InputsObject<Inputs>) => string)
|
|
49
|
-
readonly
|
|
48
|
+
readonly codec: QueryCodec<A>
|
|
50
49
|
}
|
|
51
50
|
| undefined
|
|
52
|
-
// 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
|
|
53
51
|
readonly onSettled?: ((generation: number) => void) | undefined
|
|
54
52
|
}
|
|
55
53
|
|
|
@@ -1,7 +1,14 @@
|
|
|
1
1
|
import { expect, test } from 'vitest'
|
|
2
2
|
import { makeStore } from './store'
|
|
3
3
|
|
|
4
|
-
|
|
4
|
+
// Notifications land on the microtask queue — poll turns of it, never wall-clock.
|
|
5
|
+
const until = async (predicate: () => boolean, rounds = 1000): Promise<void> => {
|
|
6
|
+
if (predicate() || rounds <= 0) {
|
|
7
|
+
return
|
|
8
|
+
}
|
|
9
|
+
await new Promise<void>((resolve) => queueMicrotask(resolve))
|
|
10
|
+
return until(predicate, rounds - 1)
|
|
11
|
+
}
|
|
5
12
|
|
|
6
13
|
const counter = () => ({ n: 0 })
|
|
7
14
|
|
|
@@ -14,19 +21,26 @@ test('subscribe observes changes; getSnapshot is always current', async () => {
|
|
|
14
21
|
|
|
15
22
|
store.set(1)
|
|
16
23
|
expect(store.getSnapshot()).toBe(1)
|
|
17
|
-
await
|
|
24
|
+
await until(() => notifications.n > 0)
|
|
18
25
|
expect(notifications.n).toBe(1)
|
|
19
26
|
})
|
|
20
27
|
|
|
21
28
|
test('a no-op write notifies no one', async () => {
|
|
22
29
|
const store = makeStore('same')
|
|
30
|
+
const witness = makeStore(0)
|
|
23
31
|
const notifications = counter()
|
|
32
|
+
const witnessWakes = counter()
|
|
24
33
|
store.subscribe(() => {
|
|
25
34
|
notifications.n++
|
|
26
35
|
})
|
|
36
|
+
witness.subscribe(() => {
|
|
37
|
+
witnessWakes.n++
|
|
38
|
+
})
|
|
27
39
|
|
|
28
40
|
store.set('same')
|
|
29
|
-
|
|
41
|
+
// Both stores share the scheduler, so the witness flush drains anything the no-op could have queued.
|
|
42
|
+
witness.set(1)
|
|
43
|
+
await until(() => witnessWakes.n > 0)
|
|
30
44
|
expect(notifications.n).toBe(0)
|
|
31
45
|
expect(store.getSnapshot()).toBe('same')
|
|
32
46
|
})
|
|
@@ -42,7 +56,8 @@ test('a batch of writes coalesces into a single notification per microtask', asy
|
|
|
42
56
|
store.set(2)
|
|
43
57
|
store.set(3)
|
|
44
58
|
expect(store.getSnapshot()).toBe(3)
|
|
45
|
-
|
|
59
|
+
// The drain is synchronous: the first wake already carries every write the batch would have fired.
|
|
60
|
+
await until(() => notifications.n > 0)
|
|
46
61
|
expect(notifications.n).toBe(1)
|
|
47
62
|
})
|
|
48
63
|
|
|
@@ -59,20 +74,26 @@ test('stores are independent — one slice does not wake another', async () => {
|
|
|
59
74
|
})
|
|
60
75
|
|
|
61
76
|
a.set(1)
|
|
62
|
-
await
|
|
77
|
+
await until(() => aWakes.n > 0)
|
|
63
78
|
expect(aWakes.n).toBe(1)
|
|
64
79
|
expect(bWakes.n).toBe(0)
|
|
65
80
|
})
|
|
66
81
|
|
|
67
82
|
test('unsubscribe stops notifications', async () => {
|
|
68
83
|
const store = makeStore(0)
|
|
84
|
+
const witness = makeStore(0)
|
|
69
85
|
const notifications = counter()
|
|
86
|
+
const witnessWakes = counter()
|
|
70
87
|
const unsubscribe = store.subscribe(() => {
|
|
71
88
|
notifications.n++
|
|
72
89
|
})
|
|
90
|
+
witness.subscribe(() => {
|
|
91
|
+
witnessWakes.n++
|
|
92
|
+
})
|
|
73
93
|
|
|
74
94
|
unsubscribe()
|
|
75
95
|
store.set(1)
|
|
76
|
-
|
|
96
|
+
witness.set(1)
|
|
97
|
+
await until(() => witnessWakes.n > 0)
|
|
77
98
|
expect(notifications.n).toBe(0)
|
|
78
99
|
})
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import { Equal, MutableRef, Option } from 'effect'
|
|
2
|
+
import { inspectable } from '../internal/inspect'
|
|
3
|
+
import type { Scheduler } from '../internal/scheduler'
|
|
4
|
+
import type { Store } from '../internal/store'
|
|
5
|
+
import type { PendingIntent } from './remoteStateTypes'
|
|
6
|
+
|
|
7
|
+
export interface PendingNode<I> {
|
|
8
|
+
readonly entry: MutableRef.MutableRef<PendingIntent<I>>
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
// Duplicate opIds retain every node so indexed updates preserve the old map semantics.
|
|
12
|
+
export interface PendingOpEntry<I> {
|
|
13
|
+
readonly nodes: Set<PendingNode<I>>
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface PendingQueue<I> {
|
|
17
|
+
readonly store: Store<ReadonlyArray<PendingIntent<I>>>
|
|
18
|
+
readonly revision: () => number
|
|
19
|
+
readonly fold: <B>(initial: B, combine: (accumulator: B, entry: PendingIntent<I>) => B) => B
|
|
20
|
+
readonly append: (entry: PendingIntent<I>) => void
|
|
21
|
+
readonly ack: (opId: string) => void
|
|
22
|
+
readonly settle: (opId: string) => void
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export const makePendingQueue = <I>(scheduler: Scheduler): PendingQueue<I> => {
|
|
26
|
+
// Set insertion order preserves dispatch/apply order while allowing O(1) updates.
|
|
27
|
+
const ordered = new Set<PendingNode<I>>()
|
|
28
|
+
const byOpId = new Map<string, PendingOpEntry<I>>()
|
|
29
|
+
const listeners = new Set<() => void>()
|
|
30
|
+
const revision = MutableRef.make(0)
|
|
31
|
+
const initial: ReadonlyArray<PendingIntent<I>> = []
|
|
32
|
+
const snapshot = MutableRef.make<Option.Option<ReadonlyArray<PendingIntent<I>>>>(
|
|
33
|
+
Option.some(initial),
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
const materialize = (): ReadonlyArray<PendingIntent<I>> => {
|
|
37
|
+
const cached = MutableRef.get(snapshot)
|
|
38
|
+
if (Option.isSome(cached)) {
|
|
39
|
+
return cached.value
|
|
40
|
+
}
|
|
41
|
+
const current = Array.from(ordered, (node) => MutableRef.get(node.entry))
|
|
42
|
+
MutableRef.set(snapshot, Option.some(current))
|
|
43
|
+
return current
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const announce = (next: Option.Option<ReadonlyArray<PendingIntent<I>>>): void => {
|
|
47
|
+
MutableRef.set(snapshot, next)
|
|
48
|
+
MutableRef.update(revision, (current) => current + 1)
|
|
49
|
+
scheduler.schedule(listeners)
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const link = (entry: PendingIntent<I>): void => {
|
|
53
|
+
const node: PendingNode<I> = {
|
|
54
|
+
entry: MutableRef.make(entry),
|
|
55
|
+
}
|
|
56
|
+
ordered.add(node)
|
|
57
|
+
const indexed = byOpId.get(entry.opId)
|
|
58
|
+
if (indexed === undefined) {
|
|
59
|
+
byOpId.set(entry.opId, { nodes: new Set([node]) })
|
|
60
|
+
} else {
|
|
61
|
+
indexed.nodes.add(node)
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const clear = (): void => {
|
|
66
|
+
ordered.clear()
|
|
67
|
+
byOpId.clear()
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const fold = <B>(initialValue: B, combine: (accumulator: B, entry: PendingIntent<I>) => B): B => {
|
|
71
|
+
const folded = MutableRef.make(initialValue)
|
|
72
|
+
ordered.forEach((node) => {
|
|
73
|
+
MutableRef.update(folded, (accumulator) => combine(accumulator, MutableRef.get(node.entry)))
|
|
74
|
+
})
|
|
75
|
+
return MutableRef.get(folded)
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const append = (entry: PendingIntent<I>): void => {
|
|
79
|
+
link(entry)
|
|
80
|
+
announce(Option.none())
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const ack = (opId: string): void => {
|
|
84
|
+
const indexed = byOpId.get(opId)
|
|
85
|
+
if (indexed === undefined) {
|
|
86
|
+
return
|
|
87
|
+
}
|
|
88
|
+
const stillSending = MutableRef.make(false)
|
|
89
|
+
indexed.nodes.forEach((node) => {
|
|
90
|
+
if (MutableRef.get(node.entry).status === 'sending') {
|
|
91
|
+
MutableRef.set(stillSending, true)
|
|
92
|
+
}
|
|
93
|
+
})
|
|
94
|
+
if (!MutableRef.get(stillSending)) {
|
|
95
|
+
return
|
|
96
|
+
}
|
|
97
|
+
// Rebuild every duplicate once one is sending, matching the former array map.
|
|
98
|
+
indexed.nodes.forEach((node) => {
|
|
99
|
+
MutableRef.update(node.entry, (entry) => ({ ...entry, status: 'confirmed' as const }))
|
|
100
|
+
})
|
|
101
|
+
announce(Option.none())
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const settle = (opId: string): void => {
|
|
105
|
+
const indexed = byOpId.get(opId)
|
|
106
|
+
if (indexed === undefined) {
|
|
107
|
+
return
|
|
108
|
+
}
|
|
109
|
+
indexed.nodes.forEach((node) => {
|
|
110
|
+
ordered.delete(node)
|
|
111
|
+
})
|
|
112
|
+
byOpId.delete(opId)
|
|
113
|
+
announce(Option.none())
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const store: Store<ReadonlyArray<PendingIntent<I>>> = {
|
|
117
|
+
get: materialize,
|
|
118
|
+
getSnapshot: materialize,
|
|
119
|
+
getVersion: () => MutableRef.get(revision),
|
|
120
|
+
set: (next) => {
|
|
121
|
+
if (Equal.equals(materialize(), next)) {
|
|
122
|
+
return
|
|
123
|
+
}
|
|
124
|
+
clear()
|
|
125
|
+
next.forEach(link)
|
|
126
|
+
announce(Option.some(next))
|
|
127
|
+
},
|
|
128
|
+
subscribe: (listener) => {
|
|
129
|
+
listeners.add(listener)
|
|
130
|
+
return () => {
|
|
131
|
+
listeners.delete(listener)
|
|
132
|
+
}
|
|
133
|
+
},
|
|
134
|
+
...inspectable(() => ({ _id: 'reform/Store', value: materialize() })),
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
return {
|
|
138
|
+
store,
|
|
139
|
+
revision: () => MutableRef.get(revision),
|
|
140
|
+
fold,
|
|
141
|
+
append,
|
|
142
|
+
ack,
|
|
143
|
+
settle,
|
|
144
|
+
}
|
|
145
|
+
}
|