Remote / foldkit-remote-server

RemoteServer

namespace · packages/remote-server/src/index.ts:1265

const RemoteServer = {
  memory,
  prepend,
  append,
  remove,
  entity,
  mutation,
  refuse,
  forbid,
  unauthenticated,
  write,
  query,
  live,
  liveHub,
  make,
  validate,
  readHelper,
  answer,
  handlers,
}

In the guides

Members

RemoteServer.memory

function · packages/remote-server/src/index.ts:1002

memory<P = undefined>(
  config: MemoryConfig<P> & MemoryAnswering<P>,
): MemoryBackend<P>
memory<P = undefined>(config: MemoryConfig<P>): MemoryServed<P>

A backend held in memory, for a first run, a test or a demo. See memory.

RemoteServer.prepend

function · packages/remote-server/src/index.ts:1270

prepend(
  connection: ConnectionIdentity,
  ref: {
    readonly entity: string
    readonly id: string
  },
): ConnectionChange

A mutation outcome's report that ref now heads the connection.

RemoteServer.append

function · packages/remote-server/src/index.ts:1276

append(
  connection: ConnectionIdentity,
  ref: {
    readonly entity: string
    readonly id: string
  },
): ConnectionChange

A mutation outcome's report that ref now ends the connection.

RemoteServer.remove

function · packages/remote-server/src/index.ts:1282

remove(
  connection: ConnectionIdentity,
  ref: {
    readonly entity: string
    readonly id: string
  },
): ConnectionChange

A mutation outcome's report that ref left the connection.

RemoteServer.entity

function · packages/remote-server/src/index.ts:1293

entity<P = unknown, R = never>(
  entity: EntityName,
  options: {
    readonly read: EntitySource<P, R>['read']
    readonly authorize?: EntitySource<P, R>['authorize']
    readonly filter?: EntitySource<P, R>['filter']
    readonly shared?: ReadonlyArray<string> | undefined
  },
): EntitySource<P, R>

An entity source. Given the Entity, every member it declares (fields, relations, derived members) may be requested; given anything with a name and fields, those fields. A request for anything else never reaches read or authorize. Given only a name, every field passes.

RemoteServer.mutation

function · packages/remote-server/src/index.ts:1323

mutation<
  P = unknown,
  R = never,
  Name extends string = string,
  Input = unknown,
  Output = unknown,
  Refused = unknown,
>(
  mutation: MutationDescriptor<Name, Input, Output, Refused>,
  run: (context: {
    readonly input: Input
    readonly principal: P
    readonly requestId: string
  }) => Effect.Effect<
    MutationOutcome<Output>,
    RemoteServerError | MutationRefused | RemoteForbidden,
    R
  >,
): MutationSource<P, R>

RemoteServer.refuse

function · packages/remote-server/src/index.ts:1368

refuse<Refused>(
  mutation: MutationDescriptor<string, any, any, Refused>,
  refusal: NoInfer<Refused>,
  message?: string,
): MutationRefused

A refusal of mutation, as a value of its declared Refusal: fail a Source with it (yield* RemoteServer.refuse(SavePost, { _tag: 'Field', ... })) and the client reads it, decoded, with Data.refusal. message is what a person reads, beside it.

RemoteServer.forbid

function · packages/remote-server/src/index.ts:1381

forbid(reason?: string): RemoteForbidden

A refusal of who is asking: fail a Source with it (yield* RemoteServer.forbid()) and the write never runs. reason, when given, is what the person who asked reads; without it the refusal says nothing of why. answer sends it as a 403, and the client's mutation fails with _tag: 'Forbidden'.

RemoteServer.unauthenticated

function · packages/remote-server/src/index.ts:1393

unauthenticated(reason?: string): RemoteUnauthenticated

A refusal of a caller who could not be told: a serveFetch or defineDocumentHost resolver fails with it (RemoteServer.unauthenticated()) when the credential it was sent no longer holds. It answers 401, with reason, when given, for the person who asked.

RemoteServer.write

function · packages/remote-server/src/index.ts:1409

write<P = unknown, R = never, Name extends string = string, Input = unknown>(
  mutation: MutationDescriptor<
    Name,
    Input,
    {},
    {
      readonly _tag: 'Conflict'
    }
  > & {
    readonly write: AnyWrite
  },
  writer: EntityWriter<P, R>,
): MutationSource<P, R>

The Source of a mutation that is a declared Write, with no handler: the input bound to the write lands through writer, and the row as written answers as a patch (a deletion as deleted). An update writes only the keys the client named, when it named some, and fails for a row that is not there; an insert of an id already holding what it writes (a retry) answers with that row, and of one holding anything else fails; a delete of a row already gone succeeds. A row that moved past the revision the write expects is refused as a conflict.

RemoteServer.query

function · packages/remote-server/src/index.ts:1512

query<P = unknown, R = never, Input = unknown>(
  query: QueryDescriptor<string, Input, unknown>,
  run: (context: {
    readonly input: Input
    readonly window: QueryWindow
    readonly principal: P
    readonly select?: RelationRequirement | undefined
  }) => Effect.Effect<QueryPage, RemoteServerError, R>,
): QuerySource<P, R>

RemoteServer.live

function · packages/remote-server/src/index.ts:1533

live<P = unknown, R = never>(
  entity: EntityName,
  options: {
    readonly subscribe: LiveSource<P, R>['subscribe']
  },
): LiveSource<P, R>

Streams live entity patches for a client's live requirements.

RemoteServer.liveHub

function · packages/remote-server/src/index.ts:482

liveHub<P, R>(
  entities: ReadonlyArray<EntitySource<P, R>>,
  options?: {
    readonly key?: ((principal: P) => unknown) | undefined
  },
): Effect.Effect<LiveHub<P, R>>

A LiveHub over entity sources. Pass it to handlers as live, and every mutation's answered patches and deletions are published to it; call hub.changed(ref, fields) yourself only for a write that is not a mutation (a database trigger, a job). Each subscriber that selects any of those fields receives them, re-read through the entity source under its own principal.

RemoteServer.make

function · packages/remote-server/src/index.ts:1551

make<P = unknown, R = never>(config: {
  readonly entities: readonly EntitySource<P, R>[]
  readonly mutations?: readonly MutationSource<P, R>[]
  readonly queries?: readonly QuerySource<P, R>[]
  readonly live?: readonly LiveSource<P, R>[]
}): ServerDefinition<P, R>

RemoteServer.validate

function · packages/remote-server/src/index.ts:1568

validate(domain: RemoteDescriptor, server: ServerDefinition<any, any>): void

Checks every source name against the declared domain, so an undeclared entity/query/mutation fails at startup rather than returning nothing at call time.

RemoteServer.readHelper

function · packages/remote-server/src/index.ts:1590

readHelper<P, R>(
  server: ServerDefinition<P, R>,
  principal: P,
  options: HandlerOptions<P, R>,
  requests: ReadonlyArray<Requirement>,
): Effect.Effect<
  {
    readonly entities: Array<{
      readonly entity: string
      readonly id: string
      readonly values: Record<string, unknown>
    }>
    readonly settled: Array<{
      readonly entity: string
      readonly id: string
      readonly fields: Array<string>
    }>
  },
  RemoteReadError,
  R
>

Reads entity requirements through the entity sources, level by level, following relations as FoldkitRemoteRead does. Shared by the read handler and the query handler's payload: a query page's items are just requirements the page names, so one response carries both.

RemoteServer.answer

function · packages/remote-server/src/index.ts:1753

answer<R>(
  handlers: RemoteRpcClient<R>,
  body: unknown,
): Effect.Effect<
  {
    readonly status: 200 | 400 | 401 | 403 | 422 | 500
    readonly body: RemoteJsonAnswer
  },
  never,
  R
>

Answers one JSON request through handlers: what an HTTP endpoint, a worker or a server in the page calls with the body it received, parsed. The request and its payload are decoded by the protocol's own schemas before a handler sees them, so an operation name is one of Remote's three and nothing else. A body that is not a request is a 400; a caller who could not be told a 401; a refusal of who is asking a 403; a handler's failure a 500 with its message; a defect a 500 that says nothing of it. Remote.json and Remote.http are the other end.

RemoteServer.handlers

function · packages/remote-server/src/index.ts:1818

handlers<P, R, I = never>(
  server: ServerDefinition<P, R>,
  principal: P | Context.Key<I, P>,
  options?: HandlerOptions<P, R>,
): RemoteRpcClient<R | I>

Compiles the server into Remote's RPC handlers, answering as principal: a value, or a Context key (G.Principal) read on each call, which authentication provides per request. Unknown entities, entities with no allowed fields, and unknown mutations return an error or nothing rather than leaking existence.