flagnetic

中文文档
0.1.2发布于 2026/11/04Apache-2.0TypeScriptESM + CJS零依赖构建来源已验证

Flag磁性SDK用于JavaScript和TypeScript: 功能标志和实验,自托管。

中文使用文档
文档目录11 节
  1. 服务器模式
  2. 当地的覆盖
  3. 在服务器上呈现
  4. 测试
  5. 传播
  6. 浏览器客户端
  7. 传播
  8. 反应
  9. OpenFeature
  10. 评估者
  11. 发展

用于JavaScript的磁性SDK

flagnetic 是Flagnetic的JavaScript和TypeScript SDK。一个npm包,带子路径exports、ESM和CommonJS,不依赖于运行时:

进口为
flagnetic/server节点22及更高版本,使用服务器密钥: 下载规则集,跟踪流中的更改并在本地评估标志,就像服务器一样
flagnetic/client浏览器,与客户端密钥: 远程评估,从来没有评估
flagnetic/react通过浏览器客户端进行React 18及更高版本的绑定
flagnetic/openfeature服务器和web SDK的OpenFeature提供程序

react, @openfeature/server-sdk 和 @openfeature/web-sdk 是使用它们的入口点的可选对等依赖;所有其他入口点都没有它们。

npm install flagnetic

支持: 节点22及更高版本 (releases节点仍然支持; 在次要版本中删除已达到生命周期的版本),运行ES2022的浏览器,React 18及更高版本, @openfeature/server-sdk 1.23以后, @openfeature/web-sdk 1.10及更新版本。该包使用SDK API /sdk/v1 并适用于从0.1.0开始的每个flagmagnetic服务器(兼容性); docs/sdks/javascript.md 如何选择和部署每个入口点。

服务器模式

import { createServer } from "flagnetic/server";

const flagnetic = createServer(process.env.FLAGNETIC_SDK_KEY!, "https://flags.example.com");

// Wait at most two seconds for the first ruleset; it never rejects and
// tells the stage reached: "fresh", "last_good" (from the store) or "built_in".
await flagnetic.ready(2000);

const context = { units: { user: "u-1001" }, attributes: { country: "DE" } };
if (flagnetic.getBool(context, "new_checkout", false)) {
  // ...
}

// Why a value was served.
const detail = flagnetic.getDetail(context, "banner_text", "Welcome");
// detail.value, .variation, .reason, .errorCode, .rulesetVersion, .stage, .experiment

flagnetic.exposure(context, "banner_text"); // an experiment's exposure is explicit
flagnetic.alias({ fromUnitType: "anonymous", fromUnitId: "d-8f1c", toUnitType: "user", toUnitId: "u-1001" });

await flagnetic.close(); // flushes what is queued, within 5 seconds

createServer 拒绝客户端密钥(fn_cli_…),它属于 flagnetic/client;网络麻烦永远不会让它抛出。getter返回您的内置默认值,而不是每当标志未知,具有另一种类型或无法评估时抛出。该规则集是通过以下方式下载的 If-None-Match,默认情况下,流被保留,并且在p99的2秒内到达客户端;流下降时,客户端每30秒轮询一次,流上升时每300秒轮询一次。一个 store 选项命名一个文件,该文件在重新启动时保留最后一个好的规则集。

选项: pollInterval, pollIntervalWithoutStream, stream, requestTimeout, store, logger, allowOverrides, sendSummaries, sendExposures, flushInterval, summaryInterval, platform, appVersion 和 offline. 请求携带 User-Agent: flagnetic-js/<version> (<platform>)。

onChange(listener) 和 onStage(listener) 返回一个删除侦听器的函数。 stage 和 status() 报告回退链的位置。

当地的覆盖

const flagnetic = createServer(key, url, { allowOverrides: true });
flagnetic.overrides.set("new_checkout", true); // reads as reason "local_override"

overrides.set 抛出 LocalOverridesNotAllowed 没有 allowOverrides 和 LocalOverrideRefused 而规则集属于受保护的环境。两个日志 override_refused。

在服务器上呈现

flagnetic.values(context, { flags? }) 将上下文的标志计算到浏览器客户端作为它的文档中 bootstrap. 它被掩盖为客户端键的答案是:目标决定值的原因是什么 targeting_match 并且没有命名规则,因为文档被写入任何人都可以阅读的页面。

测试

import { testServer, testServerFromValues } from "flagnetic/server";

const flagnetic = testServerFromValues({ new_checkout: true, limit: 10 });
flagnetic.set("new_checkout", false); // rewrite a value; change listeners fire
flagnetic.reload();                   // back to what the client was built from
flagnetic.recorder.exposures();       // exposures and aliases that would have been sent

testServer(document) 从规则集文档构建相同的脱机客户机。两者都不发出网络请求。

传播

在flagile中进行的更改在流启动时在p99的2秒内到达服务器客户端,否则在轮询间隔内。

浏览器客户端

import { anonymousId, createClient } from "flagnetic/client";

const flagnetic = createClient("fn_cli_…", "https://flags.example.com", {
  context: { units: { anonymous: anonymousId() }, attributes: { country: "DE" } },
});

// Values serve at once: the bootstrap document, else the last good values
// this browser kept for the key and the context's units, else your defaults.
await flagnetic.ready(2000); // "fresh", "last_good" or "built_in"; never rejects

if (flagnetic.getBool("new_checkout", false)) {
  // ...
}

// After sign-in: resolves once the new context's values arrived, while the
// previous values keep serving, so there is no flash of defaults.
await flagnetic.setContext({ units: { anonymous: anonymousId(), user: "u-1001" } });

createClient 拒绝服务器密钥(fn_srv_…),绝不能捆绑发货。客户端要求服务器评估其上下文,并且从不包含评估器,因此 flagnetic/client 保持在CI中检查的gzip大小预算内。它通过上下文将自己命名给服务器 sdk, sdk_version 和 platform 属性,因为浏览器不能设置 User-Agent。

最后一个好的值保存在 localStorage 在SDK密钥下和上下文的单元(在内存中 localStorage 不能到达的,不能到达的 store: false),因此下一页加载在任何请求之前为它们提供服务,并且一个浏览器中的两个键永远不会看到彼此的值。一个 bootstrap 选项接受文档 values(context) 的 flagnetic/server 呈现到页面中,作为 fresh;一个 offline 选项接受一个值文档,并且根本不发出任何请求 offline.set, offline.reload 和 offline.recorded() 用于测试。

当选项卡变得可见或网络恢复时,客户端重新获取,最多每10秒一次,并且在选项卡可见时每300秒轮询一次。与 stream: true 它还保存更改流,读取 fetch 这样它就可以发送它的密钥和 Last-Event-ID 在报头和重新连接自己;默认情况下是关闭的,因为每个打开的选项卡都有一个连接。

anonymousId(unitType = "anonymous") 为每个单元类型的浏览器提供一个ID:在第一次调用时生成并保存在其中的随机uidv7 localStorage 对于每一个后来的访问的起源。它存在于密钥的存储之外,因此旋转或替换的客户端密钥将实验中的每个匿名访问者的ID桶保存到其中; 其中 localStorage 无法到达,则该页的ID保留。传递项目为匿名流量声明的单元类型,并在登录后将ID保留在上下文的单元中。

风险、别名和评估摘要在队列中 localStorage 下键和发送每5秒,而TAB是可见的; flush() 和 close() 在5秒内发送。具有相同键的一个源的选项卡共享队列,因此关闭的选项卡剩下的内容将由它们中的任何一个的下一批发送。当选项卡被隐藏或页面被关闭时,客户端将队列交给 navigator.sendBeacon,浏览器在页面消失后传递该密钥:客户端密钥作为 sdk_key 查询参数和信封为 text/plain,因此不需要CORS预飞行,并且不符合浏览器的64 KiB信标配额的内容将在下一次访问时保持排队。选项: context, bootstrap, offline, store, stream, pollInterval, requestTimeout, flushInterval, sendExposures, sendSummaries, allowOverrides, logger, platform 和 appVersion。吸气剂, getDetail, stage, status(), onChange, onStage, overrides, exposure(key) 和 alias 在服务器模式下工作,没有context参数。

传播

在fllagnetwork中所做的更改最迟在300秒的轮询后到达浏览器客户端,而在选项卡可见时,在选项卡再次可见或网络返回时立即到达浏览器客户端,并且在流打开的几秒钟内。

反应

import { FlagneticProvider, useFlag, useFlagDetail, useStage, useFlagnetic } from "flagnetic/react";

function App({ bootstrap }: { bootstrap?: object }) {
  return (
    <FlagneticProvider sdkKey="fn_cli_…" baseUrl="https://flags.example.com"
                       options={{ context: { units: { user: "u-1001" } } }}
                       bootstrap={bootstrap}>
      <Checkout />
    </FlagneticProvider>
  );
}

function Checkout() {
  const enabled = useFlag("new_checkout", false);  // re-renders when this flag's value changes
  const detail = useFlagDetail("banner_text", "Welcome"); // value, reason, stage, experiment
  const stage = useStage();                         // "built_in", "last_good" or "fresh"
  const client = useFlagnetic();                    // setContext, exposure, alias; undefined until mounted
  // ...
}

提供程序采用构建应用程序的客户端 createClient,它永远不会关闭,或者 sdkKey, baseUrl, options 和 bootstrap 建造并拥有一个。当提供程序挂载时,这四个字只读取一次,因此JSX中的对象字面值不会重新构建客户端;给提供者一个 key 重新开始.钩子通过订阅 useSyncExternalStore更改会重新呈现读取已更改标志的组件,而不是其他组件,并且会抛出提供商外部的钩子。

要在服务器上呈现,请传递文档 values(context) 的 flagnetic/server 作为 bootstrap,并将相同的文档写入浏览器的页面。当页面在服务器上呈现并且水合时,钩子为它服务,因此浏览器中的第一个呈现与HTML匹配; 提供者在水合之后从同一文档构建其客户端,然后发生第一个远程评估。当页面在服务器上呈现时,不会获取任何内容。一个 bootstrap 的 undefined,什么 values() 在服务器模式具有规则集之前给出,在阶段呈现内置默认值 built_in 两边都是。

OpenFeature

flagnetic/openfeature 每个OpenFeature SDK都有一个提供程序: FlagneticServerProvider 为 @openfeature/server-sdk 1.23及更高版本的Node FlagneticWebProvider 为 @openfeature/web-sdk 在浏览器中1.10和更高版本。安装你方的SDK; 入口点在运行时不加载。

import { OpenFeature } from "@openfeature/server-sdk";
import { FlagneticServerProvider } from "flagnetic/openfeature";

await OpenFeature.setProviderAndWait(new FlagneticServerProvider(process.env.FLAGNETIC_SDK_KEY!, "https://flags.example.com"));
const client = OpenFeature.getClient();

const context = { targetingKey: "u-1001", country: "DE", units: { anonymous: "d-8f1c" } };
const enabled = await client.getBooleanValue("new_checkout", false, context);

client.track("exposure", context, { flag: "pricing" }); // an experiment's exposure is explicit
client.track("alias", context, { fromUnitType: "anonymous", fromUnitId: "d-8f1c", toUnitType: "user", toUnitId: "u-1001" });
import { OpenFeature } from "@openfeature/web-sdk";
import { FlagneticWebProvider } from "flagnetic/openfeature";

await OpenFeature.setProviderAndWait(new FlagneticWebProvider("fn_cli_…", "https://flags.example.com"), { targetingKey: "u-1001" });
const enabled = OpenFeature.getClient().getBooleanValue("new_checkout", false);
await OpenFeature.setContext({ targetingKey: "u-2002" }); // evaluated for the new context

每个提供者构建并拥有其模式的客户端, provider.client,并采用该模式的选项加上两个自己的选项: unitType,目标键是()的单位ID。user 默认情况下),以及 readyTimeout多长时间? initialize 等待第一个规则集或值 (服务器上的10秒,浏览器中的5秒)。 initialize 从不失败:在舞台上 built_in 每一个吸气剂服务你的默认。上下文属性 units 按单位类型添加单位id;所有其他属性都是一个属性,a Date 作为RFC 3339字符串发送。web提供程序评估OpenFeature的上下文,因此它不需要 context 选择。

原因映射为 reasonOf 确实,每个fl磁性提供商都一样: rule, prerequisite_failed, override 和 targeting_match 是 TARGETING_MATCH, rollout 和 experiment SPLIT, fallthrough DEFAULT, off DISABLED, local_override STATIC,以及新服务器添加的一个原因 UNKNOWN. 而提供的内置默认值是 ERROR 与 FLAG_NOT_FOUND, TYPE_MISMATCH 或 GENERAL,flor磁性错误代码作为消息。旗标元数据承载着飘扬的原因 REASON_METADATA_KEY (flagnetic_reason), 与 stage, ruleset_version, variation, overridden 还有一个实验 experiment_key, experiment_variation, experiment_unit_type, experiment_unit_id, experiment_inclusion_bucket, experiment_variation_bucket 和 experiment_override。

track("exposure") 报告了由 flag,用于上下文(浏览器中的当前上下文),为 exposure 做; track("alias") 记录别名。任何其他事件名称,或者缺少所需内容的事件名称,都不起任何作用。

事件: PROVIDER_CONFIGURATION_CHANGED 用可能已经改变的标志的键; PROVIDER_STALE 当客户端移动到 last_good; PROVIDER_READY 当它再次新鲜时; PROVIDER_ERROR 当SDK密钥被撤销时,一直保留到密钥再次被接受为止。OpenFeature报告 READY 一次 initialize 或上下文更改解决,无论达到什么; 在它之后,提供者发送 ERROR 或 STALE 当客户站在那里的时候。

评估者

import { decodeRuleset } from "flagnetic/server";

const ruleset = decodeRuleset(document); // a ruleset document, as JSON text, bytes or a parsed value
const result = ruleset.evaluate("new_checkout", { units: { user: "u-1001" }, attributes: { country: "DE" } });

的正则表达式 matches 条件在包的线性时间RE2引擎上运行,因此模式意味着它在服务器上的含义,属性值不会使进程停滞。来自比包更新的服务器的规则集可能包含计算器不知道的操作符、条件类型或服务类型; ruleset.unsupportedRules() 报告他们的标志,并评估这样一个标志的答案 error 与 inconsistent_ruleset。

发展

pnpm install
pnpm lint && pnpm typecheck
pnpm test            # the shared conformance suite and unit tests, in Node
pnpm test:browser    # the same suite in Chromium (pnpm exec playwright install chromium once)
pnpm test:browsers   # the browser client cross-origin and the React bindings in Chromium, Firefox and WebKit (FLAGNETIC_BROWSERS=webkit for one)
pnpm test:p99        # 1,000 flags evaluate under 1 ms at p99
pnpm build && pnpm check:package && pnpm check:size
pnpm test:e2e        # the smokes in Node and Chromium against two real replicas (below)

烟需要端到端的控制 e2e/ 运行: 开始 flagnetic-e2e up 与 FLAGNETIC_E2E_SEED 命名一个文件,然后运行 pnpm test:e2e 用相同的 FLAGNETIC_E2E_SEED 而且,除非 flagnetic-e2e 在……上 PATH, FLAGNETIC_E2E_CMD 命名它。它们以撤销种子的密钥而结束,因此在第二次运行之前再次提起线束。

正则表达式引擎的Unicode类表是从Go的类表生成的 regexp/syntax;在Go升级运行之后 pnpm gen:unicode,并且当它们陈旧时CI失败。

版本与发布记录

flagnetic 共发布 4 个版本,首次发布于 2026/10/27;过去 12 个月发布了 4 个版本。最新版本附带 npm provenance 构建来源证明,可追溯到公开的 CI 构建记录。

版本发布日期距今
0.1.2latest2026/11/04今天
0.1.12026/10/295 天前
0.1.02026/10/277 天前
0.0.0-stage2026/10/277 天前

关于 flagnetic 的常见问题

flagnetic 是什么?

Flag磁性SDK用于JavaScript和TypeScript: 功能标志和实验,自托管。

如何安装 flagnetic?

在项目目录运行 npm install flagnetic,或使用 pnpm add / yarn add / bun add。

flagnetic 支持 TypeScript 吗?

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

flagnetic 支持 ESM 还是 CommonJS?

同时提供 ES Module 与 CommonJS 入口,import 和 require() 都可以使用,打包工具会自动选择合适的版本。

flagnetic 需要什么版本的 Node.js?

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

flagnetic 可以商用吗?

flagnetic 使用 Apache-2.0 许可。宽松许可:允许商业使用、修改和再分发,并附带明确的专利授权;分发时需保留许可与 NOTICE 文件,修改过的文件要注明改动。以上为概括,不构成法律意见,请以许可原文为准。

flagnetic 还在维护吗?

最新版本 0.1.2 发布于 2026/11/04(今天);至今共发布 4 个版本,其中过去 12 个月发布了 4 个;首次发布于 2026/10/27。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。