All files / src/utils translation-miss-queue.js

76.31% Statements 29/38
61.11% Branches 11/18
83.33% Functions 5/6
76.47% Lines 26/34

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90                      25x 25x 25x       25x       25x   4x 3x 3x   1x       24x 24x 24x                             24x     6x 6x 4x 4x 4x       4x         5x 5x                       25x     28x 24x       28x     25x  
/**
 * Translation miss-queue (SHY-0072): append-only JSONL of (text, target)
 * pairs the provider chain could not translate. Deduplicated on append
 * (same text+target queues once, surviving restarts via hydrate-on-boot).
 * Claude drains this via routine PRs that commit translations into the
 * cache SEED (the "fallback to claude" — build-time, $0).
 *
 * Length surfaces in GET /api/system/health as `translationQueueLength`
 * (computed lazily from the file so the reader never races the writer).
 */
 
const fs = require('node:fs');
const path = require('node:path');
const os = require('node:os');
 
// NUL separator: cannot appear in either field, so user-controlled text
// can never collide two distinct (text, target) pairs in the dedupe set.
const SEP = '\u0000';
 
// Under Jest, an un-overridden queue path falls back to a per-process
// tmpdir (same rationale as translation-cache's test default).
let testDefaultDir = null;
function defaultQueuePath() {
  if (process.env.NODE_ENV === 'test') {
    Eif (!testDefaultDir) testDefaultDir = fs.mkdtempSync(path.join(os.tmpdir(), 'tqueue-default-'));
    return path.join(testDefaultDir, 'translation-miss-queue.jsonl');
  }
  return path.join(__dirname, '..', '..', 'data', 'translation-miss-queue.jsonl');
}
 
function createMissQueue({ queuePath = defaultQueuePath() } = {}) {
  const seen = new Set();
  try {
    Iif (fs.existsSync(queuePath)) {
      for (const line of fs.readFileSync(queuePath, 'utf-8').split('\n')) {
        if (!line.trim()) continue;
        try {
          const e = JSON.parse(line);
          seen.add(`${e.text}${SEP}${e.target}`);
        } catch {
          // A corrupt line never blocks queueing; backfill tooling skips it too.
        }
      }
    }
  } catch {
    // Unreadable queue file = start with an empty dedupe set; appends recreate it.
  }
 
  return {
    /** @returns {boolean} true if appended, false if deduplicated away */
    enqueue(text, target, reason) {
      const k = `${text}${SEP}${target}`;
      if (seen.has(k)) return false;
      seen.add(k);
      fs.mkdirSync(path.dirname(queuePath), { recursive: true });
      fs.appendFileSync(
        queuePath,
        `${JSON.stringify({ text, target, ts: new Date().toISOString(), reason })}\n`,
      );
      return true;
    },
 
    /** Lazily counted line length (admin backlog signal for /system/health). */
    length() {
      try {
        Eif (!fs.existsSync(queuePath)) return 0;
        return fs
          .readFileSync(queuePath, 'utf-8')
          .split('\n')
          .filter((l) => l.trim()).length;
      } catch {
        return 0;
      }
    },
  };
}
 
let defaultInstance = null;
/** Lazy singleton bound to env-overridable path (tests reset via resetModules). */
function getDefaultMissQueue() {
  if (!defaultInstance) {
    defaultInstance = createMissQueue({
      queuePath: process.env.TRANSLATION_MISS_QUEUE_PATH || defaultQueuePath(),
    });
  }
  return defaultInstance;
}
 
module.exports = { createMissQueue, getDefaultMissQueue };