Big News: Socket raises $60M Series C at a $1B valuation to secure software supply chains for AI-driven development.Announcement
Sign In

@openfeature/server-sdk

Package Overview
Dependencies
Maintainers
4
Versions
34
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@openfeature/server-sdk - npm Package Compare versions

Comparing version
1.19.0
to
1.20.0
+582
-37
dist/cjs/index.js

@@ -61,4 +61,11 @@ "use strict";

__export(index_exports, {
AggregateError: () => AggregateError,
AsyncLocalStorageTransactionContextPropagator: () => AsyncLocalStorageTransactionContextPropagator,
BaseEvaluationStrategy: () => BaseEvaluationStrategy,
ComparisonStrategy: () => ComparisonStrategy,
ErrorWithCode: () => ErrorWithCode,
FirstMatchStrategy: () => FirstMatchStrategy,
FirstSuccessfulStrategy: () => FirstSuccessfulStrategy,
InMemoryProvider: () => InMemoryProvider,
MultiProvider: () => MultiProvider,
NOOP_PROVIDER: () => NOOP_PROVIDER,

@@ -70,3 +77,5 @@ NOOP_TRANSACTION_CONTEXT_PROPAGATOR: () => NOOP_TRANSACTION_CONTEXT_PROPAGATOR,

ProviderEvents: () => import_core6.ServerProviderEvents,
ProviderStatus: () => import_core.ServerProviderStatus
ProviderStatus: () => import_core.ServerProviderStatus,
constructAggregateError: () => constructAggregateError,
throwAggregateErrorFromPromiseResults: () => throwAggregateErrorFromPromiseResults
});

@@ -199,7 +208,551 @@ module.exports = __toCommonJS(index_exports);

// src/open-feature.ts
// src/provider/multi-provider/multi-provider.ts
var import_core9 = require("@openfeature/core");
// src/events/open-feature-event-emitter.ts
var import_core4 = require("@openfeature/core");
var import_node_events = require("node:events");
var OpenFeatureEventEmitter = class extends import_core4.GenericEventEmitter {
constructor() {
super();
this.eventEmitter = new import_node_events.EventEmitter({ captureRejections: true });
this.eventEmitter.on("error", (err) => {
var _a;
(_a = this._logger) == null ? void 0 : _a.error("Error running event handler:", err);
});
}
};
// src/provider/multi-provider/errors.ts
var import_core5 = require("@openfeature/core");
var ErrorWithCode = class extends import_core5.OpenFeatureError {
constructor(code, message) {
super(message);
this.code = code;
}
};
var AggregateError = class extends import_core5.GeneralError {
constructor(message, originalErrors) {
super(message);
this.originalErrors = originalErrors;
}
};
var constructAggregateError = (providerErrors) => {
const errorsWithSource = providerErrors.map(({ providerName, error }) => {
return { source: providerName, error };
}).flat();
return new AggregateError(
`Provider errors occurred: ${errorsWithSource[0].source}: ${errorsWithSource[0].error}`,
errorsWithSource
);
};
var throwAggregateErrorFromPromiseResults = (result, providerEntries) => {
const errors = result.map((r, i) => {
if (r.status === "rejected") {
return { error: r.reason, providerName: providerEntries[i].name };
}
return null;
}).filter((val) => Boolean(val));
if (errors.length) {
throw constructAggregateError(errors);
}
};
// src/provider/multi-provider/hook-executor.ts
var HookExecutor = class {
constructor(logger) {
this.logger = logger;
}
beforeHooks(hooks, hookContext, hints) {
return __async(this, null, function* () {
var _a;
for (const hook of hooks != null ? hooks : []) {
Object.freeze(hookContext);
Object.assign(hookContext.context, __spreadValues({}, yield (_a = hook == null ? void 0 : hook.before) == null ? void 0 : _a.call(hook, hookContext, Object.freeze(hints))));
}
return Object.freeze(hookContext.context);
});
}
afterHooks(hooks, hookContext, evaluationDetails, hints) {
return __async(this, null, function* () {
var _a;
for (const hook of hooks != null ? hooks : []) {
yield (_a = hook == null ? void 0 : hook.after) == null ? void 0 : _a.call(hook, hookContext, evaluationDetails, hints);
}
});
}
errorHooks(hooks, hookContext, err, hints) {
return __async(this, null, function* () {
var _a;
for (const hook of hooks != null ? hooks : []) {
try {
yield (_a = hook == null ? void 0 : hook.error) == null ? void 0 : _a.call(hook, hookContext, err, hints);
} catch (err2) {
this.logger.error(`Unhandled error during 'error' hook: ${err2}`);
if (err2 instanceof Error) {
this.logger.error(err2.stack);
}
this.logger.error(err2 == null ? void 0 : err2.stack);
}
}
});
}
finallyHooks(hooks, hookContext, evaluationDetails, hints) {
return __async(this, null, function* () {
var _a;
for (const hook of hooks != null ? hooks : []) {
try {
yield (_a = hook == null ? void 0 : hook.finally) == null ? void 0 : _a.call(hook, hookContext, evaluationDetails, hints);
} catch (err) {
this.logger.error(`Unhandled error during 'finally' hook: ${err}`);
if (err instanceof Error) {
this.logger.error(err.stack);
}
this.logger.error(err == null ? void 0 : err.stack);
}
}
});
}
};
// src/events/events.ts
var import_core6 = require("@openfeature/core");
// src/provider/multi-provider/status-tracker.ts
var StatusTracker = class {
constructor(events) {
this.events = events;
this.providerStatuses = {};
}
wrapEventHandler(providerEntry) {
var _a, _b, _c, _d;
const provider = providerEntry.provider;
(_a = provider.events) == null ? void 0 : _a.addHandler(import_core6.ServerProviderEvents.Error, (details) => {
this.changeProviderStatus(providerEntry.name, import_core.ServerProviderStatus.ERROR, details);
});
(_b = provider.events) == null ? void 0 : _b.addHandler(import_core6.ServerProviderEvents.Stale, (details) => {
this.changeProviderStatus(providerEntry.name, import_core.ServerProviderStatus.STALE, details);
});
(_c = provider.events) == null ? void 0 : _c.addHandler(import_core6.ServerProviderEvents.ConfigurationChanged, (details) => {
this.events.emit(import_core6.ServerProviderEvents.ConfigurationChanged, details);
});
(_d = provider.events) == null ? void 0 : _d.addHandler(import_core6.ServerProviderEvents.Ready, (details) => {
this.changeProviderStatus(providerEntry.name, import_core.ServerProviderStatus.READY, details);
});
}
providerStatus(name) {
return this.providerStatuses[name];
}
getStatusFromProviderStatuses() {
const statuses = Object.values(this.providerStatuses);
if (statuses.includes(import_core.ServerProviderStatus.FATAL)) {
return import_core.ServerProviderStatus.FATAL;
} else if (statuses.includes(import_core.ServerProviderStatus.NOT_READY)) {
return import_core.ServerProviderStatus.NOT_READY;
} else if (statuses.includes(import_core.ServerProviderStatus.ERROR)) {
return import_core.ServerProviderStatus.ERROR;
} else if (statuses.includes(import_core.ServerProviderStatus.STALE)) {
return import_core.ServerProviderStatus.STALE;
}
return import_core.ServerProviderStatus.READY;
}
changeProviderStatus(name, status, details) {
const currentStatus = this.getStatusFromProviderStatuses();
this.providerStatuses[name] = status;
const newStatus = this.getStatusFromProviderStatuses();
if (currentStatus !== newStatus) {
if (newStatus === import_core.ServerProviderStatus.FATAL || newStatus === import_core.ServerProviderStatus.ERROR) {
this.events.emit(import_core6.ServerProviderEvents.Error, details);
} else if (newStatus === import_core.ServerProviderStatus.STALE) {
this.events.emit(import_core6.ServerProviderEvents.Stale, details);
} else if (newStatus === import_core.ServerProviderStatus.READY) {
this.events.emit(import_core6.ServerProviderEvents.Ready, details);
}
}
}
};
// src/provider/multi-provider/strategies/base-evaluation-strategy.ts
var BaseEvaluationStrategy = class {
constructor() {
this.runMode = "sequential";
}
shouldEvaluateThisProvider(strategyContext, _evalContext) {
if (strategyContext.providerStatus === import_core.ServerProviderStatus.NOT_READY || strategyContext.providerStatus === import_core.ServerProviderStatus.FATAL) {
return false;
}
return true;
}
shouldEvaluateNextProvider(_strategyContext, _context, _result) {
return true;
}
shouldTrackWithThisProvider(strategyContext, _context, _trackingEventName, _trackingEventDetails) {
if (strategyContext.providerStatus === import_core.ServerProviderStatus.NOT_READY || strategyContext.providerStatus === import_core.ServerProviderStatus.FATAL) {
return false;
}
return true;
}
hasError(resolution) {
return "thrownError" in resolution || !!resolution.details.errorCode;
}
hasErrorWithCode(resolution, code) {
var _a;
return "thrownError" in resolution ? ((_a = resolution.thrownError) == null ? void 0 : _a.code) === code : resolution.details.errorCode === code;
}
collectProviderErrors(resolutions) {
var _a;
const errors = [];
for (const resolution of resolutions) {
if ("thrownError" in resolution) {
errors.push({ providerName: resolution.providerName, error: resolution.thrownError });
} else if (resolution.details.errorCode) {
errors.push({
providerName: resolution.providerName,
error: new ErrorWithCode(resolution.details.errorCode, (_a = resolution.details.errorMessage) != null ? _a : "unknown error")
});
}
}
return { errors };
}
resolutionToFinalResult(resolution) {
return { details: resolution.details, provider: resolution.provider, providerName: resolution.providerName };
}
};
// src/provider/multi-provider/strategies/first-match-strategy.ts
var import_core7 = require("@openfeature/core");
var FirstMatchStrategy = class extends BaseEvaluationStrategy {
shouldEvaluateNextProvider(strategyContext, context, result) {
if (this.hasErrorWithCode(result, import_core7.ErrorCode.FLAG_NOT_FOUND)) {
return true;
}
if (this.hasError(result)) {
return false;
}
return false;
}
determineFinalResult(strategyContext, context, resolutions) {
const finalResolution = resolutions[resolutions.length - 1];
if (this.hasError(finalResolution)) {
return this.collectProviderErrors(resolutions);
}
return this.resolutionToFinalResult(finalResolution);
}
};
// src/provider/multi-provider/strategies/first-successful-strategy.ts
var FirstSuccessfulStrategy = class extends BaseEvaluationStrategy {
shouldEvaluateNextProvider(strategyContext, context, result) {
return this.hasError(result);
}
determineFinalResult(strategyContext, context, resolutions) {
const finalResolution = resolutions[resolutions.length - 1];
if (this.hasError(finalResolution)) {
return this.collectProviderErrors(resolutions);
}
return this.resolutionToFinalResult(finalResolution);
}
};
// src/provider/multi-provider/strategies/comparison-strategy.ts
var import_core8 = require("@openfeature/core");
var ComparisonStrategy = class extends BaseEvaluationStrategy {
constructor(fallbackProvider, onMismatch) {
super();
this.fallbackProvider = fallbackProvider;
this.onMismatch = onMismatch;
this.runMode = "parallel";
}
determineFinalResult(strategyContext, context, resolutions) {
var _a;
let value;
let fallbackResolution;
let finalResolution;
let mismatch = false;
for (const [i, resolution] of resolutions.entries()) {
if (this.hasError(resolution)) {
return this.collectProviderErrors(resolutions);
}
if (resolution.provider === this.fallbackProvider) {
fallbackResolution = resolution;
}
if (i === 0) {
finalResolution = resolution;
}
if (typeof value !== "undefined" && value !== resolution.details.value) {
mismatch = true;
} else {
value = resolution.details.value;
}
}
if (!fallbackResolution) {
throw new import_core8.GeneralError("Fallback provider not found in resolution results");
}
if (!finalResolution) {
throw new import_core8.GeneralError("Final resolution not found in resolution results");
}
if (mismatch) {
(_a = this.onMismatch) == null ? void 0 : _a.call(this, resolutions);
return {
details: fallbackResolution.details,
provider: fallbackResolution.provider
};
}
return this.resolutionToFinalResult(finalResolution);
}
};
// src/provider/multi-provider/multi-provider.ts
var MultiProvider = class _MultiProvider {
constructor(constructorProviders, evaluationStrategy = new FirstMatchStrategy(), logger = new import_core9.DefaultLogger()) {
this.constructorProviders = constructorProviders;
this.evaluationStrategy = evaluationStrategy;
this.logger = logger;
this.runsOn = "server";
this.events = new OpenFeatureEventEmitter();
this.hookContexts = /* @__PURE__ */ new WeakMap();
this.hookHints = /* @__PURE__ */ new WeakMap();
this.providerEntries = [];
this.providerEntriesByName = {};
this.statusTracker = new StatusTracker(this.events);
this.hookExecutor = new HookExecutor(this.logger);
this.registerProviders(constructorProviders);
const aggregateMetadata = Object.keys(this.providerEntriesByName).reduce((acc, name) => {
return __spreadProps(__spreadValues({}, acc), { [name]: this.providerEntriesByName[name].provider.metadata });
}, {});
this.metadata = __spreadProps(__spreadValues({}, aggregateMetadata), {
name: _MultiProvider.name
});
}
registerProviders(constructorProviders) {
var _a, _b;
const providersByName = {};
for (const constructorProvider of constructorProviders) {
const providerName = constructorProvider.provider.metadata.name;
const candidateName = (_a = constructorProvider.name) != null ? _a : providerName;
if (constructorProvider.name && providersByName[constructorProvider.name]) {
throw new Error("Provider names must be unique");
}
(_b = providersByName[candidateName]) != null ? _b : providersByName[candidateName] = [];
providersByName[candidateName].push(constructorProvider.provider);
}
for (const name of Object.keys(providersByName)) {
const useIndexedNames = providersByName[name].length > 1;
for (let i = 0; i < providersByName[name].length; i++) {
const indexedName = useIndexedNames ? `${name}-${i + 1}` : name;
this.providerEntriesByName[indexedName] = { provider: providersByName[name][i], name: indexedName };
this.providerEntries.push(this.providerEntriesByName[indexedName]);
this.statusTracker.wrapEventHandler(this.providerEntriesByName[indexedName]);
}
}
Object.freeze(this.providerEntries);
Object.freeze(this.providerEntriesByName);
}
initialize(context) {
return __async(this, null, function* () {
const result = yield Promise.allSettled(
this.providerEntries.map((provider) => {
var _a, _b;
return (_b = (_a = provider.provider).initialize) == null ? void 0 : _b.call(_a, context);
})
);
throwAggregateErrorFromPromiseResults(result, this.providerEntries);
});
}
onClose() {
return __async(this, null, function* () {
const result = yield Promise.allSettled(this.providerEntries.map((provider) => {
var _a, _b;
return (_b = (_a = provider.provider).onClose) == null ? void 0 : _b.call(_a);
}));
throwAggregateErrorFromPromiseResults(result, this.providerEntries);
});
}
resolveBooleanEvaluation(flagKey, defaultValue, context) {
return this.flagResolutionProxy(flagKey, "boolean", defaultValue, context);
}
resolveStringEvaluation(flagKey, defaultValue, context) {
return this.flagResolutionProxy(flagKey, "string", defaultValue, context);
}
resolveNumberEvaluation(flagKey, defaultValue, context) {
return this.flagResolutionProxy(flagKey, "number", defaultValue, context);
}
resolveObjectEvaluation(flagKey, defaultValue, context) {
return this.flagResolutionProxy(flagKey, "object", defaultValue, context);
}
flagResolutionProxy(flagKey, flagType, defaultValue, context) {
return __async(this, null, function* () {
var _a;
const hookContext = this.hookContexts.get(context);
const hookHints = this.hookHints.get(context);
if (!hookContext || !hookHints) {
throw new import_core9.GeneralError("Hook context not available for evaluation");
}
const tasks = [];
for (const providerEntry of this.providerEntries) {
const task = this.evaluateProviderEntry(
flagKey,
flagType,
defaultValue,
providerEntry,
hookContext,
hookHints,
context
);
tasks.push(task);
if (this.evaluationStrategy.runMode === "sequential") {
const [shouldEvaluateNext] = yield task;
if (!shouldEvaluateNext) {
break;
}
}
}
const results = yield Promise.all(tasks);
const resolutions = results.map(([, resolution]) => resolution).filter((r) => Boolean(r));
const finalResult = this.evaluationStrategy.determineFinalResult({ flagKey, flagType }, context, resolutions);
if ((_a = finalResult.errors) == null ? void 0 : _a.length) {
throw constructAggregateError(finalResult.errors);
}
if (!finalResult.details) {
throw new import_core9.GeneralError("No result was returned from any provider");
}
return finalResult.details;
});
}
evaluateProviderEntry(flagKey, flagType, defaultValue, providerEntry, hookContext, hookHints, context) {
return __async(this, null, function* () {
let evaluationResult = void 0;
const provider = providerEntry.provider;
const strategyContext = {
flagKey,
flagType,
provider,
providerName: providerEntry.name,
providerStatus: this.statusTracker.providerStatus(providerEntry.name)
};
if (!this.evaluationStrategy.shouldEvaluateThisProvider(strategyContext, context)) {
return [true, null];
}
let resolution;
try {
evaluationResult = yield this.evaluateProviderAndHooks(flagKey, defaultValue, provider, hookContext, hookHints);
resolution = {
details: evaluationResult,
provider,
providerName: providerEntry.name
};
} catch (error) {
resolution = {
thrownError: error,
provider,
providerName: providerEntry.name
};
}
return [
this.evaluationStrategy.runMode === "sequential" ? this.evaluationStrategy.shouldEvaluateNextProvider(strategyContext, context, resolution) : true,
resolution
];
});
}
evaluateProviderAndHooks(flagKey, defaultValue, provider, hookContext, hookHints) {
return __async(this, null, function* () {
var _a;
let providerContext = void 0;
let evaluationDetails;
const hookContextCopy = __spreadProps(__spreadValues({}, hookContext), { context: __spreadValues({}, hookContext.context) });
try {
providerContext = yield this.hookExecutor.beforeHooks(provider.hooks, hookContextCopy, hookHints);
const resolutionDetails = yield this.callProviderResolve(
provider,
flagKey,
defaultValue,
providerContext || {}
);
evaluationDetails = __spreadProps(__spreadValues({}, resolutionDetails), {
flagMetadata: Object.freeze((_a = resolutionDetails.flagMetadata) != null ? _a : {}),
flagKey
});
yield this.hookExecutor.afterHooks(provider.hooks, hookContextCopy, evaluationDetails, hookHints);
} catch (error) {
yield this.hookExecutor.errorHooks(provider.hooks, hookContextCopy, error, hookHints);
evaluationDetails = this.getErrorEvaluationDetails(flagKey, defaultValue, error);
}
yield this.hookExecutor.finallyHooks(provider.hooks, hookContextCopy, evaluationDetails, hookHints);
return evaluationDetails;
});
}
callProviderResolve(provider, flagKey, defaultValue, context) {
return __async(this, null, function* () {
switch (typeof defaultValue) {
case "string":
return yield provider.resolveStringEvaluation(flagKey, defaultValue, context, this.logger);
case "number":
return yield provider.resolveNumberEvaluation(flagKey, defaultValue, context, this.logger);
case "object":
return yield provider.resolveObjectEvaluation(flagKey, defaultValue, context, this.logger);
case "boolean":
return yield provider.resolveBooleanEvaluation(flagKey, defaultValue, context, this.logger);
default:
throw new import_core9.GeneralError("Invalid flag evaluation type");
}
});
}
get hooks() {
return [
{
before: (hookContext, hints) => __async(this, null, function* () {
this.hookContexts.set(hookContext.context, hookContext);
this.hookHints.set(hookContext.context, hints != null ? hints : {});
return hookContext.context;
})
}
];
}
track(trackingEventName, context, trackingEventDetails) {
var _a, _b;
for (const providerEntry of this.providerEntries) {
if (!providerEntry.provider.track) {
continue;
}
const strategyContext = {
provider: providerEntry.provider,
providerName: providerEntry.name,
providerStatus: this.statusTracker.providerStatus(providerEntry.name)
};
if (this.evaluationStrategy.shouldTrackWithThisProvider(
strategyContext,
context,
trackingEventName,
trackingEventDetails
)) {
try {
(_b = (_a = providerEntry.provider).track) == null ? void 0 : _b.call(_a, trackingEventName, context, trackingEventDetails);
} catch (error) {
this.logger.error(
`Error tracking event "${trackingEventName}" with provider "${providerEntry.name}":`,
error
);
}
}
}
}
getErrorEvaluationDetails(flagKey, defaultValue, err, flagMetadata = {}) {
const errorMessage = err == null ? void 0 : err.message;
const errorCode = (err == null ? void 0 : err.code) || import_core9.ErrorCode.GENERAL;
return {
errorCode,
errorMessage,
value: defaultValue,
reason: import_core9.StandardResolutionReasons.ERROR,
flagMetadata: Object.freeze(flagMetadata),
flagKey
};
}
};
// src/open-feature.ts
var import_core11 = require("@openfeature/core");
// src/client/internal/open-feature-client.ts
var import_core4 = require("@openfeature/core");
var import_core10 = require("@openfeature/core");
var OpenFeatureClient = class {

@@ -234,3 +787,3 @@ constructor(providerAccessor, providerStatusAccessor, emitterAccessor, apiContextAccessor, apiHooksAccessor, transactionContextAccessor, globalLogger, options, context = {}) {

this.emitterAccessor().addHandler(eventType, handler);
const shouldRunNow = (0, import_core4.statusMatchesEvent)(eventType, this._providerStatus);
const shouldRunNow = (0, import_core10.statusMatchesEvent)(eventType, this._providerStatus);
if (shouldRunNow) {

@@ -260,3 +813,3 @@ try {

setLogger(logger) {
this._clientLogger = new import_core4.SafeLogger(logger);
this._clientLogger = new import_core10.SafeLogger(logger);
return this;

@@ -371,3 +924,3 @@ }

logger: this._logger,
hookData: new import_core4.MapHookData()
hookData: new import_core10.MapHookData()
})

@@ -385,3 +938,3 @@ );

if (resolutionDetails.errorCode) {
const err = (0, import_core4.instantiateErrorByErrorCode)(resolutionDetails.errorCode, resolutionDetails.errorMessage);
const err = (0, import_core10.instantiateErrorByErrorCode)(resolutionDetails.errorCode, resolutionDetails.errorMessage);
yield this.errorHooks(allHooksReversed, hookContexts, err, options);

@@ -477,5 +1030,5 @@ evaluationDetails = this.getErrorEvaluationDetails(flagKey, defaultValue, err, resolutionDetails.flagMetadata);

if (this.providerStatus === import_core.ServerProviderStatus.NOT_READY) {
throw new import_core4.ProviderNotReadyError("provider has not yet initialized");
throw new import_core10.ProviderNotReadyError("provider has not yet initialized");
} else if (this.providerStatus === import_core.ServerProviderStatus.FATAL) {
throw new import_core4.ProviderFatalError("provider is in an irrecoverable error state");
throw new import_core10.ProviderFatalError("provider is in an irrecoverable error state");
}

@@ -485,3 +1038,3 @@ }

const errorMessage = err == null ? void 0 : err.message;
const errorCode = (err == null ? void 0 : err.code) || import_core4.ErrorCode.GENERAL;
const errorCode = (err == null ? void 0 : err.code) || import_core10.ErrorCode.GENERAL;
return {

@@ -491,3 +1044,3 @@ errorCode,

value: defaultValue,
reason: import_core4.StandardResolutionReasons.ERROR,
reason: import_core10.StandardResolutionReasons.ERROR,
flagMetadata: Object.freeze(flagMetadata),

@@ -499,19 +1052,2 @@ flagKey

// src/events/open-feature-event-emitter.ts
var import_core5 = require("@openfeature/core");
var import_node_events = require("node:events");
var OpenFeatureEventEmitter = class extends import_core5.GenericEventEmitter {
constructor() {
super();
this.eventEmitter = new import_node_events.EventEmitter({ captureRejections: true });
this.eventEmitter.on("error", (err) => {
var _a;
(_a = this._logger) == null ? void 0 : _a.error("Error running event handler:", err);
});
}
};
// src/events/events.ts
var import_core6 = require("@openfeature/core");
// src/transaction-context/no-op-transaction-context-propagator.ts

@@ -546,3 +1082,3 @@ var NoopTransactionContextPropagator = class {

var _globalThis = globalThis;
var OpenFeatureAPI = class _OpenFeatureAPI extends import_core7.OpenFeatureCommonAPI {
var OpenFeatureAPI = class _OpenFeatureAPI extends import_core11.OpenFeatureCommonAPI {
constructor() {

@@ -552,3 +1088,3 @@ super("server");

this._apiEmitter = new OpenFeatureEventEmitter();
this._defaultProvider = new import_core7.ProviderWrapper(
this._defaultProvider = new import_core11.ProviderWrapper(
NOOP_PROVIDER,

@@ -585,4 +1121,4 @@ import_core.ServerProviderStatus.NOT_READY,

return __async(this, null, function* () {
const domain = (0, import_core7.stringOrUndefined)(domainOrProvider);
const provider = domain ? (0, import_core7.objectOrUndefined)(providerOrUndefined) : (0, import_core7.objectOrUndefined)(domainOrProvider);
const domain = (0, import_core11.stringOrUndefined)(domainOrProvider);
const provider = domain ? (0, import_core11.objectOrUndefined)(providerOrUndefined) : (0, import_core11.objectOrUndefined)(domainOrProvider);
yield this.setAwaitableProvider(domain, provider);

@@ -592,4 +1128,4 @@ });

setProvider(clientOrProvider, providerOrUndefined) {
const domain = (0, import_core7.stringOrUndefined)(clientOrProvider);
const provider = domain ? (0, import_core7.objectOrUndefined)(providerOrUndefined) : (0, import_core7.objectOrUndefined)(clientOrProvider);
const domain = (0, import_core11.stringOrUndefined)(clientOrProvider);
const provider = domain ? (0, import_core11.objectOrUndefined)(providerOrUndefined) : (0, import_core11.objectOrUndefined)(clientOrProvider);
const maybePromise = this.setAwaitableProvider(domain, provider);

@@ -613,5 +1149,5 @@ Promise.resolve(maybePromise).catch((err) => {

var _a, _b;
const domain = (0, import_core7.stringOrUndefined)(domainOrContext);
const version = (0, import_core7.stringOrUndefined)(versionOrContext);
const context = (_b = (_a = (0, import_core7.objectOrUndefined)(domainOrContext)) != null ? _a : (0, import_core7.objectOrUndefined)(versionOrContext)) != null ? _b : (0, import_core7.objectOrUndefined)(contextOrUndefined);
const domain = (0, import_core11.stringOrUndefined)(domainOrContext);
const version = (0, import_core11.stringOrUndefined)(versionOrContext);
const context = (_b = (_a = (0, import_core11.objectOrUndefined)(domainOrContext)) != null ? _a : (0, import_core11.objectOrUndefined)(versionOrContext)) != null ? _b : (0, import_core11.objectOrUndefined)(contextOrUndefined);
return new OpenFeatureClient(

@@ -667,4 +1203,11 @@ () => this.getProviderForClient(domain),

0 && (module.exports = {
AggregateError,
AsyncLocalStorageTransactionContextPropagator,
BaseEvaluationStrategy,
ComparisonStrategy,
ErrorWithCode,
FirstMatchStrategy,
FirstSuccessfulStrategy,
InMemoryProvider,
MultiProvider,
NOOP_PROVIDER,

@@ -677,4 +1220,6 @@ NOOP_TRANSACTION_CONTEXT_PROPAGATOR,

ProviderStatus,
constructAggregateError,
throwAggregateErrorFromPromiseResults,
...require("@openfeature/core")
});
//# sourceMappingURL=index.js.map

@@ -173,2 +173,546 @@ var __defProp = Object.defineProperty;

// src/provider/multi-provider/multi-provider.ts
import { DefaultLogger, ErrorCode as ErrorCode3, GeneralError as GeneralError4, StandardResolutionReasons as StandardResolutionReasons2 } from "@openfeature/core";
// src/events/open-feature-event-emitter.ts
import { GenericEventEmitter } from "@openfeature/core";
import { EventEmitter } from "node:events";
var OpenFeatureEventEmitter = class extends GenericEventEmitter {
constructor() {
super();
this.eventEmitter = new EventEmitter({ captureRejections: true });
this.eventEmitter.on("error", (err) => {
var _a;
(_a = this._logger) == null ? void 0 : _a.error("Error running event handler:", err);
});
}
};
// src/provider/multi-provider/errors.ts
import { GeneralError as GeneralError2, OpenFeatureError as OpenFeatureError3 } from "@openfeature/core";
var ErrorWithCode = class extends OpenFeatureError3 {
constructor(code, message) {
super(message);
this.code = code;
}
};
var AggregateError = class extends GeneralError2 {
constructor(message, originalErrors) {
super(message);
this.originalErrors = originalErrors;
}
};
var constructAggregateError = (providerErrors) => {
const errorsWithSource = providerErrors.map(({ providerName, error }) => {
return { source: providerName, error };
}).flat();
return new AggregateError(
`Provider errors occurred: ${errorsWithSource[0].source}: ${errorsWithSource[0].error}`,
errorsWithSource
);
};
var throwAggregateErrorFromPromiseResults = (result, providerEntries) => {
const errors = result.map((r, i) => {
if (r.status === "rejected") {
return { error: r.reason, providerName: providerEntries[i].name };
}
return null;
}).filter((val) => Boolean(val));
if (errors.length) {
throw constructAggregateError(errors);
}
};
// src/provider/multi-provider/hook-executor.ts
var HookExecutor = class {
constructor(logger) {
this.logger = logger;
}
beforeHooks(hooks, hookContext, hints) {
return __async(this, null, function* () {
var _a;
for (const hook of hooks != null ? hooks : []) {
Object.freeze(hookContext);
Object.assign(hookContext.context, __spreadValues({}, yield (_a = hook == null ? void 0 : hook.before) == null ? void 0 : _a.call(hook, hookContext, Object.freeze(hints))));
}
return Object.freeze(hookContext.context);
});
}
afterHooks(hooks, hookContext, evaluationDetails, hints) {
return __async(this, null, function* () {
var _a;
for (const hook of hooks != null ? hooks : []) {
yield (_a = hook == null ? void 0 : hook.after) == null ? void 0 : _a.call(hook, hookContext, evaluationDetails, hints);
}
});
}
errorHooks(hooks, hookContext, err, hints) {
return __async(this, null, function* () {
var _a;
for (const hook of hooks != null ? hooks : []) {
try {
yield (_a = hook == null ? void 0 : hook.error) == null ? void 0 : _a.call(hook, hookContext, err, hints);
} catch (err2) {
this.logger.error(`Unhandled error during 'error' hook: ${err2}`);
if (err2 instanceof Error) {
this.logger.error(err2.stack);
}
this.logger.error(err2 == null ? void 0 : err2.stack);
}
}
});
}
finallyHooks(hooks, hookContext, evaluationDetails, hints) {
return __async(this, null, function* () {
var _a;
for (const hook of hooks != null ? hooks : []) {
try {
yield (_a = hook == null ? void 0 : hook.finally) == null ? void 0 : _a.call(hook, hookContext, evaluationDetails, hints);
} catch (err) {
this.logger.error(`Unhandled error during 'finally' hook: ${err}`);
if (err instanceof Error) {
this.logger.error(err.stack);
}
this.logger.error(err == null ? void 0 : err.stack);
}
}
});
}
};
// src/events/events.ts
import { ServerProviderEvents } from "@openfeature/core";
// src/provider/multi-provider/status-tracker.ts
var StatusTracker = class {
constructor(events) {
this.events = events;
this.providerStatuses = {};
}
wrapEventHandler(providerEntry) {
var _a, _b, _c, _d;
const provider = providerEntry.provider;
(_a = provider.events) == null ? void 0 : _a.addHandler(ServerProviderEvents.Error, (details) => {
this.changeProviderStatus(providerEntry.name, ServerProviderStatus.ERROR, details);
});
(_b = provider.events) == null ? void 0 : _b.addHandler(ServerProviderEvents.Stale, (details) => {
this.changeProviderStatus(providerEntry.name, ServerProviderStatus.STALE, details);
});
(_c = provider.events) == null ? void 0 : _c.addHandler(ServerProviderEvents.ConfigurationChanged, (details) => {
this.events.emit(ServerProviderEvents.ConfigurationChanged, details);
});
(_d = provider.events) == null ? void 0 : _d.addHandler(ServerProviderEvents.Ready, (details) => {
this.changeProviderStatus(providerEntry.name, ServerProviderStatus.READY, details);
});
}
providerStatus(name) {
return this.providerStatuses[name];
}
getStatusFromProviderStatuses() {
const statuses = Object.values(this.providerStatuses);
if (statuses.includes(ServerProviderStatus.FATAL)) {
return ServerProviderStatus.FATAL;
} else if (statuses.includes(ServerProviderStatus.NOT_READY)) {
return ServerProviderStatus.NOT_READY;
} else if (statuses.includes(ServerProviderStatus.ERROR)) {
return ServerProviderStatus.ERROR;
} else if (statuses.includes(ServerProviderStatus.STALE)) {
return ServerProviderStatus.STALE;
}
return ServerProviderStatus.READY;
}
changeProviderStatus(name, status, details) {
const currentStatus = this.getStatusFromProviderStatuses();
this.providerStatuses[name] = status;
const newStatus = this.getStatusFromProviderStatuses();
if (currentStatus !== newStatus) {
if (newStatus === ServerProviderStatus.FATAL || newStatus === ServerProviderStatus.ERROR) {
this.events.emit(ServerProviderEvents.Error, details);
} else if (newStatus === ServerProviderStatus.STALE) {
this.events.emit(ServerProviderEvents.Stale, details);
} else if (newStatus === ServerProviderStatus.READY) {
this.events.emit(ServerProviderEvents.Ready, details);
}
}
}
};
// src/provider/multi-provider/strategies/base-evaluation-strategy.ts
var BaseEvaluationStrategy = class {
constructor() {
this.runMode = "sequential";
}
shouldEvaluateThisProvider(strategyContext, _evalContext) {
if (strategyContext.providerStatus === ServerProviderStatus.NOT_READY || strategyContext.providerStatus === ServerProviderStatus.FATAL) {
return false;
}
return true;
}
shouldEvaluateNextProvider(_strategyContext, _context, _result) {
return true;
}
shouldTrackWithThisProvider(strategyContext, _context, _trackingEventName, _trackingEventDetails) {
if (strategyContext.providerStatus === ServerProviderStatus.NOT_READY || strategyContext.providerStatus === ServerProviderStatus.FATAL) {
return false;
}
return true;
}
hasError(resolution) {
return "thrownError" in resolution || !!resolution.details.errorCode;
}
hasErrorWithCode(resolution, code) {
var _a;
return "thrownError" in resolution ? ((_a = resolution.thrownError) == null ? void 0 : _a.code) === code : resolution.details.errorCode === code;
}
collectProviderErrors(resolutions) {
var _a;
const errors = [];
for (const resolution of resolutions) {
if ("thrownError" in resolution) {
errors.push({ providerName: resolution.providerName, error: resolution.thrownError });
} else if (resolution.details.errorCode) {
errors.push({
providerName: resolution.providerName,
error: new ErrorWithCode(resolution.details.errorCode, (_a = resolution.details.errorMessage) != null ? _a : "unknown error")
});
}
}
return { errors };
}
resolutionToFinalResult(resolution) {
return { details: resolution.details, provider: resolution.provider, providerName: resolution.providerName };
}
};
// src/provider/multi-provider/strategies/first-match-strategy.ts
import { ErrorCode as ErrorCode2 } from "@openfeature/core";
var FirstMatchStrategy = class extends BaseEvaluationStrategy {
shouldEvaluateNextProvider(strategyContext, context, result) {
if (this.hasErrorWithCode(result, ErrorCode2.FLAG_NOT_FOUND)) {
return true;
}
if (this.hasError(result)) {
return false;
}
return false;
}
determineFinalResult(strategyContext, context, resolutions) {
const finalResolution = resolutions[resolutions.length - 1];
if (this.hasError(finalResolution)) {
return this.collectProviderErrors(resolutions);
}
return this.resolutionToFinalResult(finalResolution);
}
};
// src/provider/multi-provider/strategies/first-successful-strategy.ts
var FirstSuccessfulStrategy = class extends BaseEvaluationStrategy {
shouldEvaluateNextProvider(strategyContext, context, result) {
return this.hasError(result);
}
determineFinalResult(strategyContext, context, resolutions) {
const finalResolution = resolutions[resolutions.length - 1];
if (this.hasError(finalResolution)) {
return this.collectProviderErrors(resolutions);
}
return this.resolutionToFinalResult(finalResolution);
}
};
// src/provider/multi-provider/strategies/comparison-strategy.ts
import { GeneralError as GeneralError3 } from "@openfeature/core";
var ComparisonStrategy = class extends BaseEvaluationStrategy {
constructor(fallbackProvider, onMismatch) {
super();
this.fallbackProvider = fallbackProvider;
this.onMismatch = onMismatch;
this.runMode = "parallel";
}
determineFinalResult(strategyContext, context, resolutions) {
var _a;
let value;
let fallbackResolution;
let finalResolution;
let mismatch = false;
for (const [i, resolution] of resolutions.entries()) {
if (this.hasError(resolution)) {
return this.collectProviderErrors(resolutions);
}
if (resolution.provider === this.fallbackProvider) {
fallbackResolution = resolution;
}
if (i === 0) {
finalResolution = resolution;
}
if (typeof value !== "undefined" && value !== resolution.details.value) {
mismatch = true;
} else {
value = resolution.details.value;
}
}
if (!fallbackResolution) {
throw new GeneralError3("Fallback provider not found in resolution results");
}
if (!finalResolution) {
throw new GeneralError3("Final resolution not found in resolution results");
}
if (mismatch) {
(_a = this.onMismatch) == null ? void 0 : _a.call(this, resolutions);
return {
details: fallbackResolution.details,
provider: fallbackResolution.provider
};
}
return this.resolutionToFinalResult(finalResolution);
}
};
// src/provider/multi-provider/multi-provider.ts
var MultiProvider = class _MultiProvider {
constructor(constructorProviders, evaluationStrategy = new FirstMatchStrategy(), logger = new DefaultLogger()) {
this.constructorProviders = constructorProviders;
this.evaluationStrategy = evaluationStrategy;
this.logger = logger;
this.runsOn = "server";
this.events = new OpenFeatureEventEmitter();
this.hookContexts = /* @__PURE__ */ new WeakMap();
this.hookHints = /* @__PURE__ */ new WeakMap();
this.providerEntries = [];
this.providerEntriesByName = {};
this.statusTracker = new StatusTracker(this.events);
this.hookExecutor = new HookExecutor(this.logger);
this.registerProviders(constructorProviders);
const aggregateMetadata = Object.keys(this.providerEntriesByName).reduce((acc, name) => {
return __spreadProps(__spreadValues({}, acc), { [name]: this.providerEntriesByName[name].provider.metadata });
}, {});
this.metadata = __spreadProps(__spreadValues({}, aggregateMetadata), {
name: _MultiProvider.name
});
}
registerProviders(constructorProviders) {
var _a, _b;
const providersByName = {};
for (const constructorProvider of constructorProviders) {
const providerName = constructorProvider.provider.metadata.name;
const candidateName = (_a = constructorProvider.name) != null ? _a : providerName;
if (constructorProvider.name && providersByName[constructorProvider.name]) {
throw new Error("Provider names must be unique");
}
(_b = providersByName[candidateName]) != null ? _b : providersByName[candidateName] = [];
providersByName[candidateName].push(constructorProvider.provider);
}
for (const name of Object.keys(providersByName)) {
const useIndexedNames = providersByName[name].length > 1;
for (let i = 0; i < providersByName[name].length; i++) {
const indexedName = useIndexedNames ? `${name}-${i + 1}` : name;
this.providerEntriesByName[indexedName] = { provider: providersByName[name][i], name: indexedName };
this.providerEntries.push(this.providerEntriesByName[indexedName]);
this.statusTracker.wrapEventHandler(this.providerEntriesByName[indexedName]);
}
}
Object.freeze(this.providerEntries);
Object.freeze(this.providerEntriesByName);
}
initialize(context) {
return __async(this, null, function* () {
const result = yield Promise.allSettled(
this.providerEntries.map((provider) => {
var _a, _b;
return (_b = (_a = provider.provider).initialize) == null ? void 0 : _b.call(_a, context);
})
);
throwAggregateErrorFromPromiseResults(result, this.providerEntries);
});
}
onClose() {
return __async(this, null, function* () {
const result = yield Promise.allSettled(this.providerEntries.map((provider) => {
var _a, _b;
return (_b = (_a = provider.provider).onClose) == null ? void 0 : _b.call(_a);
}));
throwAggregateErrorFromPromiseResults(result, this.providerEntries);
});
}
resolveBooleanEvaluation(flagKey, defaultValue, context) {
return this.flagResolutionProxy(flagKey, "boolean", defaultValue, context);
}
resolveStringEvaluation(flagKey, defaultValue, context) {
return this.flagResolutionProxy(flagKey, "string", defaultValue, context);
}
resolveNumberEvaluation(flagKey, defaultValue, context) {
return this.flagResolutionProxy(flagKey, "number", defaultValue, context);
}
resolveObjectEvaluation(flagKey, defaultValue, context) {
return this.flagResolutionProxy(flagKey, "object", defaultValue, context);
}
flagResolutionProxy(flagKey, flagType, defaultValue, context) {
return __async(this, null, function* () {
var _a;
const hookContext = this.hookContexts.get(context);
const hookHints = this.hookHints.get(context);
if (!hookContext || !hookHints) {
throw new GeneralError4("Hook context not available for evaluation");
}
const tasks = [];
for (const providerEntry of this.providerEntries) {
const task = this.evaluateProviderEntry(
flagKey,
flagType,
defaultValue,
providerEntry,
hookContext,
hookHints,
context
);
tasks.push(task);
if (this.evaluationStrategy.runMode === "sequential") {
const [shouldEvaluateNext] = yield task;
if (!shouldEvaluateNext) {
break;
}
}
}
const results = yield Promise.all(tasks);
const resolutions = results.map(([, resolution]) => resolution).filter((r) => Boolean(r));
const finalResult = this.evaluationStrategy.determineFinalResult({ flagKey, flagType }, context, resolutions);
if ((_a = finalResult.errors) == null ? void 0 : _a.length) {
throw constructAggregateError(finalResult.errors);
}
if (!finalResult.details) {
throw new GeneralError4("No result was returned from any provider");
}
return finalResult.details;
});
}
evaluateProviderEntry(flagKey, flagType, defaultValue, providerEntry, hookContext, hookHints, context) {
return __async(this, null, function* () {
let evaluationResult = void 0;
const provider = providerEntry.provider;
const strategyContext = {
flagKey,
flagType,
provider,
providerName: providerEntry.name,
providerStatus: this.statusTracker.providerStatus(providerEntry.name)
};
if (!this.evaluationStrategy.shouldEvaluateThisProvider(strategyContext, context)) {
return [true, null];
}
let resolution;
try {
evaluationResult = yield this.evaluateProviderAndHooks(flagKey, defaultValue, provider, hookContext, hookHints);
resolution = {
details: evaluationResult,
provider,
providerName: providerEntry.name
};
} catch (error) {
resolution = {
thrownError: error,
provider,
providerName: providerEntry.name
};
}
return [
this.evaluationStrategy.runMode === "sequential" ? this.evaluationStrategy.shouldEvaluateNextProvider(strategyContext, context, resolution) : true,
resolution
];
});
}
evaluateProviderAndHooks(flagKey, defaultValue, provider, hookContext, hookHints) {
return __async(this, null, function* () {
var _a;
let providerContext = void 0;
let evaluationDetails;
const hookContextCopy = __spreadProps(__spreadValues({}, hookContext), { context: __spreadValues({}, hookContext.context) });
try {
providerContext = yield this.hookExecutor.beforeHooks(provider.hooks, hookContextCopy, hookHints);
const resolutionDetails = yield this.callProviderResolve(
provider,
flagKey,
defaultValue,
providerContext || {}
);
evaluationDetails = __spreadProps(__spreadValues({}, resolutionDetails), {
flagMetadata: Object.freeze((_a = resolutionDetails.flagMetadata) != null ? _a : {}),
flagKey
});
yield this.hookExecutor.afterHooks(provider.hooks, hookContextCopy, evaluationDetails, hookHints);
} catch (error) {
yield this.hookExecutor.errorHooks(provider.hooks, hookContextCopy, error, hookHints);
evaluationDetails = this.getErrorEvaluationDetails(flagKey, defaultValue, error);
}
yield this.hookExecutor.finallyHooks(provider.hooks, hookContextCopy, evaluationDetails, hookHints);
return evaluationDetails;
});
}
callProviderResolve(provider, flagKey, defaultValue, context) {
return __async(this, null, function* () {
switch (typeof defaultValue) {
case "string":
return yield provider.resolveStringEvaluation(flagKey, defaultValue, context, this.logger);
case "number":
return yield provider.resolveNumberEvaluation(flagKey, defaultValue, context, this.logger);
case "object":
return yield provider.resolveObjectEvaluation(flagKey, defaultValue, context, this.logger);
case "boolean":
return yield provider.resolveBooleanEvaluation(flagKey, defaultValue, context, this.logger);
default:
throw new GeneralError4("Invalid flag evaluation type");
}
});
}
get hooks() {
return [
{
before: (hookContext, hints) => __async(this, null, function* () {
this.hookContexts.set(hookContext.context, hookContext);
this.hookHints.set(hookContext.context, hints != null ? hints : {});
return hookContext.context;
})
}
];
}
track(trackingEventName, context, trackingEventDetails) {
var _a, _b;
for (const providerEntry of this.providerEntries) {
if (!providerEntry.provider.track) {
continue;
}
const strategyContext = {
provider: providerEntry.provider,
providerName: providerEntry.name,
providerStatus: this.statusTracker.providerStatus(providerEntry.name)
};
if (this.evaluationStrategy.shouldTrackWithThisProvider(
strategyContext,
context,
trackingEventName,
trackingEventDetails
)) {
try {
(_b = (_a = providerEntry.provider).track) == null ? void 0 : _b.call(_a, trackingEventName, context, trackingEventDetails);
} catch (error) {
this.logger.error(
`Error tracking event "${trackingEventName}" with provider "${providerEntry.name}":`,
error
);
}
}
}
}
getErrorEvaluationDetails(flagKey, defaultValue, err, flagMetadata = {}) {
const errorMessage = err == null ? void 0 : err.message;
const errorCode = (err == null ? void 0 : err.code) || ErrorCode3.GENERAL;
return {
errorCode,
errorMessage,
value: defaultValue,
reason: StandardResolutionReasons2.ERROR,
flagMetadata: Object.freeze(flagMetadata),
flagKey
};
}
};
// src/open-feature.ts

@@ -184,7 +728,7 @@ import {

import {
ErrorCode as ErrorCode2,
ErrorCode as ErrorCode4,
ProviderFatalError,
ProviderNotReadyError,
SafeLogger,
StandardResolutionReasons as StandardResolutionReasons2,
StandardResolutionReasons as StandardResolutionReasons3,
instantiateErrorByErrorCode,

@@ -469,3 +1013,3 @@ statusMatchesEvent,

const errorMessage = err == null ? void 0 : err.message;
const errorCode = (err == null ? void 0 : err.code) || ErrorCode2.GENERAL;
const errorCode = (err == null ? void 0 : err.code) || ErrorCode4.GENERAL;
return {

@@ -475,3 +1019,3 @@ errorCode,

value: defaultValue,
reason: StandardResolutionReasons2.ERROR,
reason: StandardResolutionReasons3.ERROR,
flagMetadata: Object.freeze(flagMetadata),

@@ -483,19 +1027,2 @@ flagKey

// src/events/open-feature-event-emitter.ts
import { GenericEventEmitter } from "@openfeature/core";
import { EventEmitter } from "node:events";
var OpenFeatureEventEmitter = class extends GenericEventEmitter {
constructor() {
super();
this.eventEmitter = new EventEmitter({ captureRejections: true });
this.eventEmitter.on("error", (err) => {
var _a;
(_a = this._logger) == null ? void 0 : _a.error("Error running event handler:", err);
});
}
};
// src/events/events.ts
import { ServerProviderEvents } from "@openfeature/core";
// src/transaction-context/no-op-transaction-context-propagator.ts

@@ -645,4 +1172,11 @@ var NoopTransactionContextPropagator = class {

export {
AggregateError,
AsyncLocalStorageTransactionContextPropagator,
BaseEvaluationStrategy,
ComparisonStrategy,
ErrorWithCode,
FirstMatchStrategy,
FirstSuccessfulStrategy,
InMemoryProvider,
MultiProvider,
NOOP_PROVIDER,

@@ -654,4 +1188,6 @@ NOOP_TRANSACTION_CONTEXT_PROPAGATOR,

ServerProviderEvents as ProviderEvents,
ServerProviderStatus as ProviderStatus
ServerProviderStatus as ProviderStatus,
constructAggregateError,
throwAggregateErrorFromPromiseResults
};
//# sourceMappingURL=index.js.map

@@ -1,2 +0,2 @@

import { BaseHook, FlagValue, EvaluationContext, HookHints, EvaluationDetails, JsonValue, CommonProvider, ServerProviderStatus, Logger, ResolutionDetails, GenericEventEmitter, ServerProviderEvents, TrackingEventDetails, EvaluationLifeCycle, ManageContext, ManageLogger, Eventing, ClientMetadata, OpenFeatureCommonAPI, ProviderWrapper } from '@openfeature/core';
import { BaseHook, FlagValue, EvaluationContext, HookHints, EvaluationDetails, JsonValue, CommonProvider, ServerProviderStatus, Logger, ResolutionDetails, GenericEventEmitter, ServerProviderEvents, FlagValueType, TrackingEventDetails, ErrorCode, ProviderMetadata, OpenFeatureError, GeneralError, EvaluationLifeCycle, ManageContext, ManageLogger, Eventing, ClientMetadata, OpenFeatureCommonAPI, ProviderWrapper } from '@openfeature/core';
export * from '@openfeature/core';

@@ -216,2 +216,145 @@ export { ServerProviderEvents as ProviderEvents, ServerProviderStatus as ProviderStatus } from '@openfeature/core';

type StrategyEvaluationContext = {
flagKey: string;
flagType: FlagValueType;
};
type StrategyProviderContext = {
provider: Provider;
providerName: string;
providerStatus: ServerProviderStatus;
};
type StrategyPerProviderContext = StrategyEvaluationContext & StrategyProviderContext;
type ProviderResolutionResultBase = {
provider: Provider;
providerName: string;
};
type ProviderResolutionSuccessResult<T extends FlagValue> = ProviderResolutionResultBase & {
details: ResolutionDetails<T>;
};
type ProviderResolutionErrorResult = ProviderResolutionResultBase & {
thrownError: unknown;
};
type ProviderResolutionResult<T extends FlagValue> = ProviderResolutionSuccessResult<T> | ProviderResolutionErrorResult;
type FinalResult<T extends FlagValue> = {
details?: ResolutionDetails<T>;
provider?: Provider;
providerName?: string;
errors?: {
providerName: string;
error: unknown;
}[];
};
/**
* Base strategy to inherit from. Not directly usable, as strategies must implement the "determineResult" method
* Contains default implementations for `shouldEvaluateThisProvider` and `shouldEvaluateNextProvider`
*/
declare abstract class BaseEvaluationStrategy {
runMode: 'parallel' | 'sequential';
shouldEvaluateThisProvider(strategyContext: StrategyPerProviderContext, _evalContext?: EvaluationContext): boolean;
shouldEvaluateNextProvider<T extends FlagValue>(_strategyContext?: StrategyPerProviderContext, _context?: EvaluationContext, _result?: ProviderResolutionResult<T>): boolean;
shouldTrackWithThisProvider(strategyContext: StrategyProviderContext, _context?: EvaluationContext, _trackingEventName?: string, _trackingEventDetails?: TrackingEventDetails): boolean;
abstract determineFinalResult<T extends FlagValue>(strategyContext: StrategyEvaluationContext, context: EvaluationContext, resolutions: ProviderResolutionResult<T>[]): FinalResult<T>;
protected hasError(resolution: ProviderResolutionResult<FlagValue>): resolution is ProviderResolutionErrorResult | (ProviderResolutionSuccessResult<FlagValue> & {
details: ResolutionDetails<FlagValue> & {
errorCode: ErrorCode;
};
});
protected hasErrorWithCode(resolution: ProviderResolutionResult<FlagValue>, code: ErrorCode): boolean;
protected collectProviderErrors<T extends FlagValue>(resolutions: ProviderResolutionResult<T>[]): FinalResult<T>;
protected resolutionToFinalResult<T extends FlagValue>(resolution: ProviderResolutionSuccessResult<T>): {
details: ResolutionDetails<T>;
provider: Provider;
providerName: string;
};
}
/**
* Return the first result that did not indicate "flag not found".
* If any provider in the course of evaluation returns or throws an error, throw that error
*/
declare class FirstMatchStrategy extends BaseEvaluationStrategy {
shouldEvaluateNextProvider<T extends FlagValue>(strategyContext: StrategyPerProviderContext, context: EvaluationContext, result: ProviderResolutionResult<T>): boolean;
determineFinalResult<T extends FlagValue>(strategyContext: StrategyPerProviderContext, context: EvaluationContext, resolutions: ProviderResolutionResult<T>[]): FinalResult<T>;
}
/**
* Return the first result that did NOT result in an error
* If any provider in the course of evaluation returns or throws an error, ignore it as long as there is a successful result
* If there is no successful result, throw all errors
*/
declare class FirstSuccessfulStrategy extends BaseEvaluationStrategy {
shouldEvaluateNextProvider<T extends FlagValue>(strategyContext: StrategyPerProviderContext, context: EvaluationContext, result: ProviderResolutionResult<T>): boolean;
determineFinalResult<T extends FlagValue>(strategyContext: StrategyPerProviderContext, context: EvaluationContext, resolutions: ProviderResolutionResult<T>[]): FinalResult<T>;
}
/**
* Evaluate all providers in parallel and compare the results.
* If the values agree, return the value
* If the values disagree, return the value from the configured "fallback provider" and execute the "onMismatch"
* callback if defined
*/
declare class ComparisonStrategy extends BaseEvaluationStrategy {
private fallbackProvider;
private onMismatch?;
runMode: "parallel";
constructor(fallbackProvider: Provider, onMismatch?: ((resolutions: ProviderResolutionResult<FlagValue>[]) => void) | undefined);
determineFinalResult<T extends FlagValue>(strategyContext: StrategyPerProviderContext, context: EvaluationContext, resolutions: ProviderResolutionResult<T>[]): FinalResult<T>;
}
type ProviderEntryInput = {
provider: Provider;
name?: string;
};
type RegisteredProvider = Required<ProviderEntryInput>;
declare class MultiProvider implements Provider {
readonly constructorProviders: ProviderEntryInput[];
private readonly evaluationStrategy;
private readonly logger;
readonly runsOn = "server";
readonly events: OpenFeatureEventEmitter;
private hookContexts;
private hookHints;
metadata: ProviderMetadata;
providerEntries: RegisteredProvider[];
private providerEntriesByName;
private hookExecutor;
private statusTracker;
constructor(constructorProviders: ProviderEntryInput[], evaluationStrategy?: BaseEvaluationStrategy, logger?: Logger);
private registerProviders;
initialize(context?: EvaluationContext): Promise<void>;
onClose(): Promise<void>;
resolveBooleanEvaluation(flagKey: string, defaultValue: boolean, context: EvaluationContext): Promise<ResolutionDetails<boolean>>;
resolveStringEvaluation(flagKey: string, defaultValue: string, context: EvaluationContext): Promise<ResolutionDetails<string>>;
resolveNumberEvaluation(flagKey: string, defaultValue: number, context: EvaluationContext): Promise<ResolutionDetails<number>>;
resolveObjectEvaluation<U extends JsonValue>(flagKey: string, defaultValue: U, context: EvaluationContext): Promise<ResolutionDetails<U>>;
private flagResolutionProxy;
private evaluateProviderEntry;
private evaluateProviderAndHooks;
private callProviderResolve;
get hooks(): Hook[];
track(trackingEventName: string, context: EvaluationContext, trackingEventDetails: TrackingEventDetails): void;
private getErrorEvaluationDetails;
}
declare class ErrorWithCode extends OpenFeatureError {
code: ErrorCode;
constructor(code: ErrorCode, message: string);
}
declare class AggregateError extends GeneralError {
originalErrors: {
source: string;
error: unknown;
}[];
constructor(message: string, originalErrors: {
source: string;
error: unknown;
}[]);
}
declare const constructAggregateError: (providerErrors: {
error: unknown;
providerName: string;
}[]) => AggregateError;
declare const throwAggregateErrorFromPromiseResults: (result: PromiseSettledResult<unknown>[], providerEntries: RegisteredProvider[]) => void;
interface Tracking {

@@ -421,3 +564,3 @@ /**

export { AsyncLocalStorageTransactionContextPropagator, InMemoryProvider, NOOP_PROVIDER, NOOP_TRANSACTION_CONTEXT_PROPAGATOR, OpenFeature, OpenFeatureAPI, OpenFeatureEventEmitter };
export type { Client, Features, FlagEvaluationOptions, Hook, ManageTransactionContextPropagator, Provider, Tracking, TransactionContext, TransactionContextPropagator };
export { AggregateError, AsyncLocalStorageTransactionContextPropagator, BaseEvaluationStrategy, ComparisonStrategy, ErrorWithCode, FirstMatchStrategy, FirstSuccessfulStrategy, InMemoryProvider, MultiProvider, NOOP_PROVIDER, NOOP_TRANSACTION_CONTEXT_PROPAGATOR, OpenFeature, OpenFeatureAPI, OpenFeatureEventEmitter, constructAggregateError, throwAggregateErrorFromPromiseResults };
export type { Client, Features, FinalResult, FlagEvaluationOptions, Hook, ManageTransactionContextPropagator, Provider, ProviderResolutionErrorResult, ProviderResolutionResult, ProviderResolutionSuccessResult, StrategyEvaluationContext, StrategyPerProviderContext, StrategyProviderContext, Tracking, TransactionContext, TransactionContextPropagator };
+1
-1

@@ -189,3 +189,3 @@ Apache License

Copyright [yyyy] [name of copyright owner]
Copyright 2025 OpenFeature Maintainers

@@ -192,0 +192,0 @@ Licensed under the Apache License, Version 2.0 (the "License");

{
"name": "@openfeature/server-sdk",
"version": "1.19.0",
"version": "1.20.0",
"description": "OpenFeature SDK for JavaScript",

@@ -5,0 +5,0 @@ "main": "./dist/cjs/index.js",

@@ -19,4 +19,4 @@ <!-- markdownlint-disable MD033 -->

<!-- x-release-please-start-version -->
<a href="https://github.com/open-feature/js-sdk/releases/tag/server-sdk-v1.19.0">
<img alt="Release" src="https://img.shields.io/static/v1?label=release&message=v1.19.0&color=blue&style=for-the-badge" />
<a href="https://github.com/open-feature/js-sdk/releases/tag/server-sdk-v1.20.0">
<img alt="Release" src="https://img.shields.io/static/v1?label=release&message=v1.20.0&color=blue&style=for-the-badge" />
</a>

@@ -144,4 +144,80 @@ <!-- x-release-please-end -->

In some situations, it may be beneficial to register multiple providers in the same application.
This is possible using [domains](#domains), which is covered in more details below.
This is possible using [domains](#domains), which is covered in more detail below.
#### Multi-Provider
The Multi-Provider allows you to use multiple underlying providers as sources of flag data for the OpenFeature server SDK. When a flag is being evaluated, the Multi-Provider will consult each underlying provider it is managing in order to determine the final result. Different evaluation strategies can be defined to control which providers get evaluated and which result is used.
The Multi-Provider is a powerful tool for performing migrations between flag providers, or combining multiple providers into a single feature flagging interface. For example:
- **Migration**: Gradually migrate from one provider to another by serving some flags from your old provider and some from your new provider
- **Backup**: Use one provider as a backup for another in case of failures
- **Comparison**: Compare results from multiple providers to validate consistency
- **Hybrid**: Combine multiple providers to leverage different strengths (e.g., one for simple flags, another for complex targeting)
```ts
import { OpenFeature, MultiProvider, FirstMatchStrategy } from '@openfeature/server-sdk';
// Create providers
const primaryProvider = new YourPrimaryProvider();
const backupProvider = new YourBackupProvider();
// Create multi-provider with a strategy
const multiProvider = new MultiProvider(
[primaryProvider, backupProvider],
new FirstMatchStrategy()
);
// Register the multi-provider
await OpenFeature.setProviderAndWait(multiProvider);
// Use as normal
const client = OpenFeature.getClient();
const value = await client.getBooleanValue('my-flag', false);
```
**Available Strategies:**
- `FirstMatchStrategy`: Returns the first successful result from the list of providers
- `ComparisonStrategy`: Compares results from multiple providers and can handle discrepancies
**Migration Example:**
```ts
import { OpenFeature, MultiProvider, FirstMatchStrategy } from '@openfeature/server-sdk';
// During migration, serve some flags from the new provider and fallback to the old one
const newProvider = new NewFlagProvider();
const oldProvider = new OldFlagProvider();
const multiProvider = new MultiProvider(
[newProvider, oldProvider], // New provider is consulted first
new FirstMatchStrategy()
);
await OpenFeature.setProviderAndWait(multiProvider);
```
**Comparison Example:**
```ts
import { OpenFeature, MultiProvider, ComparisonStrategy } from '@openfeature/server-sdk';
// Compare results from two providers for validation
const providerA = new ProviderA();
const providerB = new ProviderB();
const multiProvider = new MultiProvider(
[
{ provider: providerA },
{ provider: providerB }
],
new ComparisonStrategy(providerA, (resolutions) => {
console.warn('Mismatch detected', resolutions);
})
);
await OpenFeature.setProviderAndWait(multiProvider);
```
### Targeting

@@ -148,0 +224,0 @@

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is too big to display