# @charcuterie/server 中文文档

规范地址：https://npmcn.com/package/%40charcuterie/server/

版本：0\.5\.1
npm 元数据更新时间：2026-08-28T07:15:08.373Z
中文译文更新时间：2026-11-09T13:53:29.895Z
软件包许可：MIT

本文由上游 README 翻译，可能存在偏差，请结合原文核对 API、命令与配置。版权与许可归原作者所有。

原始 npm 页面：https://www.npmjs.com/package/%40charcuterie/server
原文对照：https://npmcn.com/package/%40charcuterie/server/?lang=original
项目主页：https://github.com/Sawtaytoes/charcuterie#readme
源码仓库：https://github.com/Sawtaytoes/charcuterie

## 关键信息（来自 npm 元数据）

- 包体积（解压后）：254 kB，80 个文件
- 模块格式：ES Module
- TypeScript 类型：包内自带
- 运行依赖：0 个
- peer 依赖：@hono/node-server \>=1\.13，hono \>=4，mqtt \>=5，vite \>=5
- 首次发布：2026-08-10T10:20:49.843Z
- 最新版本发布：2026-08-28T07:15:07.883Z
- 近 30 天下载量：2483（2026-10-10 至 2026-11-08，npm 官方下载统计）
- 已发布版本数：6（近 12 个月 6 个）

## 中文简介

机队的节点服务器工具包——Hono静态资产处理器,出局后HTTP缓存政策,以及MQTT cmd/resp.

## 安装

```sh
npm install @charcuterie/server
```

也可以使用：`pnpm add @charcuterie/server`、`yarn add @charcuterie/server`、`bun add @charcuterie/server`

## README

# @ charcuterie/ 服务器

舰队的节点服务器包: **预压缩字节,诚实的缓存头,以及 一个304 帮助**,加上MQTT `cmd/*` 编号 : `resp/*` 与家庭助理交谈. 生成压缩字节的 Vite 插件生活在静态处理器旁边 因为两分之一是一份合同 把它们分开是它们如何漂移的. MQTT 是一个单独的子路径, 所以一个静态的应用程序永远不会解决 `mqtt`。 。 。 .

## 为什么会这样

机队的6个应用程序服务着来自Hono服务器的Vite SPA. 所有六个手卷起来,一个 2026年8月的审计发现: **没有压缩任何东西**穆克马吉克是 最坏的情况和导致这个软件包的情况: 它服务于一个1.02 MB 捆绑的未压缩 (290克B击出) `Cache-Control: no-cache, no-store, must-revalidate` 在一个 _内容隐藏_ 文件名 。 每次访问,每次重装,每个客户端导航 被重新下載了1.2 MB,可能已经是318 kB并可能永远被缓存.

失败的不是任何人不小心。 是"保存目录"看起来像 20行,20行 省去压缩,缓存政策,流出, `Range`, (中文(简体) ), MIME 类型、逆向防御以及缺失路径和缺失路径之间的区别 资产.

## 使用量

两分之一 双线.

```ts
// vite.config.ts — the build half
import { precompressAssets } from "@charcuterie/server/vite"
import { createViteConfig } from "@charcuterie/vite-config"

export default createViteConfig({
  plugins: [react(), precompressAssets()],
})
```

```ts
// server — the serve half
import { createStaticHandler } from "@charcuterie/server"

app.route("/api", apiApp)
app.use("*", createStaticHandler({ rootDir: webDistDir }))
```

按先后顺序收养是安全的:没有 `.br`编号 :`.gz` 处理器磁盘上的兄弟姐妹 和兄弟姐妹是惰性的 直到找到他们.

## 在开放标签中部署更新

`createStaticHandler` 并在 `/__charcuterie/deployment` 和 SSE 标记流 `/__charcuterie/deployment/events`标记是部署的SHA-256散列 `index.html`。一个 Vite 构建在命名新资产集时更改了 shell.

一个 `EventSource` 更换容器后重新连接。 它的首次活动来自新的 容器中包含新的标记。 对齐服务器 `useDeploymentUpdate` 从 `@charcuterie/ui`; 它报告 `isUpdateAvailable` 并给应用程序一个 `reload` 行动。 这个 钩子 **没有** 自己重新装入标签,因为标签可以有未保存的工作.

已经拥有 SSE 连接的应用程序可以避免第二流 :

```tsx
const { checkForUpdate, isUpdateAvailable, reload } = useDeploymentUpdate({
  isEventSourceEnabled: false,
})

// In the existing SSE source's successful reconnect callback:
void checkForUpdate()
```

当 `isUpdateAvailable` 是真的,打电话 `reload` 从此 按钮。 静态外壳已经发出 `Cache-Control: no-cache`,因此重载将获取 新外壳及其新的不可变资产 URL。 设置两者 `deploymentPath` 财务报告和审定财务报表 `deploymentEventsPath` 改为 `false` 仅用于没有浏览器客户端的仅资产源.

## MQTT cmd/ resp 数据

唯节. 导入 `@charcuterie/server/mqtt`,而不是主桶,并添加 `mqtt` 作为 应用程序的依赖性。 命令和响应主题为 **从未保留** - 一个经纪人 重放不能在夜间重放 。 拒绝同一动作的重叠命令 与 `{ ok: false, reason: "already-running" }`。 。 。 .

```ts
import { createMqttService } from "@charcuterie/server/mqtt"

const mqtt = await createMqttService({
  base: "board-game-picker",
  host: process.env.MQTT_HOST,
  password: process.env.MQTT_PASS,
  username: process.env.MQTT_USER,
})

mqtt.handleCommand("sync", async (payload) => {
  // run the work
  return { ok: true, payload }
})
// board-game-picker/cmd/sync  →  board-game-picker/resp/sync
```

TLS 默认端口为时 `8883` (单位:千美元)`mqtt.octen.dev`) (中文(简体) ). 过 `isTls` 以覆盖. `truenas-mqtt`是因为 `trigger`编号 :`status` 树是一个遗留的特例——新应用 `cmd`编号 :`resp`这样做 **没有** 属于 `@charcuterie/streams` (浏览器,只推.

## 外出 HTTP 缓存和节流

与上面所有东西相反的方向:这是针对应用程序的反应 **接收** 从别人的服务器。 导入 `@charcuterie/server/http`零点 依赖关系,无 `fetch` 属于自己的.

**图书馆从不拥有存储器.** 5个应用程序在机队缓存第三方HTTP和所有 五个选择了不同的底片——两个SQLite表,两个JSON文件目录,一个 内存 `Map` 写成一个文件,每个文件都坐在那里。 什麽 重复为 _政策_因此,这就是移动在这里: 多久的答案仍然是真实的, 并允许我们问多少次.

```ts
import {
  createHttpCache,
  createThrottle,
  ONE_DAY_MS,
} from "@charcuterie/server/http"

const cache = createHttpCache<string>({
  fetchFromOrigin: async ({ etag, key }) => {
    const response = await fetch(key, {
      headers: etag == null ? {} : { "if-none-match": etag },
    })

    if (response.status === 304) return { outcome: "unchanged" }
    if (response.status === 404) return { outcome: "missing" }
    if (!response.ok) return { outcome: "unavailable" }

    return {
      etag: response.headers.get("etag"),
      outcome: "payload",
      payload: await response.text(),
    }
  },
  lifetime: ONE_DAY_MS,
  store: {
    // Whatever the app already has. Synchronous is fine.
    read: ({ key }) => selectRow(key),
    write: ({ key, record }) => upsertRow(key, record),
  },
  throttle: createThrottle({ minIntervalMs: 1_000 }),
})

const { payload, source } = await cache.get({ key: url })
```

### 终身

`"immutable" | "none" | number`。 。 。 . `"immutable"` 是一个 **关于 URL 的主张**没有暗示.. 在一个 40 个字符的 hash 中的文件不能成为另一个文件. `"none"` 保持一个 完全读出商店。 一个数字是毫秒,它设置在呼叫站点, 因为只有呼叫网站才知道 答案的速度.

### QQ A 304节省了带宽. 它不节省预算.

2026-08-24日以活的 GitHub API 测量,未经认证:每个条件 304请求又来了 `x-ratelimit-remaining` 仍然跌倒一个。 GitHub 文档 a 304作为自由反对 **小学** 费率限制和未经校正的地址限制 这不是这个限度。 一个 `ETag` 买回尸体,不拿预算. **只有一辈子 未用完节省的预算**因为唯一的自由请求是从未发出过的请求.

### 这四把礼节斧头是四样东西

| 选项 | 手段 |
| --- | --- |
| `minIntervalMs` | 最小的间隙从两个开始. |
| `maxConcurrent` | 有多少人可能同时在逃. |
| `maxPerWindow` + 键 `windowMs` | 预算可以补充. |
| `cooldownMs` | 失败后一切都停止了. |

一个缺口不是预算:每秒一个请求允许3600小时,60小时 预算允许在第一秒为三. `run` 槽的队列; `tryRun` 返回时 `null` 而不是排队, 对于一个呼叫者 在民意调查循环 宁可放弃工作 然后把它捡起来 下一滴.

### 也处理过

-   **阴相克取.** `missLifetime` 记得"起源没有这种东西" 一个应用已经取决于。 默认为 `"none"`。 。 。 .
-   **重现状态.** `isStaleWhileRevalidate` 立即返回已淡化的正文,并 后面很新鲜 默认关闭.
-   **单飞.** 同时读取一个键崩溃成一个请求 .
-   **失败就是价值.** 一个 `"unavailable"` 源代码永远不会被写入,为 僵尸 开始冷却 存储错误可降解为错失并转到 `onError`;缓存从来不是页面无法渲染的原因.

名字已经定了.. `fetchedAt` 编号 : `expiresAt` 编号 : `etag`,没有 `Ms` 后缀在一个 `*At` 字段, `Ms` 保持每个持续时间 (单位:千美元)[决定](https://npmcn.com/package/docs/decisions/2026-08-25-an-outbound-http-cache-names-its-fields-fetchedat-and-expiresat.md)) (中文(简体) ).

## 你得到的东西

|  |  |
| --- | --- |
| **压缩** | `.br` 编号 : `.zst` 编号 : `.gz` 兄弟姐妹们,谈判反对 `Accept-Encoding`,与 `Vary` 设置。 在构建时间由Brotli质地11所写出——字节从不改变,因此在用户等待时根据请求推导出烧伤CPU. |
| **缓存** | `/assets/*` → `public, max-age=31536000, immutable`. 其他一切 \* `no-cache`。 。 。 . |
| **调整** | `ETag` + 键 `If-None-Match` 304个没有尸体,适用 **仅限** 页:1 `no-cache` 桶头. |
| **流线** | `createReadStream`所以大字节从不落地. |
| **正确性** | `Range` 请求,MIME搜索, `../` 逆向防御 和失踪 `.js` 404 而不是返回 HTML. |

### 两个缓存桶

维特只有两种文件 `dist/`,他们想要相反的头.

**内容隐藏** 文档(`/assets/index-D7e1J0tu.js`永远不能改变他们的名字后面, 所以他们是 `immutable` - 不仅仅是 `max-age`,仍然允许重新认证 轮回再装入.

**其他的都一样** —— 说 `index.html`任何内容 `public/` - 在整个部署期间保持其名称 每次必须重新确认. `no-cache` 并不意味着“ 不缓存 ” ; 这意味着 "cache,然后在再利用前重新验证". 佩尔戴着ETAG 通常的答案是304 没有尸体.

`no-store` 是表示“不要缓存”的页眉,放在被散列的资产上就是 此软件包存在以删除.

{\\fn黑体\\fs22\\bord1\\shad0\\3aHBE\\4aH00\\fscx67\\fscy66\\2cHFFFFFF\\3cH808080}Bucketing is by **路径前缀**而不是由散列形状的正反面。 维特的 `assetsDir` 是一个建筑 保证; “ 此文件名看起来散列” 是双向错误的猜测 (单位:千美元)`vendor-legacy.js` 不是散发的, `logo-v2.png` 也没有。 覆盖 `immutablePathPrefixes` 如果您需要的话 `assetsDir` 没有 `assets`,或者添加自己的 内容地址目录 :

```ts
createStaticHandler({
  immutablePathPrefixes: ["/assets/", "/images/"],
  rootDir: webDistDir,
})
```

名单 **替换** 默认而不是扩展它,所以一个重新命名的应用程序 `assetsDir` 忘记说,得到安全答案——重新确认——而不是一年 刺痛的骨骼.

## 选项

| 选项 | 默认 |  |
| --- | --- | --- |
| `rootDir` | _所需经费_ | 构建输出的绝对路径 。 相对路径解析 `process.cwd()` - 这就是 `serveStatic`行为,不是我们的. |
| `immutablePathPrefixes` | `["/assets/"]` | 可能永远被缓存的请求路径前缀 . |
| `index` | `"index.html"` | SPA外壳,相对于 `rootDir`。 。 。 . |
| `hasSpaFallback` | `true` | 服务 `index` 用于匹配无文件的无扩展路径。 关闭纯粹的资产来源. |
| `rewriteRequestPath` | —— 说 | 将请求路径映射到 `rootDir` 在查找前,用于显示其 URL 前缀不是真实目录的挂载. |

### 挂载一个生活在别处的目录

`board-games` 服务 `/images/*` 调出 `$BOARD_GAMES_IMAGES`,它远未靠近 网络根 :

```ts
app.use("/images/*", createStaticHandler({
  immutablePathPrefixes: ["/images/"],
  rewriteRequestPath: (path) => path.replace(/^\/images/, ""),
  rootDir: imagesDirectory(),
}))
```

缓存桶还在决定上 **请求** 路径,而不是重写的一个,所以 `immutablePathPrefixes` 继续命名 URL , 因为一个呼叫者看到它们 。 重写是在哪里 字节活在磁盘上;缓存是关于浏览器承诺的内容. SPA的倒计时是 不受影响 : `index` 反对 `rootDir` 直接,所以重写不能误导 壳.

`precompressAssets()` 带 `algorithms` (默认) `["br", "gz"]`和 `thresholdBytes` (默认) `1024`) (中文(简体) ). `zst` 被支持,但作为第三: 操作者更喜欢 布罗特利,每个说zstd的浏览器 也会说布罗特利, Gzip 已经是地板了 对那些不会说话的人来说.

## 移动手卷处理器

舰队中的每个版本都是三个形状之一.

**这个 `readFileSync` 循环** (点 -- -- 市场、邮件过滤器、画廊下载器、Mux-Magic) - 内容类型地图, a `Cache-Control` 特纳里,一个SPA倒置, 和一个穿梭的警卫. 删除全部内容,包括 `CONTENT_TYPES` 常数和 `isWithinRoot` 帮手.

```diff
-app.use("*", async (context) => {
-  const filePath = /* …twenty lines… */
-  context.header("Content-Type", CONTENT_TYPES[ext] ?? "application/octet-stream")
-  context.header("Cache-Control", "no-cache, no-store, must-revalidate")
-  return context.body(readFileSync(filePath))
-})
+app.use("*", createStaticHandler({ rootDir: webDistDir }))
```

**赤裸裸的 `serveStatic`** (板游戏)——已经流出,但没有压缩和 没有缓存策略 。 换个电话.

**`serveStatic` + 键 `onFound`** (板球游戏") `/images/*`———————— **这个是沉默的 今天断了.** `onFound` 运行 _之后_ `serveStatic` 已经建立了反应,所以 它的片头被丢在地上 棋盘游戏 `immutable` 在盒子艺术从来没有 到达浏览器 。 反对 `@hono/node-server` 2.1.0: (中文(简体) )

```ts
serveStatic({ root, onFound: (_p, c) => c.header("Cache-Control", "immutable") })
// → response has no cache-control header at all
```

在中间软件中设置信头 _在此之前_ `serveStatic`,这就是这个包的工作.

## 同伴依赖关系

`hono` 财务报告和审定财务报表 `@hono/node-server` 需要同级软件——应用程序拥有这些版本. `vite` 实值 a 一个 **选项** 仅由 `/vite` 进入点,所以服务器永远不会解决 维特和一个建筑永远不会解决Hono. `mqtt` 是一个 **选项** 仅由 `/mqtt` 切入点,所以仅静态的应用程序永远不会解决经纪人客户端. 这个 `/http` 进入点有 **完全没有依赖关系** - 连Hono都没有.

## 常见问题

### @charcuterie/server 是什么？

机队的节点服务器工具包——Hono静态资产处理器,出局后HTTP缓存政策,以及MQTT cmd/resp\.

### 如何安装 @charcuterie/server？

在项目目录运行 npm install @charcuterie/server，或使用 pnpm add / yarn add / bun add。它还需要宿主项目提供 peer 依赖：@hono/node-server（\>=1\.13）、hono（\>=4）。npm 7 及以上与 pnpm 8 及以上默认会自动安装必需的 peer 依赖，Yarn 需要手动安装。

### @charcuterie/server 支持 TypeScript 吗？

支持。包内自带 TypeScript 类型声明（package\.json 声明了 types 入口），安装后即可获得类型提示，无需再安装 @types 包。

### @charcuterie/server 支持 ESM 还是 CommonJS？

以 ES Module 形式发布，推荐使用 import 引入。在 CommonJS 项目中，Node\.js 20\.19\+ / 22\.12\+ 可以直接 require\(\) 不含顶层 await 的 ES Module，更早的版本需要改用动态 import\(\)。

### @charcuterie/server 可以商用吗？

@charcuterie/server 使用 MIT 许可。宽松许可：允许商业使用、修改和再分发，通常只需在分发时保留原版权与许可声明。以上为概括，不构成法律意见，请以许可原文为准。

### @charcuterie/server 的下载量有多少？

根据 npm 官方下载统计，@charcuterie/server 在 2026/10/10 – 2026/11/08 这 30 天内被下载 2,483 次，日均约 83 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入，适合横向比较热度，不能直接等同于使用人数。

### @charcuterie/server 还在维护吗？

最新版本 0\.5\.1 发布于 2026/08/28（2 个月前）；至今共发布 6 个版本，其中过去 12 个月发布了 6 个；首次发布于 2026/08/10。发布频率只是参考信号：功能稳定的工具可能很少需要更新，建议结合源码仓库的 issue 和提交记录判断。

