@openfeature/web-sdk 0.3.0-experimental โ 0.3.2-experimental
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 +157 -9
- package/dist/cjs/index.js +41 -39
- package/dist/cjs/index.js.map +4 -4
- package/dist/esm/index.js +41 -41
- package/dist/esm/index.js.map +4 -4
- package/dist/types.d.ts +65 -65
- package/package.json +6 -2
package/README.md
CHANGED
|
@@ -1,18 +1,166 @@
|
|
|
1
|
-
|
|
1
|
+
<!-- markdownlint-disable MD033 -->
|
|
2
|
+
<p align="center">
|
|
3
|
+
<picture>
|
|
4
|
+
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/open-feature/community/0e23508c163a6a1ac8c0ced3e4bd78faafe627c7/assets/logo/horizontal/white/openfeature-horizontal-white.svg">
|
|
5
|
+
<source media="(prefers-color-scheme: light)" srcset="https://raw.githubusercontent.com/open-feature/community/0e23508c163a6a1ac8c0ced3e4bd78faafe627c7/assets/logo/horizontal/black/openfeature-horizontal-black.svg">
|
|
6
|
+
<img align="center" alt="OpenFeature Logo">
|
|
7
|
+
</picture>
|
|
8
|
+
</p>
|
|
2
9
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
## Installation
|
|
10
|
+
<h2 align="center">OpenFeature Web SDK</h2>
|
|
6
11
|
|
|
7
|
-
|
|
12
|
+
[](https://www.repostatus.org/#wip)
|
|
13
|
+
[](https://www.npmjs.com/package/@openfeature/web-sdk)
|
|
14
|
+
[](https://github.com/open-feature/spec/tree/v0.5.2)
|
|
15
|
+
|
|
16
|
+
## ๐งช This SDK is experimental
|
|
17
|
+
|
|
18
|
+
The Web SDK is under development and based on a experimental client concepts.
|
|
19
|
+
For more information, see this [issue](https://github.com/open-feature/spec/issues/167).
|
|
20
|
+
|
|
21
|
+
## ๐ Hey there! Thanks for checking out the OpenFeature Web SDK
|
|
22
|
+
|
|
23
|
+
### What is OpenFeature?
|
|
24
|
+
|
|
25
|
+
[OpenFeature][openfeature-website] is an open standard that provides a vendor-agnostic, community-driven API for feature flagging that works with your favorite feature flag management tool.
|
|
26
|
+
|
|
27
|
+
### Why standardize feature flags?
|
|
28
|
+
|
|
29
|
+
Standardizing feature flags unifies tools and vendors behind a common interface which avoids vendor lock-in at the code level. Additionally, it offers a framework for building extensions and integrations and allows providers to focus on their unique value proposition.
|
|
30
|
+
|
|
31
|
+
## ๐ Requirements:
|
|
32
|
+
|
|
33
|
+
- ES2015-compatible web browser (Chrome, Edge, Firefox, etc)
|
|
34
|
+
|
|
35
|
+
## ๐ฆ Installation:
|
|
36
|
+
|
|
37
|
+
### npm
|
|
38
|
+
|
|
39
|
+
```sh
|
|
8
40
|
npm install @openfeature/web-sdk
|
|
9
41
|
```
|
|
10
42
|
|
|
11
|
-
|
|
43
|
+
### yarn
|
|
12
44
|
|
|
13
|
-
```
|
|
45
|
+
```sh
|
|
14
46
|
yarn add @openfeature/web-sdk
|
|
15
47
|
```
|
|
16
48
|
|
|
17
|
-
##
|
|
18
|
-
|
|
49
|
+
## ๐ Features:
|
|
50
|
+
|
|
51
|
+
- support for various [providers](https://openfeature.dev/docs/reference/concepts/provider)
|
|
52
|
+
- easy integration and extension via [hooks](https://openfeature.dev/docs/reference/concepts/hooks)
|
|
53
|
+
- handle flags of any type: bool, string, numeric and object
|
|
54
|
+
- [context-aware](https://openfeature.dev/docs/reference/concepts/evaluation-context) evaluation
|
|
55
|
+
|
|
56
|
+
## ๐ Usage:
|
|
57
|
+
|
|
58
|
+
### Basics:
|
|
59
|
+
|
|
60
|
+
```typescript
|
|
61
|
+
import { OpenFeature } from '@openfeature/web-sdk';
|
|
62
|
+
|
|
63
|
+
// configure a provider
|
|
64
|
+
await OpenFeature.setProvider(new YourProviderOfChoice());
|
|
65
|
+
|
|
66
|
+
// create a client
|
|
67
|
+
const client = OpenFeature.getClient('my-app');
|
|
68
|
+
|
|
69
|
+
// get a bool flag value
|
|
70
|
+
const boolValue = client.getBooleanValue('boolFlag', false);
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### Context-aware evaluation:
|
|
74
|
+
|
|
75
|
+
Sometimes the value of a flag must take into account some dynamic criteria about the application or user, such as the user location, IP, email address, or the location of the server.
|
|
76
|
+
In OpenFeature, we refer to this as [`targeting`](https://openfeature.dev/specification/glossary#targeting).
|
|
77
|
+
If the flag system you're using supports targeting, you can provide the input data using the `EvaluationContext`.
|
|
78
|
+
|
|
79
|
+
```typescript
|
|
80
|
+
// global context for static data
|
|
81
|
+
await OpenFeature.setContext({ origin: document.location.host })
|
|
82
|
+
|
|
83
|
+
// use contextual data to determine a flag value
|
|
84
|
+
const boolValue = client.getBooleanValue('some-flag', false);
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
### Providers:
|
|
88
|
+
|
|
89
|
+
To develop a provider, you need to create a new project and include the OpenFeature SDK as a dependency. This can be a new repository or included in an existing contrib repository available under the OpenFeature organization. Finally, youโll then need to write the provider itself. In most languages, this can be accomplished by implementing the provider interface exported by the OpenFeature SDK.
|
|
90
|
+
|
|
91
|
+
```typescript
|
|
92
|
+
import { JsonValue, Provider, ResolutionDetails } from '@openfeature/web-sdk';
|
|
93
|
+
|
|
94
|
+
// implement the provider interface
|
|
95
|
+
class MyProvider implements Provider {
|
|
96
|
+
readonly metadata = {
|
|
97
|
+
name: 'My Provider',
|
|
98
|
+
} as const;
|
|
99
|
+
|
|
100
|
+
resolveBooleanEvaluation(flagKey: string, defaultValue: boolean): ResolutionDetails<boolean> {
|
|
101
|
+
// resolve a boolean flag value
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
resolveStringEvaluation(flagKey: string, defaultValue: string): ResolutionDetails<string> {
|
|
105
|
+
// resolve a string flag value
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
resolveNumberEvaluation(flagKey: string, defaultValue: number): ResolutionDetails<number> {
|
|
109
|
+
// resolve a numeric flag value
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
resolveObjectEvaluation<T extends JsonValue>(flagKey: string, defaultValue: T): ResolutionDetails<T> {
|
|
113
|
+
// resolve an object flag value
|
|
114
|
+
}
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
See [here](https://openfeature.dev/docs/reference/technologies/server/javascript) for a catalog of available providers.
|
|
118
|
+
|
|
119
|
+
### Hooks:
|
|
120
|
+
|
|
121
|
+
Hooks are a mechanism that allow for the addition of arbitrary behavior at well-defined points of the flag evaluation life-cycle. Use cases include validation of the resolved flag value, modifying or adding data to the evaluation context, logging, telemetry, and tracking.
|
|
122
|
+
|
|
123
|
+
```typescript
|
|
124
|
+
import { OpenFeature, Hook, HookContext } from '@openfeature/web-sdk';
|
|
125
|
+
|
|
126
|
+
// Example hook that logs if an error occurs during flag evaluation
|
|
127
|
+
export class GlobalDebugHook implements Hook {
|
|
128
|
+
after(hookContext: HookContext, err: Error) {
|
|
129
|
+
console.log('hook context', hookContext);
|
|
130
|
+
console.error(err);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
See [here](https://openfeature.dev/docs/reference/technologies/server/javascript) for a catalog of available hooks.
|
|
136
|
+
|
|
137
|
+
### Logging:
|
|
138
|
+
|
|
139
|
+
You can implement the `Logger` interface (compatible with the `console` object, and implementations from common logging libraries such as [winston](https://www.npmjs.com/package/winston)) and set it on the global API object.
|
|
140
|
+
|
|
141
|
+
```typescript
|
|
142
|
+
// implement logger
|
|
143
|
+
class MyLogger implements Logger {
|
|
144
|
+
error(...args: unknown[]): void {
|
|
145
|
+
// implement me
|
|
146
|
+
}
|
|
147
|
+
warn(...args: unknown[]): void {
|
|
148
|
+
// implement me
|
|
149
|
+
}
|
|
150
|
+
info(...args: unknown[]): void {
|
|
151
|
+
// implement me
|
|
152
|
+
}
|
|
153
|
+
debug(...args: unknown[]): void {
|
|
154
|
+
// implement me
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// set the logger
|
|
159
|
+
OpenFeature.setLogger(new MyLogger());
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
### Complete API documentation:
|
|
163
|
+
|
|
164
|
+
See [here](https://open-feature.github.io/js-sdk/modules/OpenFeature_Web_SDK.html) for the complete API documentation.
|
|
165
|
+
|
|
166
|
+
[openfeature-website]: https://openfeature.dev
|
package/dist/cjs/index.js
CHANGED
|
@@ -94,22 +94,22 @@ var require_events = __commonJS({
|
|
|
94
94
|
var NumberIsNaN = Number.isNaN || function NumberIsNaN2(value) {
|
|
95
95
|
return value !== value;
|
|
96
96
|
};
|
|
97
|
-
function
|
|
98
|
-
|
|
97
|
+
function EventEmitter() {
|
|
98
|
+
EventEmitter.init.call(this);
|
|
99
99
|
}
|
|
100
|
-
module2.exports =
|
|
100
|
+
module2.exports = EventEmitter;
|
|
101
101
|
module2.exports.once = once;
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
102
|
+
EventEmitter.EventEmitter = EventEmitter;
|
|
103
|
+
EventEmitter.prototype._events = void 0;
|
|
104
|
+
EventEmitter.prototype._eventsCount = 0;
|
|
105
|
+
EventEmitter.prototype._maxListeners = void 0;
|
|
106
106
|
var defaultMaxListeners = 10;
|
|
107
107
|
function checkListener(listener) {
|
|
108
108
|
if (typeof listener !== "function") {
|
|
109
109
|
throw new TypeError('The "listener" argument must be of type Function. Received type ' + typeof listener);
|
|
110
110
|
}
|
|
111
111
|
}
|
|
112
|
-
Object.defineProperty(
|
|
112
|
+
Object.defineProperty(EventEmitter, "defaultMaxListeners", {
|
|
113
113
|
enumerable: true,
|
|
114
114
|
get: function() {
|
|
115
115
|
return defaultMaxListeners;
|
|
@@ -121,14 +121,14 @@ var require_events = __commonJS({
|
|
|
121
121
|
defaultMaxListeners = arg;
|
|
122
122
|
}
|
|
123
123
|
});
|
|
124
|
-
|
|
124
|
+
EventEmitter.init = function() {
|
|
125
125
|
if (this._events === void 0 || this._events === Object.getPrototypeOf(this)._events) {
|
|
126
126
|
this._events = /* @__PURE__ */ Object.create(null);
|
|
127
127
|
this._eventsCount = 0;
|
|
128
128
|
}
|
|
129
129
|
this._maxListeners = this._maxListeners || void 0;
|
|
130
130
|
};
|
|
131
|
-
|
|
131
|
+
EventEmitter.prototype.setMaxListeners = function setMaxListeners(n) {
|
|
132
132
|
if (typeof n !== "number" || n < 0 || NumberIsNaN(n)) {
|
|
133
133
|
throw new RangeError('The value of "n" is out of range. It must be a non-negative number. Received ' + n + ".");
|
|
134
134
|
}
|
|
@@ -137,13 +137,13 @@ var require_events = __commonJS({
|
|
|
137
137
|
};
|
|
138
138
|
function _getMaxListeners(that) {
|
|
139
139
|
if (that._maxListeners === void 0)
|
|
140
|
-
return
|
|
140
|
+
return EventEmitter.defaultMaxListeners;
|
|
141
141
|
return that._maxListeners;
|
|
142
142
|
}
|
|
143
|
-
|
|
143
|
+
EventEmitter.prototype.getMaxListeners = function getMaxListeners() {
|
|
144
144
|
return _getMaxListeners(this);
|
|
145
145
|
};
|
|
146
|
-
|
|
146
|
+
EventEmitter.prototype.emit = function emit(type) {
|
|
147
147
|
var args = [];
|
|
148
148
|
for (var i = 1; i < arguments.length; i++)
|
|
149
149
|
args.push(arguments[i]);
|
|
@@ -221,11 +221,11 @@ var require_events = __commonJS({
|
|
|
221
221
|
}
|
|
222
222
|
return target;
|
|
223
223
|
}
|
|
224
|
-
|
|
224
|
+
EventEmitter.prototype.addListener = function addListener(type, listener) {
|
|
225
225
|
return _addListener(this, type, listener, false);
|
|
226
226
|
};
|
|
227
|
-
|
|
228
|
-
|
|
227
|
+
EventEmitter.prototype.on = EventEmitter.prototype.addListener;
|
|
228
|
+
EventEmitter.prototype.prependListener = function prependListener(type, listener) {
|
|
229
229
|
return _addListener(this, type, listener, true);
|
|
230
230
|
};
|
|
231
231
|
function onceWrapper() {
|
|
@@ -244,17 +244,17 @@ var require_events = __commonJS({
|
|
|
244
244
|
state.wrapFn = wrapped;
|
|
245
245
|
return wrapped;
|
|
246
246
|
}
|
|
247
|
-
|
|
247
|
+
EventEmitter.prototype.once = function once2(type, listener) {
|
|
248
248
|
checkListener(listener);
|
|
249
249
|
this.on(type, _onceWrap(this, type, listener));
|
|
250
250
|
return this;
|
|
251
251
|
};
|
|
252
|
-
|
|
252
|
+
EventEmitter.prototype.prependOnceListener = function prependOnceListener(type, listener) {
|
|
253
253
|
checkListener(listener);
|
|
254
254
|
this.prependListener(type, _onceWrap(this, type, listener));
|
|
255
255
|
return this;
|
|
256
256
|
};
|
|
257
|
-
|
|
257
|
+
EventEmitter.prototype.removeListener = function removeListener(type, listener) {
|
|
258
258
|
var list, events, position, i, originalListener;
|
|
259
259
|
checkListener(listener);
|
|
260
260
|
events = this._events;
|
|
@@ -294,8 +294,8 @@ var require_events = __commonJS({
|
|
|
294
294
|
}
|
|
295
295
|
return this;
|
|
296
296
|
};
|
|
297
|
-
|
|
298
|
-
|
|
297
|
+
EventEmitter.prototype.off = EventEmitter.prototype.removeListener;
|
|
298
|
+
EventEmitter.prototype.removeAllListeners = function removeAllListeners(type) {
|
|
299
299
|
var listeners, events, i;
|
|
300
300
|
events = this._events;
|
|
301
301
|
if (events === void 0)
|
|
@@ -347,20 +347,20 @@ var require_events = __commonJS({
|
|
|
347
347
|
return unwrap ? [evlistener.listener || evlistener] : [evlistener];
|
|
348
348
|
return unwrap ? unwrapListeners(evlistener) : arrayClone(evlistener, evlistener.length);
|
|
349
349
|
}
|
|
350
|
-
|
|
350
|
+
EventEmitter.prototype.listeners = function listeners(type) {
|
|
351
351
|
return _listeners(this, type, true);
|
|
352
352
|
};
|
|
353
|
-
|
|
353
|
+
EventEmitter.prototype.rawListeners = function rawListeners(type) {
|
|
354
354
|
return _listeners(this, type, false);
|
|
355
355
|
};
|
|
356
|
-
|
|
356
|
+
EventEmitter.listenerCount = function(emitter, type) {
|
|
357
357
|
if (typeof emitter.listenerCount === "function") {
|
|
358
358
|
return emitter.listenerCount(type);
|
|
359
359
|
} else {
|
|
360
360
|
return listenerCount.call(emitter, type);
|
|
361
361
|
}
|
|
362
362
|
};
|
|
363
|
-
|
|
363
|
+
EventEmitter.prototype.listenerCount = listenerCount;
|
|
364
364
|
function listenerCount(type) {
|
|
365
365
|
var events = this._events;
|
|
366
366
|
if (events !== void 0) {
|
|
@@ -373,7 +373,7 @@ var require_events = __commonJS({
|
|
|
373
373
|
}
|
|
374
374
|
return 0;
|
|
375
375
|
}
|
|
376
|
-
|
|
376
|
+
EventEmitter.prototype.eventNames = function eventNames() {
|
|
377
377
|
return this._eventsCount > 0 ? ReflectOwnKeys(this._events) : [];
|
|
378
378
|
};
|
|
379
379
|
function arrayClone(arr, n) {
|
|
@@ -464,20 +464,8 @@ __export(src_exports, {
|
|
|
464
464
|
TypeMismatchError: () => TypeMismatchError
|
|
465
465
|
});
|
|
466
466
|
module.exports = __toCommonJS(src_exports);
|
|
467
|
-
var import_events = __toESM(require_events());
|
|
468
467
|
|
|
469
468
|
// ../shared/src/types.ts
|
|
470
|
-
var ProviderEvents = /* @__PURE__ */ ((ProviderEvents2) => {
|
|
471
|
-
ProviderEvents2["Ready"] = "PROVIDER_READY";
|
|
472
|
-
ProviderEvents2["Error"] = "PROVIDER_ERROR";
|
|
473
|
-
ProviderEvents2["ConfigurationChanged"] = "PROVIDER_CONFIGURATION_CHANGED";
|
|
474
|
-
ProviderEvents2["Shutdown"] = "PROVIDER_SHUTDOWN";
|
|
475
|
-
return ProviderEvents2;
|
|
476
|
-
})(ProviderEvents || {});
|
|
477
|
-
var ApiEvents = /* @__PURE__ */ ((ApiEvents2) => {
|
|
478
|
-
ApiEvents2["ProviderChanged"] = "providerChanged";
|
|
479
|
-
return ApiEvents2;
|
|
480
|
-
})(ApiEvents || {});
|
|
481
469
|
var StandardResolutionReasons = {
|
|
482
470
|
/**
|
|
483
471
|
* The resolved value was the result of a dynamic evaluation, such as a rule or specific user-targeting.
|
|
@@ -721,6 +709,20 @@ var NoopFeatureProvider = class {
|
|
|
721
709
|
};
|
|
722
710
|
var NOOP_PROVIDER = new NoopFeatureProvider();
|
|
723
711
|
|
|
712
|
+
// src/types.ts
|
|
713
|
+
var import_events = __toESM(require_events());
|
|
714
|
+
var ProviderEvents = /* @__PURE__ */ ((ProviderEvents2) => {
|
|
715
|
+
ProviderEvents2["Ready"] = "PROVIDER_READY";
|
|
716
|
+
ProviderEvents2["Error"] = "PROVIDER_ERROR";
|
|
717
|
+
ProviderEvents2["ConfigurationChanged"] = "PROVIDER_CONFIGURATION_CHANGED";
|
|
718
|
+
ProviderEvents2["Stale"] = "PROVIDER_STALE";
|
|
719
|
+
return ProviderEvents2;
|
|
720
|
+
})(ProviderEvents || {});
|
|
721
|
+
var ApiEvents = /* @__PURE__ */ ((ApiEvents2) => {
|
|
722
|
+
ApiEvents2["ProviderChanged"] = "providerChanged";
|
|
723
|
+
return ApiEvents2;
|
|
724
|
+
})(ApiEvents || {});
|
|
725
|
+
|
|
724
726
|
// src/open-feature.ts
|
|
725
727
|
var GLOBAL_OPENFEATURE_API_KEY = Symbol.for("@openfeature/js.api");
|
|
726
728
|
var _globalThis = globalThis;
|
|
@@ -815,7 +817,7 @@ var OpenFeatureAPI = class extends OpenFeatureCommonAPI {
|
|
|
815
817
|
getClient(name, version) {
|
|
816
818
|
return new OpenFeatureClient(
|
|
817
819
|
// functions are passed here to make sure that these values are always up to date,
|
|
818
|
-
// and so we don't have to make these public properties on the API class.
|
|
820
|
+
// and so we don't have to make these public properties on the API class.
|
|
819
821
|
() => this._provider,
|
|
820
822
|
() => this._providerReady,
|
|
821
823
|
() => this._apiEvents,
|