Rules

Kind

A kind of room, exported from rules.js under its name.

Example

export const game = {
  create(options, ctx) {
    return { board: Array(9).fill(''), x: ctx.by.id, o: null }
  },
  on(state, input, ctx) {
    if (input.type === 'join' && state.o) ctx.refuse('This game already has two players.')
  },
}

Extended by

Type Parameters

Type ParameterDefault type
Stateany
Optionsany

Properties

PropertyTypeDescription
address?stringWhere Tinylib sends people who come in for a room of this kind from outside the app, such as 'game/:id'.

Kind.create()

create(options, ctx): State;

Returns the first state, from the options tinylib.rooms.create passed. May refuse them with ctx.refuse.

Parameters
ParameterType
optionsOptions
ctxCreateContext
Returns

State

Kind.on()

on(
   state, 
   input, 
   ctx
): void;

Every input, one at a time. Changes state in place and calls ctx for outputs.

Parameters
ParameterType
stateState
inputInput
ctxContext
Returns

void

Input

type Input = 
  | {
  type: "action";
  from: Member;
  name: string;
  data?: unknown;
}
  | {
  type: "join";
  from: Member;
  via:   | {
     invite: Member;
   }
     | "link";
}
  | {
  type: "invite";
  from: Member;
}
  | {
  type: "getLink";
  from: Member;
}
  | {
  type: "deleteLink";
  from: Member;
}
  | {
  type: "leave";
  from: Member;
  why: LeaveReason;
}
  | {
  type: "connect";
  from: Member;
}
  | {
  type: "disconnect";
  from: Member;
}
  | {
  type: "open";
  from: Member;
}
  | {
  type: "timer";
  name: string;
  data?: unknown;
}
  | {
  type: "upgraded";
  fromPublish: number;
};

What happens in a room, one at a time. Requests can be refused with ctx.refuse: action, join, invite, getLink and deleteLink. The rest are facts.

  • action: a member's room.send(name, data).
  • join: someone asks to join, via the member who invited them or the room's link. They aren't a member until the rules accept, so they're welcomed on their connect, which follows at once.
  • invite: a member is about to invite someone. The rules never learn who.
  • getLink and deleteLink: a member gets the room's link, or deletes it.
  • leave: a member left, and why.
  • connect and disconnect: a member's first device opened the room, or their last closed it.
  • open: a page opened the room, on every open, a reload or a second device included.
  • timer: a timer ctx.setTimer set came due.
  • upgraded: the first input under a new publish's rules.

LeaveReason

type LeaveReason = 
  | "left"
  | "removed"
  | "blocked someone"
  | "data deleted"
  | "account gone"
  | "suspended";

Why a member left. 'blocked someone': they blocked another member, and so left.

ctx

What Tinylib hands the rules with every input, and the outputs they call. Outputs happen once on returns; when it refuses or throws, the state and every output are thrown away.

Extended by

Properties

PropertyTypeDescription
roomIdstring-
kindstringThe name of the kind's export.
nownumberWhen the input arrived, in milliseconds on Tinylib's clock.
publishnumberThe publish that last wrote this state.
membersRoomMember[]The current members in the order they joined, with their current names.
data(personaId) => MemberValues & objectctx.data(personaId) is a member's values under the keys tinylib.json declares in rulesKeys, for a current member or one leaving on this input. ctx.data.room is the data kept with this room. Writes happen after the input, one by one, and a failed write undoes nothing. Example ctx.data(winner).set('wins', (ctx.data(winner).get('wins') ?? 0) + 1, { audience: 'friends' }) ctx.data.room.set('result', { winner })

ctx.random()

random(): number;

A number from 0 up to 1 that players can't predict or rig.

Returns

number

ctx.refuse()

refuse(sentence): never;

Refuses the input: nothing changes, nothing is sent, and the member who acted gets sentence. Refusing a fact changes nothing and tells no one.

Parameters
ParameterType
sentencestring
Returns

never

Example
if (state.o) ctx.refuse('This game already has two players.')

ctx.send()

send(to, message): void;

Sends message to the open app of each of to, once. It isn't kept: a member without the room open misses it.

Parameters
ParameterType
toRecipients
messageunknown
Returns

void

Example
ctx.send('everyone', { screen: 'vote' })

ctx.notify()

notify(
   id, 
   title, 
   text?
): void;

Sends a notification with title and text to the devices of member id, unless they have the room open. Needs "notifications" in tinylib.json's uses.

Parameters
ParameterType
idstring
titlestring
text?string
Returns

void

Example
ctx.notify(next, 'Your move', `${input.from.name} played.`)

ctx.status()

status(
   id, 
   text, 
   waiting
): void;

Sets the line Tinylib's screens show member id about this room, and whether the room waits on them. Tinylib keeps it; the last one is the room's record when it ends.

Parameters
ParameterType
idstring
textstring
waitingboolean
Returns

void

Example
ctx.status(next, 'Your move', true)

ctx.name()

name(text): void;

What Tinylib calls the room outside the app: in the person's rooms list, in an invitation, and in its own sentences about the room. Until set, "this room". At most 60 characters.

Parameters
ParameterType
textstring
Returns

void

Example
ctx.name(options.title)

ctx.setTimer()

setTimer(
   name, 
   at, 
   data?
): void;

Wakes the room at at, on Tinylib's clock, with a timer input carrying name and data. Setting a name again moves it.

Parameters
ParameterType
namestring
atnumber
data?unknown
Returns

void

Example
ctx.setTimer('flag', ctx.now + 60_000)

ctx.cancelTimer()

cancelTimer(name): void;
Parameters
ParameterType
namestring
Returns

void

ctx.remove()

remove(id): void;

Removes member id, which comes back as a leave input with why: 'removed'.

Parameters
ParameterType
idstring
Returns

void

ctx.end()

end(): void;

Ends the room now. It takes no more input, and its members keep a record of it in their past rooms.

Returns

void

Recipients

type Recipients = "sender" | "everyone" | string | string[];

to for ctx.send: a member's id, a list of ids, everyone, or the input's sender.

MemberValues

A member's values under the keys tinylib.json declares in rulesKeys, from ctx.data(personaId).

MemberValues.get()

get(key): unknown;

The current value under key, earlier writes in this input included. Throws for a key rulesKeys doesn't declare.

Parameters
ParameterType
keystring
Returns

unknown

MemberValues.keys()

keys(prefix?): string[];

The declared keys that start with prefix, sorted.

Parameters
ParameterType
prefix?string
Returns

string[]

MemberValues.set()

set(
   key, 
   value, 
   options?
): void;

Writes value under key after the input, for audience, 'me' by default.

Parameters
ParameterType
keystring
valueunknown
options?{ audience?: Audience; }
options.audience?Audience
Returns

void

MemberValues.remove()

remove(key): void;

Removes the value under key after the input.

Parameters
ParameterType
keystring
Returns

void

RoomData

The data kept with this room, for its members during the room and after, from ctx.data.room.

RoomData.get()

get(key): unknown;

The current value under key, earlier writes in this input included.

Parameters
ParameterType
keystring
Returns

unknown

RoomData.keys()

keys(prefix?): string[];

The keys that start with prefix, sorted.

Parameters
ParameterType
prefix?string
Returns

string[]

RoomData.set()

set(key, value): void;

Writes value under key after the input.

Parameters
ParameterType
keystring
valueunknown
Returns

void

RoomData.remove()

remove(key): void;

Removes the value under key after the input.

Parameters
ParameterType
keystring
Returns

void

Audience

type Audience = "me" | "friends";

Who reads a value: the persona only, or also their friends who have the app.

CreateContext

What Tinylib hands the rules with every input, and the outputs they call. Outputs happen once on returns; when it refuses or throws, the state and every output are thrown away.

Extends

Properties

PropertyTypeDescriptionInherited from
roomIdstring-Context.roomId
kindstringThe name of the kind's export.Context.kind
nownumberWhen the input arrived, in milliseconds on Tinylib's clock.Context.now
publishnumberThe publish that last wrote this state.Context.publish
membersRoomMember[]The current members in the order they joined, with their current names.Context.members
data(personaId) => MemberValues & objectctx.data(personaId) is a member's values under the keys tinylib.json declares in rulesKeys, for a current member or one leaving on this input. ctx.data.room is the data kept with this room. Writes happen after the input, one by one, and a failed write undoes nothing. Example ctx.data(winner).set('wins', (ctx.data(winner).get('wins') ?? 0) + 1, { audience: 'friends' }) ctx.data.room.set('result', { winner })Context.data
byMemberThe creator, who is the room's first member.-

Member

A persona: its id, and its current display name.

Extended by

Properties

PropertyType
idstring
namestring

RoomMember

A current member, as ctx.members lists them.

Extends

Properties

PropertyTypeDescriptionInherited from
idstring-Member.id
namestring-Member.name
joinedAtnumber--
connectedbooleanTrue while they have the room open on at least one device.-

withViews()

function withViews<State, View, Options>(kind): Kind<State, Options>;

After create, after every input the rules don't refuse, and on open, sends each member { view } and sets their status. A kind's end() waits until those have gone out, so the last status becomes the room's record.

Type Parameters

Type ParameterDefault type
State-
View-
Optionsany

Parameters

ParameterType
kindViewKind<State, View, Options>

Returns

Kind<State, Options>

Example

export const game = withViews({
  create: (options, ctx) => ({ board: Array(9).fill(''), x: ctx.by.id, o: null, next: ctx.by.id }),
  on(state, input, ctx) {},
  view: (state, member) => ({ board: state.board, mine: state.next === member.id }),
  status: (state, member) => (state.next === member.id ? { text: 'Your move', waiting: true } : { text: 'Their move', waiting: false }),
})

ViewKind

A kind of room, exported from rules.js under its name.

Example

export const game = {
  create(options, ctx) {
    return { board: Array(9).fill(''), x: ctx.by.id, o: null }
  },
  on(state, input, ctx) {
    if (input.type === 'join' && state.o) ctx.refuse('This game already has two players.')
  },
}

Extends

  • Kind<State, Options>

Type Parameters

Type Parameter
State
View
Options

Properties

PropertyTypeDescriptionInherited from
address?stringWhere Tinylib sends people who come in for a room of this kind from outside the app, such as 'game/:id'.Kind.address

ViewKind.create()

create(options, ctx): State;

Returns the first state, from the options tinylib.rooms.create passed. May refuse them with ctx.refuse.

Parameters
ParameterType
optionsOptions
ctxCreateContext
Returns

State

Inherited from

Kind.create

ViewKind.on()

on(
   state, 
   input, 
   ctx
): void;

Every input, one at a time. Changes state in place and calls ctx for outputs.

Parameters
ParameterType
stateState
inputInput
ctxContext
Returns

void

Inherited from

Kind.on

ViewKind.view()

view(
   state, 
   member, 
   ctx
): View;

What this member is sent.

Parameters
ParameterType
stateState
memberMember
ctxContext
Returns

View

ViewKind.status()

status(
   state, 
   member, 
   ctx
): Status;

The line Tinylib shows this member outside the app, and whether the room waits on them.

Parameters
ParameterType
stateState
memberMember
ctxContext
Returns

Status

Status

Properties

PropertyType
textstring
waitingboolean