SDK

OpenFeature

ShipSilently implements the OpenFeature provider interface, so your evaluation call sites stay vendor-neutral and switching providers is a one-line change.

Install

The provider lives on a subpath of the Edge SDK. @openfeature/server-sdk is an optional peer dependency, you only need it if you import this subpath; the core SDK has no OpenFeature dependency at all.

terminal
npm install @shipsilently/edge @openfeature/server-sdk

Register the provider

flags.ts
// ShipSilently as an OpenFeature provider. Call sites stay vendor-neutral —
// swapping providers is a one-line change and your evaluation code never moves.
//
// `@openfeature/server-sdk` is an OPTIONAL peer dependency: install it only if
// you import this subpath. The core SDK has no OpenFeature dependency.
//
//   npm install @shipsilently/edge @openfeature/server-sdk
import { OpenFeature } from '@openfeature/server-sdk';
import { ShipSilentlyProvider } from '@shipsilently/edge/openfeature';

// Accepts the same config as EdgeClient.init — hosted, kv(), or staticProvider().
const provider = new ShipSilentlyProvider({ apiKey: process.env.SHIPSILENTLY_KEY! });

// setProviderAndWait runs initialize(), which loads the flag blob.
await OpenFeature.setProviderAndWait(provider);

const client = OpenFeature.getClient();

// `targetingKey` maps to the context's `key`; other primitive attributes pass
// through untouched for targeting rules.
export const enabled = await client.getBooleanValue('new-checkout', false, {
  targetingKey: 'u_123',
  plan: 'pro',
});

// Evaluation details carry the mapped OpenFeature reason:
//   rule_match → TARGETING_MATCH   rollout → SPLIT
//   flag_disabled → DISABLED       flag_not_found → ERROR (FLAG_NOT_FOUND)
export const details = await client.getBooleanDetails('new-checkout', false, {
  targetingKey: 'u_123',
});
Use setProviderAndWait, not setProvider. The provider's initialize() is what loads the flag blob. Resolve a flag before it finishes and you get your default, silently.

Context mapping

OpenFeature's EvaluationContext maps onto ShipSilently's attribute model like this:

OpenFeatureShipSilently
targetingKeykey
String / number / boolean attributesPassed through unchanged
Nested objects and arraysDropped — not part of the attribute model
targetingKey is not userId. It maps to key, while rollout bucketing reads userId (then email). If you use percentage rollouts through OpenFeature, set userId explicitly in the context as well, otherwise every user buckets identically.

Reason mapping

ShipSilently reasonOpenFeature reason
rule_matchTARGETING_MATCH
rolloutSPLIT
flag_disabledDISABLED
defaultDEFAULT
flag_not_foundERROR with errorCode: FLAG_NOT_FOUND

When a rule decided the result, its UUID is surfaced as the OpenFeature variant.

Wrapping an existing client

If you already hold an EdgeClient, wrap it instead of building a second one, so both share a single blob and a single refresh loop:

flags.ts
import { EdgeClient } from '@shipsilently/edge';
import { ShipSilentlyProvider } from '@shipsilently/edge/openfeature';

const client = EdgeClient.init({ apiKey: process.env.SHIPSILENTLY_KEY! });
await client.load();

const provider = ShipSilentlyProvider.fromClient(client);

Performance

The provider's resolve* methods are async because the OpenFeature interface requires it, but there is no I/O on the resolve path once the blob is loaded. Every resolution is the same synchronous, sub-millisecond in-memory evaluation the Edge SDK performs directly.