Utilities Reference
Introduction
Reepolee ships a handful of small utility modules that don't warrant their own section but are useful across many code paths. This page collects them in one place for reference.
Environment Helpers ($lib/env)
import { require_env, sanitize_env_value, get_storage_mode } from "$lib/env";
require_env(name: string): string
Reads a required environment variable. If the variable is unset, prints a red error message and exits the process immediately. Use it at the point of need - the function call is right next to where the value is used, so it's clear which variable is required and why:
const connection_string = require_env("CONNECTION_STRING");
The exit happens synchronously with process.exit(1), so you'll see the error immediately at startup rather than as a mysterious failure later.
sanitize_env_value(raw: string): string
Strips surrounding quotes and whitespace from an environment variable value. Bun's .env parser preserves literal quote characters, so a .env file with FOO="some value" would include the quotes in the string. This helper removes them:
const clean = sanitize_env_value('"sqlite:app.db"'); // → "sqlite:app.db"
const clean = sanitize_env_value("'sqlite:app.db'"); // → "sqlite:app.db"
require_env() calls this internally, so you don't need to call it explicitly when using require_env().
get_storage_mode(): StorageMode | null
Returns the storage mode from the STORAGE environment variable:
"local"- local filesystem storage"s3"- S3-compatible object storagenull- not set (auto-detect from presence of S3 credentials)
If the env var is set to an unrecognised value, exits with an error message listing the valid options.
const mode = get_storage_mode();
if (mode === "s3") {
// Use S3 client
} else {
// Use local filesystem
}
SQL Timing Wrapper ($lib/timed_sql)
import { timed_query } from "$lib/timed_sql";
timed_query(component, label, fn, extra?)
Wraps any async SQL operation with automatic duration logging. The component and label identify the operation in the logs, while the optional extra parameter can be a static object or a function that derives extra data from the result:
const records = await timed_query(
"partners",
"get_all",
() => db`SELECT * FROM partners ORDER BY id ASC`,
(result) => ({ count: result.length }),
);
await timed_query(
"partners",
"update",
() => db`UPDATE partners SET title = ${title} WHERE id = ${id}`,
{ id }, // logged as extra data
);
The output goes through log_info() in $lib/logger, so it appears in the systemd journal with the [INFO] level and the component/label/duration fields visible.
Local Storage ($lib/local_storage)
import { get_local_storage_dir, delete_local_file } from "$lib/local_storage";
get_local_storage_dir(): string | null
Returns the resolved local storage directory path. The behaviour depends on the STORAGE env var:
STORAGE=local- requiresLOCAL_STORAGE_DIR, returns the resolved absolute pathSTORAGE=s3- returnsnull(local storage not available)- Unset - auto-detects from
LOCAL_STORAGE_DIRif set, returnsnullotherwise
const dir = get_local_storage_dir();
if (dir) {
// e.g. "/home/deploy/app/storage"
}
delete_local_file(bucket: string, key: string): Promise<void>
Deletes a file from the local storage directory at {local_storage_dir}/{bucket}/{key}. Silently skips if local storage is not configured:
await delete_local_file("users", "avatars/uuid.webp");
// Deletes {local_storage_dir}/users/avatars/uuid.webp
This is the local-storage equivalent of delete_from_s3() from $lib/s3. For a uniform interface regardless of storage mode, use delete_from_local() from $lib/s3 instead - it delegates here automatically.
Logger API ($lib/logger)
import { log_info, log_warn, log_error, duration_ms, create_file_logger } from "$lib/logger";
Structured logging functions
log_info("component", "message", { optional: "data" });
log_warn("component", "message", { optional: "data" });
log_error("component", "message", error, { optional: "data" });
Format: [{timestamp}] [{level}] [{component}] {message} {JSON-data}
The component is a short identifier for the logical module (e.g. "s3", "auth", "partners"). The data object is serialised as JSON and appended to the log line.
duration_ms(start: bigint): string
Returns a human-readable duration string from a process.hrtime.bigint() start value:
const start = process.hrtime.bigint();
// ... do work ...
console.log(`Took ${duration_ms(start)}`); // "Took 1.42ms"
create_file_logger(path: string): (data, req) => Promise<void>
Creates a file-based appender that writes structured JSON lines to date-rotated files:
const log_request = create_file_logger("logs/requests.ndjson");
// In a handler
await log_request({ method: req.method, path: url.pathname }, req);
The logger creates a subdirectory named after the file's base name (e.g. logs/requests/), writes to a date-stamped file (e.g. logs/requests/2026-06-03.ndjson), and includes the current user's email from the session in every entry.
Translation Merge Utilities ($lib/translation_merge)
import {
sort_object,
clean_for_translation,
extract_untranslated,
apply_translations,
merge_into_english,
merge_with_missing_prefix,
sync_lang_to_en,
count_leaves,
collect_leaf_paths,
} from "$lib/translation_merge";
These are pure functions for manipulating nested translation JSON objects. They're used internally by the generator's sync_translations and extraction scripts, and can be useful for custom translation tooling:
// Count how many strings need translation
const untranslated = extract_untranslated(en_obj, sl_obj);
const missing_count = count_leaves(untranslated);
// Apply machine-translated results back, preserving already-translated keys
const merged = apply_translations(sl_obj, ai_translated_obj);
// Collect all dot-notation paths for auditing
const paths = collect_leaf_paths(en_obj);
// ["ui.title", "actions.save", "errors.required", ...]
// Sort keys alphabetically (deep)
const sorted = sort_object(messy_obj);
These functions are all synchronous, pure (no filesystem or network calls), and operate only on JSON objects in memory.