mirror of
https://github.com/hanzo-apps/dataroom.git
synced 2026-10-30 14:47:36 +00:00
* KV-optional: in-process backend when KV_URL unset Make dataroom run WITH and WITHOUT external Hanzo KV, uniform with commerce infra/kv.go. KV_URL unset -> in-process MemoryKV (single-replica correct: cache, rate-limit, tus upload locks, export/download job stores, digest queues all work with no external datastore). KV_URL set -> external Hanzo KV over ioredis (multi-replica HA). Malformed KV_URL fails CLOSED (throws) -- never a silent in-process fallback. - lib/kv/url.ts: resolveKvUrl -- fail-closed parse, kv:// brand scheme -> redis:// wire scheme, password redaction in errors. - lib/kv/memory-kv.ts: in-process ioredis-compatible store (strings+TTL, zset, set, hash, list, pipeline) -- the exact surface dataroom uses. - lib/kv/select.ts: the one backend selector (mirrors NewKVClient/FromURL). - lib/redis.ts: use createKvClient() for redis + lockerRedisClient; drop the redis://localhost:6379 silent default (the WITHOUT-KV break) and vestigial REDIS_URL; Upstash-compat shims unchanged. - Tests (node --test, dep-free): 25 pass proving selection + in-process correctness incl NX lock mutual-exclusion, TTL eviction, zset ordering, pipeline shape, fail-closed URL, cross-instance isolation (multi-replica split-brain contract). * kv: warn once when falling back to in-process (non-silent split-brain) Emit a one-time startup warning when KV_URL is unset so the WITHOUT-KV mode is never silent — a multi-replica deploy with KV_URL unset splits sessions, rate-limit windows and tus locks across replicas (single-replica only). --------- Co-authored-by: Hanzo Dev <[email protected]>
46 lines
2.0 KiB
TypeScript
46 lines
2.0 KiB
TypeScript
/**
|
|
* createKvClient — the ONE backend selector, mirroring commerce infra/kv.go's
|
|
* NewKVClient/NewKVClientFromURL split:
|
|
*
|
|
* KV_URL unset ⇒ MemoryKV (in-process, single-replica correct)
|
|
* KV_URL set ⇒ ioredis (external Hanzo KV, multi-replica HA)
|
|
* KV_URL bad ⇒ THROW (fail closed — resolveKvUrl never returns a
|
|
* silent in-process fallback for a malformed DSN)
|
|
*
|
|
* Kept SYNCHRONOUS so `lib/redis.ts` can export module-level singletons exactly
|
|
* as before. ioredis is a static import (a declared dependency, present in the
|
|
* built image); MemoryKV is the pure in-process leaf. The method surface either
|
|
* backend exposes is identical, so every call site and the Upstash-compat shims
|
|
* in `lib/redis.ts` are backend-agnostic.
|
|
*/
|
|
import IORedis, { type Redis } from "ioredis";
|
|
|
|
import { MemoryKV } from "./memory-kv";
|
|
import { resolveKvUrl } from "./url";
|
|
|
|
const IOREDIS_OPTS = { maxRetriesPerRequest: 3, lazyConnect: true } as const;
|
|
|
|
let warnedInProcess = false;
|
|
|
|
export function createKvClient(): Redis {
|
|
const url = resolveKvUrl(process.env.KV_URL); // throws on malformed → fail closed
|
|
if (url === null) {
|
|
// WITHOUT-KV: in-process backend. Warn ONCE per process so this mode is never
|
|
// SILENT — a multi-replica deploy with KV_URL unset would split sessions,
|
|
// rate-limit windows and tus locks across replicas (single-replica only).
|
|
if (!warnedInProcess) {
|
|
warnedInProcess = true;
|
|
console.warn(
|
|
"[kv] KV_URL is unset — using the in-process KV backend. This is correct " +
|
|
"for a SINGLE replica only; multi-replica HA requires KV_URL (external Hanzo KV).",
|
|
);
|
|
}
|
|
// `unknown as Redis` is a controlled cast — MemoryKV implements the exact
|
|
// ioredis subset dataroom uses (proven by lib/kv/memory-kv.test.ts), not the
|
|
// full ioredis type.
|
|
return new MemoryKV() as unknown as Redis;
|
|
}
|
|
// WITH-KV: external Hanzo KV over the RESP wire protocol.
|
|
return new IORedis(url, IOREDIS_OPTS);
|
|
}
|