On this pageFunctions
Subscription
/**
* Combines multiple Subscriptions records into one. Throws on duplicate
* keys so a misconfigured aggregate fails loudly at startup rather than
* silently overriding.
*/
<Model, Message, Services = never>(): (records: readonly Array<Readonly<Record<string, Subscription<Model, Message, any, Services>>>>) => Subscriptions<Model, Message, Services>/**
* Build a Subscription that emits a Message on every
* `requestAnimationFrame` tick, with the inter-frame delta in milliseconds.
*/
<Model, Message>(config: AnimationFrameConfig<Model, Message>): {
dependenciesSchema: Struct<{
isActive: Boolean
}>
dependenciesToStream: (__namedParameters: {
isActive: boolean
}) => Stream<Message, never, never>
modelToDependencies: (model: Model) => {
isActive: boolean
}
}/**
* Lifts a record of child Subscriptions into a parent's Model and Message
* context, applying a Model accessor and a Message wrapper uniformly to
* every entry. Per-entry dependency types, schemas, and `keepAliveEquivalence`
* settings are preserved; each lifted entry's variant (with or without
* `readDependencies`) matches its source entry's.
*/
<Subscriptions extends Readonly<Record<string, Subscription<any, any, any, any>>>>(subscriptions: Subscriptions): (config: {
toChildModel: (parentModel: ParentModel) => ChildModelOf<Subscriptions>
toParentMessage: (message: ChildMessageOf<Subscriptions>) => ParentMessage
}) => {
readonly [K in string | number | symbol]: Subscriptions[K] extends Subscription<any, any, Dependencies, Services>
? Subscription<ParentModel, ParentMessage, Dependencies, Services>
: never
}/**
* Declares a Subscriptions record. The Model, Message, and optional Services
* generics are provided up front; the entries record follows, built from
* calls to the `entry` builder passed into the inner function.
*
* Reach for `Subscription.aggregate` to combine multiple records, and
* `Subscription.lift` to translate a child Submodel's record into a parent
* context.
*/
<Model, Message, Services = never>(): (build: (entry: EntryBuilder<Model, Message, Services>) => Entries) => {
readonly [K in string | number | symbol]: Entries[K] & SubscriptionBrand
}/**
* Wraps a Stream as a Subscription entry whose lifecycle is independent of
* the Model. The Stream runs for the lifetime of the Subscriptions record;
* no Model change tears it down or restarts it. Use for any Stream whose
* work doesn't depend on Model state, such as system theme listeners,
* viewport width observers, or route-independent timers.
*
* Returns an entry shape, not a branded Subscription. Pass it into `make`
* as an entry value.
*/
<Message, Services = never>(stream: Stream<Message, never, Services>): EntryWithoutKeepAlive<unknown, Message, Record<string, never>, Services>/**
* Configuration for the `animationFrame` Subscription helper.
*
* `isActive(model)` controls whether the request-animation-frame loop is
* scheduled at all. When it returns `false` (e.g. the game is paused, the
* scene is static, or the canvas is offscreen), no rAF callbacks fire and
* no Messages are emitted. The Subscription system automatically restarts
* the loop when `isActive` flips back to `true`.
*/
type AnimationFrameConfig = Readonly<{
isActive: (model: Model) => boolean
toMessage: (deltaTime: number) => Message
}>/**
* A single subscription entry produced by `Subscription.make`,
* `Subscription.lift`, or `Subscription.aggregate`. The brand field is
* `never`, so application code cannot manually construct a `Subscription`
* value: it must go through one of those constructors (or a helper like
* `Subscription.persistent` that returns an entry shape, then through
* `make`).
*
* Two variants by `keepAliveEquivalence` presence:
*
* - Without `keepAliveEquivalence` (the common case), every Model change recomputes
* the dependencies. Equivalent dependencies leave the Stream alone; any
* change tears it down and restarts. `dependenciesToStream` takes a single
* argument: the latest dependencies.
* - With `keepAliveEquivalence` (an escape hatch), Model changes that the
* equivalence treats as equal leave the Stream running, but the running
* Stream can still read the latest dependencies via the second
* `readDependencies` argument. Use this when the Stream needs mid-flight
* access to data that changes often but shouldn't trigger restarts
* (Foldkit UI's `Ui.DragAndDrop.autoScroll` reading the latest pointer
* `clientY` each rAF tick is the canonical example).
*
* `dependenciesSchema` must be a `Schema.Struct` so every dependency is
* explicitly named at the schema level.
*/
type Subscription = Entry<Model, Message, Dependencies, Services> & SubscriptionBrand