# @controlvar/js-sdk 中文文档

规范地址：https://npmcn.com/package/%40controlvar/js-sdk/

版本：0\.3\.0
npm 元数据更新时间：2026-11-06T23:00:53.500Z
中文译文更新时间：2026-11-10T19:14:17.679Z
软件包许可：MIT

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

原始 npm 页面：https://www.npmjs.com/package/%40controlvar/js-sdk
原文对照：https://npmcn.com/package/%40controlvar/js-sdk/?lang=original
项目主页：https://github.com/Controlvar/controlvar/tree/main/sdk/js#readme
源码仓库：https://github.com/Controlvar/controlvar

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

- 包体积（解压后）：232 kB，36 个文件
- 模块格式：CommonJS
- TypeScript 类型：包内自带
- Node.js 要求：\>=18
- 运行依赖：0 个
- 首次发布：2026-10-17T21:45:51.034Z
- 最新版本发布：2026-11-06T23:00:53.148Z
- 近 30 天下载量：3543（2026-10-11 至 2026-11-09，npm 官方下载统计）
- 已发布版本数：5（近 12 个月 5 个）

## 中文简介

JavaScript/TypeScript SDK 用于 Controlvar 公共 API (Node QQ 18和浏览器,零运行时间依赖性)

## 安装

```sh
npm install @controlvar/js-sdk
```

也可以使用：`pnpm add @controlvar/js-sdk`、`yarn add @controlvar/js-sdk`、`bun add @controlvar/js-sdk`

## README

# @ controlvar/js-sdk (英语)

**贝塔.** 这是 0.x 发布: 锁定您测试的精确版本 .

Controlvar公共API的JavaScript/TypeScript客户端. 18号节点和浏览器, 零运行时间依赖关系(全球) `fetch`),"共同JS"以类型声明来构建.

旗帜评估和实验任务 **由服务器决定** 默认; 结果为 内存中每个用户+密钥被缓存. 服务器侧码可以选择使用 **当地评价** (单位:千美元)`evaluation: 'local'`,β:旗从服务器的规则中按程序回答,并带有 服务器的答案,任何无法解答的都完全转到服务器。 见 [`docs/sdk/local-evaluation.md`](https://github.com/Controlvar/controlvar/blob/main/docs/sdk/local-evaluation.md)。 。 。 .

完整文档: [`docs/sdk/javascript.md`](https://github.com/Controlvar/controlvar/blob/main/docs/sdk/javascript.md)。 。 。 .

## 安装

如果安装 `@getexperimently/js-sdk`,此软件包在工程改名前的名称,解开并安装 `@controlvar/js-sdk`。 。 。 .

这个 `@controlvar/js-sdk` 软件包已打开 [无](https://www.npmjs.com/package/@controlvar/js-sdk)0.3.0是一个 β释放.

```bash
npm install @controlvar/js-sdk@0.3.0
```

欲研究SDK本身,见下文Development.

## 本版本的更改

-   `getStringValue`, (中文(简体) ), `getNumberValue` 财务报告和审定财务报表 `getJsonValue` 新的: 他们读取了字符串的值, 数字或Json旗服务。 无法返回时, 每人返回默认值, 并传递原因到 `onError(err, 'value')`。见 [旗帜值](https://github.com/Controlvar/controlvar/blob/main/docs/sdk/javascript.md#flag-values)。 。 。 .
-   `FlagEvaluation` 收益 `value` 财务报告和审定财务报表 `valueType`。 。 。 .
-   `onExposure` 是新: 当创建客户端时, 您会给客户端一个函数, 它每次调用一次 时间:服务器在实验中输入一个用户,而答案并非来自其缓存。 见 下出出相钩.
-   `SwallowedOperation`,类型 `onError`第二次辩论有两个新成员: `'value'`,用于 返回默认值的键入计数器,以及 `'exposure'`,用于在 `onExposure`。 。 。 . `ExperimentationErrorCode`类型 `ExperimentationError`是因为 `code`,有两个新成员: `'TYPE_MISMATCH'`,对于一个旗帜服务 另一种类型比读取器 `'HOOK_ERROR'`, (中文(简体) ), 失败的 `onExposure`。可以详尽地检查任一类型,例如 `switch` 谁 `default` 指定值为 `never`,停止编译,直到它处理全部四个 新成员: `'value'`, (中文(简体) ), `'exposure'`, (中文(简体) ), `'TYPE_MISMATCH'` 财务报告和审定财务报表 `'HOOK_ERROR'`。 。 。 .
-   `eventId` 新开的 `track()` 财务报告和审定财务报表 `trackBatch()`。见下文事件编号.
-   本地评价也回答字符串,数字或json旗的值. SDK 无法使用旗下条目 改为(a) `evaluation` 或者说 `value_type` 它不知道,或者一个错误类型的值)只发送 标记到服务器, 其余的规则仍然在本地响应; 在此之前 版本 a `evaluation` 它不知道拒绝整个规则, 客户端继续回答 从它的上一个.
-   一旦当地评价停止回答国旗(`maxStaleMs` 规则被抛弃, `close()`),它在当地回答的用户呼叫到服务器;在此版本之前 用户从缓存中保存本地答案最多 `cacheTtlMs`。 。 。 .
-   A 类 `cacheTtlMs` 联 合 国 `0` 缓存什么。 在这个版本之前,一个答案在同一版本内被重用 毫秒,每个缓存的答案都活了一毫秒 `cacheTtlMs`。 。 。 .
-   有限度 `cacheTtlMs` 24小时以上a `track()` 没有键仍然到达实验 或旗子,其答案仍然被缓存;在此版本之前,它只到达在 最后24小时.
-   请求错误消息将方法和路径命名为没有查询字符串,该字符串带有 用户 id 和用户的属性; 在此版本之前, 有的将完整 URL 命名 .

## 事件编号

`track()` 财务报告和审定财务报表 `trackBatch()` 拿着 `eventId`编号 `event_id`。服务器存储发送的事件 同样的 `event_id`,用户,实验,旗帜和事件名称仅使用一次. 它需要一个 包含 Controlvar 服务器 `event_id` 更改( # 1640, 之后的第一个平台发布) 合并:一个更老的服务器忽略了字段.

-   SDK没有身份证明: `eventId` 没有人被派遣,再被派遣 再来一次 做一次ID, 当事件发生, 并给同样的一个 每次发送它.
-   `undefined`, (中文(简体) ), `null` 财务报告和审定财务报表 `''` 无。 服务器拒绝超过 255 个字符的ID 或 一个持有 NUL 的 `422`SDK不检查它.
-   一次活动吸引了几个实验和旗帜 在每个条目中都带有同样的标识。 A 类 `429` 重审后发送同样的ID。 SDK不会再次发出取消时间的请求.

## 快开始

```ts
import { ExperimentationClient } from '@controlvar/js-sdk';

const client = new ExperimentationClient({ apiUrl: 'http://localhost:8000', apiKey: 'eptk_...' });
const user = { userId: 'user-123', attributes: { plan: 'pro' } };

const assignment = await client.getAssignment('checkout_flow', user); // { experimentKey, userId, variantId, variantName, isControl, configuration, assigned?, reason? }
const variant = await client.getVariant('checkout_flow', user);       // variantName, 'control' on failure
const { enabled, config, reason } = await client.evaluateFlag('new_search', user); // attributes sent as ?context=
const on = await client.isFeatureEnabled('new_search', user);         // false on failure

await client.track('user-123', 'purchase', { value: 49.99, experimentKey: 'checkout_flow' });
await client.track('user-123', 'page_view'); // no key → every experiment + flag answered for this user
```

A 类 `track` 在每一个实验中都没有按键,并标出客户端为用户在 最后24小时, 缓存与否, 但实验是草稿、 暂停、 完成或存档. 没有,它就被发送一次,没有钥匙,并被保存为历史。 密钥存在于此客户端的记忆中 (最多10 000个用户) `clearCache()` 留着吧 在服务器上,通过 `experimentKey` 打开 转换.

## API 密码

| 方法 | 回返 | 失败时 |
| --- | --- | --- |
| `getAssignment(experimentKey, user)` | `Promise<Assignment>` | 拒绝方式 `ExperimentationError` (单位:千美元)`code`, (中文(简体) ), `status`, (中文(简体) ), `body`(中文(简体) ) |
| `getVariant(experimentKey, user)` | `Promise<string>` | `defaultVariant` (单位:千美元)`'control'`(中文(简体) ) |
| `evaluateFlag(flagKey, user)` | `Promise<FlagEvaluation>` —— 说 `{ key, enabled, config, reason?, value?, valueType? }` | 拒绝方式 `ExperimentationError` |
| `isFeatureEnabled(flagKey, user)` | `Promise<boolean>` | `false` |
| `getStringValue(flagKey, user, defaultValue)` | `Promise<string>`,字符串旗的字符串服务于此用户 | `defaultValue`绝不拒绝; `onError(err, 'value')` |
| `getNumberValue(flagKey, user, defaultValue)` | `Promise<number>`,数字标记的数字服务于此用户 | `defaultValue`绝不拒绝; `onError(err, 'value')` |
| `getJsonValue(flagKey, user, defaultValue)` | `Promise<T>`,用于此用户的新对象或阵列副本 | `defaultValue`绝不拒绝; `onError(err, 'value')` |
| `getAllFlags(userId, attributes?)` | `Promise<Record<string, boolean>>` (没有缓存) | 拒绝 |
| `track(userId, eventName, { value?, properties?, experimentKey?, featureFlagKey?, eventType?, timestamp?, eventId? })` | `Promise<void>` | 绝不拒绝; `onError` |
| `trackBatch(events)` | `Promise<{ successCount, failureCount, errors }>` (满分100分) | 从不拒绝 |
| `reportError({ errorType, message, featureFlagKey?, experimentKey?, userId?, stackTrace?, metadata?, timestamp? })` | `Promise<boolean>`数字 : `true` 当服务器存储报告时( 201) | `false`绝不拒绝; `onError` |
| `fetchAssignments(userId, { activeOnly? })` | `Promise<AssignmentRecord[]>` 从服务器 | 拒绝 |
| `getAssignments(userId)`, (中文(简体) ), `getEvaluatedFlags(userId)`, (中文(简体) ), `clearCache()` | 模拟缓存访问 | —— 说 |
| `getCachedFlags(userId)` | `FlagEvaluation[]`: 缓存、 未过期的用户评价; 在本地模式下, 每个带 `source` (单位:千美元)`'local'` 或者说 `'server'`(中文(简体) ) | —— 说 |
| `ready({ timeoutMs? })`, (中文(简体) ), `status()`, (中文(简体) ), `close()` | 本地模式: 准备状态、 规则设置状态、 关闭( 发送评价计数) | 从不拒绝 |
| `consistentHash(userId, flagKey)`, (中文(简体) ), `md5Hex(s)`, (中文(简体) ), `md5Bytes(s)` | 兼容散列工具( 不是旗下打桶功能) | —— 说 |

配置 : `apiUrl`, (中文(简体) ), `apiKey` (所需); `timeoutMs` (5000个), `cacheTtlMs` (30万; 计; `0` 缓存什么, `defaultVariant` (单位:千美元)`'control'`, (中文(简体) ), `fetch`, (中文(简体) ), `onError(err, 'track' | 'trackBatch' | 'refresh' | 'flush' | 'evaluate' | 'reportError' | 'value' | 'exposure')`, (中文(简体) ), `onExposure(exposure)` (见接触钩), `evaluation` (单位:千美元)`'server'` 或者说 `'local'`, (中文(简体) ), `refreshIntervalMs` (30 000美元,最低5 000美元), `maxStaleMs`。 。 。 . 失败永远不会被缓存; 同时调用同一个用户 + 密钥共享一个请求; a 429 是 之后再试一次 `Retry-After`。 。 。 .

`user.attributes` 以 `context` 派任时 **财务报告和审定财务报表** 国旗评价 (单位:千美元)`&context=<url-encoded JSON>`,在空出时省略),所以旗下目标设定规则与之相对评价 (单位:千美元)`country` 也匹配 `user.country`; 嵌入对象平整为点键, 如 `app.version`) (中文(简体) ). Caches 仅由 user + 密钥键入- call `clearCache()` 修改用户属性后.

`reportError` 决定 `true` 当服务器存储报告时。 存储的报告数到 旗下错误率和自动回滚 仅当客户端的密钥有 `sdk:ruleset` 范围和范围 由用户创建,用户可以更改特征标记;旗帜的安全检查显示其他人是 `uncounted_error_count`。 。 。 .

### 招生情况: `assigned` 财务报告和审定财务报表 `reason`

一个 `Assignment` 还携带 `assigned` 财务报告和审定财务报表 `reason`。 。 。 . `assigned: false` 表示服务器没有 注册用户( e)`reason` 实值 `'holdout'`, (中文(简体) ), `'mutual_exclusion'` 或者说 `'targeting'`)并返回 控制变体,这样你就能实现默认体验;没有记录曝光. **导出时 与仓库接触,不包括与 `assigned: false`**:用户没有注册, 财务报告和审定财务报表 `reason` 说为什么 在字段之前的服务器不会发送, 所以 `assigned` 实值 `undefined` (从来没有) `false`) (中文(简体) ). 特意决定如何对待这些行而不是放下 带有 `assigned === true` 过滤器.

一个不是ACTIVE的实验 以控制变体回答每个用户, `assigned: false` 财务报告和审定财务报表 `reason` 达到其地位`'draft'`, (中文(简体) ), `'paused'`, (中文(简体) ), `'completed'` 或者说 `'archived'`) (中文(简体) ).

## 曝光钩

`onExposure` 是您创建客户端时给客户端的函数。 客户每人打一次电话 时间:服务器将用户录入实验,而答案并非来自客户端 缓存:服务器记录曝光的那一刻. 用它来传送曝光到你的分析 工具或仓库.

```ts
const client = new ExperimentationClient({
  apiUrl: 'http://localhost:8000',
  apiKey: process.env.CONTROLVAR_API_KEY!,
  onExposure: (exposure) => {
    // your analytics tool's call goes here
    console.log('exposure', exposure.experimentKey, exposure.variantName, exposure.userId);
  },
  onError: (error, operation) => {
    if (operation === 'exposure') console.warn('onExposure failed', error.cause);
  },
});
```

它收到一个 `Exposure`数字 : `experimentKey`, (中文(简体) ), `userId`, (中文(简体) ), `variantId` (单位:千美元)`null` 当服务器发送时 (无)和 `variantName`。它不要求缓存的答案, 实验没有注册的用户 或未运行的实验(`assigned: false`),一个失败的呼叫,或者一个特征旗. 若为 它扔出,或者返回一个承诺,拒绝,答案是不变的,错误将发生 `onError` 操作 `'exposure'` 财务报告和审定财务报表 `code` `'HOOK_ERROR'`。 。 。 .

详细情况 : [曝光钩](https://github.com/Controlvar/controlvar/blob/main/docs/sdk/javascript.md#exposure-hook)。 。 。 .

## 使用的后端端点

每个请求都携带 `X-API-Key`, (中文(简体) ), `Content-Type: application/json`, (中文(简体) ), `Accept: application/json`。 。 。 .

| SDK电话 | 方法和路径 | 200个答复 |
| --- | --- | --- |
| `getAssignment`, (中文(简体) ), `getVariant` | `POST /api/v1/tracking/assign` `{experiment_key, user_id, context?}` | `{experiment_key, user_id, variant_id, variant_name, is_control, configuration, assigned, reason}` |
| `evaluateFlag`, (中文(简体) ), `isFeatureEnabled` | `GET /api/v1/feature-flags/evaluate/{flag_key}?user_id=…&context=<url-encoded JSON>` | `{key, enabled, config, reason}` |
| `getAllFlags` | `GET /api/v1/feature-flags/user/{user_id}?context=<url-encoded JSON>` | `{"<flag_key>": bool}` |
| `track` 有密钥 | `POST /api/v1/tracking/track` | 忽略 |
| `track` 无密钥 | `POST /api/v1/tracking/batch` `{events: [...]}` (≤100),每个实验一个条目并标出它到达的旗帜;没有, `POST /api/v1/tracking/track` 无密钥 | 忽略 |
| `trackBatch` | `POST /api/v1/tracking/batch` `{events: [...]}` (≤100) | `{success_count, failure_count, errors}` |
| `fetchAssignments` | `GET /api/v1/tracking/assignments/{user_id}?active_only=true` | 列列表 |

出错:401个坏键,404个未知的实验或旗下键,或无控制变体(从未被缓存)的草稿实验,422个无效事件,429个速率有限. 非 ACTIVE 的旗帜不是错误: 它回答 `enabled: false`, (中文(简体) ), `reason: "inactive"`。 。 。 .

## 合同烟

```bash
cd sdk/js && npm run build --silent && node examples/contract_smoke.mjs
```

信封 : `CONTROLVAR_API_URL` (默认) `http://localhost:8000`, (中文(简体) ), `CONTROLVAR_API_KEY` (必须), `CONTRACT_EXPERIMENT_KEY` (单位:千美元)`sdk_contract_ab`, (中文(简体) ), `CONTRACT_FLAG_KEY` (单位:千美元)`sdk_contract_flag`, (中文(简体) ), `CONTRACT_USER_ID`。 。 。 .

CI负责SDK的合约烟雾 与真正的API: `js` 位于 `sdks:` 清单 SDK 现场合同工作 [pr-qa-gate.yml (英语)](https://github.com/Controlvar/controlvar/blob/main/.github/workflows/pr-qa-gate.yml)。 。 。 .

## 发展

从 `sdk/js` 在克隆中 [此寄存器](https://github.com/Controlvar/controlvar)数字 :

```bash
npm install
npm test
npm run build
```

-   `npm test`: Jest测试(嘲弄;当地评价员对照测试/sdk-contract/Rulesset-vectors.json)
-   `npm run build`: tsc → det/ (CommonJS +.d.ts) (中文(简体) )

## 常见问题

### @controlvar/js-sdk 是什么？

JavaScript/TypeScript SDK 用于 Controlvar 公共 API \(Node QQ 18和浏览器,零运行时间依赖性\)

### 如何安装 @controlvar/js-sdk？

在项目目录运行 npm install @controlvar/js-sdk，或使用 pnpm add / yarn add / bun add。

### @controlvar/js-sdk 支持 TypeScript 吗？

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

### @controlvar/js-sdk 支持 ESM 还是 CommonJS？

以 CommonJS 形式发布，可以直接 require\(\)；在 ES Module 项目中也可以用 import 引入（Node\.js 会把 module\.exports 作为默认导出）。

### @controlvar/js-sdk 需要什么版本的 Node\.js？

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

### @controlvar/js-sdk 可以商用吗？

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

### @controlvar/js-sdk 的下载量有多少？

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

### @controlvar/js-sdk 还在维护吗？

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

