agentsclimarketplace

Api database upstash

Skill agents-inc/skills/src/skills/api-database-upstash

The official skills marketplace for Agents Inc, an agent composition framework that builds stacks and compiles specialized subagents for Claude Code

Install
npx -y skills add agents-inc/skills --skill api-database-upstash

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

One thing to look at

  • 18 stars18 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.

What its author says it does

Copied from the file, not written here

Upstash serverless Redis -- REST-based client, auto-serialization, pipelines, rate limiting, QStash, edge compatibility, global replication

SKILL.md

18.0 KB, ~4.2k tokens by cl100k_base, as published. Nobody here has run it

Upstash Patterns

Quick Guide: Upstash provides a REST/HTTP-based Redis client (@upstash/redis) designed for serverless and edge runtimes where TCP connections are unavailable. Unlike ioredis/node-redis, every command is an HTTP request -- no persistent connections, no connection pools, no teardown. The client automatically serializes/deserializes JSON (objects stored via set come back as objects from get), which is convenient but has gotchas with large numbers and cross-client compatibility. Use redis.pipeline() to batch commands into a single HTTP request, redis.multi() for atomic transactions, and @upstash/ratelimit for pre-built rate limiting algorithms. For background jobs, use @upstash/qstash which pushes messages to your API via HTTP webhooks.


<critical_requirements>

CRITICAL: Before Using This Skill

All code must follow project conventions in CLAUDE.md (kebab-case, named exports, import ordering, import type, named constants)

(You MUST use Redis.fromEnv() for initialization in production code -- never hardcode UPSTASH_REDIS_REST_URL or UPSTASH_REDIS_REST_TOKEN values)

(You MUST handle the pending promise from @upstash/ratelimit responses in edge runtimes -- use context.waitUntil(pending) on Vercel Edge/Cloudflare Workers or analytics data is lost)

(You MUST use redis.pipeline() when issuing 3+ independent commands in a single handler -- each command is a separate HTTP round-trip without pipelining)

(You MUST NOT use Upstash for Pub/Sub, blocking commands (BRPOP, BLPOP, XREAD BLOCK), or Lua scripting -- REST API does not support these; use ioredis with a TCP connection instead)

</critical_requirements>


Examples

  • Core Patterns -- Client setup, commands, auto-serialization, pipeline, transactions
  • Rate Limiting -- @upstash/ratelimit algorithms, middleware, analytics
  • QStash -- Background jobs, scheduling, message publishing

Additional resources:

  • reference.md -- Command cheat sheet, constructor options, environment variables, eviction policies

Auto-detection: Upstash, @upstash/redis, @upstash/ratelimit, @upstash/qstash, Redis.fromEnv, UPSTASH_REDIS_REST_URL, UPSTASH_REDIS_REST_TOKEN, Ratelimit.slidingWindow, Ratelimit.fixedWindow, Ratelimit.tokenBucket, serverless Redis, edge Redis, REST Redis

When to use:

  • Serverless functions (AWS Lambda, Vercel, Netlify) that cannot maintain TCP connections
  • Edge runtimes (Cloudflare Workers, Vercel Edge, Fastly Compute) that only support HTTP
  • Rate limiting API routes with pre-built algorithms (sliding window, fixed window, token bucket)
  • Caching in serverless/edge where ioredis connection pooling is impractical
  • Background job scheduling with QStash (push-based, no long-running consumers needed)
  • Global read latency optimization via Upstash Global Database with read replicas

Key patterns covered:

  • @upstash/redis client setup with Redis.fromEnv() and constructor options
  • Automatic JSON serialization/deserialization behavior and gotchas
  • Pipeline batching (redis.pipeline()) and atomic transactions (redis.multi())
  • @upstash/ratelimit algorithms: sliding window, fixed window, token bucket
  • @upstash/qstash for serverless background jobs and scheduling
  • Global Database architecture (primary + read regions, eventual consistency)
  • Edge runtime compatibility and context.waitUntil() patterns

When NOT to use:

  • Long-running servers with persistent connections (use ioredis -- lower latency per command via TCP)
  • Pub/Sub, blocking commands, or Lua scripting (REST API does not support these)
  • Write-heavy workloads on Global Database (writes always go to primary region)
  • Latency-critical paths where per-command HTTP overhead (~5-15ms) is unacceptable (use ioredis with TCP for <1ms per command)
  • Large payloads (>1 MB) -- REST API has payload size limits

<philosophy>

Philosophy

Upstash exists because serverless and edge runtimes cannot maintain TCP connections. Traditional Redis clients (ioredis, node-redis) rely on persistent TCP sockets -- they fail in Cloudflare Workers, break in short-lived Lambda functions, and cannot run in browser/WebAssembly environments. Upstash replaces TCP with REST/HTTP, trading per-command latency (~5-15ms vs <1ms) for universal compatibility.

Core principles:

  1. Connectionless by design -- Every command is a stateless HTTP request. No connection pools, no teardown, no connection limits. This is a feature, not a limitation.
  2. Auto-serialization is default -- Objects go in, objects come out. No manual JSON.stringify/JSON.parse. This simplifies 90% of use cases but surprises developers who expect raw string behavior.
  3. Pipeline for performance -- Without pipelining, N commands = N HTTP requests. Always batch independent commands with redis.pipeline() to reduce round-trips.
  4. Rate limiting as a first-class citizen -- @upstash/ratelimit provides production-ready algorithms without writing Lua scripts. The library handles all the Redis plumbing internally.
  5. Push-based messaging -- QStash delivers messages TO your API via HTTP webhooks. No long-running consumer processes needed -- perfect for serverless.
</philosophy>
<patterns>

Core Patterns

Pattern 1: Client Setup with Redis.fromEnv()

Initialize using environment variables for zero-config deployment. See examples/core.md for full examples including constructor options and timeout configuration.

// Good Example
import { Redis } from "@upstash/redis";

const redis = Redis.fromEnv();
// Reads UPSTASH_REDIS_REST_URL and UPSTASH_REDIS_REST_TOKEN automatically

export { redis };

Why good: Zero-config, environment variables injected by platform (Vercel, Fly.io), no secrets in code

// Bad Example
import { Redis } from "@upstash/redis";

const redis = new Redis({
  url: "https://us1-merry-cat-12345.upstash.io",
  token: "AXXXAAIgcDE...",
});

Why bad: Hardcoded credentials leak in version control, non-portable across environments


Pattern 2: Automatic JSON Serialization

Upstash auto-serializes objects with JSON.stringify on write and JSON.parse on read. See examples/core.md for type-safe patterns and disabling auto-serialization.

// Good Example -- objects round-trip automatically
interface UserProfile {
  name: string;
  email: string;
  loginCount: number;
}

const CACHE_TTL_SECONDS = 3600;

await redis.set<UserProfile>(
  "user:123",
  {
    name: "Alice",
    email: "[email protected]",
    loginCount: 42,
  },
  { ex: CACHE_TTL_SECONDS },
);

// Returns typed object -- no JSON.parse needed
const user = await redis.get<UserProfile>("user:123");
// user is UserProfile | null

Why good: TypeScript generics provide type safety, no manual serialization, TTL set via options object

// Bad Example -- unnecessary manual serialization
await redis.set("user:123", JSON.stringify({ name: "Alice" }));
const raw = await redis.get("user:123");
const user = JSON.parse(raw as string); // Double-serialized: "{\"name\":\"Alice\"}"

Why bad: Auto-serialization already calls JSON.stringify -- doing it manually results in double-encoded strings that return as escaped JSON


Pattern 3: Pipeline Batching

Batch multiple commands into a single HTTP request. Without pipelining, each command is a separate round-trip (~5-15ms each). See examples/core.md for typed pipeline results.

// Good Example -- single HTTP request for all commands
const USER_TTL_SECONDS = 3600;

const pipe = redis.pipeline();
pipe.set("user:123:name", "Alice", { ex: USER_TTL_SECONDS });
pipe.set("user:123:email", "[email protected]", { ex: USER_TTL_SECONDS });
pipe.incr("stats:signups");

const results = await pipe.exec<["OK", "OK", number]>();
// results[0] => "OK"
// results[1] => "OK"
// results[2] => 1 (incremented value)

Why good: Single HTTP round-trip for 3 commands, typed results with generics, named TTL constant

// Bad Example -- 3 separate HTTP requests
await redis.set("user:123:name", "Alice");
await redis.set("user:123:email", "[email protected]");
await redis.incr("stats:signups");
// 3 round-trips = ~15-45ms total vs ~5-15ms with pipeline

Why bad: Each await is a separate HTTP request, tripling latency in serverless where every millisecond of cold start matters


Pattern 4: Atomic Transactions

Use redis.multi() when commands must execute atomically. See examples/core.md for examples.

// Good Example -- atomic counter + flag update
const tx = redis.multi();
tx.incr("order:count");
tx.set("order:last-updated", Date.now());
const [count, status] = await tx.exec<[number, "OK"]>();

Why good: All commands execute atomically (no interleaving from other clients), typed results

When to use pipeline vs transaction:

  • Pipeline (redis.pipeline()) -- Commands are independent, you want batching for speed, atomicity not required
  • Transaction (redis.multi()) -- Commands must all succeed together, no interleaving allowed

Pattern 5: Rate Limiting with @upstash/ratelimit

Pre-built rate limiting that handles all Redis internals. See examples/rate-limiting.md for all algorithms, middleware integration, and analytics.

// Good Example
import { Ratelimit } from "@upstash/ratelimit";
import { Redis } from "@upstash/redis";

const MAX_REQUESTS = 10;
const WINDOW_DURATION = "10 s";

const ratelimit = new Ratelimit({
  redis: Redis.fromEnv(),
  limiter: Ratelimit.slidingWindow(MAX_REQUESTS, WINDOW_DURATION),
  analytics: true,
});

const { success, limit, remaining, reset, pending } =
  await ratelimit.limit("user:123");

// CRITICAL: In edge runtimes, handle the pending promise
// context.waitUntil(pending);

if (!success) {
  return new Response("Too Many Requests", {
    status: 429,
    headers: {
      "X-RateLimit-Limit": String(limit),
      "X-RateLimit-Remaining": String(remaining),
      "X-RateLimit-Reset": String(reset),
    },
  });
}

Why good: No Lua scripts needed, named constants for limits, analytics for monitoring, proper 429 response with standard headers


Pattern 6: QStash Background Jobs

Push-based messaging for serverless. See examples/qstash.md for scheduling, retries, and receiver verification.

// Good Example -- publish a background job
import { Client } from "@upstash/qstash";

const qstash = new Client({
  token: process.env.QSTASH_TOKEN!,
});

await qstash.publishJSON({
  url: "https://your-app.com/api/process-order",
  body: { orderId: "order-456", action: "fulfill" },
  retries: 3,
  delay: "10s",
});

Why good: Fire-and-forget from handler, automatic retries on failure, configurable delay, at-least-once delivery guaranteed

</patterns>

<decision_framework>

Decision Framework

Upstash vs ioredis/node-redis

Which Redis client should I use?
|-- Running in edge runtime (Cloudflare Workers, Vercel Edge)?
|   --> @upstash/redis (only option -- no TCP available)
|-- Running in serverless (Lambda, Vercel Serverless)?
|   |-- Short-lived functions with no connection reuse?
|   |   --> @upstash/redis (no connection management overhead)
|   |-- Long-lived functions with connection pooling?
|       --> ioredis (lower per-command latency)
|-- Running on a persistent server (Docker, EC2, K8s)?
|   --> ioredis (persistent TCP = <1ms latency vs ~5-15ms HTTP)
|-- Need Pub/Sub, blocking commands, or Lua scripts?
|   --> ioredis (REST API cannot support these)
|-- Need to run in browser or WebAssembly?
    --> @upstash/redis (HTTP works everywhere)

Which Rate Limiting Algorithm?

Which @upstash/ratelimit algorithm should I use?
|-- Need strict, evenly distributed limiting?
|   --> slidingWindow -- smoothest, no burst-at-boundary issues
|-- Need simple, low-overhead limiting?
|   --> fixedWindow -- cheapest computationally, allows boundary bursts
|-- Need to allow burst traffic up to a capacity?
|   --> tokenBucket -- smooths bursts, allows initial spike up to maxTokens
|-- Need multi-region rate limiting?
    --> fixedWindow (slidingWindow has high Redis command overhead in multi-region)

Pipeline vs Transaction vs Sequential

How should I batch these Redis commands?
|-- Commands are independent (no ordering dependency)?
|   --> Pipeline (redis.pipeline()) -- non-atomic but single HTTP request
|-- Commands must execute atomically (all-or-nothing)?
|   --> Transaction (redis.multi()) -- atomic, single HTTP request
|-- Only 1-2 commands?
    --> Sequential is fine -- pipeline overhead not worth it

Global Database vs Regional

Should I use Upstash Global Database?
|-- Read-heavy workload with users worldwide?
|   --> Global Database -- reads from nearest replica
|-- Write-heavy workload?
|   --> Regional Database -- writes always go to primary, replication doubles write cost
|-- Need strong consistency?
|   --> Regional Database -- Global is eventually consistent
|-- Latency-sensitive reads from multiple continents?
    --> Global Database -- sub-1ms reads from nearest region

</decision_framework>


<red_flags>

RED FLAGS

High Priority Issues:

  • Using JSON.stringify() before passing objects to redis.set() -- auto-serialization already handles this, resulting in double-encoded strings like "{\"name\":\"Alice\"}" that break on read
  • Ignoring the pending promise from ratelimit.limit() in edge runtimes -- analytics data and multi-region sync are lost silently; use context.waitUntil(pending)
  • Issuing 5+ sequential await redis.get/set() calls without pipelining -- each is a separate HTTP request, adding 25-75ms of unnecessary latency
  • Attempting Pub/Sub (redis.subscribe), blocking commands (BRPOP, BLPOP), or Lua scripting (eval) -- Upstash REST API does not support these; use ioredis with TCP

Medium Priority Issues:

  • Missing TTL on cached keys -- same as any Redis: unbounded memory growth until eviction kicks in
  • Using Global Database for write-heavy workloads -- writes always route to primary region and replication doubles command costs
  • Not setting automaticDeserialization: false when interoperating with non-Upstash clients -- other clients store raw strings, Upstash will fail to parse them as JSON
  • Creating a new Redis instance per request instead of reusing a module-level singleton -- while connectionless, the client still benefits from HTTP keep-alive and warm connections

Common Mistakes:

  • Expecting redis.get() to return a string when an object was stored -- auto-deserialization returns the original object type, not a JSON string
  • Assuming pipeline execution is atomic -- pipelines batch for network efficiency but other clients can interleave; use redis.multi() for atomicity
  • Using Ratelimit.slidingWindow with MultiRegionRatelimit -- sliding window has high Redis command overhead in multi-region setups; use fixedWindow instead
  • Storing values larger than 1 MB -- REST API has payload size limits; store references and fetch large data from object storage

Gotchas & Edge Cases:

  • Large numbers become strings: JavaScript cannot safely handle numbers > 2^53 - 1 (Number.MAX_SAFE_INTEGER). Upstash returns these as strings even when the TypeScript type says number. Always validate large numeric values.
  • Base64 encoding by default: The SDK requests base64-encoded responses to handle edge cases. If you see garbled output like dmFsdWU=, the response encoding is interfering -- check responseEncoding option.
  • redis.get() returns null for missing keys, not undefined: This matters for TypeScript narrowing -- check result !== null, not truthiness.
  • SET options use an object, not positional args: Upstash uses redis.set("key", "value", { ex: 300 }) not redis.set("key", "value", "EX", 300) -- the ioredis positional argument style does not work.
  • Global Database is eventually consistent: A write followed immediately by a read from a different region may return stale data. Design for eventual consistency or use regional database for strong consistency.
  • hgetall returns an empty object {} for non-existent keys: Check Object.keys(result).length === 0, not result === null.
  • blockUntilReady() does not work on Cloudflare Workers: Cloudflare's Date.now() behaves differently; use limit() with manual retry logic instead.
  • No WATCH command: Upstash REST API does not support WATCH for optimistic locking. Use redis.multi() for atomic operations or implement application-level optimistic concurrency.
  • Auto-pipelining is available: The SDK can automatically batch commands issued during the same event loop tick via enableAutoPipelining: true in the constructor.

</red_flags>


<critical_reminders>

CRITICAL REMINDERS

All code must follow project conventions in CLAUDE.md (kebab-case, named exports, import ordering, import type, named constants)

(You MUST use Redis.fromEnv() for initialization in production code -- never hardcode UPSTASH_REDIS_REST_URL or UPSTASH_REDIS_REST_TOKEN values)

(You MUST handle the pending promise from @upstash/ratelimit responses in edge runtimes -- use context.waitUntil(pending) on Vercel Edge/Cloudflare Workers or analytics data is lost)

(You MUST use redis.pipeline() when issuing 3+ independent commands in a single handler -- each command is a separate HTTP round-trip without pipelining)

(You MUST NOT use Upstash for Pub/Sub, blocking commands (BRPOP, BLPOP, XREAD BLOCK), or Lua scripting -- REST API does not support these; use ioredis with a TCP connection instead)

Failure to follow these rules will cause credential leaks, silent data loss in edge runtimes, unnecessary latency from sequential HTTP requests, and runtime errors from unsupported commands.

</critical_reminders>

Keep looking

Skills are one crate of 328,083. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.