@dr-sentry/sdk
v1.2.2
Published
Official Node.js SDK for the DeploySentry platform – feature flag evaluation with rich metadata
Maintainers
Readme
DeploySentry Node.js SDK
Official Node.js/TypeScript SDK for integrating with the DeploySentry platform. Provides feature flag evaluation with rich metadata including categories, ownership, expiration tracking, and real-time updates via SSE.
Installation
npm install @dr-sentry/sdkQuick Start
import { DeploySentryClient } from '@dr-sentry/sdk';
const client = new DeploySentryClient({
apiKey: 'ds_live_xxx',
environment: 'production',
project: 'my-app',
application: 'my-web-app',
});
await client.initialize();
// Boolean flag
const darkMode = await client.boolValue('dark-mode', false, {
userId: 'user-42',
});
// String flag
const variant = await client.stringValue('checkout-variant', 'control');
// Integer flag
const maxRetries = await client.intValue('max-retries', 3);
// JSON flag
const config = await client.jsonValue('rate-limits', { rpm: 100 });
// Clean up when done
client.close();High-scale Deployments
Create one DeploySentryClient singleton per process and reuse it. Each client
performs a startup snapshot fetch, opens an SSE stream, and runs safety resyncs,
so creating clients per request or per worker task will amplify traffic.
For k3s/Kubernetes workloads with many pods for the same
project/application/environment, prefer a local flag proxy/operator or a
Redis-backed shared cache so only one or a small number of upstream clients
connect to DeploySentry. Move to this pattern around 50+ pods, or sooner for
bursty rollouts. Monitor X-RateLimit-Class and X-RateLimit-Remaining when
tuning large deployments.
Status reporting (optional)
Enable reportStatus to have the SDK push version + health to DeploySentry automatically. No separate startup code needed — the SDK posts POST /applications/:id/status on init and on an interval.
const client = new DeploySentryClient({
apiKey: process.env.DS_API_KEY!,
environment: 'production',
project: 'my-app',
application: 'my-web-app',
// Agentless status reporting
applicationId: process.env.DS_APPLICATION_ID!, // UUID of the app
reportStatus: true,
reportStatusIntervalMs: 30_000, // default; 0 = startup-only
reportStatusVersion: process.env.APP_VERSION, // optional override
reportStatusCommitSha: process.env.GIT_SHA,
reportStatusDeploySlot: process.env.DS_DEPLOY_SLOT, // "stable" | "canary"
reportStatusTags: { region: process.env.REGION ?? 'unknown' },
reportStatusHealthProvider: async () => ({
state: (await isDbUp()) ? 'healthy' : 'degraded',
score: 0.99,
}),
});
await client.initialize();The API key must carry the status:write scope and be scoped to a single application + environment. When reportStatus is true but applicationId is omitted, the reporter logs a warning and is disabled — flag evaluation continues to work.
Without a reportStatusHealthProvider, the SDK sends health: "healthy" on every tick ("process alive" floor). Supply a provider if your app can detect degradation (stale DB, downstream outages, etc.).
Version auto-detection (when reportStatusVersion is omitted) checks these env vars in order: APP_VERSION, GIT_SHA, GIT_COMMIT, SOURCE_COMMIT, RAILWAY_GIT_COMMIT_SHA, RENDER_GIT_COMMIT, VERCEL_GIT_COMMIT_SHA, HEROKU_SLUG_COMMIT, npm_package_version — falling back to the literal string "unknown".
The first /status report with a version DeploySentry has not seen auto-creates a mode=record deployment with source="app-push", so deploy history populates even without a Railway/Render/etc. webhook configured.
Evaluation Context
Pass targeting context with every evaluation:
const enabled = await client.boolValue('premium-feature', false, {
userId: 'user-42',
orgId: 'org-7',
attributes: {
plan: 'enterprise',
region: 'us-east-1',
},
});Rich Metadata
Every flag carries structured metadata describing its purpose, ownership, and lifecycle:
const result = await client.detail('new-checkout');
console.log(result.metadata.category); // 'release' | 'feature' | 'experiment' | 'ops' | 'permission' | 'envvar'
console.log(result.metadata.purpose); // "Gradual rollout of new checkout flow"
console.log(result.metadata.owners); // ["payments-team", "[email protected]"]
console.log(result.metadata.isPermanent); // false
console.log(result.metadata.expiresAt); // "2026-06-01T00:00:00Z"
console.log(result.metadata.tags); // ["checkout", "q2-2026"]Flag Categories
Flags are classified into categories that drive lifecycle policies:
| Category | Description |
| ------------ | -------------------------------------------------- |
| release | Gradual rollout gates for new releases |
| feature | Long-lived feature toggles |
| experiment | A/B tests and experiments with defined end dates |
| ops | Operational controls (kill switches, rate limits) |
| permission | Entitlement and access-control flags |
| envvar | Non-secret environment variable tracking |
Query by category:
const experiments = client.flagsByCategory('experiment');
const killSwitches = client.flagsByCategory('ops');Lifecycle Management
Find expired flags that should be cleaned up:
const expired = client.expiredFlags();
for (const flag of expired) {
console.warn(`Flag "${flag.key}" expired at ${flag.metadata.expiresAt}`);
}Look up flag owners:
const owners = client.flagOwners('new-checkout');
// ["payments-team", "[email protected]"]List all cached flags:
const flags = client.allFlags();Inspecting Flag Sources (admin / support view)
inspect() returns a provenance-tagged snapshot of every flag the SDK currently knows about — drawn from all sources at once (live server cache, offline file config, and offline defaults). Each entry reports the resolved value, its type, where the value came from, an evaluation reason, and when it was last fetched. Wire it into an admin or support panel to see exactly what an application is serving and why.
const view = client.inspect();
// [
// {
// key: 'new-checkout',
// value: true,
// type: 'boolean',
// enabled: true,
// source: 'server', // 'server' | 'cache' | 'file' | 'default'
// reason: 'LIVE', // LIVE | CACHE | OFFLINE_FILE | OFFLINE_DEFAULT | DEFAULT
// fetchedAt: '2026-06-23T12:00:00.000Z',
// stale: false, // true when a cached value is past its TTL
// },
// ...
// ]| source | Meaning |
| --- | --- |
| server | Fetched live from the API and still within its cache TTL. |
| cache | A previously-fetched server value past its TTL, still served because the server is currently unreachable (stale: true). |
| file | An offline file — a mode: 'file' config, or the offline defaults loaded via loadDefaults*() (the reason distinguishes OFFLINE_FILE vs OFFLINE_DEFAULT). |
| default | No value from any source; callers receive their coded defaultValue. |
inspect() is a read-only snapshot — it does not contact the server or evaluate targeting rules for a specific user.
Flag-set version
Every change to a project's flag state advances a monotonic per-project version on the server. getFlagsVersion() returns the version the SDK last loaded — use it to validate exactly which flag set an application is serving (e.g. log it on boot, surface it in an admin panel, or assert it in a deploy gate).
const version = client.getFlagsVersion();
// number — e.g. 42 — once a server load has succeeded
// null — offline/file mode, or before the first successful syncThe SDK also logs loaded flags version=N whenever the loaded version changes. The version is captured from the flag list response on every full sync; it advances on any flag-state change (the flag, its per-environment toggles, or its targeting rules).
Register / Dispatch Pattern
The register/dispatch pattern lets you centralize all flag-gated behavior in one place instead of scattering if/else checks throughout your codebase. Register named operations with their handlers and optional flag keys, then dispatch by operation name at the call site.
Basic usage
// --- registrations.ts (single-point registration) ---
import { client } from './deploysentry';
// Default handler (no flag key) — always used as fallback
client.register('checkout', () => {
return processLegacyCheckout();
});
// Flag-gated handler — used when 'new-checkout-flow' is enabled
client.register('checkout', () => {
return processNewCheckout();
}, 'new-checkout-flow');
// --- somewhere-else.ts (call site) ---
// Dispatch selects the right handler based on flag state.
// No flag logic here — the call site doesn't know or care.
const checkout = client.dispatch<() => Promise<Order>>('checkout');
await checkout();How it works
register(operation, handler, flagKey?)— Registers a handler for a named operation. IfflagKeyis provided, the handler is only selected when that flag is enabled. OmitflagKeyto register the default/fallback handler.dispatch(operation)— Returns the first registered handler whose flag is enabled. If no flagged handler matches, returns the default handler. Throws if no handlers are registered.
Single-point registration
Keep all registrations in a single file (e.g. src/flags/registrations.ts) that runs at startup. This gives you:
- One place to see every flag-gated behavior in your app
- Easy auditing of which flags control which operations
- Simple cleanup when a flag is retired (remove the registration, keep the handler)
// src/flags/registrations.ts
import { client } from './deploysentry';
import { legacySearch, vectorSearch } from '../search';
import { standardPricing, tieredPricing } from '../pricing';
// Search
client.register('search', legacySearch);
client.register('search', vectorSearch, 'vector-search-v2');
// Pricing
client.register('calculate-price', standardPricing);
client.register('calculate-price', tieredPricing, 'tiered-pricing-experiment');Offline Mode
For testing or environments without network access, enable offline mode. The client will return default values without contacting the API:
const client = new DeploySentryClient({
apiKey: 'not-used',
environment: 'test',
project: 'my-app',
application: 'my-web-app',
offlineMode: true,
});
await client.initialize(); // no-op in offline mode
const value = await client.boolValue('any-flag', true);
// Returns the default value: trueOffline Defaults
For deployments where the SDK may briefly be unable to reach DeploySentry (CI runs without network, cold-start before SSE connects, air-gapped builds), commit a flag snapshot to your repo and load it as fallback. The SDK still prefers the live API; defaults only fire when both the API and the in-process cache are empty.
Generate the snapshot with the CLI:
deploysentry flags export --application web --env production -f flags.jsonThen load it after constructing the client:
const client = new DeploySentryClient({
apiKey: process.env.DEPLOYSENTRY_API_KEY!,
environment: 'production',
project: 'my-app',
application: 'web',
});
await client.loadDefaultsFromFile('flags.json');
// or sync from an already-parsed object:
// client.loadDefaults(JSON.parse(fs.readFileSync('flags.json', 'utf-8')));Evaluation order:
- Live API when reachable.
- In-process cache (entries from prior live calls).
- Offline defaults loaded above — picks the per-environment value matching your
environmentoption (by UUID first, then by name, case-insensitive). Falls back to the flag's globaldefault_valuewhen no env matches. - Caller's
defaultValuewhen the flag isn't in the export.
Configuration
| Option | Type | Required | Default | Description |
| -------------- | -------- | -------- | ------------------------------ | -------------------------------------- |
| apiKey | string | Yes | - | API key for authentication |
| environment | string | Yes | - | Target environment |
| project | string | Yes | - | Project identifier |
| application | string | Yes | - | Application identifier |
| baseURL | string | No | https://api.dr-sentry.com | API base URL |
| cacheTimeout | number | No | 60000 | Cache TTL in milliseconds |
| offlineMode | boolean | No | false | Return defaults without API calls |
| mode | string | No | server | server, file, or server-with-fallback |
| flagFilePath | string | No | .deploysentry/flags.yaml | Path to YAML flag config file |
Offline / File Mode
The SDK can load flag configurations from a local YAML/JSON file. The server is always the source of truth — the exported file is a backup/fallback only. In production use server-with-fallback: the SDK reaches the server first on every evaluation and consults the local file only when the server (and the in-process cache) are unreachable. Pure file mode (no server contact) is intended for local development, CI, and testing — not for normal production operation.
Modes
| Mode | Behavior |
| --- | --- |
| server (default) | API calls + SSE streaming. Server only. |
| file | Load from local file, evaluate locally. No server contact — dev/CI/testing only. |
| server-with-fallback | Recommended for resilience. Always try the server first; fall back to the local file only when the server is unreachable, then return to live as soon as it recovers. |
Usage
// File mode — local development, CI, testing
const client = new DeploySentryClient({
apiKey: 'not-used',
environment: 'staging',
project: 'my-project',
application: 'my-web-app',
mode: 'file',
flagFilePath: '.deploysentry/flags.yaml', // default
});
await client.initialize();
// Fallback mode — production resilience
const client = new DeploySentryClient({
apiKey: 'ds_live_xxx',
environment: 'production',
project: 'my-project',
application: 'my-web-app',
mode: 'server-with-fallback',
});
await client.initialize();Generating the YAML file
Export from the DeploySentry dashboard: App Settings → Export flags.yaml. Place the downloaded file at .deploysentry/flags.yaml in your project root.
Local rule evaluation
In file mode, targeting rules are evaluated locally. The SDK matches context attributes against rule conditions using the same operators as the server: equals, not_equals, in, not_in, contains, starts_with, ends_with, greater_than, less_than.
API Endpoints
The SDK communicates with these DeploySentry API endpoints:
POST /api/v1/flags/evaluate- Evaluate a single flagPOST /api/v1/flags/batch-evaluate- Evaluate multiple flagsGET /api/v1/flags/stream?project_id=X&environment_id=Y- SSE stream for real-time updatesGET /api/v1/flags?project_id=X- List all flags for a project
Authentication uses the Authorization: ApiKey <key> header.
Session Consistency
Bind evaluations to a session so the server caches results for a consistent user experience:
const client = new DeploySentryClient({
apiKey: 'ds_key_xxxxxxxxxxxx',
environment: 'production',
project: 'my-project',
application: 'my-web-app',
sessionId: `user:${userId}`,
});
await client.refreshSession();- The session ID is sent as an
X-DeploySentry-Sessionheader on every request. - The server caches evaluation results per session for 30 minutes (sliding TTL).
- Omit the session ID to always get fresh evaluations on each request.
License
Apache-2.0
