@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
package/src/calc/calcFamily.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Context, Effect, Layer, MutableRef } from 'effect'
|
|
1
|
+
import { Context, Effect, Layer, MutableRef, type Schema } from 'effect'
|
|
2
2
|
import { type Manifest, definitionClass } from '../definition/definition'
|
|
3
3
|
import {
|
|
4
4
|
type InputsObject,
|
|
@@ -9,45 +9,70 @@ import {
|
|
|
9
9
|
} from '../internal/sources'
|
|
10
10
|
import { resolveScheduler } from '../internal/scheduler'
|
|
11
11
|
import { resolveInstrumentation, stateUpdateHook } from '../runtime/instrumentation'
|
|
12
|
+
import { reuse } from '../internal/reuse'
|
|
12
13
|
import { makeDerivedStore, type Store } from '../internal/store'
|
|
13
14
|
import { readTracked } from '../internal/track'
|
|
14
15
|
import { type FamilyOptions, type FamilyStore } from '../state/stateFamily'
|
|
15
16
|
import { type AnySource } from '../state/token'
|
|
16
|
-
import type { AnySchema } from '../internal/variance'
|
|
17
17
|
|
|
18
|
-
export interface
|
|
18
|
+
export interface CalcFamilySchemaReflection {
|
|
19
|
+
readonly ast: Schema.Schema<unknown, unknown, unknown>['ast']
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface CalcFamilyManifest<N extends string> extends Manifest {
|
|
19
23
|
readonly kind: 'CalcFamily'
|
|
20
24
|
readonly name: N
|
|
21
|
-
readonly key:
|
|
22
|
-
readonly output:
|
|
25
|
+
readonly key: CalcFamilySchemaReflection
|
|
26
|
+
readonly output: CalcFamilySchemaReflection
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export const CalcFamilyStoreTypeId: unique symbol = Symbol.for('reform/CalcFamilyStore')
|
|
30
|
+
export type CalcFamilyStoreTypeId = typeof CalcFamilyStoreTypeId
|
|
31
|
+
|
|
32
|
+
export interface CalcFamilyStore<N extends string, in out K, in out Out> extends FamilyStore<
|
|
33
|
+
K,
|
|
34
|
+
Out
|
|
35
|
+
> {
|
|
36
|
+
readonly [CalcFamilyStoreTypeId]: N
|
|
23
37
|
}
|
|
24
38
|
|
|
25
39
|
export interface CalcFamilyClass<
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
40
|
+
N extends string,
|
|
41
|
+
Inputs extends ReadonlyArray<AnySource>,
|
|
42
|
+
K,
|
|
43
|
+
Out,
|
|
30
44
|
> {
|
|
31
45
|
new (): { readonly Key: K }
|
|
32
|
-
readonly manifest: CalcFamilyManifest<N
|
|
33
|
-
readonly store: Context.Tag<
|
|
46
|
+
readonly manifest: CalcFamilyManifest<N>
|
|
47
|
+
readonly store: Context.Tag<CalcFamilyStore<N, K, Out>, FamilyStore<K, Out>>
|
|
34
48
|
readonly inputs: Inputs
|
|
35
49
|
}
|
|
36
50
|
|
|
37
|
-
export interface CalcFamilyConfig<
|
|
38
|
-
|
|
51
|
+
export interface CalcFamilyConfig<
|
|
52
|
+
Inputs extends ReadonlyArray<AnySource>,
|
|
53
|
+
KeySchema extends Schema.Schema.AnyNoContext,
|
|
54
|
+
OutputSchema extends Schema.Schema.AnyNoContext,
|
|
55
|
+
> {
|
|
56
|
+
readonly key: KeySchema
|
|
39
57
|
readonly inputs: Inputs
|
|
40
|
-
readonly output:
|
|
58
|
+
readonly output: OutputSchema
|
|
41
59
|
}
|
|
42
60
|
|
|
43
|
-
export const make = <
|
|
61
|
+
export const make = <
|
|
62
|
+
const N extends string,
|
|
63
|
+
const Inputs extends ReadonlyArray<AnySource>,
|
|
64
|
+
KeySchema extends Schema.Schema.AnyNoContext,
|
|
65
|
+
OutputSchema extends Schema.Schema.AnyNoContext,
|
|
66
|
+
>(
|
|
44
67
|
name: N,
|
|
45
|
-
config: CalcFamilyConfig<Inputs,
|
|
46
|
-
): CalcFamilyClass<N, Inputs,
|
|
47
|
-
|
|
68
|
+
config: CalcFamilyConfig<Inputs, KeySchema, OutputSchema>,
|
|
69
|
+
): CalcFamilyClass<N, Inputs, Schema.Schema.Type<KeySchema>, Schema.Schema.Type<OutputSchema>> => {
|
|
70
|
+
type K = Schema.Schema.Type<KeySchema>
|
|
71
|
+
type Out = Schema.Schema.Type<OutputSchema>
|
|
72
|
+
const store = Context.GenericTag<CalcFamilyStore<N, K, Out>, FamilyStore<K, Out>>(
|
|
48
73
|
`reform/calcFamily/${name}`,
|
|
49
74
|
)
|
|
50
|
-
const manifest: CalcFamilyManifest<N
|
|
75
|
+
const manifest: CalcFamilyManifest<N> = {
|
|
51
76
|
kind: 'CalcFamily',
|
|
52
77
|
name,
|
|
53
78
|
key: config.key,
|
|
@@ -63,19 +88,21 @@ export const make = <const N extends string, const Inputs extends ReadonlyArray<
|
|
|
63
88
|
export const read = <N extends string, Inputs extends ReadonlyArray<AnySource>, K, Out>(
|
|
64
89
|
family: CalcFamilyClass<N, Inputs, K, Out>,
|
|
65
90
|
key: K,
|
|
66
|
-
): Effect.Effect<Out, never,
|
|
91
|
+
): Effect.Effect<Out, never, CalcFamilyStore<N, K, Out>> =>
|
|
67
92
|
Effect.flatMap(family.store, (familyStore) => readTracked(familyStore.at(key)))
|
|
68
93
|
|
|
69
|
-
export interface CalcFamilyLiveOptionsExternalApi<
|
|
70
|
-
extends
|
|
94
|
+
export interface CalcFamilyLiveOptionsExternalApi<
|
|
95
|
+
Inputs extends ReadonlyArray<AnySource>,
|
|
96
|
+
> extends FamilyOptions {
|
|
71
97
|
readonly invalidateBy?: InvalidateBy<Inputs>
|
|
98
|
+
readonly reuse?: boolean
|
|
72
99
|
}
|
|
73
100
|
|
|
74
101
|
export const live = <N extends string, Inputs extends ReadonlyArray<AnySource>, K, Out>(
|
|
75
102
|
family: CalcFamilyClass<N, Inputs, K, Out>,
|
|
76
103
|
compute: (key: K) => (inputs: InputsObject<Inputs>) => Out,
|
|
77
104
|
options: CalcFamilyLiveOptionsExternalApi<Inputs> = {},
|
|
78
|
-
): Layer.Layer<
|
|
105
|
+
): Layer.Layer<CalcFamilyStore<N, K, Out>, never, InputStores<Inputs>> =>
|
|
79
106
|
Layer.scoped(
|
|
80
107
|
family.store,
|
|
81
108
|
Effect.gen(function* () {
|
|
@@ -96,7 +123,9 @@ export const live = <N extends string, Inputs extends ReadonlyArray<AnySource>,
|
|
|
96
123
|
subscribers.delete(key)
|
|
97
124
|
}
|
|
98
125
|
|
|
99
|
-
const createMember = (
|
|
126
|
+
const createMember = (
|
|
127
|
+
key: K,
|
|
128
|
+
): { readonly store: Store<Out>; readonly unsubscribe: () => void } => {
|
|
100
129
|
const body = compute(key)
|
|
101
130
|
const memo = MutableRef.make<
|
|
102
131
|
{ readonly key: ReadonlyArray<unknown>; readonly output: Out } | undefined
|
|
@@ -109,7 +138,9 @@ export const live = <N extends string, Inputs extends ReadonlyArray<AnySource>,
|
|
|
109
138
|
return prev.output
|
|
110
139
|
}
|
|
111
140
|
const end = instrumentation.calcRecomputed(family.manifest.name)
|
|
112
|
-
const
|
|
141
|
+
const fresh = body(args)
|
|
142
|
+
const output =
|
|
143
|
+
options.reuse === true && prev !== undefined ? reuse(prev.output, fresh) : fresh
|
|
113
144
|
end()
|
|
114
145
|
MutableRef.set(memo, { key: memoKey, output })
|
|
115
146
|
return output
|
package/src/calc/compose.ts
CHANGED
|
@@ -13,20 +13,7 @@ export function composeCalcs<LOut, LErr, LIn, AOut, AErr, AIn, BOut, BErr, BIn>(
|
|
|
13
13
|
LErr | AErr | BErr,
|
|
14
14
|
BIn | Exclude<AIn | Exclude<LIn, AOut>, BOut>
|
|
15
15
|
>
|
|
16
|
-
export function composeCalcs<
|
|
17
|
-
LOut,
|
|
18
|
-
LErr,
|
|
19
|
-
LIn,
|
|
20
|
-
AOut,
|
|
21
|
-
AErr,
|
|
22
|
-
AIn,
|
|
23
|
-
BOut,
|
|
24
|
-
BErr,
|
|
25
|
-
BIn,
|
|
26
|
-
COut,
|
|
27
|
-
CErr,
|
|
28
|
-
CIn,
|
|
29
|
-
>(
|
|
16
|
+
export function composeCalcs<LOut, LErr, LIn, AOut, AErr, AIn, BOut, BErr, BIn, COut, CErr, CIn>(
|
|
30
17
|
leaf: Layer.Layer<LOut, LErr, LIn>,
|
|
31
18
|
upstream1: Layer.Layer<AOut, AErr, AIn>,
|
|
32
19
|
upstream2: Layer.Layer<BOut, BErr, BIn>,
|
package/src/channel/channel.ts
CHANGED
|
@@ -1,14 +1,20 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
Chunk,
|
|
3
|
+
Context,
|
|
4
|
+
Duration,
|
|
5
|
+
Effect,
|
|
6
|
+
Exit,
|
|
7
|
+
Layer,
|
|
8
|
+
Match,
|
|
9
|
+
Option,
|
|
10
|
+
Queue,
|
|
11
|
+
Scope,
|
|
12
|
+
Stream,
|
|
13
|
+
} from 'effect'
|
|
2
14
|
import { type Manifest, definitionClass } from '../definition/definition'
|
|
15
|
+
import { makeBucketCache } from '../internal/bucketCache'
|
|
3
16
|
import type { Tagged } from '../runtime/bus'
|
|
4
17
|
import { resolveInstrumentation } from '../runtime/instrumentation'
|
|
5
|
-
import { type ProcedureEntry, Procedures } from './procedureRegistry'
|
|
6
|
-
export {
|
|
7
|
-
type ProcedureEntry,
|
|
8
|
-
type ProcedureRegistry,
|
|
9
|
-
Procedures,
|
|
10
|
-
proceduresLayer,
|
|
11
|
-
} from './procedureRegistry'
|
|
12
18
|
|
|
13
19
|
export type ChannelPolicy =
|
|
14
20
|
| { readonly _tag: 'merge' }
|
|
@@ -38,6 +44,111 @@ export interface ChannelConfig {
|
|
|
38
44
|
readonly policy: ChannelPolicy
|
|
39
45
|
}
|
|
40
46
|
|
|
47
|
+
export interface ProcedureEntry {
|
|
48
|
+
readonly name: string
|
|
49
|
+
readonly channelName: string
|
|
50
|
+
readonly handles: ReadonlySet<string>
|
|
51
|
+
readonly run: (event: Tagged) => Effect.Effect<unknown, unknown, never>
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export interface ProcedureRegistry {
|
|
55
|
+
readonly entries: Array<ProcedureEntry>
|
|
56
|
+
readonly byName: Map<string, Set<ProcedureEntry>>
|
|
57
|
+
readonly byTag: Map<string, Set<ProcedureEntry>>
|
|
58
|
+
readonly channelsByTag: Map<string, Set<string>>
|
|
59
|
+
readonly channelsFor: (tag: string) => ReadonlyArray<string>
|
|
60
|
+
readonly byChannelTag: Map<string, Map<string, Set<ProcedureEntry>>>
|
|
61
|
+
readonly register: (entry: ProcedureEntry) => void
|
|
62
|
+
readonly unregister: (entry: ProcedureEntry) => void
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const ProceduresBase: Context.TagClass<Procedures, 'reform/Procedures', ProcedureRegistry> =
|
|
66
|
+
Context.Tag('reform/Procedures')<Procedures, ProcedureRegistry>()
|
|
67
|
+
export class Procedures extends ProceduresBase {}
|
|
68
|
+
|
|
69
|
+
const getOrCreate = <K, V>(map: Map<K, V>, key: K, make: () => V): V => {
|
|
70
|
+
const existing = map.get(key)
|
|
71
|
+
if (existing !== undefined) {
|
|
72
|
+
return existing
|
|
73
|
+
}
|
|
74
|
+
const created = make()
|
|
75
|
+
map.set(key, created)
|
|
76
|
+
return created
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const dropFrom = <K, T>(map: Map<K, Set<T>>, key: K, target: T): void => {
|
|
80
|
+
const bucket = map.get(key)
|
|
81
|
+
if (bucket === undefined) {
|
|
82
|
+
return
|
|
83
|
+
}
|
|
84
|
+
bucket.delete(target)
|
|
85
|
+
if (bucket.size === 0) {
|
|
86
|
+
map.delete(key)
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const makeProcedureRegistry = (): ProcedureRegistry => {
|
|
91
|
+
const entries = new Set<ProcedureEntry>()
|
|
92
|
+
const byName = new Map<string, Set<ProcedureEntry>>()
|
|
93
|
+
const byTag = new Map<string, Set<ProcedureEntry>>()
|
|
94
|
+
const channelsByTag = new Map<string, Set<string>>()
|
|
95
|
+
const channelsFor = makeBucketCache(channelsByTag)
|
|
96
|
+
const byChannelTag = new Map<string, Map<string, Set<ProcedureEntry>>>()
|
|
97
|
+
const snapshot: { value: Option.Option<Array<ProcedureEntry>> } = {
|
|
98
|
+
value: Option.some([]),
|
|
99
|
+
}
|
|
100
|
+
return {
|
|
101
|
+
get entries() {
|
|
102
|
+
return Option.getOrElse(snapshot.value, () => {
|
|
103
|
+
const current = Array.from(entries)
|
|
104
|
+
snapshot.value = Option.some(current)
|
|
105
|
+
return current
|
|
106
|
+
})
|
|
107
|
+
},
|
|
108
|
+
byName,
|
|
109
|
+
byTag,
|
|
110
|
+
channelsByTag,
|
|
111
|
+
channelsFor: channelsFor.read,
|
|
112
|
+
byChannelTag,
|
|
113
|
+
register: (entry) => {
|
|
114
|
+
entries.add(entry)
|
|
115
|
+
snapshot.value = Option.none()
|
|
116
|
+
getOrCreate(byName, entry.name, () => new Set()).add(entry)
|
|
117
|
+
entry.handles.forEach((tag) => {
|
|
118
|
+
getOrCreate(byTag, tag, () => new Set()).add(entry)
|
|
119
|
+
getOrCreate(channelsByTag, tag, () => new Set()).add(entry.channelName)
|
|
120
|
+
channelsFor.invalidate(tag)
|
|
121
|
+
const channelMap = getOrCreate(byChannelTag, entry.channelName, () => new Map())
|
|
122
|
+
getOrCreate(channelMap, tag, () => new Set()).add(entry)
|
|
123
|
+
})
|
|
124
|
+
},
|
|
125
|
+
unregister: (entry) => {
|
|
126
|
+
entries.delete(entry)
|
|
127
|
+
snapshot.value = Option.none()
|
|
128
|
+
dropFrom(byName, entry.name, entry)
|
|
129
|
+
entry.handles.forEach((tag) => {
|
|
130
|
+
dropFrom(byTag, tag, entry)
|
|
131
|
+
const channelMap = byChannelTag.get(entry.channelName)
|
|
132
|
+
if (channelMap !== undefined) {
|
|
133
|
+
dropFrom(channelMap, tag, entry)
|
|
134
|
+
if (channelMap.get(tag) === undefined) {
|
|
135
|
+
dropFrom(channelsByTag, tag, entry.channelName)
|
|
136
|
+
channelsFor.invalidate(tag)
|
|
137
|
+
}
|
|
138
|
+
if (channelMap.size === 0) {
|
|
139
|
+
byChannelTag.delete(entry.channelName)
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
})
|
|
143
|
+
},
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
export const proceduresLayer: Layer.Layer<Procedures> = Layer.sync(
|
|
148
|
+
Procedures,
|
|
149
|
+
makeProcedureRegistry,
|
|
150
|
+
)
|
|
151
|
+
|
|
41
152
|
export interface ChannelRuntime {
|
|
42
153
|
readonly name: string
|
|
43
154
|
readonly policy: ChannelPolicy
|
|
@@ -53,7 +164,9 @@ const ChannelsBase: Context.TagClass<
|
|
|
53
164
|
> = Context.Tag('reform/Channels')<Channels, { readonly byName: Map<string, ChannelRuntime> }>()
|
|
54
165
|
export class Channels extends ChannelsBase {}
|
|
55
166
|
|
|
56
|
-
export const channelsLayer: Layer.Layer<Channels> = Layer.sync(Channels, () => ({
|
|
167
|
+
export const channelsLayer: Layer.Layer<Channels> = Layer.sync(Channels, () => ({
|
|
168
|
+
byName: new Map(),
|
|
169
|
+
}))
|
|
57
170
|
|
|
58
171
|
export const make = (name: string, config: ChannelConfig): ChannelClass =>
|
|
59
172
|
definitionClass<ChannelClass>({
|
|
@@ -155,9 +268,13 @@ export const live = (channel: ChannelClass): Layer.Layer<never, never, Channels
|
|
|
155
268
|
|
|
156
269
|
const events = Stream.fromQueue(queue)
|
|
157
270
|
const driven: Stream.Stream<unknown> = Match.value(channel.policy).pipe(
|
|
158
|
-
Match.tag('merge', () =>
|
|
271
|
+
Match.tag('merge', () =>
|
|
272
|
+
Stream.mapEffect(events, runMatching, { concurrency: 'unbounded' }),
|
|
273
|
+
),
|
|
159
274
|
Match.tag('latest', () =>
|
|
160
|
-
Stream.flatMap(events, (event) => Stream.fromEffect(runMatching(event)), {
|
|
275
|
+
Stream.flatMap(events, (event) => Stream.fromEffect(runMatching(event)), {
|
|
276
|
+
switch: true,
|
|
277
|
+
}),
|
|
161
278
|
),
|
|
162
279
|
Match.tag('debounce', (policy) =>
|
|
163
280
|
events.pipe(
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { Option, Schema } from 'effect'
|
|
2
|
+
import { expect, it } from '@effect/vitest'
|
|
3
|
+
import { make } from './composition'
|
|
4
|
+
import { ui } from './ui'
|
|
5
|
+
|
|
6
|
+
class ItemUi extends ui('composition.parser.ItemUi')<{
|
|
7
|
+
props: { readonly label: string }
|
|
8
|
+
}>() {}
|
|
9
|
+
|
|
10
|
+
class Item extends make('composition.parser.Item', {
|
|
11
|
+
title: 'Item',
|
|
12
|
+
props: Schema.Struct({ label: Schema.String }),
|
|
13
|
+
ui: ItemUi,
|
|
14
|
+
})<Item>() {}
|
|
15
|
+
|
|
16
|
+
it('validates reflected props against the composition schema', () => {
|
|
17
|
+
expect(Item.parseProps({ label: 'ready' })).toEqual(Option.some({ label: 'ready' }))
|
|
18
|
+
expect(Option.isNone(Item.parseProps({ label: 1 }))).toBe(true)
|
|
19
|
+
})
|