Sync / Editing server data

A sixty-second path: one column

foldkit-sync/entity keeps the edits one per cell; foldkit-sync/remote shows them through Remote; foldkit-sync/journal keeps the table. In the domain, shared by client and server:

ts
import { EditableEntity } from 'foldkit-sync/entity'

export const ProductEdits = EditableEntity.make(Product, { members: ['cents'] })

In the application, the durable Message, its fold, and the edits shown through Remote:

ts
const Base = Bundle.compose({
  remote: Remote.Model,
  edits: Schema.Array(ProductEdits.Edit),
}).pipe(Bundle.withMessages({ ...Remote.messages, EditedProducts: ProductEdits.edited }))

// in update
EditedProducts: ({ changes, at, by }) => ({
  model: modifyFields(model, {
    edits: edits =>
      ProductEdits.merge(edits, changes, Option.fromUndefinedOr(at), Option.fromUndefinedOr(by)),
  }),
}),

// every read of a row draws its edits, as Remote overlays
const Shown = RemoteEdits.make(Data, ProductEdits)

// in Sync.mount's options: after every live update, never in replay
afterUpdate: model => ({ model: Shown.reconcile(model, model.edits, replica.replicaId).model }),

In the Sync contract, the stamp the journal writes at commit:

ts
stamp: {
  EditedProducts: ({ changes }, commit) =>
    Message.EditedProducts({ changes, ...ProductEdits.stamped(commit) }),
},

On the server, the table:

ts
const { settle, absorb } = editsJournal({
  documentId: RegistrySync.documentId,
  journal,
  editsOf: operation => /* EditedProducts' changes and at, or none */,
  // One cell, with its sequence, never moving a row back: foldkit-remote-drizzle's
  // applyEdits(Db.Product), over the binding Remote reads the table through.
  apply: (change, at) => applyProduct(change, at).pipe(Effect.provide(database)),
  // The highest revision any row holds: settling refuses a table past the journal.
  tableRevision: Effect.try(() => sqlite.highestRevision()),
  holdsThrough: (snapshot, through) => ProductEdits.holdsThrough(snapshot.edits, through),
  absorbed: (through, cursor) => /* the server's AbsorbedEdits operation */,
  server,
})
// journalExchange({ ..., settle }) applies on every exchange; a clock calls absorb.

editsOf and absorbed are the application's Messages, spelled out in examples/registry/src/journal.ts; they are elided above. The snippets compile in examples/registry/test/guide.test-d.ts.