Tinylib
DiscoverDocsSign in
Sign in
Tinylib
AboutDiscoverDocsMake an appPricingTermsPrivacyDeveloper TermsContact

Start

  • Quickstart
  • How Tinylib works

Guides

  • Data
  • Rooms and rules
  • Games
  • Friends
  • Places and links
  • Reminders and notifications
  • Publishing
  • Errors and offline
  • Platforms

Client API

  • tinylib
  • tinylib.data
  • tinylib.rooms
  • tinylib.invitations
  • tinylib.friends
  • tinylib.places
  • tinylib.reminders
  • Types
  • tinylib-sdk/kits

Rules API

  • Kinds
  • ctx
    • roomId
    • kind
    • now
    • version
    • members
    • user
    • random()
    • data
    • refuse()
    • emit()
    • notify()
    • setName()
    • setTimer()
    • cancelTimer()
    • remove()
    • end()
    • Recipients
    • MemberValues
    • RoomData
    • RoomMember

Reference

  • tinylib.json
  • Command line
  • Limits

Plain text

  • llms.txt
  • llms-full.txt

ctx

Every handler but view, status, and canInvite gets ctx as its last argument.

A handler's effects, like events and notifications, happen together after it returns, and only if it neither refuses nor throws. Otherwise none of them happens and state is unchanged. If Tinylib can't carry out one effect, it skips that effect alone and shows the error on your developer page and in npx tinylib-sdk dev. An input that would leave the room's state or data past its limit is the exception: none of its effects happen.

ctx.roomId

ctx.roomId: string
The room's id.

ctx.kind

ctx.kind: string
The kind this room runs: the name of its export in rules.js.

ctx.now

ctx.now: number
When Tinylib took this input, in milliseconds since the Unix epoch. It's the same throughout the input. In a timer's handler, it's when the timer went off, at or after its at.

Use it instead of Date.now(), which doesn't follow npx tinylib-sdk dev when it moves the clock forward to test timers.

ctx.version

ctx.version: number
The number of the version whose rules last handled an input in this room without refusing or throwing. In migrate, it's fromVersion.

ctx.members

ctx.members: RoomMember[]
The room's current members, guests included, in the order they joined. In create it's the creator alone. In join it includes the new member, and in leave it no longer includes the member who left.

ctx.user

ctx.user: Member | null
The user the input is about: the creator in create, the member who acted in an action, the new member in join, the member who left in leave, and the member who came or went in presence. Null in a timer's handler, in beat, and in migrate.

ctx.random()

ctx.random(): number
Returns a random number like Math.random(), drawn on Tinylib's servers. Members can't predict it, so use it for dice and shuffles.
JavaScript
roll(state, data, ctx) { state.die = 1 + Math.floor(ctx.random() * 6) },

ctx.data

ctx.data: ((userId: string) => MemberValues) & { room: RoomData }
ctx.data(userId) is a member's data, as MemberValues, where userId is a current member's id, or the leaving member's in leave. ctx.data.room is the room's data, as RoomData.
JavaScript
move(state, square, ctx) { // ... if (state.winner) { if (state.winner !== 'draw') ctx.data(ctx.user.id).add('wins', 1, { audience: 'friends' }) ctx.data.room.set('final', { board: state.board, winner: state.winner }) } },

Throws

TypeError
When userId isn't a string.

ctx.refuse()

ctx.refuse(sentence: string): never
Stops the handler, drops everything it did, and rejects the client's call with refused, with sentence as the error's message. A try/catch around it doesn't stop the refusal.
JavaScript
move(state, square, ctx) { if (state.players[state.turn] !== ctx.user.id) ctx.refuse("It's not your turn.") // ... },

Parameters

sentencestring
A sentence for the user. Past 300 characters, it's cut.

Throws

TypeError
When it's called outside create and actions, the inputs a client waits on. The input then fails, even when a try/catch catches the error.

ctx.emit()

ctx.emit(to: Recipients, event: unknown): void
Sends event to the members in to who have the room open, through room.onEvent, after this input's views. Nobody else ever gets it, so use events for one-off moments like a sound, and put what members must see in view. An id that isn't a member's, or an event past 64 KB, is skipped.
JavaScript
move(state, square, ctx) { // ... ctx.emit('everyone', { sound: 'place', square }) },

Throws

TypeError
  • When event can't be saved as JSON.
  • When to isn't a Recipients value, or is 'user' where ctx.user is null.

ctx.notify()

ctx.notify(to: string, notice: Notice): void
Sends a notification to member to. Tapping it opens the room at the kind's address. It needs "notifications" in uses; without it, nothing is sent.

It reaches the member only when they don't have the room open anywhere, haven't turned off your app's Rooms switch, and haven't muted the room. Guests never get one. A member has one notification from the room at a time, and a newer one replaces it. Opening or leaving the room clears it there and on the member's Android phones; another browser clears it once the room opens in it.

An id that isn't a member's is skipped, and so is a notification whose title or text is too long; nothing is cut to fit. Past 60 per hour to one member, or 300 per hour from the room, Tinylib drops the room's notifications without an error.

JavaScript
move(state, square, ctx) { // ... const next = state.players[state.turn] if (next) ctx.notify(next, { title: 'Your turn', text: `${ctx.user.name} played.` }) },

Parameters

tostring
The id of a current member.
noticeNotice
What the notification says.
notice.titlestring
The notification's first line, up to 80 characters.
notice.textstringoptional
The line under the title, up to 300 characters.

Throws

TypeError
When to isn't a string, notice isn't an object, or its title or text isn't a string.

ctx.setName()

ctx.setName(text: string): void
Sets the room's name, which members see in their list of rooms. Tinylib trims it, and skips a name that's empty, past 60 characters, or caught by its filter.
JavaScript
create(options, ctx) { if (typeof options.title === 'string') ctx.setName(options.title) // ... },

Throws

TypeError
When text isn't a string.

ctx.setTimer()

ctx.setTimer(name: string, at: number, data?: unknown): void
Runs timers[name] with data at time at. Each name holds one timer: setting it again moves it and replaces its data. For several deadlines, like one clock per player, keep them in state and set the timer for the soonest.

An at in the past goes off as soon as it can. A timer set in a timer's handler or in beat waits at least 1 second. The rules can't read a timer back, so keep any deadline your client shows in state.

Timers count toward the size of the room's state. They don't keep a room going: one unused for its kind's idleMs ends, and ending a room cancels its timers. A timer with no handler by its name fails when it goes off, and one whose handler throws doesn't run again.

JavaScript
move(state, square, ctx) { // ... ctx.setTimer('turn', ctx.now + 60_000) },

Parameters

namestring
The name of its handler in timers.
atnumber
Milliseconds since the Unix epoch, as ctx.now gives them.
dataunknownoptional
Any JSON value, passed to the timer's handler.

Throws

TypeError
When name isn't a string, at isn't a finite number, or data can't be saved as JSON.

ctx.cancelTimer()

ctx.cancelTimer(name: string): void
Cancels the timer name, if it's set.

Throws

TypeError
When name isn't a string.

ctx.remove()

ctx.remove(id: string): void
Removes member id after this input, then runs leave for them with why as 'removed'. They can join again like anyone else.

An id that isn't a member's is skipped. Removing the last member ends the room.

Throws

TypeError
When id isn't a string.

ctx.end()

ctx.end(): void
Ends the room after this input. This input's views, events, and notifications still go out. Then its timers are canceled, its link stops working, and no handler runs again, leave included. Members keep the room in their past rooms, with its last status and its room data.

To play again, reset state instead.

JavaScript
resign(state, data, ctx) { state.winner = state.players.x === ctx.user.id ? 'o' : 'x' ctx.data.room.set('final', { board: state.board, winner: state.winner }) ctx.end() },

Recipients

type Recipients = 'user' | 'everyone' | string | string[]
Who ctx.emit reaches: a member's id, a list of ids, 'everyone' for every current member, or 'user' for ctx.user.

MemberValues

interface MemberValues
What the rules keep for a member, under the keys rulesKeys declares in tinylib.json. Only the rules write these keys, and any room of your app can, so a member's wins can add up across rooms.

A write is skipped when:

  • rulesKeys doesn't declare its key, or the key is longer than 128 bytes.
  • It's for someone who isn't a member, other than the leaving member in leave.
  • It would take the user's data your rules write past 64 KB.

Methods

get(key)
Returns a copy of the value under key, including writes made earlier in this input, or undefined.
keys(prefix?)
Returns the declared keys that have a value and start with prefix, sorted.
set(key, value, options?)
Sets key to value.
remove(key)
Removes key.
add(key, by, options?)
Adds by to the number under key, starting from 0 when it holds no number.
max(key, value, options?)
Sets key to value if value is larger or key holds no number, even when other rooms write the key at the same time.
min(key, value, options?)
Sets key to value if value is smaller or key holds no number, even when other rooms write the key at the same time.

MemberValues.get()

MemberValues.get(key: string): unknown
Returns a copy of the value under key, including writes made earlier in this input, or undefined.

Throws

TypeError
  • When key isn't a string or isn't declared in rulesKeys.
  • When the id passed to ctx.data isn't a current member's, or the leaving member's in leave.

MemberValues.keys()

MemberValues.keys(prefix?: string): string[]
Returns the declared keys that have a value and start with prefix, sorted. Without prefix, returns every one.

Throws

TypeError
When the id passed to ctx.data isn't a current member's, or the leaving member's in leave.

MemberValues.set()

MemberValues.set(key: string, value: unknown, options?: { audience?: Audience }): void
Sets key to value. With audience: 'friends', the member's friends who have the app can read it. Without audience, the key keeps its audience, or 'me' for a new key.

Throws

TypeError
When key isn't a string, value is undefined or can't be saved as JSON, or audience isn't 'me' or 'friends'.

MemberValues.remove()

MemberValues.remove(key: string): void
Removes key.

Throws

TypeError
When key isn't a string.

MemberValues.add()

MemberValues.add(key: string, by: number, options?: { audience?: Audience }): void
Adds by to the number under key, starting from 0 when it holds no number. When rooms add to the same key at the same time, every add counts. audience works as in set.
JavaScript
ctx.data(ctx.user.id).add('wins', 1, { audience: 'friends' })

Throws

TypeError
When key isn't a string, by isn't a finite number, or audience isn't 'me' or 'friends'.

MemberValues.max()

MemberValues.max(key: string, value: number, options?: { audience?: Audience }): void
Sets key to value if value is larger or key holds no number, even when other rooms write the key at the same time. audience works as in set.
JavaScript
ctx.data(ctx.user.id).max('best', state.score, { audience: 'friends' })

Throws

TypeError
When key isn't a string, value isn't a finite number, or audience isn't 'me' or 'friends'.

MemberValues.min()

MemberValues.min(key: string, value: number, options?: { audience?: Audience }): void
Sets key to value if value is smaller or key holds no number, even when other rooms write the key at the same time. audience works as in set.

Throws

TypeError
When key isn't a string, value isn't a finite number, or audience isn't 'me' or 'friends'.

RoomData

interface RoomData
What a room keeps for its members to read while it runs and after it ends, like the final board. Only the room's rules write it. Every member can read all of it with tinylib.rooms.data, so keep secrets in state, behind view.

Methods

get(key)
Returns a copy of the value under key, including writes made earlier in this input, or undefined.
keys(prefix?)
Returns the keys that start with prefix, sorted.
set(key, value)
Sets key to value.
remove(key)
Removes key.

RoomData.get()

RoomData.get(key: string): unknown
Returns a copy of the value under key, including writes made earlier in this input, or undefined.

Throws

TypeError
When key isn't a string.

RoomData.keys()

RoomData.keys(prefix?: string): string[]
Returns the keys that start with prefix, sorted. Without prefix, returns every key.

RoomData.set()

RoomData.set(key: string, value: unknown): void
Sets key to value. When the input's writes would leave the room's data past 256 KB, the whole input changes nothing, as the full-room rule says.

Throws

TypeError
When key isn't a string, or value is undefined or can't be saved as JSON.

RoomData.remove()

RoomData.remove(key: string): void
Removes key.

Throws

TypeError
When key isn't a string.

RoomMember

interface RoomMember extends Member
A Member as ctx.members lists them.
Extends Member, so it also has id, name, and guest.

Properties

joinedAtnumber
When the member joined, in milliseconds on Tinylib's clock.
presentboolean
True while the member has the room open on a device, and as they come in, before their page connects. After they close it everywhere, it stays true for 5 seconds, until presence runs with present false.
PreviousKindsNexttinylib.json

On this page

  • roomId
  • kind
  • now
  • version
  • members
  • user
  • random()
  • data
  • refuse()
  • emit()
  • notify()
  • setName()
  • setTimer()
  • cancelTimer()
  • remove()
  • end()
  • Recipients
  • MemberValues
  • get()
  • keys()
  • set()
  • remove()
  • add()
  • max()
  • min()
  • RoomData
  • get()
  • keys()
  • set()
  • remove()
  • RoomMember