server.js

What server.js exports: game or shared, never both. A server.js at the top of the app's folder turns rooms on. Tinylib runs it in each room, where no page can read it, and keeps the room's state: every function gets the state and a ServerContext. Functions are synchronous. Math.random(), Date.now() and crypto.randomUUID() come from the room's secret seed and the time the call arrived, so a room's run repeats exactly. Only the state lasts from one call to the next; tinylib dev loads server.js afresh for every call. Every room runs the latest published server.js, and a room ends when the latest exports the other one.

Example

export const game = {
  players: { min: 2, max: 2 },
  setup: ({ players }) => ({ moves: [], white: players[0].id, winner: null }),
  turn: (state, { players }) => (state.winner ? [] : [players[state.moves.length % 2].id]),
  actions: {
    move(state, move, { player, refuse }) {
      if (!legal(state, move)) refuse("That move isn't legal.")
      state.moves.push(move)
      if (mate(state)) state.winner = player.id
    },
  },
  left(state, { player, players }) {
    state.winner = players.find((other) => other.id !== player.id).id
  },
  outcome: (state) => (state.winner ? { winners: [state.winner] } : null),
}

Properties

PropertyType
game?Game<unknown>
shared?Shared<unknown>

Game

People join until players.max are in, or until a player calls room.start() once players.min are. Then setup runs with them, and nobody joins after.

Type Parameters

Type ParameterDefault type
Stateunknown

Properties

PropertyTypeDescription
playersobjectHow many play: min from 1, max from 2 to limits.rooms.maxMembers. A game keeps the numbers of the publish it was created under, as its state keeps its shape.
players.minnumber-
players.maxnumber-
actionsRecord<string, (state, args, ctx) => void>Each runs on room.act(name, args) once the game has started, changes state in place, and may call ctx.refuse or ctx.remove. Tinylib runs it on a copy of the state and keeps the copy only if it returns. Any throw other than ctx.refuse rejects the page's call with failed, and the developer sees the stack in the console.
anytime?Record<string, (state, args, ctx) => void>Actions any player still in may call while the game runs, whatever turn says: resigning, offering a draw, claiming a win on time. They run as actions do. A name in both actions and anytime fails every call when the room first runs server.js.

setup()

setup(ctx): State;

Runs once, when the game starts, and returns the first state. ctx.players are the players, in the order of room.members, so someone who left and came back keeps their first place, and ctx.player is null. If it throws, the call that started the game rejects with failed and the game waits.

Parameters
ParameterType
ctxServerContext
Returns

State

view()?

optional view(state, ctx): unknown;

Runs after every change, once per player, and returns what that person sees. Nothing else reaches their device. Without it, everyone sees the whole state.

Parameters
ParameterType
stateState
ctxServerContext
Returns

unknown

turn()?

optional turn(state, ctx): string[];

Runs after every change and returns the ids of the players who may call actions now. Tinylib drops anyone who has left, refuses everyone else with not_your_turn, and sends "Your turn" to someone newly named who isn't looking at the game. anytime actions skip it. When it names nobody and outcome returns null, the game ends. Without it, every player still in may act, and no "Your turn" is sent.

Parameters
ParameterType
stateState
ctxServerContext
Returns

string[]

left()?

optional left(state, ctx): void;

Runs when a player leaves a game that has started, blocks another player, is removed by ctx.remove, or is dealt out by the idle rule, with ctx.player as who left. Resigning or dealing them out is the game's rule. It can't refuse. If it throws, the leave still happens and the state is unchanged. When no outcome follows and fewer than players.min are left, the game ends with no outcome. When Tinylib takes a player out, as a suspension does, and fewer than players.min would be left, the game ends as if it never started and left doesn't run.

Parameters
ParameterType
stateState
ctxServerContext
Returns

void

outcome()

outcome(state, ctx): 
  | {
  winners: string[];
}
  | null;

Runs after every change, and returns null while the game goes on. The first { winners } it returns ends the game, and each player who isn't looking gets "Game over". An empty winners is a game nobody won.

Parameters
ParameterType
stateState
ctxServerContext
Returns

| { winners: string[]; } | null

Shared

People join at any time until the room is full, and it ends when nobody is still in it.

Type Parameters

Type ParameterDefault type
Stateunknown

Properties

PropertyTypeDescription
sizenumberThe most people in the room at once, 2 to limits.rooms.maxMembers.
actionsRecord<string, (state, args, ctx) => void>As in a Game, from the moment the room is made.

setup()

setup(ctx): State;

Runs once, in host.createRoom, and returns the first state. If it throws, createRoom rejects with failed and no room is made.

Parameters
ParameterType
ctxServerContext
Returns

State

view()?

optional view(state, ctx): unknown;

As in a Game.

Parameters
ParameterType
stateState
ctxServerContext
Returns

unknown

joinable()?

optional joinable(state, ctx): boolean;

Runs when someone accepts an invite, and ctx.player is the person joining. false turns them away with not_joinable. Without it, people can join until the room is full.

Parameters
ParameterType
stateState
ctxServerContext
Returns

boolean

left()?

optional left(state, ctx): void;

Runs when someone leaves, is removed or blocks another member, with ctx.player as who left. It can't refuse. If it throws, the leave still happens and the state is unchanged.

Parameters
ParameterType
stateState
ctxServerContext
Returns

void

ServerContext

What each function of a Game or Shared gets besides the state.

Properties

PropertyTypeDescription
player| { id: string; name: string; left?: true; } | nullThe member the call is for: who acted, joined or left, or whose view it is. null in setup of a game, turn and outcome.
playersobject[]In a shared room, room.members: everyone who has been in it, in the order they joined. In a game, the players it started with, in that order, each marked left once gone.
optionsunknownWhat host.createRoom got.

refuse()

refuse(sentence): never;

Stops the action and rejects the page's call with refused, whose message is sentence. Actions only.

Parameters
ParameterType
sentencestring
Returns

never

remove()

remove(id): void;

Takes a member out of the room once the action returns, as if they left. The action is refused with not_removable when id is the caller's own, a member who left, or someone never in the room. Actions only.

Parameters
ParameterType
idstring
Returns

void