Skip to content

Persistence

Persistence modules own file layout and durability. Handlers should never directly write database files.

Small mutable collections use JSON. Loaders parse an expected top-level type and fail startup loudly for missing or invalid required files. Updates write a temporary file, rename it into place, and remove the temporary file on failure. In-memory changes retain enough previous state to roll back.

Messages are JSON objects stored one per line. Each channel or thread storage root uses zero-padded numbered shards. When the active shard reaches 1,000 lines, append creates the next shard. Queries walk these files for tail pagination, IDs, replies, search, pins, and around-message windows.

Edits and deletion locate the containing shard and rewrite it. New messages generate UUIDs and Unix-second timestamps. The server stores internal user IDs, reply targets, signature metadata, attachment records, embeds, reaction ID arrays, and interaction data.

dbMessageToPublic is the message serialization boundary. It:

  • calculates mentions/pings;
  • replaces internal author and reaction user IDs with usernames;
  • limits reply previews;
  • converts interaction user IDs to usernames while retaining signature fields;
  • hydrates poll embeds for the requesting viewer;
  • sets type: "message".

Use existing public serializers for users, roles, threads, polls, assets, and attachments. Never return a raw storage object when it contains internal identities or credentials.

Validate → authorize → normalize → mutate/persist → audit/refresh → return event. A broadcast is a claim that the mutation is durable, so it must come last.