@arki/kv

archive

for @arki/[email protected] · Tier 1: the stack

Key-value store abstraction with pluggable adapters for ARKI.

Version 0.2.1 archive

Install

bun add @arki/kv
Other package managers
npm install @arki/kv
pnpm add @arki/kv
yarn add @arki/kv

Release health for @arki/[email protected]

Doc examples

No example report was recorded for this version.

Open gaps

6 release checks fail

  • checkfile-llms-txt
  • checkfile-agents-md
  • checkfile-agent-guide
  • checkreadme-h2-order
  • checkpkg-engines
  • checkfiles-ship-dist-docs

Used by demos

No demo uses this package yet.

Runtimes

No engines field, so no runtime requirement is declared.

Benchmarks

No benchmarks are published for this version.

README

Key-value store abstraction with pluggable adapters for ARKI. Wraps Keyv for general key-value access and rate-limiter-flexible for Redis-backed rate limiting. Provides automatic TLS handling (via rediss:// URLs), Upstash support, and a graceful in-memory fallback when Redis is unreachable.

Installation

npm install @arki/kv
# or
bun add @arki/kv
# or
pnpm add @arki/kv

Usage

KV — direct construction

import { KV } from '@arki/kv';
 
const kv = new KV('redis://localhost:6379', 'my-app');
 
await kv.set('hello', 'world', 60); // 60-second TTL
const value = await kv.get('hello'); // 'world'
await kv.delete('hello');
 
await kv.close();

createKV / createKVInstance — env-driven factories

import { createKV } from '@arki/kv';
import { createKVInstance } from '@arki/kv/instance';
 
// Reads KV_URL from process.env, throws if missing.
const kv = createKV('my-app');
 
// Returns null in development if KV_URL is missing (no throw); exits in production.
const kvOrNull = createKVInstance('my-app');

Rate limiting

import { createKV } from '@arki/kv';
import { RateLimitExceededError } from '@arki/kv/errors';
 
const kv = createKV('my-app');
const limiter = kv.getRateLimiter('login', { points: 5, duration: 60 });
 
try {
  await limiter.consume(userId);
} catch (cause) {
  if (cause instanceof RateLimitExceededError) {
    // 429 + Retry-After headers from `cause.getHttpHeaders(5)`.
  }
}

When the rate-limiter Redis client is not ready, the limiter transparently falls back to an in-memory implementation — your code keeps working, persistence is briefly lost.

API

  • @arki/kv
    • KV — Redis-backed key-value + rate-limiter client.
    • createKV(namespace?, rateLimitPrefix?) — env-driven KV factory.
    • createRateLimitKV(namespace?) — env-driven KV factory tuned for rate limiting.
  • @arki/kv/env — env object holding the validated KV/Redis env vars.
  • @arki/kv/errors — RateLimitExceededError with HTTP-header helpers.
  • @arki/kv/instance — initializeKV, createKVInstance lazy-singleton helpers.

Environment variables

VariablePurpose
KV_URLPrimary Redis/Keyv URL (use rediss:// TLS).
KV_NAMESPACEOptional default namespace for keys.
RATE_LIMIT_REDIS_URLDedicated Redis URL for rate limiting.
RATE_LIMIT_PREFIXPrefix for rate-limit keys (default rl).
CACHE_TTL_DEFAULTDefault TTL in seconds (default 3600).

Documentation

@arki/kv ships an optional @arki/kv/dot adapter for the @arki/dot framework.

Benchmarks

Only the rate limiter is benchmarked, against rate-limiter-flexible, the library @arki/kv wraps. @arki/kv talks only to Redis, so its get/set path needs a server and is not measured here. While it cannot reach Redis, getRateLimiter() returns an in-memory limiter, and that is what runs below.

Node v22.13.1 on Apple M2 Max, 2026-10-13. Median time per operation, lower is better.

consume on a limiter you already hold

LibraryTime / opvs fastest
@arki/kv getRateLimiter() result, held once500.0 ns1.50×
rate-limiter-flexible RateLimiterMemory (used directly)333.0 nsfastest

Look the limiter up on every request, then consume

How the API reads when you call it inline in a request handler.

LibraryTime / opvs fastest
@arki/kv kv.getRateLimiter(id, options).consume(key)708.0 ns2.13×
Map<id, RateLimiterMemory> lookup, then consume333.0 nsfastest

Read the rows with the contracts in mind:

  • The Redis client in this benchmark points at a closed local port. Nothing is listening and nothing leaves the machine. This measures the in-memory fallback, not the Redis-backed limiter that production uses, whose cost is dominated by the network round trip.
  • The limiter allows 1,000,000,000 points, so no call is rejected and every consume takes the success path.
  • Holding the limiter and calling consume runs the same rate-limiter-flexible code as using it directly, so those two rows should match within noise. The only difference is the lookup row: getRateLimiter(id, options) builds a cache key by serialising the options to JSON on every call.

Reproduce with bun run bench in the package directory; regenerate this section with bun run bench --write-readme.

License

MIT — see LICENSE.

Public imports

This version has no API index.