Sync / foldkit-sync/do

DocumentHostConfig

interface · packages/sync/src/do.ts:47

interface DocumentHostConfig<Message, Shared, Principal, Env, RP = never> {
  readonly sync: Sync<Message, Shared>
  readonly openJournal: (
    ctx: DocumentHostContext,
    env: Env,
  ) => Promise<Journal<Operation, Shared, Principal, Operation>>
  readonly resolvePrincipal: (
    request: Request,
    env: Env,
  ) => Effect.Effect<Principal, RemoteUnauthenticated, RP>
  readonly settle?:
    | ((
        env: Env,
        journal: Journal<Operation, Shared, Principal, Operation>,
      ) => Effect.Effect<void, unknown>)
    | undefined
  readonly revalidate?:
    | {
        readonly every: Duration.Input
        readonly key?: ((principal: Principal) => unknown) | undefined
      }
    | undefined
  readonly opens?:
    ((principal: Principal) => Effect.Effect<boolean>) | undefined
  readonly admit?:
    | ((
        principal: Principal,
        operation: Operation,
      ) => AuthorizeDecision | Effect.Effect<AuthorizeDecision, unknown>)
    | undefined
  readonly pair?:
    (() => readonly [client: WebSocket, server: AcceptingWebSocket]) | undefined
}

What defineDocumentHost needs of the application, per document.

Members

DocumentHostConfig.sync

property · packages/sync/src/do.ts:49

readonly sync: Sync<Message, Shared>

The document's contract: its codecs and journal shape.

DocumentHostConfig.openJournal

property · packages/sync/src/do.ts:57

readonly openJournal: (
  ctx: DocumentHostContext,
  env: Env,
) => Promise<Journal<Operation, Shared, Principal, Operation>>

Opens the document's journal, once: on the first upgrade, not on construction, so probes never provision storage. Over D1 (d1: true), or the object's SQLite storage through ctx. May build with Effect and unwrap; a rejection answers the upgrade with a 500, and the next upgrade tries again.

DocumentHostConfig.resolvePrincipal

property · packages/sync/src/do.ts:68

readonly resolvePrincipal: (
  request: Request,
  env: Env,
) => Effect.Effect<Principal, RemoteUnauthenticated, RP>

Who is opening this socket, from the upgrade request. Fails with a RemoteUnauthenticated (foldkit-remote; RemoteServer.unauthenticated() makes one) to refuse it: a 401 carrying its message. A defect is a 500 that says nothing of it. What it needs (a session store) comes from layer.

DocumentHostConfig.settle

property · packages/sync/src/do.ts:78

readonly settle?:
  | ((
      env: Env,
      journal: Journal<Operation, Shared, Principal, Operation>,
    ) => Effect.Effect<void, unknown>)
  | undefined

Runs after each exchange's appends and before the reply is read: apply what committed to another store, through journal.recover. Takes the environment — a D1 binding, say — and the journal, because both live outside the exchange. See serveJournal.

DocumentHostConfig.revalidate

property · packages/sync/src/do.ts:93

readonly revalidate?:
  | {
      readonly every: Duration.Input
      readonly key?: ((principal: Principal) => unknown) | undefined
    }
  | undefined

Asks resolvePrincipal again, of the upgrade request, every every while the socket is open: a refusal closes it with 4401, and a principal whose key differs from the one it opened with (Policy.keyOf) closes it with 1012, which the replica reconnects after, as the new principal; without key only a refusal closes it. Without it a socket serves the principal it opened with until it closes, a revoked session included.

DocumentHostConfig.opens

property · packages/sync/src/do.ts:100

readonly opens?:
  ((principal: Principal) => Effect.Effect<boolean>) | undefined

Whether principal may open the document. See journalExchange's opens.

DocumentHostConfig.admit

property · packages/sync/src/do.ts:105

readonly admit?:
  | ((
      principal: Principal,
      operation: Operation,
    ) => AuthorizeDecision | Effect.Effect<AuthorizeDecision, unknown>)
  | undefined

Whether an operation from principal may be appended, before the journal sees it. See journalExchange's admit.

DocumentHostConfig.pair

property · packages/sync/src/do.ts:116

readonly pair?:
  (() => readonly [client: WebSocket, server: AcceptingWebSocket]) | undefined

Makes the socket pair for an upgrade: the server end is accepted before serving. Defaults to the WebSocketPair global where one exists (a worker, miniflare); pass one in tests.