Bundle / foldkit-bundle

Many of one: collections

A collection places a bundle once per key. The parent Model still owns every item; the collection routes by key:

Parent Model.rows = { a: Row.Model, b: Row.Model }
GotRowsMessage({ key: 'b', message }) -> Row.update on rows.b -> rows.b written back
ts
// Row is a bundle whose Model is { id: string, count: number }.
const RowsDeclared = Bundle.declareEach(Row, 'rows') // a record field and GotRowsMessage
const RowsPage = Bundle.parent({
  Model: Schema.Struct({ ...RowsDeclared.fields }),
  Message: defineMessageUnion({ ...RowsDeclared.cases }),
})
const Rows = RowsPage.each(RowsDeclared)

Rows.add('b', row => ({ ...row, id: 'b' })) // init, then prepare
Rows.remove('b')
  • add(key, prepare?) writes the item from init and lifts its Commands. An item cannot see its key, so prepare lets the parent store what only it knows, such as the id. Adding an existing key replaces the item.
  • remove(key) deletes the item. A Message that arrives later for that key leaves the parent unchanged.
  • update, helpers.name(key, ...input), and onOut(outMessage, key) work per item. args are given once, for every item.
  • view(parent, h, key) renders one item and viewAll(parent, h) renders all of them, each in its own slot.
  • Page.placeEach(bundle, field, config) places without a declaration.
  • placements.initial starts a collection empty unless rest gives it.

Typed keys and order

Pass a key Schema, such as a branded id, and the key type follows through add, remove, helpers, views, and onOut. Store items in an array when order matters: a record puts integer-like keys such as "2" first. Ids in an array must be unique, and an item is stored under its own id: give add a prepare that sets the id to the key, as below, and keep it through update. Writing an item whose id is not its key throws, since it could never be read back.

ts
const RowId = Schema.String.pipe(Schema.brand('RowId'))
const GotOrderedRowMessage = Link.keyedWrapper('GotOrderedRowMessage', RowMessage, RowId)

// OrderedRow is a bundle whose Model has `id: RowId`; OrderedPage's Model has `rows: Array<…>`.
const OrderedRows = OrderedRow.each(
  OrderedPage.link.collectionById('rows', GotOrderedRowMessage, { id: row => row.id }),
)
OrderedRows.add(RowId.make('first'), row => ({ ...row, id: RowId.make('first') }))
OrderedRows.remove(RowId.make('first')) // a RowId, not a string

Subscriptions restart together. Each child Subscription becomes one parent entry over every item. Adding or removing an item, or changing any item's dependencies, restarts that entry's stream for every item. A child entry with keepAliveEquivalence stays alive while the keys are unchanged, and each item reads its own latest dependencies. A bundle with Managed Resources cannot be placed per key; the type says why.