mirror of
https://github.com/hanzoai/dataroom.git
synced 2026-10-30 18:41:07 +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]>
63 lines
2.2 KiB
TypeScript
63 lines
2.2 KiB
TypeScript
import assert from "node:assert/strict";
|
|
import { test } from "node:test";
|
|
|
|
import { resolveKvUrl } from "./url.ts";
|
|
|
|
test("unset / empty / whitespace ⇒ null (in-process backend)", () => {
|
|
assert.equal(resolveKvUrl(undefined), null);
|
|
assert.equal(resolveKvUrl(null), null);
|
|
assert.equal(resolveKvUrl(""), null);
|
|
assert.equal(resolveKvUrl(" "), null);
|
|
});
|
|
|
|
test("kv:// brand scheme normalizes to redis:// wire scheme", () => {
|
|
assert.equal(
|
|
resolveKvUrl("kv://hanzo-kv.hanzo.svc:6379"),
|
|
"redis://hanzo-kv.hanzo.svc:6379",
|
|
);
|
|
assert.equal(
|
|
resolveKvUrl("kv://:secret@hanzo-kv:6379"),
|
|
"redis://:secret@hanzo-kv:6379",
|
|
);
|
|
});
|
|
|
|
test("kvs:// (TLS brand scheme) normalizes to rediss://", () => {
|
|
assert.equal(resolveKvUrl("kvs://hanzo-kv:6380"), "rediss://hanzo-kv:6380");
|
|
});
|
|
|
|
test("raw redis:// / rediss:// DSN is accepted verbatim (commerce parity)", () => {
|
|
assert.equal(
|
|
resolveKvUrl("redis://:pw@hanzo-kv:6379/0"),
|
|
"redis://:pw@hanzo-kv:6379/0",
|
|
);
|
|
assert.equal(resolveKvUrl("rediss://hanzo-kv:6380"), "rediss://hanzo-kv:6380");
|
|
});
|
|
|
|
test("FAIL CLOSED: unparseable URL throws (never silent in-process fallback)", () => {
|
|
assert.throws(() => resolveKvUrl("::: not a url :::"), /malformed/);
|
|
});
|
|
|
|
test("FAIL CLOSED: non-redis scheme throws", () => {
|
|
// A bare host:port parses as scheme 'localhost:' — must be rejected, not
|
|
// silently connected to a phantom localhost.
|
|
assert.throws(() => resolveKvUrl("localhost:6379"), /unsupported scheme|malformed/);
|
|
assert.throws(() => resolveKvUrl("http://hanzo-kv:6379"), /unsupported scheme/);
|
|
assert.throws(() => resolveKvUrl("valkey://hanzo-kv:6379"), /unsupported scheme/);
|
|
});
|
|
|
|
test("FAIL CLOSED: missing host throws", () => {
|
|
assert.throws(() => resolveKvUrl("redis://"), /missing a host/);
|
|
});
|
|
|
|
test("error message REDACTS the password (no credential leak in logs)", () => {
|
|
let msg = "";
|
|
try {
|
|
resolveKvUrl("http://:supersecretpw@hanzo-kv:6379");
|
|
} catch (e) {
|
|
msg = (e as Error).message;
|
|
}
|
|
assert.ok(msg.length > 0, "expected a throw");
|
|
assert.ok(!msg.includes("supersecretpw"), "password must not appear in error");
|
|
assert.ok(msg.includes("<redacted>"), "expected redaction marker");
|
|
});
|