osu-stable-db
v0.2.2
Published
TypeScript reader and writer for osu!stable database files.
Downloads
619
Readme
osu-stable-db
TypeScript reader and writer for osu!stable database files.
This project is implemented entirely according to the official osu! wiki page for stable database files: Legacy database file structure.
This library currently supports the latest database structure from version 20250107 onward for:
- osu!.db
- collection.db
- scores.db
The core binary and database logic is browser-compatible. A separate Node-only subpath is provided for direct file reads, writes, and osu! folder helpers.
Scope
- Supports only 20250107 and later database structures
- Preserves null string semantics used by the stable database format
- Uses bigint for 64-bit values such as DateTime ticks
- Exposes byte-array database APIs from the main entry
- Exposes direct file APIs from the ./node subpath
Older stable database versions are intentionally out of scope.
Install
pnpm add osu-stable-dbBrowser Usage
Use the main entry when you already have bytes in memory.
import {
readOsuDatabase,
writeCollectionDatabase,
type CollectionDatabase,
} from 'osu-stable-db'
const osuBytes: ArrayBuffer = bytesFromSomewhere
const osuDatabase = readOsuDatabase(osuBytes)
const collectionDatabase: CollectionDatabase = {
version: osuDatabase.version,
collections: [
{
name: 'Favorites',
beatmapMd5Hashes: ['d41d8cd98f00b204e9800998ecf8427e'],
},
],
}
const collectionBytes = writeCollectionDatabase(collectionDatabase)Node Usage
Use the Node subpath when you want to work with a full osu! folder or with direct file reads and writes through node:fs/promises.
import {
OsuFolder,
} from 'osu-stable-db/node'
const osuFolder = new OsuFolder('C:/osu!')
const osuDatabase = await osuFolder.readOsuDatabase()
const scoresDatabase = await osuFolder.readScoresDatabase()
const newestBeatmap = osuDatabase.beatmaps.at(-1)
const newestScore = scoresDatabase.beatmaps.at(-1)?.scores.at(-1)
if (newestBeatmap !== undefined) {
const osuFilePath = osuFolder.getOsuFilePath(newestBeatmap)
console.log(osuFilePath)
}
if (newestScore !== undefined) {
const osrFilePath = osuFolder.getOsrFilePath(newestScore)
console.log(osrFilePath)
}If you only need path-based file IO, the same subpath also exports helpers such as readOsuDatabaseFile, writeCollectionDatabaseFile, and writeScoresDatabaseFile.
Types And Time Values
- Date-like 64-bit values are exposed as DateTimeTicks, backed by bigint
- Helper functions for DateTimeTicks and mod flags live in src/core/utils.ts
JavaScript Date is not used as the storage type because it loses sub-millisecond tick precision.
AI Disclosure
This project is 100% AI-generated.
Tests And Fixtures
Committed minimal fixtures live in tests/files.
To run local node tests and the inspection script against your real osu! installation, set this in .env:
OSU_STABLE_DIR=C:/osu!When OSU_STABLE_DIR is set, local node tests read your real database files and verify byte-for-byte round-trip for osu!.db, collection.db, and scores.db.
You can also generate a local inspection report for a specific beatmap identifier with:
pnpm run local:inspect -- 5288868The generated report is written to:
Development
pnpm install
pnpm run typecheck
pnpm test -- --run
pnpm run buildNotes On Validation
Local validation also passes against private real-world database files referenced by OSU_STABLE_DIR:
- osu!.db: 58,295,932 bytes, 72,038 beatmaps
- collection.db: 195,402 bytes, 11 collections, 5,743 stored beatmap references
- scores.db: 3,714,272 bytes, 11,331 beatmap score groups, 26,481 scores
Those local tests passed with parsing and byte-for-byte round-trip verification enabled.
