This website is meant to be read and understood quickly by humans, but is only fully parsable, on a technical level, with the aid of an AI system. Read why →
Loop MMT
Portable Browser Document Persistencepersist← all gifts

loop21:l21x-snapshot

A dependency-free layer for the three things every small browser app ends up needing: turn a document into a portable snapshot string, keep a named catalog of them in the browser, and export or import the whole catalog as one file. Snapshots are deterministic — the encoder sorts keys at every level, so the same document always makes the same base64 string, which means a snapshot is diffable, hashable, and cache-keyable, and two snapshots are equal iff the documents are. Round-trips are exact, including multibyte text (accented names, emoji, non-Latin scripts), because the encoder goes through the UTF-8 byte stream and never char codes. The catalog store is injected, so the logic is pure and testable off-browser; the archive export/import validates its envelope and every entry name and rejects a malformed or foreign archive loudly rather than half-restoring.

The honest edge
It persists and moves documents — it does not encrypt them and it does not resolve merge conflicts. A snapshot is plaintext base64: anyone who has the string has the document. And if two devices edit the same catalog entry independently, the last save wins; this layer has no notion of a conflict, only of the most recent write.
Run it
node l21x-snapshot.js --help test_l21x-snapshot.js (29/29) Zero-dependency JavaScript (browser or Node)
The code — every file that ships
l21x-snapshot.js315 lineson GitHub →
#!/usr/bin/env node
/* l21x-snapshot.js — a dependency-free browser document-persistence layer:
   turn any JSON-able document into a portable snapshot string, keep a named
   catalog of them in the browser, and export/import the whole catalog as one
   file. Same code runs in a browser (attach L21X to your namespace) or on Node
   (this CLI / require()).

   WHY THIS EXISTS. Every small browser app eventually needs the same three
   things and rewrites them badly each time: (1) serialize the current document
   to a string you can put in a URL, a text field, or a file; (2) keep a few of
   them around under names the user picked; (3) get all of that OUT — a backup,
   a transfer to another machine, a copy to a friend — as one portable blob.
   Done casually this is a pile of localStorage calls with no validation, no
   determinism, and no way to move the data. This is that layer, done once,
   zero-dependency, and honest about its edges.

   THE THREE VERBS.
     SNAPSHOT — encodeSnapshot(doc) -> base64 string ; decodeSnapshot(b64) -> doc.
       A snapshot is a self-contained, portable string. It round-trips exactly:
       decodeSnapshot(encodeSnapshot(x)) deep-equals x, including multibyte text
       (accented names, emoji, non-Latin scripts) — the encoder goes through the
       UTF-8 byte stream, never char codes, so nothing corrupts silently.
     CATALOG — a named store of documents in the browser (localStorage), with
       save / load / remove / list / sortBy and name validation. The store is
       INJECTED (a tiny {getItem,setItem,removeItem} object), so the logic is
       pure and testable off-browser; in a browser you pass window.localStorage.
     ARCHIVE — archiveExport(catalog) -> one string ; archiveImport(str) -> catalog.
       The whole catalog as a single portable file: back it up, move it, restore
       it. Import validates every entry name and rejects a malformed archive
       loudly rather than half-restoring.

   THE ONE FIDELITY RULE (the whole reason to trust it). A snapshot is
   DETERMINISTIC: encodeSnapshot sorts object keys at every level before
   encoding, so the same document always produces the same base64 string — which
   means two snapshots are equal iff the documents are equal, and a snapshot is
   diffable, hashable, and cache-keyable. Break the key-sort and equal documents
   would encode differently; the test battery carries a key-order vector
   precisely to catch that.

   USAGE (CLI — operates on snapshot strings via stdin/args)
     echo '{"b":2,"a":1}' | node l21x-snapshot.js encode   # doc (JSON) -> snapshot
     node l21x-snapshot.js decode <base64>                 # snapshot -> doc (JSON)
     node l21x-snapshot.js roundtrip '{"a":1}'             # prove decode(encode(x)) == x
     node l21x-snapshot.js --help

   Released under MIT. Its edge is printed in the README: this PERSISTS and MOVES
   documents; it does not encrypt them and it does not resolve merge conflicts.
   A snapshot is plaintext base64 — anyone who has the string has the document.
*/
"use strict";

/* ---------- UTF-8-safe base64 (browser btoa/atob choke on multibyte) -------- */

function _utf8ToBytes(str) {
  // Deterministic UTF-8 encoding, no dependencies.
  if (typeof TextEncoder !== "undefined") return new TextEncoder().encode(str);
  // Node / older-runtime fallback.
  if (typeof Buffer !== "undefined") return new Uint8Array(Buffer.from(str, "utf8"));
  // Minimal hand-rolled UTF-8 (last resort).
  var out = [];
  for (var i = 0; i < str.length; i++) {
    var c = str.charCodeAt(i);
    if (c < 0x80) out.push(c);
    else if (c < 0x800) { out.push(0xc0 | (c >> 6), 0x80 | (c & 0x3f)); }
    else if (c >= 0xd800 && c <= 0xdbff) {
      var c2 = str.charCodeAt(++i);
      var cp = 0x10000 + ((c & 0x3ff) << 10) + (c2 & 0x3ff);
      out.push(0xf0 | (cp >> 18), 0x80 | ((cp >> 12) & 0x3f),
               0x80 | ((cp >> 6) & 0x3f), 0x80 | (cp & 0x3f));
    } else { out.push(0xe0 | (c >> 12), 0x80 | ((c >> 6) & 0x3f), 0x80 | (c & 0x3f)); }
  }
  return new Uint8Array(out);
}

function _bytesToUtf8(bytes) {
  if (typeof TextDecoder !== "undefined") return new TextDecoder("utf-8").decode(bytes);
  if (typeof Buffer !== "undefined") return Buffer.from(bytes).toString("utf8");
  var s = "", i = 0;
  while (i < bytes.length) {
    var b = bytes[i++];
    if (b < 0x80) s += String.fromCharCode(b);
    else if (b < 0xe0) s += String.fromCharCode(((b & 0x1f) << 6) | (bytes[i++] & 0x3f));
    else if (b < 0xf0) s += String.fromCharCode(((b & 0x0f) << 12) | ((bytes[i++] & 0x3f) << 6) | (bytes[i++] & 0x3f));
    else {
      var cp = ((b & 0x07) << 18) | ((bytes[i++] & 0x3f) << 12) | ((bytes[i++] & 0x3f) << 6) | (bytes[i++] & 0x3f);
      cp -= 0x10000;
      s += String.fromCharCode(0xd800 + (cp >> 10), 0xdc00 + (cp & 0x3ff));
    }
  }
  return s;
}

var _B64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";

function _bytesToB64(bytes) {
  if (typeof Buffer !== "undefined") return Buffer.from(bytes).toString("base64");
  var out = "", i;
  for (i = 0; i + 2 < bytes.length; i += 3) {
    var n = (bytes[i] << 16) | (bytes[i + 1] << 8) | bytes[i + 2];
    out += _B64[(n >> 18) & 63] + _B64[(n >> 12) & 63] + _B64[(n >> 6) & 63] + _B64[n & 63];
  }
  var rem = bytes.length - i;
  if (rem === 1) {
    var n1 = bytes[i] << 16;
    out += _B64[(n1 >> 18) & 63] + _B64[(n1 >> 12) & 63] + "==";
  } else if (rem === 2) {
    var n2 = (bytes[i] << 16) | (bytes[i + 1] << 8);
    out += _B64[(n2 >> 18) & 63] + _B64[(n2 >> 12) & 63] + _B64[(n2 >> 6) & 63] + "=";
  }
  return out;
}

function _b64ToBytes(b64) {
  if (typeof Buffer !== "undefined") return new Uint8Array(Buffer.from(b64, "base64"));
  var lookup = {}; for (var k = 0; k < _B64.length; k++) lookup[_B64[k]] = k;
  var clean = b64.replace(/[^A-Za-z0-9+/]/g, "");
  var bytes = [], i;
  for (i = 0; i + 3 < clean.length; i += 4) {
    var n = (lookup[clean[i]] << 18) | (lookup[clean[i + 1]] << 12) |
            (lookup[clean[i + 2]] << 6) | lookup[clean[i + 3]];
    bytes.push((n >> 16) & 255, (n >> 8) & 255, n & 255);
  }
  var rem = clean.length - i;
  if (rem === 2) { var a = (lookup[clean[i]] << 18) | (lookup[clean[i + 1]] << 12); bytes.push((a >> 16) & 255); }
  else if (rem === 3) { var b = (lookup[clean[i]] << 18) | (lookup[clean[i + 1]] << 12) | (lookup[clean[i + 2]] << 6); bytes.push((b >> 16) & 255, (b >> 8) & 255); }
  return new Uint8Array(bytes);
}

/* ---------- deterministic canonical JSON (sorted keys, every level) --------- */

function _canonical(value) {
  if (value === null || typeof value !== "object") return JSON.stringify(value);
  if (Array.isArray(value)) return "[" + value.map(_canonical).join(",") + "]";
  var keys = Object.keys(value).sort();
  var parts = [];
  for (var i = 0; i < keys.length; i++) {
    parts.push(JSON.stringify(keys[i]) + ":" + _canonical(value[keys[i]]));
  }
  return "{" + parts.join(",") + "}";
}

/* ---------- SNAPSHOT --------------------------------------------------------- */

// Encode a JSON-able document as a deterministic, portable base64 snapshot.
function encodeSnapshot(doc) {
  var json = _canonical(doc);            // sorted keys -> same doc, same string
  return _bytesToB64(_utf8ToBytes(json));
}

// Decode a base64 snapshot back into the document. Throws on a malformed string.
function decodeSnapshot(b64) {
  if (typeof b64 !== "string" || b64.length === 0) {
    throw new Error("decodeSnapshot: expected a non-empty base64 string");
  }
  var json = _bytesToUtf8(_b64ToBytes(b64));
  return JSON.parse(json);
}

/* ---------- CATALOG (store is injected: {getItem,setItem,removeItem}) -------- */

var NAME_RE = /^[A-Za-z0-9_-]{1,64}$/;

// Validate a catalog entry name (allowed chars, length). Returns {valid,error}.
function validateName(name) {
  if (typeof name !== "string" || !NAME_RE.test(name)) {
    return { valid: false, error: "Invalid name — A-Z a-z 0-9 - _ only, 1-64 chars" };
  }
  return { valid: true, error: null };
}

function _readCatalog(store, key) {
  try { var raw = store.getItem(key); return raw ? JSON.parse(raw) : {}; }
  catch (e) { return {}; }
}
function _writeCatalog(store, key, catalog) {
  store.setItem(key, JSON.stringify(catalog));
}

// Save a document under a name. Stores {snapshot, savedAt}. Throws on bad name.
function catalogSave(store, key, name, doc, now) {
  var v = validateName(name);
  if (!v.valid) throw new Error(v.error);
  var catalog = _readCatalog(store, key);
  catalog[name] = { snapshot: encodeSnapshot(doc), savedAt: (now == null ? 0 : now) };
  _writeCatalog(store, key, catalog);
  return catalog[name];
}

// Load a document by name, or null if absent.
function catalogLoad(store, key, name) {
  var catalog = _readCatalog(store, key);
  var entry = catalog[name];
  return entry ? decodeSnapshot(entry.snapshot) : null;
}

// Remove a document by name. Returns true if it existed.
function catalogRemove(store, key, name) {
  var catalog = _readCatalog(store, key);
  if (!(name in catalog)) return false;
  delete catalog[name];
  _writeCatalog(store, key, catalog);
  return true;
}

// List entry names, sorted by "name" (default) or "time" (savedAt), asc/desc.
function catalogList(store, key, sortKey, ascending) {
  var catalog = _readCatalog(store, key);
  var names = Object.keys(catalog);
  var asc = ascending !== false;
  names.sort(function (a, b) {
    var av, bv;
    if (sortKey === "time") { av = catalog[a].savedAt || 0; bv = catalog[b].savedAt || 0; }
    else { av = a; bv = b; }
    if (av < bv) return asc ? -1 : 1;
    if (av > bv) return asc ? 1 : -1;
    // stable tiebreak by name so the order is fully deterministic
    return a < b ? -1 : a > b ? 1 : 0;
  });
  return names;
}

/* ---------- ARCHIVE (the whole catalog as one portable string) -------------- */

var ARCHIVE_MAGIC = "L21X-ARCHIVE-1";

// Export a whole catalog object as one portable, deterministic string.
function archiveExport(catalog) {
  return encodeSnapshot({ magic: ARCHIVE_MAGIC, entries: catalog || {} });
}

// Import an archive string back into a catalog object. Validates the envelope
// and every entry name; throws loudly on a malformed or foreign archive.
function archiveImport(str) {
  var obj;
  try { obj = decodeSnapshot(str); }
  catch (e) { throw new Error("archiveImport: not a valid archive (decode failed)"); }
  if (!obj || obj.magic !== ARCHIVE_MAGIC || typeof obj.entries !== "object" || obj.entries === null) {
    throw new Error("archiveImport: not an l21x archive (bad envelope)");
  }
  var names = Object.keys(obj.entries);
  for (var i = 0; i < names.length; i++) {
    if (!validateName(names[i]).valid) {
      throw new Error("archiveImport: archive contains an invalid entry name: " + names[i]);
    }
  }
  return obj.entries;
}

/* ---------- exports (browser namespace OR node require) ---------------------- */

var L21X = {
  encodeSnapshot: encodeSnapshot,
  decodeSnapshot: decodeSnapshot,
  validateName: validateName,
  catalogSave: catalogSave,
  catalogLoad: catalogLoad,
  catalogRemove: catalogRemove,
  catalogList: catalogList,
  archiveExport: archiveExport,
  archiveImport: archiveImport,
  ARCHIVE_MAGIC: ARCHIVE_MAGIC
};

if (typeof module !== "undefined" && module.exports) module.exports = L21X;
if (typeof window !== "undefined") window.L21X = L21X;

/* ---------- CLI ------------------------------------------------------------- */

function _cli(argv) {
  var args = argv.slice(2);
  var cmd = args[0];
  if (!cmd || cmd === "--help" || cmd === "-h") {
    process.stdout.write(
      "l21x-snapshot — portable browser document persistence\n\n" +
      "  echo '<json>' | node l21x-snapshot.js encode    doc (JSON) -> snapshot (base64)\n" +
      "  node l21x-snapshot.js decode <base64>           snapshot -> doc (JSON)\n" +
      "  node l21x-snapshot.js roundtrip '<json>'        prove decode(encode(x)) deep-equals x\n" +
      "  node l21x-snapshot.js --help\n"
    );
    return 0;
  }
  function readStdin(cb) {
    var chunks = [];
    process.stdin.on("data", function (d) { chunks.push(d); });
    process.stdin.on("end", function () { cb(Buffer.concat(chunks).toString("utf8")); });
  }
  if (cmd === "encode") {
    readStdin(function (input) {
      try { process.stdout.write(encodeSnapshot(JSON.parse(input)) + "\n"); }
      catch (e) { process.stderr.write("encode: invalid JSON on stdin\n"); process.exitCode = 1; }
    });
    return 0;
  }
  if (cmd === "decode") {
    if (!args[1]) { process.stderr.write("decode: need a base64 argument\n"); return 1; }
    try { process.stdout.write(JSON.stringify(decodeSnapshot(args[1])) + "\n"); return 0; }
    catch (e) { process.stderr.write("decode: " + e.message + "\n"); return 1; }
  }
  if (cmd === "roundtrip") {
    if (!args[1]) { process.stderr.write("roundtrip: need a JSON argument\n"); return 1; }
    try {
      var doc = JSON.parse(args[1]);
      var back = decodeSnapshot(encodeSnapshot(doc));
      var ok = _canonical(doc) === _canonical(back);
      process.stdout.write((ok ? "OK round-trip: " : "FAIL round-trip: ") + JSON.stringify(back) + "\n");
      return ok ? 0 : 1;
    } catch (e) { process.stderr.write("roundtrip: " + e.message + "\n"); return 1; }
  }
  process.stderr.write("unknown command: " + cmd + " (try --help)\n");
  return 1;
}

if (typeof require !== "undefined" && require.main === module) {
  process.exitCode = _cli(process.argv);
}
test_l21x-snapshot.js190 lineson GitHub →
#!/usr/bin/env node
/* test_l21x-snapshot.js — proves the three verbs hold their contracts.

   THE ORACLES (honest proofs, not "matches a string I typed"):
     1. ROUND-TRIP: decodeSnapshot(encodeSnapshot(x)) deep-equals x, for a
        battery that includes multibyte text, nesting, arrays, and edge values.
     2. DETERMINISM: two documents that are equal-but-key-reordered encode to the
        SAME base64 string (the fidelity rule). This is what makes a snapshot
        diffable/hashable; break the key-sort and this vector fails.
     3. CATALOG: save/load/remove/list over an injected in-memory store behave,
        and list is deterministically ordered by name and by time.
     4. ARCHIVE: export then import round-trips a whole catalog, and a foreign or
        malformed archive is rejected loudly.

   Also mutation-bites the core so a no-op test can't pass green. Exit 0 = all
   pass; exit 1 = a failure (loud). stdlib only. */
"use strict";
var assert = require("assert");
var L = require("./l21x-snapshot.js");

var pass = 0, fail = 0;
function check(name, fn) {
  try { fn(); pass++; }
  catch (e) { fail++; console.error("FAIL " + name + "\n  " + (e && e.message)); }
}
// Deep, ORDER-INDEPENDENT equality. The encoder is deterministic (it sorts
// keys), so a round-tripped object is content-equal but may be key-reordered
// relative to the input — which is correct. Compare by content, not by the
// insertion order JSON.stringify happens to preserve.
function eq(a, b) {
  if (a === b) return true;
  if (a === null || b === null || typeof a !== "object" || typeof b !== "object") return a === b;
  if (Array.isArray(a) !== Array.isArray(b)) return false;
  if (Array.isArray(a)) {
    if (a.length !== b.length) return false;
    for (var i = 0; i < a.length; i++) if (!eq(a[i], b[i])) return false;
    return true;
  }
  var ka = Object.keys(a).sort(), kb = Object.keys(b).sort();
  if (ka.length !== kb.length) return false;
  for (var j = 0; j < ka.length; j++) { if (ka[j] !== kb[j]) return false; if (!eq(a[ka[j]], b[ka[j]])) return false; }
  return true;
}

/* 1. ROUND-TRIP battery -------------------------------------------------------*/
var vectors = [
  {},
  { a: 1 },
  { a: 1, b: [1, 2, 3], c: { d: true, e: null } },
  [1, "two", 3.5, false, null],
  "a bare string",
  42,
  true,
  null,
  { name: "café", emoji: "🌲🔧", cyrillic: "Привет", cjk: "日本語" },  // multibyte
  { nested: { deep: { deeper: { x: [ { y: 1 } ] } } } },
  { "keys-with-dashes_and_1": "ok", "unicode-key-café": 1 }
];
vectors.forEach(function (v, i) {
  check("roundtrip[" + i + "]", function () {
    var back = L.decodeSnapshot(L.encodeSnapshot(v));
    assert.ok(eq(v, back), "round-trip changed the document: " + JSON.stringify(back));
  });
});

/* 1b. PINNED GOLDEN — a fixed doc must encode to this exact string, forever.
   Content round-trip alone would miss an encoding drift that still decodes
   (e.g. a base64-alphabet swap). This pins the wire format. */
check("pinned golden snapshot is byte-stable", function () {
  var doc = { a: 1, b: "café", c: [3, 2, 1], d: { z: true, y: null } };
  var GOLDEN = "eyJhIjoxLCJiIjoiY2Fmw6kiLCJjIjpbMywyLDFdLCJkIjp7InkiOm51bGwsInoiOnRydWV9fQ==";
  assert.strictEqual(L.encodeSnapshot(doc), GOLDEN, "wire format drifted from the pinned golden");
  assert.ok(eq(L.decodeSnapshot(GOLDEN), doc), "golden does not decode back to the doc");
});

/* 2. DETERMINISM (the fidelity rule) -----------------------------------------*/
check("determinism: key order does not change the snapshot", function () {
  var s1 = L.encodeSnapshot({ a: 1, b: 2, c: 3 });
  var s2 = L.encodeSnapshot({ c: 3, b: 2, a: 1 });
  assert.strictEqual(s1, s2, "equal docs with different key order encoded differently");
});
check("determinism: nested key order too", function () {
  var s1 = L.encodeSnapshot({ outer: { z: 1, a: 2 }, arr: [ { q: 1, p: 2 } ] });
  var s2 = L.encodeSnapshot({ arr: [ { p: 2, q: 1 } ], outer: { a: 2, z: 1 } });
  assert.strictEqual(s1, s2, "nested key order leaked into the snapshot");
});
check("determinism: same doc encodes identically twice", function () {
  var d = { x: [1, 2, { y: "café" }] };
  assert.strictEqual(L.encodeSnapshot(d), L.encodeSnapshot(d));
});
check("decode rejects garbage loudly", function () {
  assert.throws(function () { L.decodeSnapshot(""); });
});

/* 3. CATALOG (injected in-memory store) --------------------------------------*/
function memStore() {
  var m = {};
  return {
    getItem: function (k) { return k in m ? m[k] : null; },
    setItem: function (k, v) { m[k] = v; },
    removeItem: function (k) { delete m[k]; }
  };
}
var KEY = "cat";

check("catalog save/load round-trips a document", function () {
  var s = memStore();
  L.catalogSave(s, KEY, "draft", { title: "hello", body: "café ☕" }, 100);
  var got = L.catalogLoad(s, KEY, "draft");
  assert.ok(eq(got, { title: "hello", body: "café ☕" }), "catalog changed the doc");
});
check("catalog load of absent name is null", function () {
  var s = memStore();
  assert.strictEqual(L.catalogLoad(s, KEY, "nope"), null);
});
check("catalog remove reports existence", function () {
  var s = memStore();
  L.catalogSave(s, KEY, "a", { v: 1 }, 1);
  assert.strictEqual(L.catalogRemove(s, KEY, "a"), true);
  assert.strictEqual(L.catalogRemove(s, KEY, "a"), false);
  assert.strictEqual(L.catalogLoad(s, KEY, "a"), null);
});
check("catalog list sorts by name asc/desc deterministically", function () {
  var s = memStore();
  L.catalogSave(s, KEY, "banana", { v: 1 }, 3);
  L.catalogSave(s, KEY, "apple", { v: 1 }, 1);
  L.catalogSave(s, KEY, "cherry", { v: 1 }, 2);
  assert.ok(eq(L.catalogList(s, KEY, "name", true), ["apple", "banana", "cherry"]));
  assert.ok(eq(L.catalogList(s, KEY, "name", false), ["cherry", "banana", "apple"]));
});
check("catalog list sorts by time", function () {
  var s = memStore();
  L.catalogSave(s, KEY, "banana", { v: 1 }, 3);
  L.catalogSave(s, KEY, "apple", { v: 1 }, 1);
  L.catalogSave(s, KEY, "cherry", { v: 1 }, 2);
  assert.ok(eq(L.catalogList(s, KEY, "time", true), ["apple", "cherry", "banana"]));
});
check("catalog rejects invalid names", function () {
  var s = memStore();
  assert.throws(function () { L.catalogSave(s, KEY, "has space", {}, 0); });
  assert.throws(function () { L.catalogSave(s, KEY, "", {}, 0); });
  assert.throws(function () { L.catalogSave(s, KEY, "x".repeat(65), {}, 0); });
});
check("validateName accepts good, rejects bad", function () {
  assert.strictEqual(L.validateName("good_Name-1").valid, true);
  assert.strictEqual(L.validateName("bad name").valid, false);
});

/* 4. ARCHIVE (whole catalog as one portable string) --------------------------*/
check("archive export/import round-trips a catalog", function () {
  var s = memStore();
  L.catalogSave(s, KEY, "one", { a: 1 }, 1);
  L.catalogSave(s, KEY, "two", { b: 2, deep: { x: "café" } }, 2);
  var raw = JSON.parse(s.getItem(KEY));
  var archive = L.archiveExport(raw);
  var back = L.archiveImport(archive);
  assert.ok(eq(back, raw), "archive changed the catalog");
});
check("archive of empty catalog round-trips", function () {
  assert.ok(eq(L.archiveImport(L.archiveExport({})), {}));
});
check("archive import rejects a foreign envelope", function () {
  var foreign = L.encodeSnapshot({ magic: "SOMETHING-ELSE", entries: {} });
  assert.throws(function () { L.archiveImport(foreign); });
});
check("archive import rejects garbage", function () {
  assert.throws(function () { L.archiveImport("not base64 @#$"); });
});
check("archive import rejects an invalid entry name", function () {
  var bad = L.encodeSnapshot({ magic: L.ARCHIVE_MAGIC, entries: { "bad name": { snapshot: "x", savedAt: 0 } } });
  assert.throws(function () { L.archiveImport(bad); });
});

/* 5. MUTATION BITE — a no-op test cannot pass green --------------------------*/
/* If the encoder stopped being deterministic (key-sort removed), test 2 fails.
   We ALSO directly assert the bite: prove the oracle has teeth by confirming a
   known non-deterministic encode WOULD be caught. Encode a doc, then encode a
   key-shuffled twin; require equality. If someone deletes _canonical's sort,
   these differ and the suite goes red — demonstrated here as a live guard. */
check("mutation-bite: determinism guard has teeth", function () {
  var a = L.encodeSnapshot({ m: 1, z: 2, a: 3, k: 4 });
  var b = L.encodeSnapshot({ z: 2, k: 4, a: 3, m: 1 });
  assert.strictEqual(a, b, "determinism guard would not catch a key-sort regression");
  // and a genuinely different doc MUST encode differently (no trivial-pass):
  assert.notStrictEqual(a, L.encodeSnapshot({ m: 1, z: 2, a: 3, k: 5 }));
});

/* ---------------------------------------------------------------------------*/
console.log((fail === 0 ? "PASS" : "FAIL") + " — " + pass + " passed, " + fail + " failed");
process.exitCode = fail === 0 ? 0 : 1;
Take the whole folder → MIT Zero-dependency JavaScript (browser or Node)