@openfeature/ofrep-web-provider 0.3.0 → 0.3.2

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
@@ -38,7 +38,7 @@ The provider can use headers from either a static header map or a custom header
38
38
  Headers can be given as a list of tuples or as a map of headers.
39
39
 
40
40
  ```ts
41
- import { OFREPWebProvider } from '@openfeature/ofrep-web';
41
+ import { OFREPWebProvider } from '@openfeature/ofrep-web-provider';
42
42
 
43
43
  OpenFeature.setProvider(
44
44
  new OFREPWebProvider({
@@ -52,7 +52,7 @@ OpenFeature.setProvider(
52
52
  ```
53
53
 
54
54
  ```ts
55
- import { OFREPWebProvider } from '@openfeature/ofrep-web';
55
+ import { OFREPWebProvider } from '@openfeature/ofrep-web-provider';
56
56
 
57
57
  OpenFeature.setProvider(
58
58
  new OFREPWebProvider({
@@ -69,7 +69,7 @@ The header factory is evaluated before every flag evaluation which makes it poss
69
69
  The following shows an example of loading a token and using it as bearer token.
70
70
 
71
71
  ```ts
72
- import { OFREPWebProvider } from '@openfeature/ofrep-web';
72
+ import { OFREPWebProvider } from '@openfeature/ofrep-web-provider';
73
73
 
74
74
  OpenFeature.setProvider(
75
75
  new OFREPWebProvider({
@@ -87,7 +87,7 @@ OpenFeature.setProvider(
87
87
  If needed, a custom fetch implementation can be injected, if e.g. the platform does not have fetch built in.
88
88
 
89
89
  ```ts
90
- import { OFREPWebProvider } from '@openfeature/ofrep-web';
90
+ import { OFREPWebProvider } from '@openfeature/ofrep-web-provider';
91
91
  import { fetchPolyfill } from 'some-fetch-polyfill';
92
92
 
93
93
  OpenFeature.setProvider(
package/index.cjs.js CHANGED
@@ -1,12 +1,40 @@
1
1
  'use strict';
2
2
 
3
- Object.defineProperty(exports, '__esModule', { value: true });
4
-
5
- var tslib = require('tslib');
6
- var core = require('@openfeature/core');
7
3
  var ofrepCore = require('@openfeature/ofrep-core');
8
4
  var webSdk = require('@openfeature/web-sdk');
9
5
 
6
+ /******************************************************************************
7
+ Copyright (c) Microsoft Corporation.
8
+
9
+ Permission to use, copy, modify, and/or distribute this software for any
10
+ purpose with or without fee is hereby granted.
11
+
12
+ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
13
+ REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
14
+ AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
15
+ INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
16
+ LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
17
+ OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
18
+ PERFORMANCE OF THIS SOFTWARE.
19
+ ***************************************************************************** */
20
+ /* global Reflect, Promise, SuppressedError, Symbol, Iterator */
21
+
22
+
23
+ function __awaiter(thisArg, _arguments, P, generator) {
24
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
25
+ return new (P || (P = Promise))(function (resolve, reject) {
26
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
27
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
28
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
29
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
30
+ });
31
+ }
32
+
33
+ typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
34
+ var e = new Error(message);
35
+ return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
36
+ };
37
+
10
38
  var BulkEvaluationStatus;
11
39
  (function (BulkEvaluationStatus) {
12
40
  BulkEvaluationStatus["SUCCESS_NO_CHANGES"] = "SUCCESS_NO_CHANGES";
@@ -20,6 +48,16 @@ function isResolutionError(response) {
20
48
  return 'reason' in response && 'errorCode' in response && !('value' in response);
21
49
  }
22
50
 
51
+ const ErrorMessageMap = {
52
+ [webSdk.ErrorCode.FLAG_NOT_FOUND]: 'Flag was not found',
53
+ [webSdk.ErrorCode.GENERAL]: 'General error',
54
+ [webSdk.ErrorCode.INVALID_CONTEXT]: 'Context is invalid or could be parsed',
55
+ [webSdk.ErrorCode.PARSE_ERROR]: 'Flag or flag configuration could not be parsed',
56
+ [webSdk.ErrorCode.PROVIDER_FATAL]: 'Provider is in a fatal error state',
57
+ [webSdk.ErrorCode.PROVIDER_NOT_READY]: 'Provider is not yet ready',
58
+ [webSdk.ErrorCode.TARGETING_KEY_MISSING]: 'Targeting key is missing',
59
+ [webSdk.ErrorCode.TYPE_MISMATCH]: 'Flag is not of expected type',
60
+ };
23
61
  class OFREPWebProvider {
24
62
  constructor(options, logger) {
25
63
  var _a;
@@ -30,11 +68,12 @@ class OFREPWebProvider {
30
68
  this.runsOn = 'client';
31
69
  this.events = new webSdk.OpenFeatureEventEmitter();
32
70
  this._flagCache = {};
71
+ this._flagSetMetadataCache = {};
33
72
  this._options = options;
34
73
  this._logger = logger;
35
74
  this._etag = null;
36
75
  this._ofrepAPI = new ofrepCore.OFREPApi(this._options, this._options.fetchImplementation);
37
- this._pollingInterval = (_a = this._options.pollInterval) !== null && _a !== void 0 ? _a : this.DEFAULT_POLL_INTERVAL;
76
+ this._pollingInterval = (_a = this._options.pollInterval) !== null && _a !== undefined ? _a : this.DEFAULT_POLL_INTERVAL;
38
77
  }
39
78
  /**
40
79
  * Returns a shallow copy of the flag cache, which is updated at initialization/context-change/configuration-change once the flags are re-evaluated.
@@ -47,11 +86,11 @@ class OFREPWebProvider {
47
86
  * @param context - the context to use for the evaluation
48
87
  */
49
88
  initialize(context) {
50
- return tslib.__awaiter(this, void 0, void 0, function* () {
89
+ return __awaiter(this, undefined, undefined, function* () {
51
90
  var _a;
52
91
  try {
53
92
  this._context = context;
54
- yield this._evaluateFlags(context);
93
+ yield this._fetchFlags(context);
55
94
  if (this._pollingInterval > 0) {
56
95
  this.startPolling();
57
96
  }
@@ -59,7 +98,7 @@ class OFREPWebProvider {
59
98
  }
60
99
  catch (error) {
61
100
  if (error instanceof ofrepCore.OFREPApiUnauthorizedError || error instanceof ofrepCore.OFREPForbiddenError) {
62
- throw new webSdk.ProviderFatalError('Initialization failed', error);
101
+ throw new webSdk.ProviderFatalError('Initialization failed', { cause: error });
63
102
  }
64
103
  throw error;
65
104
  }
@@ -68,18 +107,17 @@ class OFREPWebProvider {
68
107
  /* eslint-disable @typescript-eslint/no-unused-vars*/
69
108
  /* to make overrides easier we keep these unused vars */
70
109
  resolveBooleanEvaluation(flagKey, defaultValue, context) {
71
- return this.evaluate(flagKey, 'boolean');
110
+ return this._resolve(flagKey, 'boolean', defaultValue);
72
111
  }
73
112
  resolveStringEvaluation(flagKey, defaultValue, context) {
74
- return this.evaluate(flagKey, 'string');
113
+ return this._resolve(flagKey, 'string', defaultValue);
75
114
  }
76
115
  resolveNumberEvaluation(flagKey, defaultValue, context) {
77
- return this.evaluate(flagKey, 'number');
116
+ return this._resolve(flagKey, 'number', defaultValue);
78
117
  }
79
118
  resolveObjectEvaluation(flagKey, defaultValue, context) {
80
- return this.evaluate(flagKey, 'object');
119
+ return this._resolve(flagKey, 'object', defaultValue);
81
120
  }
82
- /* eslint-enable @typescript-eslint/no-unused-vars */
83
121
  /**
84
122
  * onContextChange is called when the context changes, it will re-evaluate the flags with the new context
85
123
  * and update the cache.
@@ -87,7 +125,7 @@ class OFREPWebProvider {
87
125
  * @param newContext - the new evaluation context
88
126
  */
89
127
  onContextChange(oldContext, newContext) {
90
- return tslib.__awaiter(this, void 0, void 0, function* () {
128
+ return __awaiter(this, undefined, undefined, function* () {
91
129
  var _a, _b, _c;
92
130
  try {
93
131
  this._context = newContext;
@@ -96,21 +134,21 @@ class OFREPWebProvider {
96
134
  // we do nothing because we should not call the endpoint
97
135
  return;
98
136
  }
99
- yield this._evaluateFlags(newContext);
137
+ yield this._fetchFlags(newContext);
100
138
  }
101
139
  catch (error) {
102
140
  if (error instanceof ofrepCore.OFREPApiTooManyRequestsError) {
103
- (_a = this.events) === null || _a === void 0 ? void 0 : _a.emit(webSdk.ClientProviderEvents.Stale, { message: `${error.name}: ${error.message}` });
141
+ (_a = this.events) === null || _a === undefined ? undefined : _a.emit(webSdk.ClientProviderEvents.Stale, { message: `${error.name}: ${error.message}` });
104
142
  return;
105
143
  }
106
144
  if (error instanceof webSdk.OpenFeatureError ||
107
145
  error instanceof ofrepCore.OFREPApiFetchError ||
108
146
  error instanceof ofrepCore.OFREPApiUnauthorizedError ||
109
147
  error instanceof ofrepCore.OFREPForbiddenError) {
110
- (_b = this.events) === null || _b === void 0 ? void 0 : _b.emit(webSdk.ClientProviderEvents.Error, { message: `${error.name}: ${error.message}` });
148
+ (_b = this.events) === null || _b === undefined ? undefined : _b.emit(webSdk.ClientProviderEvents.Error, { message: `${error.name}: ${error.message}` });
111
149
  return;
112
150
  }
113
- (_c = this.events) === null || _c === void 0 ? void 0 : _c.emit(webSdk.ClientProviderEvents.Error, { message: `Unknown error: ${error}` });
151
+ (_c = this.events) === null || _c === undefined ? undefined : _c.emit(webSdk.ClientProviderEvents.Error, { message: `Unknown error: ${error}` });
114
152
  }
115
153
  });
116
154
  }
@@ -122,7 +160,7 @@ class OFREPWebProvider {
122
160
  return Promise.resolve();
123
161
  }
124
162
  /**
125
- * _evaluateFlags is a function that will call the bulk evaluate flags endpoint to get the flags values.
163
+ * _fetchFlags is a function that will call the bulk evaluate flags endpoint to get the flags values.
126
164
  * @param context - the context to use for the evaluation
127
165
  * @private
128
166
  * @returns EvaluationStatus if the evaluation the API returned a 304, 200.
@@ -131,9 +169,9 @@ class OFREPWebProvider {
131
169
  * @throws ParseError if the API returned a 400 with the error code ParseError
132
170
  * @throws GeneralError if the API returned a 400 with an unknown error code
133
171
  */
134
- _evaluateFlags(context) {
135
- return tslib.__awaiter(this, void 0, void 0, function* () {
136
- var _a, _b;
172
+ _fetchFlags(context) {
173
+ return __awaiter(this, undefined, undefined, function* () {
174
+ var _a;
137
175
  try {
138
176
  const evalReq = {
139
177
  context,
@@ -144,31 +182,38 @@ class OFREPWebProvider {
144
182
  return { status: BulkEvaluationStatus.SUCCESS_NO_CHANGES, flags: [] };
145
183
  }
146
184
  if (response.httpStatus !== 200) {
147
- ofrepCore.handleEvaluationError(response);
185
+ throw new webSdk.GeneralError(`Failed OFREP bulk evaluation request, status: ${response.httpStatus}`);
148
186
  }
149
187
  const bulkSuccessResp = response.value;
150
188
  const newCache = {};
151
- (_a = bulkSuccessResp.flags) === null || _a === void 0 ? void 0 : _a.forEach((evalResp) => {
152
- if (ofrepCore.isEvaluationFailureResponse(evalResp)) {
153
- newCache[evalResp.key] = {
154
- errorCode: evalResp.errorCode,
155
- errorDetails: evalResp.errorDetails,
156
- reason: core.StandardResolutionReasons.ERROR,
157
- };
158
- }
159
- if (ofrepCore.isEvaluationSuccessResponse(evalResp) && evalResp.key) {
160
- newCache[evalResp.key] = {
161
- value: evalResp.value,
162
- flagMetadata: evalResp.metadata,
163
- reason: evalResp.reason,
164
- variant: evalResp.variant,
165
- };
166
- }
167
- });
168
- const listUpdatedFlags = this._getListUpdatedFlags(this._flagCache, newCache);
169
- this._flagCache = newCache;
170
- this._etag = (_b = response.httpResponse) === null || _b === void 0 ? void 0 : _b.headers.get('etag');
171
- return { status: BulkEvaluationStatus.SUCCESS_WITH_CHANGES, flags: listUpdatedFlags };
189
+ if ('flags' in bulkSuccessResp && Array.isArray(bulkSuccessResp.flags)) {
190
+ bulkSuccessResp.flags.forEach((evalResp) => {
191
+ if (ofrepCore.isEvaluationFailureResponse(evalResp)) {
192
+ newCache[evalResp.key] = {
193
+ reason: webSdk.StandardResolutionReasons.ERROR,
194
+ flagMetadata: evalResp.metadata,
195
+ errorCode: evalResp.errorCode,
196
+ errorDetails: evalResp.errorDetails,
197
+ };
198
+ }
199
+ if (ofrepCore.isEvaluationSuccessResponse(evalResp) && evalResp.key) {
200
+ newCache[evalResp.key] = {
201
+ value: evalResp.value,
202
+ variant: evalResp.variant,
203
+ reason: evalResp.reason,
204
+ flagMetadata: evalResp.metadata,
205
+ };
206
+ }
207
+ });
208
+ const listUpdatedFlags = this._getListUpdatedFlags(this._flagCache, newCache);
209
+ this._flagCache = newCache;
210
+ this._etag = (_a = response.httpResponse) === null || _a === void 0 ? void 0 : _a.headers.get('etag');
211
+ this._flagSetMetadataCache = typeof bulkSuccessResp.metadata === 'object' ? bulkSuccessResp.metadata : {};
212
+ return { status: BulkEvaluationStatus.SUCCESS_WITH_CHANGES, flags: listUpdatedFlags };
213
+ }
214
+ else {
215
+ throw new Error('No flags in OFREP bulk evaluation response');
216
+ }
172
217
  }
173
218
  catch (error) {
174
219
  if (error instanceof ofrepCore.OFREPApiTooManyRequestsError && error.retryAfterDate !== null) {
@@ -204,33 +249,34 @@ class OFREPWebProvider {
204
249
  return changedKeys;
205
250
  }
206
251
  /**
207
- * Evaluate is a function retrieving the value from a flag in the cache.
252
+ * _resolve is a function retrieving the value from a flag in the cache.
208
253
  * @param flagKey - name of the flag to retrieve
209
254
  * @param type - type of the flag
255
+ * @param defaultValue - default value
210
256
  * @private
211
257
  */
212
- evaluate(flagKey, type) {
258
+ _resolve(flagKey, type, defaultValue) {
213
259
  const resolved = this._flagCache[flagKey];
214
260
  if (!resolved) {
215
- throw new webSdk.FlagNotFoundError(`flag key ${flagKey} not found in cache`);
261
+ return {
262
+ value: defaultValue,
263
+ flagMetadata: this._flagSetMetadataCache,
264
+ reason: webSdk.StandardResolutionReasons.ERROR,
265
+ errorCode: webSdk.ErrorCode.FLAG_NOT_FOUND,
266
+ errorMessage: ErrorMessageMap[webSdk.ErrorCode.FLAG_NOT_FOUND],
267
+ };
216
268
  }
217
269
  if (isResolutionError(resolved)) {
218
- switch (resolved.errorCode) {
219
- case ofrepCore.EvaluationFailureErrorCode.FlagNotFound:
220
- throw new webSdk.FlagNotFoundError(`flag key ${flagKey} not found: ${resolved.errorDetails}`);
221
- case ofrepCore.EvaluationFailureErrorCode.TargetingKeyMissing:
222
- throw new core.TargetingKeyMissingError(`targeting key missing for flag key ${flagKey}: ${resolved.errorDetails}`);
223
- case ofrepCore.EvaluationFailureErrorCode.InvalidContext:
224
- throw new core.InvalidContextError(`invalid context for flag key ${flagKey}: ${resolved.errorDetails}`);
225
- case ofrepCore.EvaluationFailureErrorCode.ParseError:
226
- throw new core.ParseError(`parse error for flag key ${flagKey}: ${resolved.errorDetails}`);
227
- case ofrepCore.EvaluationFailureErrorCode.General:
228
- default:
229
- throw new webSdk.GeneralError(`general error during flag evaluation for flag key ${flagKey}: ${resolved.errorDetails}`);
230
- }
270
+ return Object.assign(Object.assign({}, resolved), { value: defaultValue, errorMessage: ErrorMessageMap[resolved.errorCode] });
231
271
  }
232
272
  if (typeof resolved.value !== type) {
233
- throw new webSdk.TypeMismatchError(`flag key ${flagKey} is not of type ${type}`);
273
+ return {
274
+ value: defaultValue,
275
+ flagMetadata: resolved.flagMetadata,
276
+ reason: webSdk.StandardResolutionReasons.ERROR,
277
+ errorCode: webSdk.ErrorCode.TYPE_MISMATCH,
278
+ errorMessage: ErrorMessageMap[webSdk.ErrorCode.TYPE_MISMATCH],
279
+ };
234
280
  }
235
281
  return {
236
282
  variant: resolved.variant,
@@ -246,14 +292,14 @@ class OFREPWebProvider {
246
292
  * @private
247
293
  */
248
294
  startPolling() {
249
- this._pollingIntervalId = setInterval(() => tslib.__awaiter(this, void 0, void 0, function* () {
295
+ this._pollingIntervalId = setInterval(() => __awaiter(this, undefined, undefined, function* () {
250
296
  var _a, _b;
251
297
  try {
252
298
  const now = new Date();
253
299
  if (this._retryPollingAfter !== undefined && this._retryPollingAfter > now) {
254
300
  return;
255
301
  }
256
- const res = yield this._evaluateFlags(this._context);
302
+ const res = yield this._fetchFlags(this._context);
257
303
  if (res.status === BulkEvaluationStatus.SUCCESS_WITH_CHANGES) {
258
304
  (_a = this.events) === null || _a === void 0 ? void 0 : _a.emit(webSdk.ClientProviderEvents.ConfigurationChanged, {
259
305
  message: 'Flags updated',
@@ -262,7 +308,7 @@ class OFREPWebProvider {
262
308
  }
263
309
  }
264
310
  catch (error) {
265
- (_b = this.events) === null || _b === void 0 ? void 0 : _b.emit(webSdk.ClientProviderEvents.Stale, { message: `Error while polling: ${error}` });
311
+ (_b = this.events) === null || _b === undefined ? undefined : _b.emit(webSdk.ClientProviderEvents.Stale, { message: `Error while polling: ${error}` });
266
312
  }
267
313
  }), this._pollingInterval);
268
314
  }
package/index.esm.d.ts ADDED
@@ -0,0 +1 @@
1
+ export * from "./src/index";
package/index.esm.js CHANGED
@@ -1,7 +1,37 @@
1
- import { __awaiter } from 'tslib';
2
- import { StandardResolutionReasons, ParseError, InvalidContextError, TargetingKeyMissingError } from '@openfeature/core';
3
- import { OFREPApi, OFREPApiUnauthorizedError, OFREPForbiddenError, OFREPApiTooManyRequestsError, OFREPApiFetchError, handleEvaluationError, isEvaluationFailureResponse, isEvaluationSuccessResponse, EvaluationFailureErrorCode } from '@openfeature/ofrep-core';
4
- import { OpenFeatureEventEmitter, ProviderFatalError, ClientProviderEvents, OpenFeatureError, FlagNotFoundError, GeneralError, TypeMismatchError } from '@openfeature/web-sdk';
1
+ import { OFREPApi, OFREPApiUnauthorizedError, OFREPForbiddenError, OFREPApiTooManyRequestsError, OFREPApiFetchError, isEvaluationFailureResponse, isEvaluationSuccessResponse } from '@openfeature/ofrep-core';
2
+ import { ErrorCode, OpenFeatureEventEmitter, StandardResolutionReasons, ProviderFatalError, ClientProviderEvents, OpenFeatureError, GeneralError } from '@openfeature/web-sdk';
3
+
4
+ /******************************************************************************
5
+ Copyright (c) Microsoft Corporation.
6
+
7
+ Permission to use, copy, modify, and/or distribute this software for any
8
+ purpose with or without fee is hereby granted.
9
+
10
+ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
11
+ REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
12
+ AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
13
+ INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
14
+ LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
15
+ OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
16
+ PERFORMANCE OF THIS SOFTWARE.
17
+ ***************************************************************************** */
18
+ /* global Reflect, Promise, SuppressedError, Symbol, Iterator */
19
+
20
+
21
+ function __awaiter(thisArg, _arguments, P, generator) {
22
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
23
+ return new (P || (P = Promise))(function (resolve, reject) {
24
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
25
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
26
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
27
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
28
+ });
29
+ }
30
+
31
+ typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
32
+ var e = new Error(message);
33
+ return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
34
+ };
5
35
 
6
36
  var BulkEvaluationStatus;
7
37
  (function (BulkEvaluationStatus) {
@@ -16,6 +46,16 @@ function isResolutionError(response) {
16
46
  return 'reason' in response && 'errorCode' in response && !('value' in response);
17
47
  }
18
48
 
49
+ const ErrorMessageMap = {
50
+ [ErrorCode.FLAG_NOT_FOUND]: 'Flag was not found',
51
+ [ErrorCode.GENERAL]: 'General error',
52
+ [ErrorCode.INVALID_CONTEXT]: 'Context is invalid or could be parsed',
53
+ [ErrorCode.PARSE_ERROR]: 'Flag or flag configuration could not be parsed',
54
+ [ErrorCode.PROVIDER_FATAL]: 'Provider is in a fatal error state',
55
+ [ErrorCode.PROVIDER_NOT_READY]: 'Provider is not yet ready',
56
+ [ErrorCode.TARGETING_KEY_MISSING]: 'Targeting key is missing',
57
+ [ErrorCode.TYPE_MISMATCH]: 'Flag is not of expected type',
58
+ };
19
59
  class OFREPWebProvider {
20
60
  constructor(options, logger) {
21
61
  var _a;
@@ -26,11 +66,12 @@ class OFREPWebProvider {
26
66
  this.runsOn = 'client';
27
67
  this.events = new OpenFeatureEventEmitter();
28
68
  this._flagCache = {};
69
+ this._flagSetMetadataCache = {};
29
70
  this._options = options;
30
71
  this._logger = logger;
31
72
  this._etag = null;
32
73
  this._ofrepAPI = new OFREPApi(this._options, this._options.fetchImplementation);
33
- this._pollingInterval = (_a = this._options.pollInterval) !== null && _a !== void 0 ? _a : this.DEFAULT_POLL_INTERVAL;
74
+ this._pollingInterval = (_a = this._options.pollInterval) !== null && _a !== undefined ? _a : this.DEFAULT_POLL_INTERVAL;
34
75
  }
35
76
  /**
36
77
  * Returns a shallow copy of the flag cache, which is updated at initialization/context-change/configuration-change once the flags are re-evaluated.
@@ -43,11 +84,11 @@ class OFREPWebProvider {
43
84
  * @param context - the context to use for the evaluation
44
85
  */
45
86
  initialize(context) {
46
- return __awaiter(this, void 0, void 0, function* () {
87
+ return __awaiter(this, undefined, undefined, function* () {
47
88
  var _a;
48
89
  try {
49
90
  this._context = context;
50
- yield this._evaluateFlags(context);
91
+ yield this._fetchFlags(context);
51
92
  if (this._pollingInterval > 0) {
52
93
  this.startPolling();
53
94
  }
@@ -55,7 +96,7 @@ class OFREPWebProvider {
55
96
  }
56
97
  catch (error) {
57
98
  if (error instanceof OFREPApiUnauthorizedError || error instanceof OFREPForbiddenError) {
58
- throw new ProviderFatalError('Initialization failed', error);
99
+ throw new ProviderFatalError('Initialization failed', { cause: error });
59
100
  }
60
101
  throw error;
61
102
  }
@@ -64,18 +105,17 @@ class OFREPWebProvider {
64
105
  /* eslint-disable @typescript-eslint/no-unused-vars*/
65
106
  /* to make overrides easier we keep these unused vars */
66
107
  resolveBooleanEvaluation(flagKey, defaultValue, context) {
67
- return this.evaluate(flagKey, 'boolean');
108
+ return this._resolve(flagKey, 'boolean', defaultValue);
68
109
  }
69
110
  resolveStringEvaluation(flagKey, defaultValue, context) {
70
- return this.evaluate(flagKey, 'string');
111
+ return this._resolve(flagKey, 'string', defaultValue);
71
112
  }
72
113
  resolveNumberEvaluation(flagKey, defaultValue, context) {
73
- return this.evaluate(flagKey, 'number');
114
+ return this._resolve(flagKey, 'number', defaultValue);
74
115
  }
75
116
  resolveObjectEvaluation(flagKey, defaultValue, context) {
76
- return this.evaluate(flagKey, 'object');
117
+ return this._resolve(flagKey, 'object', defaultValue);
77
118
  }
78
- /* eslint-enable @typescript-eslint/no-unused-vars */
79
119
  /**
80
120
  * onContextChange is called when the context changes, it will re-evaluate the flags with the new context
81
121
  * and update the cache.
@@ -83,7 +123,7 @@ class OFREPWebProvider {
83
123
  * @param newContext - the new evaluation context
84
124
  */
85
125
  onContextChange(oldContext, newContext) {
86
- return __awaiter(this, void 0, void 0, function* () {
126
+ return __awaiter(this, undefined, undefined, function* () {
87
127
  var _a, _b, _c;
88
128
  try {
89
129
  this._context = newContext;
@@ -92,21 +132,21 @@ class OFREPWebProvider {
92
132
  // we do nothing because we should not call the endpoint
93
133
  return;
94
134
  }
95
- yield this._evaluateFlags(newContext);
135
+ yield this._fetchFlags(newContext);
96
136
  }
97
137
  catch (error) {
98
138
  if (error instanceof OFREPApiTooManyRequestsError) {
99
- (_a = this.events) === null || _a === void 0 ? void 0 : _a.emit(ClientProviderEvents.Stale, { message: `${error.name}: ${error.message}` });
139
+ (_a = this.events) === null || _a === undefined ? undefined : _a.emit(ClientProviderEvents.Stale, { message: `${error.name}: ${error.message}` });
100
140
  return;
101
141
  }
102
142
  if (error instanceof OpenFeatureError ||
103
143
  error instanceof OFREPApiFetchError ||
104
144
  error instanceof OFREPApiUnauthorizedError ||
105
145
  error instanceof OFREPForbiddenError) {
106
- (_b = this.events) === null || _b === void 0 ? void 0 : _b.emit(ClientProviderEvents.Error, { message: `${error.name}: ${error.message}` });
146
+ (_b = this.events) === null || _b === undefined ? undefined : _b.emit(ClientProviderEvents.Error, { message: `${error.name}: ${error.message}` });
107
147
  return;
108
148
  }
109
- (_c = this.events) === null || _c === void 0 ? void 0 : _c.emit(ClientProviderEvents.Error, { message: `Unknown error: ${error}` });
149
+ (_c = this.events) === null || _c === undefined ? undefined : _c.emit(ClientProviderEvents.Error, { message: `Unknown error: ${error}` });
110
150
  }
111
151
  });
112
152
  }
@@ -118,7 +158,7 @@ class OFREPWebProvider {
118
158
  return Promise.resolve();
119
159
  }
120
160
  /**
121
- * _evaluateFlags is a function that will call the bulk evaluate flags endpoint to get the flags values.
161
+ * _fetchFlags is a function that will call the bulk evaluate flags endpoint to get the flags values.
122
162
  * @param context - the context to use for the evaluation
123
163
  * @private
124
164
  * @returns EvaluationStatus if the evaluation the API returned a 304, 200.
@@ -127,9 +167,9 @@ class OFREPWebProvider {
127
167
  * @throws ParseError if the API returned a 400 with the error code ParseError
128
168
  * @throws GeneralError if the API returned a 400 with an unknown error code
129
169
  */
130
- _evaluateFlags(context) {
131
- return __awaiter(this, void 0, void 0, function* () {
132
- var _a, _b;
170
+ _fetchFlags(context) {
171
+ return __awaiter(this, undefined, undefined, function* () {
172
+ var _a;
133
173
  try {
134
174
  const evalReq = {
135
175
  context,
@@ -140,31 +180,38 @@ class OFREPWebProvider {
140
180
  return { status: BulkEvaluationStatus.SUCCESS_NO_CHANGES, flags: [] };
141
181
  }
142
182
  if (response.httpStatus !== 200) {
143
- handleEvaluationError(response);
183
+ throw new GeneralError(`Failed OFREP bulk evaluation request, status: ${response.httpStatus}`);
144
184
  }
145
185
  const bulkSuccessResp = response.value;
146
186
  const newCache = {};
147
- (_a = bulkSuccessResp.flags) === null || _a === void 0 ? void 0 : _a.forEach((evalResp) => {
148
- if (isEvaluationFailureResponse(evalResp)) {
149
- newCache[evalResp.key] = {
150
- errorCode: evalResp.errorCode,
151
- errorDetails: evalResp.errorDetails,
152
- reason: StandardResolutionReasons.ERROR,
153
- };
154
- }
155
- if (isEvaluationSuccessResponse(evalResp) && evalResp.key) {
156
- newCache[evalResp.key] = {
157
- value: evalResp.value,
158
- flagMetadata: evalResp.metadata,
159
- reason: evalResp.reason,
160
- variant: evalResp.variant,
161
- };
162
- }
163
- });
164
- const listUpdatedFlags = this._getListUpdatedFlags(this._flagCache, newCache);
165
- this._flagCache = newCache;
166
- this._etag = (_b = response.httpResponse) === null || _b === void 0 ? void 0 : _b.headers.get('etag');
167
- return { status: BulkEvaluationStatus.SUCCESS_WITH_CHANGES, flags: listUpdatedFlags };
187
+ if ('flags' in bulkSuccessResp && Array.isArray(bulkSuccessResp.flags)) {
188
+ bulkSuccessResp.flags.forEach((evalResp) => {
189
+ if (isEvaluationFailureResponse(evalResp)) {
190
+ newCache[evalResp.key] = {
191
+ reason: StandardResolutionReasons.ERROR,
192
+ flagMetadata: evalResp.metadata,
193
+ errorCode: evalResp.errorCode,
194
+ errorDetails: evalResp.errorDetails,
195
+ };
196
+ }
197
+ if (isEvaluationSuccessResponse(evalResp) && evalResp.key) {
198
+ newCache[evalResp.key] = {
199
+ value: evalResp.value,
200
+ variant: evalResp.variant,
201
+ reason: evalResp.reason,
202
+ flagMetadata: evalResp.metadata,
203
+ };
204
+ }
205
+ });
206
+ const listUpdatedFlags = this._getListUpdatedFlags(this._flagCache, newCache);
207
+ this._flagCache = newCache;
208
+ this._etag = (_a = response.httpResponse) === null || _a === void 0 ? void 0 : _a.headers.get('etag');
209
+ this._flagSetMetadataCache = typeof bulkSuccessResp.metadata === 'object' ? bulkSuccessResp.metadata : {};
210
+ return { status: BulkEvaluationStatus.SUCCESS_WITH_CHANGES, flags: listUpdatedFlags };
211
+ }
212
+ else {
213
+ throw new Error('No flags in OFREP bulk evaluation response');
214
+ }
168
215
  }
169
216
  catch (error) {
170
217
  if (error instanceof OFREPApiTooManyRequestsError && error.retryAfterDate !== null) {
@@ -200,33 +247,34 @@ class OFREPWebProvider {
200
247
  return changedKeys;
201
248
  }
202
249
  /**
203
- * Evaluate is a function retrieving the value from a flag in the cache.
250
+ * _resolve is a function retrieving the value from a flag in the cache.
204
251
  * @param flagKey - name of the flag to retrieve
205
252
  * @param type - type of the flag
253
+ * @param defaultValue - default value
206
254
  * @private
207
255
  */
208
- evaluate(flagKey, type) {
256
+ _resolve(flagKey, type, defaultValue) {
209
257
  const resolved = this._flagCache[flagKey];
210
258
  if (!resolved) {
211
- throw new FlagNotFoundError(`flag key ${flagKey} not found in cache`);
259
+ return {
260
+ value: defaultValue,
261
+ flagMetadata: this._flagSetMetadataCache,
262
+ reason: StandardResolutionReasons.ERROR,
263
+ errorCode: ErrorCode.FLAG_NOT_FOUND,
264
+ errorMessage: ErrorMessageMap[ErrorCode.FLAG_NOT_FOUND],
265
+ };
212
266
  }
213
267
  if (isResolutionError(resolved)) {
214
- switch (resolved.errorCode) {
215
- case EvaluationFailureErrorCode.FlagNotFound:
216
- throw new FlagNotFoundError(`flag key ${flagKey} not found: ${resolved.errorDetails}`);
217
- case EvaluationFailureErrorCode.TargetingKeyMissing:
218
- throw new TargetingKeyMissingError(`targeting key missing for flag key ${flagKey}: ${resolved.errorDetails}`);
219
- case EvaluationFailureErrorCode.InvalidContext:
220
- throw new InvalidContextError(`invalid context for flag key ${flagKey}: ${resolved.errorDetails}`);
221
- case EvaluationFailureErrorCode.ParseError:
222
- throw new ParseError(`parse error for flag key ${flagKey}: ${resolved.errorDetails}`);
223
- case EvaluationFailureErrorCode.General:
224
- default:
225
- throw new GeneralError(`general error during flag evaluation for flag key ${flagKey}: ${resolved.errorDetails}`);
226
- }
268
+ return Object.assign(Object.assign({}, resolved), { value: defaultValue, errorMessage: ErrorMessageMap[resolved.errorCode] });
227
269
  }
228
270
  if (typeof resolved.value !== type) {
229
- throw new TypeMismatchError(`flag key ${flagKey} is not of type ${type}`);
271
+ return {
272
+ value: defaultValue,
273
+ flagMetadata: resolved.flagMetadata,
274
+ reason: StandardResolutionReasons.ERROR,
275
+ errorCode: ErrorCode.TYPE_MISMATCH,
276
+ errorMessage: ErrorMessageMap[ErrorCode.TYPE_MISMATCH],
277
+ };
230
278
  }
231
279
  return {
232
280
  variant: resolved.variant,
@@ -242,14 +290,14 @@ class OFREPWebProvider {
242
290
  * @private
243
291
  */
244
292
  startPolling() {
245
- this._pollingIntervalId = setInterval(() => __awaiter(this, void 0, void 0, function* () {
293
+ this._pollingIntervalId = setInterval(() => __awaiter(this, undefined, undefined, function* () {
246
294
  var _a, _b;
247
295
  try {
248
296
  const now = new Date();
249
297
  if (this._retryPollingAfter !== undefined && this._retryPollingAfter > now) {
250
298
  return;
251
299
  }
252
- const res = yield this._evaluateFlags(this._context);
300
+ const res = yield this._fetchFlags(this._context);
253
301
  if (res.status === BulkEvaluationStatus.SUCCESS_WITH_CHANGES) {
254
302
  (_a = this.events) === null || _a === void 0 ? void 0 : _a.emit(ClientProviderEvents.ConfigurationChanged, {
255
303
  message: 'Flags updated',
@@ -258,7 +306,7 @@ class OFREPWebProvider {
258
306
  }
259
307
  }
260
308
  catch (error) {
261
- (_b = this.events) === null || _b === void 0 ? void 0 : _b.emit(ClientProviderEvents.Stale, { message: `Error while polling: ${error}` });
309
+ (_b = this.events) === null || _b === undefined ? undefined : _b.emit(ClientProviderEvents.Stale, { message: `Error while polling: ${error}` });
262
310
  }
263
311
  }), this._pollingInterval);
264
312
  }
package/package.json CHANGED
@@ -1,10 +1,7 @@
1
1
  {
2
2
  "name": "@openfeature/ofrep-web-provider",
3
- "version": "0.3.0",
4
- "dependencies": {
5
- "tslib": "^2.3.0",
6
- "@openfeature/ofrep-core": "0.2.0"
7
- },
3
+ "version": "0.3.2",
4
+ "license": "Apache-2.0",
8
5
  "main": "./index.cjs.js",
9
6
  "typings": "./src/index.d.ts",
10
7
  "scripts": {
@@ -12,15 +9,21 @@
12
9
  "current-version": "echo $npm_package_version"
13
10
  },
14
11
  "peerDependencies": {
15
- "@openfeature/web-sdk": ">=0.4.0"
12
+ "@openfeature/web-sdk": "^1.4.0"
13
+ },
14
+ "dependencies": {
15
+ "undici": "^5.0.0",
16
+ "@openfeature/ofrep-core": "^1.0.0"
16
17
  },
17
18
  "exports": {
18
19
  "./package.json": "./package.json",
19
20
  ".": {
20
21
  "module": "./index.esm.js",
22
+ "types": "./index.esm.d.ts",
21
23
  "import": "./index.cjs.mjs",
22
24
  "default": "./index.cjs.js"
23
25
  }
24
26
  },
25
- "module": "./index.esm.js"
26
- }
27
+ "module": "./index.esm.js",
28
+ "types": "./index.esm.d.ts"
29
+ }
@@ -1,8 +1,12 @@
1
- import { FlagValue, ResolutionDetails } from '@openfeature/web-sdk';
1
+ import type { FlagMetadata, FlagValue, ResolutionDetails } from '@openfeature/web-sdk';
2
2
  import { ResolutionError } from './resolution-error';
3
3
  /**
4
- * FlagCache is a type representing the internal cache of the flags.
4
+ * Cache of flag values from bulk evaluation.
5
5
  */
6
6
  export type FlagCache = {
7
7
  [key: string]: ResolutionDetails<FlagValue> | ResolutionError;
8
8
  };
9
+ /**
10
+ * Cache of metadata from bulk evaluation.
11
+ */
12
+ export type MetadataCache = FlagMetadata;
@@ -1,8 +1,7 @@
1
- import { ResolutionReason } from '@openfeature/web-sdk';
2
- import { EvaluationFailureErrorCode } from '@openfeature/ofrep-core';
1
+ import { ErrorCode, ResolutionReason } from '@openfeature/web-sdk';
3
2
  export type ResolutionError = {
4
3
  reason: ResolutionReason;
5
- errorCode: EvaluationFailureErrorCode;
4
+ errorCode: ErrorCode;
6
5
  errorDetails?: string;
7
6
  };
8
7
  export declare function isResolutionError(response: unknown): response is ResolutionError;
@@ -16,6 +16,7 @@ export declare class OFREPWebProvider implements Provider {
16
16
  private _pollingInterval;
17
17
  private _retryPollingAfter;
18
18
  private _flagCache;
19
+ private _flagSetMetadataCache;
19
20
  private _context;
20
21
  private _pollingIntervalId?;
21
22
  constructor(options: OFREPWebProviderOptions, logger?: Logger);
@@ -44,7 +45,7 @@ export declare class OFREPWebProvider implements Provider {
44
45
  */
45
46
  onClose?(): Promise<void>;
46
47
  /**
47
- * _evaluateFlags is a function that will call the bulk evaluate flags endpoint to get the flags values.
48
+ * _fetchFlags is a function that will call the bulk evaluate flags endpoint to get the flags values.
48
49
  * @param context - the context to use for the evaluation
49
50
  * @private
50
51
  * @returns EvaluationStatus if the evaluation the API returned a 304, 200.
@@ -53,7 +54,7 @@ export declare class OFREPWebProvider implements Provider {
53
54
  * @throws ParseError if the API returned a 400 with the error code ParseError
54
55
  * @throws GeneralError if the API returned a 400 with an unknown error code
55
56
  */
56
- private _evaluateFlags;
57
+ private _fetchFlags;
57
58
  /**
58
59
  * _getListUpdatedFlags is a function that will compare the old cache with the new cache and
59
60
  * return the list of flags that have been updated / deleted / created.
@@ -63,12 +64,13 @@ export declare class OFREPWebProvider implements Provider {
63
64
  */
64
65
  private _getListUpdatedFlags;
65
66
  /**
66
- * Evaluate is a function retrieving the value from a flag in the cache.
67
+ * _resolve is a function retrieving the value from a flag in the cache.
67
68
  * @param flagKey - name of the flag to retrieve
68
69
  * @param type - type of the flag
70
+ * @param defaultValue - default value
69
71
  * @private
70
72
  */
71
- private evaluate;
73
+ private _resolve;
72
74
  /**
73
75
  * Start polling for flag updates, it will call the bulk update function every pollInterval
74
76
  * @private