Bundle / foldkit-bundle

Completeness: the wiring mistakes

placements.complete(config) turns each of these into a type error at the property that is wrong:

MistakeReported at
An update that does not accept the whole parent Message, such as one written by hand with a narrower unionupdate
subscriptions not built with placements.subscriptions(own)subscriptions
managedResources not built with placements.resources(own), when a placement has resourcesmanagedResources
init not returning placements.initial(rest), when a wiring runs startup Commandsinit
url not built with placements.url(onUrlChange), when a wiring reads the URLurl

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:

CallWhat it is
Bundle.assemble<Model, Message, Services>()([...])Page.assemble without a scope
placements.route(model, message)update's routing, as an Option
placements.initevery 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.assemble refuses 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, as test/runtime.test.ts does.
  • 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.