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.
cd ~/originchats-osl/srcosl compile main.osl -o ../originchats-oslFix every compiler warning. The originchats-osl binary is ignored by Git.
Run a test server
Section titled “Run a test server”Use a throwaway database so you don’t touch real data:
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/:
cd ~/originchats-osl/srcosl test # every test file under the current directoryosl test db/setup/init.test.osl # one fileosl test db/messages # one directoryosl 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
userIsOwnerorwriteFileAtomic, 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.
Format
Section titled “Format”osl fmt srcThis 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.
Generated Go snapshot
Section titled “Generated Go snapshot”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.
Benchmarks
Section titled “Benchmarks”The benchmarks are standalone programs. Run one with osl run, or with osl bench to profile it:
cd ~/originchats-oslosl run src/db/messages/benchmarks/around.oslosl 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.
OSL gotchas
Section titled “OSL gotchas”- Arrays and string indexes start at 1. Protocol fields such as
channel_move.positioncan 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.oslis the only file that importsstd:packages, and every other module shares its namespace. Don’t add a second top-level program beside it.
Keep the docs current
Section titled “Keep the docs current”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.
