td-feature-node
v0.0.2
Published
ThinkingData server-side experimentation SDK for Node.js
Readme
TD Feature Node.js SDK
ThinkingData 实验平台的 Node.js 服务端 SDK,分阶段实现一期远端分流。
当前版本:0.0.2。远端分流、曝光、完整服务 Demo、HTTPS、资源压力及发布检查已补齐;自动曝光默认关闭。0.0.2 待发布,0.0.1 已发布;未实现本地分流。真实项目与跨操作系统验收范围另行列明。
分阶段交付
| 阶段 | SDK | Demo | 状态 | | --- | --- | --- | --- | | S1 | npm 包、类型声明、初始化与关闭 | 初始化示例 | 已完成 | | S2 | RCC 请求、签名、响应解码、Feature 取值 | Mock RCC / 真实 RCC 两种运行方式 | 已完成本地验收 | | S3 | 单次超时、可选重试、有界并发/排队、结果缓存 | 重试恢复、缓存、过载和超时场景 | 已完成本地验收 | | S4 | Java 对齐的手动/自动曝光、去重、队列、采集适配器 | 实际使用结果后曝光 | 已完成本地验收 | | S5 | 完整兼容矩阵、负载、内部候选包验收 | 完整 API、进程关闭、显式业务回退 | 本机自动化验收;外部项保留 |
详见 接入指南、任务清单、Java 对齐基准、兼容性记录、发布验收与保留项。
使用
当前待发布版本为 [email protected]。npm 安装包只包含 dist/、README、包信息和纯生产依赖发布锁;完整文档与 Demo 保留在源码仓库,不随安装包分发。本文的构建、Demo 和验收命令均在源码仓库根目录执行。
先构建并验证本地包:
npm ci
npm test
npm run verify:package0.0.2 发布后,在消费项目从 registry 安装;默认安装无需 TypeScript、Prettier 或开发依赖:
npm install [email protected]const { createClient, SplitMode } = require('td-feature-node');
const client = createClient({
splitMode: SplitMode.REMOTE,
appId: 'your_app_id',
serverUrl: 'https://your-rcc-server.example.com',
timeout: 1000,
retryTimes: 0,
maxConcurrentRequests: 20,
maxQueueSize: 0,
enableCache: false,
autoExposure: false
});
client.fetch({
accountId: 'user_123',
distinctId: 'device_456',
featureKeys: ['checkout_button'],
customParams: { country: 'CN' }
}).then(function (config) {
const feature = config.features['checkout_button'];
const text = feature ? feature.asString('购买') : '购买';
console.log(text, feature && feature.experiment && feature.experiment.experimentId);
if (feature) client.expose(feature, { scene: 'checkout' }); // 在实际使用值后曝光。
}, function (error) {
// 网络或协议错误由业务显式处理,不会伪装成成功的空配置。
console.error(error.code, error.message);
}).then(function () {
return client.close(); // 单次脚本示例;服务端应复用 Client,在进程退出时关闭。
}, function (error) {
console.error(error.code, error.message);
return client.close();
});createClient() 不访问网络。splitMode 默认 SplitMode.REMOTE,也可直接传 'REMOTE'。SplitMode.LOCAL 仅作后续扩展预留,当前传入会同步抛出 INVALID_CONFIG。fetch() 在缓存未命中时请求 RCC,默认不排队、不自动重试;需要时可显式开启。身份必须至少包含一个非空字符串。不指定 Feature 列表或列表为空时请求全部 Feature。customParams 只接受 JSON 数据,保留身份、语言和 Feature 字段由 SDK 覆盖;使用原生 JSON.stringify,对象和数组中的 null 均保留,未提供的账号/设备字段省略。
配置
| 配置 | 默认值 | 说明 |
| --- | --- | --- |
| appId | 必填 | 平台项目 ID |
| serverUrl | 必填 | 远端决策与采集共用的 HTTP(S) 基础地址 |
| splitMode | SplitMode.REMOTE | 初始化时选择分流模式;当前仅实现 REMOTE,LOCAL 为预留值且会报错 |
| timeout | 5000 ms | 单次 HTTP 阶段超时,含连接池等待、连接和响应读取;到期中止请求 |
| maxHttpConnections | 20 | 同一 RCC 的连接池最大连接数 |
| maxConcurrentRequests | 与 maxHttpConnections 相同 | 活跃 fetch 上限,不能大于连接数;解码和退避期间保留槽位 |
| maxQueueSize | 0 | 默认不排队;可设 0–1000000,并发及等待位置满时返回 QUEUE_FULL |
| retryTimes | 0 | 默认不自动重试;首次请求后的最大重试次数,范围 0–10 |
| retryInterval | 500 ms | 首次退避间隔;允许 0 |
| retryStrategy | EXPONENTIAL_BACKOFF | 可选 FIXED_INTERVAL |
| retryJitter | true | 在退避区间内作 full jitter 随机延迟 |
| maxRetryInterval | 30000 ms | 重试等待上限;Retry-After 超过此值时停止重试,返回原 HTTP 错误 |
| enableCache | false | 显式启用成功结果缓存 |
| cacheExpireTime | 86400 秒 | RCC TTL 非正数时的回退;正数 RCC TTL 优先 |
| maxCacheSize | 10000 | LRU 缓存条目上限;0 不存储 |
| maxDecompressedResponseBytes | 10 MiB | data 字段 Base64/GZIP 解压后的上限,与 Java 默认值一致 |
| autoExposure | false | 开启后首次读取原始/类型化值时自动曝光 |
| exposureDedupMaxSize | 100000 | 曝光去重条目上限,独立于结果缓存 |
| exposureDedupExpireSeconds | 86400 秒 | 去重 TTL,读取不续期 |
| exposureQueueCapacity | 10000 | 等待曝光容量,范围 1–1000000;另有一个活跃任务 |
| exposureBatchSize | 100 | 自建采集器每批事件数,范围 1–10000 |
| exposureFlushIntervalMillis | 1000 ms | 自建采集器不足一批时的最长缓冲时间 |
| exposureFlushTimeoutMillis | 5000 ms | flush/close 等待预算;自建采集请求的单次 I/O 预算 |
| tdAnalytics | 未指定 | 复用业务已有 Node 采集实例,外部实例由业务关闭 |
| exposureAdapter | 未指定 | 自定义异步采集适配器,与 tdAnalytics 互斥,外部实例由业务关闭 |
| tlsCa | 运行时默认信任根 | 显式 PEM CA 字符串/数组会替换默认 CA 列表,作用于 RCC/自建采集 HTTPS,仍检查主机名 |
timeout 只控制每次 HTTP 尝试,重试会重新计时;排队、重试等待和 RCC 字段解码不计入该 HTTP 超时。整次 fetch 的耗时会随尝试次数与退避等待增加,可用 AbortSignal 提前取消。完整语义和示例见 S3 请求可靠性与缓存。
默认最多 20 个活跃 fetch,没有等待队列,失败只尝试一次。显式开启排队或重试后,整次 fetch 仍没有 SDK 内置的总期限;有业务截止时间时传入带期限的 signal。较早版本默认重试 3 次、排队 1000 项,接入升级时需留意默认行为变化。
serverUrl 不接受认证信息、查询参数或 fragment,末尾 / 会归一化。未知或未实现的配置会报错,当前不能设置本地模式。
Feature 读取
fetch() 返回只读的普通对象:
{
features: { checkout_button: feature },
ttl: 60 // RCC 原始 TTL,单位秒,不是缓存剩余寿命
}通过 result.features[key] 查找,缺失时为 undefined;Object.keys(result.features) 枚举 Feature。字典没有原型,__proto__、constructor 等服务端 Key 也能安全读取,experiment_detail 不进入字典。
Feature 提供 key、value、accountId、distinctId、experiment 和 hasValue 属性;转换方法为 asString(fallback)、asNumber(fallback)、asBoolean(fallback)、asJson(fallback)。省略 fallback 时默认为 null;false、0、空字符串不会被业务默认值覆盖。
JSON 数字使用原生 number:asString() 按 String(number) 输出,对象和数组按 JSON.stringify 输出。例如 1.0 读取为 1、字符串为 "1",1e23 的字符串为 "1e+23"。超出安全整数范围的数字遵循 JS 精度规则,9007199254740993 会读取为 9007199254740992;需要精确保留的长 ID 应由服务端作为字符串返回。
asNumber() 接受数字或非空数字字符串,按 Number() 解析;例如 "0x10" 为 16,"1f"、"0x1.8p1"、NaN、空白字符串和其他类型返回 fallback。asBoolean() 接受布尔值或大小写不限的 "true" / "false" 字符串。asJson() 接受对象或解析为对象的 JSON 字符串;空字符串、"null"、数组和无效 JSON 返回 fallback。
experiment 为 null 或只读实验信息对象;实验 ID、分组、控制组和自定义分桶直接从对象读取。不再提供重复元数据 getter,也不为远端模式构造版本/分流来源占位值。
读取 value 或调用转换方法会触发可选自动曝光;读取元数据和 hasValue 不会。自动曝光默认关闭。展开、序列化 Feature 也会读取 value,调试元数据时只选取所需字段。
未发布 API 在 rc.2 直接调整,不提供旧构造器或 getter 别名。旧候选迁移及维护约定见 Node API 与编码约定。
曝光
client.expose(feature, properties?) 同步返回是否入队;未匹配、重复、队列满或已关闭返回 false。返回 true 不是入库确认。入队前复制属性,所有 # 开头的自定义属性被忽略。远端曝光只发送 Java 已提供的元数据,不从结果的 Feature Key 推造远端元数据。
自动曝光默认关闭;显式开启后,value 和所有类型化读取首次触发,包括返回业务默认值的情况。元数据读取与 hasValue 不触发;每次 fetch(包括缓存命中)的结果各自有一次触发状态,再经过 Client 级去重。队列拒绝或提交失败撤销去重标记,新结果或手动曝光可重试,同一个自动结果不会再次触发。
默认采集器按 100 条一批发送,不足一批最多缓冲 1 秒;同一批共用一次 GZIP/HTTP 请求。一个批次发送期间,后续曝光留在有容量限制的队列中。client.flush() 立即刷新余量,client.close() 在关闭预算内排空。复用外部采集实例时,保留该实例的批量和定时配置;官方 track() 同步返回,不逐条等待网络回调或触发 flush。
默认自建采集器直接依赖官方 npm 包 [email protected],首次曝光时惰性加载;也可通过 tdAnalytics 复用已有官方实例。SDK 只为自建实例配置传输资源、TLS、超时与关闭,不修改官方包源码。依赖与适配边界见 曝光与采集说明。
错误与关闭
fetch(request, options?) 对参数、网络和协议错误均返回 Promise rejection。FeatureError.code 区分 INVALID_CONFIG、INVALID_REQUEST、NETWORK_ERROR、HTTP_ERROR、REMOTE_ERROR、INVALID_RESPONSE、RESPONSE_TOO_LARGE、TIMEOUT、ABORTED、QUEUE_FULL、CLIENT_CLOSED、EXPOSURE_ERROR。
HTTP 错误附 httpStatus;RCC 业务错误附原始 remoteCode 和 Java 映射后的 errorCode。未知 RCC code 映射到 5000,但保留原始 code。符合 Java 兼容契约的空数据/缺失模板仍是成功的空配置,也可缓存。
clearCache() 只清结果缓存,不清曝光去重。曝光入队后由 SDK 后台发送,业务请求无需调用或等待 flush();它是测试、批处理等需要等待此前曝光处理完成时使用的可选接口。超时或适配器 flush 失败分别拒绝为 TIMEOUT、EXPOSURE_ERROR。单个曝光提交失败会撤销去重标记。
close() 停收、取消 fetch 和退避,有限等待曝光排空并关闭自建采集实例;超时丢弃待处理曝光并取消自有连接,外部采集实例仍由业务管理。方法可重复调用,关闭后不能再次 fetch。没有本地规则轮询或采集周期定时器。flush 控制槽和关闭的具体边界见 曝光说明。
Demo 与兼容验证
npm run demo
npm run demo:s3
npm run demo:s4
npm run demo:s5
npm run demo:service
npm run verify:package -- /path/to/node-v12.0.0/bin/node /path/to/another/node
npm run verify:production-install -- /path/to/node-v12.0.0/bin/node基础 Demo 展示缓存,S3 展示可靠性,S4 展示曝光;S5 自动跑 API→RCC→缓存取值→曝光→采集→回退→关闭,service 提供常驻本机 API 和 SIGTERM 关闭。默认都只使用本机 Mock。完整接入和真实模式见 指南、Demo 说明。真实 RCC、曝光入库及四进程生命周期的执行结果见 真实联调和 PM2 验收,记录保留实际验收时的版本号。
验收从同一 tarball 安装 SDK,执行支持版本矩阵、最低 Node 版本的 engine-strict 生产安装、JS/TS/ESM 消费、八个压力场景及官方采集实例验证。测试和 Demo 从仓库运行,通过 TD_FEATURE_SDK_PATH 指向已安装的 SDK,生成绑定 SHA-256 的报告。命令、指标边界和公网发布保留项见 压力验收、发布验收。没有自动发布步骤。
当前版本为 0.0.2,最低 Node.js 12.0.0,发布 ES2019 CommonJS 与 .d.ts。[email protected] 保留为生产依赖;仓库使用完整的 package-lock.json 安装开发工具;打包时自动生成只含生产依赖的 npm-shrinkwrap.json,固定支持 Node 12 的依赖树,包括 [email protected]、@dabh/[email protected]、[email protected]、[email protected] 和 [email protected]。打包结束会移除临时发布锁,保留正常的仓库 npm ci 流程。仓库和发布包均不使用 overrides,不包含 vendor 或修改过的官方源码。构建工具属于开发依赖,维护构建使用现代 Node;TLS 功能测试另需 OpenSSL CLI,消费 SDK 无需该命令。Node 12.0–12.16 的 ESM 导入需启用 --experimental-modules,CommonJS 无需该参数。
Java 固定样本已纳入测试;维护者可用 npm run fixtures:java 从同级 Java SDK 当前源码重新生成。该命令需要 JDK 17+、Python 3 和脚本列出的本地 Maven 缓存依赖,不修改 Java 项目,不访问真实 RCC。
