@openfeature/flagd-provider 0.10.3 → 0.10.5
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 +17 -4
- package/index.cjs.js +161 -47
- package/index.esm.js +161 -47
- package/package.json +3 -3
- package/src/lib/configuration.d.ts +6 -0
- package/src/lib/service/grpc/grpc-service.d.ts +1 -1
- package/src/lib/service/in-process/data-fetch.d.ts +28 -2
- package/src/lib/service/in-process/file/file-fetch.d.ts +9 -0
- package/src/lib/service/in-process/grpc/grpc-fetch.d.ts +16 -4
- package/src/lib/service/in-process/in-process-service.d.ts +15 -4
- package/src/lib/service/service.d.ts +1 -1
package/README.md
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
# Server-Side flagd Provider
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
It is designed to conform to OpenFeature schema for flag definitions.
|
|
3
|
+
This provider is designed to use flagd's [evaluation protocol](https://github.com/open-feature/schemas/blob/main/protobuf/schema/v1/schema.proto), or locally evaluate flags defined in a flagd [flag definition](https://github.com/open-feature/schemas/blob/main/json/flagd-definitions.json).
|
|
5
4
|
This repository and package provides the client code for interacting with it via the OpenFeature server-side JavaScript SDK.
|
|
6
5
|
|
|
7
6
|
## Installation
|
|
@@ -35,8 +34,9 @@ Options can be defined in the constructor or as environment variables. Construct
|
|
|
35
34
|
| tls | FLAGD_TLS | boolean | false | |
|
|
36
35
|
| socketPath | FLAGD_SOCKET_PATH | string | - | |
|
|
37
36
|
| resolverType | FLAGD_SOURCE_RESOLVER | string | rpc | rpc, in-process |
|
|
37
|
+
| offlineFlagSourcePath | FLAGD_OFFLINE_FLAG_SOURCE_PATH | string | - | |
|
|
38
38
|
| selector | FLAGD_SOURCE_SELECTOR | string | - | |
|
|
39
|
-
| cache | FLAGD_CACHE | string | lru | lru,disabled
|
|
39
|
+
| cache | FLAGD_CACHE | string | lru | lru, disabled |
|
|
40
40
|
| maxCacheSize | FLAGD_MAX_CACHE_SIZE | int | 1000 | |
|
|
41
41
|
|
|
42
42
|
Below are examples of usage patterns.
|
|
@@ -45,7 +45,7 @@ Below are examples of usage patterns.
|
|
|
45
45
|
|
|
46
46
|
This is the default mode of operation of the provider.
|
|
47
47
|
In this mode, FlagdProvider communicates with flagd via the gRPC protocol.
|
|
48
|
-
Flag evaluations take place remotely
|
|
48
|
+
Flag evaluations take place remotely on the connected [flagd](https://flagd.dev/) instance.
|
|
49
49
|
|
|
50
50
|
```ts
|
|
51
51
|
OpenFeature.setProvider(new FlagdProvider())
|
|
@@ -74,6 +74,19 @@ Flag configurations for evaluation are obtained via gRPC protocol using [sync pr
|
|
|
74
74
|
|
|
75
75
|
In the above example, the provider expects a flag sync service implementation to be available at `localhost:8013` (default host and port).
|
|
76
76
|
|
|
77
|
+
In-process resolver can also work in an offline mode.
|
|
78
|
+
To enable this mode, you should provide a valid flag configuration file with the option `offlineFlagSourcePath`.
|
|
79
|
+
|
|
80
|
+
```
|
|
81
|
+
OpenFeature.setProvider(new FlagdProvider({
|
|
82
|
+
resolverType: 'in-process',
|
|
83
|
+
offlineFlagSourcePath: './flags.json',
|
|
84
|
+
}))
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
Offline mode uses `fs.watchFile` and polls every 5 seconds for changes to the file.
|
|
88
|
+
This mode is useful for local development, test cases, and for offline applications.
|
|
89
|
+
|
|
77
90
|
### Supported Events
|
|
78
91
|
|
|
79
92
|
The flagd provider emits `PROVIDER_READY`, `PROVIDER_ERROR` and `PROVIDER_CONFIGURATION_CHANGED` events.
|
package/index.cjs.js
CHANGED
|
@@ -8,6 +8,8 @@ var connectivityState = require('@grpc/grpc-js/build/src/connectivity-state');
|
|
|
8
8
|
var lruCache = require('lru-cache');
|
|
9
9
|
var util$6 = require('util');
|
|
10
10
|
var flagdCore = require('@openfeature/flagd-core');
|
|
11
|
+
var core = require('@openfeature/core');
|
|
12
|
+
var fs = require('fs');
|
|
11
13
|
|
|
12
14
|
const EVENT_CONFIGURATION_CHANGE = 'configuration_change';
|
|
13
15
|
const EVENT_PROVIDER_READY = 'provider_ready';
|
|
@@ -32,10 +34,11 @@ var ENV_VAR;
|
|
|
32
34
|
ENV_VAR["FLAGD_MAX_CACHE_SIZE"] = "FLAGD_MAX_CACHE_SIZE";
|
|
33
35
|
ENV_VAR["FLAGD_SOURCE_SELECTOR"] = "FLAGD_SOURCE_SELECTOR";
|
|
34
36
|
ENV_VAR["FLAGD_RESOLVER"] = "FLAGD_RESOLVER";
|
|
37
|
+
ENV_VAR["FLAGD_OFFLINE_FLAG_SOURCE_PATH"] = "FLAGD_OFFLINE_FLAG_SOURCE_PATH";
|
|
35
38
|
})(ENV_VAR || (ENV_VAR = {}));
|
|
36
39
|
const getEnvVarConfig = () => {
|
|
37
40
|
var _a;
|
|
38
|
-
return (Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign({}, (process.env[ENV_VAR.FLAGD_HOST] && {
|
|
41
|
+
return (Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign({}, (process.env[ENV_VAR.FLAGD_HOST] && {
|
|
39
42
|
host: process.env[ENV_VAR.FLAGD_HOST],
|
|
40
43
|
})), (Number(process.env[ENV_VAR.FLAGD_PORT]) && {
|
|
41
44
|
port: Number(process.env[ENV_VAR.FLAGD_PORT]),
|
|
@@ -51,6 +54,8 @@ const getEnvVarConfig = () => {
|
|
|
51
54
|
selector: process.env[ENV_VAR.FLAGD_SOURCE_SELECTOR],
|
|
52
55
|
})), ((process.env[ENV_VAR.FLAGD_RESOLVER] === 'rpc' || process.env[ENV_VAR.FLAGD_RESOLVER] === 'in-process') && {
|
|
53
56
|
resolverType: process.env[ENV_VAR.FLAGD_RESOLVER],
|
|
57
|
+
})), (process.env[ENV_VAR.FLAGD_OFFLINE_FLAG_SOURCE_PATH] && {
|
|
58
|
+
offlineFlagSourcePath: process.env[ENV_VAR.FLAGD_OFFLINE_FLAG_SOURCE_PATH],
|
|
54
59
|
})));
|
|
55
60
|
};
|
|
56
61
|
function getConfig(options = {}) {
|
|
@@ -5942,13 +5947,15 @@ class GRPCService {
|
|
|
5942
5947
|
if (data && typeof data === 'object' && 'flags' in data && (data === null || data === void 0 ? void 0 : data['flags'])) {
|
|
5943
5948
|
const flagChangeMessage = data;
|
|
5944
5949
|
const flagsChanged = Object.keys(flagChangeMessage.flags || []);
|
|
5945
|
-
|
|
5946
|
-
|
|
5947
|
-
|
|
5948
|
-
|
|
5949
|
-
(
|
|
5950
|
-
|
|
5951
|
-
|
|
5950
|
+
if (this._cacheEnabled) {
|
|
5951
|
+
// remove each changed key from cache
|
|
5952
|
+
flagsChanged.forEach((key) => {
|
|
5953
|
+
var _a, _b;
|
|
5954
|
+
if ((_a = this._cache) === null || _a === void 0 ? void 0 : _a.delete(key)) {
|
|
5955
|
+
(_b = this.logger) === null || _b === void 0 ? void 0 : _b.debug(`${FlagdProvider.name}: evicted key: ${key} from cache.`);
|
|
5956
|
+
}
|
|
5957
|
+
});
|
|
5958
|
+
}
|
|
5952
5959
|
changedCallback(flagsChanged);
|
|
5953
5960
|
}
|
|
5954
5961
|
}
|
|
@@ -5961,7 +5968,7 @@ class GRPCService {
|
|
|
5961
5968
|
}
|
|
5962
5969
|
handleError(reconnectCallback, changedCallback, disconnectCallback) {
|
|
5963
5970
|
var _a, _b;
|
|
5964
|
-
disconnectCallback();
|
|
5971
|
+
disconnectCallback('streaming connection error, will attempt reconnect...');
|
|
5965
5972
|
(_a = this.logger) === null || _a === void 0 ? void 0 : _a.error(`${FlagdProvider.name}: streaming connection error, will attempt reconnect...`);
|
|
5966
5973
|
(_b = this._cache) === null || _b === void 0 ? void 0 : _b.clear();
|
|
5967
5974
|
this.reconnect(reconnectCallback, changedCallback, disconnectCallback);
|
|
@@ -6356,6 +6363,18 @@ function isSet(value) {
|
|
|
6356
6363
|
*/
|
|
6357
6364
|
class GrpcFetch {
|
|
6358
6365
|
constructor(config, syncServiceClient, logger) {
|
|
6366
|
+
/**
|
|
6367
|
+
* Initialized will be set to true once the initial connection is successful
|
|
6368
|
+
* and the first payload has been received. Subsequent reconnects will not
|
|
6369
|
+
* change the initialized value.
|
|
6370
|
+
*/
|
|
6371
|
+
this._initialized = false;
|
|
6372
|
+
/**
|
|
6373
|
+
* Is connected represents the current known connection state. It will be
|
|
6374
|
+
* set to true once the first payload has been received.but will be set to
|
|
6375
|
+
* false if the connection is lost.
|
|
6376
|
+
*/
|
|
6377
|
+
this._isConnected = false;
|
|
6359
6378
|
const { host, port, tls, socketPath, selector } = config;
|
|
6360
6379
|
this._syncClient = syncServiceClient
|
|
6361
6380
|
? syncServiceClient
|
|
@@ -6363,55 +6382,131 @@ class GrpcFetch {
|
|
|
6363
6382
|
this._logger = logger;
|
|
6364
6383
|
this._request = { providerId: '', selector: selector ? selector : '' };
|
|
6365
6384
|
}
|
|
6366
|
-
connect(
|
|
6367
|
-
return new Promise((resolve, reject) => this.listen(
|
|
6385
|
+
connect(dataCallback, reconnectCallback, changedCallback, disconnectCallback) {
|
|
6386
|
+
return new Promise((resolve, reject) => this.listen(dataCallback, reconnectCallback, changedCallback, disconnectCallback, resolve, reject)).then(() => {
|
|
6387
|
+
this._initialized = true;
|
|
6388
|
+
});
|
|
6368
6389
|
}
|
|
6369
6390
|
disconnect() {
|
|
6370
6391
|
var _a;
|
|
6371
|
-
(
|
|
6372
|
-
|
|
6373
|
-
|
|
6392
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
6393
|
+
(_a = this._logger) === null || _a === void 0 ? void 0 : _a.debug('Disconnecting gRPC sync connection');
|
|
6394
|
+
closeStreamIfDefined(this._syncStream);
|
|
6395
|
+
this._syncClient.close();
|
|
6396
|
+
});
|
|
6374
6397
|
}
|
|
6375
|
-
listen(
|
|
6398
|
+
listen(dataCallback, reconnectCallback, changedCallback, disconnectCallback, resolveConnect, rejectConnect) {
|
|
6399
|
+
var _a;
|
|
6400
|
+
(_a = this._logger) === null || _a === void 0 ? void 0 : _a.debug('Starting gRPC sync connection');
|
|
6376
6401
|
closeStreamIfDefined(this._syncStream);
|
|
6377
6402
|
this._syncStream = this._syncClient.syncFlags(this._request);
|
|
6378
6403
|
this._syncStream.on('data', (data) => {
|
|
6379
|
-
var _a;
|
|
6380
|
-
(_a = this._logger) === null || _a === void 0 ? void 0 : _a.debug(
|
|
6381
|
-
|
|
6382
|
-
|
|
6383
|
-
|
|
6404
|
+
var _a, _b, _c, _d;
|
|
6405
|
+
(_a = this._logger) === null || _a === void 0 ? void 0 : _a.debug(`Received sync payload`);
|
|
6406
|
+
try {
|
|
6407
|
+
const changes = dataCallback(data.flagConfiguration);
|
|
6408
|
+
if (this._initialized && changes.length > 0) {
|
|
6409
|
+
changedCallback(changes);
|
|
6410
|
+
}
|
|
6411
|
+
}
|
|
6412
|
+
catch (err) {
|
|
6413
|
+
(_b = this._logger) === null || _b === void 0 ? void 0 : _b.debug('Error processing sync payload: ', (_c = err === null || err === void 0 ? void 0 : err.message) !== null && _c !== void 0 ? _c : 'unknown error');
|
|
6414
|
+
}
|
|
6384
6415
|
if (resolveConnect) {
|
|
6385
6416
|
resolveConnect();
|
|
6386
6417
|
}
|
|
6387
|
-
else {
|
|
6418
|
+
else if (!this._isConnected) {
|
|
6419
|
+
// Not the first connection and there's no active connection.
|
|
6420
|
+
(_d = this._logger) === null || _d === void 0 ? void 0 : _d.debug('Reconnected to gRPC sync');
|
|
6388
6421
|
reconnectCallback();
|
|
6389
6422
|
}
|
|
6423
|
+
this._isConnected = true;
|
|
6390
6424
|
});
|
|
6391
6425
|
this._syncStream.on('error', (err) => {
|
|
6392
|
-
var _a;
|
|
6393
|
-
(_a = this._logger) === null || _a === void 0 ? void 0 : _a.error('Connection error, attempting to reconnect'
|
|
6394
|
-
|
|
6395
|
-
|
|
6396
|
-
|
|
6426
|
+
var _a, _b, _c;
|
|
6427
|
+
(_a = this._logger) === null || _a === void 0 ? void 0 : _a.error('Connection error, attempting to reconnect');
|
|
6428
|
+
(_b = this._logger) === null || _b === void 0 ? void 0 : _b.debug(err);
|
|
6429
|
+
this._isConnected = false;
|
|
6430
|
+
const errorMessage = (_c = err === null || err === void 0 ? void 0 : err.message) !== null && _c !== void 0 ? _c : 'Failed to connect to syncFlags stream';
|
|
6431
|
+
disconnectCallback(errorMessage);
|
|
6432
|
+
rejectConnect === null || rejectConnect === void 0 ? void 0 : rejectConnect(new serverSdk.GeneralError(errorMessage));
|
|
6433
|
+
this.reconnect(dataCallback, reconnectCallback, changedCallback, disconnectCallback);
|
|
6397
6434
|
});
|
|
6398
6435
|
}
|
|
6399
|
-
reconnect(
|
|
6436
|
+
reconnect(dataCallback, reconnectCallback, changedCallback, disconnectCallback) {
|
|
6400
6437
|
const channel = this._syncClient.getChannel();
|
|
6401
6438
|
channel.watchConnectivityState(channel.getConnectivityState(true), Infinity, () => {
|
|
6402
|
-
this.listen(
|
|
6439
|
+
this.listen(dataCallback, reconnectCallback, changedCallback, disconnectCallback);
|
|
6440
|
+
});
|
|
6441
|
+
}
|
|
6442
|
+
}
|
|
6443
|
+
|
|
6444
|
+
const encoding = 'utf8';
|
|
6445
|
+
class FileFetch {
|
|
6446
|
+
constructor(filename, logger) {
|
|
6447
|
+
this._filename = filename;
|
|
6448
|
+
this._logger = logger;
|
|
6449
|
+
}
|
|
6450
|
+
connect(dataFillCallback, _, changedCallback) {
|
|
6451
|
+
var _a, _b;
|
|
6452
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
6453
|
+
(_a = this._logger) === null || _a === void 0 ? void 0 : _a.debug('Starting file sync connection');
|
|
6454
|
+
try {
|
|
6455
|
+
const output = yield fs.promises.readFile(this._filename, encoding);
|
|
6456
|
+
// Don't emit the change event for the initial read
|
|
6457
|
+
dataFillCallback(output);
|
|
6458
|
+
// Using watchFile instead of watch to support virtualized host file systems.
|
|
6459
|
+
fs.watchFile(this._filename, () => __awaiter(this, void 0, void 0, function* () {
|
|
6460
|
+
var _c;
|
|
6461
|
+
try {
|
|
6462
|
+
const data = yield fs.promises.readFile(this._filename, encoding);
|
|
6463
|
+
const changes = dataFillCallback(data);
|
|
6464
|
+
if (changes.length > 0) {
|
|
6465
|
+
changedCallback(changes);
|
|
6466
|
+
}
|
|
6467
|
+
}
|
|
6468
|
+
catch (err) {
|
|
6469
|
+
(_c = this._logger) === null || _c === void 0 ? void 0 : _c.error(`Error reading file: ${err}`);
|
|
6470
|
+
}
|
|
6471
|
+
}));
|
|
6472
|
+
}
|
|
6473
|
+
catch (err) {
|
|
6474
|
+
if (err instanceof core.OpenFeatureError) {
|
|
6475
|
+
throw err;
|
|
6476
|
+
}
|
|
6477
|
+
else {
|
|
6478
|
+
switch (err === null || err === void 0 ? void 0 : err.code) {
|
|
6479
|
+
case 'ENOENT':
|
|
6480
|
+
throw new core.GeneralError(`File not found: ${this._filename}`);
|
|
6481
|
+
case 'EACCES':
|
|
6482
|
+
throw new core.GeneralError(`File not accessible: ${this._filename}`);
|
|
6483
|
+
default:
|
|
6484
|
+
(_b = this._logger) === null || _b === void 0 ? void 0 : _b.debug(`Error reading file: ${err}`);
|
|
6485
|
+
throw new core.GeneralError();
|
|
6486
|
+
}
|
|
6487
|
+
}
|
|
6488
|
+
}
|
|
6489
|
+
});
|
|
6490
|
+
}
|
|
6491
|
+
disconnect() {
|
|
6492
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
6493
|
+
fs.unwatchFile(this._filename);
|
|
6403
6494
|
});
|
|
6404
6495
|
}
|
|
6405
6496
|
}
|
|
6406
6497
|
|
|
6407
6498
|
class InProcessService {
|
|
6408
6499
|
constructor(config, dataFetcher, logger) {
|
|
6409
|
-
this.
|
|
6500
|
+
this.config = config;
|
|
6410
6501
|
this._flagdCore = new flagdCore.FlagdCore(undefined, logger);
|
|
6411
|
-
this._dataFetcher = dataFetcher
|
|
6502
|
+
this._dataFetcher = dataFetcher
|
|
6503
|
+
? dataFetcher
|
|
6504
|
+
: config.offlineFlagSourcePath
|
|
6505
|
+
? new FileFetch(config.offlineFlagSourcePath, logger)
|
|
6506
|
+
: new GrpcFetch(config, undefined, logger);
|
|
6412
6507
|
}
|
|
6413
6508
|
connect(reconnectCallback, changedCallback, disconnectCallback) {
|
|
6414
|
-
return this._dataFetcher.connect(this.
|
|
6509
|
+
return this._dataFetcher.connect(this.setFlagConfiguration.bind(this), reconnectCallback, changedCallback, disconnectCallback);
|
|
6415
6510
|
}
|
|
6416
6511
|
disconnect() {
|
|
6417
6512
|
return __awaiter(this, void 0, void 0, function* () {
|
|
@@ -6419,25 +6514,43 @@ class InProcessService {
|
|
|
6419
6514
|
});
|
|
6420
6515
|
}
|
|
6421
6516
|
resolveBoolean(flagKey, defaultValue, context, logger) {
|
|
6422
|
-
return
|
|
6517
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
6518
|
+
return this.evaluate('boolean', flagKey, defaultValue, context, logger);
|
|
6519
|
+
});
|
|
6423
6520
|
}
|
|
6424
6521
|
resolveNumber(flagKey, defaultValue, context, logger) {
|
|
6425
|
-
return
|
|
6522
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
6523
|
+
return this.evaluate('number', flagKey, defaultValue, context, logger);
|
|
6524
|
+
});
|
|
6426
6525
|
}
|
|
6427
6526
|
resolveString(flagKey, defaultValue, context, logger) {
|
|
6428
|
-
return
|
|
6527
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
6528
|
+
return this.evaluate('string', flagKey, defaultValue, context, logger);
|
|
6529
|
+
});
|
|
6429
6530
|
}
|
|
6430
6531
|
resolveObject(flagKey, defaultValue, context, logger) {
|
|
6431
|
-
return
|
|
6532
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
6533
|
+
return this.evaluate('object', flagKey, defaultValue, context, logger);
|
|
6534
|
+
});
|
|
6432
6535
|
}
|
|
6433
|
-
|
|
6434
|
-
|
|
6435
|
-
|
|
6436
|
-
|
|
6437
|
-
|
|
6438
|
-
|
|
6439
|
-
|
|
6440
|
-
|
|
6536
|
+
evaluate(type, flagKey, defaultValue, context, logger) {
|
|
6537
|
+
const details = this._flagdCore.resolve(type, flagKey, defaultValue, context, logger);
|
|
6538
|
+
return Object.assign(Object.assign({}, details), { flagMetadata: this.addFlagMetadata() });
|
|
6539
|
+
}
|
|
6540
|
+
/**
|
|
6541
|
+
* Adds the flag metadata to the resolution details
|
|
6542
|
+
*/
|
|
6543
|
+
addFlagMetadata() {
|
|
6544
|
+
return Object.assign({}, (this.config.selector ? { scope: this.config.selector } : {}));
|
|
6545
|
+
}
|
|
6546
|
+
/**
|
|
6547
|
+
* Sets the flag configuration
|
|
6548
|
+
* @param flags The flags to set as stringified JSON
|
|
6549
|
+
* @returns {string[]} The flags that have changed
|
|
6550
|
+
* @throws — {Error} If the configuration string is invalid.
|
|
6551
|
+
*/
|
|
6552
|
+
setFlagConfiguration(flags) {
|
|
6553
|
+
return this._flagdCore.setConfigurations(flags);
|
|
6441
6554
|
}
|
|
6442
6555
|
}
|
|
6443
6556
|
|
|
@@ -6484,9 +6597,10 @@ class FlagdProvider {
|
|
|
6484
6597
|
this._status = serverSdk.ProviderStatus.READY;
|
|
6485
6598
|
})
|
|
6486
6599
|
.catch((err) => {
|
|
6487
|
-
var _a;
|
|
6600
|
+
var _a, _b;
|
|
6488
6601
|
this._status = serverSdk.ProviderStatus.ERROR;
|
|
6489
|
-
(_a = this.logger) === null || _a === void 0 ? void 0 : _a.error(`${this.metadata.name}: error during initialization: ${err.message}
|
|
6602
|
+
(_a = this.logger) === null || _a === void 0 ? void 0 : _a.error(`${this.metadata.name}: error during initialization: ${err.message}`);
|
|
6603
|
+
(_b = this.logger) === null || _b === void 0 ? void 0 : _b.debug(err);
|
|
6490
6604
|
throw err;
|
|
6491
6605
|
});
|
|
6492
6606
|
}
|
|
@@ -6519,9 +6633,9 @@ class FlagdProvider {
|
|
|
6519
6633
|
this._status = serverSdk.ProviderStatus.READY;
|
|
6520
6634
|
this._events.emit(serverSdk.ProviderEvents.Ready);
|
|
6521
6635
|
}
|
|
6522
|
-
handleError() {
|
|
6636
|
+
handleError(message) {
|
|
6523
6637
|
this._status = serverSdk.ProviderStatus.ERROR;
|
|
6524
|
-
this._events.emit(serverSdk.ProviderEvents.Error);
|
|
6638
|
+
this._events.emit(serverSdk.ProviderEvents.Error, { message });
|
|
6525
6639
|
}
|
|
6526
6640
|
handleChanged(flagsChanged) {
|
|
6527
6641
|
this._events.emit(serverSdk.ProviderEvents.ConfigurationChanged, { flagsChanged });
|
package/index.esm.js
CHANGED
|
@@ -4,6 +4,8 @@ import { ConnectivityState } from '@grpc/grpc-js/build/src/connectivity-state';
|
|
|
4
4
|
import { LRUCache } from 'lru-cache';
|
|
5
5
|
import { promisify } from 'util';
|
|
6
6
|
import { FlagdCore } from '@openfeature/flagd-core';
|
|
7
|
+
import { OpenFeatureError, GeneralError as GeneralError$1 } from '@openfeature/core';
|
|
8
|
+
import { promises, watchFile, unwatchFile } from 'fs';
|
|
7
9
|
|
|
8
10
|
const EVENT_CONFIGURATION_CHANGE = 'configuration_change';
|
|
9
11
|
const EVENT_PROVIDER_READY = 'provider_ready';
|
|
@@ -28,10 +30,11 @@ var ENV_VAR;
|
|
|
28
30
|
ENV_VAR["FLAGD_MAX_CACHE_SIZE"] = "FLAGD_MAX_CACHE_SIZE";
|
|
29
31
|
ENV_VAR["FLAGD_SOURCE_SELECTOR"] = "FLAGD_SOURCE_SELECTOR";
|
|
30
32
|
ENV_VAR["FLAGD_RESOLVER"] = "FLAGD_RESOLVER";
|
|
33
|
+
ENV_VAR["FLAGD_OFFLINE_FLAG_SOURCE_PATH"] = "FLAGD_OFFLINE_FLAG_SOURCE_PATH";
|
|
31
34
|
})(ENV_VAR || (ENV_VAR = {}));
|
|
32
35
|
const getEnvVarConfig = () => {
|
|
33
36
|
var _a;
|
|
34
|
-
return (Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign({}, (process.env[ENV_VAR.FLAGD_HOST] && {
|
|
37
|
+
return (Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign({}, (process.env[ENV_VAR.FLAGD_HOST] && {
|
|
35
38
|
host: process.env[ENV_VAR.FLAGD_HOST],
|
|
36
39
|
})), (Number(process.env[ENV_VAR.FLAGD_PORT]) && {
|
|
37
40
|
port: Number(process.env[ENV_VAR.FLAGD_PORT]),
|
|
@@ -47,6 +50,8 @@ const getEnvVarConfig = () => {
|
|
|
47
50
|
selector: process.env[ENV_VAR.FLAGD_SOURCE_SELECTOR],
|
|
48
51
|
})), ((process.env[ENV_VAR.FLAGD_RESOLVER] === 'rpc' || process.env[ENV_VAR.FLAGD_RESOLVER] === 'in-process') && {
|
|
49
52
|
resolverType: process.env[ENV_VAR.FLAGD_RESOLVER],
|
|
53
|
+
})), (process.env[ENV_VAR.FLAGD_OFFLINE_FLAG_SOURCE_PATH] && {
|
|
54
|
+
offlineFlagSourcePath: process.env[ENV_VAR.FLAGD_OFFLINE_FLAG_SOURCE_PATH],
|
|
50
55
|
})));
|
|
51
56
|
};
|
|
52
57
|
function getConfig(options = {}) {
|
|
@@ -5938,13 +5943,15 @@ class GRPCService {
|
|
|
5938
5943
|
if (data && typeof data === 'object' && 'flags' in data && (data === null || data === void 0 ? void 0 : data['flags'])) {
|
|
5939
5944
|
const flagChangeMessage = data;
|
|
5940
5945
|
const flagsChanged = Object.keys(flagChangeMessage.flags || []);
|
|
5941
|
-
|
|
5942
|
-
|
|
5943
|
-
|
|
5944
|
-
|
|
5945
|
-
(
|
|
5946
|
-
|
|
5947
|
-
|
|
5946
|
+
if (this._cacheEnabled) {
|
|
5947
|
+
// remove each changed key from cache
|
|
5948
|
+
flagsChanged.forEach((key) => {
|
|
5949
|
+
var _a, _b;
|
|
5950
|
+
if ((_a = this._cache) === null || _a === void 0 ? void 0 : _a.delete(key)) {
|
|
5951
|
+
(_b = this.logger) === null || _b === void 0 ? void 0 : _b.debug(`${FlagdProvider.name}: evicted key: ${key} from cache.`);
|
|
5952
|
+
}
|
|
5953
|
+
});
|
|
5954
|
+
}
|
|
5948
5955
|
changedCallback(flagsChanged);
|
|
5949
5956
|
}
|
|
5950
5957
|
}
|
|
@@ -5957,7 +5964,7 @@ class GRPCService {
|
|
|
5957
5964
|
}
|
|
5958
5965
|
handleError(reconnectCallback, changedCallback, disconnectCallback) {
|
|
5959
5966
|
var _a, _b;
|
|
5960
|
-
disconnectCallback();
|
|
5967
|
+
disconnectCallback('streaming connection error, will attempt reconnect...');
|
|
5961
5968
|
(_a = this.logger) === null || _a === void 0 ? void 0 : _a.error(`${FlagdProvider.name}: streaming connection error, will attempt reconnect...`);
|
|
5962
5969
|
(_b = this._cache) === null || _b === void 0 ? void 0 : _b.clear();
|
|
5963
5970
|
this.reconnect(reconnectCallback, changedCallback, disconnectCallback);
|
|
@@ -6352,6 +6359,18 @@ function isSet(value) {
|
|
|
6352
6359
|
*/
|
|
6353
6360
|
class GrpcFetch {
|
|
6354
6361
|
constructor(config, syncServiceClient, logger) {
|
|
6362
|
+
/**
|
|
6363
|
+
* Initialized will be set to true once the initial connection is successful
|
|
6364
|
+
* and the first payload has been received. Subsequent reconnects will not
|
|
6365
|
+
* change the initialized value.
|
|
6366
|
+
*/
|
|
6367
|
+
this._initialized = false;
|
|
6368
|
+
/**
|
|
6369
|
+
* Is connected represents the current known connection state. It will be
|
|
6370
|
+
* set to true once the first payload has been received.but will be set to
|
|
6371
|
+
* false if the connection is lost.
|
|
6372
|
+
*/
|
|
6373
|
+
this._isConnected = false;
|
|
6355
6374
|
const { host, port, tls, socketPath, selector } = config;
|
|
6356
6375
|
this._syncClient = syncServiceClient
|
|
6357
6376
|
? syncServiceClient
|
|
@@ -6359,55 +6378,131 @@ class GrpcFetch {
|
|
|
6359
6378
|
this._logger = logger;
|
|
6360
6379
|
this._request = { providerId: '', selector: selector ? selector : '' };
|
|
6361
6380
|
}
|
|
6362
|
-
connect(
|
|
6363
|
-
return new Promise((resolve, reject) => this.listen(
|
|
6381
|
+
connect(dataCallback, reconnectCallback, changedCallback, disconnectCallback) {
|
|
6382
|
+
return new Promise((resolve, reject) => this.listen(dataCallback, reconnectCallback, changedCallback, disconnectCallback, resolve, reject)).then(() => {
|
|
6383
|
+
this._initialized = true;
|
|
6384
|
+
});
|
|
6364
6385
|
}
|
|
6365
6386
|
disconnect() {
|
|
6366
6387
|
var _a;
|
|
6367
|
-
(
|
|
6368
|
-
|
|
6369
|
-
|
|
6388
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
6389
|
+
(_a = this._logger) === null || _a === void 0 ? void 0 : _a.debug('Disconnecting gRPC sync connection');
|
|
6390
|
+
closeStreamIfDefined(this._syncStream);
|
|
6391
|
+
this._syncClient.close();
|
|
6392
|
+
});
|
|
6370
6393
|
}
|
|
6371
|
-
listen(
|
|
6394
|
+
listen(dataCallback, reconnectCallback, changedCallback, disconnectCallback, resolveConnect, rejectConnect) {
|
|
6395
|
+
var _a;
|
|
6396
|
+
(_a = this._logger) === null || _a === void 0 ? void 0 : _a.debug('Starting gRPC sync connection');
|
|
6372
6397
|
closeStreamIfDefined(this._syncStream);
|
|
6373
6398
|
this._syncStream = this._syncClient.syncFlags(this._request);
|
|
6374
6399
|
this._syncStream.on('data', (data) => {
|
|
6375
|
-
var _a;
|
|
6376
|
-
(_a = this._logger) === null || _a === void 0 ? void 0 : _a.debug(
|
|
6377
|
-
|
|
6378
|
-
|
|
6379
|
-
|
|
6400
|
+
var _a, _b, _c, _d;
|
|
6401
|
+
(_a = this._logger) === null || _a === void 0 ? void 0 : _a.debug(`Received sync payload`);
|
|
6402
|
+
try {
|
|
6403
|
+
const changes = dataCallback(data.flagConfiguration);
|
|
6404
|
+
if (this._initialized && changes.length > 0) {
|
|
6405
|
+
changedCallback(changes);
|
|
6406
|
+
}
|
|
6407
|
+
}
|
|
6408
|
+
catch (err) {
|
|
6409
|
+
(_b = this._logger) === null || _b === void 0 ? void 0 : _b.debug('Error processing sync payload: ', (_c = err === null || err === void 0 ? void 0 : err.message) !== null && _c !== void 0 ? _c : 'unknown error');
|
|
6410
|
+
}
|
|
6380
6411
|
if (resolveConnect) {
|
|
6381
6412
|
resolveConnect();
|
|
6382
6413
|
}
|
|
6383
|
-
else {
|
|
6414
|
+
else if (!this._isConnected) {
|
|
6415
|
+
// Not the first connection and there's no active connection.
|
|
6416
|
+
(_d = this._logger) === null || _d === void 0 ? void 0 : _d.debug('Reconnected to gRPC sync');
|
|
6384
6417
|
reconnectCallback();
|
|
6385
6418
|
}
|
|
6419
|
+
this._isConnected = true;
|
|
6386
6420
|
});
|
|
6387
6421
|
this._syncStream.on('error', (err) => {
|
|
6388
|
-
var _a;
|
|
6389
|
-
(_a = this._logger) === null || _a === void 0 ? void 0 : _a.error('Connection error, attempting to reconnect'
|
|
6390
|
-
|
|
6391
|
-
|
|
6392
|
-
|
|
6422
|
+
var _a, _b, _c;
|
|
6423
|
+
(_a = this._logger) === null || _a === void 0 ? void 0 : _a.error('Connection error, attempting to reconnect');
|
|
6424
|
+
(_b = this._logger) === null || _b === void 0 ? void 0 : _b.debug(err);
|
|
6425
|
+
this._isConnected = false;
|
|
6426
|
+
const errorMessage = (_c = err === null || err === void 0 ? void 0 : err.message) !== null && _c !== void 0 ? _c : 'Failed to connect to syncFlags stream';
|
|
6427
|
+
disconnectCallback(errorMessage);
|
|
6428
|
+
rejectConnect === null || rejectConnect === void 0 ? void 0 : rejectConnect(new GeneralError(errorMessage));
|
|
6429
|
+
this.reconnect(dataCallback, reconnectCallback, changedCallback, disconnectCallback);
|
|
6393
6430
|
});
|
|
6394
6431
|
}
|
|
6395
|
-
reconnect(
|
|
6432
|
+
reconnect(dataCallback, reconnectCallback, changedCallback, disconnectCallback) {
|
|
6396
6433
|
const channel = this._syncClient.getChannel();
|
|
6397
6434
|
channel.watchConnectivityState(channel.getConnectivityState(true), Infinity, () => {
|
|
6398
|
-
this.listen(
|
|
6435
|
+
this.listen(dataCallback, reconnectCallback, changedCallback, disconnectCallback);
|
|
6436
|
+
});
|
|
6437
|
+
}
|
|
6438
|
+
}
|
|
6439
|
+
|
|
6440
|
+
const encoding = 'utf8';
|
|
6441
|
+
class FileFetch {
|
|
6442
|
+
constructor(filename, logger) {
|
|
6443
|
+
this._filename = filename;
|
|
6444
|
+
this._logger = logger;
|
|
6445
|
+
}
|
|
6446
|
+
connect(dataFillCallback, _, changedCallback) {
|
|
6447
|
+
var _a, _b;
|
|
6448
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
6449
|
+
(_a = this._logger) === null || _a === void 0 ? void 0 : _a.debug('Starting file sync connection');
|
|
6450
|
+
try {
|
|
6451
|
+
const output = yield promises.readFile(this._filename, encoding);
|
|
6452
|
+
// Don't emit the change event for the initial read
|
|
6453
|
+
dataFillCallback(output);
|
|
6454
|
+
// Using watchFile instead of watch to support virtualized host file systems.
|
|
6455
|
+
watchFile(this._filename, () => __awaiter(this, void 0, void 0, function* () {
|
|
6456
|
+
var _c;
|
|
6457
|
+
try {
|
|
6458
|
+
const data = yield promises.readFile(this._filename, encoding);
|
|
6459
|
+
const changes = dataFillCallback(data);
|
|
6460
|
+
if (changes.length > 0) {
|
|
6461
|
+
changedCallback(changes);
|
|
6462
|
+
}
|
|
6463
|
+
}
|
|
6464
|
+
catch (err) {
|
|
6465
|
+
(_c = this._logger) === null || _c === void 0 ? void 0 : _c.error(`Error reading file: ${err}`);
|
|
6466
|
+
}
|
|
6467
|
+
}));
|
|
6468
|
+
}
|
|
6469
|
+
catch (err) {
|
|
6470
|
+
if (err instanceof OpenFeatureError) {
|
|
6471
|
+
throw err;
|
|
6472
|
+
}
|
|
6473
|
+
else {
|
|
6474
|
+
switch (err === null || err === void 0 ? void 0 : err.code) {
|
|
6475
|
+
case 'ENOENT':
|
|
6476
|
+
throw new GeneralError$1(`File not found: ${this._filename}`);
|
|
6477
|
+
case 'EACCES':
|
|
6478
|
+
throw new GeneralError$1(`File not accessible: ${this._filename}`);
|
|
6479
|
+
default:
|
|
6480
|
+
(_b = this._logger) === null || _b === void 0 ? void 0 : _b.debug(`Error reading file: ${err}`);
|
|
6481
|
+
throw new GeneralError$1();
|
|
6482
|
+
}
|
|
6483
|
+
}
|
|
6484
|
+
}
|
|
6485
|
+
});
|
|
6486
|
+
}
|
|
6487
|
+
disconnect() {
|
|
6488
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
6489
|
+
unwatchFile(this._filename);
|
|
6399
6490
|
});
|
|
6400
6491
|
}
|
|
6401
6492
|
}
|
|
6402
6493
|
|
|
6403
6494
|
class InProcessService {
|
|
6404
6495
|
constructor(config, dataFetcher, logger) {
|
|
6405
|
-
this.
|
|
6496
|
+
this.config = config;
|
|
6406
6497
|
this._flagdCore = new FlagdCore(undefined, logger);
|
|
6407
|
-
this._dataFetcher = dataFetcher
|
|
6498
|
+
this._dataFetcher = dataFetcher
|
|
6499
|
+
? dataFetcher
|
|
6500
|
+
: config.offlineFlagSourcePath
|
|
6501
|
+
? new FileFetch(config.offlineFlagSourcePath, logger)
|
|
6502
|
+
: new GrpcFetch(config, undefined, logger);
|
|
6408
6503
|
}
|
|
6409
6504
|
connect(reconnectCallback, changedCallback, disconnectCallback) {
|
|
6410
|
-
return this._dataFetcher.connect(this.
|
|
6505
|
+
return this._dataFetcher.connect(this.setFlagConfiguration.bind(this), reconnectCallback, changedCallback, disconnectCallback);
|
|
6411
6506
|
}
|
|
6412
6507
|
disconnect() {
|
|
6413
6508
|
return __awaiter(this, void 0, void 0, function* () {
|
|
@@ -6415,25 +6510,43 @@ class InProcessService {
|
|
|
6415
6510
|
});
|
|
6416
6511
|
}
|
|
6417
6512
|
resolveBoolean(flagKey, defaultValue, context, logger) {
|
|
6418
|
-
return
|
|
6513
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
6514
|
+
return this.evaluate('boolean', flagKey, defaultValue, context, logger);
|
|
6515
|
+
});
|
|
6419
6516
|
}
|
|
6420
6517
|
resolveNumber(flagKey, defaultValue, context, logger) {
|
|
6421
|
-
return
|
|
6518
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
6519
|
+
return this.evaluate('number', flagKey, defaultValue, context, logger);
|
|
6520
|
+
});
|
|
6422
6521
|
}
|
|
6423
6522
|
resolveString(flagKey, defaultValue, context, logger) {
|
|
6424
|
-
return
|
|
6523
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
6524
|
+
return this.evaluate('string', flagKey, defaultValue, context, logger);
|
|
6525
|
+
});
|
|
6425
6526
|
}
|
|
6426
6527
|
resolveObject(flagKey, defaultValue, context, logger) {
|
|
6427
|
-
return
|
|
6528
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
6529
|
+
return this.evaluate('object', flagKey, defaultValue, context, logger);
|
|
6530
|
+
});
|
|
6428
6531
|
}
|
|
6429
|
-
|
|
6430
|
-
|
|
6431
|
-
|
|
6432
|
-
|
|
6433
|
-
|
|
6434
|
-
|
|
6435
|
-
|
|
6436
|
-
|
|
6532
|
+
evaluate(type, flagKey, defaultValue, context, logger) {
|
|
6533
|
+
const details = this._flagdCore.resolve(type, flagKey, defaultValue, context, logger);
|
|
6534
|
+
return Object.assign(Object.assign({}, details), { flagMetadata: this.addFlagMetadata() });
|
|
6535
|
+
}
|
|
6536
|
+
/**
|
|
6537
|
+
* Adds the flag metadata to the resolution details
|
|
6538
|
+
*/
|
|
6539
|
+
addFlagMetadata() {
|
|
6540
|
+
return Object.assign({}, (this.config.selector ? { scope: this.config.selector } : {}));
|
|
6541
|
+
}
|
|
6542
|
+
/**
|
|
6543
|
+
* Sets the flag configuration
|
|
6544
|
+
* @param flags The flags to set as stringified JSON
|
|
6545
|
+
* @returns {string[]} The flags that have changed
|
|
6546
|
+
* @throws — {Error} If the configuration string is invalid.
|
|
6547
|
+
*/
|
|
6548
|
+
setFlagConfiguration(flags) {
|
|
6549
|
+
return this._flagdCore.setConfigurations(flags);
|
|
6437
6550
|
}
|
|
6438
6551
|
}
|
|
6439
6552
|
|
|
@@ -6480,9 +6593,10 @@ class FlagdProvider {
|
|
|
6480
6593
|
this._status = ProviderStatus.READY;
|
|
6481
6594
|
})
|
|
6482
6595
|
.catch((err) => {
|
|
6483
|
-
var _a;
|
|
6596
|
+
var _a, _b;
|
|
6484
6597
|
this._status = ProviderStatus.ERROR;
|
|
6485
|
-
(_a = this.logger) === null || _a === void 0 ? void 0 : _a.error(`${this.metadata.name}: error during initialization: ${err.message}
|
|
6598
|
+
(_a = this.logger) === null || _a === void 0 ? void 0 : _a.error(`${this.metadata.name}: error during initialization: ${err.message}`);
|
|
6599
|
+
(_b = this.logger) === null || _b === void 0 ? void 0 : _b.debug(err);
|
|
6486
6600
|
throw err;
|
|
6487
6601
|
});
|
|
6488
6602
|
}
|
|
@@ -6515,9 +6629,9 @@ class FlagdProvider {
|
|
|
6515
6629
|
this._status = ProviderStatus.READY;
|
|
6516
6630
|
this._events.emit(ProviderEvents.Ready);
|
|
6517
6631
|
}
|
|
6518
|
-
handleError() {
|
|
6632
|
+
handleError(message) {
|
|
6519
6633
|
this._status = ProviderStatus.ERROR;
|
|
6520
|
-
this._events.emit(ProviderEvents.Error);
|
|
6634
|
+
this._events.emit(ProviderEvents.Error, { message });
|
|
6521
6635
|
}
|
|
6522
6636
|
handleChanged(flagsChanged) {
|
|
6523
6637
|
this._events.emit(ProviderEvents.ConfigurationChanged, { flagsChanged });
|
package/package.json
CHANGED
|
@@ -1,19 +1,19 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@openfeature/flagd-provider",
|
|
3
|
-
"version": "0.10.
|
|
3
|
+
"version": "0.10.5",
|
|
4
4
|
"scripts": {
|
|
5
5
|
"publish-if-not-exists": "cp $NPM_CONFIG_USERCONFIG .npmrc && if [ \"$(npm show $npm_package_name@$npm_package_version version)\" = \"$(npm run current-version -s)\" ]; then echo 'already published, skipping'; else npm publish --access public; fi",
|
|
6
6
|
"current-version": "echo $npm_package_version"
|
|
7
7
|
},
|
|
8
8
|
"dependencies": {
|
|
9
|
-
"@openfeature/flagd-core": "~0.1.
|
|
9
|
+
"@openfeature/flagd-core": "~0.1.10",
|
|
10
10
|
"@protobuf-ts/runtime-rpc": "2.9.0",
|
|
11
11
|
"lru-cache": "10.0.1",
|
|
12
12
|
"util": "0.12.5"
|
|
13
13
|
},
|
|
14
14
|
"peerDependencies": {
|
|
15
15
|
"@grpc/grpc-js": "~1.8.0 || ~1.9.0",
|
|
16
|
-
"@openfeature/server-sdk": ">=1.
|
|
16
|
+
"@openfeature/server-sdk": ">=1.8.0"
|
|
17
17
|
},
|
|
18
18
|
"exports": {
|
|
19
19
|
"./package.json": "./package.json",
|
|
@@ -36,6 +36,11 @@ export interface Config {
|
|
|
36
36
|
* @default 'rpc'
|
|
37
37
|
*/
|
|
38
38
|
resolverType?: ResolverType;
|
|
39
|
+
/**
|
|
40
|
+
* File source of flags to be used by offline mode.
|
|
41
|
+
* Setting this enables the offline mode of the in-process provider.
|
|
42
|
+
*/
|
|
43
|
+
offlineFlagSourcePath?: string;
|
|
39
44
|
/**
|
|
40
45
|
* Selector to be used with flag sync gRPC contract.
|
|
41
46
|
*/
|
|
@@ -60,6 +65,7 @@ export declare function getConfig(options?: FlagdProviderOptions): {
|
|
|
60
65
|
tls: boolean;
|
|
61
66
|
socketPath?: string | undefined;
|
|
62
67
|
resolverType?: ResolverType | undefined;
|
|
68
|
+
offlineFlagSourcePath?: string | undefined;
|
|
63
69
|
selector?: string | undefined;
|
|
64
70
|
cache?: CacheOption | undefined;
|
|
65
71
|
maxCacheSize?: number | undefined;
|
|
@@ -26,7 +26,7 @@ export declare class GRPCService implements Service {
|
|
|
26
26
|
private _eventStream;
|
|
27
27
|
private get _cacheActive();
|
|
28
28
|
constructor(config: Config, client?: ServiceClient, logger?: Logger | undefined);
|
|
29
|
-
connect(reconnectCallback: () => void, changedCallback: (flagsChanged: string[]) => void, disconnectCallback: () => void): Promise<void>;
|
|
29
|
+
connect(reconnectCallback: () => void, changedCallback: (flagsChanged: string[]) => void, disconnectCallback: (message: string) => void): Promise<void>;
|
|
30
30
|
disconnect(): Promise<void>;
|
|
31
31
|
resolveBoolean(flagKey: string, _: boolean, context: EvaluationContext, logger: Logger): Promise<ResolutionDetails<boolean>>;
|
|
32
32
|
resolveString(flagKey: string, _: string, context: EvaluationContext, logger: Logger): Promise<ResolutionDetails<string>>;
|
|
@@ -2,6 +2,32 @@
|
|
|
2
2
|
* Contract of in-process resolver's data fetcher
|
|
3
3
|
*/
|
|
4
4
|
export interface DataFetch {
|
|
5
|
-
|
|
6
|
-
|
|
5
|
+
/**
|
|
6
|
+
* Connects the data fetcher
|
|
7
|
+
*/
|
|
8
|
+
connect(
|
|
9
|
+
/**
|
|
10
|
+
* Callback that runs when data is received from the source
|
|
11
|
+
* @param flags The flags from the source
|
|
12
|
+
* @returns The flags that have changed
|
|
13
|
+
*/
|
|
14
|
+
dataCallback: (flags: string) => string[],
|
|
15
|
+
/**
|
|
16
|
+
* Callback that runs when the connection is re-established
|
|
17
|
+
*/
|
|
18
|
+
reconnectCallback: () => void,
|
|
19
|
+
/**
|
|
20
|
+
* Callback that runs when flags have changed
|
|
21
|
+
* @param flagsChanged The flags that have changed
|
|
22
|
+
*/
|
|
23
|
+
changedCallback: (flagsChanged: string[]) => void,
|
|
24
|
+
/**
|
|
25
|
+
* Callback that runs when the connection is disconnected
|
|
26
|
+
* @param message The reason for the disconnection
|
|
27
|
+
*/
|
|
28
|
+
disconnectCallback: (message: string) => void): Promise<void>;
|
|
29
|
+
/**
|
|
30
|
+
* Disconnects the data fetcher
|
|
31
|
+
*/
|
|
32
|
+
disconnect(): Promise<void>;
|
|
7
33
|
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { Logger } from '@openfeature/core';
|
|
2
|
+
import { DataFetch } from '../data-fetch';
|
|
3
|
+
export declare class FileFetch implements DataFetch {
|
|
4
|
+
private _filename;
|
|
5
|
+
private _logger;
|
|
6
|
+
constructor(filename: string, logger?: Logger);
|
|
7
|
+
connect(dataFillCallback: (flags: string) => string[], _: () => void, changedCallback: (flagsChanged: string[]) => void): Promise<void>;
|
|
8
|
+
disconnect(): Promise<void>;
|
|
9
|
+
}
|
|
@@ -6,13 +6,25 @@ import { DataFetch } from '../data-fetch';
|
|
|
6
6
|
* Implements the gRPC sync contract to fetch flag data.
|
|
7
7
|
*/
|
|
8
8
|
export declare class GrpcFetch implements DataFetch {
|
|
9
|
-
private _syncClient;
|
|
10
|
-
private _syncStream;
|
|
9
|
+
private readonly _syncClient;
|
|
11
10
|
private readonly _request;
|
|
11
|
+
private _syncStream;
|
|
12
12
|
private _logger;
|
|
13
|
+
/**
|
|
14
|
+
* Initialized will be set to true once the initial connection is successful
|
|
15
|
+
* and the first payload has been received. Subsequent reconnects will not
|
|
16
|
+
* change the initialized value.
|
|
17
|
+
*/
|
|
18
|
+
private _initialized;
|
|
19
|
+
/**
|
|
20
|
+
* Is connected represents the current known connection state. It will be
|
|
21
|
+
* set to true once the first payload has been received.but will be set to
|
|
22
|
+
* false if the connection is lost.
|
|
23
|
+
*/
|
|
24
|
+
private _isConnected;
|
|
13
25
|
constructor(config: Config, syncServiceClient?: FlagSyncServiceClient, logger?: Logger);
|
|
14
|
-
connect(
|
|
15
|
-
disconnect(): void
|
|
26
|
+
connect(dataCallback: (flags: string) => string[], reconnectCallback: () => void, changedCallback: (flagsChanged: string[]) => void, disconnectCallback: (message: string) => void): Promise<void>;
|
|
27
|
+
disconnect(): Promise<void>;
|
|
16
28
|
private listen;
|
|
17
29
|
private reconnect;
|
|
18
30
|
}
|
|
@@ -3,15 +3,26 @@ import { EvaluationContext, JsonValue, Logger, ResolutionDetails } from '@openfe
|
|
|
3
3
|
import { Config } from '../../configuration';
|
|
4
4
|
import { DataFetch } from './data-fetch';
|
|
5
5
|
export declare class InProcessService implements Service {
|
|
6
|
-
private
|
|
6
|
+
private readonly config;
|
|
7
7
|
private _flagdCore;
|
|
8
8
|
private _dataFetcher;
|
|
9
|
-
constructor(config: Config, dataFetcher?: DataFetch, logger?: Logger
|
|
10
|
-
connect(reconnectCallback: () => void, changedCallback: (flagsChanged: string[]) => void, disconnectCallback: () => void): Promise<void>;
|
|
9
|
+
constructor(config: Config, dataFetcher?: DataFetch, logger?: Logger);
|
|
10
|
+
connect(reconnectCallback: () => void, changedCallback: (flagsChanged: string[]) => void, disconnectCallback: (message: string) => void): Promise<void>;
|
|
11
11
|
disconnect(): Promise<void>;
|
|
12
12
|
resolveBoolean(flagKey: string, defaultValue: boolean, context: EvaluationContext, logger: Logger): Promise<ResolutionDetails<boolean>>;
|
|
13
13
|
resolveNumber(flagKey: string, defaultValue: number, context: EvaluationContext, logger: Logger): Promise<ResolutionDetails<number>>;
|
|
14
14
|
resolveString(flagKey: string, defaultValue: string, context: EvaluationContext, logger: Logger): Promise<ResolutionDetails<string>>;
|
|
15
15
|
resolveObject<T extends JsonValue>(flagKey: string, defaultValue: T, context: EvaluationContext, logger: Logger): Promise<ResolutionDetails<T>>;
|
|
16
|
-
private
|
|
16
|
+
private evaluate;
|
|
17
|
+
/**
|
|
18
|
+
* Adds the flag metadata to the resolution details
|
|
19
|
+
*/
|
|
20
|
+
private addFlagMetadata;
|
|
21
|
+
/**
|
|
22
|
+
* Sets the flag configuration
|
|
23
|
+
* @param flags The flags to set as stringified JSON
|
|
24
|
+
* @returns {string[]} The flags that have changed
|
|
25
|
+
* @throws — {Error} If the configuration string is invalid.
|
|
26
|
+
*/
|
|
27
|
+
private setFlagConfiguration;
|
|
17
28
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { EvaluationContext, JsonValue, Logger, ResolutionDetails } from '@openfeature/server-sdk';
|
|
2
2
|
export interface Service {
|
|
3
|
-
connect(reconnectCallback: () => void, changedCallback: (flagsChanged: string[]) => void, disconnectCallback: () => void): Promise<void>;
|
|
3
|
+
connect(reconnectCallback: () => void, changedCallback: (flagsChanged: string[]) => void, disconnectCallback: (message: string) => void): Promise<void>;
|
|
4
4
|
disconnect(): Promise<void>;
|
|
5
5
|
resolveBoolean(flagKey: string, defaultValue: boolean, context: EvaluationContext, logger: Logger): Promise<ResolutionDetails<boolean>>;
|
|
6
6
|
resolveString(flagKey: string, defaultValue: string, context: EvaluationContext, logger: Logger): Promise<ResolutionDetails<string>>;
|