@playfast/reform 1.1.1 → 1.2.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 +85 -55
- package/package.json +17 -17
- package/src/boundary/boundary.test.ts +62 -16
- package/src/boundary/boundary.ts +141 -32
- package/src/calc/asyncCalc.invalidate.test.ts +516 -18
- package/src/calc/asyncCalc.test.ts +207 -175
- package/src/calc/asyncCalc.ts +168 -39
- package/src/calc/calc.test.ts +3 -5
- package/src/calc/calc.ts +44 -18
- package/src/calc/calcFamily.test.ts +80 -22
- package/src/calc/calcFamily.ts +56 -25
- package/src/calc/compose.ts +1 -14
- package/src/channel/channel.ts +128 -11
- package/src/compose/composition.test.ts +19 -0
- package/src/compose/composition.ts +346 -62
- package/src/compose/props.ts +13 -6
- package/src/compose/provide.ts +187 -46
- package/src/compose/slot.ts +156 -19
- package/src/compose/structure.test.ts +6 -3
- package/src/compose/structure.ts +106 -13
- package/src/compose/ui.test.ts +19 -3
- package/src/compose/ui.ts +147 -54
- package/src/compose/ui.typecheck.ts +40 -4
- package/src/definition/definition.ts +22 -9
- package/src/event/event.fromSource.test.ts +172 -0
- package/src/event/event.test.ts +10 -0
- package/src/event/event.ts +102 -18
- package/src/feature/feature.mount.test.ts +123 -56
- package/src/feature/feature.test.ts +67 -63
- package/src/feature/feature.ts +1317 -197
- package/src/feature/feature.typecheck.ts +253 -61
- package/src/graph/closure.ts +403 -0
- package/src/index.ts +171 -245
- package/src/internal/bucketCache.ts +39 -0
- package/src/internal/capture.ts +9 -4
- package/src/internal/env.ts +1 -3
- package/src/internal/errors.ts +21 -10
- package/src/internal/queryDriver.ts +120 -15
- package/src/internal/queryDriverStore.ts +8 -5
- package/src/internal/queryDriverTypes.ts +3 -1
- package/src/internal/reuse.test.ts +10 -3
- package/src/internal/reuse.ts +5 -2
- package/src/internal/scheduler.ts +4 -1
- package/src/internal/sources.ts +25 -11
- package/src/internal/track.ts +3 -2
- package/src/internal.ts +222 -0
- package/src/namespace/namespace.ts +46 -25
- package/src/procedure/procedure.ts +38 -11
- package/src/reducer/reducer.ts +78 -34
- package/src/remote/remoteState.test.ts +515 -339
- package/src/remote/remoteState.ts +572 -107
- package/src/remote/remoteState.typecheck.ts +47 -23
- package/src/runtime/appRuntime.activation.test.ts +100 -0
- package/src/runtime/appRuntime.test.ts +157 -105
- package/src/runtime/appRuntime.ts +394 -33
- package/src/runtime/eventBudget.test.ts +1 -4
- package/src/runtime/eventBudget.ts +9 -8
- package/src/runtime/hardening.test.ts +4 -9
- package/src/runtime/instrumentation.test.ts +174 -192
- package/src/runtime/instrumentation.ts +6 -11
- package/src/runtime/loop.test.ts +36 -0
- package/src/runtime/loop.ts +69 -40
- package/src/scene/featureScene.test.ts +4 -2
- package/src/scene/scene.ts +234 -64
- package/src/scene/seedScene.test.ts +69 -86
- package/src/state/state.nominal.typecheck.ts +68 -0
- package/src/state/state.test.ts +17 -0
- package/src/state/state.ts +79 -26
- package/src/state/stateFamily.test.ts +14 -0
- package/src/state/stateFamily.ts +55 -22
- package/src/state/stateGroup.ts +53 -17
- package/src/state/token.ts +40 -10
- package/src/synced/syncedStore.ts +46 -23
- package/src/testkit/flight.testkit.ts +75 -0
- package/src/wire/tree.test.ts +8 -4
- package/src/wire/triggers.test.ts +6 -2
- package/src/wire/triggers.ts +14 -10
- package/src/calc/asyncCalcDefinitions.ts +0 -48
- package/src/channel/procedureRegistry.ts +0 -90
- package/src/feature/featureBinding.ts +0 -48
- package/src/internal/ctx.ts +0 -9
- package/src/remote/remoteStateDefinition.ts +0 -135
- package/src/remote/remoteStateLayers.ts +0 -118
- package/src/remote/remoteStateLiveTypes.ts +0 -59
- package/src/remote/remoteStateSend.ts +0 -64
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { Schema } from 'effect'
|
|
1
2
|
import * as AsyncCalc from '../calc/asyncCalc'
|
|
2
3
|
import * as Calc from '../calc/calc'
|
|
3
4
|
import * as CalcFamily from '../calc/calcFamily'
|
|
@@ -8,65 +9,85 @@ import * as RemoteState from '../remote/remoteState'
|
|
|
8
9
|
import * as State from '../state/state'
|
|
9
10
|
import * as StateFamily from '../state/stateFamily'
|
|
10
11
|
import type { AnySource } from '../state/token'
|
|
11
|
-
import type { AnySchema } from '../internal/variance'
|
|
12
12
|
|
|
13
13
|
export interface FeatureNamespace<Prefix extends string> {
|
|
14
14
|
readonly prefix: Prefix
|
|
15
|
-
readonly Event: <const N extends string, P>(
|
|
15
|
+
readonly Event: <const N extends string, P, Encoded>(
|
|
16
16
|
name: N,
|
|
17
|
-
schema:
|
|
17
|
+
schema: Schema.Schema<P, Encoded, never>,
|
|
18
18
|
) => Event.EventClass<`${Prefix}.${N}`, P>
|
|
19
19
|
readonly Channel: (name: string, config: Channel.ChannelConfig) => Channel.ChannelClass
|
|
20
|
-
readonly State: <const N extends string,
|
|
20
|
+
readonly State: <const N extends string, StateSchema extends Schema.Schema.AnyNoContext>(
|
|
21
21
|
name: N,
|
|
22
|
-
schema:
|
|
22
|
+
schema: StateSchema,
|
|
23
23
|
options?: State.StateOptions,
|
|
24
|
-
) => State.
|
|
25
|
-
readonly StateFamily: <const N extends string, K, V>(
|
|
24
|
+
) => State.StateDefinition<`${Prefix}.${N}`, Schema.Schema.Type<StateSchema>, StateSchema>
|
|
25
|
+
readonly StateFamily: <const N extends string, K, KeyEncoded, V, ValueEncoded>(
|
|
26
26
|
name: N,
|
|
27
|
-
key:
|
|
28
|
-
valueSchema:
|
|
27
|
+
key: Schema.Schema<K, KeyEncoded, never>,
|
|
28
|
+
valueSchema: Schema.Schema<V, ValueEncoded, never>,
|
|
29
29
|
options?: StateFamily.StateFamilyMakeOptionsExternalApi,
|
|
30
30
|
) => StateFamily.StateFamilyClass<`${Prefix}.${N}`, K, V>
|
|
31
|
-
readonly Calc: <
|
|
31
|
+
readonly Calc: <
|
|
32
|
+
const N extends string,
|
|
33
|
+
const Inputs extends ReadonlyArray<AnySource>,
|
|
34
|
+
OutputSchema extends Schema.Schema.AnyNoContext,
|
|
35
|
+
>(
|
|
32
36
|
name: N,
|
|
33
|
-
config: Calc.CalcConfig<Inputs,
|
|
34
|
-
) => Calc.CalcClass<`${Prefix}.${N}`, Inputs,
|
|
37
|
+
config: Calc.CalcConfig<Inputs, OutputSchema>,
|
|
38
|
+
) => Calc.CalcClass<`${Prefix}.${N}`, Inputs, Schema.Schema.Type<OutputSchema>>
|
|
35
39
|
readonly CalcFamily: <
|
|
36
40
|
const N extends string,
|
|
37
41
|
const Inputs extends ReadonlyArray<AnySource>,
|
|
38
|
-
|
|
39
|
-
|
|
42
|
+
KeySchema extends Schema.Schema.AnyNoContext,
|
|
43
|
+
OutputSchema extends Schema.Schema.AnyNoContext,
|
|
40
44
|
>(
|
|
41
45
|
name: N,
|
|
42
|
-
config: CalcFamily.CalcFamilyConfig<Inputs,
|
|
43
|
-
) => CalcFamily.CalcFamilyClass
|
|
46
|
+
config: CalcFamily.CalcFamilyConfig<Inputs, KeySchema, OutputSchema>,
|
|
47
|
+
) => CalcFamily.CalcFamilyClass<
|
|
48
|
+
`${Prefix}.${N}`,
|
|
49
|
+
Inputs,
|
|
50
|
+
Schema.Schema.Type<KeySchema>,
|
|
51
|
+
Schema.Schema.Type<OutputSchema>
|
|
52
|
+
>
|
|
44
53
|
readonly AsyncCalc: <
|
|
45
54
|
const N extends string,
|
|
46
55
|
const Inputs extends ReadonlyArray<AnySource>,
|
|
47
|
-
|
|
48
|
-
|
|
56
|
+
OutputSchema extends Schema.Schema.AnyNoContext,
|
|
57
|
+
ErrorSchema extends Schema.Schema.AnyNoContext | undefined = undefined,
|
|
49
58
|
const AlwaysOn extends boolean = false,
|
|
50
59
|
>(
|
|
51
60
|
name: N,
|
|
52
|
-
config: AsyncCalc.AsyncCalcConfig<Inputs,
|
|
53
|
-
) => AsyncCalc.AsyncCalcClass
|
|
61
|
+
config: AsyncCalc.AsyncCalcConfig<Inputs, OutputSchema, ErrorSchema, AlwaysOn>,
|
|
62
|
+
) => AsyncCalc.AsyncCalcClass<
|
|
63
|
+
`${Prefix}.${N}`,
|
|
64
|
+
Inputs,
|
|
65
|
+
Schema.Schema.Type<OutputSchema>,
|
|
66
|
+
ErrorSchema extends Schema.Schema.AnyNoContext ? Schema.Schema.Type<ErrorSchema> : never,
|
|
67
|
+
GatedOf<AlwaysOn>
|
|
68
|
+
>
|
|
54
69
|
readonly RemoteState: <
|
|
55
70
|
const N extends string,
|
|
56
71
|
const Inputs extends ReadonlyArray<AnySource>,
|
|
57
72
|
const Intents extends ReadonlyArray<Event.AnyEvent>,
|
|
58
|
-
|
|
59
|
-
|
|
73
|
+
OutputSchema extends Schema.Schema.AnyNoContext,
|
|
74
|
+
ErrorSchema extends Schema.Schema.AnyNoContext | undefined = undefined,
|
|
60
75
|
const AlwaysOn extends boolean = false,
|
|
61
76
|
>(
|
|
62
77
|
name: N,
|
|
63
|
-
config: RemoteState.RemoteStateConfigExternalApi<
|
|
78
|
+
config: RemoteState.RemoteStateConfigExternalApi<
|
|
79
|
+
Inputs,
|
|
80
|
+
Intents,
|
|
81
|
+
OutputSchema,
|
|
82
|
+
ErrorSchema,
|
|
83
|
+
AlwaysOn
|
|
84
|
+
>,
|
|
64
85
|
) => RemoteState.RemoteStateClass<
|
|
65
86
|
`${Prefix}.${N}`,
|
|
66
87
|
Inputs,
|
|
67
88
|
Intents,
|
|
68
|
-
|
|
69
|
-
|
|
89
|
+
Schema.Schema.Type<OutputSchema>,
|
|
90
|
+
ErrorSchema extends Schema.Schema.AnyNoContext ? Schema.Schema.Type<ErrorSchema> : never,
|
|
70
91
|
GatedOf<AlwaysOn>
|
|
71
92
|
>
|
|
72
93
|
}
|
|
@@ -3,7 +3,6 @@ import type { YieldWrap } from 'effect/Utils'
|
|
|
3
3
|
import { type ChannelClass, type ProcedureEntry, Procedures } from '../channel/channel'
|
|
4
4
|
import { type Manifest, definitionClass } from '../definition/definition'
|
|
5
5
|
import { type AnyEvent, type EventType } from '../event/event'
|
|
6
|
-
import { type Ctx } from '../internal/ctx'
|
|
7
6
|
import { Bus, narrowHandled } from '../runtime/bus'
|
|
8
7
|
import type { AnyValue } from '../internal/variance'
|
|
9
8
|
|
|
@@ -11,29 +10,52 @@ export interface ProcedureManifest extends Manifest {
|
|
|
11
10
|
readonly kind: 'Procedure'
|
|
12
11
|
}
|
|
13
12
|
|
|
14
|
-
export interface
|
|
13
|
+
export interface ProcedureReflection<
|
|
14
|
+
out Events extends ReadonlyArray<AnyEvent> = ReadonlyArray<AnyEvent>,
|
|
15
|
+
> {
|
|
15
16
|
new (): {}
|
|
16
17
|
readonly manifest: ProcedureManifest
|
|
17
18
|
readonly events: Events
|
|
18
19
|
readonly channel: ChannelClass
|
|
19
20
|
}
|
|
20
21
|
|
|
22
|
+
export type ProcedureCapture<Result> = <Events extends ReadonlyArray<AnyEvent>>(
|
|
23
|
+
procedure: ProcedureClass<Events>,
|
|
24
|
+
) => Result
|
|
25
|
+
|
|
26
|
+
export interface AnyProcedure extends ProcedureReflection {
|
|
27
|
+
readonly capture: <Result>(visit: ProcedureCapture<Result>) => Result
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface ProcedureClass<
|
|
31
|
+
Events extends ReadonlyArray<AnyEvent>,
|
|
32
|
+
> extends ProcedureReflection<Events> {
|
|
33
|
+
readonly capture: <Result>(visit: ProcedureCapture<Result>) => Result
|
|
34
|
+
}
|
|
35
|
+
|
|
21
36
|
export interface ProcedureConfig<Events extends ReadonlyArray<AnyEvent>> {
|
|
22
37
|
readonly events: Events
|
|
23
38
|
readonly channel: ChannelClass
|
|
24
39
|
}
|
|
25
40
|
|
|
26
|
-
export type AnyProcedure = ProcedureClass<ReadonlyArray<AnyEvent>>
|
|
27
|
-
|
|
28
41
|
export const make = <const Events extends ReadonlyArray<AnyEvent>>(
|
|
29
42
|
name: string,
|
|
30
43
|
config: ProcedureConfig<Events>,
|
|
31
|
-
): ProcedureClass<Events> =>
|
|
32
|
-
definitionClass<ProcedureClass<Events>>({
|
|
44
|
+
): ProcedureClass<Events> => {
|
|
45
|
+
const procedure: ProcedureClass<Events> = definitionClass<ProcedureClass<Events>>({
|
|
33
46
|
manifest: { kind: 'Procedure' as const, name } satisfies ProcedureManifest,
|
|
34
47
|
events: config.events,
|
|
35
48
|
channel: config.channel,
|
|
49
|
+
capture: <Result>(visit: ProcedureCapture<Result>): Result => visit(procedure),
|
|
36
50
|
})
|
|
51
|
+
return procedure
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
type GeneratorContext<Eff> = [Eff] extends [never]
|
|
55
|
+
? never
|
|
56
|
+
: [Eff] extends [YieldWrap<Effect.Effect<AnyValue, AnyValue, infer Context>>]
|
|
57
|
+
? Context
|
|
58
|
+
: never
|
|
37
59
|
|
|
38
60
|
export const live = <
|
|
39
61
|
Events extends ReadonlyArray<AnyEvent>,
|
|
@@ -42,7 +64,7 @@ export const live = <
|
|
|
42
64
|
>(
|
|
43
65
|
procedure: ProcedureClass<Events>,
|
|
44
66
|
body: (event: EventType<Events[number]>) => Generator<Eff, Done, never>,
|
|
45
|
-
): Layer.Layer<never, never, Procedures | Bus |
|
|
67
|
+
): Layer.Layer<never, never, Procedures | Bus | GeneratorContext<Eff>> => {
|
|
46
68
|
const name = procedure.manifest.name
|
|
47
69
|
const channelName = procedure.channel.manifest.name
|
|
48
70
|
const handles = new Set(procedure.events.map((event) => event.tag))
|
|
@@ -52,15 +74,20 @@ export const live = <
|
|
|
52
74
|
if (procedures.entries.some((entry) => entry.name === name)) {
|
|
53
75
|
yield* Effect.logWarning(`reform: duplicate procedure name '${name}' registered`)
|
|
54
76
|
}
|
|
55
|
-
const runtime = yield* Effect.runtime<Bus |
|
|
77
|
+
const runtime = yield* Effect.runtime<Bus | GeneratorContext<Eff>>()
|
|
56
78
|
const entry: ProcedureEntry = {
|
|
57
79
|
name,
|
|
58
80
|
channelName,
|
|
59
81
|
handles,
|
|
60
|
-
run: (event) =>
|
|
82
|
+
run: (event) =>
|
|
83
|
+
Effect.provide(
|
|
84
|
+
Effect.gen(() => body(narrowHandled(event))),
|
|
85
|
+
runtime,
|
|
86
|
+
),
|
|
61
87
|
}
|
|
62
|
-
yield* Effect.acquireRelease(
|
|
63
|
-
Effect.sync(() => procedures.
|
|
88
|
+
yield* Effect.acquireRelease(
|
|
89
|
+
Effect.sync(() => procedures.register(entry)),
|
|
90
|
+
() => Effect.sync(() => procedures.unregister(entry)),
|
|
64
91
|
)
|
|
65
92
|
}),
|
|
66
93
|
)
|
package/src/reducer/reducer.ts
CHANGED
|
@@ -2,18 +2,17 @@ import { Effect, Layer, Predicate } from 'effect'
|
|
|
2
2
|
import { type Manifest, definitionClass } from '../definition/definition'
|
|
3
3
|
import { AsyncReducer } from '../internal/errors'
|
|
4
4
|
import { type AnyEvent, type EventType } from '../event/event'
|
|
5
|
-
import type
|
|
6
|
-
import { type AnyState, type StateValue } from '../state/state'
|
|
5
|
+
import { type AnyState, type StateName, type StateStore, type StateValue } from '../state/state'
|
|
7
6
|
import {
|
|
8
7
|
type AnyFamily,
|
|
9
8
|
type FamilyKey,
|
|
10
|
-
type
|
|
9
|
+
type FamilyName,
|
|
11
10
|
type FamilyValue,
|
|
11
|
+
type StateFamilyStore,
|
|
12
12
|
Tombstone,
|
|
13
13
|
} from '../state/stateFamily'
|
|
14
14
|
import { narrowHandled, type Tagged } from '../runtime/bus'
|
|
15
15
|
import { type ReducerEntry, Reducers } from '../runtime/loop'
|
|
16
|
-
import type { AnyValue } from '../internal/variance'
|
|
17
16
|
|
|
18
17
|
export interface ReducerManifest extends Manifest {
|
|
19
18
|
readonly kind: 'Reducer'
|
|
@@ -44,6 +43,26 @@ export interface FamilyReducerClass<F extends AnyFamily, Events extends Readonly
|
|
|
44
43
|
readonly handles: ReadonlySet<string>
|
|
45
44
|
}
|
|
46
45
|
|
|
46
|
+
type ErasedFold = (...args: ReadonlyArray<never>) => unknown
|
|
47
|
+
|
|
48
|
+
interface RuntimeStateReducerConfig {
|
|
49
|
+
readonly states: readonly [AnyState]
|
|
50
|
+
readonly events: ReadonlyArray<AnyEvent>
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
interface RuntimeFamilyReducerConfig {
|
|
54
|
+
readonly family: AnyFamily
|
|
55
|
+
readonly keyOf: ErasedFold
|
|
56
|
+
readonly events: ReadonlyArray<AnyEvent>
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
interface RuntimeReducerClass {
|
|
60
|
+
new (): {}
|
|
61
|
+
readonly manifest: ReducerManifest
|
|
62
|
+
readonly config: RuntimeStateReducerConfig | RuntimeFamilyReducerConfig
|
|
63
|
+
readonly handles: ReadonlySet<string>
|
|
64
|
+
}
|
|
65
|
+
|
|
47
66
|
export function make<S extends AnyState, const Events extends ReadonlyArray<AnyEvent>>(
|
|
48
67
|
name: string,
|
|
49
68
|
config: StateReducerConfig<S, Events>,
|
|
@@ -54,9 +73,7 @@ export function make<F extends AnyFamily, const Events extends ReadonlyArray<Any
|
|
|
54
73
|
): FamilyReducerClass<F, Events>
|
|
55
74
|
export function make(
|
|
56
75
|
name: string,
|
|
57
|
-
config:
|
|
58
|
-
| StateReducerConfig<AnyState, ReadonlyArray<AnyEvent>>
|
|
59
|
-
| FamilyReducerConfig<AnyFamily, ReadonlyArray<AnyEvent>>,
|
|
76
|
+
config: RuntimeStateReducerConfig | RuntimeFamilyReducerConfig,
|
|
60
77
|
): unknown {
|
|
61
78
|
return definitionClass<unknown>({
|
|
62
79
|
manifest: { kind: 'Reducer' as const, name } satisfies ReducerManifest,
|
|
@@ -79,48 +96,75 @@ const sync = <A>(candidate: A): A => {
|
|
|
79
96
|
export function live<S extends AnyState, Events extends ReadonlyArray<AnyEvent>>(
|
|
80
97
|
reducer: StateReducerClass<S, Events>,
|
|
81
98
|
fold: (value: StateValue<S>, event: EventType<Events[number]>) => StateValue<S>,
|
|
82
|
-
): Layer.Layer<never, never, Reducers |
|
|
99
|
+
): Layer.Layer<never, never, Reducers | StateStore<StateName<S>, StateValue<S>>>
|
|
83
100
|
export function live<F extends AnyFamily, Events extends ReadonlyArray<AnyEvent>>(
|
|
84
101
|
reducer: FamilyReducerClass<F, Events>,
|
|
85
102
|
fold: (entry: FamilyValue<F>, event: EventType<Events[number]>) => FamilyValue<F> | Tombstone,
|
|
86
|
-
): Layer.Layer<
|
|
103
|
+
): Layer.Layer<
|
|
104
|
+
never,
|
|
105
|
+
never,
|
|
106
|
+
Reducers | StateFamilyStore<FamilyName<F>, FamilyKey<F>, FamilyValue<F>>
|
|
107
|
+
>
|
|
87
108
|
export function live(
|
|
88
|
-
reducer:
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
// oxlint-disable-next-line reform-rules/no-explicit-any-value -- overload-impl signature: `unknown` params fail contravariance against the State/Family folds (TS2394); only `any` subsumes both
|
|
92
|
-
fold: (acc: AnyValue, event: AnyValue) => AnyValue,
|
|
93
|
-
): Layer.Layer<never, never, AnyValue> {
|
|
109
|
+
reducer: RuntimeReducerClass,
|
|
110
|
+
fold: ErasedFold,
|
|
111
|
+
): Layer.Layer<never, never, unknown> {
|
|
94
112
|
const { config, handles } = reducer
|
|
95
113
|
const name = reducer.manifest.name
|
|
96
114
|
return Layer.scopedDiscard(
|
|
97
115
|
Effect.gen(function* () {
|
|
98
116
|
const reducers = yield* Reducers
|
|
99
|
-
if (reducers.
|
|
117
|
+
if (reducers.byName.has(name)) {
|
|
100
118
|
yield* Effect.logWarning(`reform: duplicate reducer name '${name}' registered`)
|
|
101
119
|
}
|
|
102
120
|
const entry: ReducerEntry = yield* Effect.gen(function* () {
|
|
103
121
|
if ('family' in config) {
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
122
|
+
return yield* config.family.capture<Effect.Effect<ReducerEntry, never, unknown>>(
|
|
123
|
+
(familyDefinition) =>
|
|
124
|
+
Effect.map(familyDefinition.store, (family) => ({
|
|
125
|
+
name,
|
|
126
|
+
handles,
|
|
127
|
+
apply: (event: Tagged) => {
|
|
128
|
+
// The loop calls entries only for handled events. At this
|
|
129
|
+
// existential boundary the family's decoders restore the
|
|
130
|
+
// exact key/value types before either touches its store.
|
|
131
|
+
const key = familyDefinition.decodeKeySync(
|
|
132
|
+
sync(Reflect.apply(config.keyOf, undefined, [narrowHandled(event)])),
|
|
133
|
+
)
|
|
134
|
+
const previous = family.at(key).get()
|
|
135
|
+
const next = sync(Reflect.apply(fold, undefined, [previous, event]))
|
|
136
|
+
if (next === Tombstone) {
|
|
137
|
+
family.forget(key)
|
|
138
|
+
} else if (next !== previous) {
|
|
139
|
+
// Same-reference return means "no change": skip the nominal re-decode,
|
|
140
|
+
// which would mint a fresh value and repaint every tracker of this slice.
|
|
141
|
+
family.at(key).set(familyDefinition.decodeValueSync(next))
|
|
142
|
+
}
|
|
143
|
+
},
|
|
144
|
+
})),
|
|
145
|
+
)
|
|
118
146
|
}
|
|
119
|
-
|
|
120
|
-
|
|
147
|
+
return yield* config.states[0].capture<Effect.Effect<ReducerEntry, never, unknown>>(
|
|
148
|
+
(state) =>
|
|
149
|
+
Effect.map(state.store, (store) => ({
|
|
150
|
+
name,
|
|
151
|
+
handles,
|
|
152
|
+
apply: (event: Tagged) => {
|
|
153
|
+
const previous = store.get()
|
|
154
|
+
const next = sync(Reflect.apply(fold, undefined, [previous, event]))
|
|
155
|
+
// Same-reference return means "no change": skip the nominal re-decode,
|
|
156
|
+
// which would mint a fresh value and repaint every tracker of this slice.
|
|
157
|
+
if (next === previous) {
|
|
158
|
+
return
|
|
159
|
+
}
|
|
160
|
+
store.set(state.decodeUnknownSync(next))
|
|
161
|
+
},
|
|
162
|
+
})),
|
|
163
|
+
)
|
|
121
164
|
})
|
|
122
|
-
yield* Effect.acquireRelease(
|
|
123
|
-
Effect.sync(() => reducers.
|
|
165
|
+
yield* Effect.acquireRelease(
|
|
166
|
+
Effect.sync(() => reducers.register(entry)),
|
|
167
|
+
() => Effect.sync(() => reducers.unregister(entry)),
|
|
124
168
|
)
|
|
125
169
|
}),
|
|
126
170
|
)
|