Skip to content

Development workflow

This page covers the day-to-day loop for working on originchats-osl: building, running a test server, running the tests, formatting, and benchmarks.

Terminal window
cd ~/originchats-osl/src
osl compile main.osl -o ../originchats-osl

Fix every compiler warning. The originchats-osl binary is ignored by Git.

Use a throwaway database so you don’t touch real data:

Terminal window
cd ~/originchats-osl
./originchats-osl init ./db
./scripts/run.sh

./db is ignored by Git, and run.sh recompiles on every start, so restarting it picks up your changes. To keep several test databases, put them outside the checkout and pass the path: ./scripts/run.sh ~/oc-test-db. See Run a server for what init asks.

Set ORIGINCHATS_LOG_LEVEL=debug to log every command and HTTP request.

Tests are *.test.osl files next to the code they cover, such as src/db/shards/storage.test.osl. Run them from src/:

Terminal window
cd ~/originchats-osl/src
osl test # every test file under the current directory
osl test db/setup/init.test.osl # one file
osl test db/messages # one directory

osl test prints PASS or FAIL for each file and a total. The full suite runs in seconds.

Each test file is a small standalone program, not part of the server build:

  • It imports only the modules under test, by relative path, such as import "./access.osl".
  • It defines stand-ins for anything else those modules call, such as userIsOwner or writeFileAtomic, so the test controls their results.
  • It runs checks at the top level and throws a message when one fails.
  • Tests that touch files create a fixture directory, often under /tmp, and remove it afterwards.

For example, src/api/access.test.osl imports access.osl, stubs userIsJoined and userHasPermission, and throws if a write command isn’t in TIMEOUT_BLOCKED:

import "std:ws"
import "./access.osl"
def userIsJoined(string userid) boolean (
return true
)
def userHasPermission(string userid, string permission) boolean (
return true
)
for command in ["message_new", "message_edit", "slash_call"] (
if !apiTimeoutBlocks(command) (
throw `Timeouts must restrict ${command}`
)
)

src/db/messages/testing/storage.osl is a shared harness for message storage tests and benchmarks. It imports the message storage modules and stubs the rest of the server.

Add a test next to any storage, access, or helper code you change, especially for rollback on a failed save. For behavior that crosses the whole server, such as broadcasts and listeners, test against a running server as described in Add a command.

Terminal window
osl fmt src

This rewrites every source file under src/. Run it when formatting is part of your change, not as a side effect of an unrelated one. osl opal run fmt does the same.

snapshot/gen.sh transpiles src/main.osl, strips //line comments, formats it with gofmt, and writes it to snapshot/snapshot.go. osl opal run snap runs it. It needs osl and gofmt.

The snapshot is committed, and source changes usually regenerate it, so reviewers can see how a change affects the generated Go. It’s also handy for checking how OSL compiled an expression.

The benchmarks are standalone programs. Run one with osl run, or with osl bench to profile it:

Terminal window
cd ~/originchats-osl
osl run src/db/messages/benchmarks/around.osl
osl bench src/db/messages/benchmarks/around.osl --runs 3
File Measures Fixture
src/db/messages/benchmarks/around.osl Building the message index, a full-history scan compared with an indexed messages_around, a deep messages_get page, and reading pins. Writes 100,000 messages in 100 shards to a temporary directory under /tmp, and deletes it afterwards.
src/db/messages/benchmarks/edits.osl 100 edits to one message in a 1,000-message shard, then 100 appends. Temporary directory under /tmp, deleted afterwards.
src/db/threads/benchmarks/list.osl Listing 200 threads in a forum, in full and filtered to one member. Expects an existing database at /tmp/threads-bench/db with a forum channel forum-a holding 200 threads. The repository doesn’t include a script to create it.

Each prints a line per phase with the elapsed milliseconds. Results depend on the machine and disk, so compare runs on the same machine.

  • Arrays and string indexes start at 1. Protocol fields such as channel_move.position can still be zero-based, so convert deliberately at the handler boundary.
  • Mixed arithmetic is evaluated left to right, not with the usual precedence. Always add parentheses when mixing operators, as the source does: ((shard - 1) * 1000) + position.
  • main.osl is the only file that imports std: packages, and every other module shares its namespace. Don’t add a second top-level program beside it.

When you change a command, update its page under src/content/docs/protocol/commands/ in the docs repository, and any server, concept, or client guide the change affects. Read the handler and the helpers it calls, so the page covers serialization, fallback events, and side effects that the schema alone doesn’t show.