diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..d37f11e --- /dev/null +++ b/.gitattributes @@ -0,0 +1,3 @@ +/previews/sdk-versioning/v*/@reflag/** linguist-generated=true +/previews/sdk-versioning/v*/documents/** linguist-generated=true +/previews/sdk-versioning/v*/generated-manifest.json linguist-generated=true diff --git a/.gitbook.yaml b/.gitbook.yaml new file mode 100644 index 0000000..028c255 --- /dev/null +++ b/.gitbook.yaml @@ -0,0 +1,6 @@ +# Single-space Git Sync fallback for this SDK preview branch. +# Site-wide Git Sync uses gitbook-docs.yaml and each mapped space's config. +root: ./previews/sdk-versioning/v2/ +structure: + readme: README.md + summary: SUMMARY.md diff --git a/.github/scripts/import-sdk-docs.mjs b/.github/scripts/import-sdk-docs.mjs new file mode 100644 index 0000000..0f48ff4 --- /dev/null +++ b/.github/scripts/import-sdk-docs.mjs @@ -0,0 +1,147 @@ +import assert from "node:assert/strict"; +import { execFileSync } from "node:child_process"; +import { createHash } from "node:crypto"; +import fs from "node:fs"; +import path from "node:path"; +import { pathToFileURL } from "node:url"; +import { parseArgs } from "node:util"; + +export const packages = ["node-sdk", "browser-sdk", "react-sdk", "vue-sdk"]; +const hash = (content) => createHash("sha256").update(content).digest("hex"); +const read = (root, file) => fs.readFileSync(path.join(root, file), "utf8"); + +function banner(content, message) { + const note = `{% hint style="info" %}\n${message}\n{% endhint %}\n\n`; + if (content.startsWith("---\n")) { + const end = content.indexOf("\n---\n", 4); + assert(end >= 0, "Unclosed generated frontmatter"); + return content.slice(0, end + 5) + "\n" + note + content.slice(end + 5).trimStart(); + } + return note + content; +} + +export function planImport({ source, major, commit }) { + assert(["1", "2"].includes(String(major)), "Select SDK major 1 or 2 explicitly"); + assert(/^[a-f\d]{40}$/.test(commit), "A full source commit is required"); + const files = new Map(); + const versions = {}; + for (const name of packages) { + const metadata = JSON.parse(read(source, `packages/${name}/package.json`)); + assert.equal(metadata.name, `@reflag/${name}`); + assert.equal(metadata.version.split(".")[0], String(major), `Source major mismatch: ${name}`); + versions[metadata.name] = metadata.version; + const status = + String(major) === "1" + ? "Legacy SDK v1." + : "SDK v2 candidate documentation, not a published release. Installation commands target the planned major; matching SDK/CLI releases are still required."; + const message = `${status} Generated from \`${metadata.name}@${metadata.version}\` in [the JavaScript repository](https://github.com/reflagcom/javascript/tree/${commit}/packages/${name}). SDK READMEs and code comments there remain authoritative.`; + for (const filename of ["README.md", "globals.md"]) { + const relative = `@reflag/${name}/${filename}`; + let content = read(source, `dist/docs/${relative}`); + if (filename === "README.md") { + // TypeDoc copies linked READMEs to a shared _media/README.md, which can + // collide with Native's linked README. Route the original source link. + const original = read(source, `packages/${name}/README.md`); + if (/\]\(\.\.\/browser-sdk\/README\.md\)/.test(original)) { + content = content.replaceAll("../../_media/README.md", "../browser-sdk/README.md"); + } + content = content + .split("\n") + .map((line) => { + if (!/^\s*(?:npm (?:i|install)|yarn add|pnpm add)\b/.test(line)) return line; + return line.replace( + /@reflag\/(?:node-sdk|browser-sdk|react-sdk|vue-sdk)(?![\w/@-])/g, + `$&@${major}`, + ); + }) + .join("\n"); + } + if (filename === "globals.md") { + // The canonical docs.sh strips synthetic property-anchor suffixes; + // normalize every link, including several links on the same table row. + content = content.replace(/(globals\.md#[\w-]+?)-\d+(?=[)"\s])/g, "$1"); + content = content.replace(/```[^\n]*\n[\s\S]*?```/g, (block) => + block.replace(/[ \t]+$/gm, ""), + ); + } + assert(!content.includes("_media/README.md"), `Unmapped README attachment: ${relative}`); + files.set(relative, Buffer.from(banner(content, message))); + } + } + files.set( + "documents/browser-sdk/FEEDBACK.md", + Buffer.from(read(source, "dist/docs/documents/browser-sdk/FEEDBACK.md")), + ); + // Only copy media actually referenced by these generated pages, never the + // mixed all-package index or independent Native/Management/CLI documentation. + // Snapshot Markdown pages before appending binary media to the output map. + const pages = [...files.values()]; + for (const content of pages) { + for (const match of content.toString().matchAll(/(?:\.\.\/)+_media\/([^\s)"#]+)(?:[)#"\s])/g)) { + const name = decodeURIComponent(match[1]); + assert(!name.includes("..") && !path.isAbsolute(name), "Unsafe media path"); + const relative = `_media/${name}`; + files.set(relative, fs.readFileSync(path.join(source, "dist/docs", relative))); + } + } + const manifest = { + status: String(major) === "1" ? "published-v1-sources" : "candidate-source-build-not-a-release", + sourceRepository: "https://github.com/reflagcom/javascript", + sourceCommit: commit, + sdkMajor: Number(major), + packages: versions, + transforms: [ + "GitBook version/source banner", + "major-pinned install commands", + "source-linked README routing", + "TypeDoc property-anchor normalization", + "TypeDoc code-block trailing whitespace", + ], + files: Object.fromEntries([...files].map(([name, content]) => [name, hash(content)])), + }; + return { files, manifest }; +} + +export function applyImport(plan, destination, { check = false } = {}) { + const files = new Map(plan.files); + files.set("generated-manifest.json", Buffer.from(`${JSON.stringify(plan.manifest, null, 2)}\n`)); + for (const [relative, content] of files) { + const target = path.join(destination, relative); + if (check) + assert(fs.readFileSync(target).equals(content), `Generated SDK docs differ: ${relative}`); + else { + fs.mkdirSync(path.dirname(target), { recursive: true }); + fs.writeFileSync(target, content); + } + } + // Landing, navigation and .gitbook.yaml are separately owned; never delete + // directories or touch the original sdk/ tree or other versions. +} + +if (process.argv[1] && import.meta.url === pathToFileURL(path.resolve(process.argv[1])).href) { + const { values } = parseArgs({ + options: { + source: { type: "string" }, + destination: { type: "string" }, + major: { type: "string" }, + check: { type: "boolean", default: false }, + }, + }); + assert( + values.source && values.destination && values.major, + "Use --source --destination --major 1|2 [--check]", + ); + const source = path.resolve(values.source); + const git = (...args) => + execFileSync("git", ["-C", source, ...args], { encoding: "utf8" }).trim(); + assert.equal( + git("status", "--porcelain", "--untracked-files=no"), + "", + "Use a clean SDK source checkout", + ); + const plan = planImport({ source, major: values.major, commit: git("rev-parse", "HEAD") }); + applyImport(plan, path.resolve(values.destination), { check: values.check }); + console.log( + `${values.check ? "Verified" : "Imported"} ${plan.files.size} generated files for SDK v${values.major} from ${plan.manifest.sourceCommit}.`, + ); +} diff --git a/.github/scripts/import-sdk-docs.test.mjs b/.github/scripts/import-sdk-docs.test.mjs new file mode 100644 index 0000000..a173384 --- /dev/null +++ b/.github/scripts/import-sdk-docs.test.mjs @@ -0,0 +1,77 @@ +import assert from "node:assert/strict"; +import fs from "node:fs"; +import os from "node:os"; +import path from "node:path"; +import test from "node:test"; +import { applyImport, packages, planImport } from "./import-sdk-docs.mjs"; + +function fixture(t) { + const root = fs.mkdtempSync(path.join(os.tmpdir(), "reflag-doc-import-")); + t.after(() => fs.rmSync(root, { recursive: true, force: true })); + const source = path.join(root, "source"), + destination = path.join(root, "v2"); + const put = (base, name, content) => { + const file = path.join(base, name); + fs.mkdirSync(path.dirname(file), { recursive: true }); + fs.writeFileSync(file, content); + }; + for (const name of packages) { + put( + source, + `packages/${name}/package.json`, + JSON.stringify({ name: `@reflag/${name}`, version: "2.0.0-next.0" }), + ); + put(source, `packages/${name}/README.md`, "# SDK\n[Browser](../browser-sdk/README.md)\n"); + put( + source, + `dist/docs/@reflag/${name}/README.md`, + "# SDK\n[Browser](../../_media/README.md)\nnpm install @reflag/react-sdk\n", + ); + put(source, `dist/docs/@reflag/${name}/globals.md`, "# Reference\n"); + } + put(source, "dist/docs/documents/browser-sdk/FEEDBACK.md", "# Feedback\n"); + put(source, "dist/docs/README.md", "Mixed unpublished/independent package index"); + return { source, destination, put, options: { source, major: "2", commit: "a".repeat(40) } }; +} + +test("imports nine generated pages without overwriting landing/navigation/other versions", (t) => { + const { destination, put, options } = fixture(t); + put(destination, "README.md", "handwritten landing"); + put(destination, "SUMMARY.md", "navigation"); + const plan = planImport(options); + applyImport(plan, destination); + applyImport(plan, destination, { check: true }); + assert.equal(plan.files.size, 9); + assert.equal(fs.readFileSync(path.join(destination, "README.md"), "utf8"), "handwritten landing"); + assert.equal(fs.readFileSync(path.join(destination, "SUMMARY.md"), "utf8"), "navigation"); + assert(!fs.existsSync(path.join(destination, "@reflag/management-sdk"))); +}); +test("generated README links point at canonical SDK pages instead of shared attachments", (t) => { + const { options } = fixture(t); + const plan = planImport(options); + assert.match( + plan.files.get("@reflag/react-sdk/README.md").toString(), + /\(\.\.\/browser-sdk\/README.md\)/, + ); + assert.match( + plan.files.get("@reflag/react-sdk/README.md").toString(), + /npm install @reflag\/react-sdk@2/, + ); +}); +test("wrong source majors fail before writes", (t) => { + const { destination, options } = fixture(t); + assert.throws(() => planImport({ ...options, major: "1" }), /Source major mismatch/); + assert(!fs.existsSync(destination)); +}); +test("missing feedback fails the complete plan", (t) => { + const { source, options } = fixture(t); + fs.unlinkSync(path.join(source, "dist/docs/documents/browser-sdk/FEEDBACK.md")); + assert.throws(() => planImport(options), /ENOENT/); +}); +test("changed generated output fails reproducibility checking", (t) => { + const { destination, put, options } = fixture(t); + const plan = planImport(options); + applyImport(plan, destination); + put(destination, "@reflag/node-sdk/globals.md", "manual edit"); + assert.throws(() => applyImport(plan, destination, { check: true }), /Generated SDK docs differ/); +}); diff --git a/.github/workflows/sdk-versioning-preview.yml b/.github/workflows/sdk-versioning-preview.yml new file mode 100644 index 0000000..14f99f1 --- /dev/null +++ b/.github/workflows/sdk-versioning-preview.yml @@ -0,0 +1,74 @@ +name: SDK versioning preview + +on: + pull_request: + paths: + - previews/sdk-versioning/** + - gitbook-docs.yaml + - .gitbook.yaml + - .gitattributes + - .github/scripts/** + - .github/workflows/sdk-versioning-preview.yml + +permissions: + contents: read + +jobs: + check: + runs-on: ubuntu-latest + timeout-minutes: 20 + steps: + - uses: actions/checkout@v4 + with: + persist-credentials: false + - uses: actions/setup-node@v4 + with: + node-version: 24 + - run: corepack enable + - name: Install preview checker + working-directory: previews/sdk-versioning + run: yarn install --immutable + - name: Check GitBook mappings, generated provenance, navigation and negative controls + working-directory: previews/sdk-versioning + run: yarn test + - name: Check out published v1 source + uses: actions/checkout@v4 + with: + repository: reflagcom/javascript + ref: b4fe5b3d9386b87c6546175c323862d9878e1430 + path: .sdk-v1 + persist-credentials: false + - name: Rebuild published v1 declarations and docs + working-directory: .sdk-v1 + run: | + yarn install --immutable + yarn workspace @reflag/browser-sdk build + yarn workspace @reflag/react-sdk build + yarn workspace @reflag/node-sdk build + yarn workspace @reflag/vue-sdk build + # This published source's script declares /bin/sh but contains [[. + yarn exec bash ./docs.sh + - name: Check out the exact v2 candidate source + uses: actions/checkout@v4 + with: + repository: reflagcom/javascript + ref: 9f3a0ae027e386747a2d4cf7683a68dcab70b355 + path: .sdk-candidate + persist-credentials: false + - name: Rebuild candidate declarations and docs + working-directory: .sdk-candidate + run: | + yarn install --immutable + yarn workspace @reflag/flag-evaluation build + yarn workspace @reflag/node-sdk build + yarn workspace @reflag/browser-sdk build + yarn workspace @reflag/react-sdk build + yarn workspace @reflag/vue-sdk build + yarn exec bash ./docs.sh + - name: Verify generated pages against both source checkouts + run: | + node .github/scripts/import-sdk-docs.mjs --source .sdk-v1 --destination previews/sdk-versioning/v1 --major 1 --check + node .github/scripts/import-sdk-docs.mjs --source .sdk-candidate --destination previews/sdk-versioning/v2 --major 2 --check + - name: Compile six smoke examples against matching v1 and v2 declarations + working-directory: previews/sdk-versioning + run: yarn check:examples "$GITHUB_WORKSPACE/.sdk-candidate" "$GITHUB_WORKSPACE/.sdk-v1" diff --git a/gitbook-docs.yaml b/gitbook-docs.yaml new file mode 100644 index 0000000..9da7d00 --- /dev/null +++ b/gitbook-docs.yaml @@ -0,0 +1,38 @@ +# SDK preview branch mapping, not a production-site migration. +# These are the SAME identity keys as the existing nested preview project. +# Do not merge/apply to the live site without preserving its production keys. +$schema: https://api.gitbook.com/gitbook-docs.yaml +site: + title: Reflag docs — SDK versioning preview + structure: + - type: section + key: sdk-preview-javascript + title: JavaScript SDKs + path: sdk + default: true + children: + - type: space + key: sdk-preview-v2 + title: v2 (candidate) + path: v2 + default: true + content: + directory: ./previews/sdk-versioning/v2 + - type: space + key: sdk-preview-v1 + title: v1 (legacy) + path: v1 + content: + directory: ./previews/sdk-versioning/v1 + - type: section + key: sdk-preview-guides + title: Shared docs + path: guides + children: + - type: space + key: sdk-preview-shared + title: Shared docs + path: current + default: true + content: + directory: ./previews/sdk-versioning/shared diff --git a/previews/sdk-versioning/.gitbook.yaml b/previews/sdk-versioning/.gitbook.yaml new file mode 100644 index 0000000..3dc9082 --- /dev/null +++ b/previews/sdk-versioning/.gitbook.yaml @@ -0,0 +1,5 @@ +# Single-space preview fallback; native variants use the site mapping instead. +root: ./v2/ +structure: + readme: README.md + summary: SUMMARY.md diff --git a/previews/sdk-versioning/.gitignore b/previews/sdk-versioning/.gitignore new file mode 100644 index 0000000..9fca6de --- /dev/null +++ b/previews/sdk-versioning/.gitignore @@ -0,0 +1,3 @@ +node_modules/ +.yarn/ +.pnp.* diff --git a/previews/sdk-versioning/.yarnrc.yml b/previews/sdk-versioning/.yarnrc.yml new file mode 100644 index 0000000..3186f3f --- /dev/null +++ b/previews/sdk-versioning/.yarnrc.yml @@ -0,0 +1 @@ +nodeLinker: node-modules diff --git a/previews/sdk-versioning/README.md b/previews/sdk-versioning/README.md new file mode 100644 index 0000000..4cb858d --- /dev/null +++ b/previews/sdk-versioning/README.md @@ -0,0 +1,74 @@ +# Generated SDK versioning preview + +This branch now contains the **full generated Node, Browser, React and Vue SDK documentation** for v1 and the v2 candidate, rather than the earlier React-only samples. SDK READMEs and source comments in **[reflagcom/javascript](https://github.com/reflagcom/javascript)** remain authoritative. Do not hand-edit generated SDK pages here. + +This is an SDK preview, **not the complete production-site migration**. Existing product guides, original generated `sdk/` content and production navigation are not moved into this project. Do not merge its site mapping into production without preserving production identities and completing the shared-content/routing migration. + +## GitBook configuration + +The same preview structure is available in two project locations: + +| Git Sync Project directory | Site mapping | +| -------------------------- | ---------------------------------------------------------------------------------- | +| Repository root | `/gitbook-docs.yaml` maps the `previews/sdk-versioning/{v1,v2,shared}` directories | +| `previews/sdk-versioning` | Its existing `gitbook-docs.yaml` maps `./v1`, `./v2`, `./shared` | + +Both mappings retain the **same existing `sdk-preview-*` identity keys**. The JavaScript SDKs section has native **v2 (candidate)** and **v1 (legacy)** variants; the separate shared sample has no SDK version picker. Each space has its own `.gitbook.yaml` and complete `SUMMARY.md`. Package overview/reference paths align across versions. + +For **individual-space Git Sync**, both project locations also have a `.gitbook.yaml` pointing to the complete generated v2 space. This makes actual SDK content visible when a space syncs this branch from the repository root. **Individual-space sync does not create a native variant selector**: that requires site-wide Git Sync/content mapping or variants configured in GitBook. No manual Markdown selector is being presented as a native one. + +This PR does not create sites, configure Git Sync, publish, change the live default, or prove that PR previews isolate site-wide settings. The live Reflag space observed during preparation still reported `main`/individual-space sync; it was not changed. Check the intended preview's branch/project/scope if the variant picker is absent. + +**Production identity warning:** these are preview identities, not replacements for live keys. GitBook matches spaces by `key`, not title/path/directory. Changing a live key replaces its space; changing it back does not restore the original. Export and preserve production keys, space IDs, content and URLs before any migration. + +References: [content configuration](https://gitbook.com/docs/docs-as-code/git-sync/content-configuration), [monorepos](https://gitbook.com/docs/docs-as-code/git-sync/monorepos), [native variants](https://gitbook.com/docs/manage-your-site/site-structure/variants). + +## Source versions and generated ownership + +| Variant | Source checkout | Package versions | +| ------- | ------------------------------------------ | ------------------------------------------ | +| v1 | `b4fe5b3d9386b87c6546175c323862d9878e1430` | Node 1.8.3; Browser/React 1.7.4; Vue 1.5.7 | +| v2 | `9f3a0ae027e386747a2d4cf7683a68dcab70b355` | Node/Browser/React/Vue 2.0.0-next.0 | + +The v1 checkout is Node's published `@reflag/node-sdk@1.8.3` tag. Browser/React/Vue package trees were verified identical to their respective published tags at `70b7fbe7e65e95e7bce5ccad64400b0c3e06f63c`. V2 is an **unreleased source candidate**, not a packed or deployed acceptance claim. Native/OpenFeature/Management are not included in this coordinated selector. CLI 2's separate local work is not presented as released documentation. + +Each variant includes four generated README/reference pairs, Browser feedback, and referenced media. `generated-manifest.json` records source commit, actual package versions and output hashes. The importer copies output from the JavaScript repository's canonical `docs.sh` build and only applies version/source banners, major-pinned installation commands, README-link routing, the existing TypeDoc property-anchor normalization and trailing-whitespace cleanup inside generated code blocks. It never copies the mixed all-package index, deletes content, or overwrites navigation, landing pages, another variant or the original `sdk/` tree. + +## Reproduce and check + +Build each **clean** source checkout with its own immutable dependencies. For v1: + +```sh +yarn install --immutable +yarn workspace @reflag/browser-sdk build +yarn workspace @reflag/react-sdk build +yarn workspace @reflag/node-sdk build +yarn workspace @reflag/vue-sdk build +yarn exec bash ./docs.sh +``` + +For v2, first build `@reflag/flag-evaluation`, then Node, Browser, React and Vue, followed by `yarn exec bash ./docs.sh`. Bash is explicit because the published v1 source declares `/bin/sh` but contains `[[`. + +From the docs repository root, regenerate only the owned copies: + +```sh +node .github/scripts/import-sdk-docs.mjs --source /path/to/built-v1 --destination previews/sdk-versioning/v1 --major 1 +node .github/scripts/import-sdk-docs.mjs --source /path/to/built-v2 --destination previews/sdk-versioning/v2 --major 2 +``` + +Add `--check` to compare without writing. Then: + +```sh +cd previews/sdk-versioning +yarn install --immutable +yarn test +yarn check:examples /path/to/built-v2 /path/to/built-v1 +``` + +Checks validate both mappings against GitBook's published schema, matching root/nested identity maps, generated hashes, complete navigation, equivalent SDK paths, local files/assets/anchors, and negative controls. CI independently rebuilds both exact source checkouts and verifies the generated copies. Six original React smoke examples are retained under `scripts/fixtures` plus the shared sample and compiled against matching real 1.7.4/2.0.0-next.0 declarations with anti-`any`/wrapper guards. This is **not** a claim that every partial example in every generated reference was compiled independently. + +## Rendering and remaining work + +The earlier manual native fixture established equivalent-page switching and mobile picker behavior, but **does not validate rendering of this new full generated project**. Recheck after GitBook sync: Node/Browser/React/Vue overview/reference switching, feedback links/media, mobile layout and shared-section isolation. Previously observed native limitations were cleared fragments, missing pages falling back to the destination variant home, and section reentry opening the default v2 home. + +The release-scoped automatic publisher remains a separate JavaScript-repository workstream. It must retain SDK READMEs/source comments as the source of truth, use actual published packages/versions, and preserve other majors and independent SDKs. Full shared-v2 conversion, legacy routes, production identity mapping, matching SDK/CLI artifacts and server/snapshot readiness remain cutover gates. Nothing here authorizes publication or a production mapping change. diff --git a/previews/sdk-versioning/gitbook-docs.yaml b/previews/sdk-versioning/gitbook-docs.yaml new file mode 100644 index 0000000..0853efb --- /dev/null +++ b/previews/sdk-versioning/gitbook-docs.yaml @@ -0,0 +1,39 @@ +# Generated SDK v1/v2 preview, not the production site's content mapping. +# This nested project and the repository-root mapping use identical space keys. +# Do not apply these fixture keys to the existing live site: changing a space's +# key replaces its identity. Export and preserve the real site's keys first. +$schema: https://api.gitbook.com/gitbook-docs.yaml +site: + title: Reflag docs — SDK versioning preview + structure: + - type: section + key: sdk-preview-javascript + title: JavaScript SDKs + path: sdk + default: true + children: + - type: space + key: sdk-preview-v2 + title: v2 (candidate) + path: v2 + default: true + content: + directory: ./v2 + - type: space + key: sdk-preview-v1 + title: v1 (legacy) + path: v1 + content: + directory: ./v1 + - type: section + key: sdk-preview-guides + title: Shared docs + path: guides + children: + - type: space + key: sdk-preview-shared + title: Shared docs + path: current + default: true + content: + directory: ./shared diff --git a/previews/sdk-versioning/package.json b/previews/sdk-versioning/package.json new file mode 100644 index 0000000..c374cff --- /dev/null +++ b/previews/sdk-versioning/package.json @@ -0,0 +1,18 @@ +{ + "name": "@reflag/docs-sdk-versioning-preview", + "private": true, + "scripts": { + "check": "node scripts/check-layout.mjs", + "test": "yarn check && node --test scripts/check-layout.test.mjs ../../.github/scripts/import-sdk-docs.test.mjs", + "check:examples": "node scripts/check-examples.cjs" + }, + "devDependencies": { + "ajv": "8.20.0", + "ajv-formats": "3.0.1", + "yaml": "2.9.1" + }, + "engines": { + "node": ">=24" + }, + "packageManager": "yarn@4.10.3" +} diff --git a/previews/sdk-versioning/scripts/check-examples.cjs b/previews/sdk-versioning/scripts/check-examples.cjs new file mode 100644 index 0000000..cf0618b --- /dev/null +++ b/previews/sdk-versioning/scripts/check-examples.cjs @@ -0,0 +1,124 @@ +const assert = require("node:assert/strict"); +const fs = require("node:fs"); +const os = require("node:os"); +const path = require("node:path"); +const { createRequire } = require("node:module"); + +const sdkRoot = process.argv[2] && path.resolve(process.argv[2]); +const legacyRoot = process.argv[3] && path.resolve(process.argv[3]); +if (!sdkRoot || !legacyRoot) + throw new Error("Usage: yarn check:examples /path/to/built-v2-sdk /path/to/built-v1-sdk"); +const sdkRequire = createRequire(path.join(sdkRoot, "package.json")); +const ts = sdkRequire("typescript"); +const reactTypes = path.dirname(sdkRequire.resolve("@types/react/package.json")); +const project = path.resolve(__dirname, ".."); + +function sdkPackage(base, name) { + const requireFrom = createRequire(path.join(base, "package.json")); + let dir = path.dirname(requireFrom.resolve(name)); + for (;;) { + const file = path.join(dir, "package.json"); + if (fs.existsSync(file)) { + const pkg = JSON.parse(fs.readFileSync(file, "utf8")); + if (pkg.name === name) { + const types = path.join(dir, pkg.types); + assert(fs.existsSync(types), `Build declarations first: ${name}`); + return { dir, version: pkg.version, types }; + } + } + const parent = path.dirname(dir); + if (parent === dir) throw new Error(`Package not found: ${name}`); + dir = parent; + } +} + +const temporary = fs.mkdtempSync(path.join(os.tmpdir(), "reflag-docs-examples-")); +const reports = []; +try { + for (const version of ["v1", "v2"]) { + const react = sdkPackage(version === "v1" ? legacyRoot : sdkRoot, "@reflag/react-sdk"); + const browser = sdkPackage(react.dir, "@reflag/browser-sdk"); + const expected = version === "v1" ? "1.7.4" : "2.0.0-next.0"; + assert.equal(react.version, expected, "Wrong React SDK version"); + assert.equal(browser.version, expected, "Wrong Browser SDK version"); + const output = path.join(temporary, version); + fs.mkdirSync(output); + const files = []; + for (const directory of version === "v2" ? [version, "shared"] : [version]) { + const source = + directory === "shared" + ? path.join(project, directory) + : path.join(project, "scripts/fixtures", directory); + for (const name of fs.readdirSync(source).filter((file) => file.endsWith(".md"))) { + const markdown = fs.readFileSync(path.join(source, name), "utf8"); + [...markdown.matchAll(/```tsx\r?\n([\s\S]*?)```/g)].forEach((match, index) => { + const file = path.join(output, `${directory}-${name}-${index}.tsx`); + fs.writeFileSync(file, match[1]); + files.push(file); + }); + } + } + assert.equal(files.length, version === "v1" ? 2 : 4, "Unexpected snippet coverage"); + const guard = path.join(output, "declaration-guard.tsx"); + fs.writeFileSync( + guard, + version === "v1" + ? `import { useFlag } from '@reflag/react-sdk'; +const flag = useFlag('huddle'); +const enabled: boolean = flag.isEnabled; +// @ts-expect-error Reject accidentally-any declarations. +const invalid: number = flag.isEnabled; +void enabled; void invalid; +` + : `import { useFlag, useFlagSuspense } from '@reflag/react-sdk'; +const enabled: boolean = useFlag('huddle'); +const selected = useFlag('huddle-settings', { maxParticipants: 10 }); +const count: number = selected.maxParticipants; +const suspended: boolean = useFlagSuspense('huddle'); +// @ts-expect-error V2 returns values, not wrappers. +useFlag('huddle').isEnabled; +// @ts-expect-error The inferred field is neither any nor string. +const invalid: string = selected.maxParticipants; +void enabled; void count; void suspended; void invalid; +`, + ); + const program = ts.createProgram([...files, guard], { + target: ts.ScriptTarget.ES2022, + module: ts.ModuleKind.ESNext, + moduleResolution: ts.ModuleResolutionKind.Bundler, + jsx: ts.JsxEmit.ReactJSX, + strict: true, + noEmit: true, + skipLibCheck: true, + types: [], + baseUrl: sdkRoot, + paths: { + "@reflag/react-sdk": [react.types], + "@reflag/browser-sdk": [browser.types], + react: [path.join(reactTypes, "index.d.ts")], + "react/jsx-runtime": [path.join(reactTypes, "jsx-runtime.d.ts")], + }, + }); + const diagnostics = ts.getPreEmitDiagnostics(program); + if (diagnostics.length) { + console.error( + ts.formatDiagnosticsWithColorAndContext(diagnostics, { + getCanonicalFileName: (file) => file, + getCurrentDirectory: () => project, + getNewLine: () => "\n", + }), + ); + process.exitCode = 1; + } + reports.push({ + version, + react: react.version, + browser: browser.version, + snippets: files.length, + diagnostics: diagnostics.length, + }); + } + console.log(JSON.stringify(reports, null, 2)); +} finally { + fs.rmSync(temporary, { recursive: true, force: true }); +} diff --git a/previews/sdk-versioning/scripts/check-layout.mjs b/previews/sdk-versioning/scripts/check-layout.mjs new file mode 100644 index 0000000..98aebc9 --- /dev/null +++ b/previews/sdk-versioning/scripts/check-layout.mjs @@ -0,0 +1,193 @@ +import assert from "node:assert/strict"; +import { createHash } from "node:crypto"; +import fs from "node:fs"; +import path from "node:path"; +import { fileURLToPath, pathToFileURL } from "node:url"; +import Ajv2020 from "ajv/dist/2020.js"; +import addFormats from "ajv-formats"; +import { parse } from "yaml"; + +const project = fileURLToPath(new URL("../", import.meta.url)); +export const schemaURL = "https://api.gitbook.com/gitbook-docs.yaml"; +const read = (file) => fs.readFileSync(file, "utf8"); +const prose = (markdown) => markdown.replace(/```[\s\S]*?```/g, ""); +const links = (markdown) => + [...prose(markdown).matchAll(/\[[^\]]+\]\(([^\s)]+)\)/g)].map((match) => match[1]); +const slug = (heading) => + heading + .toLowerCase() + .replace(/<\/?(?:a|code|strong|em|span|br)(?:\s[^>]*)?>/gi, "") + .replace(/[^\p{L}\p{N}_ -]/gu, "") + .replace(/ /g, "-"); + +function inside(root, target) { + const relative = path.relative(root, target); + assert( + relative !== ".." && !relative.startsWith(`..${path.sep}`) && !path.isAbsolute(relative), + `Path escapes content root: ${target}`, + ); + return target; +} +function markdownFiles(root) { + return fs.readdirSync(root, { withFileTypes: true }).flatMap((entry) => { + if (entry.isDirectory()) + return markdownFiles(path.join(root, entry.name)).map((file) => `${entry.name}/${file}`); + return entry.name.endsWith(".md") ? [entry.name] : []; + }); +} +function checkSpace(root, major) { + assert.deepEqual(parse(read(path.join(root, ".gitbook.yaml"))), { + root: "./", + structure: { readme: "README.md", summary: "SUMMARY.md" }, + }); + const pages = markdownFiles(root) + .filter((file) => file !== "SUMMARY.md") + .sort(); + const navigation = links(read(path.join(root, "SUMMARY.md"))); + assert.equal(navigation[0], "README.md", "The first page must be the overview"); + assert.deepEqual( + [...navigation].sort(), + pages, + "Each page must appear exactly once in SUMMARY.md", + ); + const headings = new Map(); + for (const name of [...pages, "SUMMARY.md"]) { + for (const href of links(read(path.join(root, name)))) { + if (/^[a-z][a-z\d+.-]*:/i.test(href)) continue; + assert(!href.startsWith("/"), `Absolute content link: ${href}`); + const [file, fragment] = href.split("#"); + const target = inside( + root, + path.resolve(root, path.dirname(name), decodeURIComponent(file || path.basename(name))), + ); + assert(fs.existsSync(target), `Missing linked page: ${name} -> ${href}`); + if (fragment && target.endsWith(".md")) { + if (!headings.has(target)) { + const ids = new Set(); + for (const match of prose(read(target)).matchAll(/^#{1,6} (.+)$/gm)) { + const base = slug(match[1]); + let id = base; + for (let suffix = 1; ids.has(id); suffix++) id = `${base}-${suffix}`; + ids.add(id); + } + headings.set(target, ids); + } + assert( + headings.get(target).has(decodeURIComponent(fragment)), + `Missing linked anchor: ${name} -> ${href}`, + ); + } + } + } + if (major) { + const manifest = JSON.parse(read(path.join(root, "generated-manifest.json"))); + assert.equal(manifest.sdkMajor, Number(major)); + assert.deepEqual(Object.keys(manifest.packages).sort(), [ + "@reflag/browser-sdk", + "@reflag/node-sdk", + "@reflag/react-sdk", + "@reflag/vue-sdk", + ]); + assert( + Object.values(manifest.packages).every((version) => version.startsWith(`${major}.`)), + "Wrong SDK source major", + ); + for (const [file, hash] of Object.entries(manifest.files)) { + assert.equal( + createHash("sha256") + .update(fs.readFileSync(inside(root, path.resolve(root, file)))) + .digest("hex"), + hash, + `Generated file drift: ${file}`, + ); + } + assert.equal(pages.length, 10, "Overview plus nine generated SDK pages"); + } + return pages; +} + +export function checkLayout(root = project) { + assert.deepEqual(parse(read(path.join(root, ".gitbook.yaml"))), { + root: "./v2/", + structure: { readme: "README.md", summary: "SUMMARY.md" }, + }); + const config = parse(read(path.join(root, "gitbook-docs.yaml"))); + assert.equal(config.$schema, schemaURL); + const sections = config.site.structure; + assert.equal(sections.length, 2, "Only SDKs are versioned; shared docs form one section"); + const [sdk, shared] = sections; + assert.equal(sdk.type, "section"); + assert.equal(shared.type, "section"); + assert.equal(sdk.default, true); + assert.notEqual(shared.default, true); + assert.deepEqual( + sdk.children.map((space) => space.content.directory), + ["./v2", "./v1"], + ); + assert.deepEqual( + shared.children.map((space) => space.content.directory), + ["./shared"], + "Shared docs must not acquire SDK variants", + ); + const nodes = [...sections, ...sections.flatMap((section) => section.children)]; + assert.equal( + new Set(nodes.map((node) => node.key)).size, + nodes.length, + "Duplicate GitBook identity key", + ); + assert( + nodes.every((node) => node.key.startsWith("sdk-preview-")), + "Preserve the existing preview identities", + ); + for (const section of sections) { + assert.equal( + section.children.filter((space) => space.default).length, + 1, + "Each section needs exactly one default", + ); + assert(section.children.every((space) => space.type === "space")); + } + assert.equal(sdk.children[0].default, true); + const v1 = checkSpace(path.join(root, "v1"), 1); + const v2 = checkSpace(path.join(root, "v2"), 2); + assert.deepEqual(v1, v2, "Equivalent SDK page paths must align across versions"); + checkSpace(path.join(root, "shared")); + assert( + !read(path.join(root, "shared/README.md")).includes(".isEnabled"), + "Shared examples must remain v2-only", + ); + const repository = path.resolve(root, "../.."); + const top = parse(read(path.join(repository, "gitbook-docs.yaml"))); + const expected = structuredClone(config); + for (const section of expected.site.structure) + for (const space of section.children) + space.content.directory = `./previews/sdk-versioning/${space.content.directory.slice(2)}`; + assert.deepEqual( + top, + expected, + "Root and nested variant mappings must have the same identities/content", + ); + assert.deepEqual(parse(read(path.join(repository, ".gitbook.yaml"))), { + root: "./previews/sdk-versioning/v2/", + structure: { readme: "README.md", summary: "SUMMARY.md" }, + }); + return config; +} + +export async function checkSchema(config) { + const response = await fetch(schemaURL, { signal: AbortSignal.timeout(30_000) }); + assert(response.ok, `GitBook schema returned HTTP ${response.status}`); + const ajv = new Ajv2020({ allErrors: true }); + addFormats(ajv); + ajv.addFormat("icon", true); + ajv.addKeyword("example"); + const validate = ajv.compile(parse(await response.text())); + assert(validate(config), ajv.errorsText(validate.errors, { separator: "\n" })); +} +if (process.argv[1] && import.meta.url === pathToFileURL(path.resolve(process.argv[1])).href) { + await checkSchema(checkLayout()); + await checkSchema(parse(read(path.resolve(project, "../../gitbook-docs.yaml")))); + console.log( + "GitBook schema, root/nested variants, generated provenance, navigation, local links and anchors passed.", + ); +} diff --git a/previews/sdk-versioning/scripts/check-layout.test.mjs b/previews/sdk-versioning/scripts/check-layout.test.mjs new file mode 100644 index 0000000..f05c8ec --- /dev/null +++ b/previews/sdk-versioning/scripts/check-layout.test.mjs @@ -0,0 +1,83 @@ +import assert from "node:assert/strict"; +import fs from "node:fs"; +import os from "node:os"; +import path from "node:path"; +import test from "node:test"; +import { fileURLToPath } from "node:url"; +import { parse, stringify } from "yaml"; +import { checkLayout } from "./check-layout.mjs"; + +const project = fileURLToPath(new URL("../", import.meta.url)); +function fixture(t) { + const repo = fs.mkdtempSync(path.join(os.tmpdir(), "reflag-docs-preview-test-")); + t.after(() => fs.rmSync(repo, { recursive: true, force: true })); + const root = path.join(repo, "previews/sdk-versioning"); + fs.mkdirSync(root, { recursive: true }); + for (const file of ["gitbook-docs.yaml", ".gitbook.yaml"]) + fs.copyFileSync(path.resolve(project, "../..", file), path.join(repo, file)); + for (const file of ["gitbook-docs.yaml", ".gitbook.yaml"]) { + fs.copyFileSync(path.join(project, file), path.join(root, file)); + } + for (const name of ["v1", "v2", "shared"]) + fs.cpSync(path.join(project, name), path.join(root, name), { recursive: true }); + return root; +} +function changeConfig(root, change) { + const file = path.join(root, "gitbook-docs.yaml"); + const config = parse(fs.readFileSync(file, "utf8")); + change(config); + fs.writeFileSync(file, stringify(config)); +} + +test("generated SDK variants and both configuration entrypoints are consistent", () => { + checkLayout(); +}); +test("duplicate GitBook identities fail", (t) => { + const root = fixture(t); + changeConfig(root, (config) => { + config.site.structure[1].key = config.site.structure[0].key; + }); + assert.throws(() => checkLayout(root), /Duplicate GitBook identity/); +}); +test("shared docs cannot acquire legacy variants", (t) => { + const root = fixture(t); + changeConfig(root, (config) => { + config.site.structure[1].children.push(config.site.structure[0].children[1]); + }); + assert.throws(() => checkLayout(root), /Shared docs must not acquire SDK variants/); +}); +test("only one SDK variant can be the default", (t) => { + const root = fixture(t); + changeConfig(root, (config) => { + config.site.structure[0].children[1].default = true; + }); + assert.throws(() => checkLayout(root), /exactly one default/); +}); +test("missing generated navigation pages fail", (t) => { + const root = fixture(t); + fs.unlinkSync(path.join(root, "v2/@reflag/node-sdk/globals.md")); + assert.throws(() => checkLayout(root), /SUMMARY.md/); +}); +test("broken same-version anchors fail", (t) => { + const root = fixture(t); + fs.appendFileSync(path.join(root, "v1/README.md"), "\n[Broken](README.md#missing-anchor)\n"); + assert.throws(() => checkLayout(root), /Missing linked anchor/); +}); +test("links cannot escape a mapped space", (t) => { + const root = fixture(t); + fs.appendFileSync(path.join(root, "v1/README.md"), "\n[Wrong version](../v2/README.md)\n"); + assert.throws(() => checkLayout(root), /Path escapes content root/); +}); +test("manual changes to generated pages fail provenance verification", (t) => { + const root = fixture(t); + fs.appendFileSync(path.join(root, "v1/@reflag/node-sdk/README.md"), "\nHandwritten SDK change\n"); + assert.throws(() => checkLayout(root), /Generated file drift/); +}); +test("root and nested projects must map the same existing identity keys", (t) => { + const root = fixture(t); + const file = path.resolve(root, "../../gitbook-docs.yaml"); + const config = parse(fs.readFileSync(file, "utf8")); + config.site.structure[0].children[1].key = "replacement-space"; + fs.writeFileSync(file, stringify(config)); + assert.throws(() => checkLayout(root), /same identities/); +}); diff --git a/previews/sdk-versioning/scripts/fixtures/v1/README.md b/previews/sdk-versioning/scripts/fixtures/v1/README.md new file mode 100644 index 0000000..8e629b3 --- /dev/null +++ b/previews/sdk-versioning/scripts/fixtures/v1/README.md @@ -0,0 +1,29 @@ +# React SDK + +> SDK v1 / legacy. This is an unpublished version-switcher prototype, not a production documentation update. + +Use the version picker to compare the same React overview and API reference between v1 and v2. General product guides will remain shared and use v2 examples only. + +## Installation + +```sh +npm install @reflag/react-sdk@1 +``` + +## Reading a Boolean flag + +V1 returns a flag wrapper with an `isEnabled` property and flag-bound tracking. + +```tsx +import { useFlag } from "@reflag/react-sdk"; + +export function StartHuddleButton() { + const { isEnabled, track } = useFlag("huddle"); + if (!isEnabled) return null; + return ; +} +``` + +## Next step + +Open the [API reference](reference.md#useflag), then switch versions. You should remain on the equivalent reference page when possible. diff --git a/previews/sdk-versioning/scripts/fixtures/v1/reference.md b/previews/sdk-versioning/scripts/fixtures/v1/reference.md new file mode 100644 index 0000000..3533eb5 --- /dev/null +++ b/previews/sdk-versioning/scripts/fixtures/v1/reference.md @@ -0,0 +1,28 @@ +# API reference + +> SDK v1 / legacy. Unpublished navigation prototype. + +## useFlag + +`useFlag("huddle")` returns the v1 wrapper. Read its Boolean `isEnabled` property rather than treating the wrapper itself as a Boolean. + +```tsx +import { useFlag } from "@reflag/react-sdk"; + +export function FlagStatus() { + const flag = useFlag("huddle"); + return {flag.isEnabled ? "Enabled" : "Disabled"}; +} +``` + +## Flag wrapper + +This v1-only anchor intentionally has no matching v2 heading. V2 removes ordinary flag wrappers and uses direct values, separate tracking and separate feedback hooks. + +## Navigation checks + +- [React overview](README.md) +- [Shared useFlag anchor](reference.md#useflag) +- [V1-only wrapper anchor](reference.md#flag-wrapper) + +The corresponding v2 reference adds a `useFlagSuspense` heading, and v2 has a separate Suspense page. These differences let us test the native version picker's missing-page and missing-anchor behavior. diff --git a/previews/sdk-versioning/scripts/fixtures/v2/README.md b/previews/sdk-versioning/scripts/fixtures/v2/README.md new file mode 100644 index 0000000..c68cc60 --- /dev/null +++ b/previews/sdk-versioning/scripts/fixtures/v2/README.md @@ -0,0 +1,35 @@ +# React SDK + +> SDK v2 / candidate. This is an unpublished version-switcher prototype, not a production documentation update or release announcement. + +Use the version picker to compare the same React overview and API reference between v1 and v2. General product guides will remain shared and use v2 examples only. + +## Compatibility + +V2 requires matching Browser SDK v2 and compatible Reflag server endpoints. Server bootstrap must come from Node SDK v2. Native and OpenFeature integrations retain their own supported versions; this preview does not upgrade them. + +## Reading direct flag values + +A bare read returns a Boolean, with implicit false. Supply a positional default for JSON values. Tracking uses a separate hook. + +```tsx +import { useFlag, useTrack } from "@reflag/react-sdk"; + +export function StartHuddleButton() { + const enabled = useFlag("huddle"); + const settings = useFlag("huddle-settings", { maxParticipants: 10 }); + const track = useTrack(); + if (!enabled) return null; + return ( + + ); +} +``` + +## Next step + +Open the [API reference](reference.md#useflag), then switch versions. You should remain on the equivalent reference page when possible. + +[Suspense](suspense.md) is a v2-only page included to test switching when the other version has no equivalent page. diff --git a/previews/sdk-versioning/scripts/fixtures/v2/reference.md b/previews/sdk-versioning/scripts/fixtures/v2/reference.md new file mode 100644 index 0000000..8f3a710 --- /dev/null +++ b/previews/sdk-versioning/scripts/fixtures/v2/reference.md @@ -0,0 +1,31 @@ +# API reference + +> SDK v2 / candidate. Unpublished navigation prototype. + +## useFlag + +`useFlag(key, defaultValue?)` returns a direct value. The second argument is payload data, not hook options. Bare or explicit-undefined reads return Boolean; explicit null requests arbitrary JSON. + +```tsx +import { useFlag } from "@reflag/react-sdk"; + +export function FlagStatus() { + const enabled = useFlag("huddle"); + return {enabled ? "Enabled" : "Disabled"}; +} +``` + +Non-null defaults determine the result's top-level category and inferred type. Registered schemas check default compatibility, but selected values are not recursively schema-validated at runtime. This ordinary hook never suspends. + +## useFlagSuspense + +This v2-only anchor intentionally has no matching v1 heading. Use `useFlagSuspense` with a React Suspense boundary when the component must wait for initialization. + +See the separate [Suspense page](suspense.md) for the full example. + +## Navigation checks + +- [React overview](README.md) +- [Shared useFlag anchor](reference.md#useflag) +- [V2-only Suspense anchor](reference.md#useflagsuspense) +- [V2-only Suspense page](suspense.md) diff --git a/previews/sdk-versioning/scripts/fixtures/v2/suspense.md b/previews/sdk-versioning/scripts/fixtures/v2/suspense.md new file mode 100644 index 0000000..d37bd20 --- /dev/null +++ b/previews/sdk-versioning/scripts/fixtures/v2/suspense.md @@ -0,0 +1,27 @@ +# Suspense + +> SDK v2 / candidate. This page intentionally does not exist in the v1 prototype. + +## Wait for initialization + +```tsx +import { Suspense } from "react"; +import { useFlagSuspense } from "@reflag/react-sdk"; + +function HuddleStatus() { + const enabled = useFlagSuspense("huddle"); + return {enabled ? "Enabled" : "Disabled"}; +} + +export function Example() { + return ( + Loading flags...}> + + + ); +} +``` + +Try switching to v1 from this page. The preview should make the absence understandable; this test records what GitBook's native picker actually does, rather than assuming it maps every page or anchor. + +[Return to the API reference](reference.md#useflagsuspense). diff --git a/previews/sdk-versioning/shared/.gitbook.yaml b/previews/sdk-versioning/shared/.gitbook.yaml new file mode 100644 index 0000000..5241e22 --- /dev/null +++ b/previews/sdk-versioning/shared/.gitbook.yaml @@ -0,0 +1,4 @@ +root: ./ +structure: + readme: README.md + summary: SUMMARY.md diff --git a/previews/sdk-versioning/shared/README.md b/previews/sdk-versioning/shared/README.md new file mode 100644 index 0000000..cb5e674 --- /dev/null +++ b/previews/sdk-versioning/shared/README.md @@ -0,0 +1,22 @@ +# Shared docs + +> Unpublished layout prototype. This shared section has one current set of examples and is not duplicated by SDK version. + +## One current product story + +Getting started, Next.js, the product handbook, integrations and general guides will use v2 examples only. Legacy examples belong inside the corresponding SDK/tooling documentation, not in tabs on shared pages. + +```tsx +import { useFlag } from "@reflag/react-sdk"; + +export function FlagStatus() { + const enabled = useFlag("huddle"); + return {enabled ? "Enabled" : "Disabled"}; +} +``` + +## Test the section boundary + +Use the site navigation to open JavaScript SDKs. Only that section should offer the v1/v2 picker. Return here after selecting v1: this shared example must remain the v2 direct-value example, with no legacy version of this page. + +This is a routing and navigation prototype, not a release announcement. The v2 SDK, CLI and server readiness gates still apply. diff --git a/previews/sdk-versioning/shared/SUMMARY.md b/previews/sdk-versioning/shared/SUMMARY.md new file mode 100644 index 0000000..c159547 --- /dev/null +++ b/previews/sdk-versioning/shared/SUMMARY.md @@ -0,0 +1,3 @@ +# Table of contents + +- [Shared docs](README.md) diff --git a/previews/sdk-versioning/v1/.gitbook.yaml b/previews/sdk-versioning/v1/.gitbook.yaml new file mode 100644 index 0000000..5241e22 --- /dev/null +++ b/previews/sdk-versioning/v1/.gitbook.yaml @@ -0,0 +1,4 @@ +root: ./ +structure: + readme: README.md + summary: SUMMARY.md diff --git a/previews/sdk-versioning/v1/@reflag/browser-sdk/README.md b/previews/sdk-versioning/v1/@reflag/browser-sdk/README.md new file mode 100644 index 0000000..3453247 --- /dev/null +++ b/previews/sdk-versioning/v1/@reflag/browser-sdk/README.md @@ -0,0 +1,613 @@ +--- +layout: + visible: true +title: + visible: true +description: + visible: false +tableOfContents: + visible: true +outline: + visible: true +pagination: + visible: true +--- + +{% hint style="info" %} +Legacy SDK v1. Generated from `@reflag/browser-sdk@1.7.4` in [the JavaScript repository](https://github.com/reflagcom/javascript/tree/b4fe5b3d9386b87c6546175c323862d9878e1430/packages/browser-sdk). SDK READMEs and code comments there remain authoritative. +{% endhint %} + +# Reflag Browser SDK + +Basic client for [Reflag.com](https://reflag.com). If you're using React, you'll be better off with the Reflag React SDK. + +Reflag supports flag toggling, tracking flag usage, [collecting feedback](#qualitative-feedback-on-beta-flags) on flags, and [remotely configuring flags](#remote-config). + +## Install + +First find your `publishableKey` under [environment settings](https://app.reflag.com/env-current/settings/app-environments) in Reflag. + +The package can be imported or used directly in a HTML script tag: + +A. Import module: + +```typescript +import { ReflagClient } from "@reflag/browser-sdk"; + +const user = { + id: 42, + role: "manager", +}; + +const company = { + id: 99, + plan: "enterprise", +}; + +const reflagClient = new ReflagClient({ publishableKey, user, company }); + +await reflagClient.initialize(); + +const { + isEnabled, + config: { payload: question }, + track, + requestFeedback, +} = reflagClient.getFlag("huddle"); + +if (isEnabled) { + // Show flag. When retrieving `isEnabled` the client automatically + // sends a "check" event for the "huddle" flag which is shown in the + // Reflag UI. + + // On usage, call `track` to let Reflag know that a user interacted with the flag + track(); + + // The `payload` is a user-supplied JSON in Reflag that is dynamically picked + // out depending on the user/company. + const question = payload?.question ?? "Tell us what you think of Huddles"; + + // Use `requestFeedback` to create "Send feedback" buttons easily for specific + // flags. This is not related to `track` and you can call them individually. + requestFeedback({ title: question }); +} + +// `track` just calls `reflagClient.track()` to send an event using the same flag key +// You can also use `track` on the client directly to send any custom event. +reflagClient.track("huddle"); + +// similarly, `requestFeedback` just calls `reflagClient.requestFeedback({flagKey: })` +// which you can also call directly: +reflagClient.requestFeedback({ flagKey: "huddle" }); +``` + +B. Script tag (client-side directly in html) + +See [example/browser.html](https://github.com/reflagcom/javascript/tree/main/packages/browser-sdk/example/browser.html) for a working example: + +```html + + +Loading... + +``` + +### Init options + +Supply these to the constructor call: + +```typescript +type Configuration = { + logger: console; // by default only logs warn/error, by passing `console` you'll log everything + apiBaseUrl?: "https://front.reflag.com"; + credentials?: "include" | "same-origin" | "omit"; // forwarded to fetch requests; "include" also enables credentials for the default EventSource transport + feedback?: undefined; // See FEEDBACK.md + enableTracking?: true; // set to `false` to stop sending track events and user/company updates to Reflag servers. Useful when you're impersonating a user + enableLiveFlagUpdates?: false; // Set to `true` to keep flags up to date over SSE (browser SDK default: false) + eventSourceFactory?: (url: string) => { + // Advanced: provide a custom EventSource-compatible transport + addEventListener: (type: string, cb: (event: any) => void) => void; + close: () => void; + }; + fallbackFlags?: + | string[] + | Record; // Enable these flags if unable to contact reflag.com. Can be a list of flag keys or a record with configuration values + timeoutMs?: number; // Timeout for fetching flags (default: 5000ms) + staleWhileRevalidate?: boolean; // Revalidate in the background when cached flags turn stale to avoid latency in the UI (default: false) + staleTimeMs?: number; // at initialization time flags are loaded from the cache unless they have gone stale. Defaults to 0 which means the cache is disabled. Increase this in the case of a non-SPA + expireTimeMs?: number; // In case we're unable to fetch flags from Reflag, cached/stale flags will be used instead until they expire after `expireTimeMs`. Default is 30 days + offline?: boolean; // Use the SDK in offline mode. Offline mode is useful during testing and local development +}; +``` + +## Migrating from Bucket SDK + +If you have been using the Bucket SDKs, the following list will help you migrate to Reflag SDK: + +- `Bucket*` classes, and types have been renamed to `Reflag*` (e.g. `BucketClient` is now `ReflagClient`) +- `Feature*` classes, and types have been renamed to `Feature*` (e.g. `Feature` is now `Flag`, `RawFeatures` is now `RawFlags`) +- All methods that contained `feature` in the name have been renamed to use the `flag` terminology (e.g. `getFeature` is `getFlag`) +- The `fallbackFeatures` property in client constructor and configuration files has been renamed to `fallbackFlags` +- `featureKey` has been renamed to `flagKey` in all methods that accepts that argument +- The new cookies that are stored in the client's browser are now `reflag-*` prefixed instead og `bucket-*` +- The `featuresUpdated` hook has been renamed to `flagsUpdated` +- The `checkIsEnabled` and `checkConfig` hooks have been removed, use `check` from now on + +To ease in transition to Reflag SDK, some of the old methods have been preserved as aliases to the new methods: + +- `getFeature` method is an alias for `getFlag` +- `getFeatures` method is an alias for `getFlags` +- `featuresUpdated` hook is an alias for `flagsUpdated` + +If you are running with strict Content Security Policies active on your website, you will need change them as follows: + +- `connect-src https://front.bucket.co` to `connect-src https://front.reflag.com` + +Finally, if you have customized the look & feel of the Feedback component, update `--bucket-feedback-*` CSS classes to `--reflag-feedback-*` + +## Flag toggles + +Reflag determines which flags are active for a given user/company. The user/company is given in the ReflagClient constructor. + +If you supply `user` or `company` objects, they must include at least the `id` property otherwise they will be ignored in their entirety. +In addition to the `id`, you must also supply anything additional that you want to be able to evaluate flag targeting rules against. + +Attributes cannot be nested (multiple levels) and must be either strings, integers or booleans. +Some attributes are special and used in Reflag UI: + +- `name` -- display name for `user`/`company`, +- `email` -- is accepted for `user`s and will be highlighted in the Reflag UI if available, +- `avatar` -- can be provided for both `user` and `company` and should be an URL to an image. + +```ts +const reflagClient = new ReflagClient({ + publishableKey, + user: { + id: "user_123", + name: "John Doe", + email: "john@acme.com" + avatar: "https://example.com/images/udsy6363" + }, + company: { + id: "company_123", + name: "Acme, Inc", + avatar: "https://example.com/images/31232ds" + }, +}); +``` + +To retrieve flags along with their targeting information, use `getFlag(key: string)`: + +```ts +const huddle = reflagClient.getFlag("huddle"); +// { +// isEnabled: true, +// config: { key: "zoom", payload: { ... } }, +// track: () => Promise +// requestFeedback: (options: RequestFeedbackData) => void +// } +``` + +You can use `getFlags()` to retrieve all enabled flags currently. + +```ts +const flags = reflagClient.getFlags(); +// { +// huddle: { +// isEnabled: true, +// targetingVersion: 42, +// config: ... +// } +// } +``` + +`getFlags()` is meant to be more low-level than `getFlag()` and it typically used +by down-stream clients, like the React SDK. + +Note that accessing `isEnabled` on the object returned by `getFlags` does not automatically +generate a `check` event, contrary to the `isEnabled` property on the object returned by `getFlag`. + +## End-user opt-in + +If a flag has end-user opt-in enabled in Reflag, you can list the opt-in options for the current context and set or cancel opt-in for the current user or company. + +```ts +const optInFlags = reflagClient.getOptInFlags(); +// [{ key, name, description, isEnabled, userOptedIn, companyOptedIn, isOptedIn }] + +try { + const response = await reflagClient.setOptIn("huddle", { + optedIn: true, // Use false to cancel this scope's opt-in. + scope: "user", // Use "company" to change the current company's opt-in. + }); + if (!response?.ok) { + console.error("Could not update opt-in"); + } +} catch (error) { + console.error("Could not update opt-in", error); +} +``` + +By default, `setOptIn()` changes the opt-in for the current user, so the current context must include a `user.id`. To manage the current company's opt-in instead, pass `scope: "company"`; the context must then include a `company.id`. + +User and company opt-ins are managed independently. Setting `optedIn` to `false` removes the opt-in only for the selected scope. For example, cancelling a user's opt-in does not change the company's opt-in for the same flag. + +`setOptIn` returns a promise so you can wait for the new membership state to be synchronized. On success, it resolves after the refreshed flag state has been applied locally, the requested membership change has been confirmed, and `flagsUpdated` listeners have been notified when flags change. + +The promise resolves to a `Response`, or `undefined` if skipped due to invalid input, offline mode etc. Check `response?.ok` for success. + +The `description` comes from the dedicated SDK-facing opt-in description configured in Reflag. + +When using bootstrapped flags, the first `getOptInFlags()` or `getIsLoadingOptInFlags()` call requests a flags refresh. The list call returns the currently available list synchronously, and the loading getter returns `true` until that refresh succeeds or fails. Normal initialization also exposes loading through the client's state. + +Listen for `optInFlagsLoadingUpdated` to update UI when this loading state changes. `flagsUpdated` is emitted when a successful refresh updates the list. + +If fetching opt-in metadata fails, loading ends without exposing an error. Call `reflagClient.refresh()` to retry. + +## Remote config + +Remote config is a dynamic and flexible approach to configuring flag behavior outside of your app – without needing to re-deploy it. + +Similar to `isEnabled`, each flag has a `config` property. This configuration is managed from within Reflag. +It is managed similar to the way access to flags is managed, but instead of the binary `isEnabled` you can have +multiple configuration values which are given to different user/companies. + +```ts +const flags = reflagClient.getFlags(); +// { +// huddle: { +// isEnabled: true, +// targetingVersion: 42, +// config: { +// key: "gpt-3.5", +// payload: { maxTokens: 10000, model: "gpt-3.5-beta1" } +// } +// } +// } +``` + +`key` is mandatory for a config, but if a flag has no config or no config value was matched against the context, the `key` will be `undefined`. Make sure to check against this case when trying to use the configuration in your application. `payload` is an optional JSON value for arbitrary configuration needs. + +Just as `isEnabled`, accessing `config` on the object returned by `getFlags` does not automatically +generate a `check` event, contrary to the `config` property on the object returned by `getFlag`. + +## Server-side rendering and bootstrapping + +For server-side rendered applications, you can render immediately with pre-fetched flag data by bootstrapping the client. + +### Init options bootstrapped + +```typescript +type Configuration = { + logger: console; // by default only logs warn/error, by passing `console` you'll log everything + apiBaseUrl?: "https://front.reflag.com"; + credentials?: "include" | "same-origin" | "omit"; // forwarded to fetch requests; "include" also enables credentials for the default EventSource transport + feedback?: undefined; // See FEEDBACK.md + enableTracking?: true; // set to `false` to stop sending track events and user/company updates to Reflag servers. Useful when you're impersonating a user + offline?: boolean; // Use the SDK in offline mode. Offline mode is useful during testing and local development + bootstrappedState?: { + context: ReflagContext; + flags: FetchedFlags; + flagStateVersion?: number; + }; // Pre-fetched evaluated state from server-side (see Server-side rendering section) + bootstrappedFlags?: FetchedFlags; // Deprecated: use `bootstrappedState` instead +}; +``` + +### Using `bootstrappedState` + +Use the Node SDK's `getFlagsForBootstrap()` method to pre-fetch evaluated state server-side, then pass the returned object directly to the browser client: + +```typescript +// Server-side: Get bootstrapped state using Node SDK +import { ReflagClient as ReflagNodeClient } from "@reflag/node-sdk"; + +const serverClient = new ReflagNodeClient({ secretKey: "your-secret-key" }); +await serverClient.initialize(); + +const bootstrappedState = serverClient.getFlagsForBootstrap({ + user: { id: "user123", name: "John Doe", email: "john@acme.com" }, + company: { id: "company456", name: "Acme Inc", plan: "enterprise" }, +}); + +// Pass the bootstrapped state to the client using your framework's preferred method +app.get("/", (req, res) => { + res.set("Content-Type", "text/html"); + res.send( + Buffer.from( + ` +
`, + ), + ); +}); + +// Client-side: Initialize with pre-fetched evaluated state +import { ReflagClient } from "@reflag/browser-sdk"; + +const reflagClient = new ReflagClient({ + publishableKey: "your-publishable-key", + bootstrappedState, // Contains context, flags, and optional flagStateVersion +}); + +await reflagClient.initialize(); +const { isEnabled } = reflagClient.getFlag("huddle"); +``` + +The `bootstrappedState` object contains: + +- `context`: the evaluation context used on the server +- `flags`: the evaluated raw flags +- `flagStateVersion`: an optional version used to avoid redundant live-update refreshes immediately after bootstrapping + +If you want live flag updates to continue working after bootstrapping, use a recent `@reflag/node-sdk` so `getFlagsForBootstrap()` includes `flagStateVersion`. + +If a bootstrapped application requests opt-in flags, the browser SDK performs one flags refresh. Applications that do not request opt-in data do not make this request. + +If you previously used `bootstrappedFlags`, migrate like this: + +```typescript +// Before +const { flags } = serverClient.getFlagsForBootstrap(context); +const client = new ReflagClient({ + publishableKey, + user: context.user, + company: context.company, + other: context.other, + bootstrappedFlags: flags, +}); + +// After +const bootstrappedState = serverClient.getFlagsForBootstrap(context); +const client = new ReflagClient({ + publishableKey, + bootstrappedState, +}); +``` + +{% hint style="info" %} +After bootstrapping, any live flag updates are fetched directly by the browser SDK from Reflag using the browser-visible context. If your bootstrapped snapshot depends on server-only or secret context that is not available in the browser, later live refreshes may differ. In that case, keep `enableLiveFlagUpdates` disabled. + +Requesting opt-in flags also triggers a browser-side refresh, even when `enableLiveFlagUpdates` is disabled. +{% endhint %} + +This eliminates loading states and removes the initial render's dependency on the flags API. + +## Context management + +### Updating user/company/other context + +Attributes given for the user/company/other context in the ReflagClient constructor can be updated for use in flag targeting evaluation with the `updateUser()`, `updateCompany()` and `updateOtherContext()` methods. +They return a promise which resolves once the flags have been re-evaluated follow the update of the attributes. + +{% hint style="info" %} +{% endhint %} + +### setContext() + +The `setContext()` method allows you to replace the entire context (user, company, and other attributes) at once. This method is useful when you need to completely change the context, such as when a user logs in or switches between different accounts. + +```ts +await reflagClient.setContext({ + user: { + id: "new-user-123", + name: "Jane Doe", + email: "jane@example.com", + role: "admin", + }, + company: { + id: "company-456", + name: "New Company Inc", + plan: "enterprise", + }, + other: { + feature: "beta", + locale: "en-US", + }, +}); +``` + +The method will: + +- Replace the entire context with the new values +- Re-evaluate all flags based on the new context +- Update the user and company information on Reflag servers +- Return a promise that resolves once the flags have been re-evaluated + +### getContext() + +The `getContext()` method returns the current context being used for flag evaluation. This is useful for debugging or when you need to inspect the current user, company, and other attributes. + +```ts +const currentContext = reflagClient.getContext(); +console.log(currentContext); +// { +// user: { id: "user-123", name: "John Doe", email: "john@example.com" }, +// company: { id: "company-456", name: "Acme Inc", plan: "enterprise" }, +// other: { locale: "en-US", feature: "beta" } +// } +``` + +The returned context object contains: + +- `user`: Current user attributes (if any) +- `company`: Current company attributes (if any) +- `other`: Additional context attributes not related to user or company + +## Toolbar + +The Reflag Toolbar is great for toggling flags on/off for yourself to ensure that everything works both when a flag is on and when it's off. + +Toolbar screenshot + +The toolbar will automatically appear on `localhost`. However, it can also be incredibly useful in production. +You have full control over when it appears through the `toolbar` configuration option passed to the `ReflagClient`. + +You can pass a simple boolean to force the toolbar to appear/disappear: + +```typescript +const client = new ReflagClient({ + // show the toolbar even in production if the user is an internal/admin user + toolbar: user?.isInternal, + ... +}); +``` + +You can also configure the position of the toolbar on the screen: + +```typescript +const client = new ReflagClient({ + toolbar: { + show: true; + position: { + placement: "bottom-left", + offset: {x: "1rem", y: "1rem"} + } + } + ... +}) +``` + +See [the reference](https://docs.reflag.com/supported-languages/browser-sdk/globals#toolbaroptions) for details. + +## Qualitative feedback on beta flags + +Reflag can collect qualitative feedback from your users in the form of a [Customer Satisfaction Score](https://en.wikipedia.org/wiki/Customer_satisfaction) and a comment. + +### Automated feedback collection + +The Reflag Browser SDK comes with automated feedback collection mode enabled by default, which lets the Reflag service ask your users for feedback for relevant flags just after they've used them. + +{% hint style="info" %} +To get started with automatic feedback collection, make sure you've set `user` in the `ReflagClient` constructor. +{% endhint %} + +Automated feedback surveys work even if you're not using the SDK to send events to Reflag. +It works because the Reflag Browser SDK maintains a live connection to Reflag's servers and can automatically show a feedback prompt whenever the Reflag servers determines that an event should trigger a prompt - regardless of how this event is sent to Reflag. + +You can find all the options to make changes to the default behavior in the [Reflag feedback documentation](../../documents/browser-sdk/FEEDBACK.md). + +### Reflag feedback UI + +Reflag can assist you with collecting your user's feedback by offering a pre-built UI, allowing you to get started with minimal code and effort. + +[Read the Reflag feedback UI documentation](../../documents/browser-sdk/FEEDBACK.md) + +### Reflag feedback SDK + +Feedback can be submitted to Reflag using the SDK: + +```ts +reflagClient.feedback({ + flagKey: "my-flag-key", // String (required), copy from Flag feedback tab + score: 5, // Number: 1-5 (optional) + comment: "Absolutely stellar work!", // String (optional) +}); +``` + +### Reflag feedback API + +If you are not using the Reflag Browser SDK, you can still submit feedback using the HTTP API. + +See details in [Feedback HTTP API](https://docs.reflag.com/api/http-api#post-feedback) + +## Tracking flag usage + +The `track` function lets you send events to Reflag to denote flag usage. +By default Reflag expects event names to align with the flag keys, but +you can customize it as you wish. + +```ts +reflagClient.track("huddle", { voiceHuddle: true }); +``` + +## Event listeners + +Event listeners allow for capturing various events occurring in the `ReflagClient`. This is useful to build integrations with other system or for various debugging purposes. The available events are: + +- `check`: Your code used `isEnabled` or `config` for a flag +- `flagsUpdated`: Flags were updated. Either because they were loaded as part of initialization or because the user/company updated +- `optInFlagsLoadingUpdated`: The opt-in flag loading state changed +- `user`: User information updated (similar to the `identify` call used in tracking terminology) +- `company`: Company information updated (sometimes to the `group` call used in tracking terminology) +- `track`: Track event occurred. + +Use the `on()` method to add an event listener to respond to certain events. See the API reference for details on each hook. + +```ts +import { ReflagClient, CheckEvent, RawFlags } from "@reflag/browser-sdk"; + +const client = new ReflagClient({ + // options +}); + +// or add the hooks after construction: +const unsub = client.on("check", (check: CheckEvent) => + console.log(`Check event ${check}`), +); +// use the returned function to unsubscribe, or call `off()` with the same arguments again +unsub(); +``` + +## Zero PII + +The Reflag Browser SDK doesn't collect any metadata and HTTP IP addresses are _not_ being stored. + +For tracking individual users, we recommend using something like database ID as userId, as it's unique and doesn't include any PII (personal identifiable information). If, however, you're using e.g. email address as userId, but prefer not to send any PII to Reflag, you can hash the sensitive data before sending it to Reflag: + +```ts +import reflag from "@reflag/browser-sdk"; +import { sha256 } from "crypto-hash"; + +reflag.user(await sha256("john_doe")); +``` + +## Use of cookies + +The Reflag Browser SDK uses a couple of cookies to support automated feedback surveys. These cookies are not used for tracking purposes and thus should not need to appear in cookie consent forms. + +The two cookies are: + +- `reflag-prompt-${userId}`: store the last automated feedback prompt message ID received to avoid repeating surveys +- `reflag-token-${userId}`: caching a token used to connect to Reflag's live messaging infrastructure that is used to deliver automated feedback surveys in real time. + +## TypeScript + +Types are bundled together with the library and exposed automatically when importing through a package manager. + +## Content Security Policy (CSP) + +If you are running with strict Content Security Policies active on your website, you will need to enable these directives in order to use the SDK: + +| Directive | Values | Reason | +| ----------- | ---------------------------------------------------- | ------------------------------------------------------------------------------------------------------- | +| connect-src | [https://front.reflag.com](https://front.reflag.com) | API requests plus Server-Sent Events for live flag updates and automated feedback surveys. | +| style-src | 'unsafe-inline' | The feedback UI is styled with inline styles. Not having this directive results unstyled HTML elements. | + +If you are including the Reflag tracking SDK with a ` + + + + +``` + +If using Nuxt, wrap `` in ``. `` only renders client-side currently. + +### 2. Use `useFlag get flag status + +```vue + + + +``` + +See [useFlag()](#useflag) for a full example + +## Setting context + +Reflag determines which flags are active for a given `user`, `company`, or `other` context. +You can pass these to the `ReflagProvider` using the `context` prop. + +### Using the `context` prop + +```vue + + + +``` + +### Legacy individual props (deprecated) + +For backward compatibility, you can still use individual props, but these are deprecated and will be removed in the next major version: + +```vue + + + +``` + +> [!Important] +> The `user`, `company`, and `otherContext` props are deprecated. Use the `context` prop instead, which provides the same functionality in a more structured way. + +### Context requirements + +If you supply `user` or `company` objects, they must include at least the `id` property otherwise they will be ignored in their entirety. +In addition to the `id`, you must also supply anything additional that you want to be able to evaluate flag targeting rules against. +Attributes which are not properties of the `user` or `company` can be supplied using the `other` property. + +Attributes cannot be nested (multiple levels) and must be either strings, numbers or booleans. +A number of special attributes exist: + +- `name` -- display name for `user`/`company`, +- `email` -- the email of the user, +- `avatar` -- the URL for `user`/`company` avatar image. + +To retrieve flags along with their targeting information, use `useFlag(key: string)` hook (described in a section below). + +Note that accessing `isEnabled` on the object returned by `useFlag()` automatically +generates a `check` event. + +## Remote config + +Remote config is a dynamic and flexible approach to configuring flag behavior outside of your app – without needing to re-deploy it. + +Similar to `isEnabled`, each flag accessed using the `useFlag()` hook, has a `config` property. This configuration is managed from within Reflag. It is managed similar to the way access to flags is managed, but instead of the +binary `isEnabled` you can have multiple configuration values which are given to different user/companies. + +### Get started with Remote config + +```ts +const { + isEnabled, + config: { key, payload }, +} = useFlag("huddles"); + +// isEnabled: true, +// key: "gpt-3.5", +// payload: { maxTokens: 10000, model: "gpt-3.5-beta1" } +``` + +`key` is mandatory for a config, but if a flag has no config or no config value was matched against the context, the `key` will be `undefined`. Make sure to check against this case when trying to use the configuration in your application. `payload` is an optional JSON value for arbitrary configuration needs. + +Note that, similar to `isEnabled`, accessing `config` on the object returned by `useFlag()` automatically +generates a `check` event. + +## `` component + +The `` initializes the Reflag SDK, fetches flags and starts listening for automated feedback survey events. The component can be configured using a number of props: + +- `publishableKey` is used to connect the provider to an _environment_ on Reflag. Find your `publishableKey` under [environment settings](https://app.reflag.com/env-current/settings/app-environments) in Reflag, +- `context`: An object containing `user`, `company`, and `other` properties that make up the evaluation context used to determine if a flag is enabled or not. `company` and `user` contexts are automatically transmitted to Reflag servers so the Reflag app can show you which companies have access to which flags etc. +- `company`, `user` and `otherContext` (deprecated): Individual props for context. These are deprecated in favor of the `context` prop and will be removed in the next major version. + + > [!Note] + > If you specify `company` and/or `user` they must have at least the `id` property, otherwise they will be ignored in their entirety. You should also supply anything additional you want to be able to evaluate flag targeting against, + +- `timeoutMs`: Timeout in milliseconds when fetching flags from the server, +- `staleWhileRevalidate`: If set to `true`, stale flags will be returned while refetching flags in the background, +- `expireTimeMs`: If set, flags will be cached between page loads for this duration (in milliseconds), +- `staleTimeMs`: Maximum time (in milliseconds) that stale flags will be returned if `staleWhileRevalidate` is true and new flags cannot be fetched. + +- `enableTracking`: Set to `false` to stop sending tracking events and user/company updates to Reflag. Useful when you're impersonating a user (defaults to `true`), +- `apiBaseUrl`: Optional base URL for the Reflag API. This also controls the SSE origin used for live flag updates and automated feedback, +- `credentials`: Optional fetch credentials mode. Set to `"include"` when proxying through your backend and authenticating with cookies; this also enables credentials for live-update SSE connections. +- `appBaseUrl`: Optional base URL for the Reflag application. Use this to override the default app URL, + +- `debug`: Set to `true` to enable debug logging to the console. If both `logger` and `debug` are provided, `logger` takes precedence, +- `logger`: Optional custom logger implementation (`debug`, `info`, `warn`, `error`) used by the underlying client, +- `toolbar`: Optional [configuration](https://docs.reflag.com/supported-languages/browser-sdk/globals#toolbaroptions) for the Reflag toolbar, +- `feedback`: Optional configuration for feedback collection + +### Loading states + +ReflagProvider lets you define a template to be shown while ReflagProvider is initializing: + +```vue + +``` + +If you want more control over loading screens, `useIsLoading()` returns a `Ref` which you can use to customize the loading experience. + +## `` component + +The `` component is a specialized version of `ReflagProvider` designed for server-side rendering and preloaded flag scenarios. It uses pre-fetched flags for the initial render, resulting in faster initial page loads and better SSR compatibility. + +### Usage + +```vue + + + +``` + +### Getting bootstrapped flags + +You'll typically generate the `bootstrappedFlags` object on your server using the Node.js SDK or by fetching from the Reflag API. Pass the full object returned by `getFlagsForBootstrap()` directly to ``. It contains: + +- `context`: the evaluation context used on the server +- `flags`: the evaluated raw flags +- `flagStateVersion`: an optional version used to avoid redundant live-update refreshes immediately after bootstrapping + +If you want live flag updates to continue working after bootstrapping, use a recent `@reflag/node-sdk` so `getFlagsForBootstrap()` includes `flagStateVersion`. + +With `ReflagBootstrappedProvider`, `useOptInFlags()` requests a flags refresh on first use. It reports `isLoading: true` until that refresh settles. + +Here's an example using the Node.js SDK: + +```js +// server.js (Node.js/SSR) +import { ReflagClient } from "@reflag/node-sdk"; + +const client = new ReflagClient({ + secretKey: "your-secret-key", // Use secret key on server +}); +await client.initialize(); + +// Fetch flags for specific context +const context = { + user: { id: "user123", name: "John Doe", email: "john@acme.com" }, + company: { id: "company456", name: "Acme Inc", plan: "enterprise" }, +}; + +const bootstrappedFlags = client.getFlagsForBootstrap(context); + +// Pass to your Vue app +``` + +### ReflagBootstrappedProvider Props + +`ReflagBootstrappedProvider` accepts all the same props as `ReflagProvider` except: + +- `flags`: The pre-fetched bootstrapped state object containing `context`, evaluated `flags`, and an optional `flagStateVersion` +- All other props available in `ReflagProvider` are supported except `context`, `user`, `company`, and `otherContext` (which are extracted from `flags.context`) + +If the `flags` prop is not provided or is undefined, the provider will not initialize the client and will render in a non-loading state. + +{% hint style="info" %} +After bootstrapping, any live flag updates are fetched directly by the browser SDK from Reflag using the browser-visible context. If your bootstrapped snapshot depends on server-only or secret context that is not available in the browser, later live refreshes may differ. In that case, keep `enableLiveFlagUpdates` disabled. + +Requesting opt-in flags also triggers a browser-side refresh, even when `enableLiveFlagUpdates` is disabled. +{% endhint %} + +## `` component + +The `` is a lower-level component that accepts a pre-initialized `ReflagClient` instance. This is useful for advanced use cases where you need full control over client initialization or want to share a client instance across multiple parts of your application. + +### ReflagClientProvider Usage + +```vue + + + +``` + +### ReflagClientProvider Props + +The `ReflagClientProvider` accepts the following props: + +- `client`: A pre-initialized `ReflagClient` instance + +### Slots + +- `loading`: Optional slot to show while the client is initializing (same as `ReflagProvider`) + +> [!Note] +> Most applications should use `ReflagProvider` or `ReflagBootstrappedProvider` instead of `ReflagClientProvider`. Only use this component when you need the advanced control it provides. + +## Hooks + +### `useFlag()` + +Returns the state of a given flag for the current context. The composable provides access to flags and their configurations. + +`useFlag()` returns an object with this shape: + +```ts +{ + isEnabled: boolean, // is the flag enabled + track: () => void, // send a track event when the flag is used + requestFeedback: (...) => void // open up a feedback dialog + config: {key: string, payload: any}, // remote configuration for this flag + isLoading: boolean // if you want to manage loading state at the flag level +} +``` + +Example: + +```vue + + + +``` + +See the reference docs for details. + +### `useOptInFlags()` and `useSetOptIn()` + +Use these composables to build an end-user opt-in UI for flags where opt-in is enabled in Reflag. + +```vue + + + +``` + +By default, `useSetOptIn()` changes the opt-in for the current user, so the current context must include a `user.id`. To manage the current company's opt-in instead, pass `scope: "company"`; the context must then include a `company.id`. + +User and company opt-ins are managed independently. Setting `optedIn` to `false` removes the opt-in only for the selected scope. For example, cancelling a user's opt-in does not change the company's opt-in for the same flag. + +`setOptIn` returns a promise so you can wait for the new membership state to be synchronized. On success, it resolves after the refreshed flag state has been applied locally, the requested membership change has been confirmed, and subscribers have been notified when flags change. Vue may not have committed the resulting render yet. + +The promise resolves to a `Response`, or `undefined` if skipped due to invalid input, offline mode etc. Check `response?.ok` for success. + +`useOptInFlags()` returns `{ flags, isLoading }`, where both values are computed refs. With `ReflagBootstrappedProvider`, it fetches opt-in metadata on first use and reports `isLoading: true` until that refresh succeeds or fails. + +If fetching opt-in metadata fails, loading ends without exposing an error. Call `client.refresh()` on the client returned by `useClient()` to retry. + +With a regular `ReflagProvider`, opt-in metadata arrives as part of the normal initial flags request, so `useOptInFlags().isLoading` remains `false`. Use the general `useIsLoading()` composable or the provider's loading slot for that initial loading state. + +### `useTrack()` + +`useTrack()` returns a function which lets you send custom events to Reflag. It takes a string argument with the event name and optionally an object with properties to attach the event. + +Using `track` returned from `useFlag()` calls this track function with the flag key as the event name. + +```vue + + + +``` + +### `useRequestFeedback()` + +Returns a function that lets you open up a dialog to ask for feedback on a specific feature. This is useful for collecting targeted feedback about specific features. + +See [Automated Feedback Surveys](https://docs.reflag.com/product-handbook/live-satisfaction) for how to do this automatically, without code. + +When using the `useRequestFeedback` you must pass the flag key to `requestFeedback`. +The example below shows how to use `position` to ensure the popover appears next to the "Give feedback!" button. + +```vue + + + +``` + +See the [Feedback Documentation](https://github.com/reflagcom/javascript/blob/main/packages/browser-sdk/FEEDBACK.md#manual-feedback-collection) for more information on `requestFeedback` options. + +### `useSendFeedback()` + +Returns a function that lets you send feedback to Reflag. This is useful if you've manually collected feedback through your own UI and want to send it to Reflag. + +```vue + + + +``` + +### `useUpdateUser()`, `useUpdateCompany()` and `useUpdateOtherContext()` + +These composables return functions that let you update the attributes for the currently set user, company, or other context. Updates to user/company are stored remotely and affect flag targeting, while "other" context updates only affect the current session. + +```vue + + + +``` + +Note: To change the `user.id` or `company.id`, you need to update the props passed to `ReflagProvider` instead of using these composables. + +### `useClient()` + +Returns the `ReflagClient` used by the `ReflagProvider`. The client offers more functionality that +is not directly accessible through the other composables. + +```vue + + + +``` + +### `useIsLoading()` + +Returns a `Ref` to indicate if Reflag has finished loading. +Initially, the value will be `true` if no bootstrap flags have been provided and the client has not be initialized. + +```vue + + + +``` + +### `useOnEvent()` + +Vue composable for listening to Reflag client events. This composable automatically handles mounting and unmounting of event listeners. + +Available events include: + +- `flagsUpdated`: Triggered when flags are updated +- `track`: Triggered when tracking events are sent +- `feedback`: Triggered when feedback is sent + +```vue + + + +``` + +You can also provide a specific client instance if needed: + +```vue + + + +``` + +## Content Security Policy (CSP) + +See [CSP](https://github.com/reflagcom/javascript/blob/main/packages/browser-sdk/README.md#content-security-policy-csp) for info on using Reflag React SDK with CSP + +## License + +MIT License + +Copyright (c) 2025 Bucket ApS diff --git a/previews/sdk-versioning/v1/@reflag/vue-sdk/globals.md b/previews/sdk-versioning/v1/@reflag/vue-sdk/globals.md new file mode 100644 index 0000000..eb7d494 --- /dev/null +++ b/previews/sdk-versioning/v1/@reflag/vue-sdk/globals.md @@ -0,0 +1,1897 @@ +--- +layout: + visible: true +title: + visible: true +description: + visible: false +tableOfContents: + visible: true +outline: + visible: true +pagination: + visible: true +--- + +{% hint style="info" %} +Legacy SDK v1. Generated from `@reflag/vue-sdk@1.5.7` in [the JavaScript repository](https://github.com/reflagcom/javascript/tree/b4fe5b3d9386b87c6546175c323862d9878e1430/packages/vue-sdk). SDK READMEs and code comments there remain authoritative. +{% endhint %} + +# @reflag/vue-sdk + +## Interfaces + +### CheckEvent + +Event representing checking the flag evaluation result + +#### Properties + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
PropertyTypeDescription
+ + `action` + + + +`"check-is-enabled"` \| `"check-config"` + + + +`check-is-enabled` means `isEnabled` was checked, `check-config` means `config` was checked. + +
+ + `evaluationErrors?` + + + +\{ `code`: `string`; `field`: `string`; `message`: `string`; `operator`: `string`; \}[] + + + +Non-fatal diagnostics produced while evaluating the flag. + +
+ + `key` + + + +`string` + + + +Flag key. + +
+ + ~~`missingContextFields?`~~ + + + +`string`[] + + + +Missing context fields. + +**Deprecated** + +Use `evaluationErrors` and check for `MISSING_CONTEXT_FIELD`. + +
+ + `ruleEvaluationResults?` + + + +`boolean`[] + + + +Rule evaluation results. + +
+ + `value?` + + + + \| `boolean` \| \{ `key`: `string`; `payload`: `any`; \} + + + +Result of flag or configuration evaluation. +If `action` is `check-is-enabled`, this is the result of the flag evaluation and `value` is a boolean. +If `action` is `check-config`, this is the result of the configuration evaluation. + +
+ + `version?` + + + +`number` + + + +Version of targeting rules. + +
+ +*** + +### CompanyContext + +Context is a set of key-value pairs. +This is used to determine if feature targeting matches and to track events. +Id should always be present so that it can be referenced to an existing company. + +#### Indexable + +```ts +[key: string]: ContextValue +``` + +#### Properties + + + + + + + + + + + + + + + + + + + + + +
PropertyTypeDescription
+ + `id` + + + +`undefined` \| `string` \| `number` + + + +Company id + +
+ + `name?` + + + +`string` + + + +Company name + +
+ +*** + +### Flag\ + +#### Type Parameters + + + + + + + + + + + + + + +
Type ParameterDefault type
+ +`TConfig` *extends* [`FlagType`](globals.md#flagtype)\[`"config"`\] + + + +[`EmptyFlagRemoteConfig`](globals.md#emptyflagremoteconfig) + +
+ +#### Properties + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
PropertyType
+ + `config` + + + +`Ref`\< \| [`EmptyFlagRemoteConfig`](globals.md#emptyflagremoteconfig) \| \{ `key`: `string`; \} & `TConfig`, \| [`EmptyFlagRemoteConfig`](globals.md#emptyflagremoteconfig) \| \{ `key`: `string`; \} & `TConfig`\> + +
+ + `isEnabled` + + + +`Ref`\<`boolean`, `boolean`\> + +
+ + `isLoading` + + + +`Ref`\<`boolean`, `boolean`\> + +
+ + `key` + + + +`string` + +
+ + `requestFeedback` + + + +(`opts`: [`RequestFlagFeedbackOptions`](globals.md#requestflagfeedbackoptions)) => `void` + +
+ +#### Methods + +##### track() + +```ts +track(): + | undefined + | Promise< + | undefined +| Response> +``` + +###### Returns + + \| `undefined` + \| [`Promise`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Promise)\< + \| `undefined` + \| [`Response`](https://developer.mozilla.org/docs/Web/API/Response)\> + +*** + +### Flags + +*** + +### UserContext + +Context is a set of key-value pairs. +This is used to determine if feature targeting matches and to track events. +Id should always be present so that it can be referenced to an existing user. + +#### Indexable + +```ts +[key: string]: ContextValue +``` + +#### Properties + + + + + + + + + + + + + + + + + + + + + + + + + + +
PropertyTypeDescription
+ + `email?` + + + +`string` + + + +User email + +
+ + `id` + + + +`undefined` \| `string` \| `number` + + + +User id + +
+ + `name?` + + + +`string` + + + +User name + +
+ +## Type Aliases + +### BootstrappedFlags + +```ts +type BootstrappedFlags = BootstrappedState & { + flags: RawFlags; +}; +``` + +#### Type declaration + + + + + + + + + + + + + + +
NameType
+ +`flags` + + + +[`RawFlags`](../browser-sdk/globals.md#rawflags) + +
+ +*** + +### EmptyFlagRemoteConfig + +```ts +type EmptyFlagRemoteConfig = { + key: undefined; + payload: undefined; +}; +``` + +#### Type declaration + + + + + + + + + + + + + + + + + + +
NameType
+ + `key` + + + +`undefined` + +
+ + `payload` + + + +`undefined` + +
+ +*** + +### FlagKey + +```ts +type FlagKey = keyof TypedFlags; +``` + +*** + +### FlagType + +```ts +type FlagType = { + config: { + payload: any; + }; +}; +``` + +#### Type declaration + + + + + + + + + + + + + + + + + + +
NameType
+ + `config`? + + + +\{ + `payload`: `any`; + \} + +
+ +`config.payload` + + + +`any` + +
+ +*** + +### OptInFlag + +```ts +type OptInFlag = Omit & { + key: FlagKey; +}; +``` + +An opt-in-enabled flag for the generated Vue SDK flag definitions. + +Includes all fields from [BrowserOptInFlag](../browser-sdk/globals.md#optinflag): `name`, `description`, +`isEnabled`, `userOptedIn`, `companyOptedIn`, and `isOptedIn`. +Only `key` is narrowed to the generated [FlagKey](globals.md#flagkey) type. + +#### Type declaration + + + + + + + + + + + + + + +
NameType
+ +`key` + + + +[`FlagKey`](globals.md#flagkey) + +
+ +*** + +### ReflagBaseProps + +```ts +type ReflagBaseProps = { + debug: boolean; + initialLoading: boolean; + logger: Logger; +}; +``` + +**`Internal`** + +Base props for the ReflagProvider and ReflagBootstrappedProvider. + +#### Type declaration + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
+ + `debug`? + + + +`boolean` + + + +Set to `true` to enable debug logging to the console. + +
+ + `initialLoading`? + + + +`boolean` + + + +Set to `true` to show the loading component while the client is initializing. + +
+ + `logger`? + + + +[`Logger`](../browser-sdk/globals.md#logger) + + + +A custom logger to use for SDK logs. +Use this for advanced control or filtering of SDK logs. +If both `logger` and `debug` are provided, `logger` takes precedence. + +
+ +*** + +### ReflagBootstrappedProps + +```ts +type ReflagBootstrappedProps = ReflagInitOptionsBase & ReflagBaseProps & { + flags: BootstrappedFlags; +}; +``` + +Props for the ReflagBootstrappedProvider. + +#### Type declaration + + + + + + + + + + + + + + + + +
NameTypeDescription
+ +`flags` + + + +[`BootstrappedFlags`](globals.md#bootstrappedflags) + + + +Pre-fetched flags used for the initial render. The browser client fetches opt-in +metadata on demand when opt-in flags are requested. + +
+ +*** + +### ReflagClientProviderProps + +```ts +type ReflagClientProviderProps = Omit & { + client: ReflagClient; +}; +``` + +Props for the ReflagClientProvider. + +#### Type declaration + + + + + + + + + + + + + + + + +
NameTypeDescription
+ +`client` + + + +[`ReflagClient`](../browser-sdk/globals.md#reflagclient) + + + +A pre-initialized ReflagClient to use. + +
+ +*** + +### ReflagInitOptionsBase + +```ts +type ReflagInitOptionsBase = Omit; +``` + +**`Internal`** + +Base init options for the ReflagProvider and ReflagBootstrappedProvider. + +*** + +### ReflagProps + +```ts +type ReflagProps = ReflagInitOptionsBase & ReflagBaseProps & { + company: CompanyContext; + context: ReflagContext; + otherContext: Record; + user: UserContext; +}; +``` + +Props for the ReflagProvider. + +#### Type declaration + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
+ +`company`? + + + +[`CompanyContext`](globals.md#companycontext) + + + +Company related context. If you provide `id` Reflag will enrich the evaluation context with +company attributes on Reflag servers. + +**Deprecated** + +Use `context` instead, this property will be removed in the next major version + +
+ +`context`? + + + +[`ReflagContext`](../browser-sdk/globals.md#reflagcontext) + + + +The context to use for the ReflagClient containing user, company, and other context. + +
+ +`otherContext`? + + + +[`Record`](https://www.typescriptlang.org/docs/handbook/utility-types.html#recordkeys-type)\<`string`, `string` \| `number` \| `undefined`\> + + + +Context which is not related to a user or a company. + +**Deprecated** + +Use `context` instead, this property will be removed in the next major version + +
+ +`user`? + + + +[`UserContext`](globals.md#usercontext) + + + +User related context. If you provide `id` Reflag will enrich the evaluation context with +user attributes on Reflag servers. + +**Deprecated** + +Use `context` instead, this property will be removed in the next major version + +
+ +*** + +### RequestFlagFeedbackOptions + +```ts +type RequestFlagFeedbackOptions = Omit; +``` + +*** + +### SetOptInOptions + +```ts +type SetOptInOptions = { + optedIn: boolean; + scope?: "user" | "company"; +}; +``` + +Options for changing the current user or company's opt-in membership. + +#### Type declaration + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
+ + `optedIn` + + + +`boolean` + + + +Whether the scoped subject has opted in. + +
+ + `scope`? + + + +`"user"` \| `"company"` + + + +Whether to update the current user or current company. Defaults to `user`. + +
+ +*** + +### TrackEvent + +```ts +type TrackEvent = { + attributes: | Record + | null; + company: CompanyContext; + eventName: string; + user: UserContext; +}; +``` + +#### Type declaration + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameType
+ + `attributes`? + + + + \| [`Record`](https://www.typescriptlang.org/docs/handbook/utility-types.html#recordkeys-type)\<`string`, `any`\> + \| `null` + +
+ + `company`? + + + +[`CompanyContext`](globals.md#companycontext) + +
+ + `eventName` + + + +`string` + +
+ + `user` + + + +[`UserContext`](globals.md#usercontext) + +
+ +*** + +### TypedFlags + +```ts +type TypedFlags = keyof Flags extends never ? Record : { [TypedFlagKey in keyof Flags]: Flags[TypedFlagKey] extends FlagType ? Flag : Flag }; +``` + +*** + +### UseOptInFlagsResult + +```ts +type UseOptInFlagsResult = { + flags: ComputedRef; + isLoading: ComputedRef; +}; +``` + +#### Type declaration + + + + + + + + + + + + + + + + + + +
NameType
+ + `flags` + + + +`ComputedRef`\<[`OptInFlag`](globals.md#optinflag)[]\> + +
+ + `isLoading` + + + +`ComputedRef`\<`boolean`\> + +
+ +## Variables + +### default + +```ts +default: { + install: void; +}; +``` + +#### Type declaration + + + + + + + + + + + + + + +
NameType
+ + `install()` + + + +`void` + +
+ +*** + +### ReflagBootstrappedProvider + +```ts +const ReflagBootstrappedProvider: DefineComponent, Record, unknown>; +``` + +## Functions + +### useClient() + +```ts +function useClient(): ReflagClient +``` + +Vue composable for getting the Reflag client. + +This composable returns the Reflag client. You can use this to get the Reflag +client at any point in your application. + +#### Returns + +[`ReflagClient`](../browser-sdk/globals.md#reflagclient) + +The Reflag client. + +#### Example + +```ts +import { useClient } from '@reflag/vue-sdk'; + +const client = useClient(); + +console.log(client.getContext()); +``` + +*** + +### useFlag() + +```ts +function useFlag(key: TKey): TypedFlags[TKey] +``` + +Vue composable for getting the state of a given flag for the current context. + +This composable returns an object with the state of the flag for the current context. + +#### Type Parameters + + + + + + + + + + + + +
Type Parameter
+ +`TKey` *extends* `string` + +
+ +#### Parameters + + + + + + + + + + + + + + + + +
ParameterTypeDescription
+ +`key` + + + +`TKey` + + + +The key of the flag to get the state of. + +
+ +#### Returns + +[`TypedFlags`](globals.md#typedflags)\[`TKey`\] + +An object with the state of the flag. + +#### Example + +```ts +import { useFlag } from '@reflag/vue-sdk'; + +const { isEnabled, config, track, requestFeedback } = useFlag("huddles"); + +function StartHuddlesButton() { + const { isEnabled, config: { payload }, track } = useFlag("huddles"); + if (isEnabled) { + return ; +} +``` + +*** + +### useIsLoading() + +```ts +function useIsLoading(): Ref +``` + +Vue composable for checking if the Reflag client is loading. + +This composable returns a boolean value that indicates whether the Reflag client is loading. +You can use this to check if the Reflag client is loading at any point in your application. +Initially, the value will be true until the client is initialized. + +#### Returns + +`Ref`\<`boolean`, `boolean`\> + +#### Example + +```ts +import { useIsLoading } from '@reflag/vue-sdk'; + +const isLoading = useIsLoading(); + +console.log(isLoading); +``` + +*** + +### useOnEvent() + +```ts +function useOnEvent( + event: THookType, + handler: (arg0: HookArgs[THookType]) => void, + client?: ReflagClient): void +``` + +Vue composable for listening to Reflag client events. + +#### Type Parameters + + + + + + + + + + + + +
Type Parameter
+ +`THookType` *extends* keyof [`HookArgs`](../browser-sdk/globals.md#hookargs) + +
+ +#### Parameters + + + + + + + + + + + + + + + + + + + + + + + + + + +
ParameterTypeDescription
+ +`event` + + + +`THookType` + + + +The event to listen to. + +
+ +`handler` + + + +(`arg0`: [`HookArgs`](../browser-sdk/globals.md#hookargs)\[`THookType`\]) => `void` + + + +The function to call when the event is triggered. + +
+ +`client`? + + + +[`ReflagClient`](../browser-sdk/globals.md#reflagclient) + + + +The Reflag client to listen to. If not provided, the client will be retrieved from the context. + +
+ +#### Returns + +`void` + +#### Example + +```ts +import { useOnEvent } from '@reflag/vue-sdk'; + +useOnEvent("flagsUpdated", () => { + console.log("flags updated"); +}); +``` + +*** + +### useOptInFlags() + +```ts +function useOptInFlags(): UseOptInFlagsResult +``` + +Vue composable for getting opt-in-enabled flags and their loading state for +the current context. + +The loading state is only used with `ReflagBootstrappedProvider` while +opt-in metadata is fetched on demand. Regular providers load opt-in metadata +with the initial flags; use [useIsLoading](globals.md#useisloading) for their loading state. + +If fetching opt-in metadata fails, loading ends without exposing an error. +Call `refresh()` on the client returned by [useClient](globals.md#useclient) to retry. + +#### Returns + +[`UseOptInFlagsResult`](globals.md#useoptinflagsresult) + +*** + +### useRequestFeedback() + +```ts +function useRequestFeedback(): (options: RequestFeedbackData) => void +``` + +Vue composable for requesting user feedback. + +This composable returns a function that can be used to trigger the feedback +collection flow with the Reflag SDK. You can use this to prompt users for +feedback at any point in your application. + +#### Returns + +`Function` + +A function that requests feedback from the user. The function accepts: + - `options`: An object containing feedback request options. + +##### Parameters + + + + + + + + + + + + + + +
ParameterType
+ +`options` + + + +[`RequestFeedbackData`](../browser-sdk/globals.md#requestfeedbackdata) + +
+ +##### Returns + +`void` + +#### Example + +```ts +import { useRequestFeedback } from '@reflag/vue-sdk'; + +const requestFeedback = useRequestFeedback(); + +// Request feedback from the user +requestFeedback({ + prompt: "How was your experience?", + metadata: { page: "dashboard" } +}); +``` + +*** + +### useSendFeedback() + +```ts +function useSendFeedback(): (opts: UnassignedFeedback) => Promise< + | undefined +| Response> +``` + +Vue composable for sending feedback. + +This composable returns a function that can be used to send feedback to the +Reflag SDK. You can use this to send feedback from your application. + +#### Returns + +`Function` + +A function that sends feedback to the Reflag SDK. The function accepts: + - `options`: An object containing feedback options. + +##### Parameters + + + + + + + + + + + + + + +
ParameterType
+ +`opts` + + + +[`UnassignedFeedback`](../browser-sdk/globals.md#unassignedfeedback) + +
+ +##### Returns + +[`Promise`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Promise)\< + \| `undefined` + \| [`Response`](https://developer.mozilla.org/docs/Web/API/Response)\> + +#### Example + +```ts +import { useSendFeedback } from '@reflag/vue-sdk'; + +const sendFeedback = useSendFeedback(); + +// Send feedback from the user +sendFeedback({ + feedback: "I love this flag!", + metadata: { page: "dashboard" } +}); +``` + +*** + +### useSetOptIn() + +```ts +function useSetOptIn(): (key: string, options: SetOptInOptions) => Promise< + | undefined +| Response> +``` + +Vue composable for setting whether the current user or company has opted into a flag. + +Check the returned Response's `ok` property and catch promise rejections. +HTTP failures return a non-OK Response; offline mode, invalid arguments, or +missing scoped context return undefined. Confirmation failures can reject +after the membership changed remotely. See [ReflagClient.setOptIn](../browser-sdk/globals.md#setoptin). + +#### Returns + +`Function` + +##### Parameters + + + + + + + + + + + + + + + + + + +
ParameterType
+ +`key` + + + +`string` + +
+ +`options` + + + +[`SetOptInOptions`](globals.md#setoptinoptions) + +
+ +##### Returns + +[`Promise`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Promise)\< + \| `undefined` + \| [`Response`](https://developer.mozilla.org/docs/Web/API/Response)\> + +*** + +### useTrack() + +```ts +function useTrack(): (eventName: string, attributes?: + | null + | Record) => Promise< + | undefined +| Response> +``` + +Vue composable for tracking custom events. + +This composable returns a function that can be used to track custom events +with the Reflag SDK. + +#### Returns + +`Function` + +A function that tracks an event. The function accepts: + - `eventName`: The name of the event to track. + - `attributes`: (Optional) Additional attributes to associate with the event. + +##### Parameters + + + + + + + + + + + + + + + + + + +
ParameterType
+ +`eventName` + + + +`string` + +
+ +`attributes`? + + + + \| `null` \| [`Record`](https://www.typescriptlang.org/docs/handbook/utility-types.html#recordkeys-type)\<`string`, `any`\> + +
+ +##### Returns + +[`Promise`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Promise)\< + \| `undefined` + \| [`Response`](https://developer.mozilla.org/docs/Web/API/Response)\> + +#### Example + +```ts +import { useTrack } from '@reflag/vue-sdk'; + +const track = useTrack(); + +// Track a custom event +track('button_clicked', { buttonName: 'Start Huddles' }); +``` + +*** + +### useUpdateCompany() + +```ts +function useUpdateCompany(): (opts: {}) => Promise +``` + +Vue composable for updating the company context. + +This composable returns a function that can be used to update the company +context with the Reflag SDK. You can use this to update the company context +at any point in your application. + +#### Returns + +`Function` + +A function that updates the company context. The function accepts: + - `opts`: An object containing the company context to update. + +##### Parameters + + + + + + + + + + + + + + +
ParameterType
+ +`opts` + + + +\{\} + +
+ +##### Returns + +[`Promise`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Promise)\<`void`\> + +#### Example + +```ts +import { useUpdateCompany } from '@reflag/vue-sdk'; + +const updateCompany = useUpdateCompany(); + +// Update the company context +updateCompany({ id: "123", name: "Acme Inc." }); +``` + +*** + +### useUpdateOtherContext() + +```ts +function useUpdateOtherContext(): (opts: {}) => Promise +``` + +Vue composable for updating the other context. + +This composable returns a function that can be used to update the other +context with the Reflag SDK. You can use this to update the other context +at any point in your application. + +#### Returns + +`Function` + +A function that updates the other context. The function accepts: + - `opts`: An object containing the other context to update. + +##### Parameters + + + + + + + + + + + + + + +
ParameterType
+ +`opts` + + + +\{\} + +
+ +##### Returns + +[`Promise`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Promise)\<`void`\> + +#### Example + +```ts +import { useUpdateOtherContext } from '@reflag/vue-sdk'; + +const updateOtherContext = useUpdateOtherContext(); + +// Update the other context +updateOtherContext({ id: "123", name: "Acme Inc." }); +``` + +*** + +### useUpdateUser() + +```ts +function useUpdateUser(): (opts: {}) => Promise +``` + +Vue composable for updating the user context. + +This composable returns a function that can be used to update the user context +with the Reflag SDK. You can use this to update the user context at any point +in your application. + +#### Returns + +`Function` + +A function that updates the user context. The function accepts: + - `opts`: An object containing the user context to update. + +##### Parameters + + + + + + + + + + + + + + +
ParameterType
+ +`opts` + + + +\{\} + +
+ +##### Returns + +[`Promise`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Promise)\<`void`\> + +#### Example + +```ts +import { useUpdateUser } from '@reflag/vue-sdk'; + +const updateUser = useUpdateUser(); + +// Update the user context +updateUser({ id: "123", name: "John Doe" }); +``` + +## References + +### ReflagClientProvider + +Renames and re-exports [ReflagBootstrappedProvider](globals.md#reflagbootstrappedprovider) + +### ReflagProvider + +Renames and re-exports [ReflagBootstrappedProvider](globals.md#reflagbootstrappedprovider) diff --git a/previews/sdk-versioning/v1/README.md b/previews/sdk-versioning/v1/README.md new file mode 100644 index 0000000..bb182cc --- /dev/null +++ b/previews/sdk-versioning/v1/README.md @@ -0,0 +1,20 @@ +# JavaScript SDKs — v1 + +{% hint style="info" %} +Legacy SDK documentation generated from published v1 sources: Node **1.8.3**, Browser/React **1.7.4**, and Vue **1.5.7**. The SDK READMEs and source comments in the [JavaScript repository](https://github.com/reflagcom/javascript) remain the source of truth. +{% endhint %} + +Use the **v1 / v2 variant picker** in the JavaScript SDKs section to change versions. Both variants use the same package and reference paths so equivalent pages can be matched. + +## SDK documentation + +- [React SDK](./@reflag/react-sdk/README.md) · [API reference](./@reflag/react-sdk/globals.md) +- [Vue SDK](./@reflag/vue-sdk/README.md) · [API reference](./@reflag/vue-sdk/globals.md) +- [Browser SDK](./@reflag/browser-sdk/README.md) · [API reference](./@reflag/browser-sdk/globals.md) · [Feedback](documents/browser-sdk/FEEDBACK.md) +- [Node.js SDK](./@reflag/node-sdk/README.md) · [API reference](./@reflag/node-sdk/globals.md) + +## Compatibility + +These are the wrapper-based v1 APIs. Install packages pinned to major **1**, and keep legacy declarations/CLI 1 with a v1 application. Do not mix v1 consumers with a Node v2 bootstrap snapshot. + +React Native, OpenFeature and Management have their own supported contracts; they are not upgraded by this selector. Product guides remain shared rather than duplicated into this version. diff --git a/previews/sdk-versioning/v1/SUMMARY.md b/previews/sdk-versioning/v1/SUMMARY.md new file mode 100644 index 0000000..ac7bc45 --- /dev/null +++ b/previews/sdk-versioning/v1/SUMMARY.md @@ -0,0 +1,12 @@ +# Table of contents + +- [JavaScript SDKs — v1](README.md) +- [React SDK](@reflag/react-sdk/README.md) + - [Reference](@reflag/react-sdk/globals.md) +- [Vue SDK](@reflag/vue-sdk/README.md) + - [Reference](@reflag/vue-sdk/globals.md) +- [Browser SDK](@reflag/browser-sdk/README.md) + - [Reference](@reflag/browser-sdk/globals.md) + - [Feedback](documents/browser-sdk/FEEDBACK.md) +- [Node.js SDK](@reflag/node-sdk/README.md) + - [Reference](@reflag/node-sdk/globals.md) diff --git a/previews/sdk-versioning/v1/_media/type-check-failed.png b/previews/sdk-versioning/v1/_media/type-check-failed.png new file mode 100644 index 0000000..4ffbd40 Binary files /dev/null and b/previews/sdk-versioning/v1/_media/type-check-failed.png differ diff --git a/previews/sdk-versioning/v1/_media/type-check-payload-failed.png b/previews/sdk-versioning/v1/_media/type-check-payload-failed.png new file mode 100644 index 0000000..6f7a486 Binary files /dev/null and b/previews/sdk-versioning/v1/_media/type-check-payload-failed.png differ diff --git a/previews/sdk-versioning/v1/documents/browser-sdk/FEEDBACK.md b/previews/sdk-versioning/v1/documents/browser-sdk/FEEDBACK.md new file mode 100644 index 0000000..25ea4a0 --- /dev/null +++ b/previews/sdk-versioning/v1/documents/browser-sdk/FEEDBACK.md @@ -0,0 +1,473 @@ +--- +layout: + visible: true +title: + visible: true +description: + visible: false +tableOfContents: + visible: true +outline: + visible: true +pagination: + visible: true +--- + +# Reflag Feedback UI + +The Reflag Browser SDK includes a UI you can use to collect feedback from user +about particular flags. + +![image](https://github.com/reflagcom/javascript/assets/34348/c387bac1-f2e2-4efd-9dda-5030d76f9532) + +## Global feedback configuration + +The Reflag Browser SDK feedback UI is configured with reasonable defaults, +positioning itself as a [dialog](#dialog) in the lower right-hand corner of +the viewport, displayed in English, and with a [light-mode theme](#custom-styling). + +These settings can be overwritten when initializing the Reflag Browser SDK: + +```typescript +const reflag = new ReflagClient({ + publishableKey: "reflag-publishable-key", + user: { id: "42" }, + feedback: { + ui: { + position: POSITION_CONFIG, // See positioning section + translations: TRANSLATION_KEYS, // See internationalization section + + // Enable automated feedback surveys. Default: `true` + enableAutoFeedback: boolean, + + /** + * Do your own feedback prompt handling or override + * default settings at runtime. + */ + autoFeedbackHandler: (promptMessage, handlers) => { + // See Automated Feedback Surveys section + }, + }, + }, +}); +``` + +See also: + +- [Positioning and behavior](#positioning-and-behavior) for the position option, +- [Static language configuration](#static-language-configuration) if you want to translate the feedback UI, +- [Automated feedback surveys](#automated-feedback-surveys) to override default configuration. + +## Automated feedback surveys + +Automated feedback surveys are enabled by default. + +When automated feedback surveys are enabled, the Reflag Browser SDK +will open and maintain a connection to the Reflag service. When a user +triggers an event tracked by a flag and is eligible to be prompted +for feedback, the Reflag service will send a request to the SDK instance. +By default, this request will open up the Reflag feedback UI in the user's +browser, but you can intercept the request and override this behavior. + +The live connection for automated feedback is established when the +`ReflagClient` is initialized. + +### Disabling automated feedback surveys + +You can disable automated collection in the `ReflagClient` constructor: + +```typescript +const reflag = new ReflagClient({ + publishableKey: "reflag-publishable-key", + user: { id: "42" }, + feedback: { + enableAutoFeedback: false, + }, +}); +``` + +### Overriding prompt event defaults + +If you are not satisfied with the default UI behavior when an automated prompt +event arrives, you can can [override the global defaults](#global-feedback-configuration) +or intercept and override settings at runtime like this: + +```javascript +const reflag = new ReflagClient({ + publishableKey: "reflag-publishable-key", + user: { id: "42" }, + feedback: { + autoFeedbackHandler: (promptMessage, handlers) => { + // Pass your overrides here. Everything is optional + handlers.openFeedbackForm({ + title: promptMessage.question, + + position: POSITION_CONFIG, // See positioning section + translations: TRANSLATION_KEYS, // See internationalization section + + // Trigger side effects with the collected data, + // for example posting it back into your own CRM + onAfterSubmit: (feedback) => { + storeFeedbackInCRM({ + score: feedback.score, + comment: feedback.comment, + }); + }, + }); + }, + }, +}); +``` + +See also: + +- [Positioning and behavior](#positioning-and-behavior) for the position option. +- [Runtime language configuration](#runtime-language-configuration) if you want + to translate the feedback UI. +- [Use your own UI to collect feedback](#using-your-own-ui-to-collect-feedback) if + the feedback UI doesn't match your design. + +## Manual feedback collection + +To open up the feedback collection UI, call `reflagClient.requestFeedback(options)` +with the appropriate options. This approach is particularly beneficial if you wish +to retain manual control over feedback collection from your users while leveraging +the convenience of the Reflag feedback UI to reduce the amount of code you need +to maintain. + +Examples of this could be if you want the click of a `give us feedback`-button +or the end of a specific user flow, to trigger a pop-up displaying the feedback +user interface. + +### reflagClient.requestFeedback() options + +Minimal usage with defaults: + +```javascript +reflagClient.requestFeedback({ + flagKey: "reflag-flag-key", + title: "How satisfied are you with file uploads?", +}); +``` + +All options: + +```javascript +reflagClient.requestFeedback({ + flagKey: "reflag-flag-key", // [Required] + userId: "your-user-id", // [Optional] if user persistence is + // enabled (default in browsers), + companyId: "users-company-or-account-id", // [Optional] + title: "How satisfied are you with file uploads?" // [Optional] + + position: POSITION_CONFIG, // [Optional] see the positioning section + translations: TRANSLATION_KEYS // [Optional] see the internationalization section + + // [Optional] trigger side effects with the collected data, + // for example sending the feedback to your own CRM + onAfterSubmit: (feedback) => { + storeFeedbackInCRM({ + score: feedback.score, + comment: feedback.comment + }) + } +}) +``` + +See also: + +- [Positioning and behavior](#positioning-and-behavior) for the position option. +- [Runtime language configuration](#runtime-language-configuration) if + you want to translate the feedback UI. + +## Positioning and behavior + +The feedback UI can be configured to be placed and behave in 3 different ways: + +### Positioning configuration + +#### Modal + +A modal overlay with a backdrop that blocks interaction with the underlying +page. It can be dismissed with the keyboard shortcut `` or the dedicated +close button in the top right corner. It is always centered on the page, capturing +focus, and making it the primary interface the user needs to interact with. + +![image](https://github.com/reflagcom/javascript/assets/331790/6c6efbd3-cf7d-4d5b-b126-7ac978b2e512) + +Using a modal is the strongest possible push for feedback. You are interrupting the +user's normal flow, which can cause annoyance. A good use-case for the modal is +when the user finishes a linear flow that they don't perform often, for example +setting up a new account. + +```javascript +position: { + type: "MODAL"; +} +``` + +#### Dialog + +A dialog that appears in a specified corner of the viewport, without limiting the +user's interaction with the rest of the page. It can be dismissed with the dedicated +close button, but will automatically disappear after a short time period if the user +does not interact with it. + +![image](https://github.com/reflagcom/javascript/assets/331790/30413513-fd5f-4a2c-852a-9b074fa4666c) + +Using a dialog is a soft push for feedback. It lets the user continue their work +with a minimal amount of intrusion. The user can opt-in to respond but is not +required to. A good use case for this behavior is when a user uses a flag where +the expected outcome is predictable, possibly because they have used it multiple +times before. For example: Uploading a file, switching to a different view of a +visualization, visiting a specific page, or manipulating some data. + +The default feedback UI behavior is a dialog placed in the bottom right corner of +the viewport. + +```typescript +position: { + type: "DIALOG"; + placement: "top-left" | "top-right" | "bottom-left" | "bottom-right"; + offset?: { + x?: string | number; // e.g. "-5rem", "10px" or 10 (pixels) + y?: string | number; + } +} +``` + +#### Popover + +A popover that is anchored relative to a DOM-element (typically a button). It can +be dismissed by clicking outside the popover or by pressing the dedicated close button. + +![image](https://github.com/reflagcom/javascript/assets/331790/4c5c5597-9ed3-4d4d-90c0-950926d0d967) + +You can use the popover mode to implement your own button to collect feedback manually. + +```typescript +type Position = { + type: "POPOVER"; + anchor: DOMElement; +}; +``` + +Popover feedback button example: + +```html + + +``` + +## Internationalization (i18n) + +By default, the feedback UI is written in English. However, you can supply your own +translations by passing an object in the options to either or both of the +`new ReflagClient(options)` or `reflagClient.requestFeedback(options)` calls. +These translations will replace the English ones used by the feedback interface. +See examples below. + +![image](https://github.com/reflagcom/javascript/assets/331790/68805b38-e9f6-4de5-9f55-188216983e3c) + +See [default English localization keys](https://github.com/reflagcom/javascript/tree/main/packages/browser-sdk/src/feedback/ui/config/defaultTranslations.tsx) +for a reference of what translation keys can be supplied. + +### Static language configuration + +If you know the language at page load, you can configure your translation keys while +initializing the Reflag Browser SDK: + +```typescript +new ReflagClient({ + publishableKey: "my-publishable-key", + feedback: { + ui: { + translations: { + DefaultQuestionLabel: + "Dans quelle mesure êtes-vous satisfait de cette fonctionnalité ?", + QuestionPlaceholder: + "Comment pouvons-nous améliorer cette fonctionnalité ?", + ScoreStatusDescription: "Choisissez une note et laissez un commentaire", + ScoreStatusLoading: "Chargement...", + ScoreStatusReceived: "La note a été reçue !", + ScoreVeryDissatisfiedLabel: "Très insatisfait", + ScoreDissatisfiedLabel: "Insatisfait", + ScoreNeutralLabel: "Neutre", + ScoreSatisfiedLabel: "Satisfait", + ScoreVerySatisfiedLabel: "Très satisfait", + SuccessMessage: "Merci d'avoir envoyé vos commentaires!", + SendButton: "Envoyer", + }, + }, + }, +}); +``` + +### Runtime language configuration + +If you only know the user's language after the page has loaded, you can provide +translations to either the `reflagClient.requestFeedback(options)` call or +the `autoFeedbackHandler` option before the feedback interface opens. +See examples below. + +```typescript +reflagClient.requestFeedback({ + ... // Other options + translations: { + // your translation keys + } +}) +``` + +### Translations + +When you are collecting feedback through the Reflag automation, you can intercept +the default prompt handling and override the defaults. + +If you set the prompt question in the Reflag app to be one of your own translation +keys, you can even get a translated version of the question you want to ask your +customer in the feedback UI. + +```javascript +new ReflagClient({ + publishableKey: "reflag-publishable-key", + feedback: { + autoFeedbackHandler: (message, handlers) => { + const translatedQuestion = + i18nLookup[message.question] ?? message.question; + handlers.openFeedbackForm({ + title: translatedQuestion, + translations: { + // your static translation keys + }, + }); + }, + }, +}); +``` + +## Custom styling + +You can adapt parts of the look of the Reflag feedback UI by applying CSS custom +properties to your page in your CSS `:root`-scope. + +For example, a dark mode theme might look like this: + +![image](https://github.com/reflagcom/javascript/assets/34348/5d579b7b-a830-4530-8b40-864488a8597e) + +```css +:root { + --reflag-feedback-dialog-background-color: #1e1f24; + --reflag-feedback-dialog-color: rgba(255, 255, 255, 0.92); + --reflag-feedback-dialog-secondary-color: rgba(255, 255, 255, 0.3); + --reflag-feedback-dialog-border: rgba(255, 255, 255, 0.16); + --reflag-feedback-dialog-primary-button-background-color: #655bfa; + --reflag-feedback-dialog-primary-button-color: white; + --reflag-feedback-dialog-input-border-color: rgba(255, 255, 255, 0.16); + --reflag-feedback-dialog-input-focus-border-color: rgba(255, 255, 255, 0.3); + --reflag-feedback-dialog-error-color: #f56565; + + --reflag-feedback-dialog-rating-1-color: #ed8936; + --reflag-feedback-dialog-rating-1-background-color: #7b341e; + --reflag-feedback-dialog-rating-2-color: #dd6b20; + --reflag-feedback-dialog-rating-2-background-color: #652b19; + --reflag-feedback-dialog-rating-3-color: #787c91; + --reflag-feedback-dialog-rating-3-background-color: #3e404c; + --reflag-feedback-dialog-rating-4-color: #38a169; + --reflag-feedback-dialog-rating-4-background-color: #1c4532; + --reflag-feedback-dialog-rating-5-color: #48bb78; + --reflag-feedback-dialog-rating-5-background-color: #22543d; + + --reflag-feedback-dialog-submitted-check-background-color: #38a169; + --reflag-feedback-dialog-submitted-check-color: #ffffff; +} +``` + +Other examples of custom styling can be found in our [development example style-sheet](https://github.com/reflagcom/javascript/tree/main/packages/browser-sdk/src/feedback/ui/index.css). + +## Using your own UI to collect feedback + +You may have very strict design guidelines for your app and maybe the Reflag feedback +UI doesn't quite work for you. In this case, you can implement your own feedback +collection mechanism, which follows your own design guidelines. This is the data +type you need to collect: + +```typescript +type DataToCollect = { + // Customer satisfaction score + score?: 1 | 2 | 3 | 4 | 5; + + // The comment. + comment?: string; +}; +``` + +Either `score` or `comment` must be defined in order to pass validation in the +Reflag API. + +### Manual feedback collection with custom UI + +Examples of a HTML-form that collects the relevant data can be found +in [feedback.html](https://github.com/reflagcom/javascript/tree/main/packages/browser-sdk/example/feedback/feedback.html) and [feedback.jsx](https://github.com/reflagcom/javascript/tree/main/packages/browser-sdk/example/feedback/Feedback.jsx). + +Once you have collected the feedback data, pass it along to `reflagClient.feedback()`: + +```javascript +reflagClient.feedback({ + flagKey: "reflag-flag-key", + userId: "your-user-id", + score: 5, + comment: "Best thing I've ever tried!", +}); +``` + +### Intercepting automated feedback survey events + +When using automated feedback surveys, the Reflag service will, when specified, +send a feedback prompt message to your user's instance of the Reflag Browser SDK. +This will result in the feedback UI being opened. + +You can intercept this behavior and open your own custom feedback collection form: + +```typescript +new ReflagClient({ + publishableKey: "reflag-publishable-key", + feedback: { + autoFeedbackHandler: async (promptMessage, handlers) => { + // This opens your custom UI + customFeedbackCollection({ + // The question configured in the Reflag UI for the flag + question: promptMessage.question, + // When the user successfully submits feedback data. + // Use this instead of `reflagClient.feedback()`, otherwise + // the feedback prompt handler will keep being called + // with the same prompt message + onFeedbackSubmitted: (feedback) => { + handlers.reply(feedback); + }, + // When the user closes the custom feedback form + // without leaving any response. + // It is important to feed this back, otherwise + // the feedback prompt handler will keep being called + // with the same prompt message + onFeedbackDismissed: () => { + handlers.reply(null); + }, + }); + }, + }, +}); +``` diff --git a/previews/sdk-versioning/v1/generated-manifest.json b/previews/sdk-versioning/v1/generated-manifest.json new file mode 100644 index 0000000..ca64b38 --- /dev/null +++ b/previews/sdk-versioning/v1/generated-manifest.json @@ -0,0 +1,32 @@ +{ + "status": "published-v1-sources", + "sourceRepository": "https://github.com/reflagcom/javascript", + "sourceCommit": "b4fe5b3d9386b87c6546175c323862d9878e1430", + "sdkMajor": 1, + "packages": { + "@reflag/node-sdk": "1.8.3", + "@reflag/browser-sdk": "1.7.4", + "@reflag/react-sdk": "1.7.4", + "@reflag/vue-sdk": "1.5.7" + }, + "transforms": [ + "GitBook version/source banner", + "major-pinned install commands", + "source-linked README routing", + "TypeDoc property-anchor normalization", + "TypeDoc code-block trailing whitespace" + ], + "files": { + "@reflag/node-sdk/README.md": "abb57cc1f359aa6dde1c2efa2e303d6e4e6207b30999f8356d10f0a54b41e740", + "@reflag/node-sdk/globals.md": "eaeb4cd4f0a23d96f9ebe027dab962a16fcfa0800d1b4246ded837f6f0a8c848", + "@reflag/browser-sdk/README.md": "bbe6fff035c406e2b283703883073613d2269f7f23c60b828e09cd3611d52fcd", + "@reflag/browser-sdk/globals.md": "cd87381d6bc5490623375b41231f190f4c26b9118a8f98bc858523d49ca2476a", + "@reflag/react-sdk/README.md": "45c7983809e2eb8c97bc0710c9107d368fb6ab306f9cd7798767b9f2163296c2", + "@reflag/react-sdk/globals.md": "d30061ec411518e28bd91267afb67148879215702209840b786687c6aeb5d1cf", + "@reflag/vue-sdk/README.md": "b8783f8d2f6c4ae486d80c131a63d4ddd75e455b8c3570698174d864c304775c", + "@reflag/vue-sdk/globals.md": "108ded15fb05d92db0370ef34aa2268ce877f714a5096bad8a2dc26e217b6010", + "documents/browser-sdk/FEEDBACK.md": "9a287958d63d328d244ebcc325d9fc75f3d6a575eaa777bdedecabff2d1bcc3a", + "_media/type-check-failed.png": "f04547c0bf0425bcf554aa1a1dddec56096ca9967d120bdca7737562327d4db8", + "_media/type-check-payload-failed.png": "8b53c24ecd19288111bda14eab7c490f7e26abba569eea11e16504cc7abeb54a" + } +} diff --git a/previews/sdk-versioning/v2/.gitbook.yaml b/previews/sdk-versioning/v2/.gitbook.yaml new file mode 100644 index 0000000..5241e22 --- /dev/null +++ b/previews/sdk-versioning/v2/.gitbook.yaml @@ -0,0 +1,4 @@ +root: ./ +structure: + readme: README.md + summary: SUMMARY.md diff --git a/previews/sdk-versioning/v2/@reflag/browser-sdk/README.md b/previews/sdk-versioning/v2/@reflag/browser-sdk/README.md new file mode 100644 index 0000000..808c4c6 --- /dev/null +++ b/previews/sdk-versioning/v2/@reflag/browser-sdk/README.md @@ -0,0 +1,167 @@ +--- +layout: + visible: true +title: + visible: true +description: + visible: false +tableOfContents: + visible: true +outline: + visible: true +pagination: + visible: true +--- + +{% hint style="info" %} +SDK v2 candidate documentation, not a published release. Installation commands target the planned major; matching SDK/CLI releases are still required. Generated from `@reflag/browser-sdk@2.0.0-next.0` in [the JavaScript repository](https://github.com/reflagcom/javascript/tree/9f3a0ae027e386747a2d4cf7683a68dcab70b355/packages/browser-sdk). SDK READMEs and code comments there remain authoritative. +{% endhint %} + +# Reflag Browser SDK v2 + +Client-side feature flags and JSON values for [Reflag](https://reflag.com). + +> This is a breaking v2 prerelease. It requires the v2 evaluated-flags/check protocol on the server. Do not switch existing v1 applications or bootstrap consumers without coordinating that upgrade. Nothing in this branch publishes or deploys the SDK/server. + +## Get started + +```ts +import { ReflagClient } from "@reflag/browser-sdk"; + +const reflag = new ReflagClient({ + publishableKey: "your-publishable-key", + user: { id: "alice", role: "manager" }, + company: { id: "acme", plan: "enterprise" }, +}); +await reflag.initialize(); + +const enabled = reflag.getFlag("huddles"); // boolean, implicit false +const settings = reflag.getFlag("assistant-settings", { model: "quick" }); +// settings: { model: string } + +if (enabled) { + // Application usage tracking is separate from reading the flag. + await reflag.track("Started huddle"); +} +``` + +Use only a **publishable** key in a browser. Secret keys belong on your server. + +## Direct-value reads + +```ts +reflag.getFlag("release"); // boolean; missing/mismatched -> false +reflag.getFlag("model", "quick"); // string +reflag.getFlag("limit", 10); // number +reflag.getFlag("settings", { model: "quick" }); +reflag.getFlag("nullable-settings", null); // FlagValue (arbitrary JSON) +``` + +- Defaults are positional JSON values; generation is not required for useful types. Primitive literals widen, and readonly defaults are accepted. +- A non-null default accepts only the same **top-level JSON category**: Boolean, number, string, array or object. A mismatch warns without logging the payload and returns the default. A bare read can never return a truthy object. +- Explicit `null` accepts any JSON value, including authored null. Missing values return null. +- Falsy selected values (`false`, `0`, `""`, null) are preserved when accepted by the requested category. +- Object fields and array elements are **not schema-validated**. Validate untrusted/nested payloads in your application. +- Values and defaults are returned as independent mutable copies. There are no reserved JSON property names. + +`getFlags()` returns a direct-value record (entries may be undefined) and registers checks for its returned roots. `getFlagResults()` returns owned evaluated diagnostics **without checks**; it is intended for tooling, not ordinary application reads. `getFlagsForBootstrap()` serializes protocol-v2 evaluated state without checks. + +## Checks and delivery + +Calling a getter registers a requested-root check immediately, even if its return value is ignored. There are no `.isEnabled`, `.config`, `.value` or flag `.track()` wrappers. Delivery remains buffered through `/bulk` and identity-aware rate-limited. + +A selected check carries the authored selected key as `evalResult`, its `sourceVersionId` and the available snapshot `flagStateVersion`. Payload equality is not identity: equal-valued selections or segment-only snapshot changes remain distinguishable on subsequent reads. Defaults, category mismatches, local overrides and static fallback values do not fabricate authored selections. Diagnostics and bootstrap serialization generate no checks. The server evaluates dependencies; fetching their results does not itself register reads. + +`enableTracking: false` disables checks and user/company/usage tracking. `offline: true` skips HTTP and tracking. `await reflag.stop()` flushes buffered events and closes the client's background transports. + +## Context, refresh and cache + +```ts +await reflag.updateUser({ role: "admin" }); +await reflag.updateCompany({ plan: "enterprise" }); +await reflag.updateOtherContext({ region: "eu" }); +await reflag.setContext({ + user: { id: "bob" }, + company: { id: "other-company" }, +}); +await reflag.refresh(); +``` + +`setContext()` replaces the context; update methods shallow-merge their section. User/company sections require IDs, including valid numeric zero. Context supports nested JSON and arrays. A context change clears the old context's values while fetching; stale/in-flight responses cannot replace a newer context or snapshot. + +Values come from `GET /flags/evaluated`, not legacy `/features/evaluated`. Server evaluation retains the complete internal graph and authorized public roots. The Browser SDK does not cache dependency evaluations of its own. + +Constructor options include: + +| Option | Behavior | +| ------------------------------- | --------------------------------------------------------------------------- | +| `apiBaseUrl`, `appBaseUrl` | API origin and toolbar/feedback links | +| `credentials` | Fetch credentials mode; `include` also enables default SSE credentials | +| `timeoutMs` | Flag request timeout; default 5,000 ms | +| `staleTimeMs` | Fresh-cache duration; default 0 | +| `staleWhileRevalidate` | Serve stale cached state while revalidating; default false | +| `expireTimeMs` | Cache expiry; default 30 days | +| `storage` | Optional async storage adapter | +| `fallbackFlags` | Value record or array of enabled Boolean keys, without authored provenance | +| `enableLiveFlagUpdates` | Refresh after SSE publications; default false in Browser, true in React/Vue | +| `eventSourceFactory` | Optional EventSource-compatible transport | +| `trackingQueue` | Buffered-delivery `flushDelayMs` and `maxSize` | +| `logger`, `toolbar`, `feedback` | Logging, Boolean override toolbar and feedback options | + +Evaluated cache and override storage use isolated v2 keys. V1 cache entries are never interpreted as v2 values. Malformed refreshes leave known-good state intact. Valid saved v2 selections retain their real provenance on restore. There is no automatic periodic HTTP fallback after an SSE failure; manual refresh/context updates remain available. + +## Overrides and toolbar + +```ts +reflag.setFlagOverride("release", true); +reflag.setFlagOverride("settings", { model: "local" }); +reflag.setFlagOverride("nullable-settings", null); // a real null override +reflag.setFlagOverride("release", undefined); // clear the override +``` + +Overrides apply after server evaluation and do not rewrite authored dependency results. The built-in toolbar appears on localhost by default and offers Boolean overrides only. It uses diagnostic access, so opening it does not register application checks. + +## Server bootstrap + +Generate the full payload with **Node SDK v2** on your server: + +```ts +const bootstrappedState = serverClient.getFlagsForBootstrap(context); +// Send via your framework's safe JSON/hydration mechanism. +``` + +```ts +const reflag = new ReflagClient({ publishableKey, bootstrappedState }); +await reflag.initialize(); +const enabled = reflag.getFlag("release"); +``` + +The payload includes `protocolVersion: 2`, context, evaluated diagnostics/selections and an optional snapshot version. V1 payloads and the old `bootstrappedFlags` option are rejected. Preserve the entire envelope; do not pass just its `flags` property. A version is required for bootstrapped live updates. Never embed secrets or server-only context in public bootstrap data. Later refreshes use browser-visible context and can differ from server-only evaluation; disable live updates when that is inappropriate. + +## End-user opt-in + +```ts +const options = reflag.getOptInFlags(); +const loading = reflag.getIsLoadingOptInFlags(); +await reflag.setOptIn("huddles", { optedIn: true }); +await reflag.setOptIn("huddles", { optedIn: false, scope: "company" }); +``` + +The list contains presentation/membership metadata (`key`, `name`, `description`, `isEnabled`, `userOptedIn`, `companyOptedIn`, `isOptedIn`); this is not the ordinary flag getter API. User/company grants remain independent. The selected scope requires its ID. Successful mutations wait for versioned evaluation and membership confirmation. + +Metadata arrives with ordinary `/flags/evaluated` responses and is cached with the evaluated snapshot. Bootstrapped clients whose snapshot lacks metadata fetch it on demand from the same v2 endpoint, without replacing server-derived values or provenance with browser-context evaluations. Loading settles on success or failure, including an empty list. Opt-in mutations confirm values and membership together in one versioned refresh. `optInFlagsLoadingUpdated` and `flagsUpdated` support UI subscriptions. + +## Tracking, feedback and events + +Use `track(eventName, attributes?)`, `feedback(payload)` and `requestFeedback({ flagKey, ... })` on the client. See [FEEDBACK.md](../../documents/browser-sdk/FEEDBACK.md) for the feedback UI and prompting configuration. + +`on(event, handler)` returns an unsubscribe function. Events include `stateUpdated`, `flagsUpdated` (evaluated diagnostics), `check`, `track`, `user`, `company` and `optInFlagsLoadingUpdated`. + +## Migration from v1 + +- Replace `getFlag(key).isEnabled` with `getFlag(key)`. +- Replace legacy remote config access with a separately authored multivariate flag and a positional default. Legacy config is **not** automatically converted into v2 values. +- Move flag `.track()` / `.requestFeedback()` calls to ordinary client methods. +- Replace wrapper overrides with `setFlagOverride(key, value)`; undefined clears, null is a value. +- `getFlags()` now returns values and registers checks. Tooling should use `getFlagResults()`. +- Upgrade both ends of bootstrap to v2. Old Browser/framework clients remain incompatible with v2 bootstrap. diff --git a/previews/sdk-versioning/v2/@reflag/browser-sdk/globals.md b/previews/sdk-versioning/v2/@reflag/browser-sdk/globals.md new file mode 100644 index 0000000..9333955 --- /dev/null +++ b/previews/sdk-versioning/v2/@reflag/browser-sdk/globals.md @@ -0,0 +1,5258 @@ +--- +layout: + visible: true +title: + visible: true +description: + visible: false +tableOfContents: + visible: true +outline: + visible: true +pagination: + visible: true +--- + +{% hint style="info" %} +SDK v2 candidate documentation, not a published release. Installation commands target the planned major; matching SDK/CLI releases are still required. Generated from `@reflag/browser-sdk@2.0.0-next.0` in [the JavaScript repository](https://github.com/reflagcom/javascript/tree/9f3a0ae027e386747a2d4cf7683a68dcab70b355/packages/browser-sdk). SDK READMEs and code comments there remain authoritative. +{% endhint %} + +# @reflag/browser-sdk + +## Classes + +### ReflagClient + +ReflagClient lets you interact with the Reflag API. + +#### Constructors + +##### new ReflagClient() + +```ts +new ReflagClient(opts: InitOptions): ReflagClient +``` + +Create a new ReflagClient instance. + +###### Parameters + + + + + + + + + + + + + + +
ParameterType
+ +`opts` + + + +[`InitOptions`](globals.md#initoptions) + +
+ +###### Returns + +[`ReflagClient`](globals.md#reflagclient) + +#### Properties + + + + + + + + + + + + + + + + +
PropertyModifierType
+ + `logger` + + + +`readonly` + + + +[`Logger`](globals.md#logger) + +
+ +#### Methods + +##### applyBootstrappedState() + +```ts +applyBootstrappedState(bootstrappedState: BootstrappedState, triggerEvent: boolean): void +``` + +###### Parameters + + + + + + + + + + + + + + + + + + + + + +
ParameterTypeDefault value
+ +`bootstrappedState` + + + +[`BootstrappedState`](globals.md#bootstrappedstate) + + + +`undefined` + +
+ +`triggerEvent` + + + +`boolean` + + + +`true` + +
+ +###### Returns + +`void` + +##### feedback() + +```ts +feedback(payload: Feedback): Promise< + | undefined +| Response> +``` + +Submit user feedback to Reflag. Must include either `score` or `comment`, or both. + +###### Parameters + + + + + + + + + + + + + + + + +
ParameterTypeDescription
+ +`payload` + + + +[`Feedback`](globals.md#feedback) + + + +The feedback details to submit. + +
+ +###### Returns + +[`Promise`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Promise)\< + \| `undefined` + \| [`Response`](https://developer.mozilla.org/docs/Web/API/Response)\> + +The server response. + +##### getConfig() + +```ts +getConfig(): Config +``` + +Get the current configuration. + +###### Returns + +[`Config`](globals.md#config) + +##### getContext() + +```ts +getContext(): ReflagContext +``` + +Get the current context. + +###### Returns + +[`ReflagContext`](globals.md#reflagcontext) + +##### ~~getFeatures()~~ + +```ts +getFeatures(): Record +``` + +###### Returns + +[`Record`](https://www.typescriptlang.org/docs/handbook/utility-types.html#recordkeys-type)\<`string`, `undefined` \| [`FlagValue`](globals.md#flagvalue)\> + +###### Deprecated + +Use `getFlags` instead. + +##### getFlag() + +###### Call Signature + +```ts +getFlag(key: string, defaultValue?: undefined): boolean +``` + +Boolean-first reads; positional defaults select other top-level JSON categories. + +###### Parameters + + + + + + + + + + + + + + + + + + +
ParameterType
+ +`key` + + + +`string` + +
+ +`defaultValue`? + + + +`undefined` + +
+ +###### Returns + +`boolean` + +###### Call Signature + +```ts +getFlag(key: string, defaultValue: Default): FlagValueFromDefault +``` + +Boolean-first reads; positional defaults select other top-level JSON categories. + +###### Type Parameters + + + + + + + + + + + + +
Type Parameter
+ +`Default` *extends* `object` \| [`FlagDefaultValue`](globals.md#flagdefaultvalue) + +
+ +###### Parameters + + + + + + + + + + + + + + + + + + +
ParameterType
+ +`key` + + + +`string` + +
+ +`defaultValue` + + + +`Default` + +
+ +###### Returns + +[`FlagValueFromDefault`](globals.md#flagvaluefromdefaultt)\<`Default`\> + +##### getFlagOverride() + +```ts +getFlagOverride(key: string): undefined | FlagValue +``` + +###### Parameters + + + + + + + + + + + + + + +
ParameterType
+ +`key` + + + +`string` + +
+ +###### Returns + +`undefined` \| [`FlagValue`](globals.md#flagvalue) + +##### getFlagResults() + +```ts +getFlagResults(): RawFlags +``` + +Owned evaluated diagnostics; inspecting these is not an application read. + +###### Returns + +[`RawFlags`](globals.md#rawflags) + +##### getFlags() + +```ts +getFlags(): Record +``` + +Return direct values and register checks for these returned roots. +Use getFlagResults() for diagnostic/serialization access without checks. + +###### Returns + +[`Record`](https://www.typescriptlang.org/docs/handbook/utility-types.html#recordkeys-type)\<`string`, `undefined` \| [`FlagValue`](globals.md#flagvalue)\> + +##### getFlagsForBootstrap() + +```ts +getFlagsForBootstrap(): BootstrappedState +``` + +###### Returns + +[`BootstrappedState`](globals.md#bootstrappedstate) + +##### getIsLoadingOptInFlags() + +```ts +getIsLoadingOptInFlags(): boolean +``` + +Returns whether opt-in flags are loading for the current context. + +Calling this method requests opt-in metadata if it is not already available. + +###### Returns + +`boolean` + +##### getOptInFlags() + +```ts +getOptInFlags(): OptInFlag[] +``` + +Returns opt-in-enabled flags for the current context. + +###### Returns + +[`OptInFlag`](globals.md#optinflag)[] + +##### getState() + +```ts +getState(): State +``` + +###### Returns + +[`State`](globals.md#state) + +##### initialize() + +```ts +initialize(): Promise +``` + +Initialize the Reflag SDK. + +Must be called before calling other SDK methods. + +###### Returns + +[`Promise`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Promise)\<`void`\> + +##### off() + +```ts +off(type: THookType, handler: (args0: HookArgs[THookType]) => void): void +``` + +Remove an event listener + +###### Type Parameters + + + + + + + + + + + + +
Type Parameter
+ +`THookType` *extends* keyof [`HookArgs`](globals.md#hookargs) + +
+ +###### Parameters + + + + + + + + + + + + + + + + + + + + + +
ParameterTypeDescription
+ +`type` + + + +`THookType` + + + +Type of event to remove. + +
+ +`handler` + + + +(`args0`: [`HookArgs`](globals.md#hookargs)\[`THookType`\]) => `void` + + + +The same function that was passed to `on`. + +
+ +###### Returns + +`void` + +A function to remove the hook. + +##### on() + +```ts +on(type: THookType, handler: (args0: HookArgs[THookType]) => void): () => void +``` + +Add an event listener + +###### Type Parameters + + + + + + + + + + + + +
Type Parameter
+ +`THookType` *extends* keyof [`HookArgs`](globals.md#hookargs) + +
+ +###### Parameters + + + + + + + + + + + + + + + + + + + + + +
ParameterTypeDescription
+ +`type` + + + +`THookType` + + + +Type of events to listen for + +
+ +`handler` + + + +(`args0`: [`HookArgs`](globals.md#hookargs)\[`THookType`\]) => `void` + + + +The function to call when the event is triggered. + +
+ +###### Returns + +`Function` + +A function to remove the hook. + +###### Returns + +`void` + +##### refresh() + +```ts +refresh(): Promise +``` + +Force refresh flags from the API, bypassing cache. + +###### Returns + +[`Promise`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Promise)\<`undefined` \| [`RawFlags`](globals.md#rawflags)\> + +##### requestFeedback() + +```ts +requestFeedback(options: RequestFeedbackData): void +``` + +Display the Reflag feedback form UI programmatically. + +This can be used to collect feedback from users in Reflag in cases where Automated Feedback Surveys isn't appropriate. + +###### Parameters + + + + + + + + + + + + + + +
ParameterType
+ +`options` + + + +[`RequestFeedbackData`](globals.md#requestfeedbackdata) + +
+ +###### Returns + +`void` + +##### setContext() + +```ts +setContext(context: ReflagDeprecatedContext): Promise +``` + +Update the context. +Replaces the existing context with a new context. + +###### Parameters + + + + + + + + + + + + + + + + +
ParameterTypeDescription
+ +`context` + + + +[`ReflagDeprecatedContext`](globals.md#reflagdeprecatedcontext) + + + +The context to update. + +
+ +###### Returns + +[`Promise`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Promise)\<`void`\> + +##### setFlagOverride() + +```ts +setFlagOverride(key: string, value: undefined | FlagValue): void +``` + +Overrides are applied after server evaluation and never invent selections. + +###### Parameters + + + + + + + + + + + + + + + + + + +
ParameterType
+ +`key` + + + +`string` + +
+ +`value` + + + +`undefined` \| [`FlagValue`](globals.md#flagvalue) + +
+ +###### Returns + +`void` + +##### setOptIn() + +```ts +setOptIn(flagKey: string, options: SetOptInOptions): Promise< + | undefined +| Response> +``` + +Set whether the current user or company has opted into a flag. + +###### Parameters + + + + + + + + + + + + + + + + + + +
ParameterType
+ +`flagKey` + + + +`string` + +
+ +`options` + + + +[`SetOptInOptions`](globals.md#setoptinoptions) + +
+ +###### Returns + +[`Promise`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Promise)\< + \| `undefined` + \| [`Response`](https://developer.mozilla.org/docs/Web/API/Response)\> + +##### showToolbarToggle() + +```ts +showToolbarToggle(position?: ToolbarPosition): void +``` + +###### Parameters + + + + + + + + + + + + + + +
ParameterType
+ +`position`? + + + +[`ToolbarPosition`](globals.md#toolbarposition) + +
+ +###### Returns + +`void` + +##### stop() + +```ts +stop(): Promise +``` + +Stop the SDK. +This will stop any automated feedback surveys. + +###### Returns + +[`Promise`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Promise)\<`void`\> + +##### track() + +```ts +track(eventName: string, attributes?: + | null + | Record): Promise< + | undefined +| Response> +``` + +Track an event in Reflag. + +###### Parameters + + + + + + + + + + + + + + + + + + + + + +
ParameterTypeDescription
+ +`eventName` + + + +`string` + + + +The name of the event. + +
+ +`attributes`? + + + + \| `null` \| [`Record`](https://www.typescriptlang.org/docs/handbook/utility-types.html#recordkeys-type)\<`string`, `any`\> + + + +Any attributes you want to attach to the event. + +
+ +###### Returns + +[`Promise`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Promise)\< + \| `undefined` + \| [`Response`](https://developer.mozilla.org/docs/Web/API/Response)\> + +##### updateCompany() + +```ts +updateCompany(company: {}): Promise +``` + +Update the company context. +Performs a shallow merge with the existing company context. +It will not update the context if nothing has changed. + +###### Parameters + + + + + + + + + + + + + + + + +
ParameterTypeDescription
+ +`company` + + + +\{\} + + + +The company details. + +
+ +###### Returns + +[`Promise`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Promise)\<`void`\> + +##### updateFlags() + +```ts +updateFlags( + flags: RawFlags, + triggerEvent: boolean, + flagStateVersion?: number): void +``` + +Update the flags. + +###### Parameters + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ParameterTypeDefault valueDescription
+ +`flags` + + + +[`RawFlags`](globals.md#rawflags) + + + +`undefined` + + + +The flags to update. + +
+ +`triggerEvent` + + + +`boolean` + + + +`true` + + + +Whether to trigger the `flagsUpdated` event. + +
+ +`flagStateVersion`? + + + +`number` + + + +`undefined` + + + +‐ + +
+ +###### Returns + +`void` + +##### updateOtherContext() + +```ts +updateOtherContext(otherContext: {}): Promise +``` + +Update the company context. +Performs a shallow merge with the existing company context. +It will not update the context if nothing has changed. + +###### Parameters + + + + + + + + + + + + + + + + +
ParameterTypeDescription
+ +`otherContext` + + + +\{\} + + + +Additional context. + +
+ +###### Returns + +[`Promise`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Promise)\<`void`\> + +##### updateUser() + +```ts +updateUser(user: {}): Promise +``` + +Update the user context. +Performs a shallow merge with the existing user context. +It will not update the context if nothing has changed. + +###### Parameters + + + + + + + + + + + + + + +
ParameterType
+ +`user` + + + +\{\} + +
+ +###### Returns + +[`Promise`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Promise)\<`void`\> + +## Interfaces + +### CheckEvent + +A root read records selection identity, never its JSON payload. + +#### Properties + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
PropertyType
+ + `action` + + + +`"check"` + +
+ + `evalContext?` + + + +[`ReflagContext`](globals.md#reflagcontext) + +
+ + `evalErrors?` + + + +[`EvaluationError`](globals.md#evaluationerror)[] + +
+ + `evalMissingFields?` + + + +`string`[] + +
+ + `evalResult?` + + + +`string` + +
+ + `evalRuleResults?` + + + +`boolean`[] + +
+ + `flagStateVersion?` + + + +`number` + +
+ + `key` + + + +`string` + +
+ + `sourceVersionId?` + + + +`string` + +
+ +*** + +### CompanyContext + +Context is a set of key-value pairs. +This is used to determine if feature targeting matches and to track events. +Id should always be present so that it can be referenced to an existing company. + +#### Indexable + +```ts +[key: string]: ContextValue +``` + +#### Properties + + + + + + + + + + + + + + + + + + + + + +
PropertyTypeDescription
+ + `id` + + + +`undefined` \| `string` \| `number` + + + +Company id + +
+ + `name?` + + + +`string` + + + +Company name + +
+ +*** + +### Config + +ReflagClient configuration. + +#### Properties + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
PropertyTypeDescription
+ + `apiBaseUrl` + + + +`string` + + + +Base URL of Reflag servers. + +
+ + `appBaseUrl` + + + +`string` + + + +Base URL of the Reflag web app. + +
+ + `bootstrapped` + + + +`boolean` + + + +Whether the client is bootstrapped. + +
+ + `enableTracking` + + + +`boolean` + + + +Whether to enable tracking. + +
+ + `offline` + + + +`boolean` + + + +Whether to enable offline mode. + +
+ + `sseBaseUrl` + + + +`string` + + + +Base URL used for pubsub SSE connections. +Defaults to `apiBaseUrl`. + +
+ +*** + +### FeedbackScoreSubmission + +#### Properties + + + + + + + + + + + + + + + + + + + + + + +
PropertyType
+ + `feedbackId?` + + + +`string` + +
+ + `question` + + + +`string` + +
+ + `score` + + + +`number` + +
+ +*** + +### FeedbackSubmission + +#### Properties + + + + + + + + + + + + + + + + + + + + + + + + + + +
PropertyType
+ + `comment` + + + +`string` + +
+ + `feedbackId?` + + + +`string` + +
+ + `question` + + + +`string` + +
+ + `score` + + + +`number` + +
+ +*** + +### HookArgs + +#### Properties + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
PropertyTypeDescription
+ + `check` + + + +[`CheckEvent`](globals.md#checkevent) + + + +‐ + +
+ + `company` + + + +[`CompanyContext`](globals.md#companycontext) + + + +‐ + +
+ + ~~`featuresUpdated`~~ + + + +[`RawFlags`](globals.md#rawflags) + + + +**Deprecated** + +Use `flagsUpdated` instead. + +
+ + `flagsUpdated` + + + +[`RawFlags`](globals.md#rawflags) + + + +‐ + +
+ + `optInFlagsLoadingUpdated` + + + +`boolean` + + + +‐ + +
+ + `stateUpdated` + + + +[`State`](globals.md#state) + + + +‐ + +
+ + `track` + + + +[`TrackEvent`](globals.md#trackevent) + + + +‐ + +
+ + `user` + + + +[`UserContext`](globals.md#usercontext) + + + +‐ + +
+ +*** + +### Logger + +#### Methods + +##### debug() + +```ts +debug(message: string, ...args: any[]): void +``` + +###### Parameters + + + + + + + + + + + + + + + + + + +
ParameterType
+ +`message` + + + +`string` + +
+ +...`args` + + + +`any`[] + +
+ +###### Returns + +`void` + +##### error() + +```ts +error(message: string, ...args: any[]): void +``` + +###### Parameters + + + + + + + + + + + + + + + + + + +
ParameterType
+ +`message` + + + +`string` + +
+ +...`args` + + + +`any`[] + +
+ +###### Returns + +`void` + +##### info() + +```ts +info(message: string, ...args: any[]): void +``` + +###### Parameters + + + + + + + + + + + + + + + + + + +
ParameterType
+ +`message` + + + +`string` + +
+ +...`args` + + + +`any`[] + +
+ +###### Returns + +`void` + +##### warn() + +```ts +warn(message: string, ...args: any[]): void +``` + +###### Parameters + + + + + + + + + + + + + + + + + + +
ParameterType
+ +`message` + + + +`string` + +
+ +...`args` + + + +`any`[] + +
+ +###### Returns + +`void` + +*** + +### OnScoreSubmitResult + +#### Properties + + + + + + + + + + + + + + +
PropertyType
+ + `feedbackId` + + + +`string` + +
+ +*** + +### OpenFeedbackFormOptions + +#### Properties + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
PropertyTypeDescription
+ + `key` + + + +`string` + + + +‐ + +
+ + `onClose?` + + + +() => `void` + + + +‐ + +
+ + `onDismiss?` + + + +() => `void` + + + +‐ + +
+ + `onScoreSubmit?` + + + +(`data`: [`FeedbackScoreSubmission`](globals.md#feedbackscoresubmission)) => [`Promise`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Promise)\<[`OnScoreSubmitResult`](globals.md#onscoresubmitresult)\> + + + +‐ + +
+ + `onSubmit` + + + +(`data`: [`FeedbackSubmission`](globals.md#feedbacksubmission)) => \| `void` \| [`Promise`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Promise)\<`void`\> + + + +‐ + +
+ + `openWithCommentVisible?` + + + +`boolean` + + + +Open the form with both the score and comment fields visible. +Defaults to `false` + +
+ + `position?` + + + +[`Position`](globals.md#position) + + + +Control the placement and behavior of the feedback form. + +
+ + `title?` + + + +`string` + + + +‐ + +
+ + `translations?` + + + +[`Partial`](https://www.typescriptlang.org/docs/handbook/utility-types.html#partialtype)\<[`FeedbackTranslations`](globals.md#feedbacktranslations)\> + + + +Add your own custom translations for the feedback form. +Undefined translation keys fall back to english defaults. + +
+ +*** + +### ReflagContext + +Context is a set of key-value pairs. +This is used to determine if feature targeting matches and to track events. + +#### Extended by + +- [`ReflagDeprecatedContext`](globals.md#reflagdeprecatedcontext) + +#### Properties + + + + + + + + + + + + + + + + + + + + + + + + + + +
PropertyTypeDescription
+ + `company?` + + + +[`CompanyContext`](globals.md#companycontext) + + + +Company related context. If you provide `id` Reflag will enrich the evaluation context with +company attributes on Reflag servers. + +
+ + `other?` + + + +[`Record`](https://www.typescriptlang.org/docs/handbook/utility-types.html#recordkeys-type)\<`string`, [`ContextValue`](globals.md#contextvalue)\> + + + +Context which is not related to a user or a company. + +
+ + `user?` + + + +[`UserContext`](globals.md#usercontext) + + + +User related context. If you provide `id` Reflag will enrich the evaluation context with +user attributes on Reflag servers. + +
+ +*** + +### ~~ReflagDeprecatedContext~~ + +**`Internal`** + +#### Deprecated + +Use `ReflagContext` instead, this interface will be removed in the next major version + +#### Extends + +- [`ReflagContext`](globals.md#reflagcontext) + +#### Properties + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
PropertyTypeDescription
+ + ~~`company?`~~ + + + +[`CompanyContext`](globals.md#companycontext) + + + +Company related context. If you provide `id` Reflag will enrich the evaluation context with +company attributes on Reflag servers. + +
+ + ~~`other?`~~ + + + +[`Record`](https://www.typescriptlang.org/docs/handbook/utility-types.html#recordkeys-type)\<`string`, [`ContextValue`](globals.md#contextvalue)\> + + + +Context which is not related to a user or a company. + +
+ + ~~`otherContext?`~~ + + + +[`Record`](https://www.typescriptlang.org/docs/handbook/utility-types.html#recordkeys-type)\<`string`, [`ContextValue`](globals.md#contextvalue)\> + + + +Context which is not related to a user or a company. + +**Deprecated** + +Use `other` instead, this property will be removed in the next major version + +
+ + ~~`user?`~~ + + + +[`UserContext`](globals.md#usercontext) + + + +User related context. If you provide `id` Reflag will enrich the evaluation context with +user attributes on Reflag servers. + +
+ +*** + +### ToolbarPosition + +#### Properties + + + + + + + + + + + + + + + + + + +
PropertyType
+ + `offset?` + + + +[`Offset`](globals.md#offset) + +
+ + `placement` + + + +[`DialogPlacement`](globals.md#dialogplacement) + +
+ +*** + +### UserContext + +Context is a set of key-value pairs. +This is used to determine if feature targeting matches and to track events. +Id should always be present so that it can be referenced to an existing user. + +#### Indexable + +```ts +[key: string]: ContextValue +``` + +#### Properties + + + + + + + + + + + + + + + + + + + + + + + + + + +
PropertyTypeDescription
+ + `email?` + + + +`string` + + + +User email + +
+ + `id` + + + +`undefined` \| `string` \| `number` + + + +User id + +
+ + `name?` + + + +`string` + + + +User name + +
+ +## Type Aliases + +### BootstrappedState + +```ts +type BootstrappedState = { + context: ReflagContext; + flags: RawFlags; + flagStateVersion: number; + protocolVersion: 2; +}; +``` + +Pre-fetched evaluated state used to bootstrap the client. + +#### Type declaration + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameType
+ + `context` + + + +[`ReflagContext`](globals.md#reflagcontext) + +
+ + `flags` + + + +[`RawFlags`](globals.md#rawflags) + +
+ + `flagStateVersion`? + + + +`number` + +
+ + `protocolVersion` + + + +`2` + +
+ +*** + +### ContextValue + +```ts +type ContextValue = DefinedContextValue | undefined; +``` + +A context value. Object properties set to `undefined` are omitted. + +*** + +### DefinedContextValue + +```ts +type DefinedContextValue = + | string + | number + | boolean + | null + | DefinedContextValue[] +| {}; +``` + +A JSON-compatible context value that is not `undefined`. + +*** + +### DialogPlacement + +```ts +type DialogPlacement = "bottom-right" | "bottom-left" | "top-right" | "top-left"; +``` + +*** + +### EvaluationError + +```ts +type EvaluationError = { + code: string; + field: string; + message: string; + operator: string; + source: { + key: string; + type: "flag" | "segment"; + }; +}; +``` + +#### Type declaration + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameType
+ + `code` + + + +`string` + +
+ + `field`? + + + +`string` + +
+ + `message` + + + +`string` + +
+ + `operator`? + + + +`string` + +
+ + `source`? + + + +\{ + `key`: `string`; + `type`: `"flag"` \| `"segment"`; + \} + +
+ +`source.key` + + + +`string` + +
+ +`source.type` + + + +`"flag"` \| `"segment"` + +
+ +*** + +### EventSourceFactory() + +```ts +type EventSourceFactory = (url: string) => EventSourceLike; +``` + +#### Parameters + + + + + + + + + + + + + + +
ParameterType
+ +`url` + + + +`string` + +
+ +#### Returns + +[`EventSourceLike`](globals.md#eventsourcelike) + +*** + +### EventSourceLike + +```ts +type EventSourceLike = { + addEventListener: (type: string, cb: (event: any) => void) => void; + close: () => void; +}; +``` + +#### Type declaration + + + + + + + + + + + + + + + + + + +
NameType
+ + `addEventListener` + + + +(`type`: `string`, `cb`: (`event`: `any`) => `void`) => `void` + +
+ + `close` + + + +() => `void` + +
+ +*** + +### FallbackFlagOverride + +```ts +type FallbackFlagOverride = FlagValue; +``` + +*** + +### Feedback + +```ts +type Feedback = UnassignedFeedback & { + companyId: string; + userId: string; +}; +``` + +#### Type declaration + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
+ +`companyId`? + + + +`string` + + + +Company ID from your own application. + +
+ +`userId`? + + + +`string` + + + +User ID from your own application. + +
+ +*** + +### FeedbackOptions + +```ts +type FeedbackOptions = { + autoFeedbackHandler: FeedbackPromptHandler; + enableAutoFeedback: boolean; + ui: { + position: Position; + translations: Partial; + }; +}; +``` + +#### Type declaration + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
+ + `autoFeedbackHandler`? + + + +[`FeedbackPromptHandler`](globals.md#feedbackprompthandler) + + + +‐ + +
+ + `enableAutoFeedback`? + + + +`boolean` + + + +Enables automatic feedback prompting if it's set up in Reflag + +
+ + `ui`? + + + +\{ + `position`: [`Position`](globals.md#position); + `translations`: [`Partial`](https://www.typescriptlang.org/docs/handbook/utility-types.html#partialtype)\<[`FeedbackTranslations`](globals.md#feedbacktranslations)\>; + \} + + + +With these options you can override the look of the feedback prompt + +
+ +`ui.position`? + + + +[`Position`](globals.md#position) + + + +Control the placement and behavior of the feedback form. + +
+ +`ui.translations`? + + + +[`Partial`](https://www.typescriptlang.org/docs/handbook/utility-types.html#partialtype)\<[`FeedbackTranslations`](globals.md#feedbacktranslations)\> + + + +Add your own custom translations for the feedback form. +Undefined translation keys fall back to english defaults. + +
+ +*** + +### FeedbackPrompt + +```ts +type FeedbackPrompt = { + featureId: string; + promptId: string; + question: string; + showAfter: Date; + showBefore: Date; +}; +``` + +#### Type declaration + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
+ + `featureId` + + + +`string` + + + +Feature ID from Reflag + +
+ + `promptId` + + + +`string` + + + +Id of the prompt + +
+ + `question` + + + +`string` + + + +Specific question user was asked + +
+ + `showAfter` + + + +[`Date`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Date) + + + +Feedback prompt should appear only after this time + +
+ + `showBefore` + + + +[`Date`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Date) + + + +Feedback prompt will not be shown after this time + +
+ +*** + +### FeedbackPromptHandler() + +```ts +type FeedbackPromptHandler = (prompt: FeedbackPrompt, handlers: FeedbackPromptHandlerCallbacks) => void; +``` + +#### Parameters + + + + + + + + + + + + + + + + + + +
ParameterType
+ +`prompt` + + + +[`FeedbackPrompt`](globals.md#feedbackprompt) + +
+ +`handlers` + + + +[`FeedbackPromptHandlerCallbacks`](globals.md#feedbackprompthandlercallbacks) + +
+ +#### Returns + +`void` + +*** + +### FeedbackPromptHandlerCallbacks + +```ts +type FeedbackPromptHandlerCallbacks = { + openFeedbackForm: (options: FeedbackPromptHandlerOpenFeedbackFormOptions) => void; + reply: FeedbackPromptReplyHandler; +}; +``` + +#### Type declaration + + + + + + + + + + + + + + + + + + +
NameType
+ + `openFeedbackForm` + + + +(`options`: [`FeedbackPromptHandlerOpenFeedbackFormOptions`](globals.md#feedbackprompthandleropenfeedbackformoptions)) => `void` + +
+ + `reply` + + + +[`FeedbackPromptReplyHandler`](globals.md#feedbackpromptreplyhandler) + +
+ +*** + +### FeedbackPromptHandlerOpenFeedbackFormOptions + +```ts +type FeedbackPromptHandlerOpenFeedbackFormOptions = Omit; +``` + +*** + +### FeedbackPromptReply + +```ts +type FeedbackPromptReply = { + comment: string; + companyId: string; + question: string; + score: number; +}; +``` + +#### Type declaration + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameType
+ + `comment`? + + + +`string` + +
+ + `companyId`? + + + +`string` + +
+ + `question` + + + +`string` + +
+ + `score`? + + + +`number` + +
+ +*** + +### FeedbackPromptReplyHandler() + +```ts +type FeedbackPromptReplyHandler = (reply: T) => T extends null ? Promise : Promise<{ + feedbackId: string; +}>; +``` + +#### Type Parameters + + + + + + + + + + + + +
Type Parameter
+ +`T` *extends* [`FeedbackPromptReply`](globals.md#feedbackpromptreply) \| `null` + +
+ +#### Parameters + + + + + + + + + + + + + + +
ParameterType
+ +`reply` + + + +`T` + +
+ +#### Returns + +`T` *extends* `null` ? [`Promise`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Promise)\<`void`\> : [`Promise`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Promise)\<\{ + `feedbackId`: `string`; + \}\> + +*** + +### FeedbackTranslations + +```ts +type FeedbackTranslations = { + DefaultQuestionLabel: string; + QuestionPlaceholder: string; + ScoreDissatisfiedLabel: string; + ScoreNeutralLabel: string; + ScoreSatisfiedLabel: string; + ScoreStatusDescription: string; + ScoreStatusLoading: string; + ScoreStatusReceived: string; + ScoreVeryDissatisfiedLabel: string; + ScoreVerySatisfiedLabel: string; + SendButton: string; + SuccessMessage: string; +}; +``` + +You can use this to override text values in the feedback form +with desired language translation + +#### Type declaration + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameType
+ + `DefaultQuestionLabel` + + + +`string` + +
+ + `QuestionPlaceholder` + + + +`string` + +
+ + `ScoreDissatisfiedLabel` + + + +`string` + +
+ + `ScoreNeutralLabel` + + + +`string` + +
+ + `ScoreSatisfiedLabel` + + + +`string` + +
+ + `ScoreStatusDescription` + + + +`string` + +
+ + `ScoreStatusLoading` + + + +`string` + +
+ + `ScoreStatusReceived` + + + +`string` + +
+ + `ScoreVeryDissatisfiedLabel` + + + +`string` + +
+ + `ScoreVerySatisfiedLabel` + + + +`string` + +
+ + `SendButton` + + + +`string` + +
+ + `SuccessMessage` + + + +`string` + +
+ +*** + +### FlagDefaultFor\ + +```ts +type FlagDefaultFor = keyof Registry extends never ? FlagDefaultValue | object : Key extends keyof Registry ? ReadonlyFlagValue | null : never; +``` + +Registered defaults match the value schema; null explicitly requests arbitrary JSON. + +#### Type Parameters + + + + + + + + + + + + + + + +
Type Parameter
+ +`Registry` + +
+ +`Key` *extends* `PropertyKey` + +
+ +*** + +### FlagDefaultValue + +```ts +type FlagDefaultValue = + | null + | boolean + | number + | string + | readonly FlagDefaultValue[] +| {}; +``` + +*** + +### FlagOverrides + +```ts +type FlagOverrides = Record; +``` + +*** + +### FlagValue + +```ts +type FlagValue = + | null + | boolean + | number + | string + | FlagValue[] +| {}; +``` + +*** + +### FlagValueFromDefault\ + +```ts +type FlagValueFromDefault = T extends null ? FlagValue : T extends boolean ? boolean : T extends number ? number : T extends string ? string : T extends readonly infer Element[] ? [Element] extends [never] ? FlagValue[] : FlagValueFromDefault[] : T extends object ? { -readonly [Key in keyof T]: FlagValueFromDefault } : never; +``` + +Widen literals and return mutable copies. Null requests arbitrary JSON. + +#### Type Parameters + + + + + + + + + + + + +
Type Parameter
+ +`T` + +
+ +*** + +### FlagValues + +```ts +type FlagValues = Record; +``` + +*** + +### InitOptions + +```ts +type InitOptions = ReflagDeprecatedContext & { + apiBaseUrl: string; + appBaseUrl: string; + bootstrappedState: BootstrappedState; + credentials: "include" | "same-origin" | "omit"; + enableLiveFlagUpdates: boolean; + enableTracking: boolean; + eventSourceFactory: EventSourceFactory; + expireTimeMs: number; + fallbackFlags: | string[] + | Record; + feedback: FeedbackOptions; + logger: Logger; + offline: boolean; + publishableKey: string; + sdkVersion: string; + sseBaseUrl: string; + staleTimeMs: number; + staleWhileRevalidate: boolean; + storage: StorageAdapter; + timeoutMs: number; + toolbar: ToolbarOptions; + trackingQueue: { + flushDelayMs: number; + maxSize: number; + retryBaseDelayMs: number; + retryMaxDelayMs: number; + }; +}; +``` + +ReflagClient initialization options. + +#### Type declaration + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
+ +`apiBaseUrl`? + + + +`string` + + + +Base URL of Reflag servers. You can override this to use your mocked server. + +
+ +`appBaseUrl`? + + + +`string` + + + +Base URL of the Reflag web app. Links open ín this app by default. + +
+ +`bootstrappedState`? + + + +[`BootstrappedState`](globals.md#bootstrappedstate) + + + +Pre-fetched evaluated state used for the initial flag state. +The client fetches opt-in metadata on demand when opt-in flags are requested. + +
+ +`credentials`? + + + +`"include"` \| `"same-origin"` \| `"omit"` + + + +When proxying requests, you may want to include credentials like cookies +so you can authorize the request in the proxy. +This option controls the `credentials` option of the fetch API. + +
+ +`enableLiveFlagUpdates`? + + + +`boolean` + + + +Whether to enable live flag updates. + +When enabled, the SDK opens a Server-Sent Events (SSE) connection and +refreshes flag definitions automatically whenever they change on the +server, without relying on context changes or manual refreshes. + +Defaults to `false` in the browser SDK. + +
+ +`enableTracking`? + + + +`boolean` + + + +Whether to enable tracking. Defaults to `true`. + +
+ +`eventSourceFactory`? + + + +[`EventSourceFactory`](globals.md#eventsourcefactory) + + + +Optional factory used to create SSE connections. + +By default the SDK uses the global `EventSource` implementation available +in browsers. This option is intended for alternative runtimes where you +need to provide an EventSource-compatible transport manually. The React +Native wrapper already injects a transport automatically. + +
+ +`expireTimeMs`? + + + +`number` + + + +If set, flags will be cached between page loads for this duration + +
+ +`fallbackFlags`? + + + + \| `string`[] + \| [`Record`](https://www.typescriptlang.org/docs/handbook/utility-types.html#recordkeys-type)\<`string`, [`FallbackFlagOverride`](globals.md#fallbackflagoverride)\> + + + +Value-only startup fallbacks. An array enables the listed Boolean flags. +Fallback values never identify an authored selection. + +
+ +`feedback`? + + + +[`FeedbackOptions`](globals.md#feedbackoptions) + + + +AutoFeedback specific configuration + +
+ +`logger`? + + + +[`Logger`](globals.md#logger) + + + +You can provide a logger to see the logs of the network calls. +This is undefined by default. +For debugging purposes you can just set the browser console to this property: +```javascript +options.logger = window.console; +``` + +
+ +`offline`? + + + +`boolean` + + + +Whether to enable offline mode. Defaults to `false`. + +
+ +`publishableKey` + + + +`string` + + + +Publishable key for authentication + +
+ +`sdkVersion`? + + + +`string` + + + +Version of the SDK + +
+ +`sseBaseUrl`? + + + +`string` + + + +**Deprecated** + +SSE now uses the same origin as `apiBaseUrl` by default. +Override only if you need a separate pubsub host temporarily. + +
+ +`staleTimeMs`? + + + +`number` + + + +Stale flags will be returned if staleWhileRevalidate is true if no new flags can be fetched + +
+ +`staleWhileRevalidate`? + + + +`boolean` + + + +If set to true stale flags will be returned while refetching flags + +
+ +`storage`? + + + +[`StorageAdapter`](globals.md#storageadapter) + + + +Optional storage adapter used for caching flags and overrides. +Useful for React Native (AsyncStorage). + +
+ +`timeoutMs`? + + + +`number` + + + +Timeout in milliseconds when fetching flags + +
+ +`toolbar`? + + + +[`ToolbarOptions`](globals.md#toolbaroptions) + + + +Toolbar configuration + +
+ +`trackingQueue`? + + + +\{ + `flushDelayMs`: `number`; + `maxSize`: `number`; + `retryBaseDelayMs`: `number`; + `retryMaxDelayMs`: `number`; + \} + + + +Queue settings for tracking updates sent to `/bulk`. +Applies to user/company updates, check events, and prompt events. +Events are buffered in memory and flushed in the background. + +
+ +`trackingQueue.flushDelayMs`? + + + +`number` + + + +Delay in milliseconds before flushing queued events. +Lower values send sooner; slightly higher values batch better. +Defaults to 200ms. + +
+ +`trackingQueue.maxSize`? + + + +`number` + + + +Maximum number of queued events retained locally. +Oldest events are dropped when the cap is exceeded. +Defaults to 100. + +
+ +`trackingQueue.retryBaseDelayMs`? + + + +`number` + + + +Deprecated: retries are no longer performed for bulk delivery. + +
+ +`trackingQueue.retryMaxDelayMs`? + + + +`number` + + + +Deprecated: retries are no longer performed for bulk delivery. + +
+ +*** + +### Offset + +```ts +type Offset = { + x: string | number; + y: string | number; +}; +``` + +#### Type declaration + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
+ + `x`? + + + +`string` \| `number` + + + +Offset from the nearest horizontal screen edge after placement is resolved + +
+ + `y`? + + + +`string` \| `number` + + + +Offset from the nearest vertical screen edge after placement is resolved + +
+ +*** + +### OptInFlag + +```ts +type OptInFlag = RawFlagOptIn & { + isEnabled: boolean; + key: string; +}; +``` + +#### Type declaration + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
+ +`isEnabled` + + + +`boolean` + + + +Result of flag evaluation. + +
+ +`key` + + + +`string` + + + +Flag key. + +
+ +*** + +### PopoverPlacement + +```ts +type PopoverPlacement = Placement; +``` + +*** + +### Position + +```ts +type Position = + | { + type: "MODAL"; + } + | { + offset: Offset; + placement: DialogPlacement; + type: "DIALOG"; + } + | { + anchor: any | null; + placement: PopoverPlacement; + type: "POPOVER"; +}; +``` + +*** + +### RawFlag + +```ts +type RawFlag = { + allocationIndex: number; + errors: EvaluationError[]; + matchedRuleId: string; + optIn: RawFlagOptIn | null; + optInEnabled: boolean; + resolved: boolean; + ruleResults: RuleEvaluationResult[]; + sourceVersionId: string; + value: FlagValue | undefined; + variantKey: string; +}; +``` + +Evaluated diagnostics for v2 transport/bootstrap, not a flag wrapper. + +#### Type declaration + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
+ + `allocationIndex`? + + + +`number` + + + +‐ + +
+ + `errors` + + + +[`EvaluationError`](globals.md#evaluationerror)[] + + + +‐ + +
+ + `matchedRuleId`? + + + +`string` + + + +‐ + +
+ + `optIn`? + + + +[`RawFlagOptIn`](globals.md#rawflagoptin) \| `null` + + + +‐ + +
+ + `optInEnabled`? + + + +`boolean` + + + +Absent in Node bootstrap/older snapshots; null optIn means unavailable. + +
+ + `resolved` + + + +`boolean` + + + +‐ + +
+ + `ruleResults` + + + +[`RuleEvaluationResult`](globals.md#ruleevaluationresult)[] + + + +‐ + +
+ + `sourceVersionId`? + + + +`string` + + + +‐ + +
+ + `value` + + + +[`FlagValue`](globals.md#flagvalue) \| `undefined` + + + +‐ + +
+ + `variantKey`? + + + +`string` + + + +‐ + +
+ +*** + +### RawFlagOptIn + +```ts +type RawFlagOptIn = { + companyOptedIn: boolean; + description: string | null; + isOptedIn: boolean; + name: string; + userOptedIn: boolean; +}; +``` + +#### Type declaration + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
+ + `companyOptedIn` + + + +`boolean` + + + +Whether the current company has opted into the flag. + +
+ + `description` + + + +`string` \| `null` + + + +SDK-facing opt-in description. + +
+ + `isOptedIn` + + + +`boolean` + + + +Whether either the current user or company has opted into the flag. + +
+ + `name` + + + +`string` + + + +Display name of the opt-in flag. + +
+ + `userOptedIn` + + + +`boolean` + + + +Whether the current user has opted into the flag. + +
+ +*** + +### RawFlags + +```ts +type RawFlags = Record; +``` + +*** + +### RequestFeedbackData + +```ts +type RequestFeedbackData = Omit & { + companyId: string; + flagKey: string; + onAfterSubmit: (data: FeedbackSubmission) => void; +}; +``` + +#### Type declaration + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
+ +`companyId`? + + + +`string` + + + +Company ID from your own application. + +
+ +`flagKey` + + + +`string` + + + +Flag key. + +
+ +`onAfterSubmit`? + + + +(`data`: [`FeedbackSubmission`](globals.md#feedbacksubmission)) => `void` + + + +Allows you to handle a copy of the already submitted +feedback. + +This can be used for side effects, such as storing a +copy of the feedback in your own application or CRM. + +
+ +*** + +### RequestFeedbackOptions + +```ts +type RequestFeedbackOptions = RequestFeedbackData & { + userId: string; +}; +``` + +#### Type declaration + + + + + + + + + + + + + + + + +
NameTypeDescription
+ +`userId` + + + +`string` + + + +User ID from your own application. + +
+ +*** + +### RuleEvaluationResult + +```ts +type RuleEvaluationResult = { + allocationIndex: number; + destination: | { + type: "nextRule"; + } + | { + type: "variant"; + variantKey: string; + }; + matched: boolean; + ruleId: string; +}; +``` + +#### Type declaration + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameType
+ + `allocationIndex`? + + + +`number` + +
+ + `destination`? + + + + \| \{ + `type`: `"nextRule"`; + \} + \| \{ + `type`: `"variant"`; + `variantKey`: `string`; + \} + +
+ + `matched` + + + +`boolean` + +
+ + `ruleId` + + + +`string` + +
+ +*** + +### SetOptInOptions + +```ts +type SetOptInOptions = { + optedIn: boolean; + scope: "user" | "company"; +}; +``` + +Represents a flag. + +#### Type declaration + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
+ + `optedIn` + + + +`boolean` + + + +Whether the scoped subject has opted in. + +
+ + `scope`? + + + +`"user"` \| `"company"` + + + +Whether to update the current user or current company. Defaults to `user`. + +
+ +*** + +### State + +```ts +type State = "idle" | "initializing" | "initialized" | "stopped"; +``` + +State of the client. + +*** + +### StorageAdapter + +```ts +type StorageAdapter = { + getItem: Promise; + removeItem: Promise; + setItem: Promise; +}; +``` + +#### Type declaration + + + + + + + + + + + + + + + + + + + + + + +
NameType
+ + `getItem()` + + + +[`Promise`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Promise)\<`null` \| `string`\> + +
+ + `removeItem()`? + + + +[`Promise`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Promise)\<`void`\> + +
+ + `setItem()` + + + +[`Promise`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Promise)\<`void`\> + +
+ +*** + +### ToolbarOptions + +```ts +type ToolbarOptions = + | boolean + | { + position: ToolbarPosition; + show: boolean; +}; +``` + +Toolbar options. + +*** + +### TrackEvent + +```ts +type TrackEvent = { + attributes: | Record + | null; + company: CompanyContext; + eventName: string; + user: UserContext; +}; +``` + +#### Type declaration + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameType
+ + `attributes`? + + + + \| [`Record`](https://www.typescriptlang.org/docs/handbook/utility-types.html#recordkeys-type)\<`string`, `any`\> + \| `null` + +
+ + `company`? + + + +[`CompanyContext`](globals.md#companycontext) + +
+ + `eventName` + + + +`string` + +
+ + `user` + + + +[`UserContext`](globals.md#usercontext) + +
+ +*** + +### UnassignedFeedback + +```ts +type UnassignedFeedback = { + comment: string; + feedbackId: string; + flagKey: string; + promptedQuestion: string; + promptId: string; + question: string; + score: number; + source: "prompt" | "sdk" | "widget"; +}; +``` + +#### Type declaration + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
+ + `comment`? + + + +`string` + + + +User supplied comment about your flag. + +
+ + `feedbackId`? + + + +`string` + + + +Reflag feedback ID + +
+ + `flagKey` + + + +`string` + + + +Flag key. + +
+ + `promptedQuestion`? + + + +`string` + + + +The original question. +This only needs to be populated if the feedback was submitted through the automated feedback surveys channel. + +
+ + `promptId`? + + + +`string` + + + +Reflag feedback prompt ID. + +This only exists if the feedback was submitted +as part of an automated prompt from Reflag. + +Used for internal state management of automated +feedback. + +
+ + `question`? + + + +`string` + + + +The question that was presented to the user. + +
+ + `score`? + + + +`number` + + + +Customer satisfaction score. + +
+ + `source`? + + + +`"prompt"` \| `"sdk"` \| `"widget"` + + + +Source of the feedback, depending on how the user was asked +- `prompt` - Feedback submitted by way of an automated feedback survey (prompted) +- `widget` - Feedback submitted via `requestFeedback` +- `sdk` - Feedback submitted via `feedback` + +
+ +## Variables + +### DEFAULT\_TRANSLATIONS + +```ts +const DEFAULT_TRANSLATIONS: FeedbackTranslations; +``` + +```tsx +import { FeedbackTranslations } from "../types"; +/** + * {@includeCode ./defaultTranslations.tsx} + */ +export const DEFAULT_TRANSLATIONS: FeedbackTranslations = { + DefaultQuestionLabel: "How satisfied are you with this feature?", + QuestionPlaceholder: "Write a comment", + ScoreStatusDescription: "Pick a score and leave a comment", + ScoreStatusLoading: "Saving score, please wait...", + ScoreStatusReceived: "Score has been received!", + ScoreVeryDissatisfiedLabel: "Very dissatisfied (1/5)", + ScoreDissatisfiedLabel: "Dissatisfied (2/5)", + ScoreNeutralLabel: "Neutral (3/5)", + ScoreSatisfiedLabel: "Satisfied (4/5)", + ScoreVerySatisfiedLabel: "Very satisfied (5/5)", + SuccessMessage: "Feedback received, thank you!", + SendButton: "Send feedback", +}; +``` + +*** + +### feedbackContainerId + +```ts +const feedbackContainerId: "reflag-feedback-dialog-container" = "reflag-feedback-dialog-container"; +``` + +ID of HTML DIV element which contains the feedback dialog + +*** + +### propagatedEvents + +```ts +const propagatedEvents: string[]; +``` + +These events will be propagated to the feedback dialog + +#### See + +[https://developer.mozilla.org/en-US/docs/Web/API/Element#events](https://developer.mozilla.org/en-US/docs/Web/API/Element#events) diff --git a/previews/sdk-versioning/v2/@reflag/node-sdk/README.md b/previews/sdk-versioning/v2/@reflag/node-sdk/README.md new file mode 100644 index 0000000..262b835 --- /dev/null +++ b/previews/sdk-versioning/v2/@reflag/node-sdk/README.md @@ -0,0 +1,240 @@ +--- +layout: + visible: true +title: + visible: true +description: + visible: false +tableOfContents: + visible: true +outline: + visible: true +pagination: + visible: true +--- + +{% hint style="info" %} +SDK v2 candidate documentation, not a published release. Installation commands target the planned major; matching SDK/CLI releases are still required. Generated from `@reflag/node-sdk@2.0.0-next.0` in [the JavaScript repository](https://github.com/reflagcom/javascript/tree/9f3a0ae027e386747a2d4cf7683a68dcab70b355/packages/node-sdk). SDK READMEs and code comments there remain authoritative. +{% endhint %} + +# Reflag Node.js SDK + +Server-side Boolean and multivariate flag values for [Reflag](https://reflag.com). + +> This branch implements the breaking v2 API. It requires the collection-capable evaluator, the v2 `/flags` endpoint, and compatible variant-key check ingestion. It has not been published or deployed. + +## Installation and initialization + +```sh +yarn add @reflag/node-sdk@2 +``` + +Create one client per process and initialize it before reading flags. Obtain your environment's secret key from Reflag's environment settings; complete flag definitions must remain server-side. + +```ts +import { ReflagClient } from "@reflag/node-sdk"; + +export const reflag = new ReflagClient({ + secretKey: process.env.REFLAG_SECRET_KEY, +}); +await reflag.initialize(); +``` + +## Direct values, immediate checks + +```ts +const context = { + user: { id: "user-1" }, + company: { id: "acme", plan: "enterprise" }, +}; + +const enabled = reflag.getFlag(context, "new-editor"); // boolean; implicit false +const model = reflag.getFlag(context, "model", "quick"); // string +const limits = reflag.getFlag(context, "limits", { maxItems: 10 }); // { maxItems: number } +``` + +**`getFlag()` returns the value itself**, not a flag object. There is no `.value`, `.isEnabled`, `.config`, or flag `.track()` wrapper. Without a default it is a Boolean read: unavailable or non-Boolean values return `false`, never a truthy object. With a positional default, missing values use that default. Valid `false`, `0`, empty strings, arrays, and objects are preserved rather than replaced by truthiness checks. + +Non-null defaults specify the expected JSON category: Boolean, number, string, array, or object. A category mismatch logs a warning without the payload and returns the default. This is not nested payload validation: object properties and array elements must still match your authored schema. Use an explicit `null` default for arbitrary JSON or nullable/off selections, and narrow the result before accessing object fields: + +```ts +const payload = reflag.getFlag(context, "nullable-settings", null); // FlagValue +// Authored null is preserved, as are any other valid JSON values. Missing uses null. +``` + +Defaults infer useful types without generated declarations, including widening primitive literals. Readonly defaults are accepted; results and defaults are returned as independent copies. Defaults are ordinary JSON payloads, with no reserved property names. + +Each call immediately registers a check for the requested root, including when you do not subsequently use its return value. Dependencies traversed internally do not register extra checks. Delivery is buffered and rate-limited. A check records the selected **variant key**, never the JSON payload: + +```ts +{ + type: "feature-flag-event", + action: "check", + key: "model", + evalResult: "quality", + sourceVersionId: "version-id", + flagStateVersion: 42, + evalContext: { company: { id: "acme", plan: "enterprise" } }, +} +``` + +Boolean selections send `"true"` / `"false"` while returning Boolean values. Caller defaults, including category-mismatch defaults, do not claim an authored selection: their checks omit `evalResult` and `sourceVersionId`. Check deduplication includes normalized context, selection identity, root version, and snapshot version. Equal-valued variants are not interchangeable selections. Batch `getFlags()` remains a raw value record, without per-root caller defaults; unavailable batch values may be `undefined`. + +```ts +const values = reflag.getFlags(context); // value record; checks each returned root +const bound = reflag.bindClient(context); +const value = bound.getFlag("model", "quick"); +const allValues = bound.getFlags(); + +// Ordinary usage tracking is separate from flag checks. +await bound.track("Document created", { attributes: { source: "editor" } }); +await reflag.flush(); +``` + +`enableTracking: false` disables checks, context synchronization, and bound-client usage tracking. Explicit calls to the root client's `track()`, `updateUser()`, and `updateCompany()` remain separate operations. + +## Evaluation and synchronization + +The SDK fetches a complete `{ flags, segments }` graph from secret-key-only `GET /flags`. Definitions are validated and prepared before the flags, segments, and snapshot version replace the current cache together. Malformed graphs and stale responses cannot replace last-known-good state. + +`getFlag()` evaluates only its root and dependencies. `getFlags()` evaluates the collection. References are reevaluated on every traversal: there is **no context/dependency-result caching**. Only immutable definition preparation is retained. Returned objects are independent copies, so changing a value cannot mutate the saved definitions. + +Synchronization modes: + +- `push` (default): authenticated SSE updates with reconnect handling. +- `polling`: periodic background refreshes. +- `in-request`: request-driven refreshes, suitable for constrained runtimes. + +```ts +await reflag.refreshFlags(); +await reflag.refreshFlags(42); // request at least this snapshot version +const definitions = reflag.getFlagDefinitions(); // { flags, segments }, not an array +``` + +Refreshes are throttled. In-request mode can queue a refresh during the throttle window until a later access; a refresh call is not an unconditional synchronization barrier. + +## Fallback providers + +```ts +import { ReflagClient, fallbackProviders } from "@reflag/node-sdk"; + +const client = new ReflagClient({ + secretKey: process.env.REFLAG_SECRET_KEY, + flagsFallbackProvider: fallbackProviders.file({ directory: ".reflag" }), +}); +await client.initialize(); +``` + +Live state is primary. When the initial live fetch fails, the SDK can load a saved complete collection. Later outages retain the in-memory graph rather than reloading older fallback state. Accepted live snapshots are saved in order; `flush()` also waits for pending saves. + +Available providers: `static`, `file`, `redis`, `s3`, and `gcs`, plus custom `FlagsFallbackProvider` implementations. A v2 snapshot contains: + +```ts +{ + version: 2, + savedAt: "2026-01-01T00:00:00.000Z", + flagStateVersion: 42, + flags: [/* compiled flags */], + segments: [/* compiled segment predicates */], +} +``` + +V1 flag-only snapshots are incompatible and are not automatically expanded or converted. Built-in files/S3/GCS objects use `flags-fallback-v2-.json`; Redis uses `:v2:`. This keeps v1 and v2 processes from overwriting each other's fallback state. + +A static fallback can contain any JSON values: + +```ts +fallbackProviders.static({ + flags: { "new-editor": false, model: "quick", limits: null }, +}); +``` + +Static values, `fallbackFlags`, and overrides do not identify authored variants. Their checks omit `evalResult` and `sourceVersionId`; they must not claim exposure to an authored selection. Restored real collection snapshots retain their real selection/version metadata. Providers are not loaded in offline mode. + +## Overrides and tests + +```ts +const client = new ReflagClient({ + offline: true, + flagOverrides: { "new-editor": true, limits: { maxItems: 5 } }, +}); +await client.initialize(); +client.setFlagOverrides({ model: "quick" }); +const remove = client.pushFlagOverrides((context) => ({ + model: context.company?.id === "qa" ? "quality" : "quick", +})); +remove(); +client.clearFlagOverrides(); +``` + +Overrides are direct JSON values. Pushed layers win over base overrides and can be removed independently. They apply **after evaluation**, so overriding a dependency's returned value does not rewrite dependent flag evaluations. Offline mode does not fetch, load a provider, or send tracking events. + +## Typed flags + +Generated declarations are optional. Singular reads infer their return type from the default, or return Boolean when no default is supplied. Optionally augment `Flags` to constrain keys, check non-null defaults against each key's value schema, and type batch records: + +```ts +declare module "@reflag/node-sdk" { + interface Flags { + "new-editor": boolean; + model: "quick" | "quality"; + limits: { maxItems: number } | null; + } +} + +const model = reflag.getFlag(context, "model", "quick"); // string +const models = reflag.getFlags(context); // models.model: "quick" | "quality" | undefined +``` + +Augmentation rejects defaults with the wrong category, missing required properties, or incompatible nested types. Named-interface and readonly defaults are supported. Explicit `null` remains an arbitrary-JSON request, and bare/`undefined` reads still return Boolean. Return types continue to be inferred and widened from the default; generated types do not add nested runtime validation. The v2 CLI generates direct-value schemas from current production-environment variants. Regenerate old CLI wrapper declarations when migrating. Both destructuring `.isEnabled` and accessing it through an assigned singular value fail type checking, including without generated declarations. See the Express example for a typed direct-value integration. + +## Bootstrap state + +```ts +const bootstrap = reflag.getFlagsForBootstrap(context); +// { protocolVersion: 2, context, flags: { model: { resolved, value, +// variantKey, sourceVersionId, matchedRuleId, ruleResults, errors, ... } }, +// flagStateVersion } +``` + +Bootstrap uses batch evaluation and preserves selection metadata and raw context. It does **not** register browser checks. Local overrides/value-only fallbacks carry their values without fabricated authored-selection metadata. + +**This is a v2 bootstrap contract, not the existing v1 Browser/React/Vue bootstrap shape.** Those consumers require a coordinated migration before accepting this state. Likewise, the current OpenFeature provider deliberately remains on published Node SDK v1 until its separate migration. + +## Remote evaluation + +```ts +const enabled = await reflag.getFlagRemote( + "new-editor", + undefined, + "user-1", + "acme", +); +const model = await reflag.getFlagRemote("model", "quick", "user-1", "acme"); +const boundModel = await reflag + .bindClient(context) + .getFlagRemote("model", "quick"); +const values = await reflag.getFlagsRemote("user-1", "acme", { + other: { region: "eu" }, +}); +``` + +These methods use `/flags/evaluated` and return direct values. Singular remote reads use the same defaults, category checks, and JSON validation as local reads. The root `getFlagRemote(key, defaultValue, userId?, companyId?, context?)` places the default second; use `undefined` for an implicit Boolean read when passing explicit IDs. This moves the old positional IDs one place to the right. `getFlagsRemote()` keeps its existing argument order and returns a raw value record. + +Each requested singular root registers a check, including a default after a failed/missing remote evaluation, unless tracking is disabled. Only accepted authored selections retain the server's selection/version metadata. Explicit IDs, including numeric zero, take precedence over IDs in additional context. Stored attribute updates are asynchronous; remote evaluation need not immediately observe a just-sent update. + +## Edge runtimes + +Use `EdgeClient` with Node compatibility enabled. It selects in-request synchronization. Call `ctx.waitUntil(client.flush())` before returning from a Worker so queued events and refresh work can complete. + +## Configuration + +Configuration precedence is constructor options, environment variables, then `reflag.config.json`. Supported environment variables include `REFLAG_SECRET_KEY`, `REFLAG_API_BASE_URL`, `REFLAG_LOG_LEVEL`, `REFLAG_OFFLINE`, `REFLAG_CONFIG_FILE`, and Boolean override lists `REFLAG_FLAGS_ENABLED` / `REFLAG_FLAGS_DISABLED`. + +Constructor options also include a custom `logger` / `httpClient`, `flagsSyncMode`, `flagsPushUrl`, `fetchTimeoutMs`, `flagsFetchRetries`, `flagsFallbackProvider`, direct-value `flagOverrides`, and `batchOptions` (`maxSize`, `intervalMs`, `flushOnExit`). `emitEvaluationEvents` is deprecated and has no effect. + +Invalid options/context or non-JSON defaults can throw. Network or definition-refresh failures are logged and retain usable state. Singular reads use their caller default (implicit `false`) without fabricating an authored false selection; batch reads retain raw values/`undefined`. Call `await client.flush()` before shutting down, then `client.destroy()` to stop background work. + +## License + +MIT License. Copyright (c) 2025 Bucket ApS. diff --git a/previews/sdk-versioning/v2/@reflag/node-sdk/globals.md b/previews/sdk-versioning/v2/@reflag/node-sdk/globals.md new file mode 100644 index 0000000..59068f9 --- /dev/null +++ b/previews/sdk-versioning/v2/@reflag/node-sdk/globals.md @@ -0,0 +1,5470 @@ +--- +layout: + visible: true +title: + visible: true +description: + visible: false +tableOfContents: + visible: true +outline: + visible: true +pagination: + visible: true +--- + +{% hint style="info" %} +SDK v2 candidate documentation, not a published release. Installation commands target the planned major; matching SDK/CLI releases are still required. Generated from `@reflag/node-sdk@2.0.0-next.0` in [the JavaScript repository](https://github.com/reflagcom/javascript/tree/9f3a0ae027e386747a2d4cf7683a68dcab70b355/packages/node-sdk). SDK READMEs and code comments there remain authoritative. +{% endhint %} + +# @reflag/node-sdk + +## Classes + +### BoundReflagClient + +Context-bound reads have the same direct-value and immediate-check semantics. + +#### Constructors + +##### new BoundReflagClient() + +```ts +new BoundReflagClient(client: ReflagClient, options: ContextWithTracking): BoundReflagClient +``` + +###### Parameters + + + + + + + + + + + + + + + + + + +
ParameterType
+ +`client` + + + +[`ReflagClient`](globals.md#reflagclient) + +
+ +`options` + + + +[`ContextWithTracking`](globals.md#contextwithtracking) + +
+ +###### Returns + +[`BoundReflagClient`](globals.md#boundreflagclient) + +#### Accessors + +##### company + +###### Get Signature + +```ts +get company(): + | undefined + | { +[key: string]: any; avatar: string; + id: undefined | IdType; + name: string; +} +``` + +###### Returns + + \| `undefined` + \| \{ +`[key: string]`: `any`; `avatar`: `string`; + `id`: `undefined` \| [`IdType`](globals.md#idtype); + `name`: `string`; + \} + +##### otherContext + +###### Get Signature + +```ts +get otherContext(): + | undefined +| Record +``` + +###### Returns + + \| `undefined` + \| [`Record`](https://www.typescriptlang.org/docs/handbook/utility-types.html#recordkeys-type)\<`string`, `any`\> + +##### user + +###### Get Signature + +```ts +get user(): + | undefined + | { +[key: string]: any; avatar: string; + email: string; + id: undefined | IdType; + name: string; +} +``` + +###### Returns + + \| `undefined` + \| \{ +`[key: string]`: `any`; `avatar`: `string`; + `email`: `string`; + `id`: `undefined` \| [`IdType`](globals.md#idtype); + `name`: `string`; + \} + +#### Methods + +##### bindClient() + +```ts +bindClient(next: ContextWithTracking): BoundReflagClient +``` + +###### Parameters + + + + + + + + + + + + + + +
ParameterType
+ +`next` + + + +[`ContextWithTracking`](globals.md#contextwithtracking) + +
+ +###### Returns + +[`BoundReflagClient`](globals.md#boundreflagclient) + +##### flush() + +```ts +flush(): Promise +``` + +###### Returns + +[`Promise`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Promise)\<`void`\> + +##### getFlag() + +###### Call Signature + +```ts +getFlag(key: Key, defaultValue?: undefined): boolean +``` + +###### Type Parameters + + + + + + + + + + + + +
Type Parameter
+ +`Key` *extends* `string` + +
+ +###### Parameters + + + + + + + + + + + + + + + + + + +
ParameterType
+ +`key` + + + +`Key` + +
+ +`defaultValue`? + + + +`undefined` + +
+ +###### Returns + +`boolean` + +###### Call Signature + +```ts +getFlag(key: Key, defaultValue: Default): FlagValueFromDefault +``` + +###### Type Parameters + + + + + + + + + + + + + + + +
Type Parameter
+ +`Key` *extends* `string` + +
+ +`Default` *extends* `object` \| [`FlagDefaultValue`](globals.md#flagdefaultvalue) + +
+ +###### Parameters + + + + + + + + + + + + + + + + + + +
ParameterType
+ +`key` + + + +`Key` + +
+ +`defaultValue` + + + +`Default` + +
+ +###### Returns + +[`FlagValueFromDefault`](globals.md#flagvaluefromdefaultt)\<`Default`\> + +##### getFlagRemote() + +###### Call Signature + +```ts +getFlagRemote(key: Key, defaultValue?: undefined): Promise +``` + +###### Type Parameters + + + + + + + + + + + + +
Type Parameter
+ +`Key` *extends* `string` + +
+ +###### Parameters + + + + + + + + + + + + + + + + + + +
ParameterType
+ +`key` + + + +`Key` + +
+ +`defaultValue`? + + + +`undefined` + +
+ +###### Returns + +[`Promise`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Promise)\<`boolean`\> + +###### Call Signature + +```ts +getFlagRemote(key: Key, defaultValue: Default): Promise> +``` + +###### Type Parameters + + + + + + + + + + + + + + + +
Type Parameter
+ +`Key` *extends* `string` + +
+ +`Default` *extends* `object` \| [`FlagDefaultValue`](globals.md#flagdefaultvalue) + +
+ +###### Parameters + + + + + + + + + + + + + + + + + + +
ParameterType
+ +`key` + + + +`Key` + +
+ +`defaultValue` + + + +`Default` + +
+ +###### Returns + +[`Promise`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Promise)\<[`FlagValueFromDefault`](globals.md#flagvaluefromdefaultt)\<`Default`\>\> + +##### getFlags() + +```ts +getFlags(): Record +``` + +###### Returns + +[`Record`](https://www.typescriptlang.org/docs/handbook/utility-types.html#recordkeys-type)\<`string`, `undefined` \| [`FlagValue`](globals.md#flagvalue)\> + +##### getFlagsForBootstrap() + +```ts +getFlagsForBootstrap(): BootstrappedFlags +``` + +###### Returns + +[`BootstrappedFlags`](globals.md#bootstrappedflags) + +##### getFlagsRemote() + +```ts +getFlagsRemote(): Promise> +``` + +###### Returns + +[`Promise`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Promise)\<[`Record`](https://www.typescriptlang.org/docs/handbook/utility-types.html#recordkeys-type)\<`string`, `undefined` \| [`FlagValue`](globals.md#flagvalue)\>\> + +##### refreshFlags() + +```ts +refreshFlags(waitForVersion?: number): Promise +``` + +###### Parameters + + + + + + + + + + + + + + +
ParameterType
+ +`waitForVersion`? + + + +`number` + +
+ +###### Returns + +[`Promise`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Promise)\<`void`\> + +##### track() + +```ts +track(event: string, options?: TrackOptions & { + companyId: IdType; +}): Promise +``` + +###### Parameters + + + + + + + + + + + + + + + + + + +
ParameterType
+ +`event` + + + +`string` + +
+ +`options`? + + + +[`TrackOptions`](globals.md#trackoptions) & \{ `companyId`: [`IdType`](globals.md#idtype); \} + +
+ +###### Returns + +[`Promise`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Promise)\<`void`\> + +*** + +### EdgeClient + +The EdgeClient is ReflagClient pre-configured to be used in edge runtimes, like +Cloudflare Workers. + +It always uses `flagsSyncMode: "in-request"`. Refresh fetch starts are +throttled to at most once per second. A `refreshFlags()` call made during +that throttle window only records pending refresh work, so the call may +resolve before the fetch runs. That pending refresh is executed on the next +request/access or `refreshFlags()` call after the window expires. + +#### Example + +```ts +// set the REFLAG_SECRET_KEY environment variable or pass the secret key in the constructor +const client = new EdgeClient(); + +// evaluate a flag +const context = { + user: { id: "user-id" }, + company: { id: "company-id" }, +} +const { isEnabled } = client.getFlag(context, "flag-key"); + +``` + +#### Extends + +- [`ReflagClient`](globals.md#reflagclient) + +#### Constructors + +##### new EdgeClient() + +```ts +new EdgeClient(options: EdgeClientOptions): EdgeClient +``` + +###### Parameters + + + + + + + + + + + + + + +
ParameterType
+ +`options` + + + +[`EdgeClientOptions`](globals.md#edgeclientoptions) + +
+ +###### Returns + +[`EdgeClient`](globals.md#edgeclient) + +###### Overrides + +[`ReflagClient`](globals.md#reflagclient).[`constructor`](globals.md#constructors) + +#### Properties + + + + + + + + + + + + + + + + + + + + + +
PropertyModifierType
+ + `httpClient` + + + +`readonly` + + + +[`HttpClient`](globals.md#httpclient) + +
+ + `logger` + + + +`readonly` + + + +[`Logger`](globals.md#logger) + +
+ +#### Accessors + +##### flagOverrides + +###### Set Signature + +```ts +set flagOverrides(overrides: + | Partial> + | FlagOverridesFn): void +``` + +###### Deprecated + +Use setFlagOverrides. + +###### Parameters + + + + + + + + + + + + + + +
ParameterType
+ +`overrides` + + + + \| [`Partial`](https://www.typescriptlang.org/docs/handbook/utility-types.html#partialtype)\<[`Record`](https://www.typescriptlang.org/docs/handbook/utility-types.html#recordkeys-type)\<`string`, [`FlagValue`](globals.md#flagvalue)\>\> \| [`FlagOverridesFn`](globals.md#flagoverridesfn) + +
+ +###### Returns + +`void` + +###### Inherited from + +[`ReflagClient`](globals.md#reflagclient).[`flagOverrides`](globals.md#flagoverrides) + +#### Methods + +##### bindClient() + +```ts +bindClient(options: ContextWithTracking): BoundReflagClient +``` + +###### Parameters + + + + + + + + + + + + + + +
ParameterType
+ +`options` + + + +[`ContextWithTracking`](globals.md#contextwithtracking) + +
+ +###### Returns + +[`BoundReflagClient`](globals.md#boundreflagclient) + +###### Inherited from + +[`ReflagClient`](globals.md#reflagclient).[`bindClient`](globals.md#bindclient) + +##### clearFlagOverrides() + +```ts +clearFlagOverrides(): void +``` + +###### Returns + +`void` + +###### Inherited from + +[`ReflagClient`](globals.md#reflagclient).[`clearFlagOverrides`](globals.md#clearflagoverrides) + +##### destroy() + +```ts +destroy(): void +``` + +###### Returns + +`void` + +###### Inherited from + +[`ReflagClient`](globals.md#reflagclient).[`destroy`](globals.md#destroy) + +##### flush() + +```ts +flush(): Promise +``` + +###### Returns + +[`Promise`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Promise)\<`void`\> + +###### Inherited from + +[`ReflagClient`](globals.md#reflagclient).[`flush`](globals.md#flush) + +##### getFlag() + +###### Call Signature + +```ts +getFlag( + options: ContextWithTracking, + key: Key, + defaultValue?: undefined): boolean +``` + +Read a Boolean with an implicit false default, or supply an explicit value default. + +###### Type Parameters + + + + + + + + + + + + +
Type Parameter
+ +`Key` *extends* `string` + +
+ +###### Parameters + + + + + + + + + + + + + + + + + + + + + + +
ParameterType
+ +`options` + + + +[`ContextWithTracking`](globals.md#contextwithtracking) + +
+ +`key` + + + +`Key` + +
+ +`defaultValue`? + + + +`undefined` + +
+ +###### Returns + +`boolean` + +###### Inherited from + +[`ReflagClient`](globals.md#reflagclient).[`getFlag`](globals.md#getflag) + +###### Call Signature + +```ts +getFlag( + options: ContextWithTracking, + key: Key, +defaultValue: Default): FlagValueFromDefault +``` + +Read a Boolean with an implicit false default, or supply an explicit value default. + +###### Type Parameters + + + + + + + + + + + + + + + +
Type Parameter
+ +`Key` *extends* `string` + +
+ +`Default` *extends* `object` \| [`FlagDefaultValue`](globals.md#flagdefaultvalue) + +
+ +###### Parameters + + + + + + + + + + + + + + + + + + + + + + +
ParameterType
+ +`options` + + + +[`ContextWithTracking`](globals.md#contextwithtracking) + +
+ +`key` + + + +`Key` + +
+ +`defaultValue` + + + +`Default` + +
+ +###### Returns + +[`FlagValueFromDefault`](globals.md#flagvaluefromdefaultt)\<`Default`\> + +###### Inherited from + +[`ReflagClient`](globals.md#reflagclient).[`getFlag`](globals.md#getflag) + +##### getFlagDefinitions() + +```ts +getFlagDefinitions(): FlagCollection +``` + +Complete definitions, including segments needed for offline evaluation. + +###### Returns + +[`FlagCollection`](globals.md#flagcollection) + +###### Inherited from + +[`ReflagClient`](globals.md#reflagclient).[`getFlagDefinitions`](globals.md#getflagdefinitions) + +##### getFlagRemote() + +###### Call Signature + +```ts +getFlagRemote( + key: Key, + defaultValue?: undefined, + userId?: IdType, + companyId?: IdType, +context?: ContextWithTracking): Promise +``` + +###### Type Parameters + + + + + + + + + + + + +
Type Parameter
+ +`Key` *extends* `string` + +
+ +###### Parameters + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ParameterType
+ +`key` + + + +`Key` + +
+ +`defaultValue`? + + + +`undefined` + +
+ +`userId`? + + + +[`IdType`](globals.md#idtype) + +
+ +`companyId`? + + + +[`IdType`](globals.md#idtype) + +
+ +`context`? + + + +[`ContextWithTracking`](globals.md#contextwithtracking) + +
+ +###### Returns + +[`Promise`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Promise)\<`boolean`\> + +###### Inherited from + +[`ReflagClient`](globals.md#reflagclient).[`getFlagRemote`](globals.md#getflagremote) + +###### Call Signature + +```ts +getFlagRemote( + key: Key, + defaultValue: Default, + userId?: IdType, + companyId?: IdType, +context?: ContextWithTracking): Promise> +``` + +###### Type Parameters + + + + + + + + + + + + + + + +
Type Parameter
+ +`Key` *extends* `string` + +
+ +`Default` *extends* `object` \| [`FlagDefaultValue`](globals.md#flagdefaultvalue) + +
+ +###### Parameters + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ParameterType
+ +`key` + + + +`Key` + +
+ +`defaultValue` + + + +`Default` + +
+ +`userId`? + + + +[`IdType`](globals.md#idtype) + +
+ +`companyId`? + + + +[`IdType`](globals.md#idtype) + +
+ +`context`? + + + +[`ContextWithTracking`](globals.md#contextwithtracking) + +
+ +###### Returns + +[`Promise`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Promise)\<[`FlagValueFromDefault`](globals.md#flagvaluefromdefaultt)\<`Default`\>\> + +###### Inherited from + +[`ReflagClient`](globals.md#reflagclient).[`getFlagRemote`](globals.md#getflagremote) + +##### getFlags() + +```ts +getFlags(options: ContextWithTracking): Record +``` + +Return all values and immediately register checks for the returned roots. + +###### Parameters + + + + + + + + + + + + + + +
ParameterType
+ +`options` + + + +[`ContextWithTracking`](globals.md#contextwithtracking) + +
+ +###### Returns + +[`Record`](https://www.typescriptlang.org/docs/handbook/utility-types.html#recordkeys-type)\<`string`, `undefined` \| [`FlagValue`](globals.md#flagvalue)\> + +###### Inherited from + +[`ReflagClient`](globals.md#reflagclient).[`getFlags`](globals.md#getflags) + +##### getFlagsForBootstrap() + +```ts +getFlagsForBootstrap(options: ContextWithTracking): BootstrappedFlags +``` + +Serialization is not a read by the browser; it must not emit check events. + +###### Parameters + + + + + + + + + + + + + + +
ParameterType
+ +`options` + + + +[`ContextWithTracking`](globals.md#contextwithtracking) + +
+ +###### Returns + +[`BootstrappedFlags`](globals.md#bootstrappedflags) + +###### Inherited from + +[`ReflagClient`](globals.md#reflagclient).[`getFlagsForBootstrap`](globals.md#getflagsforbootstrap) + +##### getFlagsRemote() + +```ts +getFlagsRemote( + userId?: IdType, + companyId?: IdType, +context?: ContextWithTracking): Promise> +``` + +###### Parameters + + + + + + + + + + + + + + + + + + + + + + +
ParameterType
+ +`userId`? + + + +[`IdType`](globals.md#idtype) + +
+ +`companyId`? + + + +[`IdType`](globals.md#idtype) + +
+ +`context`? + + + +[`ContextWithTracking`](globals.md#contextwithtracking) + +
+ +###### Returns + +[`Promise`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Promise)\<[`Record`](https://www.typescriptlang.org/docs/handbook/utility-types.html#recordkeys-type)\<`string`, `undefined` \| [`FlagValue`](globals.md#flagvalue)\>\> + +###### Inherited from + +[`ReflagClient`](globals.md#reflagclient).[`getFlagsRemote`](globals.md#getflagsremote) + +##### initialize() + +```ts +initialize(): Promise +``` + +###### Returns + +[`Promise`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Promise)\<`void`\> + +###### Inherited from + +[`ReflagClient`](globals.md#reflagclient).[`initialize`](globals.md#initialize) + +##### pushFlagOverrides() + +```ts +pushFlagOverrides(overrides: + | Partial> + | FlagOverridesFn): () => void +``` + +###### Parameters + + + + + + + + + + + + + + +
ParameterType
+ +`overrides` + + + + \| [`Partial`](https://www.typescriptlang.org/docs/handbook/utility-types.html#partialtype)\<[`Record`](https://www.typescriptlang.org/docs/handbook/utility-types.html#recordkeys-type)\<`string`, [`FlagValue`](globals.md#flagvalue)\>\> \| [`FlagOverridesFn`](globals.md#flagoverridesfn) + +
+ +###### Returns + +`Function` + +###### Returns + +`void` + +###### Inherited from + +[`ReflagClient`](globals.md#reflagclient).[`pushFlagOverrides`](globals.md#pushflagoverrides) + +##### refreshFlags() + +```ts +refreshFlags(waitForVersion?: number): Promise +``` + +###### Parameters + + + + + + + + + + + + + + +
ParameterType
+ +`waitForVersion`? + + + +`number` + +
+ +###### Returns + +[`Promise`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Promise)\<`void`\> + +###### Inherited from + +[`ReflagClient`](globals.md#reflagclient).[`refreshFlags`](globals.md#refreshflags) + +##### setFlagOverrides() + +```ts +setFlagOverrides(overrides: + | Partial> + | FlagOverridesFn): void +``` + +###### Parameters + + + + + + + + + + + + + + +
ParameterType
+ +`overrides` + + + + \| [`Partial`](https://www.typescriptlang.org/docs/handbook/utility-types.html#partialtype)\<[`Record`](https://www.typescriptlang.org/docs/handbook/utility-types.html#recordkeys-type)\<`string`, [`FlagValue`](globals.md#flagvalue)\>\> \| [`FlagOverridesFn`](globals.md#flagoverridesfn) + +
+ +###### Returns + +`void` + +###### Inherited from + +[`ReflagClient`](globals.md#reflagclient).[`setFlagOverrides`](globals.md#setflagoverrides) + +##### track() + +```ts +track( + userId: IdType, + event: string, + options?: TrackOptions & { + companyId: IdType; +}): Promise +``` + +###### Parameters + + + + + + + + + + + + + + + + + + + + + + +
ParameterType
+ +`userId` + + + +[`IdType`](globals.md#idtype) + +
+ +`event` + + + +`string` + +
+ +`options`? + + + +[`TrackOptions`](globals.md#trackoptions) & \{ `companyId`: [`IdType`](globals.md#idtype); \} + +
+ +###### Returns + +[`Promise`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Promise)\<`void`\> + +###### Inherited from + +[`ReflagClient`](globals.md#reflagclient).[`track`](globals.md#track) + +##### updateCompany() + +```ts +updateCompany(companyId: IdType, options?: TrackOptions & { + userId: IdType; +}): Promise +``` + +###### Parameters + + + + + + + + + + + + + + + + + + +
ParameterType
+ +`companyId` + + + +[`IdType`](globals.md#idtype) + +
+ +`options`? + + + +[`TrackOptions`](globals.md#trackoptions) & \{ `userId`: [`IdType`](globals.md#idtype); \} + +
+ +###### Returns + +[`Promise`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Promise)\<`void`\> + +###### Inherited from + +[`ReflagClient`](globals.md#reflagclient).[`updateCompany`](globals.md#updatecompany) + +##### updateUser() + +```ts +updateUser(userId: IdType, options?: TrackOptions): Promise +``` + +###### Parameters + + + + + + + + + + + + + + + + + + +
ParameterType
+ +`userId` + + + +[`IdType`](globals.md#idtype) + +
+ +`options`? + + + +[`TrackOptions`](globals.md#trackoptions) + +
+ +###### Returns + +[`Promise`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Promise)\<`void`\> + +###### Inherited from + +[`ReflagClient`](globals.md#reflagclient).[`updateUser`](globals.md#updateuser) + +*** + +### ReflagClient + +V2 client: flag reads return values and immediately register checks. + +#### Extended by + +- [`EdgeClient`](globals.md#edgeclient) + +#### Constructors + +##### new ReflagClient() + +```ts +new ReflagClient(options: ClientOptions): ReflagClient +``` + +###### Parameters + + + + + + + + + + + + + + +
ParameterType
+ +`options` + + + +[`ClientOptions`](globals.md#clientoptions) + +
+ +###### Returns + +[`ReflagClient`](globals.md#reflagclient) + +#### Properties + + + + + + + + + + + + + + + + + + + + + +
PropertyModifierType
+ + `httpClient` + + + +`readonly` + + + +[`HttpClient`](globals.md#httpclient) + +
+ + `logger` + + + +`readonly` + + + +[`Logger`](globals.md#logger) + +
+ +#### Accessors + +##### flagOverrides + +###### Set Signature + +```ts +set flagOverrides(overrides: + | Partial> + | FlagOverridesFn): void +``` + +###### Deprecated + +Use setFlagOverrides. + +###### Parameters + + + + + + + + + + + + + + +
ParameterType
+ +`overrides` + + + + \| [`Partial`](https://www.typescriptlang.org/docs/handbook/utility-types.html#partialtype)\<[`Record`](https://www.typescriptlang.org/docs/handbook/utility-types.html#recordkeys-type)\<`string`, [`FlagValue`](globals.md#flagvalue)\>\> \| [`FlagOverridesFn`](globals.md#flagoverridesfn) + +
+ +###### Returns + +`void` + +#### Methods + +##### bindClient() + +```ts +bindClient(options: ContextWithTracking): BoundReflagClient +``` + +###### Parameters + + + + + + + + + + + + + + +
ParameterType
+ +`options` + + + +[`ContextWithTracking`](globals.md#contextwithtracking) + +
+ +###### Returns + +[`BoundReflagClient`](globals.md#boundreflagclient) + +##### clearFlagOverrides() + +```ts +clearFlagOverrides(): void +``` + +###### Returns + +`void` + +##### destroy() + +```ts +destroy(): void +``` + +###### Returns + +`void` + +##### flush() + +```ts +flush(): Promise +``` + +###### Returns + +[`Promise`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Promise)\<`void`\> + +##### getFlag() + +###### Call Signature + +```ts +getFlag( + options: ContextWithTracking, + key: Key, + defaultValue?: undefined): boolean +``` + +Read a Boolean with an implicit false default, or supply an explicit value default. + +###### Type Parameters + + + + + + + + + + + + +
Type Parameter
+ +`Key` *extends* `string` + +
+ +###### Parameters + + + + + + + + + + + + + + + + + + + + + + +
ParameterType
+ +`options` + + + +[`ContextWithTracking`](globals.md#contextwithtracking) + +
+ +`key` + + + +`Key` + +
+ +`defaultValue`? + + + +`undefined` + +
+ +###### Returns + +`boolean` + +###### Call Signature + +```ts +getFlag( + options: ContextWithTracking, + key: Key, +defaultValue: Default): FlagValueFromDefault +``` + +Read a Boolean with an implicit false default, or supply an explicit value default. + +###### Type Parameters + + + + + + + + + + + + + + + +
Type Parameter
+ +`Key` *extends* `string` + +
+ +`Default` *extends* `object` \| [`FlagDefaultValue`](globals.md#flagdefaultvalue) + +
+ +###### Parameters + + + + + + + + + + + + + + + + + + + + + + +
ParameterType
+ +`options` + + + +[`ContextWithTracking`](globals.md#contextwithtracking) + +
+ +`key` + + + +`Key` + +
+ +`defaultValue` + + + +`Default` + +
+ +###### Returns + +[`FlagValueFromDefault`](globals.md#flagvaluefromdefaultt)\<`Default`\> + +##### getFlagDefinitions() + +```ts +getFlagDefinitions(): FlagCollection +``` + +Complete definitions, including segments needed for offline evaluation. + +###### Returns + +[`FlagCollection`](globals.md#flagcollection) + +##### getFlagRemote() + +###### Call Signature + +```ts +getFlagRemote( + key: Key, + defaultValue?: undefined, + userId?: IdType, + companyId?: IdType, +context?: ContextWithTracking): Promise +``` + +###### Type Parameters + + + + + + + + + + + + +
Type Parameter
+ +`Key` *extends* `string` + +
+ +###### Parameters + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ParameterType
+ +`key` + + + +`Key` + +
+ +`defaultValue`? + + + +`undefined` + +
+ +`userId`? + + + +[`IdType`](globals.md#idtype) + +
+ +`companyId`? + + + +[`IdType`](globals.md#idtype) + +
+ +`context`? + + + +[`ContextWithTracking`](globals.md#contextwithtracking) + +
+ +###### Returns + +[`Promise`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Promise)\<`boolean`\> + +###### Call Signature + +```ts +getFlagRemote( + key: Key, + defaultValue: Default, + userId?: IdType, + companyId?: IdType, +context?: ContextWithTracking): Promise> +``` + +###### Type Parameters + + + + + + + + + + + + + + + +
Type Parameter
+ +`Key` *extends* `string` + +
+ +`Default` *extends* `object` \| [`FlagDefaultValue`](globals.md#flagdefaultvalue) + +
+ +###### Parameters + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ParameterType
+ +`key` + + + +`Key` + +
+ +`defaultValue` + + + +`Default` + +
+ +`userId`? + + + +[`IdType`](globals.md#idtype) + +
+ +`companyId`? + + + +[`IdType`](globals.md#idtype) + +
+ +`context`? + + + +[`ContextWithTracking`](globals.md#contextwithtracking) + +
+ +###### Returns + +[`Promise`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Promise)\<[`FlagValueFromDefault`](globals.md#flagvaluefromdefaultt)\<`Default`\>\> + +##### getFlags() + +```ts +getFlags(options: ContextWithTracking): Record +``` + +Return all values and immediately register checks for the returned roots. + +###### Parameters + + + + + + + + + + + + + + +
ParameterType
+ +`options` + + + +[`ContextWithTracking`](globals.md#contextwithtracking) + +
+ +###### Returns + +[`Record`](https://www.typescriptlang.org/docs/handbook/utility-types.html#recordkeys-type)\<`string`, `undefined` \| [`FlagValue`](globals.md#flagvalue)\> + +##### getFlagsForBootstrap() + +```ts +getFlagsForBootstrap(options: ContextWithTracking): BootstrappedFlags +``` + +Serialization is not a read by the browser; it must not emit check events. + +###### Parameters + + + + + + + + + + + + + + +
ParameterType
+ +`options` + + + +[`ContextWithTracking`](globals.md#contextwithtracking) + +
+ +###### Returns + +[`BootstrappedFlags`](globals.md#bootstrappedflags) + +##### getFlagsRemote() + +```ts +getFlagsRemote( + userId?: IdType, + companyId?: IdType, +context?: ContextWithTracking): Promise> +``` + +###### Parameters + + + + + + + + + + + + + + + + + + + + + + +
ParameterType
+ +`userId`? + + + +[`IdType`](globals.md#idtype) + +
+ +`companyId`? + + + +[`IdType`](globals.md#idtype) + +
+ +`context`? + + + +[`ContextWithTracking`](globals.md#contextwithtracking) + +
+ +###### Returns + +[`Promise`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Promise)\<[`Record`](https://www.typescriptlang.org/docs/handbook/utility-types.html#recordkeys-type)\<`string`, `undefined` \| [`FlagValue`](globals.md#flagvalue)\>\> + +##### initialize() + +```ts +initialize(): Promise +``` + +###### Returns + +[`Promise`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Promise)\<`void`\> + +##### pushFlagOverrides() + +```ts +pushFlagOverrides(overrides: + | Partial> + | FlagOverridesFn): () => void +``` + +###### Parameters + + + + + + + + + + + + + + +
ParameterType
+ +`overrides` + + + + \| [`Partial`](https://www.typescriptlang.org/docs/handbook/utility-types.html#partialtype)\<[`Record`](https://www.typescriptlang.org/docs/handbook/utility-types.html#recordkeys-type)\<`string`, [`FlagValue`](globals.md#flagvalue)\>\> \| [`FlagOverridesFn`](globals.md#flagoverridesfn) + +
+ +###### Returns + +`Function` + +###### Returns + +`void` + +##### refreshFlags() + +```ts +refreshFlags(waitForVersion?: number): Promise +``` + +###### Parameters + + + + + + + + + + + + + + +
ParameterType
+ +`waitForVersion`? + + + +`number` + +
+ +###### Returns + +[`Promise`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Promise)\<`void`\> + +##### setFlagOverrides() + +```ts +setFlagOverrides(overrides: + | Partial> + | FlagOverridesFn): void +``` + +###### Parameters + + + + + + + + + + + + + + +
ParameterType
+ +`overrides` + + + + \| [`Partial`](https://www.typescriptlang.org/docs/handbook/utility-types.html#partialtype)\<[`Record`](https://www.typescriptlang.org/docs/handbook/utility-types.html#recordkeys-type)\<`string`, [`FlagValue`](globals.md#flagvalue)\>\> \| [`FlagOverridesFn`](globals.md#flagoverridesfn) + +
+ +###### Returns + +`void` + +##### track() + +```ts +track( + userId: IdType, + event: string, + options?: TrackOptions & { + companyId: IdType; +}): Promise +``` + +###### Parameters + + + + + + + + + + + + + + + + + + + + + + +
ParameterType
+ +`userId` + + + +[`IdType`](globals.md#idtype) + +
+ +`event` + + + +`string` + +
+ +`options`? + + + +[`TrackOptions`](globals.md#trackoptions) & \{ `companyId`: [`IdType`](globals.md#idtype); \} + +
+ +###### Returns + +[`Promise`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Promise)\<`void`\> + +##### updateCompany() + +```ts +updateCompany(companyId: IdType, options?: TrackOptions & { + userId: IdType; +}): Promise +``` + +###### Parameters + + + + + + + + + + + + + + + + + + +
ParameterType
+ +`companyId` + + + +[`IdType`](globals.md#idtype) + +
+ +`options`? + + + +[`TrackOptions`](globals.md#trackoptions) & \{ `userId`: [`IdType`](globals.md#idtype); \} + +
+ +###### Returns + +[`Promise`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Promise)\<`void`\> + +##### updateUser() + +```ts +updateUser(userId: IdType, options?: TrackOptions): Promise +``` + +###### Parameters + + + + + + + + + + + + + + + + + + +
ParameterType
+ +`userId` + + + +[`IdType`](globals.md#idtype) + +
+ +`options`? + + + +[`TrackOptions`](globals.md#trackoptions) + +
+ +###### Returns + +[`Promise`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Promise)\<`void`\> + +## Interfaces + +### ContextWithTracking + +#### Extends + +- [`Context`](globals.md#context) + +#### Properties + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
PropertyType
+ + `company?` + + + +\{ `[key: string]`: `any`; `avatar`: `string`; `id`: `undefined` \| [`IdType`](globals.md#idtype); `name`: `string`; \} + +
+ +`company.avatar?` + + + +`string` + +
+ +`company.id` + + + +`undefined` \| [`IdType`](globals.md#idtype) + +
+ +`company.name?` + + + +`string` + +
+ + `enableTracking?` + + + +`boolean` + +
+ + `meta?` + + + +[`TrackingMeta`](globals.md#trackingmeta) + +
+ + `other?` + + + +[`Record`](https://www.typescriptlang.org/docs/handbook/utility-types.html#recordkeys-type)\<`string`, `any`\> + +
+ + `user?` + + + +\{ `[key: string]`: `any`; `avatar`: `string`; `email`: `string`; `id`: `undefined` \| [`IdType`](globals.md#idtype); `name`: `string`; \} + +
+ +`user.avatar?` + + + +`string` + +
+ +`user.email?` + + + +`string` + +
+ +`user.id` + + + +`undefined` \| [`IdType`](globals.md#idtype) + +
+ +`user.name?` + + + +`string` + +
+ +*** + +### Flags + +Augment this interface with flag keys and their returned value types. + +*** + +### FlagsFallbackProvider + +#### Methods + +##### load() + +```ts +load(context: FlagsFallbackProviderContext): Promise +``` + +###### Parameters + + + + + + + + + + + + + + +
ParameterType
+ +`context` + + + +[`FlagsFallbackProviderContext`](globals.md#flagsfallbackprovidercontext) + +
+ +###### Returns + +[`Promise`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Promise)\<`undefined` \| [`FlagsFallbackSnapshot`](globals.md#flagsfallbacksnapshot)\> + +##### save() + +```ts +save(context: FlagsFallbackProviderContext, snapshot: FlagsFallbackSnapshot): Promise +``` + +###### Parameters + + + + + + + + + + + + + + + + + + +
ParameterType
+ +`context` + + + +[`FlagsFallbackProviderContext`](globals.md#flagsfallbackprovidercontext) + +
+ +`snapshot` + + + +[`FlagsFallbackSnapshot`](globals.md#flagsfallbacksnapshot) + +
+ +###### Returns + +[`Promise`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Promise)\<`void`\> + +*** + +### HttpClient + +#### Methods + +##### get() + +```ts +get( + url: string, + headers: Record, +timeoutMs: number): Promise> +``` + +###### Type Parameters + + + + + + + + + + + + +
Type Parameter
+ +`Response` + +
+ +###### Parameters + + + + + + + + + + + + + + + + + + + + + + +
ParameterType
+ +`url` + + + +`string` + +
+ +`headers` + + + +[`Record`](https://www.typescriptlang.org/docs/handbook/utility-types.html#recordkeys-type)\<`string`, `string`\> + +
+ +`timeoutMs` + + + +`number` + +
+ +###### Returns + +[`Promise`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Promise)\<[`HttpClientResponse`](globals.md#httpclientresponseresponse)\<`Response`\>\> + +##### post() + +```ts +post( + url: string, + headers: Record, +body: Body): Promise> +``` + +###### Type Parameters + + + + + + + + + + + + + + + +
Type Parameter
+ +`Body` + +
+ +`Response` + +
+ +###### Parameters + + + + + + + + + + + + + + + + + + + + + + +
ParameterType
+ +`url` + + + +`string` + +
+ +`headers` + + + +[`Record`](https://www.typescriptlang.org/docs/handbook/utility-types.html#recordkeys-type)\<`string`, `string`\> + +
+ +`body` + + + +`Body` + +
+ +###### Returns + +[`Promise`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Promise)\<[`HttpClientResponse`](globals.md#httpclientresponseresponse)\<`Response`\>\> + +*** + +### Logger + +#### Methods + +##### debug() + +```ts +debug(message: string, data?: any): void +``` + +###### Parameters + + + + + + + + + + + + + + + + + + +
ParameterType
+ +`message` + + + +`string` + +
+ +`data`? + + + +`any` + +
+ +###### Returns + +`void` + +##### error() + +```ts +error(message: string, data?: any): void +``` + +###### Parameters + + + + + + + + + + + + + + + + + + +
ParameterType
+ +`message` + + + +`string` + +
+ +`data`? + + + +`any` + +
+ +###### Returns + +`void` + +##### info() + +```ts +info(message: string, data?: any): void +``` + +###### Parameters + + + + + + + + + + + + + + + + + + +
ParameterType
+ +`message` + + + +`string` + +
+ +`data`? + + + +`any` + +
+ +###### Returns + +`void` + +##### warn() + +```ts +warn(message: string, data?: any): void +``` + +###### Parameters + + + + + + + + + + + + + + + + + + +
ParameterType
+ +`message` + + + +`string` + +
+ +`data`? + + + +`any` + +
+ +###### Returns + +`void` + +## Type Aliases + +### Attributes + +```ts +type Attributes = Record; +``` + +*** + +### BatchBufferOptions\ + +```ts +type BatchBufferOptions = { + flushHandler: (items: T[]) => Promise; + flushOnExit: boolean; + intervalMs: number; + logger: Logger; + maxSize: number; +}; +``` + +#### Type Parameters + + + + + + + + + + + + +
Type Parameter
+ +`T` + +
+ +#### Type declaration + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameType
+ + `flushHandler` + + + +(`items`: `T`[]) => [`Promise`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Promise)\<`void`\> + +
+ + `flushOnExit`? + + + +`boolean` + +
+ + `intervalMs`? + + + +`number` + +
+ + `logger`? + + + +[`Logger`](globals.md#logger) + +
+ + `maxSize`? + + + +`number` + +
+ +*** + +### BootstrappedFlags + +```ts +type BootstrappedFlags = { + context: Context; + flags: RawFlags; + flagStateVersion: number; + protocolVersion: 2; +}; +``` + +#### Type declaration + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameType
+ + `context` + + + +[`Context`](globals.md#context) + +
+ + `flags` + + + +[`RawFlags`](globals.md#rawflags) + +
+ + `flagStateVersion`? + + + +`number` + +
+ + `protocolVersion` + + + +`2` + +
+ +*** + +### CacheStrategy + +```ts +type CacheStrategy = "periodically-update" | "in-request"; +``` + +*** + +### ClientOptions + +```ts +type ClientOptions = { + apiBaseUrl: string; + batchOptions: Omit, "flushHandler" | "logger">; + cacheStrategy: CacheStrategy; + configFile: string; + emitEvaluationEvents: boolean; + fallbackFlags: | TypedFlagKey[] + | FlagOverrides; + fetchTimeoutMs: number; + flagOverrides: | FlagOverrides + | FlagOverridesFn; + flagsFallbackProvider: FlagsFallbackProvider; + flagsFetchRetries: number; + flagsPushUrl: string; + flagsSyncMode: FlagsSyncMode; + host: string; + httpClient: HttpClient; + logger: Logger; + logLevel: LogLevel; + offline: boolean; + secretKey: string; +}; +``` + +#### Type declaration + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
+ + `apiBaseUrl`? + + + +`string` + + + +‐ + +
+ + `batchOptions`? + + + +[`Omit`](https://www.typescriptlang.org/docs/handbook/utility-types.html#omittype-keys)\<[`BatchBufferOptions`](globals.md#batchbufferoptionst)\<`any`\>, `"flushHandler"` \| `"logger"`\> + + + +‐ + +
+ + `cacheStrategy`? + + + +[`CacheStrategy`](globals.md#cachestrategy) + + + +**Deprecated** + +Use flagsSyncMode. + +
+ + `configFile`? + + + +`string` + + + +‐ + +
+ + `emitEvaluationEvents`? + + + +`boolean` + + + +**Deprecated** + +Evaluation events are not emitted. + +
+ + `fallbackFlags`? + + + + \| [`TypedFlagKey`](globals.md#typedflagkey)[] + \| [`FlagOverrides`](globals.md#flagoverrides) + + + +Value-only startup fallbacks. Prefer a complete saved collection. + +
+ + `fetchTimeoutMs`? + + + +`number` + + + +‐ + +
+ + `flagOverrides`? + + + + \| [`FlagOverrides`](globals.md#flagoverrides) + \| [`FlagOverridesFn`](globals.md#flagoverridesfn) + + + +Applied after evaluation; overrides do not change dependency results. + +
+ + `flagsFallbackProvider`? + + + +[`FlagsFallbackProvider`](globals.md#flagsfallbackprovider) + + + +‐ + +
+ + `flagsFetchRetries`? + + + +`number` + + + +‐ + +
+ + `flagsPushUrl`? + + + +`string` + + + +‐ + +
+ + `flagsSyncMode`? + + + +[`FlagsSyncMode`](globals.md#flagssyncmode) + + + +‐ + +
+ + `host`? + + + +`string` + + + +**Deprecated** + +Use apiBaseUrl. + +
+ + `httpClient`? + + + +[`HttpClient`](globals.md#httpclient) + + + +‐ + +
+ + `logger`? + + + +[`Logger`](globals.md#logger) + + + +‐ + +
+ + `logLevel`? + + + +[`LogLevel`](globals.md#loglevel) + + + +‐ + +
+ + `offline`? + + + +`boolean` + + + +‐ + +
+ + `secretKey`? + + + +`string` + + + +‐ + +
+ +*** + +### Context + +```ts +type Context = { + company: { + [key: string]: any; avatar: string; + id: IdType | undefined; + name: string; + }; + other: Record; + user: { + [key: string]: any; avatar: string; + email: string; + id: IdType | undefined; + name: string; + }; +}; +``` + +#### Type declaration + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameType
+ + `company`? + + + +\{ +`[key: string]`: `any`; `avatar`: `string`; + `id`: [`IdType`](globals.md#idtype) \| `undefined`; + `name`: `string`; + \} + +
+ +`company.avatar`? + + + +`string` + +
+ +`company.id` + + + +[`IdType`](globals.md#idtype) \| `undefined` + +
+ +`company.name`? + + + +`string` + +
+ + `other`? + + + +[`Record`](https://www.typescriptlang.org/docs/handbook/utility-types.html#recordkeys-type)\<`string`, `any`\> + +
+ + `user`? + + + +\{ +`[key: string]`: `any`; `avatar`: `string`; + `email`: `string`; + `id`: [`IdType`](globals.md#idtype) \| `undefined`; + `name`: `string`; + \} + +
+ +`user.avatar`? + + + +`string` + +
+ +`user.email`? + + + +`string` + +
+ +`user.id` + + + +[`IdType`](globals.md#idtype) \| `undefined` + +
+ +`user.name`? + + + +`string` + +
+ +*** + +### EdgeClientOptions + +```ts +type EdgeClientOptions = Omit; +``` + +*** + +### FileFallbackProviderOptions + +```ts +type FileFallbackProviderOptions = { + directory: string; +}; +``` + +#### Type declaration + + + + + + + + + + + + + + + + +
NameTypeDescription
+ + `directory`? + + + +`string` + + + +Directory where per-environment snapshots are stored. + +
+ +*** + +### FlagCollection + +```ts +type FlagCollection = CompiledCollection & { + segments: NonNullable; +}; +``` + +#### Type declaration + + + + + + + + + + + + + + +
NameType
+ +`segments` + + + +[`NonNullable`](https://www.typescriptlang.org/docs/handbook/utility-types.html#nonnullabletype)\<`CompiledCollection`\[`"segments"`\]\> + +
+ +*** + +### FlagDefaultFor\ + +```ts +type FlagDefaultFor = keyof Registry extends never ? FlagDefaultValue | object : Key extends keyof Registry ? ReadonlyFlagValue | null : never; +``` + +Registered defaults match the value schema; null explicitly requests arbitrary JSON. + +#### Type Parameters + + + + + + + + + + + + + + + +
Type Parameter
+ +`Registry` + +
+ +`Key` *extends* `PropertyKey` + +
+ +*** + +### FlagDefaultValue + +```ts +type FlagDefaultValue = + | null + | boolean + | number + | string + | readonly FlagDefaultValue[] +| {}; +``` + +JSON defaults may be readonly; returned values are independent mutable copies. + +*** + +### FlagDefinition + +```ts +type FlagDefinition = CompiledFlag; +``` + +*** + +### FlagEvent + +```ts +type FlagEvent = { + action: "check"; + evalContext: Record; + evalErrors: EvaluationError[]; + evalMissingFields: string[]; + evalResult: string; + evalRuleResults: boolean[]; + flagStateVersion: number; + key: string; + sourceVersionId: string; +}; +``` + +A check records the selected variant key, never its JSON payload. + +#### Type declaration + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameType
+ + `action` + + + +`"check"` + +
+ + `evalContext`? + + + +[`Record`](https://www.typescriptlang.org/docs/handbook/utility-types.html#recordkeys-type)\<`string`, `unknown`\> + +
+ + `evalErrors`? + + + +`EvaluationError`[] + +
+ + `evalMissingFields`? + + + +`string`[] + +
+ + `evalResult`? + + + +`string` + +
+ + `evalRuleResults`? + + + +`boolean`[] + +
+ + `flagStateVersion`? + + + +`number` + +
+ + `key` + + + +`string` + +
+ + `sourceVersionId`? + + + +`string` + +
+ +*** + +### FlagOverrides + +```ts +type FlagOverrides = Partial : { [Key in keyof Flags]: Flags[Key] }>; +``` + +*** + +### FlagOverridesFn() + +```ts +type FlagOverridesFn = (context: Context) => FlagOverrides; +``` + +#### Parameters + + + + + + + + + + + + + + +
ParameterType
+ +`context` + + + +[`Context`](globals.md#context) + +
+ +#### Returns + +[`FlagOverrides`](globals.md#flagoverrides) + +*** + +### FlagsAPIResponse + +```ts +type FlagsAPIResponse = FlagCollection & { + flagStateVersion: number; + protocolVersion: 2; +}; +``` + +#### Type declaration + + + + + + + + + + + + + + + + + + +
NameType
+ +`flagStateVersion` + + + +`number` + +
+ +`protocolVersion` + + + +`2` + +
+ +*** + +### FlagsFallbackProviderContext + +```ts +type FlagsFallbackProviderContext = { + secretKeyHash: string; +}; +``` + +#### Type declaration + + + + + + + + + + + + + + +
NameType
+ + `secretKeyHash` + + + +`string` + +
+ +*** + +### FlagsFallbackSnapshot + +```ts +type FlagsFallbackSnapshot = FlagCollection & { + flagStateVersion: number; + savedAt: string; + source: "static"; + version: 2; +}; +``` + +V1 snapshots cannot be evaluated by the collection evaluator. + +#### Type declaration + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameType
+ +`flagStateVersion`? + + + +`number` + +
+ +`savedAt` + + + +`string` + +
+ +`source`? + + + +`"static"` + +
+ +`version` + + + +`2` + +
+ +*** + +### FlagsSyncMode + +```ts +type FlagsSyncMode = "polling" | "in-request" | "push"; +``` + +*** + +### FlagValue + +```ts +type FlagValue = + | null + | boolean + | number + | string + | FlagValue[] +| {}; +``` + +*** + +### FlagValueFromDefault\ + +```ts +type FlagValueFromDefault = T extends null ? FlagValue : T extends boolean ? boolean : T extends number ? number : T extends string ? string : T extends readonly infer Element[] ? [Element] extends [never] ? FlagValue[] : FlagValueFromDefault[] : T extends object ? { -readonly [Key in keyof T]: FlagValueFromDefault } : never; +``` + +Infer values from defaults, widening literals. Null explicitly requests arbitrary JSON. + +#### Type Parameters + + + + + + + + + + + + +
Type Parameter
+ +`T` + +
+ +*** + +### GCSFallbackProviderClient + +```ts +type GCSFallbackProviderClient = + | GCSLegacyClient + | GCSGoogleApisClient; +``` + +*** + +### GCSFallbackProviderOptions + +```ts +type GCSFallbackProviderOptions = { + bucket: string; + client: GCSFallbackProviderClient; + keyPrefix: string; +}; +``` + +#### Type declaration + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
+ + `bucket` + + + +`string` + + + +Bucket where snapshots are stored. + +
+ + `client`? + + + +[`GCSFallbackProviderClient`](globals.md#gcsfallbackproviderclient) + + + +Optional GCS client. A default client is created when omitted. + +Accepts either a legacy `bucket().file()` client or a generated +`@googleapis/storage` client. + +TODO(next major): Replace this with a simpler object-store interface. + +
+ + `keyPrefix`? + + + +`string` + + + +Prefix for generated per-environment keys. + +
+ +*** + +### GCSGoogleApisClient + +```ts +type GCSGoogleApisClient = { + objects: { + get: Promise<{ + data: unknown; + }>; + insert: Promise; + }; +}; +``` + +#### Type declaration + + + + + + + + + + + + + + + + + + + + + + +
NameType
+ + `objects` + + + +\{ + `get`: [`Promise`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Promise)\<\{ + `data`: `unknown`; + \}\>; + `insert`: [`Promise`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Promise)\<`unknown`\>; + \} + +
+ + `get()` + + + +[`Promise`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Promise)\<\{ + `data`: `unknown`; + \}\> + +
+ + `insert()` + + + +[`Promise`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Promise)\<`unknown`\> + +
+ +*** + +### GCSLegacyClient + +```ts +type GCSLegacyClient = { + bucket: { + file: { + download: Promise<[Uint8Array]>; + exists: Promise<[boolean]>; + save: Promise; + }; + }; +}; +``` + +#### Type declaration + + + + + + + + + + + + + + +
NameType
+ + `bucket()` + + + +\{ + `file`: \{ + `download`: [`Promise`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Promise)\<\[[`Uint8Array`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Uint8Array)\]\>; + `exists`: [`Promise`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Promise)\<\[`boolean`\]\>; + `save`: [`Promise`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Promise)\<`unknown`\>; + \}; + \} + +
+ +*** + +### HttpClientResponse\ + +```ts +type HttpClientResponse = { + body: Response | undefined; + ok: boolean; + status: number; +}; +``` + +#### Type Parameters + + + + + + + + + + + + +
Type Parameter
+ +`Response` + +
+ +#### Type declaration + + + + + + + + + + + + + + + + + + + + + + +
NameType
+ + `body` + + + +`Response` \| `undefined` + +
+ + `ok` + + + +`boolean` + +
+ + `status` + + + +`number` + +
+ +*** + +### IdType + +```ts +type IdType = string | number; +``` + +*** + +### LogLevel + +```ts +type LogLevel = typeof LOG_LEVELS[number]; +``` + +*** + +### RawFlag + +```ts +type RawFlag = Omit, "sourceVersionId" | "value" | "resolved"> & { + resolved: boolean; + sourceVersionId: string; + value: FlagValue | undefined; +}; +``` + +#### Type declaration + + + + + + + + + + + + + + + + + + + + + + +
NameType
+ +`resolved` + + + +`boolean` + +
+ +`sourceVersionId`? + + + +`string` + +
+ +`value` + + + +[`FlagValue`](globals.md#flagvalue) \| `undefined` + +
+ +*** + +### RawFlags + +```ts +type RawFlags = Record; +``` + +*** + +### RedisFallbackProviderOptions + +```ts +type RedisFallbackProviderOptions = { + client: { + get: Promise; + set: Promise; + }; + keyPrefix: string; +}; +``` + +#### Type declaration + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
+ + `client`? + + + +\{ + `get`: [`Promise`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Promise)\<`undefined` \| `null` \| `string`\>; + `set`: [`Promise`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Promise)\<`unknown`\>; + \} + + + +Optional Redis client. When omitted, a client is created using `REDIS_URL`. + +
+ + `get()` + + + +[`Promise`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Promise)\<`undefined` \| `null` \| `string`\> + + + +‐ + +
+ + `set()` + + + +[`Promise`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Promise)\<`unknown`\> + + + +‐ + +
+ + `keyPrefix`? + + + +`string` + + + +Prefix for generated per-environment keys. + +
+ +*** + +### S3FallbackProviderOptions + +```ts +type S3FallbackProviderOptions = { + bucket: string; + client: { + send: Promise; + }; + keyPrefix: string; +}; +``` + +#### Type declaration + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
+ + `bucket` + + + +`string` + + + +Bucket where snapshots are stored. + +
+ + `client`? + + + +\{ + `send`: [`Promise`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Promise)\<`any`\>; + \} + + + +Optional S3 client. A default client is created when omitted. + +
+ + `send()` + + + +[`Promise`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Promise)\<`any`\> + + + +‐ + +
+ + `keyPrefix`? + + + +`string` + + + +Prefix for generated per-environment keys. + +
+ +*** + +### StaticFallbackProviderOptions + +```ts +type StaticFallbackProviderOptions = { + flags: Record; +}; +``` + +#### Type declaration + + + + + + + + + + + + + + + + +
NameTypeDescription
+ + `flags` + + + +[`Record`](https://www.typescriptlang.org/docs/handbook/utility-types.html#recordkeys-type)\<`string`, [`FlagValue`](globals.md#flagvalue)\> + + + +Static fallback flags keyed by flag key. + +
+ +*** + +### TrackingMeta + +```ts +type TrackingMeta = { + active: boolean; +}; +``` + +#### Type declaration + + + + + + + + + + + + + + +
NameType
+ + `active`? + + + +`boolean` + +
+ +*** + +### TrackOptions + +```ts +type TrackOptions = { + attributes: Attributes; + meta: TrackingMeta; +}; +``` + +#### Type declaration + + + + + + + + + + + + + + + + + + +
NameType
+ + `attributes`? + + + +[`Attributes`](globals.md#attributes) + +
+ + `meta`? + + + +[`TrackingMeta`](globals.md#trackingmeta) + +
+ +*** + +### TypedFlagKey + +```ts +type TypedFlagKey = keyof Flags extends never ? string : Extract; +``` + +*** + +### TypedFlags + +```ts +type TypedFlags = keyof Flags extends never ? Record : { [Key in keyof Flags]: Flags[Key] | undefined }; +``` + +## Variables + +### fallbackProviders + +```ts +const fallbackProviders: { + file: (__namedParameters: FileFallbackProviderOptions) => FlagsFallbackProvider; + gcs: (__namedParameters: GCSFallbackProviderOptions) => FlagsFallbackProvider; + redis: (__namedParameters: RedisFallbackProviderOptions) => FlagsFallbackProvider; + s3: (__namedParameters: S3FallbackProviderOptions) => FlagsFallbackProvider; + static: (__namedParameters: StaticFallbackProviderOptions) => FlagsFallbackProvider; +}; +``` + +#### Type declaration + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDefault value
+ + `file` + + + +(`__namedParameters`: [`FileFallbackProviderOptions`](globals.md#filefallbackprovideroptions)) => [`FlagsFallbackProvider`](globals.md#flagsfallbackprovider) + + + +createFileFallbackProvider + +
+ + `gcs` + + + +(`__namedParameters`: [`GCSFallbackProviderOptions`](globals.md#gcsfallbackprovideroptions)) => [`FlagsFallbackProvider`](globals.md#flagsfallbackprovider) + + + +createGCSFallbackProvider + +
+ + `redis` + + + +(`__namedParameters`: [`RedisFallbackProviderOptions`](globals.md#redisfallbackprovideroptions)) => [`FlagsFallbackProvider`](globals.md#flagsfallbackprovider) + + + +createRedisFallbackProvider + +
+ + `s3` + + + +(`__namedParameters`: [`S3FallbackProviderOptions`](globals.md#s3fallbackprovideroptions)) => [`FlagsFallbackProvider`](globals.md#flagsfallbackprovider) + + + +createS3FallbackProvider + +
+ + `static` + + + +(`__namedParameters`: [`StaticFallbackProviderOptions`](globals.md#staticfallbackprovideroptions)) => [`FlagsFallbackProvider`](globals.md#flagsfallbackprovider) + + + +createStaticFallbackProvider + +
diff --git a/previews/sdk-versioning/v2/@reflag/react-sdk/README.md b/previews/sdk-versioning/v2/@reflag/react-sdk/README.md new file mode 100644 index 0000000..c0e6d91 --- /dev/null +++ b/previews/sdk-versioning/v2/@reflag/react-sdk/README.md @@ -0,0 +1,171 @@ +--- +layout: + visible: true +title: + visible: true +description: + visible: false +tableOfContents: + visible: true +outline: + visible: true +pagination: + visible: true +--- + +{% hint style="info" %} +SDK v2 candidate documentation, not a published release. Installation commands target the planned major; matching SDK/CLI releases are still required. Generated from `@reflag/react-sdk@2.0.0-next.0` in [the JavaScript repository](https://github.com/reflagcom/javascript/tree/9f3a0ae027e386747a2d4cf7683a68dcab70b355/packages/react-sdk). SDK READMEs and code comments there remain authoritative. +{% endhint %} + +# Reflag React SDK v2 + +Direct-value feature flags for React, backed by [Browser SDK v2](../browser-sdk/README.md). + +> Breaking prerelease: requires compatible v2 server endpoints and **Node SDK v2** for bootstrap. The v2 CLI generates direct-value declarations; regenerate legacy wrapper-shaped output when migrating. Useful default-based inference does not require generated declarations. React Native remains on published React/Browser v1 in this repository. + +## Setup + +```tsx +import { ReflagProvider } from "@reflag/react-sdk"; + + + +; +``` + +Provider options forward Browser SDK settings, including `logger`, `debug`, `enableTracking`, `offline`, cache/fallback options, `toolbar`, `feedback`, `credentials` and API origins. A custom logger takes precedence over debug logging. Live updates default to true; set `enableLiveFlagUpdates={false}` to disable them. + +## `useFlag(key, defaultValue?)` + +```tsx +import { useFlag, useTrack } from "@reflag/react-sdk"; + +function Huddles() { + const enabled = useFlag("huddles"); // boolean, implicit false + const settings = useFlag("huddle-settings", { maxParticipants: 10 }); + const track = useTrack(); + if (!enabled) return null; + return ( + + ); +} +``` + +Bare reads return Boolean/false, including while initial data is unavailable. A positional default infers widened direct types, even without generated declarations: + +```ts +useFlag("model", "quick"); // string +useFlag("limit", 10); // number +useFlag("settings", { model: "quick" }); // { model: string } +useFlag("nullable-settings", null); // FlagValue: arbitrary JSON +``` + +Non-null defaults enforce only the top-level JSON category (Boolean/number/string/array/object). Mismatches return the default and warn without logging the payload. Explicit null accepts authored null and all other JSON; missing values return null. Accepted false, zero and empty strings are preserved. Nested fields/elements are not schema-validated. Values/defaults are independent mutable copies; readonly defaults are accepted. + +`useFlag` never suspends. Its second argument is **payload data**, not `{ suspense: ... }` options; ordinary JSON has no reserved keys. Use the dedicated hook below for suspension. Use `useIsLoading()` or the provider's `loadingComponent` when you need an explicit loading UI. + +## `useFlagSuspense(key, defaultValue?)` + +```tsx +import { Suspense } from "react"; +import { useFlagSuspense } from "@reflag/react-sdk"; + +function Model() { + const model = useFlagSuspense("model", "quick"); + return {model}; +} + +Loading flags…}> + +; +``` + +This hook has the same Boolean/default-based return types. It waits for initialization before reading. Once initialization settles, missing flags return their default rather than suspending forever. Bootstrap values can render immediately. Suspended attempts do not register a root read before data is ready. + +The provider's existing `suspense` prop continues to configure **`useOptInFlags` only**. It does not change `useFlag`; `useFlagSuspense` is explicit. + +## Checks and usage tracking + +A completed getter/hook read registers its requested-root check immediately, even if the value is ignored. Actual delivery remains buffered and identity-aware rate-limited by the Browser SDK. Checks contain selected variant/source/snapshot identity, not payloads. Defaults, category mismatches and local overrides never invent authored selections. Diagnostic/bootstrap inspection registers no checks. + +Application usage and feedback remain separate hooks: + +```ts +const track = useTrack(); +await track("Started huddle", { button: "cta" }); +const requestFeedback = useRequestFeedback(); +requestFeedback({ flagKey: "huddles", title: "How did it go?" }); +const sendFeedback = useSendFeedback(); +await sendFeedback({ flagKey: "huddles", score: 5 }); +``` + +See [Browser feedback documentation](../../documents/browser-sdk/FEEDBACK.md) for UI/prompt configuration. + +## Optional key registration + +```ts +import "@reflag/react-sdk"; +declare module "@reflag/react-sdk" { + interface Flags { + huddles: boolean; + "huddle-settings": { maxParticipants: number }; + } +} +``` + +Registration constrains keys and checks non-null defaults against the registered value schema, including required and nested properties. Named-interface and readonly defaults are supported. Explicit `null` remains an arbitrary-JSON request; bare/`undefined` reads remain Boolean. The positional default still determines the widened return type, and generated declarations do not add nested runtime validation. The same rules apply to `useFlagSuspense`. The v2 CLI generates direct-value declarations from current production-environment variants; regenerate legacy wrapper declarations when migrating. + +## Bootstrap and SSR + +On the server, use Node SDK v2: + +```ts +const flags = serverClient.getFlagsForBootstrap(context); +``` + +Pass the **entire** safe, serialized envelope to the client provider: + +```tsx +import { ReflagBootstrappedProvider } from "@reflag/react-sdk"; + + + +; +``` + +The envelope includes `protocolVersion: 2`, context, evaluated flag results and optional `flagStateVersion`. V1 bootstrap is rejected. Use a snapshot version for live updates; later refreshes use browser-visible context. Do not send secrets/server-only attributes to the browser. Disable live updates if later browser evaluation must not replace server-only results. + +For a pre-existing Browser v2 client, use ``. The provider does not own its lifecycle; initialize/stop externally, except that a suspending read can start on-demand initialization. + +## Context, loading and events + +- `useUpdateUser()`, `useUpdateCompany()` and `useUpdateOtherContext()` return async shallow-update functions. `useClient().setContext(context)` replaces the whole context. +- Context changes clear old-context values while fetching. Stale/in-flight responses cannot replace a newer context/snapshot. +- `useIsLoading()` returns provider loading state. `loadingComponent` and `initialLoading` configure initial presentation; they are not getter arguments. +- `useClient()` returns the underlying Browser client. Its `getFlags()` returns direct values/checks; `getFlagResults()` is diagnostic-only. +- `useOnEvent(type, handler, client?)` subscribes and unsubscribes on unmount. A supplied external client works without a provider. + +## Opt-in hooks + +```tsx +const { flags, isLoading } = useOptInFlags(); +const setOptIn = useSetOptIn(); +await setOptIn("huddles", { optedIn: true }); +await setOptIn("huddles", { optedIn: false, scope: "company" }); +``` + +Metadata arrives with the initial v2 evaluation for regular providers. Bootstrapped providers fetch missing metadata on demand from `/flags/evaluated` without replacing server-derived values. `isLoading` covers initialization and metadata retrieval; it settles on success or failure. Metadata presentation includes `isEnabled` and membership fields, separately from direct flag reads. User/company grants remain independent. Mutations confirm versioned evaluation and membership in a single refresh. + +`useOptInFlags({ suspense: true | false })` retains its own per-hook loading options and may inherit provider `suspense`. These options are unrelated to the positional payload defaults used by `useFlag` / `useFlagSuspense`. + +## Migration from v1 + +Use `const enabled = useFlag(key)` instead of destructuring `.isEnabled`. Replace wrapper config access with an authored multivariate flag and positional default. Move `.track()` and `.requestFeedback()` to separate hooks. Replace `useFlag(key, { suspense: true })` with `useFlagSuspense(key)`, and use `useIsLoading()` instead of a flag wrapper's loading property. Upgrade Browser/server bootstrap together. Nothing here publishes the prerelease or deploys its required server changes. diff --git a/previews/sdk-versioning/v2/@reflag/react-sdk/globals.md b/previews/sdk-versioning/v2/@reflag/react-sdk/globals.md new file mode 100644 index 0000000..7e1e288 --- /dev/null +++ b/previews/sdk-versioning/v2/@reflag/react-sdk/globals.md @@ -0,0 +1,2437 @@ +--- +layout: + visible: true +title: + visible: true +description: + visible: false +tableOfContents: + visible: true +outline: + visible: true +pagination: + visible: true +--- + +{% hint style="info" %} +SDK v2 candidate documentation, not a published release. Installation commands target the planned major; matching SDK/CLI releases are still required. Generated from `@reflag/react-sdk@2.0.0-next.0` in [the JavaScript repository](https://github.com/reflagcom/javascript/tree/9f3a0ae027e386747a2d4cf7683a68dcab70b355/packages/react-sdk). SDK READMEs and code comments there remain authoritative. +{% endhint %} + +# @reflag/react-sdk + +## Interfaces + +### CheckEvent + +A root read records selection identity, never its JSON payload. + +#### Properties + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
PropertyType
+ + `action` + + + +`"check"` + +
+ + `evalContext?` + + + +[`ReflagContext`](../browser-sdk/globals.md#reflagcontext) + +
+ + `evalErrors?` + + + +[`EvaluationError`](../browser-sdk/globals.md#evaluationerror)[] + +
+ + `evalMissingFields?` + + + +`string`[] + +
+ + `evalResult?` + + + +`string` + +
+ + `evalRuleResults?` + + + +`boolean`[] + +
+ + `flagStateVersion?` + + + +`number` + +
+ + `key` + + + +`string` + +
+ + `sourceVersionId?` + + + +`string` + +
+ +*** + +### CompanyContext + +Context is a set of key-value pairs. +This is used to determine if feature targeting matches and to track events. +Id should always be present so that it can be referenced to an existing company. + +#### Indexable + +```ts +[key: string]: ContextValue +``` + +#### Properties + + + + + + + + + + + + + + + + + + + + + +
PropertyTypeDescription
+ + `id` + + + +`undefined` \| `string` \| `number` + + + +Company id + +
+ + `name?` + + + +`string` + + + +Company name + +
+ +*** + +### Flags + +Optional key/value registration; singular reads infer their type from defaults. + +*** + +### UserContext + +Context is a set of key-value pairs. +This is used to determine if feature targeting matches and to track events. +Id should always be present so that it can be referenced to an existing user. + +#### Indexable + +```ts +[key: string]: ContextValue +``` + +#### Properties + + + + + + + + + + + + + + + + + + + + + + + + + + +
PropertyTypeDescription
+ + `email?` + + + +`string` + + + +User email + +
+ + `id` + + + +`undefined` \| `string` \| `number` + + + +User id + +
+ + `name?` + + + +`string` + + + +User name + +
+ +## Type Aliases + +### BootstrappedFlags + +```ts +type BootstrappedFlags = BootstrappedState & { + flags: RawFlags; +}; +``` + +#### Type declaration + + + + + + + + + + + + + + +
NameType
+ +`flags` + + + +[`RawFlags`](../browser-sdk/globals.md#rawflags) + +
+ +*** + +### FlagDefaultValue + +```ts +type FlagDefaultValue = + | null + | boolean + | number + | string + | readonly FlagDefaultValue[] +| {}; +``` + +*** + +### FlagKey + +```ts +type FlagKey = keyof Flags extends never ? string : Extract; +``` + +*** + +### FlagValue + +```ts +type FlagValue = + | null + | boolean + | number + | string + | FlagValue[] +| {}; +``` + +*** + +### FlagValueFromDefault\ + +```ts +type FlagValueFromDefault = T extends null ? FlagValue : T extends boolean ? boolean : T extends number ? number : T extends string ? string : T extends readonly infer Element[] ? [Element] extends [never] ? FlagValue[] : FlagValueFromDefault[] : T extends object ? { -readonly [Key in keyof T]: FlagValueFromDefault } : never; +``` + +Widen literals and return mutable copies. Null requests arbitrary JSON. + +#### Type Parameters + + + + + + + + + + + + +
Type Parameter
+ +`T` + +
+ +*** + +### OptInFlag + +```ts +type OptInFlag = Omit & { + key: FlagKey; +}; +``` + +An opt-in-enabled flag for the generated React SDK flag definitions. + +#### Type declaration + + + + + + + + + + + + + + +
NameType
+ +`key` + + + +[`FlagKey`](globals.md#flagkey) + +
+ +*** + +### RawFlags + +```ts +type RawFlags = RawFlags; +``` + +Describes a collection of evaluated raw flags. + +*** + +### ReflagBootstrappedProps + +```ts +type ReflagBootstrappedProps = ReflagPropsBase & ReflagInitOptionsBase & { + flags: BootstrappedFlags; +}; +``` + +Props for the ReflagBootstrappedProvider. + +#### Type declaration + + + + + + + + + + + + + + + + +
NameTypeDescription
+ +`flags` + + + +[`BootstrappedFlags`](globals.md#bootstrappedflags) + + + +Pre-fetched flags used for the initial render. The browser client fetches opt-in +metadata on demand when opt-in flags are requested. + +
+ +*** + +### ReflagClientProviderProps + +```ts +type ReflagClientProviderProps = Omit & { + client: ReflagClient; +}; +``` + +Props for the ReflagClientProvider. + +#### Type declaration + + + + + + + + + + + + + + +
NameType
+ +`client` + + + +[`ReflagClient`](../browser-sdk/globals.md#reflagclient) + +
+ +*** + +### ReflagInitOptionsBase + +```ts +type ReflagInitOptionsBase = Omit; +``` + +**`Internal`** + +Base init options for the ReflagProvider and ReflagBootstrappedProvider. + +*** + +### ReflagProps + +```ts +type ReflagProps = ReflagPropsBase & ReflagInitOptionsBase & { + company: CompanyContext; + context: ReflagContext; + otherContext: Record; + user: UserContext; +}; +``` + +Props for the ReflagProvider. + +#### Type declaration + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
+ +`company`? + + + +[`CompanyContext`](globals.md#companycontext) + + + +Company related context. If you provide `id` Reflag will enrich the evaluation context with +company attributes on Reflag servers. + +**Deprecated** + +Use `context` instead, this property will be removed in the next major version + +
+ +`context`? + + + +[`ReflagContext`](../browser-sdk/globals.md#reflagcontext) + + + +The context to use for the ReflagClient containing user, company, and other context. + +
+ +`otherContext`? + + + +[`Record`](https://www.typescriptlang.org/docs/handbook/utility-types.html#recordkeys-type)\<`string`, `string` \| `number` \| `undefined`\> + + + +Context which is not related to a user or a company. + +**Deprecated** + +Use `context` instead, this property will be removed in the next major version + +
+ +`user`? + + + +[`UserContext`](globals.md#usercontext) + + + +User related context. If you provide `id` Reflag will enrich the evaluation context with +user attributes on Reflag servers. + +**Deprecated** + +Use `context` instead, this property will be removed in the next major version + +
+ +*** + +### ReflagPropsBase + +```ts +type ReflagPropsBase = { + children: ReactNode; + debug: boolean; + initialLoading: boolean; + loadingComponent: ReactNode; + logger: Logger; + suspense: boolean; +}; +``` + +**`Internal`** + +Base props for the ReflagProvider and ReflagBootstrappedProvider. + +#### Type declaration + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
+ + `children`? + + + +`ReactNode` + + + +The children to render after the client is initialized. + +
+ + `debug`? + + + +`boolean` + + + +Set to `true` to enable debug logging to the console, + +
+ + `initialLoading`? + + + +`boolean` + + + +Set to `true` to show the loading component while the client is initializing. + +
+ + `loadingComponent`? + + + +`ReactNode` + + + +A React component to show while the client is initializing. + +
+ + `logger`? + + + +[`Logger`](../browser-sdk/globals.md#logger) + + + +A custom logger to use for SDK logs. +Use this for advanced control or filtering of SDK logs. +If both `logger` and `debug` are provided, `logger` takes precedence. + +
+ + `suspense`? + + + +`boolean` + + + +Set to `true` to make `useOptInFlags` suspend while its metadata loads. +Use `useFlagSuspense` for suspending direct-value reads. + +
+ +*** + +### RequestFeedbackOptions + +```ts +type RequestFeedbackOptions = Omit; +``` + +*** + +### SetOptInOptions + +```ts +type SetOptInOptions = { + optedIn: boolean; + scope: "user" | "company"; +}; +``` + +Represents a flag. + +#### Type declaration + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
+ + `optedIn` + + + +`boolean` + + + +Whether the scoped subject has opted in. + +
+ + `scope`? + + + +`"user"` \| `"company"` + + + +Whether to update the current user or current company. Defaults to `user`. + +
+ +*** + +### StorageAdapter + +```ts +type StorageAdapter = { + getItem: Promise; + removeItem: Promise; + setItem: Promise; +}; +``` + +#### Type declaration + + + + + + + + + + + + + + + + + + + + + + +
NameType
+ + `getItem()` + + + +[`Promise`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Promise)\<`null` \| `string`\> + +
+ + `removeItem()`? + + + +[`Promise`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Promise)\<`void`\> + +
+ + `setItem()` + + + +[`Promise`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Promise)\<`void`\> + +
+ +*** + +### TrackEvent + +```ts +type TrackEvent = { + attributes: | Record + | null; + company: CompanyContext; + eventName: string; + user: UserContext; +}; +``` + +#### Type declaration + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameType
+ + `attributes`? + + + + \| [`Record`](https://www.typescriptlang.org/docs/handbook/utility-types.html#recordkeys-type)\<`string`, `any`\> + \| `null` + +
+ + `company`? + + + +[`CompanyContext`](globals.md#companycontext) + +
+ + `eventName` + + + +`string` + +
+ + `user` + + + +[`UserContext`](globals.md#usercontext) + +
+ +*** + +### TypedFlags + +```ts +type TypedFlags = keyof Flags extends never ? Record : { [Key in keyof Flags]: Flags[Key] | undefined }; +``` + +*** + +### UseOptInFlagsOptions + +```ts +type UseOptInFlagsOptions = { + suspense: boolean; +}; +``` + +#### Type declaration + + + + + + + + + + + + + + + + +
NameTypeDescription
+ + `suspense`? + + + +`boolean` + + + +Override the provider suspense setting for this `useOptInFlags` call. +When true, `useOptInFlags` throws a promise while opt-in flags are loading. + +
+ +*** + +### UseOptInFlagsResult + +```ts +type UseOptInFlagsResult = { + flags: OptInFlag[]; + isLoading: boolean; +}; +``` + +#### Type declaration + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
+ + `flags` + + + +[`OptInFlag`](globals.md#optinflag)[] + + + +Opt-in-enabled flags for the current context. + +
+ + `isLoading` + + + +`boolean` + + + +Whether a bootstrapped provider is fetching opt-in metadata. + +
+ +## Functions + +### ReflagBootstrappedProvider() + +```ts +function ReflagBootstrappedProvider(__namedParameters: ReflagBootstrappedProps): Element +``` + +Bootstrapped Provider for the ReflagClient using pre-fetched flags. + +#### Parameters + + + + + + + + + + + + + + +
ParameterType
+ +`__namedParameters` + + + +[`ReflagBootstrappedProps`](globals.md#reflagbootstrappedprops) + +
+ +#### Returns + +`Element` + +*** + +### ReflagClientProvider() + +```ts +function ReflagClientProvider(__namedParameters: ReflagClientProviderProps): Element +``` + +#### Parameters + + + + + + + + + + + + + + +
ParameterType
+ +`__namedParameters` + + + +[`ReflagClientProviderProps`](globals.md#reflagclientproviderprops) + +
+ +#### Returns + +`Element` + +*** + +### ReflagProvider() + +```ts +function ReflagProvider(__namedParameters: ReflagProps): Element +``` + +Provider for the ReflagClient. + +#### Parameters + + + + + + + + + + + + + + +
ParameterType
+ +`__namedParameters` + + + +[`ReflagProps`](globals.md#reflagprops) + +
+ +#### Returns + +`Element` + +*** + +### useClient() + +```ts +function useClient(): ReflagClient +``` + +Returns the current `ReflagClient` used by the `ReflagProvider`. + +This is useful if you need to access the `ReflagClient` outside of the `ReflagProvider`. + +#### Returns + +[`ReflagClient`](../browser-sdk/globals.md#reflagclient) + +The `ReflagClient`. + +#### Example + +```ts +import { useClient } from '@reflag/react-sdk'; + +function App() { + const client = useClient(); + console.log(client.getContext()); +} +``` + +*** + +### ~~useFeature()~~ + +#### Deprecated + +Use useFlag. + +#### Call Signature + +```ts +function useFeature(key: string, defaultValue?: undefined): boolean +``` + +Boolean-first, non-suspending reads. Supply a positional default for JSON values. + +##### Parameters + + + + + + + + + + + + + + + + + + +
ParameterType
+ +`key` + + + +`string` + +
+ +`defaultValue`? + + + +`undefined` + +
+ +##### Returns + +`boolean` + +##### Deprecated + +Use useFlag. + +#### Call Signature + +```ts +function useFeature(key: Key, defaultValue: Default): FlagValueFromDefault +``` + +Boolean-first, non-suspending reads. Supply a positional default for JSON values. + +##### Type Parameters + + + + + + + + + + + + + + + +
Type Parameter
+ +`Key` *extends* `string` + +
+ +`Default` *extends* `object` \| [`FlagDefaultValue`](globals.md#flagdefaultvalue) + +
+ +##### Parameters + + + + + + + + + + + + + + + + + + +
ParameterType
+ +`key` + + + +`Key` + +
+ +`defaultValue` + + + +`Default` + +
+ +##### Returns + +[`FlagValueFromDefault`](globals.md#flagvaluefromdefaultt)\<`Default`\> + +##### Deprecated + +Use useFlag. + +#### Call Signature + +```ts +function useFeature(key: string, defaultValue: Default): FlagValueFromDefault +``` + +Boolean-first, non-suspending reads. Supply a positional default for JSON values. + +##### Type Parameters + + + + + + + + + + + + +
Type Parameter
+ +`Default` *extends* `object` \| [`FlagDefaultValue`](globals.md#flagdefaultvalue) + +
+ +##### Parameters + + + + + + + + + + + + + + + + + + +
ParameterType
+ +`key` + + + +`string` + +
+ +`defaultValue` + + + +`Default` + +
+ +##### Returns + +[`FlagValueFromDefault`](globals.md#flagvaluefromdefaultt)\<`Default`\> + +##### Deprecated + +Use useFlag. + +*** + +### useFlag() + +#### Call Signature + +```ts +function useFlag(key: string, defaultValue?: undefined): boolean +``` + +Boolean-first, non-suspending reads. Supply a positional default for JSON values. + +##### Parameters + + + + + + + + + + + + + + + + + + +
ParameterType
+ +`key` + + + +`string` + +
+ +`defaultValue`? + + + +`undefined` + +
+ +##### Returns + +`boolean` + +#### Call Signature + +```ts +function useFlag(key: Key, defaultValue: Default): FlagValueFromDefault +``` + +Boolean-first, non-suspending reads. Supply a positional default for JSON values. + +##### Type Parameters + + + + + + + + + + + + + + + +
Type Parameter
+ +`Key` *extends* `string` + +
+ +`Default` *extends* `object` \| [`FlagDefaultValue`](globals.md#flagdefaultvalue) + +
+ +##### Parameters + + + + + + + + + + + + + + + + + + +
ParameterType
+ +`key` + + + +`Key` + +
+ +`defaultValue` + + + +`Default` + +
+ +##### Returns + +[`FlagValueFromDefault`](globals.md#flagvaluefromdefaultt)\<`Default`\> + +#### Call Signature + +```ts +function useFlag(key: string, defaultValue: Default): FlagValueFromDefault +``` + +Boolean-first, non-suspending reads. Supply a positional default for JSON values. + +##### Type Parameters + + + + + + + + + + + + +
Type Parameter
+ +`Default` *extends* `object` \| [`FlagDefaultValue`](globals.md#flagdefaultvalue) + +
+ +##### Parameters + + + + + + + + + + + + + + + + + + +
ParameterType
+ +`key` + + + +`string` + +
+ +`defaultValue` + + + +`Default` + +
+ +##### Returns + +[`FlagValueFromDefault`](globals.md#flagvaluefromdefaultt)\<`Default`\> + +*** + +### useFlagSuspense() + +#### Call Signature + +```ts +function useFlagSuspense(key: string, defaultValue?: undefined): boolean +``` + +Wait for initialization, then read once. Missing flags use the default, not an endless suspension. + +##### Parameters + + + + + + + + + + + + + + + + + + +
ParameterType
+ +`key` + + + +`string` + +
+ +`defaultValue`? + + + +`undefined` + +
+ +##### Returns + +`boolean` + +#### Call Signature + +```ts +function useFlagSuspense(key: Key, defaultValue: Default): FlagValueFromDefault +``` + +Wait for initialization, then read once. Missing flags use the default, not an endless suspension. + +##### Type Parameters + + + + + + + + + + + + + + + +
Type Parameter
+ +`Key` *extends* `string` + +
+ +`Default` *extends* `object` \| [`FlagDefaultValue`](globals.md#flagdefaultvalue) + +
+ +##### Parameters + + + + + + + + + + + + + + + + + + +
ParameterType
+ +`key` + + + +`Key` + +
+ +`defaultValue` + + + +`Default` + +
+ +##### Returns + +[`FlagValueFromDefault`](globals.md#flagvaluefromdefaultt)\<`Default`\> + +#### Call Signature + +```ts +function useFlagSuspense(key: string, defaultValue: Default): FlagValueFromDefault +``` + +Wait for initialization, then read once. Missing flags use the default, not an endless suspension. + +##### Type Parameters + + + + + + + + + + + + +
Type Parameter
+ +`Default` *extends* `object` \| [`FlagDefaultValue`](globals.md#flagdefaultvalue) + +
+ +##### Parameters + + + + + + + + + + + + + + + + + + +
ParameterType
+ +`key` + + + +`string` + +
+ +`defaultValue` + + + +`Default` + +
+ +##### Returns + +[`FlagValueFromDefault`](globals.md#flagvaluefromdefaultt)\<`Default`\> + +*** + +### useIsLoading() + +```ts +function useIsLoading(): boolean +``` + +Returns a boolean indicating if the Reflag client is loading. +You can use this to check if the Reflag client is loading at any point in your application. +Initially, the value will be true until the client is initialized. + +#### Returns + +`boolean` + +A boolean indicating if the Reflag client is loading. + +#### Example + +```ts +import { useIsLoading } from '@reflag/react-sdk'; + +const isLoading = useIsLoading(); + +console.log(isLoading); +``` + +*** + +### useOnEvent() + +```ts +function useOnEvent( + event: THookType, + handler: (arg0: HookArgs[THookType]) => void, + client?: ReflagClient): void +``` + +Attach a callback handler to client events to act on changes. It automatically disposes itself on unmount. + +#### Type Parameters + + + + + + + + + + + + +
Type Parameter
+ +`THookType` *extends* keyof [`HookArgs`](../browser-sdk/globals.md#hookargs) + +
+ +#### Parameters + + + + + + + + + + + + + + + + + + + + + + + + + + +
ParameterTypeDescription
+ +`event` + + + +`THookType` + + + +The event to listen to. + +
+ +`handler` + + + +(`arg0`: [`HookArgs`](../browser-sdk/globals.md#hookargs)\[`THookType`\]) => `void` + + + +The function to call when the event is triggered. + +
+ +`client`? + + + +[`ReflagClient`](../browser-sdk/globals.md#reflagclient) + + + +The Reflag client to listen to. If not provided, the client will be retrieved from the context. + +
+ +#### Returns + +`void` + +#### Example + +```ts +import { useOnEvent } from '@reflag/react-sdk'; + +useOnEvent("flagsUpdated", () => { + console.log("flags updated"); +}); +``` + +*** + +### useOptInFlags() + +```ts +function useOptInFlags(options: UseOptInFlagsOptions): UseOptInFlagsResult +``` + +Returns opt-in-enabled flags and their loading state for the current context. + +The loading state is only used with `ReflagBootstrappedProvider` while +opt-in metadata is fetched on demand. Regular providers load opt-in metadata +with the initial flags. +When suspense is enabled for the provider or this hook, it suspends instead +of returning a loading result. + +#### Parameters + + + + + + + + + + + + + + +
ParameterType
+ +`options` + + + +[`UseOptInFlagsOptions`](globals.md#useoptinflagsoptions) + +
+ +#### Returns + +[`UseOptInFlagsResult`](globals.md#useoptinflagsresult) + +*** + +### useRequestFeedback() + +```ts +function useRequestFeedback(): (options: RequestFeedbackData) => void +``` + +Returns a function to open up the feedback form +Note: When calling `useRequestFeedback`, user/company must already be set. + +See [link](../../documents/browser-sdk/FEEDBACK.md) for more information + +```ts +const requestFeedback = useRequestFeedback(); +reflag.requestFeedback({ + flagKey: "file-uploads", + title: "How satisfied are you with file uploads?", +}); +``` + +#### Returns + +`Function` + +##### Parameters + + + + + + + + + + + + + + +
ParameterType
+ +`options` + + + +[`RequestFeedbackData`](../browser-sdk/globals.md#requestfeedbackdata) + +
+ +##### Returns + +`void` + +*** + +### useSendFeedback() + +```ts +function useSendFeedback(): (opts: UnassignedFeedback) => Promise< + | undefined +| Response> +``` + +Returns a function to manually send feedback collected from a user. +Note: When calling `useSendFeedback`, user/company must already be set. + +See [link](../../documents/browser-sdk/FEEDBACK.md) for more information + +```ts +const sendFeedback = useSendFeedback(); +sendFeedback({ + flagKey: "huddle"; + question: "How did you like the new huddle feature?"; + score: 5; + comment: "I loved it!"; +}); +``` + +#### Returns + +`Function` + +##### Parameters + + + + + + + + + + + + + + +
ParameterType
+ +`opts` + + + +[`UnassignedFeedback`](../browser-sdk/globals.md#unassignedfeedback) + +
+ +##### Returns + +[`Promise`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Promise)\< + \| `undefined` + \| [`Response`](https://developer.mozilla.org/docs/Web/API/Response)\> + +*** + +### useSetOptIn() + +```ts +function useSetOptIn(): (key: string, options: SetOptInOptions) => Promise< + | undefined +| Response> +``` + +Returns a function to set whether the current user or company has opted into a flag. + +#### Returns + +`Function` + +##### Parameters + + + + + + + + + + + + + + + + + + +
ParameterType
+ +`key` + + + +`string` + +
+ +`options` + + + +[`SetOptInOptions`](globals.md#setoptinoptions) + +
+ +##### Returns + +[`Promise`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Promise)\< + \| `undefined` + \| [`Response`](https://developer.mozilla.org/docs/Web/API/Response)\> + +*** + +### useTrack() + +```ts +function useTrack(): (eventName: string, attributes?: + | null + | Record) => Promise< + | undefined +| Response> +``` + +Returns a function to send an event when a user performs an action +Note: When calling `useTrack`, user/company must already be set. + +```ts +const track = useTrack(); +track("Started Huddle", { button: "cta" }); +``` + +#### Returns + +`Function` + +##### Parameters + + + + + + + + + + + + + + + + + + +
ParameterType
+ +`eventName` + + + +`string` + +
+ +`attributes`? + + + + \| `null` \| [`Record`](https://www.typescriptlang.org/docs/handbook/utility-types.html#recordkeys-type)\<`string`, `any`\> + +
+ +##### Returns + +[`Promise`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Promise)\< + \| `undefined` + \| [`Response`](https://developer.mozilla.org/docs/Web/API/Response)\> + +*** + +### useUpdateCompany() + +```ts +function useUpdateCompany(): (opts: {}) => Promise +``` + +Returns a function to update the current company's information. +For example, if the company changed plan or opted into a beta-feature. + +The method returned is a function which returns a promise that +resolves when after the features have been updated as a result +of the company update. + +```ts +const updateCompany = useUpdateCompany(); +updateCompany({ plan: "enterprise" }).then(() => console.log("Flags updated")); +``` + +#### Returns + +`Function` + +##### Parameters + + + + + + + + + + + + + + +
ParameterType
+ +`opts` + + + +\{\} + +
+ +##### Returns + +[`Promise`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Promise)\<`void`\> + +*** + +### useUpdateOtherContext() + +```ts +function useUpdateOtherContext(): (opts: {}) => Promise +``` + +Returns a function to update the "other" context information. +For example, if the user changed workspace, you can set the workspace id here. + +The method returned is a function which returns a promise that +resolves when after the features have been updated as a result +of the update to the "other" context. + +```ts +const updateOtherContext = useUpdateOtherContext(); +updateOtherContext({ workspaceId: newWorkspaceId }) + .then(() => console.log("Flags updated")); +``` + +#### Returns + +`Function` + +##### Parameters + + + + + + + + + + + + + + +
ParameterType
+ +`opts` + + + +\{\} + +
+ +##### Returns + +[`Promise`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Promise)\<`void`\> + +*** + +### useUpdateUser() + +```ts +function useUpdateUser(): (opts: {}) => Promise +``` + +Returns a function to update the current user's information. +For example, if the user changed role or opted into a beta-feature. + +The method returned is a function which returns a promise that +resolves when after the features have been updated as a result +of the user update. + +```ts +const updateUser = useUpdateUser(); +updateUser({ optInHuddles: "true" }).then(() => console.log("Flags updated")); +``` + +#### Returns + +`Function` + +##### Parameters + + + + + + + + + + + + + + +
ParameterType
+ +`opts` + + + +\{\} + +
+ +##### Returns + +[`Promise`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Promise)\<`void`\> diff --git a/previews/sdk-versioning/v2/@reflag/vue-sdk/README.md b/previews/sdk-versioning/v2/@reflag/vue-sdk/README.md new file mode 100644 index 0000000..c0625f9 --- /dev/null +++ b/previews/sdk-versioning/v2/@reflag/vue-sdk/README.md @@ -0,0 +1,146 @@ +--- +layout: + visible: true +title: + visible: true +description: + visible: false +tableOfContents: + visible: true +outline: + visible: true +pagination: + visible: true +--- + +{% hint style="info" %} +SDK v2 candidate documentation, not a published release. Installation commands target the planned major; matching SDK/CLI releases are still required. Generated from `@reflag/vue-sdk@2.0.0-next.0` in [the JavaScript repository](https://github.com/reflagcom/javascript/tree/9f3a0ae027e386747a2d4cf7683a68dcab70b355/packages/vue-sdk). SDK READMEs and code comments there remain authoritative. +{% endhint %} + +# Reflag Vue SDK v2 (beta) + +Reactive direct-value flags for Vue 3, backed by [Browser SDK v2](../browser-sdk/README.md). + +> Breaking prerelease: requires the compatible v2 server protocol. Bootstrap must come from **Node SDK v2**, not v1. Generated declarations are optional; the v2 CLI supports `--format vue` and direct-value schemas. Regenerate legacy wrapper-shaped declarations when migrating. + +## Setup + +```vue + + + +``` + +Provider options forward Browser settings, including cache/storage, fallback values, offline/tracking, feedback, toolbar, credentials and API origins. `logger` takes precedence over `debug`. Live updates default to true; disable with `:enable-live-flag-updates="false"`. For an ordinary client-only Nuxt provider, wrap it in ``. + +## `useFlag(key, defaultValue?)` + +```vue + + + +``` + +Vue unwraps refs in templates; use `.value` in script. This is a **Vue ref**, not a flag wrapper. There are no `.isEnabled`, `.config`, flag `.track()` or flag loading fields. + +```ts +useFlag("release"); // ShallowRef +useFlag("model", "quick"); // ShallowRef +useFlag("limit", 10); // ShallowRef +useFlag("settings", { model: "quick" }); // ShallowRef<{ model: string }> +useFlag("nullable-settings", null); // ShallowRef +``` + +Defaults are positional JSON; literals widen without generated types. Non-null defaults require the same top-level category (Boolean/number/string/array/object), otherwise the default is returned with a payload-safe warning. Null explicitly accepts arbitrary JSON, including authored null; missing returns null. Valid false/zero/empty strings are preserved. Nested payload schemas are not validated by the SDK. Returned values/defaults are independent copies; readonly defaults are accepted. + +The ref updates when evaluated flags change. A composable read registers the requested root through the Browser SDK, even if its result is ignored; refresh-driven rereads are identity-aware rate-limited and delivered in batches. Diagnostics/bootstrap inspection register none. Checks carry genuine selected key/source/snapshot identity, never payloads or invented selections for defaults/overrides. + +## Loading, context and client access + +- `useIsLoading()` returns a Boolean ref for provider loading; the `#loading` slot and `initialLoading` control initial presentation. +- `useUpdateUser()`, `useUpdateCompany()` and `useUpdateOtherContext()` return async shallow-update functions. +- `useClient()` exposes the Browser client, including `setContext(context)`, `refresh()` and `setFlagOverride(key, value)`. Undefined clears an override; null is a JSON override. +- `getFlags()` returns direct values and registers returned-root checks. Use `getFlagResults()` for tooling without checks. +- `useOnEvent(type, handler, client?)` subscribes on mount and unsubscribes on unmount. An external client may be supplied explicitly. + +A context change clears old values while fetching. Late responses for previous contexts or lower snapshot versions cannot replace accepted state. V2 cache/override storage is isolated from v1. Browser fetching uses `/flags/evaluated` and never re-evaluates/caches dependencies locally. + +## Bootstrap + +On the server, use Node SDK v2 and your framework's safe serialization mechanism: + +```ts +const flags = serverClient.getFlagsForBootstrap(context); +``` + +```vue + + + +``` + +Pass the entire envelope (`protocolVersion: 2`, context, evaluated flags and optional snapshot version). Legacy v1 bootstrap is rejected. A snapshot version is required for live updates. Never expose secret keys/server-only attributes in bootstrap data; later refreshes use browser-visible context, so disable live updates when they must not replace server-only evaluations. + +`ReflagClientProvider` accepts an externally initialized Browser v2 client. Its lifecycle belongs to the application. + +## Opt-in, tracking and feedback + +```ts +const { flags, isLoading } = useOptInFlags(); // refs to metadata/list loading +const setOptIn = useSetOptIn(); +await setOptIn("huddles", { optedIn: true }); +await setOptIn("huddles", { optedIn: false, scope: "company" }); + +const track = useTrack(); +await track("Started huddle", { button: "cta" }); +const requestFeedback = useRequestFeedback(); +requestFeedback({ flagKey: "huddles", title: "How did it go?" }); +const sendFeedback = useSendFeedback(); +await sendFeedback({ flagKey: "huddles", score: 5 }); +``` + +Opt-in metadata arrives with ordinary `/flags/evaluated` responses. Bootstrapped providers fetch missing metadata on demand from the same v2 endpoint without replacing server-derived values or provenance; loading settles on success or failure. User/company grants remain independent; successful mutations confirm versioned evaluation and membership in a single refresh. The metadata list retains presentation/membership fields such as `isEnabled`; ordinary reads remain direct values. + +`enableTracking: false` disables checks and ordinary tracking. Offline mode skips HTTP/tracking. See [Browser feedback documentation](../../documents/browser-sdk/FEEDBACK.md) for prompting/UI options. + +## Optional key registration and migration + +```ts +import "@reflag/vue-sdk"; +declare module "@reflag/vue-sdk" { + interface Flags { + huddles: boolean; + "huddle-settings": { maxParticipants: number }; + } +} +``` + +Registration constrains keys and checks non-null defaults against the registered value schema, including required and nested properties. Named-interface and readonly defaults are supported. Explicit `null` remains an arbitrary-JSON request; bare/`undefined` reads remain Boolean refs. Positional defaults still determine widened direct-value types, and generated declarations do not add nested runtime validation. The v2 CLI generates these schemas from current production-environment variants with `--format vue`; regenerate old wrapper declarations when migrating. + +Replace `const { isEnabled } = useFlag(key)` with `const enabled = useFlag(key)` (then `enabled.value` in script). Replace remote-config wrappers with separately authored multivariate flags and positional defaults, and move usage/feedback into their separate composables. Bootstrap both ends with v2. This branch does not publish SDKs or deploy the required server changes. diff --git a/previews/sdk-versioning/v2/@reflag/vue-sdk/globals.md b/previews/sdk-versioning/v2/@reflag/vue-sdk/globals.md new file mode 100644 index 0000000..52e136b --- /dev/null +++ b/previews/sdk-versioning/v2/@reflag/vue-sdk/globals.md @@ -0,0 +1,1789 @@ +--- +layout: + visible: true +title: + visible: true +description: + visible: false +tableOfContents: + visible: true +outline: + visible: true +pagination: + visible: true +--- + +{% hint style="info" %} +SDK v2 candidate documentation, not a published release. Installation commands target the planned major; matching SDK/CLI releases are still required. Generated from `@reflag/vue-sdk@2.0.0-next.0` in [the JavaScript repository](https://github.com/reflagcom/javascript/tree/9f3a0ae027e386747a2d4cf7683a68dcab70b355/packages/vue-sdk). SDK READMEs and code comments there remain authoritative. +{% endhint %} + +# @reflag/vue-sdk + +## Interfaces + +### CheckEvent + +A root read records selection identity, never its JSON payload. + +#### Properties + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
PropertyType
+ + `action` + + + +`"check"` + +
+ + `evalContext?` + + + +[`ReflagContext`](../browser-sdk/globals.md#reflagcontext) + +
+ + `evalErrors?` + + + +[`EvaluationError`](../browser-sdk/globals.md#evaluationerror)[] + +
+ + `evalMissingFields?` + + + +`string`[] + +
+ + `evalResult?` + + + +`string` + +
+ + `evalRuleResults?` + + + +`boolean`[] + +
+ + `flagStateVersion?` + + + +`number` + +
+ + `key` + + + +`string` + +
+ + `sourceVersionId?` + + + +`string` + +
+ +*** + +### CompanyContext + +Context is a set of key-value pairs. +This is used to determine if feature targeting matches and to track events. +Id should always be present so that it can be referenced to an existing company. + +#### Indexable + +```ts +[key: string]: ContextValue +``` + +#### Properties + + + + + + + + + + + + + + + + + + + + + +
PropertyTypeDescription
+ + `id` + + + +`undefined` \| `string` \| `number` + + + +Company id + +
+ + `name?` + + + +`string` + + + +Company name + +
+ +*** + +### Flags + +*** + +### UserContext + +Context is a set of key-value pairs. +This is used to determine if feature targeting matches and to track events. +Id should always be present so that it can be referenced to an existing user. + +#### Indexable + +```ts +[key: string]: ContextValue +``` + +#### Properties + + + + + + + + + + + + + + + + + + + + + + + + + + +
PropertyTypeDescription
+ + `email?` + + + +`string` + + + +User email + +
+ + `id` + + + +`undefined` \| `string` \| `number` + + + +User id + +
+ + `name?` + + + +`string` + + + +User name + +
+ +## Type Aliases + +### BootstrappedFlags + +```ts +type BootstrappedFlags = BootstrappedState; +``` + +*** + +### FlagDefaultValue + +```ts +type FlagDefaultValue = + | null + | boolean + | number + | string + | readonly FlagDefaultValue[] +| {}; +``` + +*** + +### FlagKey + +```ts +type FlagKey = keyof Flags extends never ? string : Extract; +``` + +*** + +### FlagValue + +```ts +type FlagValue = + | null + | boolean + | number + | string + | FlagValue[] +| {}; +``` + +*** + +### FlagValueFromDefault\ + +```ts +type FlagValueFromDefault = T extends null ? FlagValue : T extends boolean ? boolean : T extends number ? number : T extends string ? string : T extends readonly infer Element[] ? [Element] extends [never] ? FlagValue[] : FlagValueFromDefault[] : T extends object ? { -readonly [Key in keyof T]: FlagValueFromDefault } : never; +``` + +Widen literals and return mutable copies. Null requests arbitrary JSON. + +#### Type Parameters + + + + + + + + + + + + +
Type Parameter
+ +`T` + +
+ +*** + +### OptInFlag + +```ts +type OptInFlag = Omit & { + key: FlagKey; +}; +``` + +#### Type declaration + + + + + + + + + + + + + + +
NameType
+ +`key` + + + +[`FlagKey`](globals.md#flagkey) + +
+ +*** + +### ReflagBaseProps + +```ts +type ReflagBaseProps = { + debug: boolean; + initialLoading: boolean; + logger: Logger; +}; +``` + +**`Internal`** + +Base props for the ReflagProvider and ReflagBootstrappedProvider. + +#### Type declaration + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
+ + `debug`? + + + +`boolean` + + + +Set to `true` to enable debug logging to the console. + +
+ + `initialLoading`? + + + +`boolean` + + + +Set to `true` to show the loading component while the client is initializing. + +
+ + `logger`? + + + +[`Logger`](../browser-sdk/globals.md#logger) + + + +A custom logger to use for SDK logs. +Use this for advanced control or filtering of SDK logs. +If both `logger` and `debug` are provided, `logger` takes precedence. + +
+ +*** + +### ReflagBootstrappedProps + +```ts +type ReflagBootstrappedProps = ReflagInitOptionsBase & ReflagBaseProps & { + flags: BootstrappedFlags; +}; +``` + +Props for the ReflagBootstrappedProvider. + +#### Type declaration + + + + + + + + + + + + + + + + +
NameTypeDescription
+ +`flags` + + + +[`BootstrappedFlags`](globals.md#bootstrappedflags) + + + +Pre-fetched flags used for the initial render. The browser client fetches opt-in +metadata on demand when opt-in flags are requested. + +
+ +*** + +### ReflagClientProviderProps + +```ts +type ReflagClientProviderProps = Omit & { + client: ReflagClient; +}; +``` + +Props for the ReflagClientProvider. + +#### Type declaration + + + + + + + + + + + + + + + + +
NameTypeDescription
+ +`client` + + + +[`ReflagClient`](../browser-sdk/globals.md#reflagclient) + + + +A pre-initialized ReflagClient to use. + +
+ +*** + +### ReflagInitOptionsBase + +```ts +type ReflagInitOptionsBase = Omit; +``` + +**`Internal`** + +Base init options for the ReflagProvider and ReflagBootstrappedProvider. + +*** + +### ReflagProps + +```ts +type ReflagProps = ReflagInitOptionsBase & ReflagBaseProps & { + company: CompanyContext; + context: ReflagContext; + otherContext: Record; + user: UserContext; +}; +``` + +Props for the ReflagProvider. + +#### Type declaration + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
+ +`company`? + + + +[`CompanyContext`](globals.md#companycontext) + + + +Company related context. If you provide `id` Reflag will enrich the evaluation context with +company attributes on Reflag servers. + +**Deprecated** + +Use `context` instead, this property will be removed in the next major version + +
+ +`context`? + + + +[`ReflagContext`](../browser-sdk/globals.md#reflagcontext) + + + +The context to use for the ReflagClient containing user, company, and other context. + +
+ +`otherContext`? + + + +[`Record`](https://www.typescriptlang.org/docs/handbook/utility-types.html#recordkeys-type)\<`string`, `string` \| `number` \| `undefined`\> + + + +Context which is not related to a user or a company. + +**Deprecated** + +Use `context` instead, this property will be removed in the next major version + +
+ +`user`? + + + +[`UserContext`](globals.md#usercontext) + + + +User related context. If you provide `id` Reflag will enrich the evaluation context with +user attributes on Reflag servers. + +**Deprecated** + +Use `context` instead, this property will be removed in the next major version + +
+ +*** + +### RequestFlagFeedbackOptions + +```ts +type RequestFlagFeedbackOptions = Omit; +``` + +*** + +### SetOptInOptions + +```ts +type SetOptInOptions = { + optedIn: boolean; + scope: "user" | "company"; +}; +``` + +Represents a flag. + +#### Type declaration + + + + + + + + + + + + + + + + + + + + + +
NameTypeDescription
+ + `optedIn` + + + +`boolean` + + + +Whether the scoped subject has opted in. + +
+ + `scope`? + + + +`"user"` \| `"company"` + + + +Whether to update the current user or current company. Defaults to `user`. + +
+ +*** + +### TrackEvent + +```ts +type TrackEvent = { + attributes: | Record + | null; + company: CompanyContext; + eventName: string; + user: UserContext; +}; +``` + +#### Type declaration + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameType
+ + `attributes`? + + + + \| [`Record`](https://www.typescriptlang.org/docs/handbook/utility-types.html#recordkeys-type)\<`string`, `any`\> + \| `null` + +
+ + `company`? + + + +[`CompanyContext`](globals.md#companycontext) + +
+ + `eventName` + + + +`string` + +
+ + `user` + + + +[`UserContext`](globals.md#usercontext) + +
+ +*** + +### TypedFlags + +```ts +type TypedFlags = keyof Flags extends never ? Record : { [Key in keyof Flags]: Flags[Key] | undefined }; +``` + +*** + +### UseOptInFlagsResult + +```ts +type UseOptInFlagsResult = { + flags: ComputedRef; + isLoading: ComputedRef; +}; +``` + +#### Type declaration + + + + + + + + + + + + + + + + + + +
NameType
+ + `flags` + + + +`ComputedRef`\<[`OptInFlag`](globals.md#optinflag)[]\> + +
+ + `isLoading` + + + +`ComputedRef`\<`boolean`\> + +
+ +## Variables + +### default + +```ts +default: { + install: void; +}; +``` + +#### Type declaration + + + + + + + + + + + + + + +
NameType
+ + `install()` + + + +`void` + +
+ +*** + +### ReflagBootstrappedProvider + +```ts +const ReflagBootstrappedProvider: DefineComponent, Record, unknown>; +``` + +## Functions + +### useClient() + +```ts +function useClient(): ReflagClient +``` + +Vue composable for getting the Reflag client. + +This composable returns the Reflag client. You can use this to get the Reflag +client at any point in your application. + +#### Returns + +[`ReflagClient`](../browser-sdk/globals.md#reflagclient) + +The Reflag client. + +#### Example + +```ts +import { useClient } from '@reflag/vue-sdk'; + +const client = useClient(); + +console.log(client.getContext()); +``` + +*** + +### useFlag() + +#### Call Signature + +```ts +function useFlag(key: string, defaultValue?: undefined): ShallowRef +``` + +Reactive direct values: Boolean/false by default, or infer a positional JSON default. + +##### Parameters + + + + + + + + + + + + + + + + + + +
ParameterType
+ +`key` + + + +`string` + +
+ +`defaultValue`? + + + +`undefined` + +
+ +##### Returns + +`ShallowRef`\<`boolean`\> + +#### Call Signature + +```ts +function useFlag(key: Key, defaultValue: Default): ShallowRef> +``` + +Reactive direct values: Boolean/false by default, or infer a positional JSON default. + +##### Type Parameters + + + + + + + + + + + + + + + +
Type Parameter
+ +`Key` *extends* `string` + +
+ +`Default` *extends* `object` \| [`FlagDefaultValue`](globals.md#flagdefaultvalue) + +
+ +##### Parameters + + + + + + + + + + + + + + + + + + +
ParameterType
+ +`key` + + + +`Key` + +
+ +`defaultValue` + + + +`Default` + +
+ +##### Returns + +`ShallowRef`\<[`FlagValueFromDefault`](globals.md#flagvaluefromdefaultt)\<`Default`\>\> + +#### Call Signature + +```ts +function useFlag(key: string, defaultValue: Default): ShallowRef> +``` + +Reactive direct values: Boolean/false by default, or infer a positional JSON default. + +##### Type Parameters + + + + + + + + + + + + +
Type Parameter
+ +`Default` *extends* `object` \| [`FlagDefaultValue`](globals.md#flagdefaultvalue) + +
+ +##### Parameters + + + + + + + + + + + + + + + + + + +
ParameterType
+ +`key` + + + +`string` + +
+ +`defaultValue` + + + +`Default` + +
+ +##### Returns + +`ShallowRef`\<[`FlagValueFromDefault`](globals.md#flagvaluefromdefaultt)\<`Default`\>\> + +*** + +### useIsLoading() + +```ts +function useIsLoading(): Ref +``` + +Vue composable for checking if the Reflag client is loading. + +This composable returns a boolean value that indicates whether the Reflag client is loading. +You can use this to check if the Reflag client is loading at any point in your application. +Initially, the value will be true until the client is initialized. + +#### Returns + +`Ref`\<`boolean`, `boolean`\> + +#### Example + +```ts +import { useIsLoading } from '@reflag/vue-sdk'; + +const isLoading = useIsLoading(); + +console.log(isLoading); +``` + +*** + +### useOnEvent() + +```ts +function useOnEvent( + event: THookType, + handler: (arg0: HookArgs[THookType]) => void, + client?: ReflagClient): void +``` + +Vue composable for listening to Reflag client events. + +#### Type Parameters + + + + + + + + + + + + +
Type Parameter
+ +`THookType` *extends* keyof [`HookArgs`](../browser-sdk/globals.md#hookargs) + +
+ +#### Parameters + + + + + + + + + + + + + + + + + + + + + + + + + + +
ParameterTypeDescription
+ +`event` + + + +`THookType` + + + +The event to listen to. + +
+ +`handler` + + + +(`arg0`: [`HookArgs`](../browser-sdk/globals.md#hookargs)\[`THookType`\]) => `void` + + + +The function to call when the event is triggered. + +
+ +`client`? + + + +[`ReflagClient`](../browser-sdk/globals.md#reflagclient) + + + +The Reflag client to listen to. If not provided, the client will be retrieved from the context. + +
+ +#### Returns + +`void` + +#### Example + +```ts +import { useOnEvent } from '@reflag/vue-sdk'; + +useOnEvent("flagsUpdated", () => { + console.log("flags updated"); +}); +``` + +*** + +### useOptInFlags() + +```ts +function useOptInFlags(): UseOptInFlagsResult +``` + +Vue composable for getting opt-in-enabled flags and their loading state for +the current context. + +The loading state is only used with `ReflagBootstrappedProvider` while +opt-in metadata is fetched on demand. Regular providers load opt-in metadata +with the initial flags. + +#### Returns + +[`UseOptInFlagsResult`](globals.md#useoptinflagsresult) + +*** + +### useRequestFeedback() + +```ts +function useRequestFeedback(): (options: RequestFeedbackData) => void +``` + +Vue composable for requesting user feedback. + +This composable returns a function that can be used to trigger the feedback +collection flow with the Reflag SDK. You can use this to prompt users for +feedback at any point in your application. + +#### Returns + +`Function` + +A function that requests feedback from the user. The function accepts: + - `options`: An object containing feedback request options. + +##### Parameters + + + + + + + + + + + + + + +
ParameterType
+ +`options` + + + +[`RequestFeedbackData`](../browser-sdk/globals.md#requestfeedbackdata) + +
+ +##### Returns + +`void` + +#### Example + +```ts +import { useRequestFeedback } from '@reflag/vue-sdk'; + +const requestFeedback = useRequestFeedback(); + +// Request feedback from the user +requestFeedback({ + prompt: "How was your experience?", + metadata: { page: "dashboard" } +}); +``` + +*** + +### useSendFeedback() + +```ts +function useSendFeedback(): (opts: UnassignedFeedback) => Promise< + | undefined +| Response> +``` + +Vue composable for sending feedback. + +This composable returns a function that can be used to send feedback to the +Reflag SDK. You can use this to send feedback from your application. + +#### Returns + +`Function` + +A function that sends feedback to the Reflag SDK. The function accepts: + - `options`: An object containing feedback options. + +##### Parameters + + + + + + + + + + + + + + +
ParameterType
+ +`opts` + + + +[`UnassignedFeedback`](../browser-sdk/globals.md#unassignedfeedback) + +
+ +##### Returns + +[`Promise`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Promise)\< + \| `undefined` + \| [`Response`](https://developer.mozilla.org/docs/Web/API/Response)\> + +#### Example + +```ts +import { useSendFeedback } from '@reflag/vue-sdk'; + +const sendFeedback = useSendFeedback(); + +// Send feedback from the user +sendFeedback({ + feedback: "I love this flag!", + metadata: { page: "dashboard" } +}); +``` + +*** + +### useSetOptIn() + +```ts +function useSetOptIn(): (key: string, options: SetOptInOptions) => Promise< + | undefined +| Response> +``` + +Vue composable for setting whether the current user or company has opted into a flag. + +#### Returns + +`Function` + +##### Parameters + + + + + + + + + + + + + + + + + + +
ParameterType
+ +`key` + + + +`string` + +
+ +`options` + + + +[`SetOptInOptions`](globals.md#setoptinoptions) + +
+ +##### Returns + +[`Promise`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Promise)\< + \| `undefined` + \| [`Response`](https://developer.mozilla.org/docs/Web/API/Response)\> + +*** + +### useTrack() + +```ts +function useTrack(): (eventName: string, attributes?: + | null + | Record) => Promise< + | undefined +| Response> +``` + +Vue composable for tracking custom events. + +This composable returns a function that can be used to track custom events +with the Reflag SDK. + +#### Returns + +`Function` + +A function that tracks an event. The function accepts: + - `eventName`: The name of the event to track. + - `attributes`: (Optional) Additional attributes to associate with the event. + +##### Parameters + + + + + + + + + + + + + + + + + + +
ParameterType
+ +`eventName` + + + +`string` + +
+ +`attributes`? + + + + \| `null` \| [`Record`](https://www.typescriptlang.org/docs/handbook/utility-types.html#recordkeys-type)\<`string`, `any`\> + +
+ +##### Returns + +[`Promise`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Promise)\< + \| `undefined` + \| [`Response`](https://developer.mozilla.org/docs/Web/API/Response)\> + +#### Example + +```ts +import { useTrack } from '@reflag/vue-sdk'; + +const track = useTrack(); + +// Track a custom event +track('button_clicked', { buttonName: 'Start Huddles' }); +``` + +*** + +### useUpdateCompany() + +```ts +function useUpdateCompany(): (opts: {}) => Promise +``` + +Vue composable for updating the company context. + +This composable returns a function that can be used to update the company +context with the Reflag SDK. You can use this to update the company context +at any point in your application. + +#### Returns + +`Function` + +A function that updates the company context. The function accepts: + - `opts`: An object containing the company context to update. + +##### Parameters + + + + + + + + + + + + + + +
ParameterType
+ +`opts` + + + +\{\} + +
+ +##### Returns + +[`Promise`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Promise)\<`void`\> + +#### Example + +```ts +import { useUpdateCompany } from '@reflag/vue-sdk'; + +const updateCompany = useUpdateCompany(); + +// Update the company context +updateCompany({ id: "123", name: "Acme Inc." }); +``` + +*** + +### useUpdateOtherContext() + +```ts +function useUpdateOtherContext(): (opts: {}) => Promise +``` + +Vue composable for updating the other context. + +This composable returns a function that can be used to update the other +context with the Reflag SDK. You can use this to update the other context +at any point in your application. + +#### Returns + +`Function` + +A function that updates the other context. The function accepts: + - `opts`: An object containing the other context to update. + +##### Parameters + + + + + + + + + + + + + + +
ParameterType
+ +`opts` + + + +\{\} + +
+ +##### Returns + +[`Promise`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Promise)\<`void`\> + +#### Example + +```ts +import { useUpdateOtherContext } from '@reflag/vue-sdk'; + +const updateOtherContext = useUpdateOtherContext(); + +// Update the other context +updateOtherContext({ id: "123", name: "Acme Inc." }); +``` + +*** + +### useUpdateUser() + +```ts +function useUpdateUser(): (opts: {}) => Promise +``` + +Vue composable for updating the user context. + +This composable returns a function that can be used to update the user context +with the Reflag SDK. You can use this to update the user context at any point +in your application. + +#### Returns + +`Function` + +A function that updates the user context. The function accepts: + - `opts`: An object containing the user context to update. + +##### Parameters + + + + + + + + + + + + + + +
ParameterType
+ +`opts` + + + +\{\} + +
+ +##### Returns + +[`Promise`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Promise)\<`void`\> + +#### Example + +```ts +import { useUpdateUser } from '@reflag/vue-sdk'; + +const updateUser = useUpdateUser(); + +// Update the user context +updateUser({ id: "123", name: "John Doe" }); +``` + +## References + +### ReflagClientProvider + +Renames and re-exports [ReflagBootstrappedProvider](globals.md#reflagbootstrappedprovider) + +### ReflagProvider + +Renames and re-exports [ReflagBootstrappedProvider](globals.md#reflagbootstrappedprovider) diff --git a/previews/sdk-versioning/v2/README.md b/previews/sdk-versioning/v2/README.md new file mode 100644 index 0000000..9bf6751 --- /dev/null +++ b/previews/sdk-versioning/v2/README.md @@ -0,0 +1,22 @@ +# JavaScript SDKs — v2 + +{% hint style="warning" %} +**Candidate documentation, not a release announcement.** These pages are generated from SDK candidate commit [`9f3a0ae`](https://github.com/reflagcom/javascript/commit/9f3a0ae027e386747a2d4cf7683a68dcab70b355), version **2.0.0-next.0**. Matching SDK/CLI releases and server readiness are still required. A staging server deployment alone does not release these packages. +{% endhint %} + +Use the **v1 / v2 variant picker** in the JavaScript SDKs section to change versions. Both variants use the same package and reference paths so equivalent pages can be matched. + +## SDK documentation + +- [React SDK](./@reflag/react-sdk/README.md) · [API reference](./@reflag/react-sdk/globals.md) +- [Vue SDK](./@reflag/vue-sdk/README.md) · [API reference](./@reflag/vue-sdk/globals.md) +- [Browser SDK](./@reflag/browser-sdk/README.md) · [API reference](./@reflag/browser-sdk/globals.md) · [Feedback](documents/browser-sdk/FEEDBACK.md) +- [Node.js SDK](./@reflag/node-sdk/README.md) · [API reference](./@reflag/node-sdk/globals.md) + +## Compatibility + +V2 reads return direct Boolean/JSON values, with positional defaults for JSON reads. Tracking and feedback are separate APIs. For server bootstrap, use matching Node and Browser/framework v2 packages. Regenerate direct-value declarations with CLI 2 when that release is available. + +React Native, OpenFeature and Management retain their independently supported contracts; this selector does not claim v2 support for them. + +SDK READMEs and API comments remain authored in the [JavaScript repository](https://github.com/reflagcom/javascript). The pages here are generated, version-labelled copies—not a second manually maintained SDK reference. diff --git a/previews/sdk-versioning/v2/SUMMARY.md b/previews/sdk-versioning/v2/SUMMARY.md new file mode 100644 index 0000000..8576811 --- /dev/null +++ b/previews/sdk-versioning/v2/SUMMARY.md @@ -0,0 +1,12 @@ +# Table of contents + +- [JavaScript SDKs — v2](README.md) +- [React SDK](@reflag/react-sdk/README.md) + - [Reference](@reflag/react-sdk/globals.md) +- [Vue SDK](@reflag/vue-sdk/README.md) + - [Reference](@reflag/vue-sdk/globals.md) +- [Browser SDK](@reflag/browser-sdk/README.md) + - [Reference](@reflag/browser-sdk/globals.md) + - [Feedback](documents/browser-sdk/FEEDBACK.md) +- [Node.js SDK](@reflag/node-sdk/README.md) + - [Reference](@reflag/node-sdk/globals.md) diff --git a/previews/sdk-versioning/v2/documents/browser-sdk/FEEDBACK.md b/previews/sdk-versioning/v2/documents/browser-sdk/FEEDBACK.md new file mode 100644 index 0000000..25ea4a0 --- /dev/null +++ b/previews/sdk-versioning/v2/documents/browser-sdk/FEEDBACK.md @@ -0,0 +1,473 @@ +--- +layout: + visible: true +title: + visible: true +description: + visible: false +tableOfContents: + visible: true +outline: + visible: true +pagination: + visible: true +--- + +# Reflag Feedback UI + +The Reflag Browser SDK includes a UI you can use to collect feedback from user +about particular flags. + +![image](https://github.com/reflagcom/javascript/assets/34348/c387bac1-f2e2-4efd-9dda-5030d76f9532) + +## Global feedback configuration + +The Reflag Browser SDK feedback UI is configured with reasonable defaults, +positioning itself as a [dialog](#dialog) in the lower right-hand corner of +the viewport, displayed in English, and with a [light-mode theme](#custom-styling). + +These settings can be overwritten when initializing the Reflag Browser SDK: + +```typescript +const reflag = new ReflagClient({ + publishableKey: "reflag-publishable-key", + user: { id: "42" }, + feedback: { + ui: { + position: POSITION_CONFIG, // See positioning section + translations: TRANSLATION_KEYS, // See internationalization section + + // Enable automated feedback surveys. Default: `true` + enableAutoFeedback: boolean, + + /** + * Do your own feedback prompt handling or override + * default settings at runtime. + */ + autoFeedbackHandler: (promptMessage, handlers) => { + // See Automated Feedback Surveys section + }, + }, + }, +}); +``` + +See also: + +- [Positioning and behavior](#positioning-and-behavior) for the position option, +- [Static language configuration](#static-language-configuration) if you want to translate the feedback UI, +- [Automated feedback surveys](#automated-feedback-surveys) to override default configuration. + +## Automated feedback surveys + +Automated feedback surveys are enabled by default. + +When automated feedback surveys are enabled, the Reflag Browser SDK +will open and maintain a connection to the Reflag service. When a user +triggers an event tracked by a flag and is eligible to be prompted +for feedback, the Reflag service will send a request to the SDK instance. +By default, this request will open up the Reflag feedback UI in the user's +browser, but you can intercept the request and override this behavior. + +The live connection for automated feedback is established when the +`ReflagClient` is initialized. + +### Disabling automated feedback surveys + +You can disable automated collection in the `ReflagClient` constructor: + +```typescript +const reflag = new ReflagClient({ + publishableKey: "reflag-publishable-key", + user: { id: "42" }, + feedback: { + enableAutoFeedback: false, + }, +}); +``` + +### Overriding prompt event defaults + +If you are not satisfied with the default UI behavior when an automated prompt +event arrives, you can can [override the global defaults](#global-feedback-configuration) +or intercept and override settings at runtime like this: + +```javascript +const reflag = new ReflagClient({ + publishableKey: "reflag-publishable-key", + user: { id: "42" }, + feedback: { + autoFeedbackHandler: (promptMessage, handlers) => { + // Pass your overrides here. Everything is optional + handlers.openFeedbackForm({ + title: promptMessage.question, + + position: POSITION_CONFIG, // See positioning section + translations: TRANSLATION_KEYS, // See internationalization section + + // Trigger side effects with the collected data, + // for example posting it back into your own CRM + onAfterSubmit: (feedback) => { + storeFeedbackInCRM({ + score: feedback.score, + comment: feedback.comment, + }); + }, + }); + }, + }, +}); +``` + +See also: + +- [Positioning and behavior](#positioning-and-behavior) for the position option. +- [Runtime language configuration](#runtime-language-configuration) if you want + to translate the feedback UI. +- [Use your own UI to collect feedback](#using-your-own-ui-to-collect-feedback) if + the feedback UI doesn't match your design. + +## Manual feedback collection + +To open up the feedback collection UI, call `reflagClient.requestFeedback(options)` +with the appropriate options. This approach is particularly beneficial if you wish +to retain manual control over feedback collection from your users while leveraging +the convenience of the Reflag feedback UI to reduce the amount of code you need +to maintain. + +Examples of this could be if you want the click of a `give us feedback`-button +or the end of a specific user flow, to trigger a pop-up displaying the feedback +user interface. + +### reflagClient.requestFeedback() options + +Minimal usage with defaults: + +```javascript +reflagClient.requestFeedback({ + flagKey: "reflag-flag-key", + title: "How satisfied are you with file uploads?", +}); +``` + +All options: + +```javascript +reflagClient.requestFeedback({ + flagKey: "reflag-flag-key", // [Required] + userId: "your-user-id", // [Optional] if user persistence is + // enabled (default in browsers), + companyId: "users-company-or-account-id", // [Optional] + title: "How satisfied are you with file uploads?" // [Optional] + + position: POSITION_CONFIG, // [Optional] see the positioning section + translations: TRANSLATION_KEYS // [Optional] see the internationalization section + + // [Optional] trigger side effects with the collected data, + // for example sending the feedback to your own CRM + onAfterSubmit: (feedback) => { + storeFeedbackInCRM({ + score: feedback.score, + comment: feedback.comment + }) + } +}) +``` + +See also: + +- [Positioning and behavior](#positioning-and-behavior) for the position option. +- [Runtime language configuration](#runtime-language-configuration) if + you want to translate the feedback UI. + +## Positioning and behavior + +The feedback UI can be configured to be placed and behave in 3 different ways: + +### Positioning configuration + +#### Modal + +A modal overlay with a backdrop that blocks interaction with the underlying +page. It can be dismissed with the keyboard shortcut `` or the dedicated +close button in the top right corner. It is always centered on the page, capturing +focus, and making it the primary interface the user needs to interact with. + +![image](https://github.com/reflagcom/javascript/assets/331790/6c6efbd3-cf7d-4d5b-b126-7ac978b2e512) + +Using a modal is the strongest possible push for feedback. You are interrupting the +user's normal flow, which can cause annoyance. A good use-case for the modal is +when the user finishes a linear flow that they don't perform often, for example +setting up a new account. + +```javascript +position: { + type: "MODAL"; +} +``` + +#### Dialog + +A dialog that appears in a specified corner of the viewport, without limiting the +user's interaction with the rest of the page. It can be dismissed with the dedicated +close button, but will automatically disappear after a short time period if the user +does not interact with it. + +![image](https://github.com/reflagcom/javascript/assets/331790/30413513-fd5f-4a2c-852a-9b074fa4666c) + +Using a dialog is a soft push for feedback. It lets the user continue their work +with a minimal amount of intrusion. The user can opt-in to respond but is not +required to. A good use case for this behavior is when a user uses a flag where +the expected outcome is predictable, possibly because they have used it multiple +times before. For example: Uploading a file, switching to a different view of a +visualization, visiting a specific page, or manipulating some data. + +The default feedback UI behavior is a dialog placed in the bottom right corner of +the viewport. + +```typescript +position: { + type: "DIALOG"; + placement: "top-left" | "top-right" | "bottom-left" | "bottom-right"; + offset?: { + x?: string | number; // e.g. "-5rem", "10px" or 10 (pixels) + y?: string | number; + } +} +``` + +#### Popover + +A popover that is anchored relative to a DOM-element (typically a button). It can +be dismissed by clicking outside the popover or by pressing the dedicated close button. + +![image](https://github.com/reflagcom/javascript/assets/331790/4c5c5597-9ed3-4d4d-90c0-950926d0d967) + +You can use the popover mode to implement your own button to collect feedback manually. + +```typescript +type Position = { + type: "POPOVER"; + anchor: DOMElement; +}; +``` + +Popover feedback button example: + +```html + + +``` + +## Internationalization (i18n) + +By default, the feedback UI is written in English. However, you can supply your own +translations by passing an object in the options to either or both of the +`new ReflagClient(options)` or `reflagClient.requestFeedback(options)` calls. +These translations will replace the English ones used by the feedback interface. +See examples below. + +![image](https://github.com/reflagcom/javascript/assets/331790/68805b38-e9f6-4de5-9f55-188216983e3c) + +See [default English localization keys](https://github.com/reflagcom/javascript/tree/main/packages/browser-sdk/src/feedback/ui/config/defaultTranslations.tsx) +for a reference of what translation keys can be supplied. + +### Static language configuration + +If you know the language at page load, you can configure your translation keys while +initializing the Reflag Browser SDK: + +```typescript +new ReflagClient({ + publishableKey: "my-publishable-key", + feedback: { + ui: { + translations: { + DefaultQuestionLabel: + "Dans quelle mesure êtes-vous satisfait de cette fonctionnalité ?", + QuestionPlaceholder: + "Comment pouvons-nous améliorer cette fonctionnalité ?", + ScoreStatusDescription: "Choisissez une note et laissez un commentaire", + ScoreStatusLoading: "Chargement...", + ScoreStatusReceived: "La note a été reçue !", + ScoreVeryDissatisfiedLabel: "Très insatisfait", + ScoreDissatisfiedLabel: "Insatisfait", + ScoreNeutralLabel: "Neutre", + ScoreSatisfiedLabel: "Satisfait", + ScoreVerySatisfiedLabel: "Très satisfait", + SuccessMessage: "Merci d'avoir envoyé vos commentaires!", + SendButton: "Envoyer", + }, + }, + }, +}); +``` + +### Runtime language configuration + +If you only know the user's language after the page has loaded, you can provide +translations to either the `reflagClient.requestFeedback(options)` call or +the `autoFeedbackHandler` option before the feedback interface opens. +See examples below. + +```typescript +reflagClient.requestFeedback({ + ... // Other options + translations: { + // your translation keys + } +}) +``` + +### Translations + +When you are collecting feedback through the Reflag automation, you can intercept +the default prompt handling and override the defaults. + +If you set the prompt question in the Reflag app to be one of your own translation +keys, you can even get a translated version of the question you want to ask your +customer in the feedback UI. + +```javascript +new ReflagClient({ + publishableKey: "reflag-publishable-key", + feedback: { + autoFeedbackHandler: (message, handlers) => { + const translatedQuestion = + i18nLookup[message.question] ?? message.question; + handlers.openFeedbackForm({ + title: translatedQuestion, + translations: { + // your static translation keys + }, + }); + }, + }, +}); +``` + +## Custom styling + +You can adapt parts of the look of the Reflag feedback UI by applying CSS custom +properties to your page in your CSS `:root`-scope. + +For example, a dark mode theme might look like this: + +![image](https://github.com/reflagcom/javascript/assets/34348/5d579b7b-a830-4530-8b40-864488a8597e) + +```css +:root { + --reflag-feedback-dialog-background-color: #1e1f24; + --reflag-feedback-dialog-color: rgba(255, 255, 255, 0.92); + --reflag-feedback-dialog-secondary-color: rgba(255, 255, 255, 0.3); + --reflag-feedback-dialog-border: rgba(255, 255, 255, 0.16); + --reflag-feedback-dialog-primary-button-background-color: #655bfa; + --reflag-feedback-dialog-primary-button-color: white; + --reflag-feedback-dialog-input-border-color: rgba(255, 255, 255, 0.16); + --reflag-feedback-dialog-input-focus-border-color: rgba(255, 255, 255, 0.3); + --reflag-feedback-dialog-error-color: #f56565; + + --reflag-feedback-dialog-rating-1-color: #ed8936; + --reflag-feedback-dialog-rating-1-background-color: #7b341e; + --reflag-feedback-dialog-rating-2-color: #dd6b20; + --reflag-feedback-dialog-rating-2-background-color: #652b19; + --reflag-feedback-dialog-rating-3-color: #787c91; + --reflag-feedback-dialog-rating-3-background-color: #3e404c; + --reflag-feedback-dialog-rating-4-color: #38a169; + --reflag-feedback-dialog-rating-4-background-color: #1c4532; + --reflag-feedback-dialog-rating-5-color: #48bb78; + --reflag-feedback-dialog-rating-5-background-color: #22543d; + + --reflag-feedback-dialog-submitted-check-background-color: #38a169; + --reflag-feedback-dialog-submitted-check-color: #ffffff; +} +``` + +Other examples of custom styling can be found in our [development example style-sheet](https://github.com/reflagcom/javascript/tree/main/packages/browser-sdk/src/feedback/ui/index.css). + +## Using your own UI to collect feedback + +You may have very strict design guidelines for your app and maybe the Reflag feedback +UI doesn't quite work for you. In this case, you can implement your own feedback +collection mechanism, which follows your own design guidelines. This is the data +type you need to collect: + +```typescript +type DataToCollect = { + // Customer satisfaction score + score?: 1 | 2 | 3 | 4 | 5; + + // The comment. + comment?: string; +}; +``` + +Either `score` or `comment` must be defined in order to pass validation in the +Reflag API. + +### Manual feedback collection with custom UI + +Examples of a HTML-form that collects the relevant data can be found +in [feedback.html](https://github.com/reflagcom/javascript/tree/main/packages/browser-sdk/example/feedback/feedback.html) and [feedback.jsx](https://github.com/reflagcom/javascript/tree/main/packages/browser-sdk/example/feedback/Feedback.jsx). + +Once you have collected the feedback data, pass it along to `reflagClient.feedback()`: + +```javascript +reflagClient.feedback({ + flagKey: "reflag-flag-key", + userId: "your-user-id", + score: 5, + comment: "Best thing I've ever tried!", +}); +``` + +### Intercepting automated feedback survey events + +When using automated feedback surveys, the Reflag service will, when specified, +send a feedback prompt message to your user's instance of the Reflag Browser SDK. +This will result in the feedback UI being opened. + +You can intercept this behavior and open your own custom feedback collection form: + +```typescript +new ReflagClient({ + publishableKey: "reflag-publishable-key", + feedback: { + autoFeedbackHandler: async (promptMessage, handlers) => { + // This opens your custom UI + customFeedbackCollection({ + // The question configured in the Reflag UI for the flag + question: promptMessage.question, + // When the user successfully submits feedback data. + // Use this instead of `reflagClient.feedback()`, otherwise + // the feedback prompt handler will keep being called + // with the same prompt message + onFeedbackSubmitted: (feedback) => { + handlers.reply(feedback); + }, + // When the user closes the custom feedback form + // without leaving any response. + // It is important to feed this back, otherwise + // the feedback prompt handler will keep being called + // with the same prompt message + onFeedbackDismissed: () => { + handlers.reply(null); + }, + }); + }, + }, +}); +``` diff --git a/previews/sdk-versioning/v2/generated-manifest.json b/previews/sdk-versioning/v2/generated-manifest.json new file mode 100644 index 0000000..3727645 --- /dev/null +++ b/previews/sdk-versioning/v2/generated-manifest.json @@ -0,0 +1,30 @@ +{ + "status": "candidate-source-build-not-a-release", + "sourceRepository": "https://github.com/reflagcom/javascript", + "sourceCommit": "9f3a0ae027e386747a2d4cf7683a68dcab70b355", + "sdkMajor": 2, + "packages": { + "@reflag/node-sdk": "2.0.0-next.0", + "@reflag/browser-sdk": "2.0.0-next.0", + "@reflag/react-sdk": "2.0.0-next.0", + "@reflag/vue-sdk": "2.0.0-next.0" + }, + "transforms": [ + "GitBook version/source banner", + "major-pinned install commands", + "source-linked README routing", + "TypeDoc property-anchor normalization", + "TypeDoc code-block trailing whitespace" + ], + "files": { + "@reflag/node-sdk/README.md": "39b8d207011c72958eba5dc00b9c8f50151303e4d73dcd212e98abf95b876dd5", + "@reflag/node-sdk/globals.md": "2860f77c95f5076c65fd47188ddafbb7682d78788ec2740c492c3308c493a35b", + "@reflag/browser-sdk/README.md": "6ecf0a7c814a1a8e84e58e8a4d9b471e7b6bd84b217019ba8636a14317bf873a", + "@reflag/browser-sdk/globals.md": "197ea4b302be1232163a5bd7be5989b77155ff56b7e94110fb821521210c8ea8", + "@reflag/react-sdk/README.md": "3aea43927c2cc4e1b78f9b5b50b8fee32fdfc561dfb94b517dd6174f2117255b", + "@reflag/react-sdk/globals.md": "4dabcbc11904bac65940838bfb0e92611c2803c4b514960ebd12c844dd128b85", + "@reflag/vue-sdk/README.md": "679f2b6bc4d13e15166a55e1ec7ec5ca04726c6ed8c1cfe462d299b0402d586a", + "@reflag/vue-sdk/globals.md": "bc2177da27865d9cc44110bbf5142ac1ec6bd8ff42f358f3f71974c39dc71ecb", + "documents/browser-sdk/FEEDBACK.md": "9a287958d63d328d244ebcc325d9fc75f3d6a575eaa777bdedecabff2d1bcc3a" + } +} diff --git a/previews/sdk-versioning/yarn.lock b/previews/sdk-versioning/yarn.lock new file mode 100644 index 0000000..f49c126 --- /dev/null +++ b/previews/sdk-versioning/yarn.lock @@ -0,0 +1,79 @@ +# This file is generated by running "yarn install" inside your project. +# Manual changes might be lost - proceed with caution! + +__metadata: + version: 8 + cacheKey: 10c0 + +"@reflag/docs-sdk-versioning-preview@workspace:.": + version: 0.0.0-use.local + resolution: "@reflag/docs-sdk-versioning-preview@workspace:." + dependencies: + ajv: "npm:8.20.0" + ajv-formats: "npm:3.0.1" + yaml: "npm:2.9.1" + languageName: unknown + linkType: soft + +"ajv-formats@npm:3.0.1": + version: 3.0.1 + resolution: "ajv-formats@npm:3.0.1" + dependencies: + ajv: "npm:^8.0.0" + peerDependencies: + ajv: ^8.0.0 + peerDependenciesMeta: + ajv: + optional: true + checksum: 10c0/168d6bca1ea9f163b41c8147bae537e67bd963357a5488a1eaf3abe8baa8eec806d4e45f15b10767e6020679315c7e1e5e6803088dfb84efa2b4e9353b83dd0a + languageName: node + linkType: hard + +"ajv@npm:8.20.0, ajv@npm:^8.0.0": + version: 8.20.0 + resolution: "ajv@npm:8.20.0" + dependencies: + fast-deep-equal: "npm:^3.1.3" + fast-uri: "npm:^3.0.1" + json-schema-traverse: "npm:^1.0.0" + require-from-string: "npm:^2.0.2" + checksum: 10c0/5df9a1c8f83863cde1bd3a9ddb426f599718f88e3dc9153616c79fb28e0be455335830d7f21d745576519f057b371352daa31047b6a33d7036fe08777d60cf2a + languageName: node + linkType: hard + +"fast-deep-equal@npm:^3.1.3": + version: 3.1.3 + resolution: "fast-deep-equal@npm:3.1.3" + checksum: 10c0/40dedc862eb8992c54579c66d914635afbec43350afbbe991235fdcb4e3a8d5af1b23ae7e79bef7d4882d0ecee06c3197488026998fb19f72dc95acff1d1b1d0 + languageName: node + linkType: hard + +"fast-uri@npm:^3.0.1": + version: 3.1.9 + resolution: "fast-uri@npm:3.1.9" + checksum: 10c0/052757540d8c0bbc16bb124a45b8f5cb5d0932f9b89f019221ee41cdb3cafc1db44b2e809d6b888b87922af3e5560a863124454e9d9193e1fc955887357b5add + languageName: node + linkType: hard + +"json-schema-traverse@npm:^1.0.0": + version: 1.0.0 + resolution: "json-schema-traverse@npm:1.0.0" + checksum: 10c0/71e30015d7f3d6dc1c316d6298047c8ef98a06d31ad064919976583eb61e1018a60a0067338f0f79cabc00d84af3fcc489bd48ce8a46ea165d9541ba17fb30c6 + languageName: node + linkType: hard + +"require-from-string@npm:^2.0.2": + version: 2.0.2 + resolution: "require-from-string@npm:2.0.2" + checksum: 10c0/aaa267e0c5b022fc5fd4eef49d8285086b15f2a1c54b28240fdf03599cbd9c26049fee3eab894f2e1f6ca65e513b030a7c264201e3f005601e80c49fb2937ce2 + languageName: node + linkType: hard + +"yaml@npm:2.9.1": + version: 2.9.1 + resolution: "yaml@npm:2.9.1" + bin: + yaml: bin.mjs + checksum: 10c0/9d06676ef02b89559a8b10a71882bd2cdae81e8393dc8dbc871615c3259475d9609ad973d19cb3c9e71e72d6206e3d1e7aa241c7e55f3d16fd2e77db305827d6 + languageName: node + linkType: hard