@openfeature/server-sdk 1.13.5 → 1.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -16,8 +16,8 @@
16
16
  <img alt="Specification" src="https://img.shields.io/static/v1?label=specification&message=v0.8.0&color=yellow&style=for-the-badge" />
17
17
  </a>
18
18
  <!-- x-release-please-start-version -->
19
- <a href="https://github.com/open-feature/js-sdk/releases/tag/server-sdk-v1.13.5">
20
- <img alt="Release" src="https://img.shields.io/static/v1?label=release&message=v1.13.5&color=blue&style=for-the-badge" />
19
+ <a href="https://github.com/open-feature/js-sdk/releases/tag/server-sdk-v1.14.0">
20
+ <img alt="Release" src="https://img.shields.io/static/v1?label=release&message=v1.14.0&color=blue&style=for-the-badge" />
21
21
  </a>
22
22
  <!-- x-release-please-end -->
23
23
  <br/>
@@ -44,7 +44,7 @@
44
44
 
45
45
  ### Requirements
46
46
 
47
- - Node.js version 16+
47
+ - Node.js version 18+
48
48
 
49
49
  ### Install
50
50
 
@@ -54,6 +54,9 @@
54
54
  npm install --save @openfeature/server-sdk
55
55
  ```
56
56
 
57
+ > [!TIP]
58
+ > This SDK is designed to run in Node.JS. If you're interested in browser support, check out the [Web SDK](https://openfeature.dev/docs/reference/technologies/client/web/).
59
+
57
60
  #### yarn
58
61
 
59
62
  ```sh
@@ -158,6 +161,9 @@ const requestContext = {
158
161
  const boolValue = await client.getBooleanValue('some-flag', false, requestContext);
159
162
  ```
160
163
 
164
+ Context is merged by the SDK before a flag evaluation occurs.
165
+ The merge order is defined [here](https://openfeature.dev/specification/sections/evaluation-context#requirement-323) in the OpenFeature specification.
166
+
161
167
  ### Hooks
162
168
 
163
169
  [Hooks](https://openfeature.dev/docs/reference/concepts/hooks) allow for custom logic to be added at well-defined points of the flag evaluation life-cycle.
package/dist/cjs/index.js CHANGED
@@ -27,37 +27,14 @@ __export(src_exports, {
27
27
  NOOP_TRANSACTION_CONTEXT_PROPAGATOR: () => NOOP_TRANSACTION_CONTEXT_PROPAGATOR,
28
28
  OpenFeature: () => OpenFeature,
29
29
  OpenFeatureAPI: () => OpenFeatureAPI,
30
- OpenFeatureClient: () => OpenFeatureClient,
31
30
  OpenFeatureEventEmitter: () => OpenFeatureEventEmitter,
32
- ProviderEvents: () => import_core2.ServerProviderEvents,
33
- ProviderStatus: () => import_core3.ServerProviderStatus
31
+ ProviderEvents: () => import_core6.ServerProviderEvents,
32
+ ProviderStatus: () => import_core.ServerProviderStatus
34
33
  });
35
34
  module.exports = __toCommonJS(src_exports);
36
35
 
37
- // src/client/open-feature-client.ts
38
- var import_core7 = require("@openfeature/core");
39
-
40
- // src/open-feature.ts
41
- var import_core6 = require("@openfeature/core");
42
-
43
- // src/events/open-feature-event-emitter.ts
44
- var import_core = require("@openfeature/core");
45
- var import_node_events = require("events");
46
- var OpenFeatureEventEmitter = class extends import_core.GenericEventEmitter {
47
- eventEmitter = new import_node_events.EventEmitter({ captureRejections: true });
48
- constructor() {
49
- super();
50
- this.eventEmitter.on("error", (err) => {
51
- this._logger?.error("Error running event handler:", err);
52
- });
53
- }
54
- };
55
-
56
- // src/events/events.ts
57
- var import_core2 = require("@openfeature/core");
58
-
59
36
  // src/provider/provider.ts
60
- var import_core3 = require("@openfeature/core");
37
+ var import_core = require("@openfeature/core");
61
38
 
62
39
  // src/provider/no-op-provider.ts
63
40
  var REASON_NO_OP = "No-op";
@@ -87,17 +64,17 @@ var NoopFeatureProvider = class {
87
64
  var NOOP_PROVIDER = new NoopFeatureProvider();
88
65
 
89
66
  // src/provider/in-memory-provider/in-memory-provider.ts
90
- var import_core5 = require("@openfeature/core");
67
+ var import_core3 = require("@openfeature/core");
91
68
 
92
69
  // src/provider/in-memory-provider/variant-not-found-error.ts
93
- var import_core4 = require("@openfeature/core");
94
- var VariantFoundError = class _VariantFoundError extends import_core4.OpenFeatureError {
70
+ var import_core2 = require("@openfeature/core");
71
+ var VariantFoundError = class _VariantFoundError extends import_core2.OpenFeatureError {
95
72
  code;
96
73
  constructor(message) {
97
74
  super(message);
98
75
  Object.setPrototypeOf(this, _VariantFoundError.prototype);
99
76
  this.name = "VariantFoundError";
100
- this.code = import_core4.ErrorCode.GENERAL;
77
+ this.code = import_core2.ErrorCode.GENERAL;
101
78
  }
102
79
  };
103
80
 
@@ -119,7 +96,7 @@ var InMemoryProvider = class {
119
96
  putConfiguration(flagConfiguration) {
120
97
  const flagsChanged = Object.entries(flagConfiguration).filter(([key, value]) => this._flagConfiguration[key] !== value).map(([key]) => key);
121
98
  this._flagConfiguration = { ...flagConfiguration };
122
- this.events.emit(import_core2.ServerProviderEvents.ConfigurationChanged, { flagsChanged });
99
+ this.events.emit(import_core6.ServerProviderEvents.ConfigurationChanged, { flagsChanged });
123
100
  }
124
101
  resolveBooleanEvaluation(flagKey, defaultValue, context, logger) {
125
102
  return this.resolveFlagWithReason(flagKey, defaultValue, context, logger);
@@ -137,12 +114,12 @@ var InMemoryProvider = class {
137
114
  try {
138
115
  const resolutionResult = this.lookupFlagValue(flagKey, defaultValue, ctx, logger);
139
116
  if (typeof resolutionResult?.value != typeof defaultValue) {
140
- throw new import_core5.TypeMismatchError();
117
+ throw new import_core3.TypeMismatchError();
141
118
  }
142
119
  return resolutionResult;
143
120
  } catch (error) {
144
- if (!(error instanceof import_core5.OpenFeatureError)) {
145
- throw new import_core5.GeneralError(error?.message || "unknown error");
121
+ if (!(error instanceof import_core3.OpenFeatureError)) {
122
+ throw new import_core3.GeneralError(error?.message || "unknown error");
146
123
  }
147
124
  throw error;
148
125
  }
@@ -151,11 +128,11 @@ var InMemoryProvider = class {
151
128
  if (!(flagKey in this._flagConfiguration)) {
152
129
  const message = `no flag found with key ${flagKey}`;
153
130
  logger?.debug(message);
154
- throw new import_core5.FlagNotFoundError(message);
131
+ throw new import_core3.FlagNotFoundError(message);
155
132
  }
156
133
  const flagSpec = this._flagConfiguration[flagKey];
157
134
  if (flagSpec.disabled) {
158
- return { value: defaultValue, reason: import_core5.StandardResolutionReasons.DISABLED };
135
+ return { value: defaultValue, reason: import_core3.StandardResolutionReasons.DISABLED };
159
136
  }
160
137
  const isContextEval = ctx && flagSpec?.contextEvaluator;
161
138
  const variant = isContextEval ? flagSpec.contextEvaluator?.(ctx) : flagSpec.defaultVariant;
@@ -168,122 +145,16 @@ var InMemoryProvider = class {
168
145
  return {
169
146
  value,
170
147
  ...variant && { variant },
171
- reason: isContextEval ? import_core5.StandardResolutionReasons.TARGETING_MATCH : import_core5.StandardResolutionReasons.STATIC
148
+ reason: isContextEval ? import_core3.StandardResolutionReasons.TARGETING_MATCH : import_core3.StandardResolutionReasons.STATIC
172
149
  };
173
150
  }
174
151
  };
175
152
 
176
- // src/transaction-context/no-op-transaction-context-propagator.ts
177
- var NoopTransactionContextPropagator = class {
178
- getTransactionContext() {
179
- return {};
180
- }
181
- setTransactionContext(_, callback, ...args) {
182
- callback(...args);
183
- }
184
- };
185
- var NOOP_TRANSACTION_CONTEXT_PROPAGATOR = new NoopTransactionContextPropagator();
186
-
187
- // src/transaction-context/async-local-storage-transaction-context-propagator.ts
188
- var import_async_hooks = require("async_hooks");
189
- var AsyncLocalStorageTransactionContextPropagator = class {
190
- asyncLocalStorage = new import_async_hooks.AsyncLocalStorage();
191
- getTransactionContext() {
192
- return this.asyncLocalStorage.getStore() ?? {};
193
- }
194
- setTransactionContext(transactionContext, callback, ...args) {
195
- this.asyncLocalStorage.run(transactionContext, callback, ...args);
196
- }
197
- };
198
-
199
153
  // src/open-feature.ts
200
- var GLOBAL_OPENFEATURE_API_KEY = Symbol.for("@openfeature/js-sdk/api");
201
- var _globalThis = globalThis;
202
- var OpenFeatureAPI = class _OpenFeatureAPI extends import_core6.OpenFeatureCommonAPI {
203
- _statusEnumType = import_core3.ServerProviderStatus;
204
- _apiEmitter = new OpenFeatureEventEmitter();
205
- _defaultProvider = new import_core6.ProviderWrapper(NOOP_PROVIDER, import_core3.ServerProviderStatus.NOT_READY, this._statusEnumType);
206
- _domainScopedProviders = /* @__PURE__ */ new Map();
207
- _createEventEmitter = () => new OpenFeatureEventEmitter();
208
- _transactionContextPropagator = NOOP_TRANSACTION_CONTEXT_PROPAGATOR;
209
- constructor() {
210
- super("server");
211
- }
212
- /**
213
- * Gets a singleton instance of the OpenFeature API.
214
- * @ignore
215
- * @returns {OpenFeatureAPI} OpenFeature API
216
- */
217
- static getInstance() {
218
- const globalApi = _globalThis[GLOBAL_OPENFEATURE_API_KEY];
219
- if (globalApi) {
220
- return globalApi;
221
- }
222
- const instance = new _OpenFeatureAPI();
223
- _globalThis[GLOBAL_OPENFEATURE_API_KEY] = instance;
224
- return instance;
225
- }
226
- getProviderStatus(domain) {
227
- if (!domain) {
228
- return this._defaultProvider.status;
229
- }
230
- return this._domainScopedProviders.get(domain)?.status ?? this._defaultProvider.status;
231
- }
232
- setContext(context) {
233
- this._context = context;
234
- return this;
235
- }
236
- getContext() {
237
- return this._context;
238
- }
239
- getClient(domainOrContext, versionOrContext, contextOrUndefined) {
240
- const domain = (0, import_core6.stringOrUndefined)(domainOrContext);
241
- const version = (0, import_core6.stringOrUndefined)(versionOrContext);
242
- const context = (0, import_core6.objectOrUndefined)(domainOrContext) ?? (0, import_core6.objectOrUndefined)(versionOrContext) ?? (0, import_core6.objectOrUndefined)(contextOrUndefined);
243
- return new OpenFeatureClient(
244
- () => this.getProviderForClient(domain),
245
- () => this.getProviderStatus(domain),
246
- () => this.buildAndCacheEventEmitterForClient(domain),
247
- () => this._logger,
248
- { domain, version },
249
- context
250
- );
251
- }
252
- /**
253
- * Clears all registered providers and resets the default provider.
254
- * @returns {Promise<void>}
255
- */
256
- clearProviders() {
257
- return super.clearProvidersAndSetDefault(NOOP_PROVIDER);
258
- }
259
- setTransactionContextPropagator(transactionContextPropagator) {
260
- const baseMessage = "Invalid TransactionContextPropagator, will not be set: ";
261
- if (typeof transactionContextPropagator?.getTransactionContext !== "function") {
262
- this._logger.error(`${baseMessage}: getTransactionContext is not a function.`);
263
- } else if (typeof transactionContextPropagator?.setTransactionContext !== "function") {
264
- this._logger.error(`${baseMessage}: setTransactionContext is not a function.`);
265
- } else {
266
- this._transactionContextPropagator = transactionContextPropagator;
267
- }
268
- return this;
269
- }
270
- setTransactionContext(transactionContext, callback, ...args) {
271
- this._transactionContextPropagator.setTransactionContext(transactionContext, callback, ...args);
272
- }
273
- getTransactionContext() {
274
- try {
275
- return this._transactionContextPropagator.getTransactionContext();
276
- } catch (err) {
277
- const error = err;
278
- this._logger.error(`Error getting transaction context: ${error?.message}, returning empty context.`);
279
- this._logger.error(error?.stack);
280
- return {};
281
- }
282
- }
283
- };
284
- var OpenFeature = OpenFeatureAPI.getInstance();
154
+ var import_core7 = require("@openfeature/core");
285
155
 
286
- // src/client/open-feature-client.ts
156
+ // src/client/internal/open-feature-client.ts
157
+ var import_core4 = require("@openfeature/core");
287
158
  var OpenFeatureClient = class {
288
159
  constructor(providerAccessor, providerStatusAccessor, emitterAccessor, globalLogger, options, context = {}) {
289
160
  this.providerAccessor = providerAccessor;
@@ -310,7 +181,7 @@ var OpenFeatureClient = class {
310
181
  }
311
182
  addHandler(eventType, handler) {
312
183
  this.emitterAccessor().addHandler(eventType, handler);
313
- const shouldRunNow = (0, import_core7.statusMatchesEvent)(eventType, this._providerStatus);
184
+ const shouldRunNow = (0, import_core4.statusMatchesEvent)(eventType, this._providerStatus);
314
185
  if (shouldRunNow) {
315
186
  try {
316
187
  handler({
@@ -330,7 +201,7 @@ var OpenFeatureClient = class {
330
201
  return this.emitterAccessor().getHandlers(eventType);
331
202
  }
332
203
  setLogger(logger) {
333
- this._clientLogger = new import_core7.SafeLogger(logger);
204
+ this._clientLogger = new import_core4.SafeLogger(logger);
334
205
  return this;
335
206
  }
336
207
  setContext(context) {
@@ -423,10 +294,10 @@ var OpenFeatureClient = class {
423
294
  };
424
295
  try {
425
296
  const frozenContext = await this.beforeHooks(allHooks, hookContext, options);
426
- if (this.providerStatus === import_core3.ServerProviderStatus.NOT_READY) {
427
- throw new import_core7.ProviderNotReadyError("provider has not yet initialized");
428
- } else if (this.providerStatus === import_core3.ServerProviderStatus.FATAL) {
429
- throw new import_core7.ProviderFatalError("provider is in an irrecoverable error state");
297
+ if (this.providerStatus === import_core.ServerProviderStatus.NOT_READY) {
298
+ throw new import_core4.ProviderNotReadyError("provider has not yet initialized");
299
+ } else if (this.providerStatus === import_core.ServerProviderStatus.FATAL) {
300
+ throw new import_core4.ProviderFatalError("provider is in an irrecoverable error state");
430
301
  }
431
302
  const resolution = await resolver.call(this._provider, flagKey, defaultValue, frozenContext, this._logger);
432
303
  const evaluationDetails = {
@@ -434,17 +305,20 @@ var OpenFeatureClient = class {
434
305
  flagMetadata: Object.freeze(resolution.flagMetadata ?? {}),
435
306
  flagKey
436
307
  };
308
+ if (evaluationDetails.errorCode) {
309
+ throw (0, import_core4.instantiateErrorByErrorCode)(evaluationDetails.errorCode);
310
+ }
437
311
  await this.afterHooks(allHooksReversed, hookContext, evaluationDetails, options);
438
312
  return evaluationDetails;
439
313
  } catch (err) {
440
314
  const errorMessage = err?.message;
441
- const errorCode = err?.code || import_core7.ErrorCode.GENERAL;
315
+ const errorCode = err?.code || import_core4.ErrorCode.GENERAL;
442
316
  await this.errorHooks(allHooksReversed, hookContext, err, options);
443
317
  return {
444
318
  errorCode,
445
319
  errorMessage,
446
320
  value: defaultValue,
447
- reason: import_core7.StandardResolutionReasons.ERROR,
321
+ reason: import_core4.StandardResolutionReasons.ERROR,
448
322
  flagMetadata: Object.freeze({}),
449
323
  flagKey
450
324
  };
@@ -504,6 +378,150 @@ var OpenFeatureClient = class {
504
378
  }
505
379
  };
506
380
 
381
+ // src/events/open-feature-event-emitter.ts
382
+ var import_core5 = require("@openfeature/core");
383
+ var import_node_events = require("events");
384
+ var OpenFeatureEventEmitter = class extends import_core5.GenericEventEmitter {
385
+ eventEmitter = new import_node_events.EventEmitter({ captureRejections: true });
386
+ constructor() {
387
+ super();
388
+ this.eventEmitter.on("error", (err) => {
389
+ this._logger?.error("Error running event handler:", err);
390
+ });
391
+ }
392
+ };
393
+
394
+ // src/events/events.ts
395
+ var import_core6 = require("@openfeature/core");
396
+
397
+ // src/transaction-context/no-op-transaction-context-propagator.ts
398
+ var NoopTransactionContextPropagator = class {
399
+ getTransactionContext() {
400
+ return {};
401
+ }
402
+ setTransactionContext(_, callback, ...args) {
403
+ callback(...args);
404
+ }
405
+ };
406
+ var NOOP_TRANSACTION_CONTEXT_PROPAGATOR = new NoopTransactionContextPropagator();
407
+
408
+ // src/transaction-context/async-local-storage-transaction-context-propagator.ts
409
+ var import_async_hooks = require("async_hooks");
410
+ var AsyncLocalStorageTransactionContextPropagator = class {
411
+ asyncLocalStorage = new import_async_hooks.AsyncLocalStorage();
412
+ getTransactionContext() {
413
+ return this.asyncLocalStorage.getStore() ?? {};
414
+ }
415
+ setTransactionContext(transactionContext, callback, ...args) {
416
+ this.asyncLocalStorage.run(transactionContext, callback, ...args);
417
+ }
418
+ };
419
+
420
+ // src/open-feature.ts
421
+ var GLOBAL_OPENFEATURE_API_KEY = Symbol.for("@openfeature/js-sdk/api");
422
+ var _globalThis = globalThis;
423
+ var OpenFeatureAPI = class _OpenFeatureAPI extends import_core7.OpenFeatureCommonAPI {
424
+ _statusEnumType = import_core.ServerProviderStatus;
425
+ _apiEmitter = new OpenFeatureEventEmitter();
426
+ _defaultProvider = new import_core7.ProviderWrapper(
427
+ NOOP_PROVIDER,
428
+ import_core.ServerProviderStatus.NOT_READY,
429
+ this._statusEnumType
430
+ );
431
+ _domainScopedProviders = /* @__PURE__ */ new Map();
432
+ _createEventEmitter = () => new OpenFeatureEventEmitter();
433
+ _transactionContextPropagator = NOOP_TRANSACTION_CONTEXT_PROPAGATOR;
434
+ constructor() {
435
+ super("server");
436
+ }
437
+ /**
438
+ * Gets a singleton instance of the OpenFeature API.
439
+ * @ignore
440
+ * @returns {OpenFeatureAPI} OpenFeature API
441
+ */
442
+ static getInstance() {
443
+ const globalApi = _globalThis[GLOBAL_OPENFEATURE_API_KEY];
444
+ if (globalApi) {
445
+ return globalApi;
446
+ }
447
+ const instance = new _OpenFeatureAPI();
448
+ _globalThis[GLOBAL_OPENFEATURE_API_KEY] = instance;
449
+ return instance;
450
+ }
451
+ getProviderStatus(domain) {
452
+ if (!domain) {
453
+ return this._defaultProvider.status;
454
+ }
455
+ return this._domainScopedProviders.get(domain)?.status ?? this._defaultProvider.status;
456
+ }
457
+ async setProviderAndWait(domainOrProvider, providerOrUndefined) {
458
+ const domain = (0, import_core7.stringOrUndefined)(domainOrProvider);
459
+ const provider = domain ? (0, import_core7.objectOrUndefined)(providerOrUndefined) : (0, import_core7.objectOrUndefined)(domainOrProvider);
460
+ await this.setAwaitableProvider(domain, provider);
461
+ }
462
+ setProvider(clientOrProvider, providerOrUndefined) {
463
+ const domain = (0, import_core7.stringOrUndefined)(clientOrProvider);
464
+ const provider = domain ? (0, import_core7.objectOrUndefined)(providerOrUndefined) : (0, import_core7.objectOrUndefined)(clientOrProvider);
465
+ const maybePromise = this.setAwaitableProvider(domain, provider);
466
+ Promise.resolve(maybePromise).catch((err) => {
467
+ this._logger.error("Error during provider initialization:", err);
468
+ });
469
+ return this;
470
+ }
471
+ setContext(context) {
472
+ this._context = context;
473
+ return this;
474
+ }
475
+ getContext() {
476
+ return this._context;
477
+ }
478
+ getClient(domainOrContext, versionOrContext, contextOrUndefined) {
479
+ const domain = (0, import_core7.stringOrUndefined)(domainOrContext);
480
+ const version = (0, import_core7.stringOrUndefined)(versionOrContext);
481
+ const context = (0, import_core7.objectOrUndefined)(domainOrContext) ?? (0, import_core7.objectOrUndefined)(versionOrContext) ?? (0, import_core7.objectOrUndefined)(contextOrUndefined);
482
+ return new OpenFeatureClient(
483
+ () => this.getProviderForClient(domain),
484
+ () => this.getProviderStatus(domain),
485
+ () => this.buildAndCacheEventEmitterForClient(domain),
486
+ () => this._logger,
487
+ { domain, version },
488
+ context
489
+ );
490
+ }
491
+ /**
492
+ * Clears all registered providers and resets the default provider.
493
+ * @returns {Promise<void>}
494
+ */
495
+ clearProviders() {
496
+ return super.clearProvidersAndSetDefault(NOOP_PROVIDER);
497
+ }
498
+ setTransactionContextPropagator(transactionContextPropagator) {
499
+ const baseMessage = "Invalid TransactionContextPropagator, will not be set: ";
500
+ if (typeof transactionContextPropagator?.getTransactionContext !== "function") {
501
+ this._logger.error(`${baseMessage}: getTransactionContext is not a function.`);
502
+ } else if (typeof transactionContextPropagator?.setTransactionContext !== "function") {
503
+ this._logger.error(`${baseMessage}: setTransactionContext is not a function.`);
504
+ } else {
505
+ this._transactionContextPropagator = transactionContextPropagator;
506
+ }
507
+ return this;
508
+ }
509
+ setTransactionContext(transactionContext, callback, ...args) {
510
+ this._transactionContextPropagator.setTransactionContext(transactionContext, callback, ...args);
511
+ }
512
+ getTransactionContext() {
513
+ try {
514
+ return this._transactionContextPropagator.getTransactionContext();
515
+ } catch (err) {
516
+ const error = err;
517
+ this._logger.error(`Error getting transaction context: ${error?.message}, returning empty context.`);
518
+ this._logger.error(error?.stack);
519
+ return {};
520
+ }
521
+ }
522
+ };
523
+ var OpenFeature = OpenFeatureAPI.getInstance();
524
+
507
525
  // src/index.ts
508
526
  __reExport(src_exports, require("@openfeature/core"), module.exports);
509
527
  // Annotate the CommonJS export names for ESM import in node:
@@ -514,7 +532,6 @@ __reExport(src_exports, require("@openfeature/core"), module.exports);
514
532
  NOOP_TRANSACTION_CONTEXT_PROPAGATOR,
515
533
  OpenFeature,
516
534
  OpenFeatureAPI,
517
- OpenFeatureClient,
518
535
  OpenFeatureEventEmitter,
519
536
  ProviderEvents,
520
537
  ProviderStatus,