Surface / foldkit-surface

Surfaces

A Surface binds a projection and the Messages a feature may use into a named, inspectable contract. App.surface(name, { params?, model, messages? }) is the form to write: params are the fields of a Schema.Struct (or a schema, kept as is), and model returns a Projection, or an object of Projections and field refs that becomes Projection.struct.

ts
const TodoDetail = App.surface('TodoDetail', {
  model: ({ model }) => ({ todos: model.todos, selectedTodoId: model.selectedTodoId }),
  messages: [Message.ToggledTodo],
})

// A parameterized Surface may read `params`; dynamic lookups are OptionalRefs.
const ById = App.surface('ById', {
  params: { id: Schema.String },
  model: ({ model, params }) => ({ todo: model.todosById.at(params.id) }),
})

Surface.read(TodoDetail, root)                     // project purely
Surface.read(ById, root, { id: 't1' })
Surface.view(TodoDetail, render)                   // bind a renderer
Surface.rootView(TodoDetail, undefined, render)    // bound to the app root

The Surface's Model is the projected value ({ todos; selectedTodoId }), its Params the fields, and its Message the union of the constructors listed; a constructor from another application is a compile error at the constructor. A parameterized Surface's projection is not evaluated until it has params.

Surface.at(surface, params) is the Surface as the Model activates it: params is the value, or a function of the Model returning it as an Option (none while the Surface is inactive, on another route say). Its projectionOf(model) is the projection for those params, an Option too, and it carries the Surface's owner and messages, the tags of the Messages the Surface lists; a Subscription derives what to fetch from a list of them (foldkit-remote's Data.subscriptions).

Surface.each(child, { from, instances }) is one Surface definition as zero to many keyed instances: instances maps the parent's projected Model to { key, params } items, and there is an instance per item while the parent is active, none while it is not. Keys are stable strings (__proto__ and duplicates throw). The family keeps its from parent, so a consumer can fetch the parent first and what the parent reveals after. SurfaceSource is the shared shape — at/when values resolve their lone instance through it, keyed by Surface name — read with Surface.instances(source, model).

Surface.make, the explicit form

Surface.make(app, name, { Params?, model, messages? }) takes the wrappers App.surface lifts: Params as a schema and model returning a Projection (Projection.struct({ … }) for several fields). App.surface compiles to it.