/**
* Load a module from the TypeScript build with its `#private` members aliased
* onto a public `__t` bag.
*
* dev/docs/CLJS.md's method for golden vectors is "splice the real modules into
* a harness rather than transcribing their logic into a generator". Most of
* rooms-kit obliges directly — `dist/lib/crypto.js` and friends export
* everything the vectors need. The state machines do not: the reaction fold
* (`ChatClient.#applyEntry`), the log's entry projection (`RoomLog.#emit`,
* `#authorOf`, `#canJoinLocally`) and the whole membership machine
* (`Net.#verified`, `#onPresence`, `#sweep`, …) are ES2022 hard-private, and
* `tsc` emits them as native `#names` — unreachable from outside the class.
*
* So this loader does the smallest possible mechanical edit to the REAL
* compiled module: it injects one class field
*
* __t = { m: (...a) => this.#m(...a), … };
*
* per requested class, leaving every line of logic untouched, and rewrites the
* relative import specifiers to absolute file URLs so the patched copy can live
* outside dist/. Nothing is retyped, so no fixture can encode a misreading of
* the source.
*
* The port has no hard-private members (CLJS has no equivalent; the canon's
* idiom is `_name` string keys, as in peer-kit), so `test/*.test.mjs` reaches
* the same methods on the built CLJS dist directly and replays these vectors
* against them.
*/
import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
import { dirname, join, resolve } from "node:path";
import { pathToFileURL } from "node:url";
const SPEC = /(from\s*|import\s*\(\s*)("|')(\.\.?\/[^"']+)\2/g;
let counter = 0;
/**
* @param {string} file absolute path of a module inside dist/
* @param {string} outDir writable scratch directory
* @param {Record<string,string[]>} classes className -> private member names
*/
export async function loadExposed(file, outDir, classes) {
let src = readFileSync(file, "utf8");
// relative specifiers must survive the move out of dist/
src = src.replace(SPEC, (m, lead, q, spec) => `${lead}${q}${pathToFileURL(resolve(dirname(file), spec)).href}${q}`);
for (const [cls, members] of Object.entries(classes)) {
const re = new RegExp(`(class\\s+${cls}\\b[^{]*\\{)`);
if (!re.test(src)) throw new Error(`expose: class ${cls} not found in ${file}`);
const bag = members.map((m) => `${m}: (...a) => this.#${m}(...a)`).join(", ");
src = src.replace(re, `$1\n __t = { ${bag} };\n`);
}
mkdirSync(outDir, { recursive: true });
const out = join(outDir, `exposed-${counter++}.mjs`);
writeFileSync(out, src);
return import(pathToFileURL(out).href);
}