Architecture
This page is a map of the originchats-osl source for contributors. It shows where each part of the server lives, what happens at startup, and the path a WebSocket command takes from arrival to response.
OSL compiles the whole of src/ into one Go program. main.osl pulls every other module in with import, and all modules share one namespace, so a function defined in any file can be called from any other.
Source layout
Section titled “Source layout”| Path | Responsibility |
|---|---|
src/main.osl |
Command line (init and use), startup order, background threads, HTTP routes, and the WebSocket server. It’s the only file that imports std: packages. |
src/config/ |
Defaults, loading, validation, automod rule compilation, and config_update merging. |
src/db/ |
Everything on disk: one directory per resource (users/, channels/, messages/, threads/, and so on), plus shared storage in storage.osl and transactions.osl. |
src/db/setup/ |
The init wizard and the initial database contents. |
src/db/migrations/ |
Converters for older storage layouts. |
src/db/shards/, src/db/message_index/ |
Numbered JSONL message files and their rebuildable index. See Persistence. |
src/api/index.osl |
Parses frames, checks access, dispatches commands, and routes responses. |
src/api/access.osl |
Tables of which commands need which permission, which are blocked during a timeout, and which work before login. |
src/api/connections.osl |
Connect and disconnect hooks and the heartbeat. |
src/api/handlers/<group>/ |
One file per protocol command, such as handlers/webhooks/webhook_create.osl. |
src/api/helpers/<area>/ |
Rules shared between handlers: assets, auth, automod, channels, link embeds, messages, notifications, polls, rate limits, reactions, reports, roles, server maintenance, slash commands, threads, uploads, users, voice, and webhooks. |
src/slash/ |
The built-in slash commands. Each file registers itself at import time. |
src/plugins/ |
OSL plugin hooks and the JavaScript plugin runtime. |
src/attachments/, src/avatars/, src/assets/ |
HTTP upload and download for attachments, local account avatars, server icons, emoji, and stickers. attachments/storage.osl holds the local and S3 backends. |
src/webhooks/ |
Incoming webhook HTTP endpoints. |
src/messages/ |
The HTTP message page at /messages/{channel}/{message}. |
src/pages/ |
Public HTML pages: /, /info, /user/{username}, and /invite/{code}. |
src/utils/ |
Response builders (objErr, channelResponse), broadcast helpers, and logging. |
Tests (*.test.osl) and benchmarks sit next to the code they cover. See Development workflow.
Startup
Section titled “Startup”main.osl runs top to bottom. For use <directory>, it:
- Checks the directory, changes into it, and loads
.env. - Loads and validates
config.json, then resolves the server icon and banner. - Imports the database, slash command, plugin, API, and HTTP modules. Built-in slash commands register here.
- Creates the S3 client and exits if S3 storage is selected but not configured.
- Undoes any interrupted file transaction (
dbRecover). - Loads users, roles, channels, webhooks, access settings, and unread state into memory (
dbSeedMemory). - Builds mention history if needed (
mentionsMigrate), then migrates attachment details if needed (attachmentMigrate). - Loads saved timeouts and JavaScript plugins.
- Starts background threads: timeout expiry (every second), the Rotur ban check (every hour), four link preview workers, and the WebSocket heartbeat (every 30 seconds).
- Registers HTTP routes, attaches the WebSocket server at
/, and listens.
A failure in step 1, 2, or 4 to 7 stops the server. A missing or unreadable core file such as users.json in step 6 stops it with Missing required database file: <path> or Invalid JSON in <path>: .... See Updating for what the migrations do.
How a WebSocket command is handled
Section titled “How a WebSocket command is handled”- Connect.
onConnectsends thehandshakeframe. - Parse.
onMessageparses the frame as JSON. Invalid JSON getsInvalid message format. - Check access.
handleCmdcallsapiAccessError. It requires a login (except forauth,login,register, andapplication_submit), current membership, and the command’s permission fromCOMMAND_PERMISSIONSorCOMMAND_ALTERNATIVES. - Check timeouts. If the command is in
TIMEOUT_BLOCKEDand the user is timed out, the reply is arate_limitframe. - Plugins. JavaScript plugins can block or rewrite the request (
jsPluginPre), and OSL plugins wrap the dispatch (pluginHandle). - Dispatch.
dispatchCmdmatchescmdto a handler. An unknown command returns no frames. - Handle. The handler validates the request, checks resource rules, changes state, and returns a list of frames. If it throws, the text becomes an
errorframe. - Refresh. Commands in
METADATA_REFRESH_COMMANDS, such as role changes, triggermetadataRefresh. - Route. Each frame gets the request’s
listener. A frame markedglobal: truegoes to the caller, then loses itslistenerand goes to every other logged-in connection that can see itschanneland isn’t severed from the caller. Other frames go to the caller only. - Follow-up.
linkEmbedSchedulequeues link previews for new messages, and the request is logged.
Handlers never send the caller’s copy of a broadcast themselves. They return it, and the router does the rest.
Shared operations
Section titled “Shared operations”Handlers stay thin by calling shared helpers:
- Message handlers share
messageSchemaandschemaMessageIdfor request fields, andmessageContextfor channel and thread access. channelResponsebuilds{cmd, channel}responses and addsthread_idfor thread messages.- Pinning and unpinning share
messagePinChange. - Bans, timeouts, and severs share
userBanChange,timeoutApply, anduserSeverChangebetween WebSocket handlers and slash commands. These own owner protection, audit entries, and disconnects. connectionActorturns a connection into the actor recorded in audit entries.refreshChannelssends each connection an updated channel list after a channel change.
| Kept on disk and reloaded at startup | Kept in memory only |
|---|---|
| Config, users, roles, channels, threads, messages, polls, unreads, mention history, webhooks, reports, audit log, attachment details, access settings, timeouts, custom emoji and stickers | Open connections, presence, client and device details, voice channel members, upload tokens, pending slash calls from bots, and bot-registered slash commands |
Users, roles, channels, webhooks, and access settings are read from memory and written through on every change. Messages are read from disk through bounded caches. See Persistence.
Presence ordering
Section titled “Presence ordering”Login completion, disconnects, status changes, and permission refreshes take the presence_events lock, so presence frames go out in order. The list of connections per user is guarded by the connections lock. Saving users uses its own users lock and rollback.
Release the connections lock and any storage lock before sending frames. Only the outer presence_events lock should be held while sending, to keep ordering. Public clients and devices lists are computed from userConns each time, not stored in a second index.
