app/lib/search/ask-twins.mjs
56 lines and 6 functions in DrDustinEdwards/dustinedwards-info, at commit 1590ba4 on 2026-10-24. This file on GitHub.
Functions
| Function | Lines | Complexity | Covered | CRAP | Mutation |
|---|---|---|---|---|---|
| withRetry | 14 to 29 | 7 | 100% | WatchCRAP 7 | Watch68.8% |
| <anonymous:17> | 17 to 17 | 1 | 100% | OkCRAP 1 | Critical33.3% |
| <anonymous:17> | 17 to 17 | 1 | 100% | OkCRAP 1 | No data: no mutants in this code |
| uploadTwins | 43 to 60 | 5 | 100% | OkCRAP 5 | Ok92.3% |
| <anonymous:51> | 51 to 51 | 1 | 100% | OkCRAP 1 | Ok100% |
| <anonymous:52> | 52 to 52 | 1 | 100% | OkCRAP 1 | Ok100% |
Imports
It imports no other module of this repo. Imported by app/lib/search/ask.server.ts.
Source
// A failed key still joins keys: the caller prunes every key it is not given, so leaving it out would
// turn a transient into a deletion. Failures are returned so the sync report can refuse to converge.
export const TWIN_ATTEMPTS = 3;
const TWIN_BACKOFF_MS = 500;
/**
* @template T
* @param {() => Promise<T>} fn
* @param {{ attempts?: number, backoffMs?: number, sleep?: (ms: number) => Promise<void> }} [opts]
* @returns {Promise<T>}
*/
export async function withRetry(fn, opts = {}) {
const attempts = opts.attempts ?? TWIN_ATTEMPTS;
const backoffMs = opts.backoffMs ?? TWIN_BACKOFF_MS;
const sleep = opts.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
/** @type {unknown} */
let last;
for (let attempt = 1; attempt <= attempts; attempt += 1) {
try {
return await fn();
} catch (error) {
last = error;
if (attempt < attempts) await sleep(backoffMs * 2 ** (attempt - 1));
}
}
throw last;
}
/**
* @param {Array<{ key: string, path: string }>} twins the item key and the asset path of each twin
* @param {{
* fetchText: (path: string) => Promise<string>,
* upload: (key: string, body: string) => Promise<unknown>,
* log?: (...args: unknown[]) => void,
* attempts?: number,
* backoffMs?: number,
* sleep?: (ms: number) => Promise<void>,
* }} io `fetchText` throws on a missing or unreadable twin
* @returns {Promise<{ keys: string[], failed: Array<{ key: string, error: string }> }>}
*/
export async function uploadTwins(twins, io) {
const retry = { attempts: io.attempts, backoffMs: io.backoffMs, sleep: io.sleep };
const log = io.log ?? console.error;
const keys = [];
const failed = [];
for (const { key, path } of twins) {
keys.push(key);
try {
const body = await withRetry(() => io.fetchText(path), retry);
await withRetry(() => io.upload(key, body), retry);
} catch (error) {
const message = error instanceof Error ? `${error.name}: ${error.message}` : String(error);
log("ask twin upload failed after retries", key, message);
failed.push({ key, error: message });
}
}
return { keys, failed };
}