V Clojure symboly a kolekce podporují tzv. metadata. Slouží k anotování dat a důležité je, že nejsou součástí hodnoty daného objektu. Občas by se mi to hodilo i ve světě javascriptu.

Cílem je mít možnost definovat na objekt metadata, která lze kdykoliv získat a zároveň která nejsou “enumerable”, tj. nepropadnou do JSON.stringify, Object.entries, ... apod.

Nejde o žádnou vědu - javascript nabízí statickou funkci Object.defineProperty, která při správné konfiguraci dělá přesně to, co potřebuju.

Začnu tím, že vlastnost meta bude definována skrz symbol, takže se k ní není možné jednoduše dostat zvenku.

Důležitá je ta konfigurace, kdy value nepotřebuje další komentrář, writable umožňuje hodnotu metadat měnit, configurable ji naopak dovoluje smazat a enumerable ji dělá “tajnou”.

// meta.js
const metaProp = Symbol.for("meta");

export function withMeta(m, x) {
  Object.defineProperty(x, metaProp, {
    value: m,
    writable: true,
    configurable: true,
    enumerable: false,
  });
  return x;
}

export function meta(x) {
  return x[metaProp];
}

export function alterMeta(x, f) {
  x[metaProp] = f(x[metaProp]);
  return x;
}

export function resetMeta(x) {
  delete x[metaProp];
  return x;
}

Použití je následovné:

const a = withMeta({ version: 1 }, { foo: "bar" });
// { foo: "bar" }

Object.entries(a);
// [["foo", "bar"]]

meta(a);
// { version: 1 }

const incVersion = ({ version, ...rest }) => ({
  version: version + 1,
  ...rest
});
alterMeta(a, incVersion)
// { foo: "bar" }

meta(a);
// { version: 2 }

const b = { ...a };
meta(b);
// undefined