Bundle / foldkit-bundle-surface
Sixty seconds
ts
import { Schema } from 'effect'
import { defineMessageUnion } from 'foldkit/message'
import { Bundle } from 'foldkit-bundle'
import { BundleSurface } from 'foldkit-bundle-surface'
import { Module, Surface } from 'foldkit-surface'
const Search = Bundle.make('Search', {
Model: Schema.Struct({ query: Schema.String }),
Message: defineMessageUnion({ Typed: { query: Schema.String } }),
init: () => ({ model: { query: '' } }),
update: (_model, message) => ({ model: { query: message.query } }),
})
const Searchbox = Bundle.declare(Search, 'search')
const Model = Schema.Struct({ ...Searchbox.fields })
const Message = defineMessageUnion({ ...Searchbox.cases })
const App = Surface.application({ Model, Message })
const Page = BundleSurface.parent(App)
const placements = Page.assemble(Page.at(Searchbox))
const AppModule = Page.module(placements, [])
Module.validate(AppModule) // []The Module now records one owner for search. Nothing has mounted or run:
validation inspects declarations. Pass other contracts as the second argument
to Page.module, then check the findings before starting the application.
Rendering a Surface over search only observes that owner.
BundleSurface.parent(app)isBundle.parentbuilt from the application's own Model and Message, withmodulebound to that application.Page.module(assembly, items)makes onebundlecontract per placement, brings each wiring's contract as-is, and adds your other contracts. It performs no I/O.BundleSurface.contract(app, placement)is one contract, forModule.makeorModule.add. It owns the placement's path, names the parent Message tags its Messages travel under, and lists its args asargsmetadata when the bundle has an args Schema or is abundle.withpreset.BundleSurface.link(fieldRef, wrapper)isLink.makeover a Surface field ref, for a custom placement. Its contract names the ref's application.BundleSurface.module(app, assembly, items)isPage.modulewithout a scope.
What the existing rules catch
| Mistake | Finding |
|---|---|
| Two placements on one path, or a placement on a path Sync or Remote owns | ownership-overlap |
| A placement through another application's field ref | foreign-contract |
| A wrapper variant missing from the application's Message | unknown-message |
| A placement's Link path that is not a Model field | unknown-path |
Module.manifest and Module.toMarkdown list each placement as the owner of
its path.