diff --git a/AGENTS.md b/AGENTS.md
index aafe4a0..a4f55ae 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -11,6 +11,26 @@ This package converts Pro Cycling Manager CDB binary database files to and from
- Low-level binary format handling lives in [src/reader.ts](src/reader.ts), [src/writer.ts](src/writer.ts), [src/types.ts](src/types.ts), and [src/tableMetadata.ts](src/tableMetadata.ts).
- Compression helpers live in [src/compression.ts](src/compression.ts).
+### Terminology
+
+Three words, deliberately not interchangeable:
+
+- **CDB** — Cyanide's binary database format, the `.cdb` file. This is what the
+ library reads and writes, and it is always handled as a *buffer*, never a path:
+ the public API takes `cdbBuffer` / returns `Uint8Array`, and only the CLI in
+ [src/cli.ts](src/cli.ts) ever touches the filesystem. The format internals live
+ in [src/reader.ts](src/reader.ts), [src/writer.ts](src/writer.ts),
+ [src/compression.ts](src/compression.ts) and [src/tableMetadata.ts](src/tableMetadata.ts).
+- **database** — on its own, always the *SQLite* side: a `sql.js` `Database`
+ instance (aliased `SqlDatabase`) or the `.sqlite` file it exports to. Never use
+ it bare for a `.cdb`; say "CDB" or "CDB database" when that is what you mean.
+- **save** — a `.cdb` the *game itself wrote* as the player played, as opposed to
+ an official release or a community update. Nothing in the conversion path cares
+ about the difference, so this word belongs only where the provenance is the
+ actual point: the reverse-engineering notes in
+ [src/keyInference.ts](src/keyInference.ts) and [src/tableMetadata.ts](src/tableMetadata.ts)
+ ("observed in real saves"). Do not use it as a generic name for the input file.
+
## Commands
- Install: `npm install`
diff --git a/LICENSE b/LICENSE
index 1af42c5..ddb588c 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,6 +1,6 @@
MIT License
-Copyright (c) 2025 Mathieu Picciolli
+Copyright (c) 2025 PCMStack
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
diff --git a/README.md b/README.md
index 94fb2ca..584af51 100644
--- a/README.md
+++ b/README.md
@@ -1,13 +1,13 @@
# cdb-converter
[](https://www.npmjs.com/package/cdb-converter)
-[](https://github.com/mpicciolli/cdb-converter/actions/workflows/ci.yml)
+[](https://github.com/PCMStack/converter/actions/workflows/ci.yml)
[](./LICENSE)
[](https://nodejs.org)
Convert **Pro Cycling Manager CDB** database files to and from SQLite, straight from the command line or your own code. Lightweight, isomorphic (Node.js **and** the browser), and zero-configuration.
-The conversion is **lossless**: a full `cdb → sqlite → cdb` round-trip preserves every table, column, data type, and flag — so you can edit a save in any SQLite tool and load it back into the game. Optionally, it can reconstruct the save's relationships as real `PRIMARY KEY` / `FOREIGN KEY` constraints, turning the export into a normalized database you can explore with JOINs and ER-diagram tools.
+The conversion is **lossless**: a full `cdb → sqlite → cdb` round-trip preserves every table, column, data type, and flag — so you can edit a database in any SQLite tool and load it back into the game. Optionally, it can reconstruct the database relationships as real `PRIMARY KEY` / `FOREIGN KEY` constraints, turning the export into a normalized database you can explore with JOINs and ER-diagram tools.
> [!NOTE]
> Based on [agfor/pcmdbedit](https://github.com/agfor/pcmdbedit/) — many thanks to agfor for the foundational work.
@@ -53,7 +53,7 @@ npm install cdb-converter
The fastest way to try it is the CLI:
```bash
-npx cdb-converter save.cdb
+npx cdb-converter database.cdb
```
## Command line
@@ -61,17 +61,17 @@ npx cdb-converter save.cdb
The package ships a `cdb-converter` command. The conversion direction is auto-detected from the input file extension.
```bash
-# CDB → SQLite (default output: save.sqlite)
-npx cdb-converter save.cdb
+# CDB → SQLite (default output: database.sqlite)
+npx cdb-converter database.cdb
-# SQLite → CDB (default output: save.cdb)
-npx cdb-converter save.sqlite
+# SQLite → CDB (default output: database.cdb)
+npx cdb-converter database.sqlite
# Provide an explicit output path (directories are created as needed)
-npx cdb-converter save.cdb data/save.sqlite
+npx cdb-converter database.cdb data/database.sqlite
# Reconstruct PRIMARY KEY / FOREIGN KEY constraints (CDB → SQLite only)
-npx cdb-converter save.cdb save.sqlite --normalize
+npx cdb-converter database.cdb database.sqlite --normalize
# Help / version
npx cdb-converter --help
@@ -83,10 +83,10 @@ npx cdb-converter --version
| `.cdb` | CDB → SQLite | `.sqlite` |
| `.sqlite` / `.db` | SQLite → CDB | `.cdb` |
-| Option | Effect |
-| ------------------- | -------------------------------------------------------------------------------------------------- |
+| Option | Effect |
+| ------------------- | --------------------------------------------------------------------------------------------------------------------------- |
| `-n`, `--normalize` | (CDB → SQLite only) reconstruct PK/FK constraints from PCM naming conventions. See [Normalized schema](#normalized-schema). |
-| `--index-fk` | Implies `--normalize`; also indexes every FK column for faster JOINs (roughly doubles output size). |
+| `--index-fk` | Implies `--normalize`; also indexes every FK column for faster JOINs (roughly doubles output size). |
## Library usage
@@ -100,7 +100,7 @@ import { cdbToSql } from "cdb-converter";
const SQL = await initSqlJs();
// Read and convert a CDB file
-const cdbBuffer = fs.readFileSync("save.cdb");
+const cdbBuffer = fs.readFileSync("database.cdb");
const db = cdbToSql(cdbBuffer, SQL);
// Query it like any SQLite database
@@ -108,7 +108,7 @@ const result = db.exec("SELECT * FROM Teams LIMIT 5");
console.log(result[0].values);
// Export to a .sqlite file
-fs.writeFileSync("save.sqlite", db.export());
+fs.writeFileSync("database.sqlite", db.export());
```
> [!IMPORTANT]
@@ -154,11 +154,11 @@ import { sqlToCdb } from "cdb-converter";
const SQL = await initSqlJs();
// Load a SQLite database and convert back to CDB
-const sqliteBuffer = fs.readFileSync("save.sqlite");
+const sqliteBuffer = fs.readFileSync("database.sqlite");
const db = new SQL.Database(sqliteBuffer);
const cdbBuffer = sqlToCdb(db); // automatically compressed
-fs.writeFileSync("save.cdb", Buffer.from(cdbBuffer));
+fs.writeFileSync("database.cdb", Buffer.from(cdbBuffer));
```
### Compression
@@ -269,11 +269,11 @@ A full `cdb → sqlite → cdb` round-trip on a real ~60k-row database stays wel
Normalization is opt-in and costs only what you ask for (measured against the default conversion, ~60k rows):
-| Mode | Conversion time | Output size |
-| --------------------------------------------- | --------------- | ----------- |
-| Default (flat) | baseline | baseline |
-| `normalize` | +~10% | +~40% |
-| `normalize` + `indexForeignKeys` | +~40% | +~130% |
+| Mode | Conversion time | Output size |
+| -------------------------------- | --------------- | ----------- |
+| Default (flat) | baseline | baseline |
+| `normalize` | +~10% | +~40% |
+| `normalize` + `indexForeignKeys` | +~40% | +~130% |
See **[bench/README.md](bench/README.md)** for the full per-fixture numbers, the bundle breakdown, and how to reproduce them (`npm run bench`).
diff --git a/biome.json b/biome.json
index 611c519..15d8158 100644
--- a/biome.json
+++ b/biome.json
@@ -10,6 +10,12 @@
"enabled": false
},
"files": {
- "includes": ["**", "!dist", "!node_modules", "!coverage"]
+ "includes": [
+ "**",
+ "!dist",
+ "!node_modules",
+ "!coverage",
+ "!samples/browser/vendor"
+ ]
}
}
diff --git a/package.json b/package.json
index 65c813e..d6f6b22 100644
--- a/package.json
+++ b/package.json
@@ -3,11 +3,15 @@
"version": "0.3.0",
"description": "Convert Pro Cycling Manager CDB files to/from SQLite and other formats. TypeScript library with zero configuration.",
"license": "MIT",
- "author": "mpicciolli",
+ "author": "PCMStack",
"repository": {
"type": "git",
- "url": "https://github.com/mpicciolli/cdb-converter"
+ "url": "https://github.com/PCMStack/converter"
},
+ "bugs": {
+ "url": "https://github.com/PCMStack/converter/issues"
+ },
+ "homepage": "https://github.com/PCMStack/converter#readme",
"keywords": [
"cdb",
"database",
diff --git a/samples/browser/README.md b/samples/browser/README.md
index 98126fd..e25bfcd 100644
--- a/samples/browser/README.md
+++ b/samples/browser/README.md
@@ -1,14 +1,19 @@
# Browser sample
-This sample shows how to use `cdbToSql` in the browser with `sql.js`.
+This sample shows how to use `cdbToSql` in the browser with `sql.js`. It is also
+the public demo, deployed to GitHub Pages at
+by [`.github/workflows/static.yml`](../../.github/workflows/static.yml), which
+copies this folder verbatim.
## What it does
-- Load a `.cdb` file from an ``
-- Convert it to a SQLite database in the browser
-- Display the detected tables from `DB_STRUCTURE`
+- Load a `.cdb` database from an `` or by drag and drop
+- Convert it to a SQLite database, entirely in the browser
+- Display the detected tables from `DB_STRUCTURE` and preview their first rows
- Download the generated `.sqlite` file
+Nothing is ever uploaded: the conversion runs client-side, in WebAssembly.
+
## Run the sample
Serve this folder with any static HTTP server, then open `/samples/browser/` in your browser.
@@ -17,9 +22,45 @@ Example with VS Code Live Server or any equivalent local server:
1. Start a local server from the repository root
2. Open `http://127.0.0.1:4173/samples/browser/`
-3. Select a `.cdb` file and click **Convert**
+3. Select a `.cdb` database and click **Convert**
+
+Opening `index.html` straight from the filesystem does not work: `app.js` is an
+ES module and `sql-wasm.wasm` is fetched over HTTP, both of which browsers block
+on `file://`.
## Files
- `index.html` defines the UI
- `app.js` initializes `sql.js`, runs `cdbToSql`, and prepares the SQLite download
+- `style.css` maps the PCMStack design tokens onto Pico's CSS variables
+- `assets/` holds the PCMStack favicon, wordmark and Open Graph card
+
+The header inlines `assets/logo.svg` rather than pointing an `` at it: the
+wordmark is built from live ``, and an externally referenced SVG cannot
+reach the page's webfonts, so it would fall back to a system sans. The file is
+kept as the source of truth for that markup.
+- `vendor/` holds the third-party runtime dependencies, served from this origin
+
+## Vendored dependencies
+
+`vendor/` is checked in on purpose. This page hands the user's own database to
+the code it loads, so it must not depend on a third-party CDN staying up or
+serving what it served yesterday.
+
+| File | Source |
+| -------------- | ----------------------------------------- |
+| `pico.min.css` | `@picocss/pico` v2.1.1 |
+| `sql-wasm.js` | `sql.js` v1.14.1, from `node_modules` |
+| `sql-wasm.wasm`| `sql.js` v1.14.1, from `node_modules` |
+
+To refresh the `sql.js` pair after a dependency bump:
+
+```bash
+cp node_modules/sql.js/dist/sql-wasm.{js,wasm} samples/browser/vendor/
+```
+
+`app.js` points `initSqlJs`'s `locateFile` at `./vendor/`, so the `.wasm` is
+resolved next to the page rather than next to the script that loaded it.
+
+The folder is excluded from Biome in [`biome.json`](../../biome.json): these are
+minified upstream artifacts and must not be reformatted.
diff --git a/samples/browser/app.js b/samples/browser/app.js
index f7b5f25..5252a81 100644
--- a/samples/browser/app.js
+++ b/samples/browser/app.js
@@ -41,9 +41,11 @@ async function convertFile() {
const startedAt = performance.now();
try {
+ // sql.js resolves its .wasm relative to this callback, not to the script
+ // URL, so it has to be pointed at the vendored copy explicitly.
const SQL = await initSqlJs({
locateFile: (filename) =>
- `https://cdnjs.cloudflare.com/ajax/libs/sql.js/1.10.2/${filename}`,
+ new URL(`./vendor/${filename}`, import.meta.url).href,
});
const cdbBuffer = await file.arrayBuffer();
const db = cdbToSql(new Uint8Array(cdbBuffer), SQL);
@@ -299,7 +301,7 @@ function copyInstallCommand() {
const command = "npm install cdb-converter";
void navigator.clipboard?.writeText(command);
copyInstallButton.innerHTML =
- '';
+ '';
window.setTimeout(() => {
copyInstallButton.innerHTML =
'';
diff --git a/samples/browser/assets/favicon.svg b/samples/browser/assets/favicon.svg
new file mode 100644
index 0000000..6ad9eb5
--- /dev/null
+++ b/samples/browser/assets/favicon.svg
@@ -0,0 +1,5 @@
+
diff --git a/samples/browser/assets/logo.svg b/samples/browser/assets/logo.svg
new file mode 100644
index 0000000..b4e0f56
--- /dev/null
+++ b/samples/browser/assets/logo.svg
@@ -0,0 +1,6 @@
+
diff --git a/samples/browser/assets/og-image.png b/samples/browser/assets/og-image.png
new file mode 100644
index 0000000..a350ca8
Binary files /dev/null and b/samples/browser/assets/og-image.png differ
diff --git a/samples/browser/assets/og-image.svg b/samples/browser/assets/og-image.svg
new file mode 100644
index 0000000..41136de
--- /dev/null
+++ b/samples/browser/assets/og-image.svg
@@ -0,0 +1,56 @@
+
diff --git a/samples/browser/index.html b/samples/browser/index.html
index aa679a0..b05f8d4 100644
--- a/samples/browser/index.html
+++ b/samples/browser/index.html
@@ -1,20 +1,63 @@
-
+
- cdb-converter
-
+
+ cdb-converter: PCMStack
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+