Skip to content

Repository files navigation

@resolvx/core

Composable TypeScript primitives for production Node.js.

No magic. No framework opinions. Seven focused modules you can import individually or together. Tree-shakeable, typed end-to-end, zero mandatory dependencies.

npm install @resolvx/core

chalk is an optional peer dependency for coloured log output. Install it if you want colour, skip it if you don't — the logger works either way.


Modules

result — Explicit error handling

Stop writing try/catch everywhere. Result<T, E> makes failure visible in function signatures.

import { capture, captureAsync, ok, err, combine, unwrapOr } from "@resolvx/core/result";

// Wrap a throwing function
const parsed = capture(() => JSON.parse(raw));
if (!parsed.ok) {
  console.error("Bad JSON:", parsed.error);
  return;
}
console.log(parsed.value);

// Async version
const result = await captureAsync(() => fetch(url).then(r => r.json()));

// Combine an array of Results — returns first Err or Ok([...values])
const all = combine([parseA, parseB, parseC]);

// Default on failure
const name = unwrapOr(parseName(input), "anonymous");

API: ok err capture captureAsync map mapErr andThen tap tapErr unwrapOr unwrapOrElse unwrap combine partition fromNullable fromThrowable


async — Async control flow

import { retry, pool, memoize, debounce, throttle, once, withTimeout, sleep } from "@resolvx/core/async";

// Retry with exponential backoff + jitter
const data = await retry(() => fetchFromApi(url), {
  attempts:       4,
  baseDelayMs:    300,
  isNonRetryable: e => e instanceof HttpError && e.status === 404,
  onFailedAttempt: (err, attempt, nextDelay) =>
    console.warn(`Attempt ${attempt} failed, retrying in ${nextDelay}ms`),
});

// Bounded concurrency — 5 requests at a time, output order preserved
const pages = await pool(urls.map(url => () => fetch(url).then(r => r.json())), 5);

// Memoize with TTL + in-flight deduplication
// Two concurrent calls with the same args share one execution
const getUser = memoize(fetchUser, { ttlMs: 60_000 });

// Timeout
const result = await withTimeout(() => heavyComputation(), 5000, "heavyComputation");

API: sleep withTimeout retry pool memoize debounce throttle once


cache — In-memory caching

Three implementations, consistent interface.

import { TtlCache, LruCache, DedupeCache } from "@resolvx/core/cache";

// Time-to-live cache — entries expire after ttlMs
const prices = new TtlCache<string, number>(30_000);
const price = await prices.getOrSet("ETH", () => fetchPrice("ETH"));

// LRU cache — evicts least-recently-used when full
const images = new LruCache<string, Buffer>(100);
images.set("logo", buffer);

// Deduplication cache — concurrent callers share one in-flight request
const users = new DedupeCache<string, User>(60_000);
// Called 10 times simultaneously → only 1 fetchUser() call
const user = await users.getOrFetch(userId, id => fetchUser(id));

API: TtlCache · LruCache · DedupeCache


fmt — Formatting

Pure functions. No side effects.

import { fmtNum, fmtUsd, fmtMs, fmtDuration, fmtBytes, fmtToken, fmtHex, fmtRelative, renderTable } from "@resolvx/core/fmt";

fmtNum(1_234_567.89, 2)   // "1,234,567.89"
fmtUsd(1234.5)             // "$1,234.50"
fmtMs(12_345)              // "12.35s"
fmtDuration(3725)          // "1h 2m 5s"
fmtBytes(1_536)            // "1.50 KB"
fmtToken(1_500_000n, 6)    // "1.500000"  (bigint — no precision loss)
fmtHex("0xabcdef1234", 6, 4) // "0xabcd…3234"
fmtRelative(Date.now() / 1000 - 3700) // "1h 1m ago"

// No-dependency ASCII table
console.log(renderTable(
  ["Name", "Age"],
  [["Alice", "30"], ["Bob", "25"]],
));

log — Structured logger

Named namespaces, child loggers, pluggable transport.

import { createLogger } from "@resolvx/core/log";

const log = createLogger("api");
log.info("Server started", { port: 3000 });
log.warn("Rate limit approaching", { remaining: 5 });
log.error("Request failed", new Error("timeout"), { url: "/api/data" });

// Child logger — inherits level and transport, adds context to every line
const reqLog = log.child("request", { requestId: "abc-123" });
reqLog.info("Processing");  // → ns=request requestId=abc-123

// Measure async operations
const data = await log.time("fetchData", () => fetch(url).then(r => r.json()));

// Control level at runtime
log.setLevel("debug");

// Custom transport (e.g. JSON to stdout for log aggregators)
const log2 = createLogger("worker", {
  transport: rec => process.stdout.write(JSON.stringify(rec) + "\n"),
});

Set LOG_LEVEL env var to control the default level (debug | info | warn | error | silent).


net — Typed HTTP

import { fetchJson, getJson, postJson, RateLimiter, HttpError, NetworkError } from "@resolvx/core/net";

// GET with timeout + retry (3 attempts, backoff, 4xx not retried)
const user = await getJson<User>("https://api.example.com/users/1");

// POST JSON
const created = await postJson<Post>("/api/posts", { title: "Hello" });

// Error types
try {
  await getJson("/api/data");
} catch (e) {
  if (e instanceof HttpError)    console.log(e.status, e.url);
  if (e instanceof NetworkError) console.log("DNS/connection failure", e.cause);
}

// Rate limiter — 10 req/s, bursting to 20
const limiter = new RateLimiter({ rps: 10, burst: 20 });
const throttledFetch = limiter.wrap(() => getJson(url));
await Promise.all(Array.from({ length: 100 }, throttledFetch));

types — TypeScript utilities

import { brand, assertUnreachable, hasKeys, isDefined, isString } from "@resolvx/core/types";

// Branded types — prevent mixing structurally identical primitives
type UserId  = Brand<string, "UserId">;
type OrderId = Brand<string, "OrderId">;

const uid = brand<UserId>("user-123");
// processOrder(uid) � type error if OrderId is expected

// Exhaustiveness check — compile error if a union case is unhandled
function describe(status: "ok" | "pending" | "failed"): string {
  switch (status) {
    case "ok":      return "✓";
    case "pending": return "…";
    case "failed":  return "✗";
    default:        return assertUnreachable(status);
  }
}

// Runtime type narrowing
if (hasKeys(data, ["price", "symbol"])) {
  console.log(data.price); // data is Record<"price"|"symbol", unknown>
}

Requirements

  • Node.js ≥ 18 (native fetch)
  • TypeScript ≥ 5.0

Contributing

Bug reports and PRs welcome. Run npm test before submitting.

License

MIT

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages