agentsclimarketplace

Nts add cache

Skill juncoding/nextjs-trpc-prisma-starter/skills/nts-add-cache

Claude Code plugin: scaffold and maintain lightweight internal management systems on Next.js + tRPC + Prisma in SPA mode

Install
npx -y skills add juncoding/nextjs-trpc-prisma-starter --skill nts-add-cache

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

  • 0 stars0 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

Retrofit Redis caching into an existing project scaffolded with nextjs-trpc-prisma-starter. Use this when the user wants to add Redis, add a cache layer, speed up frequent reads, share cache across processes, or any 'add caching to this project' request. Adds ioredis, a docker-compose redis service, a typed cache helper at src/server/lib/cache.ts, REDIS_URL env, and updates CLAUDE.md to reflect the new dependency. Refuses to run on projects that don't have the scaffolded structure.

SKILL.md

5.5 KB, as published. Nobody here has run it

Add Redis cache to an existing project

For projects scaffolded with nextjs-trpc-prisma-starter that didn't enable cache at scaffold time and now want it.

Use when

  • User says "add Redis" / "add caching" / "speed up this query" with the implication that in-process cache isn't enough.
  • A specific service method needs cross-process cache (multiple app containers / horizontal scaling).
  • User invokes /nts-add-cache.

Do NOT use when:

  • The project isn't scaffolded by this plugin — the file layout assumptions won't hold. Refuse gracefully and explain.
  • The user wants Next.js's built-in unstable_cache / cacheTag — this plugin uses SPA mode, so those aren't relevant.

What this skill does

Modifies the existing project in place. After running:

  1. ioredis added to package.json dependencies.
  2. redis service added to docker-compose.yml.
  3. REDIS_URL added to .env.example and src/env.ts.
  4. New file src/server/lib/cache.ts with a typed cache.get<T>() / cache.set() / cache.invalidate() helper.
  5. CLAUDE.md updated to mention the new cache layer.
  6. Optional: a sample service method wrapped in the cache helper, so the user has a worked example.

Confirmation flow

Before writing anything, confirm:

  1. Project root path (default: cwd).
  2. Verify package.json, CLAUDE.md, and docker-compose.yml exist — refuse if they don't.
  3. Show the user the list of files that will change.
  4. Confirm.

File changes

package.json

Add to dependencies:

"ioredis": "^5"

docker-compose.yml

Add a redis service alongside postgres:

  redis:
    image: redis:7-alpine
    container_name: {{PROJECT_NAME}}-redis
    ports: ["6379:6379"]
    volumes: [redis-data:/data]
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 5s
      timeout: 5s
      retries: 5

And add redis-data: to the volumes: block at the bottom.

.env.example

Append (with section header):


# Cache
REDIS_URL=redis://localhost:6379

src/env.ts

Add REDIS_URL: z.string().url() to the server schema and REDIS_URL: process.env.REDIS_URL to runtime.

src/server/lib/cache.ts (new)

import "server-only";
import Redis from "ioredis";
import { env } from "@/env";

const redis = new Redis(env.REDIS_URL, { lazyConnect: true, maxRetriesPerRequest: 3 });
// Lazy connect — the connection only opens on first command.

interface CacheOpts {
  /** TTL in seconds. Required — pick a value, no infinite caching. */
  ttlSeconds: number;
}

export const cache = {
  async get<T>(key: string): Promise<T | null> {
    const raw = await redis.get(key);
    if (!raw) return null;
    try { return JSON.parse(raw) as T; } catch { return null; }
  },

  async set<T>(key: string, value: T, opts: CacheOpts) {
    await redis.set(key, JSON.stringify(value), "EX", opts.ttlSeconds);
  },

  async invalidate(key: string) {
    await redis.del(key);
  },

  /** Pattern-based invalidation. Use sparingly — KEYS is O(N) on the keyspace. */
  async invalidatePattern(pattern: string) {
    const keys = await redis.keys(pattern);
    if (keys.length > 0) await redis.del(...keys);
  },

  /** Memoize an async fn. Cache hit returns immediately; miss runs `fn` and stores. */
  async memo<T>(key: string, opts: CacheOpts, fn: () => Promise<T>): Promise<T> {
    const cached = await cache.get<T>(key);
    if (cached !== null) return cached;
    const fresh = await fn();
    await cache.set(key, fresh, opts);
    return fresh;
  },
};

CLAUDE.md

Locate the stack section and add Cache: line. Locate the "Useful commands" section and add docker compose up redis if not present.

Worked example to show the user

After the install lands, show this pattern as the "how to actually use it" example:

// src/server/modules/exchange-rate/exchange-rate.service.ts
import { cache } from "@/server/lib/cache";

export const exchangeRateService = {
  async getRateMap(userId: string) {
    await requirePermission(userId, "exchange-rate:read");
    return cache.memo("exchange-rate:map", { ttlSeconds: 300 }, async () => {
      const rows = await db.exchangeRate.findMany();
      return Object.fromEntries(rows.map(r => [r.currency, r.rate]));
    });
  },

  async updateRate(userId: string, currency: string, rate: number) {
    await requirePermission(userId, "exchange-rate:write");
    await db.$transaction(async (tx) => {
      await tx.exchangeRate.upsert({ where: { currency }, create: { currency, rate }, update: { rate } });
      await auditLog(tx, { userId, action: "exchange-rate.updated", entityId: currency });
    });
    await cache.invalidate("exchange-rate:map");   // invalidate after the tx commits
  },
};

Note: invalidate AFTER the transaction commits, not inside it — if the tx rolls back, you'd otherwise have invalidated a stale entry pointlessly.

Verification

After writing all files, run:

pnpm install
docker compose up -d redis
pnpm tsc --noEmit

Report any failures. Don't try to run the app — the user owns that.

Gives 0 of the 12 instructions most databases sql skills give

Counted across 589 of the 662 authors here whose files we hold, read 2026-08-06

  • use parameterized queriesin 36 of 589, across 32 files
  • use timestamptz for timestampsin 30 of 589, across 12 files
  • create indexes concurrentlyin 29 of 589, across 23 files
  • index foreign keysin 28 of 589, across 17 files
  • use numeric type for moneyin 25 of 589, across 8 files
  • select only required columnsin 24 of 589, across 19 files
  • use cursor pagination instead of OFFSETin 23 of 589, across 15 files
  • add indexes manually on foreign key columnsin 22 of 589, across 11 files
  • read individual rule files for detailed explanationsin 18 of 589, across 4 files
  • configure connection poolingin 18 of 589, across 16 files
  • put equality columns before range columns in indexesin 17 of 589, across 9 files
  • normalize to third normal formin 17 of 589, across 8 files

Said here and by no other author read

  • Verify required files exist before proceeding
  • Show the user the list of files that will change
  • Obtain confirmation before writing anything
  • Add ioredis dependency
  • Add redis service to docker-compose
  • Add REDIS_URL to environment files

Grouped from the skills themselves: near-identical wordings counted once, and counted by distinct author, so one author publishing three of these counts once.

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.