Guard / foldkit-guard

Sync

A foldkit-sync/entity cell edit is a write to a member of a row, through the journal instead of a mutation, so it answers to the same rules. foldkit-guard/sync checks it in two halves, by where each can run, and keeps a document's edits from reaching anyone the rows would hide them from:

ts
import { GuardSync } from 'foldkit-guard/sync'
import { databaseLayer } from 'foldkit-remote-drizzle'
import { EditableEntity } from 'foldkit-sync/entity'
import { journalExchange } from 'foldkit-sync/journal'

// Edits to titles: a member with a Read rule of its own could not be one.
const TodoEdits = EditableEntity.make(Todo, { members: ['title'] })

// Who may open it: its readers, each in the rows' Read rule. Only an admin reads every todo.
const Edits = GuardSync.document(TodoEdits, { readers: [G.isAdmin] })

// Under the journal's lock: the principal's own guards, for the Sync contract's authorize.
const authorize = { EditedTodos: GuardSync.authorize(Policy, Edits) }

// Each exchange, as its principal: whether they may open it, and each edit against its row.
journalExchange({
  sync,
  journal,
  principal,
  opens: GuardSync.opens(Policy, Edits, principal),
  admit: operation =>
    GuardSync.admit(Policy, Edits, { principal, source: TodoSource, changesOf })(operation).pipe(
      Effect.provide(databaseLayer(db)),
    ),
})

sync and journal are the document's Sync contract and journal (foldkit-sync/journal), here with the principal as G.Principal holds it; changesOf reads an operation's cell edits, and db is the database TodoSource reads. A document's readers are principal guards, so of Todo's Read rule only isAdmin can be one: the rows anyone else reads differ from person to person, and a document is one audience.

  • Under the lock, authorize refuses what the principal guards settle: the row's Update, and each changed member's, with an explain's sentence. It reads no row, and its bodies must not suspend.
  • Before the journal, admit reads each row an edit names through the Entity's guarded Source: a row the principal cannot see is refused, the row's Update must hold over it as it is and as it would be after all of the operation's edits to it, and each changed member's rule must hold. An edit to a member outside the document, or a value its member cannot encode, is refused.
  • A document is one audience. Every replica that opens it receives every edit, so document refuses where it is declared an edited member that is not a field or has a Read rule of its own, a reader the rows' Read rule does not name among its top-level guards (one inside Guards.all is not enough: all of it must hold), rows with a Read rule and no readers named, and always rules over the row. opens lets any reader (anyone, when it names none) who passes the Entity's always.
  • Presence on the same socket is stamped by the server with the connection's peer id, and joins only who may open the document: Sync.presence.serve(socket, hub, { peerId, opens: GuardSync.opens(Policy, Edits, principal) }). What a presence value carries is the application's.