Bundle / foldkit-bundle
Completeness: the wiring mistakes
placements.complete(config) turns each of these into a type error at the
property that is wrong:
| Mistake | Reported at |
|---|---|
An update that does not accept the whole parent Message, such as one written by hand with a narrower union | update |
subscriptions not built with placements.subscriptions(own) | subscriptions |
managedResources not built with placements.resources(own), when a placement has resources | managedResources |
init not returning placements.initial(rest), when a wiring runs startup Commands | init |
url not built with placements.url(onUrlChange), when a wiring reads the URL | url |
placements.runtime(input) checks the same mistakes it can still make —
update, an unbranded init function, and url — and derives the rest:
subscriptions and managedResources default to the items', so an
application that adds none passes neither. Prefer it wherever the update
already routes every placement (route a narrow one with
assembly.update(own) first); keep complete for configs assembled by
hand.
Annotate the parameters of callbacks written inline in the config.
makeApplication's init: (url: Url) => … and routing.onUrlChange: (url: Url)
=> … need their types written. TypeScript does not infer from an object literal
holding an unannotated callback until it has typed that callback, and the
runtime's config is overloaded, so nothing types it through complete: the
config falls back to what complete requires of any config, and every check
fails, beginning with update's. The update error says so.
Pass the parent's own records through the same call:
placements.subscriptions(ownSubscriptions). A duplicate key throws at startup,
as Subscription.aggregate does. A placement whose wrapper variant is missing from
the parent Message is reported earlier: at Page.at, Page.place, or Page.each,
or by assemble's own type check.
Page.assemble also fails at startup when two items share a key, and, naming
both, when two claim one Message tag (two placements sharing a wrapper, or
two wirings handling one tag that neither declares shared) or a Managed
Resource tag. placements.resources(own) fails the same way when the
parent's own resource shares an item's tag.
Lower-level API
The scope is sugar over these, which remain for code that composes by hand:
| Call | What it is |
|---|---|
Bundle.assemble<Model, Message, Services>()([...]) | Page.assemble without a scope |
placements.route(model, message) | update's routing, as an Option |
placements.init | every single placement's init, then each wiring's, as one Update.Step |
declared.at<Model>()(config), declaredEach.each<Model>()(config) | a declaration placed without a scope |
Limits
- Managed Resources are provided by tag. The Foldkit runtime provides a
resource through its
ManagedResource.tag, so two placements of one bundle that use the same tag would replace each other.Page.assemblerefuses that. When a bundle with resources is placed more than once, make the bundle from a function that takes the tag, one tag per placement, astest/runtime.test.tsdoes. - Collections restart item streams together. Keeping other items' streams running while one is added needs a dependency-change signal from Foldkit's runtime.
- Collections cannot hold resources, for the tag reason above.
See also
foldkit-bundle-surface: placements as Module contracts, and the scope from a Surface application.examples/bundle: a settings page built from placements, with its transcript pinned.- Bundle versus Submodel: one child wired by hand and as a Bundle, side by side.
- Wiring: Remote, Mirror, Sync, and Agent join the same assembly through wiring, so routing, startup, Subscriptions, and the Module derive from one list.