import { Duration, Effect, Layer, Schema as S } from 'effect' import { Channel, Composition, Engine, Event, Procedure, Reducer, State, StateGroup, Ui, mount, provide, scene, type CapturedScene, type DerivedContract, type EventOf, ui, } from '@playfast/reform' import { type EngineServices, type WiredUi } from '@playfast/reform/internal' const CountBase: State.StateDefinition<'count', number, typeof S.Number> = State.make( 'count', S.Number, ) class Count extends CountBase {} class Counters extends StateGroup.make(Count) {} class Bumped extends Event.make('Bumped', S.Struct({ by: S.Number })) {} class Bump extends Reducer.make('Bump', { states: [Count], events: [Bumped], }) {} const CounterUiBase: WiredUi< DerivedContract<{ props: S.Struct<{ count: typeof S.Number }> events: { bump: S.Struct<{ by: typeof S.Number }> } }>, 'Counter' > = ui('Counter', { props: S.Struct({ count: S.Number }), events: { bump: S.Struct({ by: S.Number }) }, }) export class CounterUi extends CounterUiBase {} class Counter extends Composition.make('Counter', { title: 'Counter', ui: CounterUi, states: [Count], })() {} type CounterScene = CapturedScene< typeof CounterUi.Contract, readonly [typeof Count], | EngineServices | State.StateStore<'count', number> | Ui.UiService<'Counter'> | Composition.CompositionId<'Counter', Counter, never>, unknown, 'Counter', Counter > export const bumpedBy = (amount: number): EventOf<'Bumped', { by: number }> => Event.construct(Bumped, { by: amount }) const makeCounterScene = ( boot?: ReadonlyArray>, ): CounterScene => { const presentation = Layer.mergeAll( provide( CounterUi, Ui.make(CounterUi, ({ count }) => `count:${count}`), ), StateGroup.live(Counters, { count: 0 }), ) const app = Layer.mergeAll( Composition.live(Counter, function* () { const count = yield* StateGroup.select(Counters, 'count') const bump = yield* Event.trigger(Bumped) return mount({ props: { count }, slots: {}, events: { bump } }) }), Reducer.live(Bump, (count, event) => count + event.by), ).pipe(Layer.provideMerge(presentation), Layer.provideMerge(Engine)) return scene(Counter, boot === undefined ? { provide: [app] } : { provide: [app], boot }) } export const counterScene: typeof makeCounterScene = makeCounterScene const makeStructureCounterScene = (): CounterScene => { const presentation = Layer.mergeAll( provide( CounterUi, Ui.make(CounterUi, ({ count }) => `count:${count}`), ), StateGroup.live(Counters, { count: 0 }), ) const app = Layer.mergeAll( Composition.live(Counter, function* () { const count = yield* StateGroup.select(Counters, 'count') const bump = yield* Event.trigger(Bumped) return mount({ props: { count }, slots: {}, events: { bump } }) }), Reducer.live(Bump, (count, event) => count + event.by), ).pipe(Layer.provideMerge(presentation), Layer.provideMerge(Engine)) return scene(Counter, { provide: [app] }) } export const structureCounterScene: typeof makeStructureCounterScene = makeStructureCounterScene class Kick extends Event.make('Kick', S.Struct({ to: S.Number })) {} class Loader extends Channel.make('Loader', { policy: { _tag: 'latest' } }) {} class DelayedBump extends Procedure.make('DelayedBump', { channel: Loader, events: [Kick], }) {} const makeAsyncCounterScene = (delay: Duration.DurationInput = '40 millis'): CounterScene => { const presentation = Layer.mergeAll( provide( CounterUi, Ui.make(CounterUi, ({ count }) => `count:${count}`), ), StateGroup.live(Counters, { count: 0 }), ) const app = Layer.mergeAll( Composition.live(Counter, function* () { const count = yield* StateGroup.select(Counters, 'count') const bump = yield* Event.trigger(Bumped) return mount({ props: { count }, slots: {}, events: { bump } }) }), Reducer.live(Bump, (count, event) => count + event.by), Procedure.live(DelayedBump, function* (event) { yield* Effect.sleep(delay) yield* Event.dispatch(Bumped, { by: event.to }) }), // Without Channel.live the boot Kick is dropped. Channel.live(Loader), ).pipe(Layer.provideMerge(presentation), Layer.provideMerge(Engine)) return scene(Counter, { provide: [app], boot: [Event.construct(Kick, { to: 7 })], }) } export const asyncCounterScene: typeof makeAsyncCounterScene = makeAsyncCounterScene