@launchdarkly/node-server-sdk-redis 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/LICENSE +13 -0
  3. package/README.md +89 -0
  4. package/dist/src/LDRedisOptions.d.ts +36 -0
  5. package/dist/src/LDRedisOptions.d.ts.map +1 -0
  6. package/dist/src/LDRedisOptions.js +3 -0
  7. package/dist/src/LDRedisOptions.js.map +1 -0
  8. package/dist/src/RedisBigSegmentStore.d.ts +11 -0
  9. package/dist/src/RedisBigSegmentStore.d.ts.map +1 -0
  10. package/dist/src/RedisBigSegmentStore.js +72 -0
  11. package/dist/src/RedisBigSegmentStore.js.map +1 -0
  12. package/dist/src/RedisBigSegmentStoreFactory.d.ts +15 -0
  13. package/dist/src/RedisBigSegmentStoreFactory.d.ts.map +1 -0
  14. package/dist/src/RedisBigSegmentStoreFactory.js +19 -0
  15. package/dist/src/RedisBigSegmentStoreFactory.js.map +1 -0
  16. package/dist/src/RedisClientState.d.ts +2 -0
  17. package/dist/src/RedisClientState.d.ts.map +1 -0
  18. package/dist/src/RedisClientState.js +109 -0
  19. package/dist/src/RedisClientState.js.map +1 -0
  20. package/dist/src/RedisCore.d.ts +2 -0
  21. package/dist/src/RedisCore.d.ts.map +1 -0
  22. package/dist/src/RedisCore.js +177 -0
  23. package/dist/src/RedisCore.js.map +1 -0
  24. package/dist/src/RedisFeatureStore.d.ts +19 -0
  25. package/dist/src/RedisFeatureStore.d.ts.map +1 -0
  26. package/dist/src/RedisFeatureStore.js +41 -0
  27. package/dist/src/RedisFeatureStore.js.map +1 -0
  28. package/dist/src/RedisFeatureStoreFactory.d.ts +25 -0
  29. package/dist/src/RedisFeatureStoreFactory.d.ts.map +1 -0
  30. package/dist/src/RedisFeatureStoreFactory.js +28 -0
  31. package/dist/src/RedisFeatureStoreFactory.js.map +1 -0
  32. package/dist/src/TtlFromOptions.d.ts +2 -0
  33. package/dist/src/TtlFromOptions.d.ts.map +1 -0
  34. package/dist/src/TtlFromOptions.js +22 -0
  35. package/dist/src/TtlFromOptions.js.map +1 -0
  36. package/dist/src/index.d.ts +4 -0
  37. package/dist/src/index.d.ts.map +1 -0
  38. package/dist/src/index.js +11 -0
  39. package/dist/src/index.js.map +1 -0
  40. package/package.json +54 -0
package/CHANGELOG.md ADDED
@@ -0,0 +1,15 @@
1
+ # Changelog
2
+
3
+ ## 0.1.0 (2023-06-15)
4
+
5
+
6
+ ### Features
7
+
8
+ * Implement redis persistent store and big segment store. ([#146](https://github.com/launchdarkly/js-core/issues/146)) ([fee603c](https://github.com/launchdarkly/js-core/commit/fee603c615d6d17dc7c3248f5ef25a99539d3221))
9
+
10
+
11
+ ### Dependencies
12
+
13
+ * The following workspace dependencies were updated
14
+ * devDependencies
15
+ * @launchdarkly/node-server-sdk bumped from 0.4.4 to 0.5.0
package/LICENSE ADDED
@@ -0,0 +1,13 @@
1
+ Copyright 2023 Catamorphic, Co.
2
+
3
+ Licensed under the Apache License, Version 2.0 (the "License");
4
+ you may not use this file except in compliance with the License.
5
+ You may obtain a copy of the License at
6
+
7
+ http://www.apache.org/licenses/LICENSE-2.0
8
+
9
+ Unless required by applicable law or agreed to in writing, software
10
+ distributed under the License is distributed on an "AS IS" BASIS,
11
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ See the License for the specific language governing permissions and
13
+ limitations under the License.
package/README.md ADDED
@@ -0,0 +1,89 @@
1
+ # LaunchDarkly Server-Side SDK for Node.js
2
+
3
+ [![NPM][node-redis-npm-badge]][node-redis-npm-link]
4
+ [![Actions Status][node-redis-ci-badge]][node-redis-ci]
5
+ [![Documentation](https://img.shields.io/static/v1?label=GitHub+Pages&message=API+reference&color=00add8)](https://launchdarkly.github.io/js-core/packages/store/node-server-sdk-redis/docs/)
6
+
7
+ This library provides a Redis-backed persistence mechanism (feature store) for the [LaunchDarkly Node.js SDK](https://github.com/launchdarkly/js-core/packages/sdk/server-node), replacing the default in-memory feature store. The underlying Redis client implementation is [ioredis](https://github.com/luin/ioredis).
8
+
9
+ The minimum version of the LaunchDarkly Server-Side SDK for Node for use with this library is 8.0.0.
10
+
11
+ This SDK is a beta version and should not be considered ready for production use while this message is visible.
12
+
13
+ ## LaunchDarkly overview
14
+
15
+ [LaunchDarkly](https://www.launchdarkly.com) is a feature management platform that serves over 100 billion feature flags daily to help teams build better software, faster. [Get started](https://docs.launchdarkly.com/home/getting-started) using LaunchDarkly today!
16
+
17
+ [![Twitter Follow](https://img.shields.io/twitter/follow/launchdarkly.svg?style=social&label=Follow&maxAge=2592000)](https://twitter.com/intent/follow?screen_name=launchdarkly)
18
+
19
+ ## Supported Node versions
20
+
21
+ This package is compatible with Node.js versions 14 and above.
22
+
23
+ ## Getting started
24
+
25
+ Refer to [Using Redis as a persistent feature store](https://docs.launchdarkly.com/sdk/features/storing-data/redis#nodejs-server-side).
26
+
27
+ ## Quick setup
28
+
29
+ This assumes that you have already installed the LaunchDarkly Node.js SDK.
30
+
31
+ 1. Install this package with `npm` or `yarn`:
32
+
33
+ ```shell
34
+ npm install @launchdarkly/node-server-sdk-redis --save
35
+ ```
36
+
37
+ 2. If your application does not already have its own dependency on the `ioredis` package, add `ioredis` as well:
38
+
39
+ ```shell
40
+ npm install ioredis --save
41
+ ```
42
+
43
+ 3. Import the package:
44
+
45
+ ```typescript
46
+ import { RedisFeatureStoreFactory } = from '@launchdarkly/node-server-sdk-redis';
47
+ ```
48
+
49
+ 4. When configuring your SDK client, add the Redis feature store:
50
+
51
+ ```typescript
52
+ const storeFactory = RedisFeatureStoreFactory();
53
+ const config = { featureStore: storeFactory };
54
+ const client = LaunchDarkly.init('YOUR SDK KEY', config);
55
+ ```
56
+
57
+ By default, the store will try to connect to a local Redis instance on port 6379. You may specify an alternate configuration as described in the API documentation for `RedisFeatureStoreFactory`.
58
+
59
+ ## Caching behavior
60
+
61
+ To reduce traffic to Redis, there is an optional in-memory cache that retains the last known data for a configurable amount of time. This is on by default; to turn it off (and guarantee that the latest feature flag data will always be retrieved from Redis for every flag evaluation), configure the store as follows:
62
+
63
+ ```typescript
64
+ const factory = RedisFeatureStoreFactory({ cacheTTL: 0 });
65
+ ```
66
+
67
+ ## Contributing
68
+
69
+ We encourage pull requests and other contributions from the community. Check out our [contributing guidelines](CONTRIBUTING.md) for instructions on how to contribute to this SDK.
70
+
71
+ ## About LaunchDarkly
72
+
73
+ - LaunchDarkly is a continuous delivery platform that provides feature flags as a service and allows developers to iterate quickly and safely. We allow you to easily flag your features and manage them from the LaunchDarkly dashboard. With LaunchDarkly, you can:
74
+ - Roll out a new feature to a subset of your users (like a group of users who opt-in to a beta tester group), gathering feedback and bug reports from real-world use cases.
75
+ - Gradually roll out a feature to an increasing percentage of users, and track the effect that the feature has on key metrics (for instance, how likely is a user to complete a purchase if they have feature A versus feature B?).
76
+ - Turn off a feature that you realize is causing performance problems in production, without needing to re-deploy, or even restart the application with a changed configuration file.
77
+ - Grant access to certain features based on user attributes, like payment plan (eg: users on the ‘gold’ plan get access to more features than users in the ‘silver’ plan). Disable parts of your application to facilitate maintenance, without taking everything offline.
78
+ - LaunchDarkly provides feature flag SDKs for a wide variety of languages and technologies. Check out [our documentation](https://docs.launchdarkly.com/sdk) for a complete list.
79
+ - Explore LaunchDarkly
80
+ - [launchdarkly.com](https://www.launchdarkly.com/ 'LaunchDarkly Main Website') for more information
81
+ - [docs.launchdarkly.com](https://docs.launchdarkly.com/ 'LaunchDarkly Documentation') for our documentation and SDK reference guides
82
+ - [apidocs.launchdarkly.com](https://apidocs.launchdarkly.com/ 'LaunchDarkly API Documentation') for our API documentation
83
+ - [blog.launchdarkly.com](https://blog.launchdarkly.com/ 'LaunchDarkly Blog Documentation') for the latest product updates
84
+
85
+ [node-redis-ci-badge]: https://github.com/launchdarkly/js-core/actions/workflows/node-redis.yml/badge.svg
86
+ [node-redis-ci]: https://github.com/launchdarkly/js-core/actions/workflows/node-redis.yml
87
+
88
+ [node-redis-npm-badge]: https://img.shields.io/npm/v/@launchdarkly/node-server-sdk-redis.svg?style=flat-square
89
+ [node-redis-npm-link]: https://www.npmjs.com/package/@launchdarkly/node-server-sdk-redis
@@ -0,0 +1,36 @@
1
+ import { RedisOptions, Redis } from 'ioredis';
2
+ /**
3
+ * The standard options supported for the LaunchDarkly Redis integration.
4
+ */
5
+ export default interface LDRedisOptions {
6
+ /**
7
+ * Optional configuration parameters to be passed to the `redis` package that handles communication
8
+ * with the Redis server.
9
+ *
10
+ * This includes properties such as `host` and `port`. For more details, see:
11
+ * https://github.com/luin/ioredis
12
+ *
13
+ * If you leave this property empty, the default is to connect to `localhost:6379`.
14
+ */
15
+ redisOpts?: RedisOptions;
16
+ /**
17
+ * A string that will be prepended to all Redis keys used by the SDK.
18
+ */
19
+ prefix?: string;
20
+ /**
21
+ * Set this property if you already have a Redis client instance that you wish to reuse. In this
22
+ * case, `redisOpts` will be ignored.
23
+ */
24
+ client?: Redis;
25
+ /**
26
+ * The amount of time, in seconds, that recently read or updated items should remain in an
27
+ * in-memory cache. If it is zero, there will be no in-memory caching. The default TTL will be
28
+ * 30 seconds if one is not set.
29
+ *
30
+ * This parameter applies only to RedisFeatureStore. It is ignored for RedisBigSegmentStore.
31
+ * Caching for RedisBigSegmentStore is configured separately, in the SDK's
32
+ * `LDBigSegmentsOptions` type, since it is independent of what database implementation is used.
33
+ */
34
+ cacheTTL?: number;
35
+ }
36
+ //# sourceMappingURL=LDRedisOptions.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"LDRedisOptions.d.ts","sourceRoot":"","sources":["../../src/LDRedisOptions.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAE9C;;GAEG;AACH,MAAM,CAAC,OAAO,WAAW,cAAc;IACrC;;;;;;;;OAQG;IACH,SAAS,CAAC,EAAE,YAAY,CAAC;IAEzB;;OAEG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;IAEhB;;;OAGG;IACH,MAAM,CAAC,EAAE,KAAK,CAAC;IAEf;;;;;;;;OAQG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB"}
@@ -0,0 +1,3 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ //# sourceMappingURL=LDRedisOptions.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"LDRedisOptions.js","sourceRoot":"","sources":["../../src/LDRedisOptions.ts"],"names":[],"mappings":""}
@@ -0,0 +1,11 @@
1
+ import { LDLogger, interfaces } from '@launchdarkly/node-server-sdk';
2
+ import LDRedisOptions from './LDRedisOptions';
3
+ export default class RedisBigSegmentStore implements interfaces.BigSegmentStore {
4
+ private readonly logger?;
5
+ private state;
6
+ constructor(options?: LDRedisOptions, logger?: LDLogger | undefined);
7
+ getMetadata(): Promise<interfaces.BigSegmentStoreMetadata | undefined>;
8
+ getUserMembership(userHash: string): Promise<interfaces.BigSegmentStoreMembership | undefined>;
9
+ close(): void;
10
+ }
11
+ //# sourceMappingURL=RedisBigSegmentStore.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"RedisBigSegmentStore.d.ts","sourceRoot":"","sources":["../../src/RedisBigSegmentStore.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,+BAA+B,CAAC;AACrE,OAAO,cAAc,MAAM,kBAAkB,CAAC;AAkB9C,MAAM,CAAC,OAAO,OAAO,oBAAqB,YAAW,UAAU,CAAC,eAAe;IAMvC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAC;IAL9D,OAAO,CAAC,KAAK,CAAmB;gBAKpB,OAAO,CAAC,EAAE,cAAc,EAAmB,MAAM,CAAC,sBAAU;IAIlE,WAAW,IAAI,OAAO,CAAC,UAAU,CAAC,uBAAuB,GAAG,SAAS,CAAC;IAUtE,iBAAiB,CACrB,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,UAAU,CAAC,yBAAyB,GAAG,SAAS,CAAC;IAyB5D,KAAK,IAAI,IAAI;CAGd"}
@@ -0,0 +1,72 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.KEY_USER_EXCLUDE = exports.KEY_USER_INCLUDE = exports.KEY_LAST_SYNCHRONIZED = void 0;
13
+ const RedisClientState_1 = require("./RedisClientState");
14
+ /**
15
+ * @internal
16
+ */
17
+ exports.KEY_LAST_SYNCHRONIZED = 'big_segments_synchronized_on';
18
+ /**
19
+ * @internal
20
+ */
21
+ exports.KEY_USER_INCLUDE = 'big_segment_include';
22
+ /**
23
+ * @internal
24
+ */
25
+ exports.KEY_USER_EXCLUDE = 'big_segment_exclude';
26
+ class RedisBigSegmentStore {
27
+ // Logger is not currently used, but is included to reduce the chance of a
28
+ // compatibility break to add a log.
29
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
30
+ constructor(options, logger) {
31
+ this.logger = logger;
32
+ this.state = new RedisClientState_1.default(options);
33
+ }
34
+ getMetadata() {
35
+ return __awaiter(this, void 0, void 0, function* () {
36
+ const value = yield this.state.getClient().get(this.state.prefixedKey(exports.KEY_LAST_SYNCHRONIZED));
37
+ // Value will be true if it is a string containing any characters, which is fine
38
+ // for this check.
39
+ if (value) {
40
+ return { lastUpToDate: parseInt(value, 10) };
41
+ }
42
+ return {};
43
+ });
44
+ }
45
+ getUserMembership(userHash) {
46
+ return __awaiter(this, void 0, void 0, function* () {
47
+ const includedRefs = yield this.state
48
+ .getClient()
49
+ .smembers(this.state.prefixedKey(`${exports.KEY_USER_INCLUDE}:${userHash}`));
50
+ const excludedRefs = yield this.state
51
+ .getClient()
52
+ .smembers(this.state.prefixedKey(`${exports.KEY_USER_EXCLUDE}:${userHash}`));
53
+ // If there are no included/excluded refs, the don't return any membership.
54
+ if ((!includedRefs || !includedRefs.length) && (!excludedRefs || !excludedRefs.length)) {
55
+ return undefined;
56
+ }
57
+ const membership = {};
58
+ excludedRefs === null || excludedRefs === void 0 ? void 0 : excludedRefs.forEach((ref) => {
59
+ membership[ref] = false;
60
+ });
61
+ includedRefs === null || includedRefs === void 0 ? void 0 : includedRefs.forEach((ref) => {
62
+ membership[ref] = true;
63
+ });
64
+ return membership;
65
+ });
66
+ }
67
+ close() {
68
+ this.state.close();
69
+ }
70
+ }
71
+ exports.default = RedisBigSegmentStore;
72
+ //# sourceMappingURL=RedisBigSegmentStore.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"RedisBigSegmentStore.js","sourceRoot":"","sources":["../../src/RedisBigSegmentStore.ts"],"names":[],"mappings":";;;;;;;;;;;;AAEA,yDAAkD;AAElD;;GAEG;AACU,QAAA,qBAAqB,GAAG,8BAA8B,CAAC;AAEpE;;GAEG;AACU,QAAA,gBAAgB,GAAG,qBAAqB,CAAC;AAEtD;;GAEG;AACU,QAAA,gBAAgB,GAAG,qBAAqB,CAAC;AAEtD,MAAqB,oBAAoB;IAGvC,0EAA0E;IAC1E,oCAAoC;IACpC,6DAA6D;IAC7D,YAAY,OAAwB,EAAmB,MAAiB;QAAjB,WAAM,GAAN,MAAM,CAAW;QACtE,IAAI,CAAC,KAAK,GAAG,IAAI,0BAAgB,CAAC,OAAO,CAAC,CAAC;IAC7C,CAAC;IAEK,WAAW;;YACf,MAAM,KAAK,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,SAAS,EAAE,CAAC,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,WAAW,CAAC,6BAAqB,CAAC,CAAC,CAAC;YAC9F,gFAAgF;YAChF,kBAAkB;YAClB,IAAI,KAAK,EAAE;gBACT,OAAO,EAAE,YAAY,EAAE,QAAQ,CAAC,KAAK,EAAE,EAAE,CAAC,EAAE,CAAC;aAC9C;YACD,OAAO,EAAE,CAAC;QACZ,CAAC;KAAA;IAEK,iBAAiB,CACrB,QAAgB;;YAEhB,MAAM,YAAY,GAAG,MAAM,IAAI,CAAC,KAAK;iBAClC,SAAS,EAAE;iBACX,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,WAAW,CAAC,GAAG,wBAAgB,IAAI,QAAQ,EAAE,CAAC,CAAC,CAAC;YACvE,MAAM,YAAY,GAAG,MAAM,IAAI,CAAC,KAAK;iBAClC,SAAS,EAAE;iBACX,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,WAAW,CAAC,GAAG,wBAAgB,IAAI,QAAQ,EAAE,CAAC,CAAC,CAAC;YAEvE,2EAA2E;YAC3E,IAAI,CAAC,CAAC,YAAY,IAAI,CAAC,YAAY,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,YAAY,IAAI,CAAC,YAAY,CAAC,MAAM,CAAC,EAAE;gBACtF,OAAO,SAAS,CAAC;aAClB;YAED,MAAM,UAAU,GAAyC,EAAE,CAAC;YAE5D,YAAY,aAAZ,YAAY,uBAAZ,YAAY,CAAE,OAAO,CAAC,CAAC,GAAG,EAAE,EAAE;gBAC5B,UAAU,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC;YAC1B,CAAC,CAAC,CAAC;YACH,YAAY,aAAZ,YAAY,uBAAZ,YAAY,CAAE,OAAO,CAAC,CAAC,GAAG,EAAE,EAAE;gBAC5B,UAAU,CAAC,GAAG,CAAC,GAAG,IAAI,CAAC;YACzB,CAAC,CAAC,CAAC;YAEH,OAAO,UAAU,CAAC;QACpB,CAAC;KAAA;IAED,KAAK;QACH,IAAI,CAAC,KAAK,CAAC,KAAK,EAAE,CAAC;IACrB,CAAC;CACF;AAlDD,uCAkDC"}
@@ -0,0 +1,15 @@
1
+ import { LDOptions, interfaces } from '@launchdarkly/node-server-sdk';
2
+ import LDRedisOptions from './LDRedisOptions';
3
+ /**
4
+ * Configures a big segment store factory backed by a Redis instance.
5
+ *
6
+ * "Big segments" are a specific type of user segments. For more information, read the
7
+ * LaunchDarkly documentation about user segments: https://docs.launchdarkly.com/home/users/segments
8
+ *
9
+ * @param options The standard options supported for all LaunchDarkly Redis features, including both
10
+ * options for Redis itself and others related to the SDK's behavior.
11
+ *
12
+ * @returns A function which creates big segment stores based on the provided config.
13
+ */
14
+ export default function RedisBigSegmentStoreFactory(options?: LDRedisOptions): (config: LDOptions) => interfaces.BigSegmentStore;
15
+ //# sourceMappingURL=RedisBigSegmentStoreFactory.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"RedisBigSegmentStoreFactory.d.ts","sourceRoot":"","sources":["../../src/RedisBigSegmentStoreFactory.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,+BAA+B,CAAC;AACtE,OAAO,cAAc,MAAM,kBAAkB,CAAC;AAG9C;;;;;;;;;;GAUG;AACH,MAAM,CAAC,OAAO,UAAU,2BAA2B,CACjD,OAAO,CAAC,EAAE,cAAc,GACvB,CAAC,MAAM,EAAE,SAAS,KAAK,UAAU,CAAC,eAAe,CAEnD"}
@@ -0,0 +1,19 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const RedisBigSegmentStore_1 = require("./RedisBigSegmentStore");
4
+ /**
5
+ * Configures a big segment store factory backed by a Redis instance.
6
+ *
7
+ * "Big segments" are a specific type of user segments. For more information, read the
8
+ * LaunchDarkly documentation about user segments: https://docs.launchdarkly.com/home/users/segments
9
+ *
10
+ * @param options The standard options supported for all LaunchDarkly Redis features, including both
11
+ * options for Redis itself and others related to the SDK's behavior.
12
+ *
13
+ * @returns A function which creates big segment stores based on the provided config.
14
+ */
15
+ function RedisBigSegmentStoreFactory(options) {
16
+ return (config) => new RedisBigSegmentStore_1.default(options, config.logger);
17
+ }
18
+ exports.default = RedisBigSegmentStoreFactory;
19
+ //# sourceMappingURL=RedisBigSegmentStoreFactory.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"RedisBigSegmentStoreFactory.js","sourceRoot":"","sources":["../../src/RedisBigSegmentStoreFactory.ts"],"names":[],"mappings":";;AAEA,iEAA0D;AAE1D;;;;;;;;;;GAUG;AACH,SAAwB,2BAA2B,CACjD,OAAwB;IAExB,OAAO,CAAC,MAAiB,EAAE,EAAE,CAAC,IAAI,8BAAoB,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC;AACjF,CAAC;AAJD,8CAIC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=RedisClientState.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"RedisClientState.d.ts","sourceRoot":"","sources":["../../src/RedisClientState.ts"],"names":[],"mappings":""}
@@ -0,0 +1,109 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const ioredis_1 = require("ioredis");
4
+ const DEFAULT_PREFIX = 'launchdarkly';
5
+ /**
6
+ * Class for managing the state of a redis connection.
7
+ *
8
+ * Used for the redis persistent store as well as the redis big segment store.
9
+ *
10
+ * @internal
11
+ */
12
+ class RedisClientState {
13
+ /**
14
+ * Construct a state with the given client.
15
+ *
16
+ * @param client The client for which state is being tracked.
17
+ * @param owned Is this client owned by the store integration, or was it
18
+ * provided externally.
19
+ */
20
+ constructor(options, logger) {
21
+ this.logger = logger;
22
+ this.connected = false;
23
+ this.attempt = 0;
24
+ this.initialConnection = true;
25
+ if (options === null || options === void 0 ? void 0 : options.client) {
26
+ this.client = options.client;
27
+ this.owned = false;
28
+ }
29
+ else if (options === null || options === void 0 ? void 0 : options.redisOpts) {
30
+ this.client = new ioredis_1.Redis(options.redisOpts);
31
+ this.owned = true;
32
+ }
33
+ else {
34
+ this.client = new ioredis_1.Redis();
35
+ this.owned = true;
36
+ }
37
+ this.base_prefix = (options === null || options === void 0 ? void 0 : options.prefix) || DEFAULT_PREFIX;
38
+ // If the client is not owned, then it should already be connected.
39
+ this.connected = !this.owned;
40
+ // We don't want to log a message on the first connection, only when reconnecting.
41
+ this.initialConnection = !this.connected;
42
+ const { client } = this;
43
+ client.on('error', (err) => {
44
+ logger === null || logger === void 0 ? void 0 : logger.error(`Redis error - ${err}`);
45
+ });
46
+ client.on('reconnecting', (delay) => {
47
+ this.attempt += 1;
48
+ logger === null || logger === void 0 ? void 0 : logger.info(`Attempting to reconnect to redis (attempt # ${this.attempt}, delay: ${delay}ms)`);
49
+ });
50
+ client.on('connect', () => {
51
+ var _a;
52
+ this.attempt = 0;
53
+ if (!this.initialConnection) {
54
+ (_a = this === null || this === void 0 ? void 0 : this.logger) === null || _a === void 0 ? void 0 : _a.warn('Reconnecting to Redis');
55
+ }
56
+ this.initialConnection = false;
57
+ this.connected = true;
58
+ });
59
+ client.on('end', () => {
60
+ this.connected = false;
61
+ });
62
+ }
63
+ /**
64
+ * Get the connection state.
65
+ *
66
+ * @returns True if currently connected.
67
+ */
68
+ isConnected() {
69
+ return this.connected;
70
+ }
71
+ /**
72
+ * Get is the client is using its initial connection.
73
+ *
74
+ * @returns True if using the initial connection.
75
+ */
76
+ isInitialConnection() {
77
+ return this.initialConnection;
78
+ }
79
+ /**
80
+ * Get the redis client.
81
+ *
82
+ * @returns The redis client.
83
+ */
84
+ getClient() {
85
+ return this.client;
86
+ }
87
+ /**
88
+ * If the client is owned, then this will 'quit' the client.
89
+ */
90
+ close() {
91
+ if (this.owned) {
92
+ this.client.quit().catch((err) => {
93
+ var _a;
94
+ // Not any action that can be taken for an error on quit.
95
+ (_a = this.logger) === null || _a === void 0 ? void 0 : _a.debug('Error closing ioredis client:', err);
96
+ });
97
+ }
98
+ }
99
+ /**
100
+ * Get a key with prefix prepended.
101
+ * @param key The key to prefix.
102
+ * @returns The prefixed key.
103
+ */
104
+ prefixedKey(key) {
105
+ return `${this.base_prefix}:${key}`;
106
+ }
107
+ }
108
+ exports.default = RedisClientState;
109
+ //# sourceMappingURL=RedisClientState.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"RedisClientState.js","sourceRoot":"","sources":["../../src/RedisClientState.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=RedisCore.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"RedisCore.d.ts","sourceRoot":"","sources":["../../src/RedisCore.ts"],"names":[],"mappings":""}
@@ -0,0 +1,177 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ /**
4
+ * Internal implementation of the Redis data store.
5
+ *
6
+ * Feature flags, segments, and any other kind of entity the LaunchDarkly client may wish
7
+ * to store, are stored as hash values with the main key "{prefix}:features", "{prefix}:segments",
8
+ * etc.
9
+ *
10
+ * Redis only allows a single string value per hash key, so there is no way to store the
11
+ * item metadata (version number and deletion status) separately from the value. The SDK understands
12
+ * that some data store implementations don't have that capability, so it will always pass us a
13
+ * serialized item string that contains the metadata in it, and we're allowed to return 0 as the
14
+ * version number of a queried item to indicate "you have to deserialize the item to find out the
15
+ * metadata".
16
+ *
17
+ * When doing an upsert operation we will always deserialize the item to get the version so the
18
+ * version in the updated descriptor will be correct.
19
+ *
20
+ * The special key "{prefix}:$inited" indicates that the store contains a complete data set.
21
+ *
22
+ * @internal
23
+ */
24
+ class RedisCore {
25
+ constructor(state, logger) {
26
+ this.state = state;
27
+ this.logger = logger;
28
+ this.initedKey = this.state.prefixedKey('$inited');
29
+ }
30
+ init(allData, callback) {
31
+ const multi = this.state.getClient().multi();
32
+ allData.forEach((keyedItems) => {
33
+ const kind = keyedItems.key;
34
+ const items = keyedItems.item;
35
+ const namespaceKey = this.state.prefixedKey(kind.namespace);
36
+ // Delete the namespace for the kind.
37
+ multi.del(namespaceKey);
38
+ const namespaceContent = {};
39
+ items.forEach((keyedItem) => {
40
+ // For each item which exists.
41
+ if (keyedItem.item.serializedItem !== undefined) {
42
+ namespaceContent[keyedItem.key] = keyedItem.item.serializedItem;
43
+ }
44
+ });
45
+ // Only set if there is content to set.
46
+ if (Object.keys(namespaceContent).length > 0) {
47
+ multi.hmset(namespaceKey, namespaceContent);
48
+ }
49
+ });
50
+ multi.set(this.initedKey, '');
51
+ multi.exec((err) => {
52
+ var _a;
53
+ if (err) {
54
+ (_a = this.logger) === null || _a === void 0 ? void 0 : _a.error(`Error initializing Redis store ${err}`);
55
+ }
56
+ callback();
57
+ });
58
+ }
59
+ get(kind, key, callback) {
60
+ var _a;
61
+ if (!this.state.isConnected() && !this.state.isInitialConnection()) {
62
+ (_a = this.logger) === null || _a === void 0 ? void 0 : _a.warn(`Attempted to fetch key '${key}' while Redis connection is down`);
63
+ callback(undefined);
64
+ return;
65
+ }
66
+ this.state.getClient().hget(this.state.prefixedKey(kind.namespace), key, (err, val) => {
67
+ var _a;
68
+ if (err) {
69
+ (_a = this.logger) === null || _a === void 0 ? void 0 : _a.error(`Error fetching key '${key}' from Redis in '${kind.namespace}' ${err}`);
70
+ callback(undefined);
71
+ }
72
+ else if (val) {
73
+ // When getting we do not populate version and deleted.
74
+ // The SDK will have to deserialize to access these values.
75
+ callback({
76
+ version: 0,
77
+ deleted: false,
78
+ serializedItem: val,
79
+ });
80
+ }
81
+ else {
82
+ callback(undefined);
83
+ }
84
+ });
85
+ }
86
+ getAll(kind, callback) {
87
+ var _a;
88
+ if (!this.state.isConnected() && !this.state.isInitialConnection()) {
89
+ (_a = this.logger) === null || _a === void 0 ? void 0 : _a.warn('Attempted to fetch all keys while Redis connection is down');
90
+ callback(undefined);
91
+ return;
92
+ }
93
+ this.state.getClient().hgetall(this.state.prefixedKey(kind.namespace), (err, values) => {
94
+ var _a;
95
+ if (err) {
96
+ (_a = this.logger) === null || _a === void 0 ? void 0 : _a.error(`Error fetching '${kind.namespace}' from Redis ${err}`);
97
+ }
98
+ else if (values) {
99
+ const results = [];
100
+ Object.keys(values).forEach((key) => {
101
+ const value = values[key];
102
+ // When getting we do not populate version and deleted.
103
+ // The SDK will have to deserialize to access these values.
104
+ results.push({ key, item: { version: 0, deleted: false, serializedItem: value } });
105
+ });
106
+ callback(results);
107
+ }
108
+ else {
109
+ callback(undefined);
110
+ }
111
+ });
112
+ }
113
+ upsert(kind, key, descriptor, callback) {
114
+ // The persistent store wrapper manages interactions with a queue, so we can use watch like
115
+ // this without concerns for overlapping transactions.
116
+ this.state.getClient().watch(this.state.prefixedKey(kind.namespace));
117
+ const multi = this.state.getClient().multi();
118
+ this.get(kind, key, (old) => {
119
+ var _a;
120
+ if (old === null || old === void 0 ? void 0 : old.serializedItem) {
121
+ // Here, unfortunately, we have to deserialize the old item just to find
122
+ // out its version number. See notes on this class.
123
+ // Do not look at the meta-data, as we do not read/write it independently
124
+ // with a redis store.
125
+ const deserializedOld = kind.deserialize(old.serializedItem);
126
+ if (((deserializedOld === null || deserializedOld === void 0 ? void 0 : deserializedOld.version) || 0) >= descriptor.version) {
127
+ multi.discard();
128
+ callback(undefined, {
129
+ version: deserializedOld.version,
130
+ deleted: !(deserializedOld === null || deserializedOld === void 0 ? void 0 : deserializedOld.item),
131
+ serializedItem: old.serializedItem,
132
+ });
133
+ return;
134
+ }
135
+ }
136
+ if (descriptor.deleted) {
137
+ multi.hset(this.state.prefixedKey(kind.namespace), key, JSON.stringify({ version: descriptor.version, deleted: true }));
138
+ }
139
+ else if (descriptor.serializedItem) {
140
+ multi.hset(this.state.prefixedKey(kind.namespace), key, descriptor.serializedItem);
141
+ }
142
+ else {
143
+ // This call violates the contract.
144
+ multi.discard();
145
+ (_a = this.logger) === null || _a === void 0 ? void 0 : _a.error('Attempt to write a non-deleted item without data to Redis.');
146
+ callback(undefined, undefined);
147
+ return;
148
+ }
149
+ multi.exec((err, replies) => {
150
+ var _a;
151
+ if (!err && (replies === null || replies === undefined)) {
152
+ // This means the EXEC failed because someone modified the watched key
153
+ (_a = this.logger) === null || _a === void 0 ? void 0 : _a.debug('Concurrent modification detected, retrying');
154
+ this.upsert(kind, key, descriptor, callback);
155
+ }
156
+ else {
157
+ callback(err || undefined, descriptor);
158
+ }
159
+ });
160
+ });
161
+ }
162
+ initialized(callback) {
163
+ this.state.getClient().exists(this.initedKey, (err, count) => {
164
+ // Initialized if there is not an error and the key does exists.
165
+ // (A count >= 1)
166
+ callback(!!(!err && count));
167
+ });
168
+ }
169
+ close() {
170
+ this.state.close();
171
+ }
172
+ getDescription() {
173
+ return 'Redis';
174
+ }
175
+ }
176
+ exports.default = RedisCore;
177
+ //# sourceMappingURL=RedisCore.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"RedisCore.js","sourceRoot":"","sources":["../../src/RedisCore.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,19 @@
1
+ import { interfaces, LDFeatureStore, LDFeatureStoreDataStorage, LDFeatureStoreItem, LDFeatureStoreKindData, LDKeyedFeatureStoreItem, LDLogger } from '@launchdarkly/node-server-sdk';
2
+ import LDRedisOptions from './LDRedisOptions';
3
+ /**
4
+ * Integration between the LaunchDarkly SDK and Redis.
5
+ */
6
+ export default class RedisFeatureStore implements LDFeatureStore {
7
+ private readonly logger?;
8
+ private wrapper;
9
+ constructor(options?: LDRedisOptions, logger?: LDLogger | undefined);
10
+ get(kind: interfaces.DataKind, key: string, callback: (res: LDFeatureStoreItem | null) => void): void;
11
+ all(kind: interfaces.DataKind, callback: (res: LDFeatureStoreKindData) => void): void;
12
+ init(allData: LDFeatureStoreDataStorage, callback: () => void): void;
13
+ delete(kind: interfaces.DataKind, key: string, version: number, callback: () => void): void;
14
+ upsert(kind: interfaces.DataKind, data: LDKeyedFeatureStoreItem, callback: () => void): void;
15
+ initialized(callback: (isInitialized: boolean) => void): void;
16
+ close(): void;
17
+ getDescription?(): string;
18
+ }
19
+ //# sourceMappingURL=RedisFeatureStore.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"RedisFeatureStore.d.ts","sourceRoot":"","sources":["../../src/RedisFeatureStore.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,UAAU,EACV,cAAc,EACd,yBAAyB,EACzB,kBAAkB,EAClB,sBAAsB,EACtB,uBAAuB,EACvB,QAAQ,EAET,MAAM,+BAA+B,CAAC;AACvC,OAAO,cAAc,MAAM,kBAAkB,CAAC;AAK9C;;GAEG;AACH,MAAM,CAAC,OAAO,OAAO,iBAAkB,YAAW,cAAc;IAGxB,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAC;IAF9D,OAAO,CAAC,OAAO,CAA6B;gBAEhC,OAAO,CAAC,EAAE,cAAc,EAAmB,MAAM,CAAC,sBAAU;IAOxE,GAAG,CACD,IAAI,EAAE,UAAU,CAAC,QAAQ,EACzB,GAAG,EAAE,MAAM,EACX,QAAQ,EAAE,CAAC,GAAG,EAAE,kBAAkB,GAAG,IAAI,KAAK,IAAI,GACjD,IAAI;IAIP,GAAG,CAAC,IAAI,EAAE,UAAU,CAAC,QAAQ,EAAE,QAAQ,EAAE,CAAC,GAAG,EAAE,sBAAsB,KAAK,IAAI,GAAG,IAAI;IAIrF,IAAI,CAAC,OAAO,EAAE,yBAAyB,EAAE,QAAQ,EAAE,MAAM,IAAI,GAAG,IAAI;IAIpE,MAAM,CAAC,IAAI,EAAE,UAAU,CAAC,QAAQ,EAAE,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,IAAI,GAAG,IAAI;IAI3F,MAAM,CAAC,IAAI,EAAE,UAAU,CAAC,QAAQ,EAAE,IAAI,EAAE,uBAAuB,EAAE,QAAQ,EAAE,MAAM,IAAI,GAAG,IAAI;IAI5F,WAAW,CAAC,QAAQ,EAAE,CAAC,aAAa,EAAE,OAAO,KAAK,IAAI,GAAG,IAAI;IAI7D,KAAK,IAAI,IAAI;IAIb,cAAc,CAAC,IAAI,MAAM;CAG1B"}
@@ -0,0 +1,41 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const node_server_sdk_1 = require("@launchdarkly/node-server-sdk");
4
+ const RedisCore_1 = require("./RedisCore");
5
+ const RedisClientState_1 = require("./RedisClientState");
6
+ const TtlFromOptions_1 = require("./TtlFromOptions");
7
+ /**
8
+ * Integration between the LaunchDarkly SDK and Redis.
9
+ */
10
+ class RedisFeatureStore {
11
+ constructor(options, logger) {
12
+ this.logger = logger;
13
+ this.wrapper = new node_server_sdk_1.PersistentDataStoreWrapper(new RedisCore_1.default(new RedisClientState_1.default(options, logger), logger), (0, TtlFromOptions_1.default)(options));
14
+ }
15
+ get(kind, key, callback) {
16
+ this.wrapper.get(kind, key, callback);
17
+ }
18
+ all(kind, callback) {
19
+ this.wrapper.all(kind, callback);
20
+ }
21
+ init(allData, callback) {
22
+ this.wrapper.init(allData, callback);
23
+ }
24
+ delete(kind, key, version, callback) {
25
+ this.wrapper.delete(kind, key, version, callback);
26
+ }
27
+ upsert(kind, data, callback) {
28
+ this.wrapper.upsert(kind, data, callback);
29
+ }
30
+ initialized(callback) {
31
+ this.wrapper.initialized(callback);
32
+ }
33
+ close() {
34
+ this.wrapper.close();
35
+ }
36
+ getDescription() {
37
+ return this.wrapper.getDescription();
38
+ }
39
+ }
40
+ exports.default = RedisFeatureStore;
41
+ //# sourceMappingURL=RedisFeatureStore.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"RedisFeatureStore.js","sourceRoot":"","sources":["../../src/RedisFeatureStore.ts"],"names":[],"mappings":";;AAAA,mEASuC;AAEvC,2CAAoC;AACpC,yDAAkD;AAClD,qDAA8C;AAE9C;;GAEG;AACH,MAAqB,iBAAiB;IAGpC,YAAY,OAAwB,EAAmB,MAAiB;QAAjB,WAAM,GAAN,MAAM,CAAW;QACtE,IAAI,CAAC,OAAO,GAAG,IAAI,4CAA0B,CAC3C,IAAI,mBAAS,CAAC,IAAI,0BAAgB,CAAC,OAAO,EAAE,MAAM,CAAC,EAAE,MAAM,CAAC,EAC5D,IAAA,wBAAc,EAAC,OAAO,CAAC,CACxB,CAAC;IACJ,CAAC;IAED,GAAG,CACD,IAAyB,EACzB,GAAW,EACX,QAAkD;QAElD,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,EAAE,GAAG,EAAE,QAAQ,CAAC,CAAC;IACxC,CAAC;IAED,GAAG,CAAC,IAAyB,EAAE,QAA+C;QAC5E,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC;IACnC,CAAC;IAED,IAAI,CAAC,OAAkC,EAAE,QAAoB;QAC3D,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC;IACvC,CAAC;IAED,MAAM,CAAC,IAAyB,EAAE,GAAW,EAAE,OAAe,EAAE,QAAoB;QAClF,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,IAAI,EAAE,GAAG,EAAE,OAAO,EAAE,QAAQ,CAAC,CAAC;IACpD,CAAC;IAED,MAAM,CAAC,IAAyB,EAAE,IAA6B,EAAE,QAAoB;QACnF,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,IAAI,EAAE,IAAI,EAAE,QAAQ,CAAC,CAAC;IAC5C,CAAC;IAED,WAAW,CAAC,QAA0C;QACpD,IAAI,CAAC,OAAO,CAAC,WAAW,CAAC,QAAQ,CAAC,CAAC;IACrC,CAAC;IAED,KAAK;QACH,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE,CAAC;IACvB,CAAC;IAED,cAAc;QACZ,OAAO,IAAI,CAAC,OAAO,CAAC,cAAc,EAAE,CAAC;IACvC,CAAC;CACF;AA7CD,oCA6CC"}
@@ -0,0 +1,25 @@
1
+ import { LDClientContext } from '@launchdarkly/node-server-sdk';
2
+ import RedisFeatureStore from './RedisFeatureStore';
3
+ import LDRedisOptions from './LDRedisOptions';
4
+ /**
5
+ * Configures a feature store backed by a Redis instance.
6
+ *
7
+ * For more details about how and why you can use a persistent feature store, see
8
+ * the [Using Redis as a persistent feature store](https://docs.launchdarkly.com/sdk/features/storing-data/redis#nodejs-server-side).
9
+ *
10
+ * ```
11
+ * const redisStoreFactory = RedisFeatureStoreFactory(
12
+ * {
13
+ * redisOpts: { host: 'redishost', port: 6379 },
14
+ * prefix: 'app1',
15
+ * cacheTTL: 30
16
+ * });
17
+ * ```
18
+ *
19
+ * @param options Optional configuration, please refer to {@link LDRedisOptions}.
20
+ *
21
+ * @returns
22
+ * A factory function suitable for use in the SDK configuration (LDOptions).
23
+ */
24
+ export default function RedisFeatureStoreFactory(options?: LDRedisOptions): (config: LDClientContext) => RedisFeatureStore;
25
+ //# sourceMappingURL=RedisFeatureStoreFactory.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"RedisFeatureStoreFactory.d.ts","sourceRoot":"","sources":["../../src/RedisFeatureStoreFactory.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,+BAA+B,CAAC;AAChE,OAAO,iBAAiB,MAAM,qBAAqB,CAAC;AACpD,OAAO,cAAc,MAAM,kBAAkB,CAAC;AAE9C;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAM,CAAC,OAAO,UAAU,wBAAwB,CAAC,OAAO,CAAC,EAAE,cAAc,YACvD,eAAe,uBAEhC"}
@@ -0,0 +1,28 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const RedisFeatureStore_1 = require("./RedisFeatureStore");
4
+ /**
5
+ * Configures a feature store backed by a Redis instance.
6
+ *
7
+ * For more details about how and why you can use a persistent feature store, see
8
+ * the [Using Redis as a persistent feature store](https://docs.launchdarkly.com/sdk/features/storing-data/redis#nodejs-server-side).
9
+ *
10
+ * ```
11
+ * const redisStoreFactory = RedisFeatureStoreFactory(
12
+ * {
13
+ * redisOpts: { host: 'redishost', port: 6379 },
14
+ * prefix: 'app1',
15
+ * cacheTTL: 30
16
+ * });
17
+ * ```
18
+ *
19
+ * @param options Optional configuration, please refer to {@link LDRedisOptions}.
20
+ *
21
+ * @returns
22
+ * A factory function suitable for use in the SDK configuration (LDOptions).
23
+ */
24
+ function RedisFeatureStoreFactory(options) {
25
+ return (config) => new RedisFeatureStore_1.default(options, config.basicConfiguration.logger);
26
+ }
27
+ exports.default = RedisFeatureStoreFactory;
28
+ //# sourceMappingURL=RedisFeatureStoreFactory.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"RedisFeatureStoreFactory.js","sourceRoot":"","sources":["../../src/RedisFeatureStoreFactory.ts"],"names":[],"mappings":";;AACA,2DAAoD;AAGpD;;;;;;;;;;;;;;;;;;;GAmBG;AACH,SAAwB,wBAAwB,CAAC,OAAwB;IACvE,OAAO,CAAC,MAAuB,EAAE,EAAE,CACjC,IAAI,2BAAiB,CAAC,OAAO,EAAE,MAAM,CAAC,kBAAkB,CAAC,MAAM,CAAC,CAAC;AACrE,CAAC;AAHD,2CAGC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=TtlFromOptions.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TtlFromOptions.d.ts","sourceRoot":"","sources":["../../src/TtlFromOptions.ts"],"names":[],"mappings":""}
@@ -0,0 +1,22 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ /**
4
+ * The default TTL cache time in seconds.
5
+ */
6
+ const DEFAULT_CACHE_TTL_S = 30;
7
+ /**
8
+ * Get a cache TTL based on LDRedisOptions. If the TTL is not specified, then
9
+ * the default of 30 seconds will be used.
10
+ * @param options The options to get a TTL for.
11
+ * @returns The TTL, in seconds.
12
+ * @internal
13
+ */
14
+ function TtlFromOptions(options) {
15
+ // 0 is a valid option. So we need a null/undefined check.
16
+ if ((options === null || options === void 0 ? void 0 : options.cacheTTL) === undefined || options.cacheTTL === null) {
17
+ return DEFAULT_CACHE_TTL_S;
18
+ }
19
+ return options.cacheTTL;
20
+ }
21
+ exports.default = TtlFromOptions;
22
+ //# sourceMappingURL=TtlFromOptions.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TtlFromOptions.js","sourceRoot":"","sources":["../../src/TtlFromOptions.ts"],"names":[],"mappings":";;AAEA;;GAEG;AACH,MAAM,mBAAmB,GAAG,EAAE,CAAC;AAE/B;;;;;;GAMG;AACH,SAAwB,cAAc,CAAC,OAAwB;IAC7D,0DAA0D;IAC1D,IAAI,CAAA,OAAO,aAAP,OAAO,uBAAP,OAAO,CAAE,QAAQ,MAAK,SAAS,IAAI,OAAO,CAAC,QAAQ,KAAK,IAAI,EAAE;QAChE,OAAO,mBAAmB,CAAC;KAC5B;IACD,OAAO,OAAQ,CAAC,QAAQ,CAAC;AAC3B,CAAC;AAND,iCAMC"}
@@ -0,0 +1,4 @@
1
+ export { default as RedisFeatureStore } from './RedisFeatureStoreFactory';
2
+ export { default as RedisBigSegmentStore } from './RedisBigSegmentStoreFactory';
3
+ export { default as LDRedisOptions } from './LDRedisOptions';
4
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,OAAO,IAAI,iBAAiB,EAAE,MAAM,4BAA4B,CAAC;AAC1E,OAAO,EAAE,OAAO,IAAI,oBAAoB,EAAE,MAAM,+BAA+B,CAAC;AAEhF,OAAO,EAAE,OAAO,IAAI,cAAc,EAAE,MAAM,kBAAkB,CAAC"}
@@ -0,0 +1,11 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.RedisBigSegmentStore = exports.RedisFeatureStore = void 0;
4
+ // Exporting the factories without the 'Factory'. This keeps them in-line with
5
+ // previous store versions. The differentiation between the factory and the store
6
+ // is not critical for consuming the SDK.
7
+ var RedisFeatureStoreFactory_1 = require("./RedisFeatureStoreFactory");
8
+ Object.defineProperty(exports, "RedisFeatureStore", { enumerable: true, get: function () { return RedisFeatureStoreFactory_1.default; } });
9
+ var RedisBigSegmentStoreFactory_1 = require("./RedisBigSegmentStoreFactory");
10
+ Object.defineProperty(exports, "RedisBigSegmentStore", { enumerable: true, get: function () { return RedisBigSegmentStoreFactory_1.default; } });
11
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":";;;AAAA,8EAA8E;AAC9E,iFAAiF;AACjF,yCAAyC;AACzC,uEAA0E;AAAjE,6HAAA,OAAO,OAAqB;AACrC,6EAAgF;AAAvE,mIAAA,OAAO,OAAwB"}
package/package.json ADDED
@@ -0,0 +1,54 @@
1
+ {
2
+ "name": "@launchdarkly/node-server-sdk-redis",
3
+ "version": "0.1.0",
4
+ "description": "Redis-backed feature store for the LaunchDarkly Server-Side SDK for Node.js",
5
+ "homepage": "https://github.com/launchdarkly/js-core/tree/main/packages/store/node-server-sdk-redis",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "https://github.com/launchdarkly/js-core.git"
9
+ },
10
+ "type": "commonjs",
11
+ "main": "./dist/src/index.js",
12
+ "types": "./dist/src/index.d.ts",
13
+ "files": [
14
+ "dist"
15
+ ],
16
+ "keywords": [
17
+ "launchdarkly",
18
+ "analytics",
19
+ "client"
20
+ ],
21
+ "license": "Apache-2.0",
22
+ "scripts": {
23
+ "clean": "npx tsc --build --clean",
24
+ "test": "npx jest --ci --runInBand",
25
+ "build": "npx tsc",
26
+ "lint": "npx eslint . --ext .ts",
27
+ "lint:fix": "yarn run lint --fix",
28
+ "doc": "../../../scripts/build-doc.sh ."
29
+ },
30
+ "dependencies": {
31
+ "ioredis": "^5.3.2"
32
+ },
33
+ "peerDependencies": {
34
+ "@launchdarkly/node-server-sdk": "0.4.4"
35
+ },
36
+ "devDependencies": {
37
+ "@launchdarkly/node-server-sdk": "0.5.0",
38
+ "@types/jest": "^29.4.0",
39
+ "@typescript-eslint/eslint-plugin": "^5.22.0",
40
+ "@typescript-eslint/parser": "^5.22.0",
41
+ "eslint": "^8.14.0",
42
+ "eslint-config-airbnb-base": "^15.0.0",
43
+ "eslint-config-airbnb-typescript": "^17.0.0",
44
+ "eslint-config-prettier": "^8.7.0",
45
+ "eslint-plugin-import": "^2.26.0",
46
+ "eslint-plugin-prettier": "^4.2.1",
47
+ "jest": "^29.5.0",
48
+ "launchdarkly-js-test-helpers": "^2.2.0",
49
+ "prettier": "^2.8.4",
50
+ "ts-jest": "^29.0.5",
51
+ "typedoc": "0.23.26",
52
+ "typescript": "^4.6.3"
53
+ }
54
+ }