npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

request-kv-cache

v1.0.4

Published

Framework-agnostic request result cache with TTL, persistence, and request deduplication.

Readme

request-kv-cache

无框架依赖的请求返回值缓存。缓存值保持原始对象、数组或其他 JavaScript 值;use() 返回原生状态对象,不包装成 Vue ref

特性

  • 相同参数共享缓存结果
  • 相同缓存键的并发请求自动去重
  • use() 为相同缓存键返回同一个完整状态对象
  • 支持 TTL,0 表示永不过期
  • 支持 localStorage 或自定义同步存储
  • 请求失败不写缓存,下次调用可直接重试
  • refresh() 失败时保留原有成功缓存
  • 默认稳定序列化 primitive、数组、普通对象参数
  • 同时提供 ESM、CommonJS 和 TypeScript 类型

安装

npm install request-kv-cache

基础用法

import { createRequestCache } from "request-kv-cache";

interface User {
  id: number;
  name: string;
}

interface UserParams {
  page: number;
  status: string[];
}

const userCache = createRequestCache<User[], UserParams>({
  ttl: 30_000,
  request: async (params) => {
    const search = new URLSearchParams({
      page: String(params.page),
      status: params.status.join(","),
    });
    const response = await fetch(`/api/users?${search}`);

    if (!response.ok) {
      throw new Error(`Request failed: ${response.status}`);
    }

    return response.json() as Promise<User[]>;
  },
});

const users = await userCache.get({ page: 1, status: ["active"] });

get()refresh() 直接返回请求函数产生的原始值。缓存命中时,同一对象或数组引用会被复用,直到刷新、删除或过期。

API

createRequestCache(options)

const cache = createRequestCache<TData, TParams>({
  request,
  ttl,
  storage,
  storageKey,
  serializeKey,
});

| 参数 | 类型 | 默认值 | 说明 | | --- | --- | --- | --- | | request | (params) => Promise<TData> | 必填 | 实际请求函数 | | ttl | number | 0 | 缓存有效期,单位毫秒;0 表示永不过期 | | storage | RequestCacheStorage | 无 | 可选同步持久化存储 | | storageKey | string | request-kv-cache | 持久化命名空间 | | serializeKey | (params) => string | 稳定序列化 | 自定义缓存键 | | wrapState | (state) => state | 原始对象 | 可选状态对象包装函数,仅在每个 key 首次调用 use() 时执行 |

get(params, refresh = false)

默认返回未过期缓存;没有缓存时执行请求。第二个参数传 true 时忽略已有缓存并重新请求。同一缓存键正在请求时,所有调用仍共享同一个 Promise。

const data = await cache.get(params);
const refreshedData = await cache.get(params, true);

use(params)

返回指定 key 对应的稳定状态对象。相同 key 多次调用返回同一个对象;没有有效缓存时会自动请求。load() 使用缓存,refresh() 强制请求,失败信息写入 error,不会抛出到调用方:

const state = cache.use(params);

state.loading;
state.data;
state.error;
await state.load();
await state.refresh();

use() 默认只使用原生对象,不依赖 Vue、React 或其他框架。框架需要接管状态对象时,应通过 wrapState 包装;核心后续会直接更新包装后的同一个对象。

refresh(params)

get(params, true) 的兼容别名。忽略现有缓存并重新请求。如果同一缓存键已有进行中的请求,则复用该请求。刷新失败会抛出错误,但不会删除原有成功缓存。

const data = await cache.refresh(params);

peek(params)

同步读取未过期缓存,不发请求。没有缓存时返回 undefined

const cached = cache.peek(params);

has(params)

同步判断是否存在未过期缓存。

if (cache.has(params)) {
  console.log("cache hit");
}

remove(params)

删除指定缓存,返回是否实际删除。

cache.remove(params);

clear()

清空当前实例的全部缓存和对应持久化内容。

cache.clear();

cleanExpired()

清理已过期缓存,返回删除数量。

const removedCount = cache.cleanExpired();

size

返回当前未过期缓存数量。

console.log(cache.size);

缓存键

默认缓存键支持以下参数:

  • nullundefined、字符串、布尔值、数字、bigint
  • 数组
  • 普通对象和 Object.create(null) 对象

普通对象字段会按键名排序,因此 { page: 1, type: "a" }{ type: "a", page: 1 } 命中同一缓存。

循环引用、函数、Symbol、Date、Map、Set 和类实例默认不支持。此类参数应提供 serializeKey

const cache = createRequestCache<Result, Request>({
  request: sendRequest,
  serializeKey: (request) => `${request.method}:${request.url}`,
});

持久化

浏览器可直接传入 localStorage

const cache = createRequestCache<Result, Params>({
  request: loadResult,
  ttl: 60_000,
  storage: localStorage,
  storageKey: "user-list-cache",
});

自定义存储只需实现同步接口:

interface RequestCacheStorage {
  getItem(key: string): string | null;
  setItem(key: string, value: string): void;
  removeItem(key: string): void;
}

持久化通过 JSON 完成,因此请求返回值必须可被 JSON.stringify()。存储权限、容量或序列化失败不会中断内存缓存。

在 Vue 3 中使用

包本身不依赖 Vue。下面在创建缓存时通过 wrapState 配置一次 shallowReactive();业务 useXxx() 只返回核心维护的状态对象,不重复维护 Map,也不绑定组件生命周期。

场景一:系统配置

接口接收配置键 key,返回对应配置值 value。缓存参数就是 key,因此不同配置分别缓存:

// src/utils/system-config.ts
import { shallowReactive } from "vue";
import { createRequestCache } from "request-kv-cache";

interface ConfigResponse {
  data: string;
}

export const systemConfigCache = createRequestCache<string, string>({
  wrapState: shallowReactive,
  request: async (key) => {
    const response = await fetch(
      `/api/system/config?key=${encodeURIComponent(key)}`
    );
    if (!response.ok) {
      throw new Error(`Request failed: ${response.status}`);
    }

    const result = (await response.json()) as ConfigResponse;
    return result.data;
  },
});

export function getSystemConfig(
  key: string,
  refresh = false
): Promise<string> {
  // 配置 key 直接作为缓存 key,不同配置互不覆盖。
  return systemConfigCache.get(key, refresh);
}

export function useSystemConfig(key: string) {
  // 核心缓存负责稳定维护相同 key 的状态引用。
  return systemConfigCache.use(key);
}

Vue 文件使用 use 方式:

<script setup lang="ts">
import { useSystemConfig } from "@/utils/system-config";

const systemName = useSystemConfig("system.name");
</script>

<template>
  <button :disabled="systemName.loading" @click="systemName.refresh">刷新</button>
  <p v-if="systemName.error">系统名称加载失败</p>
  <h1>{{ systemName.data }}</h1>
</template>

其他 JavaScript/TypeScript 文件直接使用 get 方式:

import { getSystemConfig } from "@/utils/system-config";

const copyright = await getSystemConfig("system.copyright");

重复调用相同 key 会返回同一个完整状态对象,且只请求一次;不同 key 分别维护自己的状态和缓存值。

场景二:城市列表

城市列表没有请求参数。工具文件使用固定内部 key,且 ttl 默认为 0,所以应用生命周期内只有第一次调用会请求接口:

// src/utils/city-list.ts
import { shallowReactive } from "vue";
import { createRequestCache } from "request-kv-cache";

export interface City {
  code: string;
  name: string;
}

interface CityListResponse {
  data: City[];
}

export const CITY_LIST_KEY = "city-list";

export const cityListCache = createRequestCache<City[], string>({
  wrapState: shallowReactive,
  request: async () => {
    const response = await fetch("/api/cities");
    if (!response.ok) {
      throw new Error(`Request failed: ${response.status}`);
    }

    const result = (await response.json()) as CityListResponse;
    return result.data;
  },
});

export function getCityList(refresh = false): Promise<City[]> {
  // 固定 key 让 use/get 方式共享同一条城市缓存记录。
  return cityListCache.get(CITY_LIST_KEY, refresh);
}

export function useCityList() {
  // 核心缓存负责稳定维护城市列表的状态引用。
  return cityListCache.use(CITY_LIST_KEY);
}

Vue 文件使用 use 方式:

<script setup lang="ts">
import { useCityList } from "@/utils/city-list";

const cities = useCityList();
</script>

<template>
  <button :disabled="cities.loading" @click="cities.refresh">刷新城市</button>
  <p v-if="cities.error">城市加载失败</p>
  <ul>
    <li v-for="city in cities.data" :key="city.code">{{ city.name }}</li>
  </ul>
</template>

其他 JavaScript/TypeScript 文件直接使用 get 方式:

import { getCityList } from "@/utils/city-list";

const cities = await getCityList();

useCityList() 始终返回同一个完整状态对象,并和 getCityList() 使用同一个模块级缓存实例。无论被多少个文件调用,第一次成功请求后都会复用城市列表缓存。

本地调试

pnpm install
pnpm dev

调试页完全使用原生 DOM、普通对象和数组,不依赖 Vue,并提供同 key 三路并发成功、并发失败场景。

测试

完整测试使用 Node.js 内置 node:testassert,不依赖第三方测试框架:

pnpm test

pnpm test 会先构建 dist,再测试全部公开 API、TTL、并发去重、失败重试、缓存键和持久化。已有最新构建产物时也可直接运行:

pnpm test:node

License

MIT