js-dojo

2 settings path spy

GOAL

Wrap a large game-settings tree in a "spy" so that after a consumer function has run you know EXACTLY which dotted settings paths it read (e.g. 'hud.scoreDecimals') — and which whole subtrees it never looked at.

CONCEPT

A recursive `get` trap. When the value behind a property is itself an object, record nothing yet — hand back ANOTHER spy around that object, with the path extended by one segment. Only a read of a leaf (non-object) value records the full dotted path.

HINT

- Grow the path one segment per hop: path ? path + '.' + key : String(key) - Remember that typeof null is also 'object', so rule out null first. - Symbol keys are not settings — forward them and record nothing. - Because `reads` is a Set, hitting the same leaf twice costs no extra entry.

MIRRORS

A game engine's settings tree is ~40 keys deep and the frame loop diffs ALL of it on every scheduled render. Spying on what the HUD renderer actually reads tells you which slice deserves a deep diff — everything else can be reference-compared.

Run

node 02-settings-path-spy.cjs   (fails until you fix the TODO)

Source

/*
 * proto-dojo 05-proxy-reflect / 02-settings-path-spy
 * --------------------------------------------------
 * GOAL: Wrap a large game-settings tree in a "spy" so that after a
 *       consumer function has run you know EXACTLY which dotted
 *       settings paths it read (e.g. 'hud.scoreDecimals') — and which
 *       whole subtrees it never looked at.
 *
 * CONCEPT: A recursive `get` trap. When the value behind a property is
 *          itself an object, record nothing yet — hand back ANOTHER spy
 *          around that object, with the path extended by one segment.
 *          Only a read of a leaf (non-object) value records the full
 *          dotted path.
 *
 * HINT: - Grow the path one segment per hop: path ? path + '.' + key
 *         : String(key)
 *       - Remember that typeof null is also 'object', so rule out
 *         null first.
 *       - Symbol keys are not settings — forward them and record
 *         nothing.
 *       - Because `reads` is a Set, hitting the same leaf twice costs
 *         no extra entry.
 *
 * MIRRORS: A game engine's settings tree is ~40 keys deep and the frame
 *          loop diffs ALL of it on every scheduled render. Spying on
 *          what the HUD renderer actually reads tells you which slice
 *          deserves a deep diff — everything else can be
 *          reference-compared.
 *
 * Run: node 02-settings-path-spy.cjs   (fails until you fix the TODO)
 */
'use strict';
const assert = require('node:assert');

const settings = {
    game: { animation: false, mode: 'arena' },
    hud: { scoreDecimals: 5, showTimer: true },
    defaults: { player: { speed: 0, nameTags: { enabled: false } } },
    audio: { volume: 0.8 },
};

// The consumer being profiled. Leave it untouched — the exercise is to
// find
// out what it reads WITHOUT opening its source.
function renderHud(cfg) {
    const score = (1234.56789).toFixed(cfg.hud.scoreDecimals);
    const animate = cfg.game.animation !== false;
    return { score, animate };
}

/* ------------------------------------------------------------------ */
/* TODO 1: implement spy(obj, reads, path) -> Proxy                     */
/*   get trap:                                                          */
/*     - symbol key            -> just forward, record nothing          */
/*     - value is an object    -> return spy(value, reads, fullPath)    */
/*     - value is a leaf       -> reads.add(fullPath), return value     */
/* ------------------------------------------------------------------ */
function spy(obj, reads, path = '') {
    return obj; // TODO 1: replace with a Proxy whose get trap records leaf paths
}

/* ------------------------- checks --------------------------------- */
const reads = new Set();
const result = renderHud(spy(settings, reads));

assert.deepStrictEqual(result, { score: '1234.56789', animate: false },
    'the spy must be invisible — renderHud must produce the same output');

assert.ok(reads.size > 0,
    'TODO 1: the spy recorded nothing, but renderHud read 2 leaf paths — ' +
    'return a Proxy whose get trap records dotted paths');

assert.deepStrictEqual([...reads].sort(), ['game.animation', 'hud.scoreDecimals'],
    'TODO 1: expected exactly [game.animation, hud.scoreDecimals]. ' +
    'Record ONLY leaf reads (non-objects); for object values return a nested ' +
    "spy with the extended path instead. Got: " + JSON.stringify([...reads].sort()));

assert.ok(![...reads].some((p) => p.startsWith('defaults') || p.startsWith('audio')),
    'untouched subtrees (defaults, audio) must not appear — that absence ' +
    'is the actionable insight');

console.log('OK 02-settings-path-spy — HUD renderer touched:', [...reads].sort());

Solution