On this pageLifting Child Subscriptions
Subscription Organization
A Submodel owns the Subscriptions that produce its Messages. Its parent lifts those Subscriptions into the parent Model and Message types, then aggregates them with other Subscription records at that level.
This mirrors the other halves of the boundary. Update.foldChild lifts child update, h.submodel lifts child view, and Subscription.lift lifts child Streams.
Each level declares local entries with Subscription.make and lifts child records with Subscription.lift. By the time a Stream reaches the root, it emits root Messages that the Runtime can dispatch through update. This diagram follows one leaf record through those lifts:
+-------------------------------+ | ThemeMenu | | Stream<ThemeMenu.Message> | +-------------------------------+ | | Subscription.lift | wraps with GotThemeMenuMessage v +-------------------------------+ | Settings | | Stream<Settings.Message> | +-------------------------------+ | | Subscription.lift | wraps with GotSettingsMessage v +-------------------------------+ | Root | | Stream<Message> | +-------------------------------+ | v Runtime
Three functions build the hierarchy.
| Verb | What it does | When to reach for it |
|---|---|---|
Subscription.make | Declares local entries from dependency Schemas, modelToDependencies, and dependenciesToStream. | The current level owns a Subscription. |
Subscription.lift | Reads a child Model and wraps each emitted child Message. An optional when adds a parent-owned gate. | A child exports a Subscriptions record. |
Subscription.aggregate | Combines records, infers their shared types, and rejects duplicate keys at startup. | A level has more than one local or lifted record. |
A Subscription that emits child Messages belongs inside that child's folder. The child exports it without knowing which parent will lift it.
Each subscription.ts produces only the Message type for its level. Every Subscription.lift adds one wrapper, just as one h.submodel boundary does for view handlers.
Export one subscriptions record from the child. The parent decides whether to lift every entry, gate the whole record, or gate named entries. The child does not split its exports around parent-owned conditions.
The next three snippets trace one record from a leaf, through a composing Submodel, to the root.
A leaf declares its entries with Subscription.make.
A composing Submodel lifts child records, declares any local entries, and aggregates the results. Each lift supplies a read that returns an Option of the child Model. An always-present child is wrapped in Option.some.
The root uses the same shape. Its lifts target the root Model and Message.
Return Some(child) from read when the child is present, or None when it is absent. Foldkit stops the child's Subscriptions and skips its dependency functions while read returns None.
A child can express conditions from its own Model in its dependencies and Stream construction. It cannot see parent-owned state such as the active Route.
Put a parent-owned condition in when on the lift. The predicate receives the parent Model. The gated entries run only while it returns true and read returns a child. A closed gate skips read and the child’s dependency functions.
Closing a gate tears down the Stream. Foldkit also stops calling the child's modelToDependencies until the gate reopens, so hidden child changes do not restart it.
when accepts either one predicate for the whole record or a map of predicates by entry name. An omitted entry has no additional activity condition, but still stops when read returns None. For example: a Room page can keep its WebSocket alive across navigation while gating its keyboard listener to the active Room Route.
The parent owns when. The child keeps its child-owned conditions in its own Subscription definition.
Attach each gate at the level that owns its condition. When a record passes through several levels, all gates compose. The entry runs only while every gate above it is open.