Atonicode

Atonicode — questionnaire

Thanks for applying. Eight short questions, about 15 minutes. Plain answers beat polished ones — we're looking for how you actually work, not how you interview. Word limits are hard. What happens next: a paid build task, then a final call.

0 / 150 words
0 / 100 words
0 / 100 words
0 / 80 words
// config-loader.ts
interface LoaderOptions { retries?: number; backoffMs?: number; logger?: Logger; }
interface Logger { info(msg: string): void; warn(msg: string): void; error(msg: string): void; }

class ConfigError extends Error { constructor(msg: string, public readonly cause?: unknown) { super(msg); } }
class ConfigParseError extends ConfigError {}
class ConfigNotFoundError extends ConfigError {}

const defaultLogger: Logger = { info: console.log, warn: console.warn, error: console.error };

export async function loadConfig<T = unknown>(path: string, opts: LoaderOptions = {}): Promise<T> {
  const { retries = 3, backoffMs = 200, logger = defaultLogger } = opts;
  for (let attempt = 1; attempt <= retries; attempt++) {
    try {
      logger.info(`loading config from ${path} (attempt ${attempt}/${retries})`);
      const raw = await fs.promises.readFile(path, "utf8");
      try { return JSON.parse(raw) as T; }
      catch (e) { throw new ConfigParseError(`invalid JSON in ${path}`, e); }
    } catch (e) {
      if (e instanceof ConfigParseError) throw e;
      if ((e as NodeJS.ErrnoException).code === "ENOENT") throw new ConfigNotFoundError(`no config at ${path}`, e);
      logger.warn(`attempt ${attempt} failed: ${(e as Error).message}`);
      if (attempt === retries) throw new ConfigError(`giving up after ${retries} attempts`, e);
      await new Promise(r => setTimeout(r, backoffMs * 2 ** (attempt - 1)));
    }
  }
  throw new ConfigError("unreachable");
}
0 / 100 words