@playfast/reform-remote 1.1.0 → 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/dist/client.d.ts +33 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +80 -0
- package/dist/client.js.map +1 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +11 -0
- package/dist/index.js.map +1 -0
- package/dist/memory.d.ts +7 -0
- package/dist/memory.d.ts.map +1 -0
- package/dist/memory.js +21 -0
- package/dist/memory.js.map +1 -0
- package/dist/react.d.ts +15 -0
- package/dist/react.d.ts.map +1 -0
- package/dist/react.js +11 -0
- package/dist/react.js.map +1 -0
- package/dist/server.d.ts +16 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +56 -0
- package/dist/server.js.map +1 -0
- package/dist/transport.d.ts +53 -0
- package/dist/transport.d.ts.map +1 -0
- package/dist/transport.js +171 -0
- package/dist/transport.js.map +1 -0
- package/dist/wireNode.d.ts +24 -0
- package/dist/wireNode.d.ts.map +1 -0
- package/dist/wireNode.js +80 -0
- package/dist/wireNode.js.map +1 -0
- package/dist/wireRender.d.ts +20 -0
- package/dist/wireRender.d.ts.map +1 -0
- package/dist/wireRender.js +97 -0
- package/dist/wireRender.js.map +1 -0
- package/package.json +17 -5
- package/src/counterFixtures.ts +142 -0
- package/src/fixtures.ts +10 -453
- package/src/listFixtures.ts +174 -0
- package/src/optionalFixtures.ts +64 -0
- package/src/server.ts +11 -349
- package/src/slottedFixtures.ts +120 -0
- package/src/wireNode.ts +153 -0
- package/src/wireRender.ts +235 -0
package/src/fixtures.ts
CHANGED
|
@@ -1,453 +1,10 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
Procedure,
|
|
12
|
-
Reducer,
|
|
13
|
-
State,
|
|
14
|
-
StateGroup,
|
|
15
|
-
Ui,
|
|
16
|
-
each,
|
|
17
|
-
mount,
|
|
18
|
-
one,
|
|
19
|
-
provide,
|
|
20
|
-
scene,
|
|
21
|
-
slot,
|
|
22
|
-
type SlotService,
|
|
23
|
-
ui,
|
|
24
|
-
} from '@playfast/reform'
|
|
25
|
-
import {
|
|
26
|
-
type CompositionChildSummary,
|
|
27
|
-
type EngineServices,
|
|
28
|
-
type WiredUi,
|
|
29
|
-
} from '@playfast/reform/internal'
|
|
30
|
-
|
|
31
|
-
const CountBase: State.StateDefinition<'count', number, typeof S.Number> = State.make(
|
|
32
|
-
'count',
|
|
33
|
-
S.Number,
|
|
34
|
-
)
|
|
35
|
-
class Count extends CountBase {}
|
|
36
|
-
class Counters extends StateGroup.make(Count) {}
|
|
37
|
-
class Bumped extends Event.make('Bumped', S.Struct({ by: S.Number })) {}
|
|
38
|
-
class Bump extends Reducer.make('Bump', {
|
|
39
|
-
states: [Count],
|
|
40
|
-
events: [Bumped],
|
|
41
|
-
}) {}
|
|
42
|
-
const CounterUiBase: WiredUi<
|
|
43
|
-
DerivedContract<{
|
|
44
|
-
props: S.Struct<{ count: typeof S.Number }>
|
|
45
|
-
events: { bump: S.Struct<{ by: typeof S.Number }> }
|
|
46
|
-
}>,
|
|
47
|
-
'Counter'
|
|
48
|
-
> = ui('Counter', {
|
|
49
|
-
props: S.Struct({ count: S.Number }),
|
|
50
|
-
events: { bump: S.Struct({ by: S.Number }) },
|
|
51
|
-
})
|
|
52
|
-
export class CounterUi extends CounterUiBase {}
|
|
53
|
-
|
|
54
|
-
class Counter extends Composition.make('Counter', {
|
|
55
|
-
title: 'Counter',
|
|
56
|
-
ui: CounterUi,
|
|
57
|
-
states: [Count],
|
|
58
|
-
})<Counter>() {}
|
|
59
|
-
|
|
60
|
-
type CounterScene = CapturedScene<
|
|
61
|
-
typeof CounterUi.Contract,
|
|
62
|
-
readonly [typeof Count],
|
|
63
|
-
| EngineServices
|
|
64
|
-
| State.StateStore<'count', number>
|
|
65
|
-
| Ui.UiService<'Counter'>
|
|
66
|
-
| Composition.CompositionId<'Counter', Counter, never>,
|
|
67
|
-
unknown,
|
|
68
|
-
'Counter',
|
|
69
|
-
Counter
|
|
70
|
-
>
|
|
71
|
-
|
|
72
|
-
export const bumpedBy = (amount: number): EventOf<'Bumped', { by: number }> =>
|
|
73
|
-
Event.construct(Bumped, { by: amount })
|
|
74
|
-
|
|
75
|
-
const makeCounterScene = (
|
|
76
|
-
boot?: ReadonlyArray<EventOf<'Bumped', { by: number }>>,
|
|
77
|
-
): CounterScene => {
|
|
78
|
-
const presentation = Layer.mergeAll(
|
|
79
|
-
provide(
|
|
80
|
-
CounterUi,
|
|
81
|
-
Ui.make(CounterUi, ({ count }) => `count:${count}`),
|
|
82
|
-
),
|
|
83
|
-
StateGroup.live(Counters, { count: 0 }),
|
|
84
|
-
)
|
|
85
|
-
const app = Layer.mergeAll(
|
|
86
|
-
Composition.live(Counter, function* () {
|
|
87
|
-
const count = yield* StateGroup.select(Counters, 'count')
|
|
88
|
-
const bump = yield* Event.trigger(Bumped)
|
|
89
|
-
return mount({ props: { count }, slots: {}, events: { bump } })
|
|
90
|
-
}),
|
|
91
|
-
Reducer.live(Bump, (count, event) => count + event.by),
|
|
92
|
-
).pipe(Layer.provideMerge(presentation), Layer.provideMerge(Engine))
|
|
93
|
-
return scene(Counter, boot === undefined ? { provide: [app] } : { provide: [app], boot })
|
|
94
|
-
}
|
|
95
|
-
export const counterScene: typeof makeCounterScene = makeCounterScene
|
|
96
|
-
|
|
97
|
-
const makeStructureCounterScene = (): CounterScene => {
|
|
98
|
-
const presentation = Layer.mergeAll(
|
|
99
|
-
provide(
|
|
100
|
-
CounterUi,
|
|
101
|
-
Ui.make(CounterUi, ({ count }) => `count:${count}`),
|
|
102
|
-
),
|
|
103
|
-
StateGroup.live(Counters, { count: 0 }),
|
|
104
|
-
)
|
|
105
|
-
const app = Layer.mergeAll(
|
|
106
|
-
Composition.live(Counter, function* () {
|
|
107
|
-
const count = yield* StateGroup.select(Counters, 'count')
|
|
108
|
-
const bump = yield* Event.trigger(Bumped)
|
|
109
|
-
return mount({ props: { count }, slots: {}, events: { bump } })
|
|
110
|
-
}),
|
|
111
|
-
Reducer.live(Bump, (count, event) => count + event.by),
|
|
112
|
-
).pipe(Layer.provideMerge(presentation), Layer.provideMerge(Engine))
|
|
113
|
-
return scene(Counter, { provide: [app] })
|
|
114
|
-
}
|
|
115
|
-
export const structureCounterScene: typeof makeStructureCounterScene = makeStructureCounterScene
|
|
116
|
-
|
|
117
|
-
const LabelBase: State.StateDefinition<
|
|
118
|
-
'label',
|
|
119
|
-
Option.Option<string>,
|
|
120
|
-
S.OptionFromSelf<typeof S.String>
|
|
121
|
-
> = State.make('label', S.OptionFromSelf(S.String))
|
|
122
|
-
class Label extends LabelBase {}
|
|
123
|
-
class Labels extends StateGroup.make(Label) {}
|
|
124
|
-
const OptionalUiBase: WiredUi<
|
|
125
|
-
DerivedContract<{ props: S.Struct<{ label: S.Option<typeof S.String> }> }>,
|
|
126
|
-
'Optional'
|
|
127
|
-
> = ui('Optional', { props: S.Struct({ label: S.Option(S.String) }) })
|
|
128
|
-
export class OptionalUi extends OptionalUiBase {}
|
|
129
|
-
|
|
130
|
-
class Optional extends Composition.make('Optional', {
|
|
131
|
-
title: 'Optional',
|
|
132
|
-
ui: OptionalUi,
|
|
133
|
-
states: [Label],
|
|
134
|
-
})<Optional>() {}
|
|
135
|
-
|
|
136
|
-
type OptionalScene = CapturedScene<
|
|
137
|
-
typeof OptionalUi.Contract,
|
|
138
|
-
readonly [typeof Label],
|
|
139
|
-
| EngineServices
|
|
140
|
-
| State.StateStore<'label', Option.Option<string>>
|
|
141
|
-
| Ui.UiService<'Optional'>
|
|
142
|
-
| Composition.CompositionId<'Optional', Optional, never>,
|
|
143
|
-
unknown,
|
|
144
|
-
'Optional',
|
|
145
|
-
Optional
|
|
146
|
-
>
|
|
147
|
-
|
|
148
|
-
const makeOptionScene = (initial: Option.Option<string> = Option.some('hi')): OptionalScene => {
|
|
149
|
-
const presentation = Layer.mergeAll(
|
|
150
|
-
provide(
|
|
151
|
-
OptionalUi,
|
|
152
|
-
Ui.make(OptionalUi, () => null),
|
|
153
|
-
),
|
|
154
|
-
StateGroup.live(Labels, { label: initial }),
|
|
155
|
-
)
|
|
156
|
-
const app = Layer.mergeAll(
|
|
157
|
-
Composition.live(Optional, function* () {
|
|
158
|
-
const label = yield* StateGroup.select(Labels, 'label')
|
|
159
|
-
return mount({ props: { label }, slots: {} })
|
|
160
|
-
}),
|
|
161
|
-
).pipe(Layer.provideMerge(presentation), Layer.provideMerge(Engine))
|
|
162
|
-
return scene(Optional, { provide: [app] })
|
|
163
|
-
}
|
|
164
|
-
export const optionScene: typeof makeOptionScene = makeOptionScene
|
|
165
|
-
|
|
166
|
-
const GreetingBase: State.StateDefinition<'greeting', string, typeof S.String> = State.make(
|
|
167
|
-
'greeting',
|
|
168
|
-
S.String,
|
|
169
|
-
)
|
|
170
|
-
class Greeting extends GreetingBase {}
|
|
171
|
-
class Greeted extends Event.make('Greeted', S.Struct({ text: S.String })) {}
|
|
172
|
-
class Cleared extends Event.make('Cleared', S.Struct({})) {}
|
|
173
|
-
class SetGreeting extends Reducer.make('SetGreeting', {
|
|
174
|
-
states: [Greeting],
|
|
175
|
-
events: [Greeted],
|
|
176
|
-
}) {}
|
|
177
|
-
class ClearGreeting extends Reducer.make('ClearGreeting', {
|
|
178
|
-
states: [Greeting],
|
|
179
|
-
events: [Cleared],
|
|
180
|
-
}) {}
|
|
181
|
-
const PanelUiBase: WiredUi<
|
|
182
|
-
DerivedContract<{
|
|
183
|
-
props: S.Struct<{ greeting: typeof S.String }>
|
|
184
|
-
events: {
|
|
185
|
-
greet: S.Struct<{ text: typeof S.String }>
|
|
186
|
-
clear: S.Struct<{}>
|
|
187
|
-
}
|
|
188
|
-
}>,
|
|
189
|
-
'Panel'
|
|
190
|
-
> = ui('Panel', {
|
|
191
|
-
props: S.Struct({ greeting: S.String }),
|
|
192
|
-
events: { greet: S.Struct({ text: S.String }), clear: S.Struct({}) },
|
|
193
|
-
})
|
|
194
|
-
class PanelUi extends PanelUiBase {}
|
|
195
|
-
|
|
196
|
-
class Panel extends Composition.make('Panel', {
|
|
197
|
-
title: 'Panel',
|
|
198
|
-
ui: PanelUi,
|
|
199
|
-
states: [Greeting],
|
|
200
|
-
})<Panel>() {}
|
|
201
|
-
|
|
202
|
-
class MainSlot extends slot('Main')<MainSlot, typeof Panel>() {}
|
|
203
|
-
const ShellUiBase: Ui.UiClass<{ props: {}; slots: { Main: MainSlot } }, 'Shell'> = ui('Shell')<{
|
|
204
|
-
props: {}
|
|
205
|
-
slots: { Main: MainSlot }
|
|
206
|
-
}>()
|
|
207
|
-
class ShellUi extends ShellUiBase {}
|
|
208
|
-
|
|
209
|
-
class Shell extends Composition.make('Shell', {
|
|
210
|
-
title: 'Shell',
|
|
211
|
-
slots: { Main: MainSlot },
|
|
212
|
-
ui: ShellUi,
|
|
213
|
-
})<Shell>() {}
|
|
214
|
-
|
|
215
|
-
type SlottedScene = CapturedScene<
|
|
216
|
-
typeof ShellUi.Contract,
|
|
217
|
-
readonly [],
|
|
218
|
-
| EngineServices
|
|
219
|
-
| State.StateStore<'greeting', string>
|
|
220
|
-
| Ui.UiService<'Panel'>
|
|
221
|
-
| Ui.UiService<'Shell'>
|
|
222
|
-
| Composition.CompositionId<'Shell', Shell, MainSlot>
|
|
223
|
-
| Composition.CompositionId<'Panel', Panel, never>
|
|
224
|
-
| SlotService<'Main', MainSlot, CompositionChildSummary<Panel>>,
|
|
225
|
-
unknown,
|
|
226
|
-
'Shell',
|
|
227
|
-
Shell
|
|
228
|
-
>
|
|
229
|
-
|
|
230
|
-
const makeSlottedScene = (): SlottedScene => {
|
|
231
|
-
const presentation = Layer.mergeAll(
|
|
232
|
-
provide(
|
|
233
|
-
ShellUi,
|
|
234
|
-
Ui.make(ShellUi, (_props, slots) => createElement(slots.Main, {})),
|
|
235
|
-
),
|
|
236
|
-
provide(
|
|
237
|
-
PanelUi,
|
|
238
|
-
Ui.make(PanelUi, ({ greeting }) => greeting),
|
|
239
|
-
),
|
|
240
|
-
provide(MainSlot, Panel),
|
|
241
|
-
State.live(Greeting, 'hi'),
|
|
242
|
-
)
|
|
243
|
-
const app = Layer.mergeAll(
|
|
244
|
-
Composition.live(Shell, function* () {
|
|
245
|
-
yield* Effect.void
|
|
246
|
-
return mount({ props: {}, slots: { Main: one({}) } })
|
|
247
|
-
}),
|
|
248
|
-
Composition.live(Panel, function* () {
|
|
249
|
-
const greeting = yield* Greeting
|
|
250
|
-
const greet = yield* Event.trigger(Greeted)
|
|
251
|
-
const clearTrigger = yield* Event.trigger(Cleared)
|
|
252
|
-
const clear = (): void => clearTrigger({})
|
|
253
|
-
return mount({
|
|
254
|
-
props: { greeting },
|
|
255
|
-
slots: {},
|
|
256
|
-
events: { greet, clear },
|
|
257
|
-
})
|
|
258
|
-
}),
|
|
259
|
-
Reducer.live(SetGreeting, (_greeting, event) => event.text),
|
|
260
|
-
Reducer.live(ClearGreeting, () => ''),
|
|
261
|
-
).pipe(Layer.provideMerge(presentation), Layer.provideMerge(Engine))
|
|
262
|
-
return scene(Shell, { provide: [app] })
|
|
263
|
-
}
|
|
264
|
-
export const slottedScene: typeof makeSlottedScene = makeSlottedScene
|
|
265
|
-
|
|
266
|
-
const ListItem: S.Struct<{ id: typeof S.String; label: typeof S.String }> = S.Struct({
|
|
267
|
-
id: S.String,
|
|
268
|
-
label: S.String,
|
|
269
|
-
})
|
|
270
|
-
const ItemsBase: State.StateDefinition<
|
|
271
|
-
'items',
|
|
272
|
-
ReadonlyArray<{ readonly id: string; readonly label: string }>,
|
|
273
|
-
S.Array$<typeof ListItem>
|
|
274
|
-
> = State.make('items', S.Array(ListItem))
|
|
275
|
-
class Items extends ItemsBase {}
|
|
276
|
-
class Added extends Event.make('Added', ListItem) {}
|
|
277
|
-
class Removed extends Event.make('Removed', S.Struct({ id: S.String })) {}
|
|
278
|
-
class AddItem extends Reducer.make('AddItem', {
|
|
279
|
-
states: [Items],
|
|
280
|
-
events: [Added],
|
|
281
|
-
}) {}
|
|
282
|
-
class RemoveItem extends Reducer.make('RemoveItem', {
|
|
283
|
-
states: [Items],
|
|
284
|
-
events: [Removed],
|
|
285
|
-
}) {}
|
|
286
|
-
|
|
287
|
-
const BarUiBase: WiredUi<
|
|
288
|
-
DerivedContract<{
|
|
289
|
-
props: S.Struct<{}>
|
|
290
|
-
events: { add: typeof ListItem }
|
|
291
|
-
}>,
|
|
292
|
-
'Bar'
|
|
293
|
-
> = ui('Bar', {
|
|
294
|
-
props: S.Struct({}),
|
|
295
|
-
events: { add: ListItem },
|
|
296
|
-
})
|
|
297
|
-
class BarUi extends BarUiBase {}
|
|
298
|
-
|
|
299
|
-
class Bar extends Composition.make('Bar', { title: 'Bar', ui: BarUi })<Bar>() {}
|
|
300
|
-
const ItemUiBase: WiredUi<
|
|
301
|
-
DerivedContract<{
|
|
302
|
-
props: S.Struct<{ label: typeof S.String }>
|
|
303
|
-
events: { remove: S.Struct<{}> }
|
|
304
|
-
}>,
|
|
305
|
-
'Item'
|
|
306
|
-
> = ui('Item', {
|
|
307
|
-
props: S.Struct({ label: S.String }),
|
|
308
|
-
events: { remove: S.Struct({}) },
|
|
309
|
-
})
|
|
310
|
-
class ItemUi extends ItemUiBase {}
|
|
311
|
-
|
|
312
|
-
class ItemComp extends Composition.make('Item', {
|
|
313
|
-
title: 'Item',
|
|
314
|
-
ui: ItemUi,
|
|
315
|
-
props: ListItem,
|
|
316
|
-
})<ItemComp>() {}
|
|
317
|
-
|
|
318
|
-
class BarSlot extends slot('Bar')<BarSlot, typeof Bar>() {}
|
|
319
|
-
|
|
320
|
-
class ItemSlot extends slot('Item')<ItemSlot, typeof ItemComp>() {}
|
|
321
|
-
const ListUiBase: Ui.UiClass<
|
|
322
|
-
{
|
|
323
|
-
props: { items: ReadonlyArray<{ id: string; label: string }> }
|
|
324
|
-
slots: { Bar: BarSlot; Item: ItemSlot }
|
|
325
|
-
},
|
|
326
|
-
'List'
|
|
327
|
-
> = ui('List')<{
|
|
328
|
-
props: { items: ReadonlyArray<{ id: string; label: string }> }
|
|
329
|
-
slots: { Bar: BarSlot; Item: ItemSlot }
|
|
330
|
-
}>()
|
|
331
|
-
class ListUi extends ListUiBase {}
|
|
332
|
-
|
|
333
|
-
class ListComp extends Composition.make('List', {
|
|
334
|
-
title: 'List',
|
|
335
|
-
slots: { Bar: BarSlot, Item: ItemSlot },
|
|
336
|
-
ui: ListUi,
|
|
337
|
-
states: [Items],
|
|
338
|
-
})<ListComp>() {}
|
|
339
|
-
|
|
340
|
-
type ListScene = CapturedScene<
|
|
341
|
-
typeof ListUi.Contract,
|
|
342
|
-
readonly [typeof Items],
|
|
343
|
-
| EngineServices
|
|
344
|
-
| Ui.UiService<'Item'>
|
|
345
|
-
| State.StateStore<'items', ReadonlyArray<{ readonly id: string; readonly label: string }>>
|
|
346
|
-
| Ui.UiService<'Bar'>
|
|
347
|
-
| Ui.UiService<'List'>
|
|
348
|
-
| Composition.CompositionId<'List', ListComp, BarSlot | ItemSlot>
|
|
349
|
-
| Composition.CompositionId<'Bar', Bar, never>
|
|
350
|
-
| Composition.CompositionId<'Item', ItemComp, never>
|
|
351
|
-
| SlotService<'Bar', BarSlot, CompositionChildSummary<Bar>>
|
|
352
|
-
| SlotService<'Item', ItemSlot, CompositionChildSummary<ItemComp>>,
|
|
353
|
-
unknown,
|
|
354
|
-
'List',
|
|
355
|
-
ListComp
|
|
356
|
-
>
|
|
357
|
-
|
|
358
|
-
const makeListScene = (
|
|
359
|
-
initial: ReadonlyArray<{ id: string; label: string }> = [
|
|
360
|
-
{ id: 'a', label: 'A' },
|
|
361
|
-
{ id: 'b', label: 'B' },
|
|
362
|
-
],
|
|
363
|
-
): ListScene => {
|
|
364
|
-
const presentation = Layer.mergeAll(
|
|
365
|
-
provide(
|
|
366
|
-
ListUi,
|
|
367
|
-
Ui.make(ListUi, (_props, slots) =>
|
|
368
|
-
createElement(Fragment, null, createElement(slots.Bar, {}), createElement(slots.Item, {})),
|
|
369
|
-
),
|
|
370
|
-
),
|
|
371
|
-
provide(
|
|
372
|
-
BarUi,
|
|
373
|
-
Ui.make(BarUi, () => null),
|
|
374
|
-
),
|
|
375
|
-
provide(
|
|
376
|
-
ItemUi,
|
|
377
|
-
Ui.make(ItemUi, ({ label }) => label),
|
|
378
|
-
),
|
|
379
|
-
provide(BarSlot, Bar),
|
|
380
|
-
provide(ItemSlot, ItemComp),
|
|
381
|
-
State.live(Items, initial),
|
|
382
|
-
)
|
|
383
|
-
const app = Layer.mergeAll(
|
|
384
|
-
Composition.live(ListComp, function* () {
|
|
385
|
-
const listItems = yield* Items
|
|
386
|
-
return mount({
|
|
387
|
-
props: { items: listItems },
|
|
388
|
-
slots: {
|
|
389
|
-
Bar: one({}),
|
|
390
|
-
Item: each(listItems, {
|
|
391
|
-
key: (entry) => entry.id,
|
|
392
|
-
props: (entry) => ({ id: entry.id, label: entry.label }),
|
|
393
|
-
}),
|
|
394
|
-
},
|
|
395
|
-
})
|
|
396
|
-
}),
|
|
397
|
-
Composition.live(Bar, function* () {
|
|
398
|
-
const add = yield* Event.trigger(Added)
|
|
399
|
-
return mount({ props: {}, slots: {}, events: { add } })
|
|
400
|
-
}),
|
|
401
|
-
Composition.live(ItemComp, function* () {
|
|
402
|
-
const itemProps = yield* ItemComp.props
|
|
403
|
-
const removeTrigger = yield* Event.trigger(Removed)
|
|
404
|
-
// The item binds its id so the wire event carries no payload.
|
|
405
|
-
const remove = (): void => removeTrigger({ id: itemProps.id })
|
|
406
|
-
return mount({
|
|
407
|
-
props: { label: itemProps.label },
|
|
408
|
-
slots: {},
|
|
409
|
-
events: { remove },
|
|
410
|
-
})
|
|
411
|
-
}),
|
|
412
|
-
Reducer.live(AddItem, (current, event) => [...current, { id: event.id, label: event.label }]),
|
|
413
|
-
Reducer.live(RemoveItem, (current, event) => current.filter((entry) => entry.id !== event.id)),
|
|
414
|
-
).pipe(Layer.provideMerge(presentation), Layer.provideMerge(Engine))
|
|
415
|
-
return scene(ListComp, { provide: [app] })
|
|
416
|
-
}
|
|
417
|
-
export const listScene: typeof makeListScene = makeListScene
|
|
418
|
-
|
|
419
|
-
class Kick extends Event.make('Kick', S.Struct({ to: S.Number })) {}
|
|
420
|
-
class Loader extends Channel.make('Loader', { policy: { _tag: 'latest' } }) {}
|
|
421
|
-
class DelayedBump extends Procedure.make('DelayedBump', {
|
|
422
|
-
channel: Loader,
|
|
423
|
-
events: [Kick],
|
|
424
|
-
}) {}
|
|
425
|
-
|
|
426
|
-
const makeAsyncCounterScene = (delay: Duration.DurationInput = '40 millis'): CounterScene => {
|
|
427
|
-
const presentation = Layer.mergeAll(
|
|
428
|
-
provide(
|
|
429
|
-
CounterUi,
|
|
430
|
-
Ui.make(CounterUi, ({ count }) => `count:${count}`),
|
|
431
|
-
),
|
|
432
|
-
StateGroup.live(Counters, { count: 0 }),
|
|
433
|
-
)
|
|
434
|
-
const app = Layer.mergeAll(
|
|
435
|
-
Composition.live(Counter, function* () {
|
|
436
|
-
const count = yield* StateGroup.select(Counters, 'count')
|
|
437
|
-
const bump = yield* Event.trigger(Bumped)
|
|
438
|
-
return mount({ props: { count }, slots: {}, events: { bump } })
|
|
439
|
-
}),
|
|
440
|
-
Reducer.live(Bump, (count, event) => count + event.by),
|
|
441
|
-
Procedure.live(DelayedBump, function* (event) {
|
|
442
|
-
yield* Effect.sleep(delay)
|
|
443
|
-
yield* Event.dispatch(Bumped, { by: event.to })
|
|
444
|
-
}),
|
|
445
|
-
// Without Channel.live the boot Kick is dropped.
|
|
446
|
-
Channel.live(Loader),
|
|
447
|
-
).pipe(Layer.provideMerge(presentation), Layer.provideMerge(Engine))
|
|
448
|
-
return scene(Counter, {
|
|
449
|
-
provide: [app],
|
|
450
|
-
boot: [Event.construct(Kick, { to: 7 })],
|
|
451
|
-
})
|
|
452
|
-
}
|
|
453
|
-
export const asyncCounterScene: typeof makeAsyncCounterScene = makeAsyncCounterScene
|
|
1
|
+
export {
|
|
2
|
+
asyncCounterScene,
|
|
3
|
+
bumpedBy,
|
|
4
|
+
CounterUi,
|
|
5
|
+
counterScene,
|
|
6
|
+
structureCounterScene,
|
|
7
|
+
} from './counterFixtures'
|
|
8
|
+
export { OptionalUi, optionScene } from './optionalFixtures'
|
|
9
|
+
export { slottedScene } from './slottedFixtures'
|
|
10
|
+
export { listScene } from './listFixtures'
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
import { createElement, Fragment } from 'react'
|
|
2
|
+
import { Layer, Schema as S } from 'effect'
|
|
3
|
+
import {
|
|
4
|
+
Composition,
|
|
5
|
+
Engine,
|
|
6
|
+
Event,
|
|
7
|
+
Reducer,
|
|
8
|
+
State,
|
|
9
|
+
Ui,
|
|
10
|
+
each,
|
|
11
|
+
mount,
|
|
12
|
+
one,
|
|
13
|
+
provide,
|
|
14
|
+
scene,
|
|
15
|
+
slot,
|
|
16
|
+
type CapturedScene,
|
|
17
|
+
type DerivedContract,
|
|
18
|
+
type SlotService,
|
|
19
|
+
ui,
|
|
20
|
+
} from '@playfast/reform'
|
|
21
|
+
import { type CompositionChildSummary, type EngineServices, type WiredUi } from '@playfast/reform/internal'
|
|
22
|
+
|
|
23
|
+
const ListItem: S.Struct<{ id: typeof S.String; label: typeof S.String }> = S.Struct({
|
|
24
|
+
id: S.String,
|
|
25
|
+
label: S.String,
|
|
26
|
+
})
|
|
27
|
+
const ItemsBase: State.StateDefinition<
|
|
28
|
+
'items',
|
|
29
|
+
ReadonlyArray<{ readonly id: string; readonly label: string }>,
|
|
30
|
+
S.Array$<typeof ListItem>
|
|
31
|
+
> = State.make('items', S.Array(ListItem))
|
|
32
|
+
class Items extends ItemsBase {}
|
|
33
|
+
class Added extends Event.make('Added', ListItem) {}
|
|
34
|
+
class Removed extends Event.make('Removed', S.Struct({ id: S.String })) {}
|
|
35
|
+
class AddItem extends Reducer.make('AddItem', {
|
|
36
|
+
states: [Items],
|
|
37
|
+
events: [Added],
|
|
38
|
+
}) {}
|
|
39
|
+
class RemoveItem extends Reducer.make('RemoveItem', {
|
|
40
|
+
states: [Items],
|
|
41
|
+
events: [Removed],
|
|
42
|
+
}) {}
|
|
43
|
+
|
|
44
|
+
const BarUiBase: WiredUi<
|
|
45
|
+
DerivedContract<{
|
|
46
|
+
props: S.Struct<{}>
|
|
47
|
+
events: { add: typeof ListItem }
|
|
48
|
+
}>,
|
|
49
|
+
'Bar'
|
|
50
|
+
> = ui('Bar', {
|
|
51
|
+
props: S.Struct({}),
|
|
52
|
+
events: { add: ListItem },
|
|
53
|
+
})
|
|
54
|
+
class BarUi extends BarUiBase {}
|
|
55
|
+
|
|
56
|
+
class Bar extends Composition.make('Bar', { title: 'Bar', ui: BarUi })<Bar>() {}
|
|
57
|
+
const ItemUiBase: WiredUi<
|
|
58
|
+
DerivedContract<{
|
|
59
|
+
props: S.Struct<{ label: typeof S.String }>
|
|
60
|
+
events: { remove: S.Struct<{}> }
|
|
61
|
+
}>,
|
|
62
|
+
'Item'
|
|
63
|
+
> = ui('Item', {
|
|
64
|
+
props: S.Struct({ label: S.String }),
|
|
65
|
+
events: { remove: S.Struct({}) },
|
|
66
|
+
})
|
|
67
|
+
class ItemUi extends ItemUiBase {}
|
|
68
|
+
|
|
69
|
+
class ItemComp extends Composition.make('Item', {
|
|
70
|
+
title: 'Item',
|
|
71
|
+
ui: ItemUi,
|
|
72
|
+
props: ListItem,
|
|
73
|
+
})<ItemComp>() {}
|
|
74
|
+
|
|
75
|
+
class BarSlot extends slot('Bar')<BarSlot, typeof Bar>() {}
|
|
76
|
+
|
|
77
|
+
class ItemSlot extends slot('Item')<ItemSlot, typeof ItemComp>() {}
|
|
78
|
+
const ListUiBase: Ui.UiClass<
|
|
79
|
+
{
|
|
80
|
+
props: { items: ReadonlyArray<{ id: string; label: string }> }
|
|
81
|
+
slots: { Bar: BarSlot; Item: ItemSlot }
|
|
82
|
+
},
|
|
83
|
+
'List'
|
|
84
|
+
> = ui('List')<{
|
|
85
|
+
props: { items: ReadonlyArray<{ id: string; label: string }> }
|
|
86
|
+
slots: { Bar: BarSlot; Item: ItemSlot }
|
|
87
|
+
}>()
|
|
88
|
+
class ListUi extends ListUiBase {}
|
|
89
|
+
|
|
90
|
+
class ListComp extends Composition.make('List', {
|
|
91
|
+
title: 'List',
|
|
92
|
+
slots: { Bar: BarSlot, Item: ItemSlot },
|
|
93
|
+
ui: ListUi,
|
|
94
|
+
states: [Items],
|
|
95
|
+
})<ListComp>() {}
|
|
96
|
+
|
|
97
|
+
type ListScene = CapturedScene<
|
|
98
|
+
typeof ListUi.Contract,
|
|
99
|
+
readonly [typeof Items],
|
|
100
|
+
| EngineServices
|
|
101
|
+
| Ui.UiService<'Item'>
|
|
102
|
+
| State.StateStore<'items', ReadonlyArray<{ readonly id: string; readonly label: string }>>
|
|
103
|
+
| Ui.UiService<'Bar'>
|
|
104
|
+
| Ui.UiService<'List'>
|
|
105
|
+
| Composition.CompositionId<'List', ListComp, BarSlot | ItemSlot>
|
|
106
|
+
| Composition.CompositionId<'Bar', Bar, never>
|
|
107
|
+
| Composition.CompositionId<'Item', ItemComp, never>
|
|
108
|
+
| SlotService<'Bar', BarSlot, CompositionChildSummary<Bar>>
|
|
109
|
+
| SlotService<'Item', ItemSlot, CompositionChildSummary<ItemComp>>,
|
|
110
|
+
unknown,
|
|
111
|
+
'List',
|
|
112
|
+
ListComp
|
|
113
|
+
>
|
|
114
|
+
|
|
115
|
+
const makeListScene = (
|
|
116
|
+
initial: ReadonlyArray<{ id: string; label: string }> = [
|
|
117
|
+
{ id: 'a', label: 'A' },
|
|
118
|
+
{ id: 'b', label: 'B' },
|
|
119
|
+
],
|
|
120
|
+
): ListScene => {
|
|
121
|
+
const presentation = Layer.mergeAll(
|
|
122
|
+
provide(
|
|
123
|
+
ListUi,
|
|
124
|
+
Ui.make(ListUi, (_props, slots) =>
|
|
125
|
+
createElement(Fragment, null, createElement(slots.Bar, {}), createElement(slots.Item, {})),
|
|
126
|
+
),
|
|
127
|
+
),
|
|
128
|
+
provide(
|
|
129
|
+
BarUi,
|
|
130
|
+
Ui.make(BarUi, () => null),
|
|
131
|
+
),
|
|
132
|
+
provide(
|
|
133
|
+
ItemUi,
|
|
134
|
+
Ui.make(ItemUi, ({ label }) => label),
|
|
135
|
+
),
|
|
136
|
+
provide(BarSlot, Bar),
|
|
137
|
+
provide(ItemSlot, ItemComp),
|
|
138
|
+
State.live(Items, initial),
|
|
139
|
+
)
|
|
140
|
+
const app = Layer.mergeAll(
|
|
141
|
+
Composition.live(ListComp, function* () {
|
|
142
|
+
const listItems = yield* Items
|
|
143
|
+
return mount({
|
|
144
|
+
props: { items: listItems },
|
|
145
|
+
slots: {
|
|
146
|
+
Bar: one({}),
|
|
147
|
+
Item: each(listItems, {
|
|
148
|
+
key: (entry) => entry.id,
|
|
149
|
+
props: (entry) => ({ id: entry.id, label: entry.label }),
|
|
150
|
+
}),
|
|
151
|
+
},
|
|
152
|
+
})
|
|
153
|
+
}),
|
|
154
|
+
Composition.live(Bar, function* () {
|
|
155
|
+
const add = yield* Event.trigger(Added)
|
|
156
|
+
return mount({ props: {}, slots: {}, events: { add } })
|
|
157
|
+
}),
|
|
158
|
+
Composition.live(ItemComp, function* () {
|
|
159
|
+
const itemProps = yield* ItemComp.props
|
|
160
|
+
const removeTrigger = yield* Event.trigger(Removed)
|
|
161
|
+
// The item binds its id so the wire event carries no payload.
|
|
162
|
+
const remove = (): void => removeTrigger({ id: itemProps.id })
|
|
163
|
+
return mount({
|
|
164
|
+
props: { label: itemProps.label },
|
|
165
|
+
slots: {},
|
|
166
|
+
events: { remove },
|
|
167
|
+
})
|
|
168
|
+
}),
|
|
169
|
+
Reducer.live(AddItem, (current, event) => [...current, { id: event.id, label: event.label }]),
|
|
170
|
+
Reducer.live(RemoveItem, (current, event) => current.filter((entry) => entry.id !== event.id)),
|
|
171
|
+
).pipe(Layer.provideMerge(presentation), Layer.provideMerge(Engine))
|
|
172
|
+
return scene(ListComp, { provide: [app] })
|
|
173
|
+
}
|
|
174
|
+
export const listScene: typeof makeListScene = makeListScene
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { Layer, Option, Schema as S } from 'effect'
|
|
2
|
+
import {
|
|
3
|
+
Composition,
|
|
4
|
+
Engine,
|
|
5
|
+
State,
|
|
6
|
+
StateGroup,
|
|
7
|
+
Ui,
|
|
8
|
+
mount,
|
|
9
|
+
provide,
|
|
10
|
+
scene,
|
|
11
|
+
type CapturedScene,
|
|
12
|
+
type DerivedContract,
|
|
13
|
+
ui,
|
|
14
|
+
} from '@playfast/reform'
|
|
15
|
+
import { type EngineServices, type WiredUi } from '@playfast/reform/internal'
|
|
16
|
+
|
|
17
|
+
const LabelBase: State.StateDefinition<
|
|
18
|
+
'label',
|
|
19
|
+
Option.Option<string>,
|
|
20
|
+
S.OptionFromSelf<typeof S.String>
|
|
21
|
+
> = State.make('label', S.OptionFromSelf(S.String))
|
|
22
|
+
class Label extends LabelBase {}
|
|
23
|
+
class Labels extends StateGroup.make(Label) {}
|
|
24
|
+
const OptionalUiBase: WiredUi<
|
|
25
|
+
DerivedContract<{ props: S.Struct<{ label: S.Option<typeof S.String> }> }>,
|
|
26
|
+
'Optional'
|
|
27
|
+
> = ui('Optional', { props: S.Struct({ label: S.Option(S.String) }) })
|
|
28
|
+
export class OptionalUi extends OptionalUiBase {}
|
|
29
|
+
|
|
30
|
+
class Optional extends Composition.make('Optional', {
|
|
31
|
+
title: 'Optional',
|
|
32
|
+
ui: OptionalUi,
|
|
33
|
+
states: [Label],
|
|
34
|
+
})<Optional>() {}
|
|
35
|
+
|
|
36
|
+
type OptionalScene = CapturedScene<
|
|
37
|
+
typeof OptionalUi.Contract,
|
|
38
|
+
readonly [typeof Label],
|
|
39
|
+
| EngineServices
|
|
40
|
+
| State.StateStore<'label', Option.Option<string>>
|
|
41
|
+
| Ui.UiService<'Optional'>
|
|
42
|
+
| Composition.CompositionId<'Optional', Optional, never>,
|
|
43
|
+
unknown,
|
|
44
|
+
'Optional',
|
|
45
|
+
Optional
|
|
46
|
+
>
|
|
47
|
+
|
|
48
|
+
const makeOptionScene = (initial: Option.Option<string> = Option.some('hi')): OptionalScene => {
|
|
49
|
+
const presentation = Layer.mergeAll(
|
|
50
|
+
provide(
|
|
51
|
+
OptionalUi,
|
|
52
|
+
Ui.make(OptionalUi, () => null),
|
|
53
|
+
),
|
|
54
|
+
StateGroup.live(Labels, { label: initial }),
|
|
55
|
+
)
|
|
56
|
+
const app = Layer.mergeAll(
|
|
57
|
+
Composition.live(Optional, function* () {
|
|
58
|
+
const label = yield* StateGroup.select(Labels, 'label')
|
|
59
|
+
return mount({ props: { label }, slots: {} })
|
|
60
|
+
}),
|
|
61
|
+
).pipe(Layer.provideMerge(presentation), Layer.provideMerge(Engine))
|
|
62
|
+
return scene(Optional, { provide: [app] })
|
|
63
|
+
}
|
|
64
|
+
export const optionScene: typeof makeOptionScene = makeOptionScene
|