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) is Bundle.parent built from the application's own Model and Message, with module bound to that application.
  • Page.module(assembly, items) makes one bundle contract 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, for Module.make or Module.add. It owns the placement's path, names the parent Message tags its Messages travel under, and lists its args as args metadata when the bundle has an args Schema or is a bundle.with preset.
  • BundleSurface.link(fieldRef, wrapper) is Link.make over a Surface field ref, for a custom placement. Its contract names the ref's application.
  • BundleSurface.module(app, assembly, items) is Page.module without a scope.

What the existing rules catch

MistakeFinding
Two placements on one path, or a placement on a path Sync or Remote ownsownership-overlap
A placement through another application's field refforeign-contract
A wrapper variant missing from the application's Messageunknown-message
A placement's Link path that is not a Model fieldunknown-path

Module.manifest and Module.toMarkdown list each placement as the owner of its path.