# @krizaka/storage 中文文档

规范地址：https://npmcn.com/package/%40krizaka/storage/

版本：0\.11\.1
npm 元数据更新时间：2026-11-01T14:50:48.330Z
中文译文更新时间：2026-11-01T16:33:47.215Z
软件包许可：Apache-2\.0

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

原始 npm 页面：https://www.npmjs.com/package/%40krizaka/storage
原文对照：https://npmcn.com/package/%40krizaka/storage/?lang=original
项目主页：https://github.com/krizaka/krizaka-ts/tree/main/packages/storage
源码仓库：https://github.com/krizaka/krizaka-ts

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

- 包体积（解压后）：552 kB，54 个文件
- 模块格式：ES Module
- TypeScript 类型：包内自带
- Node.js 要求：\>=22
- 运行依赖：3 个
- peer 依赖：zod \>=3\.23，vitest \>=4，opendal \>=0\.49\.13 \<0\.50
- 首次发布：2026-10-27T01:48:16.797Z
- 最新版本发布：2026-11-01T14:50:47.998Z
- 已发布版本数：14（近 12 个月 14 个）

## 中文简介

每个Krizaka产品的一个端口后面的文件：放入内容哈希的名称，get, head, delete, list，签名和公共url；为HTTP对象存储和任何s3兼容的桶（签名V4，没有SDK）以及本地文件夹进行注入获取的适配器

## 安装

```sh
npm install @krizaka/storage
```

也可以使用：`pnpm add @krizaka/storage`、`yarn add @krizaka/storage`、`bun add @krizaka/storage`

## README

# @ krizaka/storage

每个Krizaka产品的文件都在一个端口后面。端口是根据它的功能来命名的(`ObjectStore`)，提供者是管理员选择的值。无供应商SDK (纯HTTP通过注入 `fetch`)，没有环境变量，没有挂钟。

-   **港口。** `put`， `get`， `head`， `delete`， `list(prefix)`， `signedUrl(key, ttl)` 对于私人区域， `publicUrl(key)` 当存在拉区或CDN时。
-   **适配器。** `remoteStore` (一个形状为Bunny Storage的HTTP区域： `AccessKey` 头, `PUT`/`GET`/`DELETE`,一个由其路径列出的带有尾部斜杠的目录，一个带有令牌签名链接的拉区)， `bucketStore` (任何兼容s3的服务：签名V4，预签名GET， `ListObjectsV2`（没有SDK）； `folderStore` （一个文件夹，用于开发、测试和Mac应用程序；密钥永远不会离开根目录）， `memoryObjectStore`． 所有运行 `objectStoreContract` （`@krizaka/storage/testkit`）。
-   **每次上传都要遵守的规则** （`createStorage`)：传入大小限制，前缀必须在a中声明 `defineLayout` （拒绝未知前缀），字节由幻数(`sniffType`： png, jpeg, gif, webp, pdf, mp4, webm, svg)，从不通过扩展名，并且拒绝与字节相矛盾的声明类型。存储的名称是内容散列的， `<name>.<sha10>.<ext>`，所以URL永远不会改变内容。
-   **管理员选择** （`@krizaka/storage/settings`): `storage.provider` （`local`， `http`， `s3`)， `storage.maxMegabytes`， `storage.signedUrlSeconds`，而非机密坐标(`endpoint`， `region`， `bucket`， `zone`， `prefix`， `pullHostname`， `publicBaseUrl`）. 钥匙留在环境中； `storeFromSettings` 将它们作为参数，并在没有需要的情况下拒绝所选择的提供者，而不是退回到另一个。一个管理类型的S3端点通过 `checkEndpoint` （仅https，没有凭据，没有本地或私有地址）。
-   **静态加密，保险库，区域和 `where`.** `encryptedStore(inner, key)` (AES-256-GCM `ObjectStore`;键是一个参数；提供者保存密文；没有链接)， `seal`/`open` (数据库中的凭据， `v1.` 信封), `COUNTRY_OF_REGION` (一个地区表：未列出的地区永远不是一个国家； `residencyOfConfig` 只有在区域和端点一致的情况下才会显示“加拿大”，而不是兔形区域)， `where(kind, active, { fixed, devOnly })` 和 `storageNotice` （信息面板的句子键，来自配置）。
-   **环境管理** （`@krizaka/storage/resolve`）。 `resolveStorage(env, admin)` 提供各种类型的数据（文档，头像）：传递各自的数据 `DeploymentStorage`)：管理员的 `storage.*` 当管理员离开时，环境提供了秘密和退路 `local`． 如果环境说 `none` 然后管理员继续打开，它抛出 `StorageError` `STORAGE_KEEPING_OFF` （在保存设置时调用它来拒绝早期）：保持在环境中打开，永远不要单独通过设置。 `openResolved` 生成存储，并在给出密钥时进行加密。
-   **供应商注册表** (adr - 005)。 `PROVIDER_KINDS` 类型表是否为数据(`bunny`， `s3` 预置DigitalOcean Spaces、AWS S3、OVHcloud和generic， `dropbox`、保留 `google-drive`/`onedrive`， `folder`， `device`)：作用域、运行时、 `devOnly`、功能、坐标和凭据字段模式。 `openProvider(kind, coordinates, credentials, { runtime, environment, fetch, clock })` （`/node`)是唯一的工厂，它拒绝(`STORAGE_BAD_CONFIG`） `devOnly` 类型在 `prod`， `folder` 外 `dev`，未知或未实现的类型和缺失的字段，并且永远不会回落。它返回store和a `ResidencyLabel` （`PROVINCE_OF_REGION`：«Canada （qubec）»，«Canada （Ontario）»…)，以便在保存前显示屏幕。 `testConnection` （探测写、读、比较、删除）和 `checkEndpointDns` （书面形式加上DNS答案：拒绝私人地址）为每个产品提供服务。
-   **注册表打开OpenDAL类型和private类型** （ADR-005修正案，krizaka-ts#239）。 `google-drive` 和 `onedrive` 在用户范围内实现(通过 `opendalStore` 和 `CLOUD_CONNECTORS`：访问令牌从密封的刷新令牌更新，一个旋转的令牌返回 `OpenContext.onRotate` 并由服务部门再次密封)； `azblob`， `gcs`， `b2` 和 `webdav` 它们的字段名是OpenDAL自己的选项 `url` 字段传递 `checkEndpoint` 和DNS检查)。 `openProvider` 现在是异步的。每种类型映射到表中的适配器(`BUILT_IN_ADAPTERS`)，而不是树枝。私有包在不触及核心的情况下添加了一个类： `defaultProviderRegistry.with({ kind, open })` (或 `createProviderRegistry({ kinds, adapters })`)返回一个新的注册表；传递给 `openProvider(…, registry)` 和 `createProviderService({ registry })`． 重复的名称被拒绝，秘密不能是坐标，凭证必须是 `secret` 字段（密封），以及运行时， `devOnly`、required-field和url规则适用于私有类型和内置类型。
-   **`Keyring` 是一个港口** （`{ keyId, wrap, unwrap, openLegacy? }`）。 `localKeyring({ keyId, key, ring })` 是进程内默认值(旧的 `Keyring` 形状 `{ keys, legacy }` 现在是 `LocalKeys`); `sealWith`， `openWith` 和 `rewrapWith` 取任何密匙环，因此KMS （AWS KMS ca-central-1, OVHcloud KMS, Vault transit）包装数据密钥，密钥永远不会到达应用程序。 `createProviderService({ vault })` 要么是老的 `{ keyId, key, ring }` 或者一个 `Keyring`． 存储 `v2.<key_id>…` 格式不变：移动到KMS是不变的 `rotateVaultKey`，而不是重返大气层。
-   **信封。** `sealEnvelope`/`openEnvelope`/`rewrapEnvelope`：每个密封值的数据密钥，由密钥加密密钥封装(`MEDIA_VAULT_KEY`)由 `key_id` （`v2.<key_id>.<wrapped>.<sealed>`); `v1.` 值仍然使用遗留键打开；旋转键只重新包装数据键。
-   **每租户信封加密** （ADR-020决定3，管理文件存储）。 `KeyService` 是KMS港口(`currentKeyId`， `wrap`， `unwrap` 绑定到a `{ tenant }` 上下文,可选 `rotate`);根密钥永远不会离开KMS。适配器: `memoryKeyService` (测试和开发人员的机器：版本， `rotate`， `destroy`), `cloudKeyService` 用于 `aws-kms` (使用SigV4签名的KMS JSON API，加密上下文中的租户， `RotateKeyOnDemand`), `okms` (OVHcloud KMS服务密钥；访问证书骑在 `fetch` 产品通过；旋转=在设置中命名的新键，然后是重新包装作业)，然后是注入的 `fetch`，没有SDK。 `tenantVault({ kms, keys, clock, audit })` 每个租户和轮换保留一个KEK，封装在数据库(`TenantKeyStore`： `dbTenantKeyStore` 在 `storageSchema` v2, `memoryTenantKeyStore`)，以及每个对象版本的新AES-256-GCM DEK。 `tenantStore(inner, vault, tenantId)` 是一个 `ObjectStore` 下 `t/<tenantId>/`，将完整键作为关联数据，因此另一个租户或重命名的对象永远不会打开。 `rotateTenant` + `rewrapAll` （只有头，没有字节）+ `dropOldVersions`； `rewrapRoot` 根变化后的； `shredTenant` 销毁密钥（墓碑优先，无种族）和每个副本，包括副本和备份，答案 `STORAGE_TENANT_SHREDDED`． 每个关键事件都是一条审计线。合同: `keyServiceContract`， `tenantKeyStoreContract`.
-   **托管提供程序** （ADR-020决定2和7）。 `managed` 是另一种注册表（管理范围）：任何s3兼容的桶(预置OVHcloud与 `bhs` 和 `ca-east-tor`、AWS S3、谷歌云存储互操作性模式、DigitalOcean Spaces（用于本地S3服务器的通用）加上KMS (`kms`： `okms`， `aws-kms`，或者 `local` 仅在开发中（在生产中拒绝），每个密钥都是一个密封的凭证，管理员只输入一次，再也不会看到。 `openProvider("managed", …, { managed: { keys, audit, mtlsFetch? } })` 返回 `forTenant(tenantId)` (一个加密 `tenantStore` 下 `t/<tenantId>/`)和探针存储，因此提供程序屏幕的“测试连接”证明桶，KMS和键表端到端； `managedResidency` 说“加拿大（quamesim）；复制：加拿大（安大略省）»从管理员声明。管理员的控制(`@krizaka/storage/settings`): `managedStorageSettings` (开/关，允许的提供者 `byo`/`managed`/`both`，配额，最长链接，私有文件夹，设备缓存，垃圾，保留界限；全局和每个租户，由 `@krizaka/settings`)， `resolveManagedPolicy` （租户>计划>产品>全球；自相矛盾的策略被拒绝）， `providerAllowed` （一扇门） `linkSeconds`, `MANAGED_PRICE_IDS` （目录条目；未输入数量）。切换租户的提供商是 `moveBatch` + `readDual` 在任何两个存储之间，在真实的S3服务器上用两个桶证明。
-   **数据库中的提供者** （`@krizaka/storage/provider-admin`， ADR-005切片2)。 `storageSchema` (表 `storage.provider` 和 `storage.default_provider`)， `dbProviderStore`/`memoryProviderStore` (合同: `providerStoreContract`), `createProviderService`：创建(a) `draft`（密封在信封里的秘密），测试（只有绿色测试验证）， `setDefault` (仅经过验证；之前的默认值变为 `retiring`)， `changeKey` （候选人先接受测试）， `disable`/`retire` （当产品仍有物体时，拒绝使用）， `rotateVaultKey`， `bootstrap` （进口旧的） `MEDIA_*` 环境曾经是 `active` 提供者, `source: env`）. 秘密回来了 `set`/`not set` 只有;每个更改都被注入 `audit` 有秘密的领域 `changed`. `moveBatch` （get，检查SHA-256， put，回读和比较，然后切换产品事务中的行；返回失败，永远不会切换）和 `readDual` （行提供程序，然后是退休的）是移动和双读。
-   **用户自己的云** （ADR-005切片4）。 `dropboxStore` （`dropbox` 类型，用户范围)：仅应用文件夹，上传会话超过150mb，没有签名或公共链接（Dropbox链接超过我们的900 s上限，所以应用程序流）。 `pkcePair`， `dropboxAuthorizeUrl`， `exchangeDropboxCode`， `refreshDropboxToken`， `revokeDropbox` 为OAuth 2 PKCE步骤；刷新令牌与任何凭证一样是密封的，并且一个已撤销或过期的令牌可以应答 `STORAGE_DISCONNECTED` （没有任何移动或删除）。用户自己的S3也是如此 `s3` 这种在 `user:<id>` 作用域，并对其端点进行DNS检查。
-   **每个经典云都通过图书馆** （ADR-005修正案，krizaka-ts#235）。 `opendalStore` （`@krizaka/storage/opendal`)是Apache OpenDAL的一个适配器，适用于谷歌Drive， OneDrive, Dropbox, Azure Blob， GCS, B2， Swift和WebDAV。只有服务 `OPENDAL_SERVICES` 开放; `fs`/`memory` 需要 `allowLocal`;类型化端点通过 `checkEndpoint` 首先是DNS检查。OpenDAL的错误映射到 `STORAGE_*` （`PermissionDenied` 在OAuth云上→ `STORAGE_DISCONNECTED`), `readStream(key)` 流式传输大文件。 `opendal` 是一个 **可选的对等** （一个本地模块59-82 MB， glibc≥2.38或musl），第一次使用时加载。 `CLOUD_CONNECTORS` （`/node`)是用户云表（Dropbox，谷歌Drive， OneDrive：端点，最窄范围，撤销样式）。 `startConnection`， `finishConnection`， `refreshConnection`， `revokeConnection` 运行Arctic的授权码+ PKCE S256，并且 `connectionAccessToken` 提要 `opendalStore` 一个新的访问令牌，并将一个旋转的刷新令牌返回以重新密封。 `dropboxStore` 它的助手被取代，直到产品移动。
-   **签名。** SigV4是根据AWS发布的向量进行检查的。HTTP区域的签名链接的令牌形式（密钥+路径+过期的SHA-256， url安全的base64）是Yomizaka对Bunny的拉区运行的令牌形式；Bunny的文档没有为它发布预期的输出。

```ts
import { createStorage, defineLayout, folderStore } from "@krizaka/storage";
import { limitsOf, readStorageSettings, storeFromSettings } from "@krizaka/storage/settings";

const settings = await readStorageSettings((key) => service.get(key, ctx));
const storage = createStorage({
  store: storeFromSettings(settings, { accessKeyId: env.MEDIA_STORAGE_ACCESS_KEY_ID, secretAccessKey: env.MEDIA_STORAGE_SECRET }, { fetch, clock }),
  layout: defineLayout(["people/", "site/home/"]),
  limits: () => readStorageSettings(read).then(limitsOf),
});
const { key, url } = await storage.upload({ prefix: "people/", name: "ana.jpg", bytes, allow: ["image/jpeg", "image/png"], declaredType: file.type });
```

apache - 2.0。

## 常见问题

### @krizaka/storage 是什么？

每个Krizaka产品的一个端口后面的文件：放入内容哈希的名称，get, head, delete, list，签名和公共url；为HTTP对象存储和任何s3兼容的桶（签名V4，没有SDK）以及本地文件夹进行注入获取的适配器

### 如何安装 @krizaka/storage？

在项目目录运行 npm install @krizaka/storage，或使用 pnpm add / yarn add / bun add。

### @krizaka/storage 支持 TypeScript 吗？

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

### @krizaka/storage 支持 ESM 还是 CommonJS？

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

### @krizaka/storage 需要什么版本的 Node\.js？

package\.json 的 engines 字段要求 Node\.js \>=22。版本不符时 npm 默认只给出警告，开启 engine-strict 后会拒绝安装。

### @krizaka/storage 可以商用吗？

@krizaka/storage 使用 Apache-2\.0 许可。宽松许可：允许商业使用、修改和再分发，并附带明确的专利授权；分发时需保留许可与 NOTICE 文件，修改过的文件要注明改动。以上为概括，不构成法律意见，请以许可原文为准。

### @krizaka/storage 还在维护吗？

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

