@playfast/reform 1.1.1 → 1.2.1

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.
Files changed (106) hide show
  1. package/README.md +85 -55
  2. package/package.json +17 -17
  3. package/src/boundary/boundary.test.ts +82 -33
  4. package/src/boundary/boundary.ts +141 -32
  5. package/src/calc/asyncCalc.invalidate.test.ts +733 -32
  6. package/src/calc/asyncCalc.test.ts +397 -225
  7. package/src/calc/asyncCalc.ts +115 -100
  8. package/src/calc/asyncCalcTypes.ts +147 -0
  9. package/src/calc/calc.test.ts +13 -16
  10. package/src/calc/calc.ts +44 -18
  11. package/src/calc/calcFamily.test.ts +90 -32
  12. package/src/calc/calcFamily.ts +56 -25
  13. package/src/calc/compose.ts +1 -14
  14. package/src/channel/channel.ts +25 -6
  15. package/src/channel/{procedureRegistry.ts → procedures.ts} +41 -23
  16. package/src/compose/composition.test.ts +19 -0
  17. package/src/compose/composition.ts +168 -81
  18. package/src/compose/compositionTypes.ts +249 -0
  19. package/src/compose/props.ts +13 -6
  20. package/src/compose/provide.ts +187 -46
  21. package/src/compose/slot.ts +156 -19
  22. package/src/compose/structure.test.ts +6 -3
  23. package/src/compose/structure.ts +106 -13
  24. package/src/compose/ui.test.ts +19 -3
  25. package/src/compose/ui.ts +147 -54
  26. package/src/compose/ui.typecheck.ts +40 -4
  27. package/src/definition/definition.ts +22 -9
  28. package/src/event/event.fromSource.test.ts +172 -0
  29. package/src/event/event.test.ts +10 -0
  30. package/src/event/event.ts +102 -18
  31. package/src/feature/feature.mount.test.ts +123 -56
  32. package/src/feature/feature.test.ts +67 -63
  33. package/src/feature/feature.ts +222 -262
  34. package/src/feature/feature.typecheck.ts +224 -91
  35. package/src/feature/featureBinding.ts +130 -20
  36. package/src/feature/featureClass.ts +162 -0
  37. package/src/feature/featureConfig.ts +192 -0
  38. package/src/feature/featureFactory.ts +163 -0
  39. package/src/feature/featureModule.ts +136 -0
  40. package/src/feature/featureModule.typecheck.ts +63 -0
  41. package/src/feature/featureNativeTree.ts +152 -0
  42. package/src/feature/featureRequirement.ts +82 -0
  43. package/src/feature/featureVariants.ts +234 -0
  44. package/src/feature/mountFeature.ts +54 -0
  45. package/src/graph/closure.ts +222 -0
  46. package/src/graph/services.ts +95 -0
  47. package/src/graph/summary.ts +134 -0
  48. package/src/index.ts +171 -245
  49. package/src/internal/bucketCache.ts +39 -0
  50. package/src/internal/capture.ts +9 -4
  51. package/src/internal/env.ts +1 -3
  52. package/src/internal/errors.ts +21 -10
  53. package/src/internal/queryDriver.ts +61 -25
  54. package/src/internal/queryDriverHydrate.ts +45 -0
  55. package/src/internal/queryDriverStore.ts +8 -5
  56. package/src/internal/queryDriverTypes.ts +11 -11
  57. package/src/internal/reuse.test.ts +10 -3
  58. package/src/internal/reuse.ts +5 -2
  59. package/src/internal/scheduler.ts +4 -1
  60. package/src/internal/sources.ts +25 -11
  61. package/src/internal/store.test.ts +27 -6
  62. package/src/internal/track.ts +3 -2
  63. package/src/internal.ts +222 -0
  64. package/src/namespace/namespace.ts +46 -25
  65. package/src/procedure/procedure.ts +38 -11
  66. package/src/reducer/reducer.ts +78 -34
  67. package/src/remote/pendingQueue.ts +145 -0
  68. package/src/remote/remoteState.test.ts +536 -354
  69. package/src/remote/remoteState.ts +145 -106
  70. package/src/remote/remoteState.typecheck.ts +47 -23
  71. package/src/remote/remoteStateMake.ts +97 -0
  72. package/src/remote/remoteStateSend.ts +95 -37
  73. package/src/remote/remoteStateTypes.ts +155 -0
  74. package/src/runtime/appRuntime.activation.test.ts +104 -0
  75. package/src/runtime/appRuntime.test.ts +187 -111
  76. package/src/runtime/appRuntime.ts +36 -130
  77. package/src/runtime/capturedAppRuntime.ts +274 -0
  78. package/src/runtime/eventBudget.test.ts +39 -12
  79. package/src/runtime/eventBudget.ts +9 -8
  80. package/src/runtime/featureMount.ts +94 -0
  81. package/src/runtime/hardening.test.ts +22 -13
  82. package/src/runtime/instrumentation.test.ts +213 -194
  83. package/src/runtime/instrumentation.ts +6 -11
  84. package/src/runtime/loop.test.ts +84 -7
  85. package/src/runtime/loop.ts +69 -40
  86. package/src/runtime/runtimeHandle.ts +124 -0
  87. package/src/scene/featureScene.test.ts +4 -2
  88. package/src/scene/scene.ts +234 -64
  89. package/src/scene/seedScene.test.ts +69 -86
  90. package/src/state/state.nominal.typecheck.ts +68 -0
  91. package/src/state/state.test.ts +17 -0
  92. package/src/state/state.ts +79 -26
  93. package/src/state/stateFamily.test.ts +39 -8
  94. package/src/state/stateFamily.ts +55 -22
  95. package/src/state/stateGroup.ts +53 -17
  96. package/src/state/token.ts +40 -10
  97. package/src/synced/syncedStore.ts +46 -23
  98. package/src/testkit/flight.testkit.ts +84 -0
  99. package/src/wire/tree.test.ts +8 -4
  100. package/src/wire/triggers.test.ts +6 -2
  101. package/src/wire/triggers.ts +14 -10
  102. package/src/calc/asyncCalcDefinitions.ts +0 -48
  103. package/src/internal/ctx.ts +0 -9
  104. package/src/remote/remoteStateDefinition.ts +0 -135
  105. package/src/remote/remoteStateLayers.ts +0 -118
  106. package/src/remote/remoteStateLiveTypes.ts +0 -59
@@ -0,0 +1,192 @@
1
+ import type { Effect } from 'effect'
2
+ import { Schema } from 'effect'
3
+ import type {
4
+ AnyComposition,
5
+ CompositionClass,
6
+ CompositionPropsReceiver,
7
+ HasExactCompositionId,
8
+ } from '../compose/composition'
9
+ import type { UiContract } from '../compose/ui'
10
+ import type { AnyFeatureGraphSummary } from '../graph/closure'
11
+ import type { FeatureLoadFailed } from '../internal/errors'
12
+ import type { Tagged } from '../runtime/bus'
13
+ import {
14
+ type AnyFeatureBinding,
15
+ type FailedProps,
16
+ type FeatureManifest,
17
+ FeatureNativeTypeId,
18
+ type FeatureToken,
19
+ } from './featureBinding'
20
+ import type { AnyLoadedModule, FeatureModule } from './featureModule'
21
+ import type { FeatureRequirement } from './featureRequirement'
22
+
23
+ export interface RuntimeFeatureTree {
24
+ readonly composition: AnyComposition
25
+ readonly load: Effect.Effect<AnyLoadedModule, FeatureLoadFailed>
26
+ readonly binding: AnyFeatureBinding
27
+ readonly eagerModule: AnyLoadedModule | undefined
28
+ readonly supportModule: AnyLoadedModule | undefined
29
+ }
30
+
31
+ export interface RuntimeFeature {
32
+ new (): {}
33
+ readonly manifest: FeatureManifest
34
+ readonly token: FeatureToken<unknown, RuntimeFeatureTree>
35
+ readonly summary: AnyFeatureGraphSummary
36
+ }
37
+
38
+ const isRuntimeFeatureToken = (
39
+ candidate: unknown,
40
+ ): candidate is FeatureToken<unknown, RuntimeFeatureTree> =>
41
+ typeof candidate === 'object' && candidate !== null && FeatureNativeTypeId in candidate
42
+
43
+ export const isFeature = (candidate: unknown): candidate is RuntimeFeature =>
44
+ (typeof candidate === 'function' || typeof candidate === 'object') &&
45
+ candidate !== null &&
46
+ 'token' in candidate &&
47
+ isRuntimeFeatureToken(candidate.token)
48
+
49
+ export type PlaceholderComposition<P> = AnyComposition & CompositionPropsReceiver<P>
50
+
51
+ export interface Placeholders<P> {
52
+ readonly loading: PlaceholderComposition<P>
53
+ readonly failed: PlaceholderComposition<FailedProps>
54
+ }
55
+
56
+ export type FeatureMountProps<
57
+ UsesInput extends boolean,
58
+ CompositionProps,
59
+ InputSchema extends Schema.Schema.AnyNoContext,
60
+ > = UsesInput extends true ? Schema.Schema.Type<InputSchema> : CompositionProps
61
+
62
+ export type FeatureUsesInput<InputSchema extends Schema.Schema.AnyNoContext> = [InputSchema] extends [
63
+ never,
64
+ ]
65
+ ? false
66
+ : true
67
+
68
+ export type FeatureInputValue<InputSchema extends Schema.Schema.AnyNoContext> = [InputSchema] extends [
69
+ never,
70
+ ]
71
+ ? never
72
+ : Schema.Schema.Type<InputSchema>
73
+
74
+ export interface FeatureConfigBaseExternalApi<
75
+ P,
76
+ C extends UiContract,
77
+ S extends ReadonlyArray<unknown>,
78
+ N extends string,
79
+ Identity,
80
+ > {
81
+ readonly composition: CompositionClass<P, C, S, N, Identity>
82
+ readonly boot?: ReadonlyArray<Tagged>
83
+ }
84
+
85
+ export interface ExactFeatureCompositionConfig<Composition extends AnyComposition> {
86
+ readonly composition: Composition
87
+ }
88
+
89
+ export type IncludesComposition<ROut, N extends string, Identity> =
90
+ HasExactCompositionId<ROut, N, Identity> extends true
91
+ ? object
92
+ : {
93
+ readonly missingCompositionService: 'The feature module must provide its root composition'
94
+ }
95
+
96
+ export type EagerFeatureConfigExternalApi<
97
+ P,
98
+ C extends UiContract,
99
+ ROut,
100
+ DirectRequires extends ReadonlyArray<FeatureRequirement>,
101
+ OpenServices,
102
+ S extends ReadonlyArray<unknown>,
103
+ N extends string,
104
+ Identity,
105
+ > = FeatureConfigBaseExternalApi<P, C, S, N, Identity> & {
106
+ readonly loadingStrategy?: 'default'
107
+ readonly module: FeatureModule<ROut, DirectRequires, OpenServices, false, never> &
108
+ IncludesComposition<ROut, N, Identity>
109
+ }
110
+
111
+ export type LazyFeatureConfigExternalApi<
112
+ CompositionProps,
113
+ C extends UiContract,
114
+ ROut,
115
+ DirectRequires extends ReadonlyArray<FeatureRequirement>,
116
+ OpenServices,
117
+ SupportOut,
118
+ SupportDirectRequires extends ReadonlyArray<FeatureRequirement>,
119
+ SupportOpenServices,
120
+ S extends ReadonlyArray<unknown>,
121
+ N extends string,
122
+ Identity,
123
+ InputSchema extends Schema.Schema.AnyNoContext,
124
+ Loading extends AnyComposition,
125
+ Failed extends AnyComposition,
126
+ > = FeatureConfigBaseExternalApi<CompositionProps, C, S, N, Identity> & {
127
+ readonly composition: CompositionClass<CompositionProps, C, S, N, Identity> &
128
+ CompositionPropsReceiver<
129
+ NoInfer<FeatureMountProps<FeatureUsesInput<InputSchema>, CompositionProps, InputSchema>>
130
+ >
131
+ readonly loadingStrategy: 'lazy'
132
+ readonly input?: InputSchema
133
+ readonly load: Effect.Effect<
134
+ FeatureModule<
135
+ ROut,
136
+ DirectRequires,
137
+ OpenServices,
138
+ FeatureUsesInput<InputSchema>,
139
+ FeatureInputValue<InputSchema>
140
+ > &
141
+ IncludesComposition<ROut, N, Identity>,
142
+ FeatureLoadFailed
143
+ >
144
+ readonly placeholder: {
145
+ readonly loading: Loading &
146
+ CompositionPropsReceiver<
147
+ NoInfer<FeatureMountProps<FeatureUsesInput<InputSchema>, CompositionProps, InputSchema>>
148
+ >
149
+ readonly failed: Failed & CompositionPropsReceiver<FailedProps>
150
+ }
151
+ readonly eager?: FeatureModule<
152
+ SupportOut,
153
+ SupportDirectRequires,
154
+ SupportOpenServices,
155
+ false,
156
+ never
157
+ >
158
+ } & (FeatureUsesInput<InputSchema> extends true ? { readonly input: InputSchema } : object)
159
+
160
+ export type MakeImplementationConfigExternalApi =
161
+ | {
162
+ readonly composition: AnyComposition
163
+ readonly boot?: ReadonlyArray<Tagged>
164
+ readonly loadingStrategy?: 'default'
165
+ readonly module: AnyLoadedModule
166
+ }
167
+ | {
168
+ readonly composition: AnyComposition
169
+ readonly boot?: ReadonlyArray<Tagged>
170
+ readonly loadingStrategy: 'lazy'
171
+ readonly input?: Schema.Schema.Any
172
+ readonly load: Effect.Effect<AnyLoadedModule, FeatureLoadFailed>
173
+ readonly placeholder: {
174
+ readonly loading: AnyComposition
175
+ readonly failed: AnyComposition
176
+ }
177
+ readonly eager?: AnyLoadedModule
178
+ }
179
+
180
+ export const isMakeImplementationConfig = (
181
+ candidate: unknown,
182
+ ): candidate is MakeImplementationConfigExternalApi =>
183
+ typeof candidate === 'object' &&
184
+ candidate !== null &&
185
+ 'composition' in candidate &&
186
+ (!('input' in candidate) || Schema.isSchema(candidate.input)) &&
187
+ ('module' in candidate || ('load' in candidate && 'placeholder' in candidate))
188
+
189
+ export type RuntimePlaceholders = {
190
+ readonly loading: AnyComposition
191
+ readonly failed: AnyComposition
192
+ }
@@ -0,0 +1,163 @@
1
+ import { type Effect, Option, Schema } from 'effect'
2
+ import type { AnyComposition, CompositionClass } from '../compose/composition'
3
+ import type { UiContract } from '../compose/ui'
4
+ import {
5
+ type AnyFeatureGraphSummary,
6
+ FeatureChildSummaryTypeId,
7
+ FeatureGraphSummaryTypeId,
8
+ FeatureGraphValidTypeId,
9
+ FeatureModuleGraphValidTypeId,
10
+ type FeatureSummaryCompositionIdentity,
11
+ type FeatureSummaryCompositionProps,
12
+ type FeatureSummaryContract,
13
+ type FeatureSummaryInput,
14
+ type FeatureSummaryName,
15
+ type FeatureSummaryOpenServices,
16
+ type FeatureSummaryStates,
17
+ type FeatureSummaryUsesInput,
18
+ SlotBindingSummaryTypeId,
19
+ } from '../graph/closure'
20
+ import type { FeatureLoadFailed } from '../internal/errors'
21
+ import type { Tagged } from '../runtime/bus'
22
+ import {
23
+ type FeatureBinding,
24
+ type FeatureBindingCapture,
25
+ FeatureBindingTypeId,
26
+ type FeatureManifest,
27
+ } from './featureBinding'
28
+ import type { RuntimePlaceholders } from './featureConfig'
29
+ import type { LoadedModule } from './featureModule'
30
+ import type { FeatureSummaryOf } from './featureNativeTree'
31
+ import type { FeatureRequirement } from './featureRequirement'
32
+
33
+ export const makeFeatureManifest = (
34
+ name: string,
35
+ strategy: 'lazy' | 'default',
36
+ composition: AnyComposition,
37
+ placeholder: Option.Option<RuntimePlaceholders>,
38
+ ): FeatureManifest => ({
39
+ kind: 'Feature',
40
+ name,
41
+ strategy,
42
+ composition: composition.manifest,
43
+ placeholder: Option.map(placeholder, (placeholders) => ({
44
+ loading: placeholders.loading.manifest,
45
+ failed: placeholders.failed.manifest,
46
+ })),
47
+ })
48
+
49
+ const makeFeatureInputParser =
50
+ (
51
+ composition: AnyComposition,
52
+ inputSchema: Schema.Schema.AnyNoContext | undefined,
53
+ placeholder: Option.Option<RuntimePlaceholders>,
54
+ ): ((input: unknown) => Option.Option<unknown>) =>
55
+ (input) =>
56
+ Option.flatMap(
57
+ Option.match(Option.fromNullable(inputSchema), {
58
+ onNone: () => Option.some(input),
59
+ onSome: (schema) => Schema.validateOption(schema)(input),
60
+ }),
61
+ (decoded) =>
62
+ Option.flatMap(composition.parseProps(decoded), () =>
63
+ Option.match(placeholder, {
64
+ onNone: () => Option.some(decoded),
65
+ onSome: (placeholders) =>
66
+ Option.map(placeholders.loading.parseProps(decoded), () => decoded),
67
+ }),
68
+ ),
69
+ )
70
+
71
+ export const makeFeatureSummary = <
72
+ FeatureIdentity,
73
+ MountProps,
74
+ CompositionProps,
75
+ C extends UiContract,
76
+ S extends ReadonlyArray<unknown>,
77
+ N extends string,
78
+ Identity,
79
+ OpenServices,
80
+ SupportOpenServices,
81
+ UsesInput extends boolean,
82
+ Input,
83
+ >(
84
+ composition: CompositionClass<CompositionProps, C, S, N, Identity>,
85
+ ): FeatureSummaryOf<
86
+ FeatureIdentity,
87
+ MountProps,
88
+ CompositionProps,
89
+ C,
90
+ S,
91
+ N,
92
+ Identity,
93
+ OpenServices,
94
+ SupportOpenServices,
95
+ UsesInput,
96
+ Input
97
+ > => ({
98
+ [SlotBindingSummaryTypeId]: SlotBindingSummaryTypeId,
99
+ [FeatureChildSummaryTypeId]: {
100
+ featureIdentity: [],
101
+ compositionIdentity: [],
102
+ },
103
+ [FeatureGraphSummaryTypeId]: {
104
+ featureIdentity: [],
105
+ mountProps: [],
106
+ compositionProps: [],
107
+ contract: [],
108
+ states: [],
109
+ name: composition.manifest.name,
110
+ compositionIdentity: [],
111
+ openServices: [],
112
+ supportOpenServices: [],
113
+ usesInput: [],
114
+ input: [],
115
+ validated: FeatureModuleGraphValidTypeId,
116
+ },
117
+ })
118
+
119
+ export const makeFeatureBinding = <
120
+ Summary extends AnyFeatureGraphSummary,
121
+ ROut,
122
+ DirectRequires extends ReadonlyArray<FeatureRequirement>,
123
+ >(
124
+ identity: symbol,
125
+ manifest: FeatureManifest,
126
+ summary: Summary,
127
+ composition: CompositionClass<
128
+ FeatureSummaryCompositionProps<Summary>,
129
+ FeatureSummaryContract<Summary>,
130
+ FeatureSummaryStates<Summary>,
131
+ FeatureSummaryName<Summary>,
132
+ FeatureSummaryCompositionIdentity<Summary>
133
+ >,
134
+ load: Effect.Effect<
135
+ LoadedModule<
136
+ ROut,
137
+ DirectRequires,
138
+ FeatureSummaryOpenServices<Summary>,
139
+ FeatureSummaryUsesInput<Summary>,
140
+ FeatureSummaryInput<Summary>
141
+ >,
142
+ FeatureLoadFailed
143
+ >,
144
+ placeholder: Option.Option<RuntimePlaceholders>,
145
+ inputSchema: Schema.Schema.AnyNoContext | undefined,
146
+ boot: ReadonlyArray<Tagged>,
147
+ ): FeatureBinding<Summary, ROut, DirectRequires> => {
148
+ const binding: FeatureBinding<Summary, ROut, DirectRequires> = {
149
+ [FeatureBindingTypeId]: FeatureBindingTypeId,
150
+ [FeatureGraphValidTypeId]: FeatureGraphValidTypeId,
151
+ manifest,
152
+ identity,
153
+ summary,
154
+ composition,
155
+ strategy: manifest.strategy,
156
+ load,
157
+ placeholder,
158
+ parseInput: makeFeatureInputParser(composition, inputSchema, placeholder),
159
+ boot,
160
+ capture: <Result>(visit: FeatureBindingCapture<Result>): Result => visit(binding),
161
+ }
162
+ return binding
163
+ }
@@ -0,0 +1,136 @@
1
+ import { Effect, type Layer, type Schema } from 'effect'
2
+ import {
3
+ FeatureModuleGraphValidTypeId,
4
+ type ModuleOpenServices,
5
+ type ValidateFeatureGraphLayer,
6
+ } from '../graph/closure'
7
+ import { FeatureLoadFailed } from '../internal/errors'
8
+ import type {
9
+ DefaultFeatureOpenServices,
10
+ FeatureModuleInput,
11
+ FeatureRequirement,
12
+ } from './featureRequirement'
13
+
14
+ export interface FeatureModule<
15
+ ROut,
16
+ DirectRequires extends ReadonlyArray<FeatureRequirement>,
17
+ OpenServices = DefaultFeatureOpenServices<ROut, DirectRequires>,
18
+ UsesInput extends boolean = false,
19
+ Input = never,
20
+ > extends AnyLoadedModule {
21
+ readonly [FeatureModuleGraphValidTypeId]: FeatureModuleGraphValidTypeId
22
+ readonly requires: DirectRequires
23
+ readonly usesInput: UsesInput
24
+ readonly layer: Layer.Layer<ROut, never, FeatureModuleInput<OpenServices, UsesInput>>
25
+ readonly inputSchema: UsesInput extends true ? Schema.Schema.AnyNoContext : undefined
26
+ readonly Input: (input: Input) => void
27
+ }
28
+
29
+ export type LoadedModuleCapture<Result> = <
30
+ ROut,
31
+ DirectRequires extends ReadonlyArray<FeatureRequirement>,
32
+ OpenServices,
33
+ UsesInput extends boolean,
34
+ Input,
35
+ >(
36
+ module: LoadedModule<ROut, DirectRequires, OpenServices, UsesInput, Input>,
37
+ ) => Result
38
+
39
+ export interface AnyLoadedModule {
40
+ readonly capture: <Result>(visit: LoadedModuleCapture<Result>) => Result
41
+ }
42
+
43
+ export type FeatureLayerOutput<L extends Layer.Layer.Any> = Layer.Layer.Success<L>
44
+
45
+ // Reject erased contexts before graph validation can mistake them for permissive inputs.
46
+ export type ExactFeatureLayer<L extends Layer.Layer.Any, UsesInput extends boolean> = Layer.Layer<
47
+ FeatureLayerOutput<L>,
48
+ never,
49
+ FeatureModuleInput<ModuleOpenServices<L, UsesInput>, UsesInput>
50
+ > &
51
+ (unknown extends Layer.Layer.Context<L>
52
+ ? {
53
+ readonly featureLayerContextMustBeExact: 'A feature layer context cannot be erased'
54
+ }
55
+ : object)
56
+
57
+ export const featureModule = <
58
+ const DirectRequires extends ReadonlyArray<FeatureRequirement>,
59
+ L extends Layer.Layer.Any,
60
+ >(
61
+ requires: DirectRequires,
62
+ layer: L & ExactFeatureLayer<L, false> & ValidateFeatureGraphLayer<L, DirectRequires, false>,
63
+ ): FeatureModule<
64
+ FeatureLayerOutput<L>,
65
+ DirectRequires,
66
+ ModuleOpenServices<L, false>,
67
+ false,
68
+ never
69
+ > => {
70
+ const module: FeatureModule<
71
+ FeatureLayerOutput<L>,
72
+ DirectRequires,
73
+ ModuleOpenServices<L, false>,
74
+ false,
75
+ never
76
+ > = {
77
+ [FeatureModuleGraphValidTypeId]: FeatureModuleGraphValidTypeId,
78
+ requires,
79
+ usesInput: false,
80
+ layer,
81
+ inputSchema: undefined,
82
+ Input: () => undefined,
83
+ capture: <Result>(visit: LoadedModuleCapture<Result>): Result => visit(module),
84
+ }
85
+ return module
86
+ }
87
+
88
+ export const featureModuleFromInput = <
89
+ const InputSchema extends Schema.Schema.AnyNoContext,
90
+ const DirectRequires extends ReadonlyArray<FeatureRequirement>,
91
+ L extends Layer.Layer.Any,
92
+ >(
93
+ input: InputSchema,
94
+ requires: DirectRequires,
95
+ layer: L & ExactFeatureLayer<L, true> & ValidateFeatureGraphLayer<L, DirectRequires, true>,
96
+ ): FeatureModule<
97
+ FeatureLayerOutput<L>,
98
+ DirectRequires,
99
+ ModuleOpenServices<L, true>,
100
+ true,
101
+ Schema.Schema.Type<InputSchema>
102
+ > => {
103
+ const module: FeatureModule<
104
+ FeatureLayerOutput<L>,
105
+ DirectRequires,
106
+ ModuleOpenServices<L, true>,
107
+ true,
108
+ Schema.Schema.Type<InputSchema>
109
+ > = {
110
+ [FeatureModuleGraphValidTypeId]: FeatureModuleGraphValidTypeId,
111
+ requires,
112
+ usesInput: true,
113
+ layer,
114
+ inputSchema: input,
115
+ Input: () => undefined,
116
+ capture: <Result>(visit: LoadedModuleCapture<Result>): Result => visit(module),
117
+ }
118
+ return module
119
+ }
120
+
121
+ // Seal dynamic imports as typed Effects so chunk failures become FeatureLoadFailed.
122
+ export const lazyImport = <M>(
123
+ thunk: () => Promise<{ readonly default: M }>,
124
+ ): Effect.Effect<M, FeatureLoadFailed> =>
125
+ Effect.tryPromise({
126
+ try: thunk,
127
+ catch: (cause) => new FeatureLoadFailed({ cause }),
128
+ }).pipe(Effect.map((module) => module.default))
129
+
130
+ export interface LoadedModule<
131
+ ROut,
132
+ DirectRequires extends ReadonlyArray<FeatureRequirement>,
133
+ OpenServices = DefaultFeatureOpenServices<ROut, DirectRequires>,
134
+ UsesInput extends boolean = false,
135
+ Input = never,
136
+ > extends FeatureModule<ROut, DirectRequires, OpenServices, UsesInput, Input> {}
@@ -0,0 +1,63 @@
1
+ import { Context, Layer, Schema as S } from 'effect'
2
+ import * as State from '../state/state'
3
+ import * as StateGroup from '../state/stateGroup'
4
+ import { type EngineServices, type FeatureModule, featureModule } from './feature'
5
+
6
+ const FeedStateBase: State.StateClass<'feed', string> = State.make('feed', S.String)
7
+ class FeedState extends FeedStateBase {}
8
+ const FilterStateBase: State.StateClass<'filter', string> = State.make('filter', S.String)
9
+ class FilterState extends FilterStateBase {}
10
+ const TodosStatesBase: StateGroup.StateGroupClass<readonly [typeof FeedState, typeof FilterState]> =
11
+ StateGroup.make(FeedState, FilterState)
12
+ class TodosStates extends TodosStatesBase {}
13
+
14
+ interface ClockService {
15
+ readonly now: () => number
16
+ }
17
+ const ClockBase: Context.TagClass<Clock, 'feature.typecheck.Clock', ClockService> = Context.Tag(
18
+ 'feature.typecheck.Clock',
19
+ )<Clock, ClockService>()
20
+ export class Clock extends ClockBase {}
21
+
22
+ declare const readsFeed: Layer.Layer<
23
+ { readonly _out: true },
24
+ never,
25
+ State.StateStore<'feed', string> | EngineServices
26
+ >
27
+
28
+ declare const readsTodos: Layer.Layer<
29
+ { readonly _out: true },
30
+ never,
31
+ State.StateStore<'feed', string> | State.StateStore<'filter', string> | EngineServices
32
+ >
33
+
34
+ const okState: FeatureModule<{ readonly _out: true }, readonly [typeof FeedState]> = featureModule(
35
+ [FeedState],
36
+ readsFeed,
37
+ )
38
+
39
+ const okGroup: FeatureModule<{ readonly _out: true }, readonly [typeof TodosStates]> =
40
+ featureModule([TodosStates], readsTodos)
41
+
42
+ const badEmpty =
43
+ // @ts-expect-error feature layer reads a store it does not declare in `requires`
44
+ featureModule([], readsFeed)
45
+
46
+ declare const readsClock: Layer.Layer<{ readonly _clock: true }, never, Clock | EngineServices>
47
+
48
+ export const okService: FeatureModule<{ readonly _clock: true }, readonly [typeof Clock]> =
49
+ featureModule([Clock], readsClock)
50
+
51
+ const badService =
52
+ // @ts-expect-error feature layer reads an ordinary Context service it did not declare
53
+ featureModule([], readsClock)
54
+
55
+ declare const privateEngine: Layer.Layer<EngineServices, never, EngineServices>
56
+ // @ts-expect-error a Feature module cannot provide its own Engine services
57
+ const invalidPrivateEngine = featureModule([], privateEngine)
58
+
59
+ void okState
60
+ void okGroup
61
+ void badEmpty
62
+ void badService
63
+ void invalidPrivateEngine
@@ -0,0 +1,152 @@
1
+ import type { Effect } from 'effect'
2
+ import type { CompositionClass } from '../compose/composition'
3
+ import type { UiContract } from '../compose/ui'
4
+ import type {
5
+ AnyFeatureGraphSummary,
6
+ FeatureGraphSummary,
7
+ FeatureSummaryCompositionIdentity,
8
+ FeatureSummaryCompositionProps,
9
+ FeatureSummaryContract,
10
+ FeatureSummaryInput,
11
+ FeatureSummaryName,
12
+ FeatureSummaryOpenServices,
13
+ FeatureSummaryStates,
14
+ FeatureSummarySupportOpenServices,
15
+ FeatureSummaryUsesInput,
16
+ } from '../graph/closure'
17
+ import type { FeatureLoadFailed } from '../internal/errors'
18
+ import type { CapturedFeatureBinding } from './featureBinding'
19
+ import type { FeatureModule } from './featureModule'
20
+ import type { FeatureRequirement } from './featureRequirement'
21
+
22
+ export interface FeatureNativeTree<
23
+ Summary extends AnyFeatureGraphSummary,
24
+ ROut,
25
+ DirectRequires extends ReadonlyArray<FeatureRequirement>,
26
+ SupportOut,
27
+ SupportDirectRequires extends ReadonlyArray<FeatureRequirement>,
28
+ Composition extends CompositionClass<
29
+ FeatureSummaryCompositionProps<Summary>,
30
+ FeatureSummaryContract<Summary>,
31
+ FeatureSummaryStates<Summary>,
32
+ FeatureSummaryName<Summary>,
33
+ FeatureSummaryCompositionIdentity<Summary>
34
+ > = CompositionClass<
35
+ FeatureSummaryCompositionProps<Summary>,
36
+ FeatureSummaryContract<Summary>,
37
+ FeatureSummaryStates<Summary>,
38
+ FeatureSummaryName<Summary>,
39
+ FeatureSummaryCompositionIdentity<Summary>
40
+ >,
41
+ > {
42
+ readonly identity: symbol
43
+ readonly summary: Summary
44
+ readonly composition: Composition
45
+ readonly load: Effect.Effect<
46
+ FeatureModule<
47
+ ROut,
48
+ DirectRequires,
49
+ FeatureSummaryOpenServices<Summary>,
50
+ FeatureSummaryUsesInput<Summary>,
51
+ FeatureSummaryInput<Summary>
52
+ >,
53
+ FeatureLoadFailed
54
+ >
55
+ readonly binding: CapturedFeatureBinding<Summary, ROut, DirectRequires>
56
+ readonly eagerModule:
57
+ | FeatureModule<ROut, DirectRequires, FeatureSummaryOpenServices<Summary>, false, never>
58
+ | undefined
59
+ readonly supportModule:
60
+ | FeatureModule<
61
+ SupportOut,
62
+ SupportDirectRequires,
63
+ FeatureSummarySupportOpenServices<Summary>,
64
+ false,
65
+ never
66
+ >
67
+ | undefined
68
+ }
69
+
70
+ export interface EagerFeatureNativeTree<
71
+ Summary extends AnyFeatureGraphSummary,
72
+ ROut,
73
+ DirectRequires extends ReadonlyArray<FeatureRequirement>,
74
+ Composition extends CompositionClass<
75
+ FeatureSummaryCompositionProps<Summary>,
76
+ FeatureSummaryContract<Summary>,
77
+ FeatureSummaryStates<Summary>,
78
+ FeatureSummaryName<Summary>,
79
+ FeatureSummaryCompositionIdentity<Summary>
80
+ > = CompositionClass<
81
+ FeatureSummaryCompositionProps<Summary>,
82
+ FeatureSummaryContract<Summary>,
83
+ FeatureSummaryStates<Summary>,
84
+ FeatureSummaryName<Summary>,
85
+ FeatureSummaryCompositionIdentity<Summary>
86
+ >,
87
+ > extends FeatureNativeTree<Summary, ROut, DirectRequires, never, readonly [], Composition> {
88
+ readonly eagerModule: FeatureModule<
89
+ ROut,
90
+ DirectRequires,
91
+ FeatureSummaryOpenServices<Summary>,
92
+ false,
93
+ never
94
+ >
95
+ readonly supportModule: undefined
96
+ }
97
+
98
+ export interface LazyFeatureNativeTree<
99
+ Summary extends AnyFeatureGraphSummary,
100
+ ROut,
101
+ DirectRequires extends ReadonlyArray<FeatureRequirement>,
102
+ SupportOut,
103
+ SupportDirectRequires extends ReadonlyArray<FeatureRequirement>,
104
+ Composition extends CompositionClass<
105
+ FeatureSummaryCompositionProps<Summary>,
106
+ FeatureSummaryContract<Summary>,
107
+ FeatureSummaryStates<Summary>,
108
+ FeatureSummaryName<Summary>,
109
+ FeatureSummaryCompositionIdentity<Summary>
110
+ > = CompositionClass<
111
+ FeatureSummaryCompositionProps<Summary>,
112
+ FeatureSummaryContract<Summary>,
113
+ FeatureSummaryStates<Summary>,
114
+ FeatureSummaryName<Summary>,
115
+ FeatureSummaryCompositionIdentity<Summary>
116
+ >,
117
+ > extends FeatureNativeTree<
118
+ Summary,
119
+ ROut,
120
+ DirectRequires,
121
+ SupportOut,
122
+ SupportDirectRequires,
123
+ Composition
124
+ > {
125
+ readonly eagerModule: undefined
126
+ }
127
+
128
+ export type FeatureSummaryOf<
129
+ FeatureIdentity,
130
+ MountProps,
131
+ CompositionProps,
132
+ C extends UiContract,
133
+ S extends ReadonlyArray<unknown>,
134
+ N extends string,
135
+ Identity,
136
+ OpenServices,
137
+ SupportOpenServices,
138
+ UsesInput extends boolean,
139
+ Input,
140
+ > = FeatureGraphSummary<
141
+ FeatureIdentity,
142
+ MountProps,
143
+ CompositionProps,
144
+ C,
145
+ S,
146
+ N,
147
+ Identity,
148
+ OpenServices,
149
+ SupportOpenServices,
150
+ UsesInput,
151
+ Input
152
+ >