⚔️ 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 hasMany relationship to BattleLog.
  • BattleLog model: Records every battle — enemy, outcome, XP/gold earned, turns taken. Has a belongsTo relationship back to Player.
  • 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):

Edit rpg-quest

Last updated: 2026-06-04T21:26:43-05:00

← Back to Experiments