SDKs · OpenFeature
OpenFeature provider
Write your application against the vendor-neutral OpenFeature API and plug darkship in as the provider. It wraps the native JavaScript SDK and adds no behaviour of its own, so both paths give the same answers.
When to use it#
- You already use OpenFeature, or want to keep the option to swap providers.
- You want the standard hooks, evaluation-context and event APIs across several services.
Use the native SDK when you need the get*Detail variants with darkship-specific reasons and bucket, date flags as Date, or the bootstrap option. The provider is a thin mapping on top; it cannot return anything the native SDK couldn't.
The JavaScript package @darkship/openfeature-provider ships two providers, DarkshipServerProvider (import from @darkship/openfeature-provider/server, for Node) and DarkshipWebProvider (import from @darkship/openfeature-provider/web, for browsers). Python has its own provider in the Python SDK (pip install "darkship[openfeature]"). Other languages use the REST API.
Install#
$ npm install @openfeature/server-sdk @darkship/openfeature-provider$ npm install @openfeature/web-sdk @darkship/openfeature-providerRegister the provider#
import { OpenFeature } from '@openfeature/server-sdk';
import { DarkshipServerProvider } from '@darkship/openfeature-provider/server'; // alias: DarkshipProvider
await OpenFeature.setProviderAndWait(
new DarkshipServerProvider({ sdkKey: process.env.DARKSHIP_SDK_KEY! }), // ds_srv_...
);
const client = OpenFeature.getClient();
const enabled = await client.getBooleanValue(
'new-checkout-flow',
false,
{ targetingKey: 'usr_marta', plan: 'pro', country: 'BR' },
);import { OpenFeature } from '@openfeature/web-sdk';
import { DarkshipWebProvider } from '@darkship/openfeature-provider/web';
await OpenFeature.setContext({ targetingKey: user.id, plan: user.plan });
await OpenFeature.setProviderAndWait(new DarkshipWebProvider({ sdkKey: 'ds_cli_prod_Zk3...' }));
const client = OpenFeature.getClient();
const enabled = client.getBooleanValue('new-checkout-flow', false);
client.addHandler('PROVIDER_CONFIGURATION_CHANGED', () => rerender());The provider constructor accepts all native init() options (see init options) plus client (reuse an existing SDK client), hooks, initTimeoutMs (server, default 3000) and offReason (STATIC by default; DISABLED if you want that reason for an environment switched off). The package depends on @darkship/sdk. With setProviderAndWait, OpenFeature waits for the provider's READY event, which maps to the SDK's ready.
Mapping reference#
API#
| OpenFeature | darkship |
|---|---|
Provider metadata.name | darkship |
resolveBooleanEvaluation | getBooleanDetail |
resolveStringEvaluation | getStringDetail |
resolveNumberEvaluation | getIntDetail or getFloatDetail, chosen by the flag's type |
resolveObjectEvaluation | getJsonDetail. Also used for date flags (see below). |
EvaluationContext.targetingKey | context.key, used for rollout hashing and key target lists |
Other context fields (email, groups, ...) | Context attributes: email and groups are matched by email and group target lists; the rest are available to targeting rules |
variant | The variation name (variationKey). flagMetadata carries ruleId, bucket and flagType |
A date flag is resolved through getObjectValue and arrives as an object carrying an ISO-8601 string. Parse it yourself: new Date(value.iso). Prefer the native SDK if you use date flags heavily.
await client.getBooleanValue('ai-search-beta', false, {
targetingKey: 'usr_marta', // becomes context.key
email: 'Marta@Acme.com', // becomes the email attribute
groups: ['beta-testers', 'acme-staff'], // becomes the groups attribute
plan: 'pro',
});Reasons#
| darkship reason | OpenFeature reason |
|---|---|
OFF | STATIC |
TARGET_MATCH, RULE_MATCH | TARGETING_MATCH, or SPLIT when a percentage rollout decided (the detail has a bucket) |
FALLTHROUGH | DEFAULT, or SPLIT when a percentage rollout decided |
DEFAULT | DEFAULT |
NOT_READY, ERROR | ERROR (with an error code; your default is returned) |
Error codes#
| darkship | OpenFeature errorCode |
|---|---|
FLAG_NOT_FOUND | FLAG_NOT_FOUND |
TYPE_MISMATCH | TYPE_MISMATCH |
INVALID_CONTEXT | INVALID_CONTEXT |
NOT_READY (reason; the SDK's code is PROVIDER_NOT_READY) | PROVIDER_NOT_READY |
MALFORMED_FLAG, GENERAL | GENERAL |
Provider events#
| SDK event | OpenFeature event |
|---|---|
ready, recovered | PROVIDER_READY |
update | PROVIDER_CONFIGURATION_CHANGED (with flagsChanged) |
stale | PROVIDER_STALE |
error | PROVIDER_ERROR |
Hooks and shutdown#
OpenFeature hooks (logging, telemetry, validation) work unchanged because they sit above the provider. On shutdown call await OpenFeature.close(); it calls the provider's onClose, which runs the SDK's close() and flushes counters.
import { OpenFeature, type Hook } from '@openfeature/server-sdk';
const audit: Hook = {
after: (ctx, details) => metrics.count('flag', { key: ctx.flagKey, variant: details.variant, reason: details.reason }),
};
OpenFeature.addHooks(audit);Parity with the native SDK#
The provider depends on @darkship/sdk and calls it; it has no evaluation code of its own. That is deliberate: native and OpenFeature paths cannot drift apart, and the package's parity test runs every shared conformance vector through the provider and compares it with the native SDK. There is no official OpenFeature conformance suite for JavaScript providers. If you see a different result between them for the same flag and context, please report it as a bug.
Thanks. This helps us prioritise the docs.