JavaScript SDK
Fetch once, poll quietly, evaluate in memory. 2.1 KB gzipped, zero dependencies, Node 18 and later.
Install
$ npm install @flagrship/sdkShips CommonJS and ESM builds with TypeScript types. Works in Node, Bun, and Deno.
Usage
1import { Flagrship } from '@flagrship/sdk';2 3const flags = new Flagrship({4 apiKey: process.env.FLAGRSHIP_KEY,5 defaults: { 'new-checkout': false },6});7 8// Resolves after the first sync attempt, success or failure. Never rejects.9await flags.ready();10 11if (flags.isEnabled('new-checkout', user.id)) {12 showNewCheckout();13}Constructor options
| Option | Type | Default | What it does |
|---|---|---|---|
| apiKey | string | required | A read-scoped key for one environment. |
| apiUrl | string | https://api.flagrship.dev | Override for self-hosted or local APIs. |
| pollInterval | number | 30000 | Milliseconds between polls. 0 disables polling entirely. |
| defaults | Record<string, boolean> | {} | What isEnabled returns for flags the SDK has no config for. |
| onUpdate | (flags) => void | — | Called after every successful config change, with a snapshot Map. |
| onError | (error) => void | — | Called when a sync fails. The SDK keeps serving its last config regardless. |
| fetch | typeof fetch | globalThis.fetch | Injectable for tests and non-standard runtimes. |
Methods
ready()
Returns a promise that resolves after the first sync attempt. It never rejects. Await it once at startup if you want the first evaluation to reflect real config rather than defaults. If the API is unreachable, it still resolves, and onError is called.
isEnabled(flagKey, userId?, defaultValue?)
Synchronous, in-memory. The evaluation order:
- Unknown flag →
defaultValue, thenoptions.defaults[flagKey], thenfalse - Disabled →
false - Rollout 100 →
true, no user ID needed - No user ID and rollout below 100 →
false(cannot bucket without an identifier) - Otherwise
bucket(flagKey, userId) < rolloutPercentage
getFlag(flagKey)
The raw config object for a flag, or undefined. Useful for debugging and for showing rollout state in an admin UI.
{ "key": "new-checkout", "enabled": true, "rolloutPercentage": 25, "targetingRules": null}allFlags()
A snapshot Map of every flag the SDK currently knows about. Safe to iterate while polling continues.
refresh()
Sync now instead of waiting for the next poll. Resolves true if config changed, false on a 304 or a failure. Never rejects.
close()
Stop polling. The last config stays in memory and isEnabled keeps working.
Sync behaviour
The SDK sends If-None-Match with the last ETag on every poll. A 304 means nothing changed and the in-memory Map is left alone; onUpdate does not fire. A 200 replaces the whole Map in one assignment, so a concurrent isEnabled call sees either the old config or the new one, never a mix.
The polling timer is unref()'d. A script that creates an SDK instance and finishes its work exits normally; a long-running server keeps polling for as long as it is alive for other reasons.
Bucketing
Two helpers are exported so you can reproduce the SDK's decision anywhere, for example to show a user which cohort they are in.
import { bucket, murmurhash3_x86_32 } from '@flagrship/sdk'; bucket('new-checkout', 'alice'); // 40murmurhash3_x86_32('hello'); // 613153351