⚔️ RPG Quest Log
Turn-based browser RPG — relationships demo with idb-activerecord
This experiment is a really basic mini RPG built on top of idb-activerecord. The focus is on demonstrating
model relationships: a Player model that hasManyBattleLog records, and each BattleLog that belongsTo a
Player. All game state — HP, XP, level, gold, and every battle — persists in IndexedDB and survives page reloads.
How It Works
- Player model: Stores character stats (HP, XP, level, gold). Has a
hasManyrelationship toBattleLog. - BattleLog model: Records every battle — enemy, outcome, XP/gold earned, turns taken. Has a
belongsTorelationship back toPlayer. - Relationships: After a battle,
player.hasMany('battleLogs')returns the full combat history.log.belongsTo('player')retrieves the owner. - Persistence: Create a hero, fight enemies, close the tab — everything is still there when you return.
Live Demo
No hero found — create one to start!
Name Your Hero
Your legend begins here, warrior.
The Relationship Code
The core of this demo is the hasMany / belongsTo declaration on the model classes. Once defined, traversing the relationship is a single async call — no manual foreign-key
joins required.
class Player extends ActiveRecord {
static tableName = 'players';
static columns = {
name: { type: 'string', nullable: false },
hp: { type: 'integer', default: 100 },
maxHp: { type: 'integer', default: 100 },
xp: { type: 'integer', default: 0 },
level: { type: 'integer', default: 1 },
gold: { type: 'integer', default: 0 },
};
// hasMany relationship — one player owns many battle logs
static hasMany = { battleLogs: BattleLog };
}
class BattleLog extends ActiveRecord {
static tableName = 'battle_logs';
static columns = {
playersId: { type: 'integer', nullable: false },
enemy: { type: 'string', nullable: false },
outcome: { type: 'string', nullable: false }, // 'victory' | 'defeat'
xpGained: { type: 'integer', default: 0 },
goldGained:{ type: 'integer', default: 0 },
turns: { type: 'integer', default: 0 },
createdAt: { type: 'integer', default: 0 },
};
}
// Fetch a player and all their battle logs via the relationship
const player = await Player.find(1);
const logs = await player.hasMany('battleLogs');
// Fetch a log and its owner via the inverse relationship
const log = await BattleLog.find(5);
const owner = await log.belongsTo('player');Learn More
See the full idb-activerecord demo for CRUD, query chaining, and the CDN usage pattern. The library source and full API docs live on GitHub →
Try It Yourself
Here's the slightly modified version of this experiment, you can fork it and try it yourself (or copy and paste the code and run it locally):
Last updated: 2026-06-04T21:26:43-05:00