@openfeature/server-sdk 1.18.0 → 1.20.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/LICENSE +1 -1
- package/README.md +84 -4
- package/dist/cjs/index.js +621 -61
- package/dist/cjs/index.js.map +4 -4
- package/dist/esm/index.js +600 -48
- package/dist/esm/index.js.map +4 -4
- package/dist/types.d.ts +149 -5
- package/package.json +4 -4
package/dist/esm/index.js
CHANGED
|
@@ -170,6 +170,550 @@ var InMemoryProvider = class {
|
|
|
170
170
|
}
|
|
171
171
|
};
|
|
172
172
|
|
|
173
|
+
// src/provider/multi-provider/multi-provider.ts
|
|
174
|
+
import { DefaultLogger, ErrorCode as ErrorCode3, GeneralError as GeneralError4, StandardResolutionReasons as StandardResolutionReasons2 } from "@openfeature/core";
|
|
175
|
+
|
|
176
|
+
// src/events/open-feature-event-emitter.ts
|
|
177
|
+
import { GenericEventEmitter } from "@openfeature/core";
|
|
178
|
+
import { EventEmitter } from "node:events";
|
|
179
|
+
var OpenFeatureEventEmitter = class extends GenericEventEmitter {
|
|
180
|
+
constructor() {
|
|
181
|
+
super();
|
|
182
|
+
this.eventEmitter = new EventEmitter({ captureRejections: true });
|
|
183
|
+
this.eventEmitter.on("error", (err) => {
|
|
184
|
+
var _a;
|
|
185
|
+
(_a = this._logger) == null ? void 0 : _a.error("Error running event handler:", err);
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
};
|
|
189
|
+
|
|
190
|
+
// src/provider/multi-provider/errors.ts
|
|
191
|
+
import { GeneralError as GeneralError2, OpenFeatureError as OpenFeatureError3 } from "@openfeature/core";
|
|
192
|
+
var ErrorWithCode = class extends OpenFeatureError3 {
|
|
193
|
+
constructor(code, message) {
|
|
194
|
+
super(message);
|
|
195
|
+
this.code = code;
|
|
196
|
+
}
|
|
197
|
+
};
|
|
198
|
+
var AggregateError = class extends GeneralError2 {
|
|
199
|
+
constructor(message, originalErrors) {
|
|
200
|
+
super(message);
|
|
201
|
+
this.originalErrors = originalErrors;
|
|
202
|
+
}
|
|
203
|
+
};
|
|
204
|
+
var constructAggregateError = (providerErrors) => {
|
|
205
|
+
const errorsWithSource = providerErrors.map(({ providerName, error }) => {
|
|
206
|
+
return { source: providerName, error };
|
|
207
|
+
}).flat();
|
|
208
|
+
return new AggregateError(
|
|
209
|
+
`Provider errors occurred: ${errorsWithSource[0].source}: ${errorsWithSource[0].error}`,
|
|
210
|
+
errorsWithSource
|
|
211
|
+
);
|
|
212
|
+
};
|
|
213
|
+
var throwAggregateErrorFromPromiseResults = (result, providerEntries) => {
|
|
214
|
+
const errors = result.map((r, i) => {
|
|
215
|
+
if (r.status === "rejected") {
|
|
216
|
+
return { error: r.reason, providerName: providerEntries[i].name };
|
|
217
|
+
}
|
|
218
|
+
return null;
|
|
219
|
+
}).filter((val) => Boolean(val));
|
|
220
|
+
if (errors.length) {
|
|
221
|
+
throw constructAggregateError(errors);
|
|
222
|
+
}
|
|
223
|
+
};
|
|
224
|
+
|
|
225
|
+
// src/provider/multi-provider/hook-executor.ts
|
|
226
|
+
var HookExecutor = class {
|
|
227
|
+
constructor(logger) {
|
|
228
|
+
this.logger = logger;
|
|
229
|
+
}
|
|
230
|
+
beforeHooks(hooks, hookContext, hints) {
|
|
231
|
+
return __async(this, null, function* () {
|
|
232
|
+
var _a;
|
|
233
|
+
for (const hook of hooks != null ? hooks : []) {
|
|
234
|
+
Object.freeze(hookContext);
|
|
235
|
+
Object.assign(hookContext.context, __spreadValues({}, yield (_a = hook == null ? void 0 : hook.before) == null ? void 0 : _a.call(hook, hookContext, Object.freeze(hints))));
|
|
236
|
+
}
|
|
237
|
+
return Object.freeze(hookContext.context);
|
|
238
|
+
});
|
|
239
|
+
}
|
|
240
|
+
afterHooks(hooks, hookContext, evaluationDetails, hints) {
|
|
241
|
+
return __async(this, null, function* () {
|
|
242
|
+
var _a;
|
|
243
|
+
for (const hook of hooks != null ? hooks : []) {
|
|
244
|
+
yield (_a = hook == null ? void 0 : hook.after) == null ? void 0 : _a.call(hook, hookContext, evaluationDetails, hints);
|
|
245
|
+
}
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
errorHooks(hooks, hookContext, err, hints) {
|
|
249
|
+
return __async(this, null, function* () {
|
|
250
|
+
var _a;
|
|
251
|
+
for (const hook of hooks != null ? hooks : []) {
|
|
252
|
+
try {
|
|
253
|
+
yield (_a = hook == null ? void 0 : hook.error) == null ? void 0 : _a.call(hook, hookContext, err, hints);
|
|
254
|
+
} catch (err2) {
|
|
255
|
+
this.logger.error(`Unhandled error during 'error' hook: ${err2}`);
|
|
256
|
+
if (err2 instanceof Error) {
|
|
257
|
+
this.logger.error(err2.stack);
|
|
258
|
+
}
|
|
259
|
+
this.logger.error(err2 == null ? void 0 : err2.stack);
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
});
|
|
263
|
+
}
|
|
264
|
+
finallyHooks(hooks, hookContext, evaluationDetails, hints) {
|
|
265
|
+
return __async(this, null, function* () {
|
|
266
|
+
var _a;
|
|
267
|
+
for (const hook of hooks != null ? hooks : []) {
|
|
268
|
+
try {
|
|
269
|
+
yield (_a = hook == null ? void 0 : hook.finally) == null ? void 0 : _a.call(hook, hookContext, evaluationDetails, hints);
|
|
270
|
+
} catch (err) {
|
|
271
|
+
this.logger.error(`Unhandled error during 'finally' hook: ${err}`);
|
|
272
|
+
if (err instanceof Error) {
|
|
273
|
+
this.logger.error(err.stack);
|
|
274
|
+
}
|
|
275
|
+
this.logger.error(err == null ? void 0 : err.stack);
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
});
|
|
279
|
+
}
|
|
280
|
+
};
|
|
281
|
+
|
|
282
|
+
// src/events/events.ts
|
|
283
|
+
import { ServerProviderEvents } from "@openfeature/core";
|
|
284
|
+
|
|
285
|
+
// src/provider/multi-provider/status-tracker.ts
|
|
286
|
+
var StatusTracker = class {
|
|
287
|
+
constructor(events) {
|
|
288
|
+
this.events = events;
|
|
289
|
+
this.providerStatuses = {};
|
|
290
|
+
}
|
|
291
|
+
wrapEventHandler(providerEntry) {
|
|
292
|
+
var _a, _b, _c, _d;
|
|
293
|
+
const provider = providerEntry.provider;
|
|
294
|
+
(_a = provider.events) == null ? void 0 : _a.addHandler(ServerProviderEvents.Error, (details) => {
|
|
295
|
+
this.changeProviderStatus(providerEntry.name, ServerProviderStatus.ERROR, details);
|
|
296
|
+
});
|
|
297
|
+
(_b = provider.events) == null ? void 0 : _b.addHandler(ServerProviderEvents.Stale, (details) => {
|
|
298
|
+
this.changeProviderStatus(providerEntry.name, ServerProviderStatus.STALE, details);
|
|
299
|
+
});
|
|
300
|
+
(_c = provider.events) == null ? void 0 : _c.addHandler(ServerProviderEvents.ConfigurationChanged, (details) => {
|
|
301
|
+
this.events.emit(ServerProviderEvents.ConfigurationChanged, details);
|
|
302
|
+
});
|
|
303
|
+
(_d = provider.events) == null ? void 0 : _d.addHandler(ServerProviderEvents.Ready, (details) => {
|
|
304
|
+
this.changeProviderStatus(providerEntry.name, ServerProviderStatus.READY, details);
|
|
305
|
+
});
|
|
306
|
+
}
|
|
307
|
+
providerStatus(name) {
|
|
308
|
+
return this.providerStatuses[name];
|
|
309
|
+
}
|
|
310
|
+
getStatusFromProviderStatuses() {
|
|
311
|
+
const statuses = Object.values(this.providerStatuses);
|
|
312
|
+
if (statuses.includes(ServerProviderStatus.FATAL)) {
|
|
313
|
+
return ServerProviderStatus.FATAL;
|
|
314
|
+
} else if (statuses.includes(ServerProviderStatus.NOT_READY)) {
|
|
315
|
+
return ServerProviderStatus.NOT_READY;
|
|
316
|
+
} else if (statuses.includes(ServerProviderStatus.ERROR)) {
|
|
317
|
+
return ServerProviderStatus.ERROR;
|
|
318
|
+
} else if (statuses.includes(ServerProviderStatus.STALE)) {
|
|
319
|
+
return ServerProviderStatus.STALE;
|
|
320
|
+
}
|
|
321
|
+
return ServerProviderStatus.READY;
|
|
322
|
+
}
|
|
323
|
+
changeProviderStatus(name, status, details) {
|
|
324
|
+
const currentStatus = this.getStatusFromProviderStatuses();
|
|
325
|
+
this.providerStatuses[name] = status;
|
|
326
|
+
const newStatus = this.getStatusFromProviderStatuses();
|
|
327
|
+
if (currentStatus !== newStatus) {
|
|
328
|
+
if (newStatus === ServerProviderStatus.FATAL || newStatus === ServerProviderStatus.ERROR) {
|
|
329
|
+
this.events.emit(ServerProviderEvents.Error, details);
|
|
330
|
+
} else if (newStatus === ServerProviderStatus.STALE) {
|
|
331
|
+
this.events.emit(ServerProviderEvents.Stale, details);
|
|
332
|
+
} else if (newStatus === ServerProviderStatus.READY) {
|
|
333
|
+
this.events.emit(ServerProviderEvents.Ready, details);
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
};
|
|
338
|
+
|
|
339
|
+
// src/provider/multi-provider/strategies/base-evaluation-strategy.ts
|
|
340
|
+
var BaseEvaluationStrategy = class {
|
|
341
|
+
constructor() {
|
|
342
|
+
this.runMode = "sequential";
|
|
343
|
+
}
|
|
344
|
+
shouldEvaluateThisProvider(strategyContext, _evalContext) {
|
|
345
|
+
if (strategyContext.providerStatus === ServerProviderStatus.NOT_READY || strategyContext.providerStatus === ServerProviderStatus.FATAL) {
|
|
346
|
+
return false;
|
|
347
|
+
}
|
|
348
|
+
return true;
|
|
349
|
+
}
|
|
350
|
+
shouldEvaluateNextProvider(_strategyContext, _context, _result) {
|
|
351
|
+
return true;
|
|
352
|
+
}
|
|
353
|
+
shouldTrackWithThisProvider(strategyContext, _context, _trackingEventName, _trackingEventDetails) {
|
|
354
|
+
if (strategyContext.providerStatus === ServerProviderStatus.NOT_READY || strategyContext.providerStatus === ServerProviderStatus.FATAL) {
|
|
355
|
+
return false;
|
|
356
|
+
}
|
|
357
|
+
return true;
|
|
358
|
+
}
|
|
359
|
+
hasError(resolution) {
|
|
360
|
+
return "thrownError" in resolution || !!resolution.details.errorCode;
|
|
361
|
+
}
|
|
362
|
+
hasErrorWithCode(resolution, code) {
|
|
363
|
+
var _a;
|
|
364
|
+
return "thrownError" in resolution ? ((_a = resolution.thrownError) == null ? void 0 : _a.code) === code : resolution.details.errorCode === code;
|
|
365
|
+
}
|
|
366
|
+
collectProviderErrors(resolutions) {
|
|
367
|
+
var _a;
|
|
368
|
+
const errors = [];
|
|
369
|
+
for (const resolution of resolutions) {
|
|
370
|
+
if ("thrownError" in resolution) {
|
|
371
|
+
errors.push({ providerName: resolution.providerName, error: resolution.thrownError });
|
|
372
|
+
} else if (resolution.details.errorCode) {
|
|
373
|
+
errors.push({
|
|
374
|
+
providerName: resolution.providerName,
|
|
375
|
+
error: new ErrorWithCode(resolution.details.errorCode, (_a = resolution.details.errorMessage) != null ? _a : "unknown error")
|
|
376
|
+
});
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
return { errors };
|
|
380
|
+
}
|
|
381
|
+
resolutionToFinalResult(resolution) {
|
|
382
|
+
return { details: resolution.details, provider: resolution.provider, providerName: resolution.providerName };
|
|
383
|
+
}
|
|
384
|
+
};
|
|
385
|
+
|
|
386
|
+
// src/provider/multi-provider/strategies/first-match-strategy.ts
|
|
387
|
+
import { ErrorCode as ErrorCode2 } from "@openfeature/core";
|
|
388
|
+
var FirstMatchStrategy = class extends BaseEvaluationStrategy {
|
|
389
|
+
shouldEvaluateNextProvider(strategyContext, context, result) {
|
|
390
|
+
if (this.hasErrorWithCode(result, ErrorCode2.FLAG_NOT_FOUND)) {
|
|
391
|
+
return true;
|
|
392
|
+
}
|
|
393
|
+
if (this.hasError(result)) {
|
|
394
|
+
return false;
|
|
395
|
+
}
|
|
396
|
+
return false;
|
|
397
|
+
}
|
|
398
|
+
determineFinalResult(strategyContext, context, resolutions) {
|
|
399
|
+
const finalResolution = resolutions[resolutions.length - 1];
|
|
400
|
+
if (this.hasError(finalResolution)) {
|
|
401
|
+
return this.collectProviderErrors(resolutions);
|
|
402
|
+
}
|
|
403
|
+
return this.resolutionToFinalResult(finalResolution);
|
|
404
|
+
}
|
|
405
|
+
};
|
|
406
|
+
|
|
407
|
+
// src/provider/multi-provider/strategies/first-successful-strategy.ts
|
|
408
|
+
var FirstSuccessfulStrategy = class extends BaseEvaluationStrategy {
|
|
409
|
+
shouldEvaluateNextProvider(strategyContext, context, result) {
|
|
410
|
+
return this.hasError(result);
|
|
411
|
+
}
|
|
412
|
+
determineFinalResult(strategyContext, context, resolutions) {
|
|
413
|
+
const finalResolution = resolutions[resolutions.length - 1];
|
|
414
|
+
if (this.hasError(finalResolution)) {
|
|
415
|
+
return this.collectProviderErrors(resolutions);
|
|
416
|
+
}
|
|
417
|
+
return this.resolutionToFinalResult(finalResolution);
|
|
418
|
+
}
|
|
419
|
+
};
|
|
420
|
+
|
|
421
|
+
// src/provider/multi-provider/strategies/comparison-strategy.ts
|
|
422
|
+
import { GeneralError as GeneralError3 } from "@openfeature/core";
|
|
423
|
+
var ComparisonStrategy = class extends BaseEvaluationStrategy {
|
|
424
|
+
constructor(fallbackProvider, onMismatch) {
|
|
425
|
+
super();
|
|
426
|
+
this.fallbackProvider = fallbackProvider;
|
|
427
|
+
this.onMismatch = onMismatch;
|
|
428
|
+
this.runMode = "parallel";
|
|
429
|
+
}
|
|
430
|
+
determineFinalResult(strategyContext, context, resolutions) {
|
|
431
|
+
var _a;
|
|
432
|
+
let value;
|
|
433
|
+
let fallbackResolution;
|
|
434
|
+
let finalResolution;
|
|
435
|
+
let mismatch = false;
|
|
436
|
+
for (const [i, resolution] of resolutions.entries()) {
|
|
437
|
+
if (this.hasError(resolution)) {
|
|
438
|
+
return this.collectProviderErrors(resolutions);
|
|
439
|
+
}
|
|
440
|
+
if (resolution.provider === this.fallbackProvider) {
|
|
441
|
+
fallbackResolution = resolution;
|
|
442
|
+
}
|
|
443
|
+
if (i === 0) {
|
|
444
|
+
finalResolution = resolution;
|
|
445
|
+
}
|
|
446
|
+
if (typeof value !== "undefined" && value !== resolution.details.value) {
|
|
447
|
+
mismatch = true;
|
|
448
|
+
} else {
|
|
449
|
+
value = resolution.details.value;
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
if (!fallbackResolution) {
|
|
453
|
+
throw new GeneralError3("Fallback provider not found in resolution results");
|
|
454
|
+
}
|
|
455
|
+
if (!finalResolution) {
|
|
456
|
+
throw new GeneralError3("Final resolution not found in resolution results");
|
|
457
|
+
}
|
|
458
|
+
if (mismatch) {
|
|
459
|
+
(_a = this.onMismatch) == null ? void 0 : _a.call(this, resolutions);
|
|
460
|
+
return {
|
|
461
|
+
details: fallbackResolution.details,
|
|
462
|
+
provider: fallbackResolution.provider
|
|
463
|
+
};
|
|
464
|
+
}
|
|
465
|
+
return this.resolutionToFinalResult(finalResolution);
|
|
466
|
+
}
|
|
467
|
+
};
|
|
468
|
+
|
|
469
|
+
// src/provider/multi-provider/multi-provider.ts
|
|
470
|
+
var MultiProvider = class _MultiProvider {
|
|
471
|
+
constructor(constructorProviders, evaluationStrategy = new FirstMatchStrategy(), logger = new DefaultLogger()) {
|
|
472
|
+
this.constructorProviders = constructorProviders;
|
|
473
|
+
this.evaluationStrategy = evaluationStrategy;
|
|
474
|
+
this.logger = logger;
|
|
475
|
+
this.runsOn = "server";
|
|
476
|
+
this.events = new OpenFeatureEventEmitter();
|
|
477
|
+
this.hookContexts = /* @__PURE__ */ new WeakMap();
|
|
478
|
+
this.hookHints = /* @__PURE__ */ new WeakMap();
|
|
479
|
+
this.providerEntries = [];
|
|
480
|
+
this.providerEntriesByName = {};
|
|
481
|
+
this.statusTracker = new StatusTracker(this.events);
|
|
482
|
+
this.hookExecutor = new HookExecutor(this.logger);
|
|
483
|
+
this.registerProviders(constructorProviders);
|
|
484
|
+
const aggregateMetadata = Object.keys(this.providerEntriesByName).reduce((acc, name) => {
|
|
485
|
+
return __spreadProps(__spreadValues({}, acc), { [name]: this.providerEntriesByName[name].provider.metadata });
|
|
486
|
+
}, {});
|
|
487
|
+
this.metadata = __spreadProps(__spreadValues({}, aggregateMetadata), {
|
|
488
|
+
name: _MultiProvider.name
|
|
489
|
+
});
|
|
490
|
+
}
|
|
491
|
+
registerProviders(constructorProviders) {
|
|
492
|
+
var _a, _b;
|
|
493
|
+
const providersByName = {};
|
|
494
|
+
for (const constructorProvider of constructorProviders) {
|
|
495
|
+
const providerName = constructorProvider.provider.metadata.name;
|
|
496
|
+
const candidateName = (_a = constructorProvider.name) != null ? _a : providerName;
|
|
497
|
+
if (constructorProvider.name && providersByName[constructorProvider.name]) {
|
|
498
|
+
throw new Error("Provider names must be unique");
|
|
499
|
+
}
|
|
500
|
+
(_b = providersByName[candidateName]) != null ? _b : providersByName[candidateName] = [];
|
|
501
|
+
providersByName[candidateName].push(constructorProvider.provider);
|
|
502
|
+
}
|
|
503
|
+
for (const name of Object.keys(providersByName)) {
|
|
504
|
+
const useIndexedNames = providersByName[name].length > 1;
|
|
505
|
+
for (let i = 0; i < providersByName[name].length; i++) {
|
|
506
|
+
const indexedName = useIndexedNames ? `${name}-${i + 1}` : name;
|
|
507
|
+
this.providerEntriesByName[indexedName] = { provider: providersByName[name][i], name: indexedName };
|
|
508
|
+
this.providerEntries.push(this.providerEntriesByName[indexedName]);
|
|
509
|
+
this.statusTracker.wrapEventHandler(this.providerEntriesByName[indexedName]);
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
Object.freeze(this.providerEntries);
|
|
513
|
+
Object.freeze(this.providerEntriesByName);
|
|
514
|
+
}
|
|
515
|
+
initialize(context) {
|
|
516
|
+
return __async(this, null, function* () {
|
|
517
|
+
const result = yield Promise.allSettled(
|
|
518
|
+
this.providerEntries.map((provider) => {
|
|
519
|
+
var _a, _b;
|
|
520
|
+
return (_b = (_a = provider.provider).initialize) == null ? void 0 : _b.call(_a, context);
|
|
521
|
+
})
|
|
522
|
+
);
|
|
523
|
+
throwAggregateErrorFromPromiseResults(result, this.providerEntries);
|
|
524
|
+
});
|
|
525
|
+
}
|
|
526
|
+
onClose() {
|
|
527
|
+
return __async(this, null, function* () {
|
|
528
|
+
const result = yield Promise.allSettled(this.providerEntries.map((provider) => {
|
|
529
|
+
var _a, _b;
|
|
530
|
+
return (_b = (_a = provider.provider).onClose) == null ? void 0 : _b.call(_a);
|
|
531
|
+
}));
|
|
532
|
+
throwAggregateErrorFromPromiseResults(result, this.providerEntries);
|
|
533
|
+
});
|
|
534
|
+
}
|
|
535
|
+
resolveBooleanEvaluation(flagKey, defaultValue, context) {
|
|
536
|
+
return this.flagResolutionProxy(flagKey, "boolean", defaultValue, context);
|
|
537
|
+
}
|
|
538
|
+
resolveStringEvaluation(flagKey, defaultValue, context) {
|
|
539
|
+
return this.flagResolutionProxy(flagKey, "string", defaultValue, context);
|
|
540
|
+
}
|
|
541
|
+
resolveNumberEvaluation(flagKey, defaultValue, context) {
|
|
542
|
+
return this.flagResolutionProxy(flagKey, "number", defaultValue, context);
|
|
543
|
+
}
|
|
544
|
+
resolveObjectEvaluation(flagKey, defaultValue, context) {
|
|
545
|
+
return this.flagResolutionProxy(flagKey, "object", defaultValue, context);
|
|
546
|
+
}
|
|
547
|
+
flagResolutionProxy(flagKey, flagType, defaultValue, context) {
|
|
548
|
+
return __async(this, null, function* () {
|
|
549
|
+
var _a;
|
|
550
|
+
const hookContext = this.hookContexts.get(context);
|
|
551
|
+
const hookHints = this.hookHints.get(context);
|
|
552
|
+
if (!hookContext || !hookHints) {
|
|
553
|
+
throw new GeneralError4("Hook context not available for evaluation");
|
|
554
|
+
}
|
|
555
|
+
const tasks = [];
|
|
556
|
+
for (const providerEntry of this.providerEntries) {
|
|
557
|
+
const task = this.evaluateProviderEntry(
|
|
558
|
+
flagKey,
|
|
559
|
+
flagType,
|
|
560
|
+
defaultValue,
|
|
561
|
+
providerEntry,
|
|
562
|
+
hookContext,
|
|
563
|
+
hookHints,
|
|
564
|
+
context
|
|
565
|
+
);
|
|
566
|
+
tasks.push(task);
|
|
567
|
+
if (this.evaluationStrategy.runMode === "sequential") {
|
|
568
|
+
const [shouldEvaluateNext] = yield task;
|
|
569
|
+
if (!shouldEvaluateNext) {
|
|
570
|
+
break;
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
const results = yield Promise.all(tasks);
|
|
575
|
+
const resolutions = results.map(([, resolution]) => resolution).filter((r) => Boolean(r));
|
|
576
|
+
const finalResult = this.evaluationStrategy.determineFinalResult({ flagKey, flagType }, context, resolutions);
|
|
577
|
+
if ((_a = finalResult.errors) == null ? void 0 : _a.length) {
|
|
578
|
+
throw constructAggregateError(finalResult.errors);
|
|
579
|
+
}
|
|
580
|
+
if (!finalResult.details) {
|
|
581
|
+
throw new GeneralError4("No result was returned from any provider");
|
|
582
|
+
}
|
|
583
|
+
return finalResult.details;
|
|
584
|
+
});
|
|
585
|
+
}
|
|
586
|
+
evaluateProviderEntry(flagKey, flagType, defaultValue, providerEntry, hookContext, hookHints, context) {
|
|
587
|
+
return __async(this, null, function* () {
|
|
588
|
+
let evaluationResult = void 0;
|
|
589
|
+
const provider = providerEntry.provider;
|
|
590
|
+
const strategyContext = {
|
|
591
|
+
flagKey,
|
|
592
|
+
flagType,
|
|
593
|
+
provider,
|
|
594
|
+
providerName: providerEntry.name,
|
|
595
|
+
providerStatus: this.statusTracker.providerStatus(providerEntry.name)
|
|
596
|
+
};
|
|
597
|
+
if (!this.evaluationStrategy.shouldEvaluateThisProvider(strategyContext, context)) {
|
|
598
|
+
return [true, null];
|
|
599
|
+
}
|
|
600
|
+
let resolution;
|
|
601
|
+
try {
|
|
602
|
+
evaluationResult = yield this.evaluateProviderAndHooks(flagKey, defaultValue, provider, hookContext, hookHints);
|
|
603
|
+
resolution = {
|
|
604
|
+
details: evaluationResult,
|
|
605
|
+
provider,
|
|
606
|
+
providerName: providerEntry.name
|
|
607
|
+
};
|
|
608
|
+
} catch (error) {
|
|
609
|
+
resolution = {
|
|
610
|
+
thrownError: error,
|
|
611
|
+
provider,
|
|
612
|
+
providerName: providerEntry.name
|
|
613
|
+
};
|
|
614
|
+
}
|
|
615
|
+
return [
|
|
616
|
+
this.evaluationStrategy.runMode === "sequential" ? this.evaluationStrategy.shouldEvaluateNextProvider(strategyContext, context, resolution) : true,
|
|
617
|
+
resolution
|
|
618
|
+
];
|
|
619
|
+
});
|
|
620
|
+
}
|
|
621
|
+
evaluateProviderAndHooks(flagKey, defaultValue, provider, hookContext, hookHints) {
|
|
622
|
+
return __async(this, null, function* () {
|
|
623
|
+
var _a;
|
|
624
|
+
let providerContext = void 0;
|
|
625
|
+
let evaluationDetails;
|
|
626
|
+
const hookContextCopy = __spreadProps(__spreadValues({}, hookContext), { context: __spreadValues({}, hookContext.context) });
|
|
627
|
+
try {
|
|
628
|
+
providerContext = yield this.hookExecutor.beforeHooks(provider.hooks, hookContextCopy, hookHints);
|
|
629
|
+
const resolutionDetails = yield this.callProviderResolve(
|
|
630
|
+
provider,
|
|
631
|
+
flagKey,
|
|
632
|
+
defaultValue,
|
|
633
|
+
providerContext || {}
|
|
634
|
+
);
|
|
635
|
+
evaluationDetails = __spreadProps(__spreadValues({}, resolutionDetails), {
|
|
636
|
+
flagMetadata: Object.freeze((_a = resolutionDetails.flagMetadata) != null ? _a : {}),
|
|
637
|
+
flagKey
|
|
638
|
+
});
|
|
639
|
+
yield this.hookExecutor.afterHooks(provider.hooks, hookContextCopy, evaluationDetails, hookHints);
|
|
640
|
+
} catch (error) {
|
|
641
|
+
yield this.hookExecutor.errorHooks(provider.hooks, hookContextCopy, error, hookHints);
|
|
642
|
+
evaluationDetails = this.getErrorEvaluationDetails(flagKey, defaultValue, error);
|
|
643
|
+
}
|
|
644
|
+
yield this.hookExecutor.finallyHooks(provider.hooks, hookContextCopy, evaluationDetails, hookHints);
|
|
645
|
+
return evaluationDetails;
|
|
646
|
+
});
|
|
647
|
+
}
|
|
648
|
+
callProviderResolve(provider, flagKey, defaultValue, context) {
|
|
649
|
+
return __async(this, null, function* () {
|
|
650
|
+
switch (typeof defaultValue) {
|
|
651
|
+
case "string":
|
|
652
|
+
return yield provider.resolveStringEvaluation(flagKey, defaultValue, context, this.logger);
|
|
653
|
+
case "number":
|
|
654
|
+
return yield provider.resolveNumberEvaluation(flagKey, defaultValue, context, this.logger);
|
|
655
|
+
case "object":
|
|
656
|
+
return yield provider.resolveObjectEvaluation(flagKey, defaultValue, context, this.logger);
|
|
657
|
+
case "boolean":
|
|
658
|
+
return yield provider.resolveBooleanEvaluation(flagKey, defaultValue, context, this.logger);
|
|
659
|
+
default:
|
|
660
|
+
throw new GeneralError4("Invalid flag evaluation type");
|
|
661
|
+
}
|
|
662
|
+
});
|
|
663
|
+
}
|
|
664
|
+
get hooks() {
|
|
665
|
+
return [
|
|
666
|
+
{
|
|
667
|
+
before: (hookContext, hints) => __async(this, null, function* () {
|
|
668
|
+
this.hookContexts.set(hookContext.context, hookContext);
|
|
669
|
+
this.hookHints.set(hookContext.context, hints != null ? hints : {});
|
|
670
|
+
return hookContext.context;
|
|
671
|
+
})
|
|
672
|
+
}
|
|
673
|
+
];
|
|
674
|
+
}
|
|
675
|
+
track(trackingEventName, context, trackingEventDetails) {
|
|
676
|
+
var _a, _b;
|
|
677
|
+
for (const providerEntry of this.providerEntries) {
|
|
678
|
+
if (!providerEntry.provider.track) {
|
|
679
|
+
continue;
|
|
680
|
+
}
|
|
681
|
+
const strategyContext = {
|
|
682
|
+
provider: providerEntry.provider,
|
|
683
|
+
providerName: providerEntry.name,
|
|
684
|
+
providerStatus: this.statusTracker.providerStatus(providerEntry.name)
|
|
685
|
+
};
|
|
686
|
+
if (this.evaluationStrategy.shouldTrackWithThisProvider(
|
|
687
|
+
strategyContext,
|
|
688
|
+
context,
|
|
689
|
+
trackingEventName,
|
|
690
|
+
trackingEventDetails
|
|
691
|
+
)) {
|
|
692
|
+
try {
|
|
693
|
+
(_b = (_a = providerEntry.provider).track) == null ? void 0 : _b.call(_a, trackingEventName, context, trackingEventDetails);
|
|
694
|
+
} catch (error) {
|
|
695
|
+
this.logger.error(
|
|
696
|
+
`Error tracking event "${trackingEventName}" with provider "${providerEntry.name}":`,
|
|
697
|
+
error
|
|
698
|
+
);
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
}
|
|
703
|
+
getErrorEvaluationDetails(flagKey, defaultValue, err, flagMetadata = {}) {
|
|
704
|
+
const errorMessage = err == null ? void 0 : err.message;
|
|
705
|
+
const errorCode = (err == null ? void 0 : err.code) || ErrorCode3.GENERAL;
|
|
706
|
+
return {
|
|
707
|
+
errorCode,
|
|
708
|
+
errorMessage,
|
|
709
|
+
value: defaultValue,
|
|
710
|
+
reason: StandardResolutionReasons2.ERROR,
|
|
711
|
+
flagMetadata: Object.freeze(flagMetadata),
|
|
712
|
+
flagKey
|
|
713
|
+
};
|
|
714
|
+
}
|
|
715
|
+
};
|
|
716
|
+
|
|
173
717
|
// src/open-feature.ts
|
|
174
718
|
import {
|
|
175
719
|
OpenFeatureCommonAPI,
|
|
@@ -180,13 +724,14 @@ import {
|
|
|
180
724
|
|
|
181
725
|
// src/client/internal/open-feature-client.ts
|
|
182
726
|
import {
|
|
183
|
-
ErrorCode as
|
|
727
|
+
ErrorCode as ErrorCode4,
|
|
184
728
|
ProviderFatalError,
|
|
185
729
|
ProviderNotReadyError,
|
|
186
730
|
SafeLogger,
|
|
187
|
-
StandardResolutionReasons as
|
|
731
|
+
StandardResolutionReasons as StandardResolutionReasons3,
|
|
188
732
|
instantiateErrorByErrorCode,
|
|
189
|
-
statusMatchesEvent
|
|
733
|
+
statusMatchesEvent,
|
|
734
|
+
MapHookData
|
|
190
735
|
} from "@openfeature/core";
|
|
191
736
|
var OpenFeatureClient = class {
|
|
192
737
|
constructor(providerAccessor, providerStatusAccessor, emitterAccessor, apiContextAccessor, apiHooksAccessor, transactionContextAccessor, globalLogger, options, context = {}) {
|
|
@@ -343,18 +888,21 @@ var OpenFeatureClient = class {
|
|
|
343
888
|
];
|
|
344
889
|
const allHooksReversed = [...allHooks].reverse();
|
|
345
890
|
const mergedContext = this.mergeContexts(invocationContext);
|
|
346
|
-
const
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
891
|
+
const hookContexts = allHooksReversed.map(
|
|
892
|
+
() => Object.freeze({
|
|
893
|
+
flagKey,
|
|
894
|
+
defaultValue,
|
|
895
|
+
flagValueType: flagType,
|
|
896
|
+
clientMetadata: this.metadata,
|
|
897
|
+
providerMetadata: this._provider.metadata,
|
|
898
|
+
context: mergedContext,
|
|
899
|
+
logger: this._logger,
|
|
900
|
+
hookData: new MapHookData()
|
|
901
|
+
})
|
|
902
|
+
);
|
|
355
903
|
let evaluationDetails;
|
|
356
904
|
try {
|
|
357
|
-
const frozenContext = yield this.beforeHooks(allHooks,
|
|
905
|
+
const frozenContext = yield this.beforeHooks(allHooks, hookContexts, mergedContext, options);
|
|
358
906
|
this.shortCircuitIfNotReady();
|
|
359
907
|
const resolution = yield resolver.call(this._provider, flagKey, defaultValue, frozenContext, this._logger);
|
|
360
908
|
const resolutionDetails = __spreadProps(__spreadValues({}, resolution), {
|
|
@@ -363,43 +911,54 @@ var OpenFeatureClient = class {
|
|
|
363
911
|
});
|
|
364
912
|
if (resolutionDetails.errorCode) {
|
|
365
913
|
const err = instantiateErrorByErrorCode(resolutionDetails.errorCode, resolutionDetails.errorMessage);
|
|
366
|
-
yield this.errorHooks(allHooksReversed,
|
|
914
|
+
yield this.errorHooks(allHooksReversed, hookContexts, err, options);
|
|
367
915
|
evaluationDetails = this.getErrorEvaluationDetails(flagKey, defaultValue, err, resolutionDetails.flagMetadata);
|
|
368
916
|
} else {
|
|
369
|
-
yield this.afterHooks(allHooksReversed,
|
|
917
|
+
yield this.afterHooks(allHooksReversed, hookContexts, resolutionDetails, options);
|
|
370
918
|
evaluationDetails = resolutionDetails;
|
|
371
919
|
}
|
|
372
920
|
} catch (err) {
|
|
373
|
-
yield this.errorHooks(allHooksReversed,
|
|
921
|
+
yield this.errorHooks(allHooksReversed, hookContexts, err, options);
|
|
374
922
|
evaluationDetails = this.getErrorEvaluationDetails(flagKey, defaultValue, err);
|
|
375
923
|
}
|
|
376
|
-
yield this.finallyHooks(allHooksReversed,
|
|
924
|
+
yield this.finallyHooks(allHooksReversed, hookContexts, evaluationDetails, options);
|
|
377
925
|
return evaluationDetails;
|
|
378
926
|
});
|
|
379
927
|
}
|
|
380
|
-
beforeHooks(hooks,
|
|
928
|
+
beforeHooks(hooks, hookContexts, mergedContext, options) {
|
|
381
929
|
return __async(this, null, function* () {
|
|
382
930
|
var _a;
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
931
|
+
let accumulatedContext = mergedContext;
|
|
932
|
+
for (const [index, hook] of hooks.entries()) {
|
|
933
|
+
const hookContextIndex = hooks.length - 1 - index;
|
|
934
|
+
const hookContext = hookContexts[hookContextIndex];
|
|
935
|
+
Object.assign(hookContext.context, accumulatedContext);
|
|
936
|
+
const hookResult = yield (_a = hook == null ? void 0 : hook.before) == null ? void 0 : _a.call(hook, hookContext, Object.freeze(options.hookHints));
|
|
937
|
+
if (hookResult) {
|
|
938
|
+
accumulatedContext = __spreadValues(__spreadValues({}, accumulatedContext), hookResult);
|
|
939
|
+
for (let i = 0; i < hooks.length; i++) {
|
|
940
|
+
Object.assign(hookContexts[hookContextIndex].context, accumulatedContext);
|
|
941
|
+
}
|
|
942
|
+
}
|
|
386
943
|
}
|
|
387
|
-
return Object.freeze(
|
|
944
|
+
return Object.freeze(accumulatedContext);
|
|
388
945
|
});
|
|
389
946
|
}
|
|
390
|
-
afterHooks(hooks,
|
|
947
|
+
afterHooks(hooks, hookContexts, evaluationDetails, options) {
|
|
391
948
|
return __async(this, null, function* () {
|
|
392
949
|
var _a;
|
|
393
|
-
for (const hook of hooks) {
|
|
950
|
+
for (const [index, hook] of hooks.entries()) {
|
|
951
|
+
const hookContext = hookContexts[index];
|
|
394
952
|
yield (_a = hook == null ? void 0 : hook.after) == null ? void 0 : _a.call(hook, hookContext, evaluationDetails, options.hookHints);
|
|
395
953
|
}
|
|
396
954
|
});
|
|
397
955
|
}
|
|
398
|
-
errorHooks(hooks,
|
|
956
|
+
errorHooks(hooks, hookContexts, err, options) {
|
|
399
957
|
return __async(this, null, function* () {
|
|
400
958
|
var _a;
|
|
401
|
-
for (const hook of hooks) {
|
|
959
|
+
for (const [index, hook] of hooks.entries()) {
|
|
402
960
|
try {
|
|
961
|
+
const hookContext = hookContexts[index];
|
|
403
962
|
yield (_a = hook == null ? void 0 : hook.error) == null ? void 0 : _a.call(hook, hookContext, err, options.hookHints);
|
|
404
963
|
} catch (err2) {
|
|
405
964
|
this._logger.error(`Unhandled error during 'error' hook: ${err2}`);
|
|
@@ -411,11 +970,12 @@ var OpenFeatureClient = class {
|
|
|
411
970
|
}
|
|
412
971
|
});
|
|
413
972
|
}
|
|
414
|
-
finallyHooks(hooks,
|
|
973
|
+
finallyHooks(hooks, hookContexts, evaluationDetails, options) {
|
|
415
974
|
return __async(this, null, function* () {
|
|
416
975
|
var _a;
|
|
417
|
-
for (const hook of hooks) {
|
|
976
|
+
for (const [index, hook] of hooks.entries()) {
|
|
418
977
|
try {
|
|
978
|
+
const hookContext = hookContexts[index];
|
|
419
979
|
yield (_a = hook == null ? void 0 : hook.finally) == null ? void 0 : _a.call(hook, hookContext, evaluationDetails, options.hookHints);
|
|
420
980
|
} catch (err) {
|
|
421
981
|
this._logger.error(`Unhandled error during 'finally' hook: ${err}`);
|
|
@@ -448,35 +1008,18 @@ var OpenFeatureClient = class {
|
|
|
448
1008
|
}
|
|
449
1009
|
getErrorEvaluationDetails(flagKey, defaultValue, err, flagMetadata = {}) {
|
|
450
1010
|
const errorMessage = err == null ? void 0 : err.message;
|
|
451
|
-
const errorCode = (err == null ? void 0 : err.code) ||
|
|
1011
|
+
const errorCode = (err == null ? void 0 : err.code) || ErrorCode4.GENERAL;
|
|
452
1012
|
return {
|
|
453
1013
|
errorCode,
|
|
454
1014
|
errorMessage,
|
|
455
1015
|
value: defaultValue,
|
|
456
|
-
reason:
|
|
1016
|
+
reason: StandardResolutionReasons3.ERROR,
|
|
457
1017
|
flagMetadata: Object.freeze(flagMetadata),
|
|
458
1018
|
flagKey
|
|
459
1019
|
};
|
|
460
1020
|
}
|
|
461
1021
|
};
|
|
462
1022
|
|
|
463
|
-
// src/events/open-feature-event-emitter.ts
|
|
464
|
-
import { GenericEventEmitter } from "@openfeature/core";
|
|
465
|
-
import { EventEmitter } from "node:events";
|
|
466
|
-
var OpenFeatureEventEmitter = class extends GenericEventEmitter {
|
|
467
|
-
constructor() {
|
|
468
|
-
super();
|
|
469
|
-
this.eventEmitter = new EventEmitter({ captureRejections: true });
|
|
470
|
-
this.eventEmitter.on("error", (err) => {
|
|
471
|
-
var _a;
|
|
472
|
-
(_a = this._logger) == null ? void 0 : _a.error("Error running event handler:", err);
|
|
473
|
-
});
|
|
474
|
-
}
|
|
475
|
-
};
|
|
476
|
-
|
|
477
|
-
// src/events/events.ts
|
|
478
|
-
import { ServerProviderEvents } from "@openfeature/core";
|
|
479
|
-
|
|
480
1023
|
// src/transaction-context/no-op-transaction-context-propagator.ts
|
|
481
1024
|
var NoopTransactionContextPropagator = class {
|
|
482
1025
|
getTransactionContext() {
|
|
@@ -621,14 +1164,23 @@ var OpenFeature = OpenFeatureAPI.getInstance();
|
|
|
621
1164
|
// src/index.ts
|
|
622
1165
|
export * from "@openfeature/core";
|
|
623
1166
|
export {
|
|
1167
|
+
AggregateError,
|
|
624
1168
|
AsyncLocalStorageTransactionContextPropagator,
|
|
1169
|
+
BaseEvaluationStrategy,
|
|
1170
|
+
ComparisonStrategy,
|
|
1171
|
+
ErrorWithCode,
|
|
1172
|
+
FirstMatchStrategy,
|
|
1173
|
+
FirstSuccessfulStrategy,
|
|
625
1174
|
InMemoryProvider,
|
|
1175
|
+
MultiProvider,
|
|
626
1176
|
NOOP_PROVIDER,
|
|
627
1177
|
NOOP_TRANSACTION_CONTEXT_PROPAGATOR,
|
|
628
1178
|
OpenFeature,
|
|
629
1179
|
OpenFeatureAPI,
|
|
630
1180
|
OpenFeatureEventEmitter,
|
|
631
1181
|
ServerProviderEvents as ProviderEvents,
|
|
632
|
-
ServerProviderStatus as ProviderStatus
|
|
1182
|
+
ServerProviderStatus as ProviderStatus,
|
|
1183
|
+
constructAggregateError,
|
|
1184
|
+
throwAggregateErrorFromPromiseResults
|
|
633
1185
|
};
|
|
634
1186
|
//# sourceMappingURL=index.js.map
|