@typeonce/ritmo 0.6.0 → 0.7.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 +2 -0
- package/dist/Brand.d.ts +9 -4
- package/dist/Brand.d.ts.map +1 -1
- package/dist/Brand.js +9 -4
- package/dist/Brand.js.map +1 -1
- package/dist/Clock.d.ts +186 -0
- package/dist/Clock.d.ts.map +1 -0
- package/dist/Clock.js +111 -0
- package/dist/Clock.js.map +1 -0
- package/dist/Command.d.ts +157 -33
- package/dist/Command.d.ts.map +1 -1
- package/dist/Command.js +49 -15
- package/dist/Command.js.map +1 -1
- package/dist/Debug.d.ts +104 -12
- package/dist/Debug.d.ts.map +1 -1
- package/dist/Debug.js.map +1 -1
- package/dist/Decode.d.ts +1 -1
- package/dist/Decode.js +1 -1
- package/dist/Descriptor.d.ts +31 -21
- package/dist/Descriptor.d.ts.map +1 -1
- package/dist/Descriptor.js +21 -12
- package/dist/Descriptor.js.map +1 -1
- package/dist/Entity.d.ts +14 -5
- package/dist/Entity.d.ts.map +1 -1
- package/dist/Entity.js +2 -2
- package/dist/Entity.js.map +1 -1
- package/dist/EntityScope.d.ts +6 -0
- package/dist/EntityScope.d.ts.map +1 -1
- package/dist/EntityScope.js +6 -0
- package/dist/EntityScope.js.map +1 -1
- package/dist/HostClock.d.ts +49 -0
- package/dist/HostClock.d.ts.map +1 -0
- package/dist/HostClock.js +46 -0
- package/dist/HostClock.js.map +1 -0
- package/dist/Inspector.d.ts +7 -1
- package/dist/Inspector.d.ts.map +1 -1
- package/dist/Inspector.js.map +1 -1
- package/dist/Machine.d.ts +12 -3
- package/dist/Machine.d.ts.map +1 -1
- package/dist/Machine.js +4 -1
- package/dist/Machine.js.map +1 -1
- package/dist/Query.d.ts +28 -24
- package/dist/Query.d.ts.map +1 -1
- package/dist/Query.js +0 -24
- package/dist/Query.js.map +1 -1
- package/dist/Runtime.d.ts +36 -16
- package/dist/Runtime.d.ts.map +1 -1
- package/dist/Runtime.js +230 -67
- package/dist/Runtime.js.map +1 -1
- package/dist/Schedule.d.ts +217 -10
- package/dist/Schedule.d.ts.map +1 -1
- package/dist/Schedule.js +5 -0
- package/dist/Schedule.js.map +1 -1
- package/dist/Schema.d.ts +61 -25
- package/dist/Schema.d.ts.map +1 -1
- package/dist/Schema.js +0 -8
- package/dist/Schema.js.map +1 -1
- package/dist/System.d.ts +149 -52
- package/dist/System.d.ts.map +1 -1
- package/dist/System.js +52 -6
- package/dist/System.js.map +1 -1
- package/dist/index.d.ts +8 -7
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +8 -7
- package/dist/index.js.map +1 -1
- package/dist/internal/cells.d.ts +3 -3
- package/dist/internal/cells.d.ts.map +1 -1
- package/dist/internal/cells.js +9 -11
- package/dist/internal/cells.js.map +1 -1
- package/dist/internal/debug.d.ts +6 -0
- package/dist/internal/debug.d.ts.map +1 -1
- package/dist/internal/debug.js +95 -1
- package/dist/internal/debug.js.map +1 -1
- package/dist/internal/game.d.ts.map +1 -1
- package/dist/internal/game.js +9 -2
- package/dist/internal/game.js.map +1 -1
- package/dist/internal/order.d.ts +10 -0
- package/dist/internal/order.d.ts.map +1 -0
- package/dist/internal/order.js +120 -0
- package/dist/internal/order.js.map +1 -0
- package/dist/internal/queries.js +2 -2
- package/dist/internal/queries.js.map +1 -1
- package/package.json +1 -1
- package/src/Brand.ts +9 -4
- package/src/Clock.ts +311 -0
- package/src/Command.ts +271 -73
- package/src/Debug.ts +111 -10
- package/src/Decode.ts +1 -1
- package/src/Descriptor.ts +37 -27
- package/src/Entity.ts +15 -5
- package/src/EntityScope.ts +6 -0
- package/src/HostClock.ts +64 -0
- package/src/Inspector.ts +7 -2
- package/src/Machine.ts +13 -3
- package/src/Query.ts +41 -33
- package/src/Runtime.ts +273 -91
- package/src/Schedule.ts +380 -13
- package/src/Schema.ts +70 -41
- package/src/System.ts +174 -88
- package/src/index.ts +8 -7
- package/src/internal/cells.ts +10 -12
- package/src/internal/debug.ts +104 -4
- package/src/internal/game.ts +11 -3
- package/src/internal/order.ts +140 -0
- package/src/internal/queries.ts +3 -3
- package/dist/Definition.d.ts +0 -99
- package/dist/Definition.d.ts.map +0 -1
- package/dist/Definition.js +0 -68
- package/dist/Definition.js.map +0 -1
- package/dist/Feature.d.ts +0 -106
- package/dist/Feature.d.ts.map +0 -1
- package/dist/Feature.js +0 -87
- package/dist/Feature.js.map +0 -1
- package/dist/Fx.d.ts +0 -128
- package/dist/Fx.d.ts.map +0 -1
- package/dist/Fx.js +0 -112
- package/dist/Fx.js.map +0 -1
- package/dist/internal/definition.d.ts +0 -17
- package/dist/internal/definition.d.ts.map +0 -1
- package/dist/internal/definition.js +0 -25
- package/dist/internal/definition.js.map +0 -1
- package/src/Definition.ts +0 -106
- package/src/Feature.ts +0 -241
- package/src/Fx.ts +0 -176
- package/src/internal/definition.ts +0 -56
package/src/Feature.ts
DELETED
|
@@ -1,241 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Pre-bind features: reusable slices that contribute a schema fragment and the
|
|
3
|
-
* schedules that operate on it.
|
|
4
|
-
*
|
|
5
|
-
* A feature builds its schedules only after the final merged schema is bound,
|
|
6
|
-
* and its builder can only reach descriptors from its own fragment and from
|
|
7
|
-
* the features it `requires`.
|
|
8
|
-
*
|
|
9
|
-
* @module Feature
|
|
10
|
-
* @docGroup core
|
|
11
|
-
*
|
|
12
|
-
* @example
|
|
13
|
-
* ```ts
|
|
14
|
-
* const Combat = Schema.Feature.define("Combat", {
|
|
15
|
-
* schema: CombatSchema,
|
|
16
|
-
* requires: [Core],
|
|
17
|
-
* build: (Game) => ({ update: [Game.Schedule(Attack, Game.Schedule.applyDeferred())] })
|
|
18
|
-
* })
|
|
19
|
-
*
|
|
20
|
-
* const project = Schema.Feature.compose({ root: Root, features: [Core, Combat] })
|
|
21
|
-
* const runtime = project.Game.Runtime.make({ services: project.Game.Runtime.services() })
|
|
22
|
-
* runtime.tick(...project.schedules.update)
|
|
23
|
-
* ```
|
|
24
|
-
*/
|
|
25
|
-
import { buildSchema } from "./internal/fragments.ts"
|
|
26
|
-
import { makeGame } from "./internal/game.ts"
|
|
27
|
-
import type * as Schedule from "./Schedule.ts"
|
|
28
|
-
import type { MergeSchemaDefinitions, Schema, SchemaDefinition, ValidateFragments } from "./Schema.ts"
|
|
29
|
-
|
|
30
|
-
type EmptySchema = SchemaDefinition<{}, {}, {}, {}>
|
|
31
|
-
|
|
32
|
-
type AnyFeature = {
|
|
33
|
-
readonly kind: "feature"
|
|
34
|
-
readonly name: string
|
|
35
|
-
readonly schema: Schema.Any
|
|
36
|
-
readonly requires: ReadonlyArray<AnyFeature>
|
|
37
|
-
readonly build: unknown
|
|
38
|
-
readonly output: FeatureBuildOutput
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
type AnySchedule = Schedule.ScheduleDefinition<any, any, any, any, any>
|
|
42
|
-
|
|
43
|
-
/**
|
|
44
|
-
* What a feature builder returns: bootstrap and update schedules, plus any
|
|
45
|
-
* other values the feature wants to expose on the composed project.
|
|
46
|
-
*/
|
|
47
|
-
export type FeatureBuildOutput = {
|
|
48
|
-
readonly bootstrap?: ReadonlyArray<AnySchedule>
|
|
49
|
-
readonly update?: ReadonlyArray<AnySchedule>
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
/**
|
|
53
|
-
* The bound authoring surface a feature builder receives. It is the normal
|
|
54
|
-
* `Game` API restricted to the descriptors the feature can see; runtimes are
|
|
55
|
-
* built from the composed project instead.
|
|
56
|
-
*/
|
|
57
|
-
export type FeatureBuildGame<Accessible extends Schema.Any, Root = unknown> =
|
|
58
|
-
Omit<Schema.Game<Accessible, Root>, "Runtime">
|
|
59
|
-
|
|
60
|
-
type FeatureBuildFunction<
|
|
61
|
-
Accessible extends Schema.Any,
|
|
62
|
-
Output extends FeatureBuildOutput = FeatureBuildOutput
|
|
63
|
-
> = (Game: FeatureBuildGame<Accessible>) => Output
|
|
64
|
-
|
|
65
|
-
export interface FeatureDefinition<
|
|
66
|
-
Name extends string = string,
|
|
67
|
-
FeatureSchema extends Schema.Any = Schema.Any,
|
|
68
|
-
Requires extends ReadonlyArray<AnyFeature> = ReadonlyArray<AnyFeature>,
|
|
69
|
-
Output extends FeatureBuildOutput = FeatureBuildOutput
|
|
70
|
-
> {
|
|
71
|
-
readonly kind: "feature"
|
|
72
|
-
readonly name: Name
|
|
73
|
-
readonly schema: FeatureSchema
|
|
74
|
-
readonly requires: Requires
|
|
75
|
-
readonly build: FeatureBuildFunction<MergeSchemaDefinitions<FeatureSchema, MergeFeatureSchemas<Requires>>, Output>
|
|
76
|
-
readonly output: Output
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
type MergeFeatureSchemas<Features extends ReadonlyArray<AnyFeature>> =
|
|
80
|
-
Features extends readonly [infer Head extends AnyFeature, ...infer Tail extends Array<AnyFeature>]
|
|
81
|
-
? MergeSchemaDefinitions<FeatureClosureSchema<Head>, MergeFeatureSchemas<Tail>>
|
|
82
|
-
: EmptySchema
|
|
83
|
-
|
|
84
|
-
type FeatureClosureSchema<F extends AnyFeature> =
|
|
85
|
-
F extends FeatureDefinition<any, infer FeatureSchema extends Schema.Any, infer Requires extends ReadonlyArray<AnyFeature>, any>
|
|
86
|
-
? MergeSchemaDefinitions<FeatureSchema, MergeFeatureSchemas<Requires>>
|
|
87
|
-
: EmptySchema
|
|
88
|
-
|
|
89
|
-
type FeatureNames<Features extends ReadonlyArray<AnyFeature>> = Features[number]["name"]
|
|
90
|
-
|
|
91
|
-
type DuplicateFeatureNames<
|
|
92
|
-
Features extends ReadonlyArray<AnyFeature>,
|
|
93
|
-
Seen extends string = never
|
|
94
|
-
> = Features extends readonly [infer Head extends AnyFeature, ...infer Tail extends Array<AnyFeature>]
|
|
95
|
-
? Head["name"] extends Seen
|
|
96
|
-
? Head["name"] | DuplicateFeatureNames<Tail, Seen>
|
|
97
|
-
: DuplicateFeatureNames<Tail, Seen | Head["name"]>
|
|
98
|
-
: never
|
|
99
|
-
|
|
100
|
-
type MissingFeatureDependencies<Features extends ReadonlyArray<AnyFeature>> =
|
|
101
|
-
Exclude<Features[number]["requires"][number]["name"], FeatureNames<Features>>
|
|
102
|
-
|
|
103
|
-
type FeatureSchemas<Features extends ReadonlyArray<AnyFeature>> = {
|
|
104
|
-
readonly [K in keyof Features]: Features[K]["schema"]
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
/**
|
|
108
|
-
* Rejects duplicate feature names, missing required features, and schema
|
|
109
|
-
* fragments whose keys or descriptor names collide.
|
|
110
|
-
*/
|
|
111
|
-
type ValidateFeatureSelection<Features extends ReadonlyArray<AnyFeature>> =
|
|
112
|
-
[DuplicateFeatureNames<Features>] extends [never]
|
|
113
|
-
? [MissingFeatureDependencies<Features>] extends [never]
|
|
114
|
-
? ValidateFragments<FeatureSchemas<Features>>
|
|
115
|
-
: { readonly __fixFeatureDependencies__: MissingFeatureDependencies<Features> }
|
|
116
|
-
: { readonly __fixFeatureSelection__: DuplicateFeatureNames<Features> }
|
|
117
|
-
|
|
118
|
-
type RebindFeatureSchedule<ScheduleValue, S extends Schema.Any, Root> =
|
|
119
|
-
ScheduleValue extends Schedule.ScheduleDefinition<any, infer Requirements, any, infer Carried, infer Failure>
|
|
120
|
-
? Schedule.ScheduleDefinition<S, Requirements, Root, Carried, Failure>
|
|
121
|
-
: never
|
|
122
|
-
|
|
123
|
-
type FeatureScheduleArray<Output, Key extends "bootstrap" | "update"> =
|
|
124
|
-
Key extends keyof Output
|
|
125
|
-
? Extract<Output[Key], ReadonlyArray<AnySchedule>>
|
|
126
|
-
: readonly []
|
|
127
|
-
|
|
128
|
-
type NormalizeFeatureOutput<Output extends object, S extends Schema.Any, Root> = {
|
|
129
|
-
readonly [K in Exclude<keyof Output, "bootstrap" | "update">]: Output[K]
|
|
130
|
-
} & {
|
|
131
|
-
readonly bootstrap: ReadonlyArray<RebindFeatureSchedule<FeatureScheduleArray<Output, "bootstrap">[number], S, Root>>
|
|
132
|
-
readonly update: ReadonlyArray<RebindFeatureSchedule<FeatureScheduleArray<Output, "update">[number], S, Root>>
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
type FeatureOutputRecord<Features extends ReadonlyArray<AnyFeature>, S extends Schema.Any, Root> = {
|
|
136
|
-
readonly [K in FeatureNames<Features>]:
|
|
137
|
-
NormalizeFeatureOutput<Extract<Features[number], { readonly name: K }>["output"], S, Root>
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
/**
|
|
141
|
-
* The result of composing features: the merged schema, the bound `Game`,
|
|
142
|
-
* each feature's normalized output, and the aggregated schedules in feature
|
|
143
|
-
* order.
|
|
144
|
-
*/
|
|
145
|
-
export interface ComposedFeatureProject<
|
|
146
|
-
Features extends ReadonlyArray<AnyFeature>,
|
|
147
|
-
Root = unknown,
|
|
148
|
-
S extends Schema.Any = MergeFeatureSchemas<Features>
|
|
149
|
-
> {
|
|
150
|
-
readonly schema: S
|
|
151
|
-
readonly Game: Schema.Game<S, Root>
|
|
152
|
-
readonly features: FeatureOutputRecord<Features, S, Root>
|
|
153
|
-
readonly schedules: {
|
|
154
|
-
readonly bootstrap: ReadonlyArray<FeatureOutputRecord<Features, S, Root>[FeatureNames<Features>]["bootstrap"][number]>
|
|
155
|
-
readonly update: ReadonlyArray<FeatureOutputRecord<Features, S, Root>[FeatureNames<Features>]["update"][number]>
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
/**
|
|
160
|
-
* Defines one pre-bind feature.
|
|
161
|
-
*/
|
|
162
|
-
export const define = <
|
|
163
|
-
const Name extends string,
|
|
164
|
-
FeatureSchema extends Schema.Any,
|
|
165
|
-
const Requires extends ReadonlyArray<AnyFeature> = [],
|
|
166
|
-
Output extends FeatureBuildOutput = FeatureBuildOutput
|
|
167
|
-
>(
|
|
168
|
-
name: Name,
|
|
169
|
-
options: {
|
|
170
|
-
readonly schema: FeatureSchema
|
|
171
|
-
readonly requires?: Requires
|
|
172
|
-
readonly build: FeatureBuildFunction<MergeSchemaDefinitions<FeatureSchema, MergeFeatureSchemas<Requires>>, Output>
|
|
173
|
-
}
|
|
174
|
-
): FeatureDefinition<Name, FeatureSchema, Requires, Output> => ({
|
|
175
|
-
kind: "feature",
|
|
176
|
-
name,
|
|
177
|
-
schema: options.schema,
|
|
178
|
-
requires: (options.requires ?? []) as Requires,
|
|
179
|
-
build: options.build,
|
|
180
|
-
output: undefined as unknown as Output
|
|
181
|
-
})
|
|
182
|
-
|
|
183
|
-
/**
|
|
184
|
-
* Composes features into one merged schema and one bound `Game`, then builds
|
|
185
|
-
* every feature against it.
|
|
186
|
-
*
|
|
187
|
-
* Aggregated schedule order is the selected feature order, not dependency
|
|
188
|
-
* order. Run them with `runtime.tick(...project.schedules.update)`.
|
|
189
|
-
*/
|
|
190
|
-
export const compose = <
|
|
191
|
-
Root,
|
|
192
|
-
const Features extends readonly [AnyFeature, ...Array<AnyFeature>]
|
|
193
|
-
>(options: {
|
|
194
|
-
readonly root: Root
|
|
195
|
-
readonly features: Features
|
|
196
|
-
} & ValidateFeatureSelection<Features>): ComposedFeatureProject<Features, Root> => {
|
|
197
|
-
const featureNames = new Set<string>()
|
|
198
|
-
for (const feature of options.features) {
|
|
199
|
-
if (featureNames.has(feature.name)) {
|
|
200
|
-
throw new Error(`Duplicate feature name: ${feature.name}`)
|
|
201
|
-
}
|
|
202
|
-
featureNames.add(feature.name)
|
|
203
|
-
}
|
|
204
|
-
for (const feature of options.features) {
|
|
205
|
-
for (const requirement of feature.requires) {
|
|
206
|
-
if (!featureNames.has(requirement.name)) {
|
|
207
|
-
throw new Error(`Missing required feature: ${requirement.name}`)
|
|
208
|
-
}
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
const schema = buildSchema(options.features.map((feature) => feature.schema))
|
|
213
|
-
const Game = makeGame(schema, options.root)
|
|
214
|
-
const features: Record<string, object> = Object.create(null)
|
|
215
|
-
const bootstrap: Array<AnySchedule> = []
|
|
216
|
-
const update: Array<AnySchedule> = []
|
|
217
|
-
|
|
218
|
-
for (const feature of options.features) {
|
|
219
|
-
const built = (feature.build as (Game: unknown) => FeatureBuildOutput & Record<string, unknown>)(Game)
|
|
220
|
-
const normalized = {
|
|
221
|
-
...built,
|
|
222
|
-
bootstrap: [...(built.bootstrap ?? [])],
|
|
223
|
-
update: [...(built.update ?? [])]
|
|
224
|
-
}
|
|
225
|
-
bootstrap.push(...normalized.bootstrap)
|
|
226
|
-
update.push(...normalized.update)
|
|
227
|
-
features[feature.name] = normalized
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
return {
|
|
231
|
-
schema,
|
|
232
|
-
Game,
|
|
233
|
-
features,
|
|
234
|
-
schedules: { bootstrap, update }
|
|
235
|
-
} as unknown as ComposedFeatureProject<Features, Root>
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
export const Feature = {
|
|
239
|
-
define,
|
|
240
|
-
compose
|
|
241
|
-
}
|
package/src/Fx.ts
DELETED
|
@@ -1,176 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Small explicit effect type used for systems and runtime orchestration.
|
|
3
|
-
*
|
|
4
|
-
* `Fx` models delayed computation with typed success, failure, and environment
|
|
5
|
-
* channels. Expected failures are returned as `Result` values; exceptions are
|
|
6
|
-
* reserved for unexpected defects in user code.
|
|
7
|
-
*
|
|
8
|
-
* Systems that cannot fail return nothing. A system returns an `Fx` when it
|
|
9
|
-
* has an expected failure: `Fx.fail(...)` rolls back the system's ECS writes
|
|
10
|
-
* and surfaces a typed `SystemFailure` from `runtime.tick(...)`. `map` and
|
|
11
|
-
* `flatMap` compose such effects.
|
|
12
|
-
*
|
|
13
|
-
* @example
|
|
14
|
-
* ```ts
|
|
15
|
-
* // Build one small delayed computation.
|
|
16
|
-
* const program = Fx.map(Fx.succeed(1), (value) => value + 1)
|
|
17
|
-
*
|
|
18
|
-
* // Execute it explicitly at the runtime boundary.
|
|
19
|
-
* const result = Fx.runSync(program)
|
|
20
|
-
* ```
|
|
21
|
-
*
|
|
22
|
-
* @module Fx
|
|
23
|
-
* @docGroup runtime
|
|
24
|
-
*
|
|
25
|
-
* @groupDescription Namespaces
|
|
26
|
-
* Grouped helper types that extract success, error, and environment information from effects.
|
|
27
|
-
*
|
|
28
|
-
* @groupDescription Interfaces
|
|
29
|
-
* Public effect contracts used by systems and runtime orchestration.
|
|
30
|
-
*
|
|
31
|
-
* @groupDescription Type Aliases
|
|
32
|
-
* Shared helper types derived from one `Fx` value.
|
|
33
|
-
*
|
|
34
|
-
* @groupDescription Functions
|
|
35
|
-
* Minimal constructors and combinators for explicit delayed computations.
|
|
36
|
-
*/
|
|
37
|
-
import * as Result from "./Result.ts"
|
|
38
|
-
|
|
39
|
-
/**
|
|
40
|
-
* String-literal type id used to mirror Effect's current `~effect/...` style.
|
|
41
|
-
*/
|
|
42
|
-
export type FxTypeId = "~ritmo/Fx"
|
|
43
|
-
|
|
44
|
-
/**
|
|
45
|
-
* Runtime value for the `Fx` type id.
|
|
46
|
-
*/
|
|
47
|
-
const fxTypeId: FxTypeId = "~ritmo/Fx"
|
|
48
|
-
|
|
49
|
-
/**
|
|
50
|
-
* A minimal Effect-style computation used by the runtime.
|
|
51
|
-
*
|
|
52
|
-
* `Fx<A, E, R>` models a computation that succeeds with `A`, may fail with `E`,
|
|
53
|
-
* and requires an environment `R`.
|
|
54
|
-
*/
|
|
55
|
-
export interface Fx<A, E = never, R = unknown> {
|
|
56
|
-
readonly [fxTypeId]: {
|
|
57
|
-
readonly _A: (_: never) => A
|
|
58
|
-
readonly _E: (_: never) => E
|
|
59
|
-
readonly _R: (_: R) => void
|
|
60
|
-
}
|
|
61
|
-
readonly run: (environment: R) => Result.Result<A, E>
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
/**
|
|
65
|
-
* Type-level helpers for `Fx`.
|
|
66
|
-
*/
|
|
67
|
-
export namespace Fx {
|
|
68
|
-
/**
|
|
69
|
-
* Extracts the success type from an effect.
|
|
70
|
-
*/
|
|
71
|
-
export type Success<T extends Fx<any, any, any>> = T extends Fx<infer A, infer _E, infer _R> ? A : never
|
|
72
|
-
/**
|
|
73
|
-
* Extracts the error type from an effect.
|
|
74
|
-
*/
|
|
75
|
-
export type Error<T extends Fx<any, any, any>> = T extends Fx<infer _A, infer E, infer _R> ? E : never
|
|
76
|
-
/**
|
|
77
|
-
* Extracts the required environment from an effect.
|
|
78
|
-
*/
|
|
79
|
-
export type Context<T extends Fx<any, any, any>> = T extends Fx<infer _A, infer _E, infer R> ? R : never
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
/**
|
|
83
|
-
* Low-level constructor for `Fx`.
|
|
84
|
-
*
|
|
85
|
-
* All public constructors eventually delegate here so the runtime shape stays
|
|
86
|
-
* consistent.
|
|
87
|
-
*/
|
|
88
|
-
const make = <A, E, R>(run: (environment: R) => Result.Result<A, E>): Fx<A, E, R> =>
|
|
89
|
-
({
|
|
90
|
-
[fxTypeId]: {
|
|
91
|
-
_A: (_: never) => undefined as A,
|
|
92
|
-
_E: (_: never) => undefined as E,
|
|
93
|
-
_R: (_: R) => undefined
|
|
94
|
-
},
|
|
95
|
-
run
|
|
96
|
-
}) as Fx<A, E, R>
|
|
97
|
-
|
|
98
|
-
/**
|
|
99
|
-
* Creates an effect that immediately succeeds with a value.
|
|
100
|
-
*
|
|
101
|
-
* Use this for pure values that should still participate in the typed effect
|
|
102
|
-
* composition model.
|
|
103
|
-
*/
|
|
104
|
-
export const succeed = <A, R = unknown>(value: A): Fx<A, never, R> =>
|
|
105
|
-
make(() => Result.success(value))
|
|
106
|
-
|
|
107
|
-
/**
|
|
108
|
-
* Wraps a synchronous computation in an `Fx`.
|
|
109
|
-
*
|
|
110
|
-
* This is the most common constructor for system implementations in the current
|
|
111
|
-
* runtime because systems are executed synchronously.
|
|
112
|
-
*/
|
|
113
|
-
export const sync = <A, R = unknown>(evaluate: () => A): Fx<A, never, R> =>
|
|
114
|
-
make(() => Result.success(evaluate()))
|
|
115
|
-
|
|
116
|
-
/**
|
|
117
|
-
* Creates an effect that returns one expected failure when run.
|
|
118
|
-
*/
|
|
119
|
-
export const fail = <E, R = unknown>(error: E): Fx<never, E, R> =>
|
|
120
|
-
make(() => Result.failure(error))
|
|
121
|
-
|
|
122
|
-
/**
|
|
123
|
-
* Transforms the success value of an effect.
|
|
124
|
-
*/
|
|
125
|
-
export const map = <A, B, E, R>(
|
|
126
|
-
self: Fx<A, E, R>,
|
|
127
|
-
f: (value: A) => B
|
|
128
|
-
): Fx<B, E, R> => make((environment) => {
|
|
129
|
-
const result = self.run(environment)
|
|
130
|
-
return result.ok ? Result.success(f(result.value)) : result
|
|
131
|
-
})
|
|
132
|
-
|
|
133
|
-
/**
|
|
134
|
-
* Sequences two effects, allowing the second one to depend on the first result.
|
|
135
|
-
*/
|
|
136
|
-
export const flatMap = <A, B, E1, E2, R1, R2>(
|
|
137
|
-
self: Fx<A, E1, R1>,
|
|
138
|
-
f: (value: A) => Fx<B, E2, R2>
|
|
139
|
-
): Fx<B, E1 | E2, R1 & R2> => make<B, E1 | E2, R1 & R2>((environment) => {
|
|
140
|
-
const first = self.run(environment as R1)
|
|
141
|
-
return first.ok ? f(first.value).run(environment as R2) : first
|
|
142
|
-
})
|
|
143
|
-
|
|
144
|
-
/**
|
|
145
|
-
* Reads the whole effect environment.
|
|
146
|
-
*
|
|
147
|
-
* Use this when a system helper wants access to all declared services at once.
|
|
148
|
-
*/
|
|
149
|
-
export const access = <R>(): Fx<R, never, R> =>
|
|
150
|
-
make((environment) => Result.success(environment))
|
|
151
|
-
|
|
152
|
-
/**
|
|
153
|
-
* Reads a single service from the environment by key.
|
|
154
|
-
*
|
|
155
|
-
* This is useful for small helpers that need one dependency without threading
|
|
156
|
-
* the entire environment type manually.
|
|
157
|
-
*/
|
|
158
|
-
export const accessService = <R, K extends keyof R>(key: K): Fx<R[K], never, R> =>
|
|
159
|
-
make((environment) => Result.success(environment[key]))
|
|
160
|
-
|
|
161
|
-
/**
|
|
162
|
-
* Supplies the environment needed by an effect.
|
|
163
|
-
*/
|
|
164
|
-
export const provide = <A, E, R>(
|
|
165
|
-
self: Fx<A, E, R>,
|
|
166
|
-
environment: R
|
|
167
|
-
): Fx<A, E, unknown> => make(() => self.run(environment))
|
|
168
|
-
|
|
169
|
-
/**
|
|
170
|
-
* Runs an effect that no longer requires any environment.
|
|
171
|
-
*
|
|
172
|
-
* The runtime uses this at the edge after it has provided the services declared
|
|
173
|
-
* by a system spec.
|
|
174
|
-
*/
|
|
175
|
-
export const runSync = <A, E>(self: Fx<A, E, unknown>): Result.Result<A, E> =>
|
|
176
|
-
self.run(undefined)
|
|
@@ -1,56 +0,0 @@
|
|
|
1
|
-
import * as Result from "../Result.ts"
|
|
2
|
-
|
|
3
|
-
export type Entry<Value, Error> = Result.Result<Value, Error>
|
|
4
|
-
|
|
5
|
-
export type ResultConstructor<Value, Raw, Error> = {
|
|
6
|
-
readonly result: (raw: Raw) => Result.Result<Value, Error>
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
type AnyEntry = Result.Result<any, any>
|
|
10
|
-
|
|
11
|
-
export type Entries = Readonly<Record<string, AnyEntry>>
|
|
12
|
-
|
|
13
|
-
export type SuccessOf<Input extends Entries> = {
|
|
14
|
-
readonly [Key in keyof Input]:
|
|
15
|
-
Input[Key] extends Result.Result<infer Value, any> ? Value : never
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
export type ErrorOf<Input extends Entries> = {
|
|
19
|
-
readonly [Key in keyof Input]:
|
|
20
|
-
Input[Key] extends Result.Result<any, infer Error> ? Error | null : never
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
export const entry = <Value, Raw, Error>(
|
|
24
|
-
constructor: ResultConstructor<Value, Raw, Error>,
|
|
25
|
-
raw: Raw
|
|
26
|
-
): Entry<Value, Error> => constructor.result(raw)
|
|
27
|
-
|
|
28
|
-
export const all = <Input extends Entries>(
|
|
29
|
-
input: Input
|
|
30
|
-
): Result.Result<SuccessOf<Input>, ErrorOf<Input>> => {
|
|
31
|
-
const values: Record<string, unknown> = {}
|
|
32
|
-
const errors: Record<string, unknown> = {}
|
|
33
|
-
let hasFailure = false
|
|
34
|
-
|
|
35
|
-
for (const key of Object.keys(input)) {
|
|
36
|
-
const entry = input[key]
|
|
37
|
-
if (entry === undefined) {
|
|
38
|
-
continue
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
if (entry.ok) {
|
|
42
|
-
values[key] = entry.value
|
|
43
|
-
errors[key] = null
|
|
44
|
-
continue
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
hasFailure = true
|
|
48
|
-
errors[key] = entry.error
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
if (hasFailure) {
|
|
52
|
-
return Result.failure(errors as ErrorOf<Input>)
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
return Result.success(values as SuccessOf<Input>)
|
|
56
|
-
}
|