A music library should not belong to the computer it was created on.

The goal for BakBeat was ordinary and specific. Put the catalog in a place the owner already trusts — iCloud Drive, OneDrive, MEGA, or some other folder they chose. Open it on one computer. Change it. Let that storage service copy the file. Open the same catalog on another computer and keep going. No BakBeat account. No sync service of our own. No database server. The person who has the file has the library.

That design is now used regularly across two Macs. It works for the intended sequential-access workflow, while simultaneous editing remains an acknowledged limitation. Copying the catalog does not copy every song it points at. Paths that belong to one machine stay in that machine’s settings.

The engineering question under that goal is narrower than a file format. What is a BakBeat library, and which part of the app is allowed to say what it contains?

One file, in storage the owner already has

A catalog that can move has to be something a person can point at. A folder full of databases, journals, and caches can move too, but only if every piece travels together and nothing else is quietly authoritative. We wanted one document: BakBeatLibrary.bbl. The storage provider’s job is to copy that file. BakBeat’s job is to read it and replace it.

Building our own cloud would have made BakBeat the owner of the library. Accounts, a server, and a sync protocol are a product of their own. They also fail in places the file does not: a login, an outage, a region. The owner’s Drive, OneDrive, or MEGA folder already answers “where does this live?” We did not need a second answer.

The catalog and the music are separate. Tracks point at files. Those files may live on a disk that does not come along when the catalog does. Opening the library on another computer restores the catalog. It does not, by itself, make every referenced audio file available. Folder choices for this Mac stay in host preferences. A new library writes empty path strings into the document so a synced file does not carry another computer’s folders as identity.

The library was a database inside a package

Before that document existed, a library was a directory named BakBeat.bbl. Inside it, Manifest.json held the library id, and Store/BakBeat.sqlite held the catalog. GRDB opened that database. The schema grew the way a product schema grows: fifty ordered migrations, from identity and tracks through playlists, video, audiobooks, remembered devices, and MiniDisc.

SQLite was a sound place to put those rows. Nothing in this change says the database was slow, corrupt, or too small. Browse, import, playback, and device work could all open it. An audit on 8 August 2026 counted SQL as a side effect across those paths, not behind one door. The library was the database, and the database was reachable from a lot of the app.

The same file also held rebuildable browse tables whose names ended in _not_authoritative. They were projections. They lived next to the rows they were derived from. A name is not a door. If product code can query them, they can start answering questions the catalog should answer.

Other stores already had other jobs, and those jobs were real. Host preferences said which folders this computer uses. ArtworkCache held generated images. Museum kept its own SQLite snapshots of preserved devices. Players such as the iPod nano 7th generation keep SQLite on the device. Those were never the BakBeat catalog. The confusion was inside the catalog itself: many callers, one database, and a second read model in the same file.

Before — SQLite sat in the middle
  • App Browse
  • App Import
  • App Playback
  • App Device integration

Each of these could open the database directly.

Library package BakBeat.bbl/
  • Package identity Manifest.json
  • Catalog authority Store/BakBeat.sqlite Artists, albums, tracks, playlists, metadata, and remembered-device facts.
  • Derived, not authority UX projections Rebuildable browse tables in the same database. Named so they would not count. Still queryable.

A document matches the way one person uses a library

The portability goal came first. It is founder intent for this product: the library moves because the owner moves a file they store themselves. The repository does not contain a design note, dated before the format existed, that writes that goal down. What the tree does contain is a rule added while the change was in progress. Do not keep SQLite because libraries usually use SQLite. Name the workload that made SQLite the usual answer, then show that BakBeat has that workload.

The workload we actually have is one person, one catalog, opened and then saved, as a whole document, in a folder they picked. It is not concurrent writers, not an ad-hoc query surface for other programs, and not a shared database server. Sequential use is the product. Two computers editing before the file has finished syncing is outside that product.

On 22 August 2026 the file format and a one-shot exporter landed. The commit says the shipping app still used SQLite. The export was for offline migration and checking the file, not a cutover. Over the next four days the cutover happened: create a library by writing the document, read the document when it exists, and stop letting product code treat the package database as the catalog. Pull request #8 merged on 26 August.

What the file actually is

BakBeatLibrary.bbl is a binary document. It is not a database engine. There is no SQL, no page cache, and no query planner. The open library is the decoded graph in memory. A save writes the whole file again.

The first eight bytes are the marker BBLDoc01, so a reader can reject a file that is not this document. A 128-byte header then carries the library’s UUID, a save counter, and where the rest of the file sits. The UUID is the library. The counter, saveSequence, goes up by one on each committed save. A newer counter on disk means memory is stale. That is how one process avoids writing an older snapshot over a newer one. It is not a merge between two computers.

The catalog is a directory of chunks. Each chunk is one collection: artists, albums, tracks, playlists, device facts, and the rest. Inside a chunk, every row stores its own column names and tagged values, so a new column is a name some older rows do not have. The directory entry points at the bytes and stores a CRC32 of them. A 64-byte footer repeats the save counter, stores a CRC32 of the directory, stores a SHA-256 of everything before the footer, and ends with a completion marker. A torn or unfinished file fails those checks instead of opening as a quiet partial catalog.

Opening the file memory-maps it when the system allows. Artists, albums, tracks, playlists, and the artwork facts that screen needs are decoded first. Videos, device file lists, and other collections stay mapped until something asks for them, or until a background pass checks the whole-file hash. That split lets the music library paint without decoding the rest of the document first.

Container version is 1. A file that needs a newer reader, or that contains a chunk kind this reader does not know, does not open. An older file that simply lacks a newer chunk is treated as having none of those rows. Podcasts and Guitar Pro artifacts were added as chunks. They were never tables in the legacy database, so a migrated library has them empty until the app writes them.

How an existing library became that file

From 22 August through 26 August the path was: export beside the live database, start using the export when it was present, then refuse to browse or search through SQLite at all.

The exporter reads Store/BakBeat.sqlite and does not write it. For each catalog table it runs SELECT *, or records zero rows when that table was never created. Projection tables are not copied. The library id comes from Manifest.json when that file is there, otherwise from the identity row. The new document starts at save sequence 1. MiniDisc queue sheets, which had been their own tables, are folded into playlists and the old chunks are cleared in the document only. The package database is left where it was.

If the document is missing and the package database is present, opening the app runs that export behind a progress sheet. A failure stays on the sheet and can be retried. A second export will not replace a document that already exists. Nothing deletes the package, and nothing copies changes back into it. After a successful export, the package is a leftover the owner can move aside. It is not a second catalog.

Product writes go through one commit path. That path loads the document, applies the edit, bumps the save counter, and writes the file. If the document is missing, catalog work throws rather than falling through into SQLite. Create writes an empty document. It does not create BakBeat.sqlite.

After — one document defines the catalog
  • App Browse
  • App Import
  • App Playback
  • App Device integration

These read and write the document. They do not update the old database.

The library BakBeatLibrary.bbl
  • Header Library identity UUID and saveSequence. A newer sequence on disk wins over memory.
  • Body Catalog chunks Named-column records for music, playlists, and device facts. A directory points at each chunk.
  • Footer Integrity check Chunk CRC32, directory CRC32, SHA-256, and a completion marker.

Copied once, into the document above. Not kept in sync. Not reversed.

Read-only migration source BakBeat.sqlite Leftover package database. Export reads it. Product code does not treat it as the catalog.
  • This computer Host folder preferences Where music and other surfaces live on this machine. Not library identity.
  • Derived ArtworkCache Generated images. Rebuildable from the catalog.
  • Derived export iTunes Music Library.xml A snapshot for other apps. Written from the catalog. Not a second catalog.
  • Separate systems Museum and on-device databases Preservation snapshots, and the player’s own SQLite. Different products.

Three things that broke while the file became the library

These are incidents from the change, not illustrations.

An import wrote an older snapshot over a newer edit

On 26 August an import held the in-memory catalog from the start of its commit. A Get Info edit during that work could look successful and then disappear, because the import saved the older graph back over the file. The correction is in the commit path. Writers in one process take a lock. Before applying an edit, the commit reads the header. If the on-disk saveSequence is newer, that file is the base and the captured snapshot is dropped. Memory is a cache. The file is the catalog.

Migration rewrote which folders this computer uses

Remembering the new document was implemented as a full placement rewrite. That cleared relocated video, museum, and audiobook folders, and it reset a music folder at /Volumes/Data/BakBeat/Music to an iCloud folder under the library home. Later imports wrote into the wrong tree. The correction is a boundary. Export and “remember this document” update artifact identity only. Host paths change when the owner picks a folder, not because the catalog file changed shape.

A cloud folder reopened yesterday’s library

Playlist sync had been writing temporary backups inside the document and replacing the file with APIs the cloud client does not treat as an update. The process could believe the write succeeded. The next launch opened the previous coordinated file. On that occasion, MP3s never reached the iPod. Saves now overwrite the destination in place, coordinated, with no sibling temporary file and no replace-style move. The storage client is supposed to see new bytes at the same path. The cost is below: that overwrite is not a crash-safe rename.

What the document does not remove

Every save rewrites the whole file. A one-field edit still encodes every chunk. That is the persistence model that matches “the file is the library.” It is a poor model for a multi-writer database, which is a workload we are not claiming.

The format is ours to maintain. Version 1 is the only container a current reader accepts. A chunk kind it does not know fails the open. That is deliberate. A partial open of an unknown catalog would be a second, quieter definition of what the library contains. Old files stay readable when they omit a chunk the new app added. New files do not open in an old app that has never heard of a chunk they include.

In-place overwrite is how a cloud folder keeps one identity for the file. It is not atomic in the crash sense. A failure while the bytes are being replaced can leave a file that no longer holds the previous good copy. A checksum rejects that open. It does not rebuild the previous copy.

saveSequence orders saves that one process can see. It does not reconcile two editors. The reader that exists is the Mac app.

SQLite did not leave the building. The one-shot exporter still reads the package database. Museum still has its own database. Device writers still speak SQLite to players that require it. Those are not leftover copies of the catalog. The catalog copy that is leftover is BakBeat.sqlite, and product code is required to leave it unread except for that export.

A single document makes ownership easier to explain. It does not make sync safe by itself. The storage provider can delay, duplicate, or deliver an older file. BakBeat will open the bytes it is given if they check out. It will not notice that another computer still has a save that has not arrived.

What the implementation holds

The tests in the tree lock these catalog rules:

  • The catalog is BakBeatLibrary.bbl. When that file exists, the package database is not the catalog.
  • Export reads SQLite and leaves those bytes unchanged. A fixture’s playlist item and device link still point at the same track.
  • Product writes exercised by those tests — import, playlists, favorites, metadata, device links — update the document and do not update the legacy database.
  • A commit that starts from a stale session reloads the newer on-disk sequence instead of overwriting it.
  • Browse after export does not fill the old projection tables. A second export refuses to replace a document already there.
  • A new library is an empty document. Creating it does not write BakBeat.sqlite.

The relationship checks use a small fixture, one artist and one track. They show that an export keeps a playlist item and a device link pointed at the same track. They do not measure speed.

Validated in daily use

The architecture is also used in practice. BakBeat’s developer regularly switches between a Mac mini and a MacBook, with the library document synchronized through iCloud Drive and the underlying music collection maintained on both computers through MEGA.

The same library can be opened and used on either Mac without rebuilding the catalog or operating a BakBeat synchronization service.

This is real-world validation of the intended sequential-access workflow, not a guarantee against simultaneous editing conflicts or interrupted synchronization. This validates the specific combination of iCloud Drive for the document and MEGA for the music, rather than compatibility with every storage provider.

The useful rule is the one the workload forced. Decide what the owner is actually doing, and who is allowed to define the data, before reaching for the store that is usual for someone else’s workload. For BakBeat, the owner is moving one catalog they keep themselves, on one computer at a time. The library is that document.

Back to case studies