Surface / foldkit-surface
Quick start
ts
import { Option, Schema } from 'effect'
import { defineMessageUnion } from 'foldkit/message'
import { Projection, Surface } from 'foldkit-surface'
const Todo = Schema.Struct({ id: Schema.String, title: Schema.String, done: Schema.Boolean })
const Model = Schema.Struct({
todos: Schema.Array(Todo),
todosById: Schema.Record(Schema.String, Todo),
selectedTodoId: Schema.Option(Schema.String),
})
const Message = defineMessageUnion({
CreatedTodo: { id: Schema.String, title: Schema.String },
ToggledTodo: { id: Schema.String },
SelectedTodo: { id: Schema.String },
})
const App = Surface.application({ Model, Message })
const TodoList = App.surface('TodoList', {
model: ({ model }) => ({ todos: model.todos, selectedTodoId: model.selectedTodoId }),
messages: [Message.ToggledTodo, Message.SelectedTodo],
})
const model: typeof Model.Type = {
todos: [{ id: 't1', title: 'Read the guide', done: false }],
todosById: {},
selectedTodoId: Option.some('t1'),
}
Surface.read(TodoList, model)
// { todos: [{ id: 't1', title: 'Read the guide', done: false }], selectedTodoId: Option.some('t1') }TodoList is a named contract: the projection a feature reads (here the two
fields, as Projection.struct lifts them) and the Messages it may send. It
reads purely (Surface.read(TodoList, model)), binds a renderer
(Surface.view), and is what foldkit-remote, foldkit-sync, and
foldkit-agent derive their work from. Projection.pick(App.model.todos) is
a writable projection of the same field for a replicator. The extra
todosById field illustrates dynamic record lookups later; this read neither
uses it nor synchronizes it with todos. Choose one authoritative collection
in a real application.