flagsmith-nodejs 1.0.9 → 2.0.0-beta.1

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 (127) hide show
  1. package/.github/workflows/pull_request.yaml +33 -0
  2. package/.gitmodules +3 -0
  3. package/.husky/pre-commit +6 -0
  4. package/.idea/flagsmith-nodejs-client.iml +12 -0
  5. package/.idea/modules.xml +8 -0
  6. package/.idea/vcs.xml +6 -0
  7. package/.prettierignore +1 -0
  8. package/.prettierrc.js +9 -0
  9. package/CONTRIBUTING.md +5 -4
  10. package/{LICENCE.md → LICENCE} +5 -6
  11. package/README.md +1 -1
  12. package/build/flagsmith-engine/environments/integrations/models.d.ts +4 -0
  13. package/build/flagsmith-engine/environments/integrations/models.js +8 -0
  14. package/build/flagsmith-engine/environments/models.d.ts +25 -0
  15. package/build/flagsmith-engine/environments/models.js +40 -0
  16. package/build/flagsmith-engine/environments/util.d.ts +3 -0
  17. package/build/flagsmith-engine/environments/util.js +19 -0
  18. package/build/flagsmith-engine/features/constants.d.ts +4 -0
  19. package/build/flagsmith-engine/features/constants.js +7 -0
  20. package/build/flagsmith-engine/features/models.d.ts +32 -0
  21. package/build/flagsmith-engine/features/models.js +85 -0
  22. package/build/flagsmith-engine/features/util.d.ts +3 -0
  23. package/build/flagsmith-engine/features/util.js +20 -0
  24. package/build/flagsmith-engine/identities/models.d.ts +15 -0
  25. package/build/flagsmith-engine/identities/models.js +47 -0
  26. package/build/flagsmith-engine/identities/traits/models.d.ts +5 -0
  27. package/build/flagsmith-engine/identities/traits/models.js +12 -0
  28. package/build/flagsmith-engine/identities/util.d.ts +4 -0
  29. package/build/flagsmith-engine/identities/util.js +22 -0
  30. package/build/flagsmith-engine/index.d.ts +8 -0
  31. package/build/flagsmith-engine/index.js +60 -0
  32. package/build/flagsmith-engine/organisations/models.d.ts +9 -0
  33. package/build/flagsmith-engine/organisations/models.js +21 -0
  34. package/build/flagsmith-engine/organisations/util.d.ts +2 -0
  35. package/build/flagsmith-engine/organisations/util.js +8 -0
  36. package/build/flagsmith-engine/projects/models.d.ts +10 -0
  37. package/build/flagsmith-engine/projects/models.js +18 -0
  38. package/build/flagsmith-engine/projects/util.d.ts +2 -0
  39. package/build/flagsmith-engine/projects/util.js +15 -0
  40. package/build/flagsmith-engine/segments/constants.d.ts +26 -0
  41. package/build/flagsmith-engine/segments/constants.js +31 -0
  42. package/build/flagsmith-engine/segments/evaluators.d.ts +6 -0
  43. package/build/flagsmith-engine/segments/evaluators.js +29 -0
  44. package/build/flagsmith-engine/segments/models.d.ts +31 -0
  45. package/build/flagsmith-engine/segments/models.js +83 -0
  46. package/build/flagsmith-engine/segments/util.d.ts +4 -0
  47. package/build/flagsmith-engine/segments/util.js +23 -0
  48. package/build/flagsmith-engine/utils/collections.d.ts +4 -0
  49. package/build/flagsmith-engine/utils/collections.js +16 -0
  50. package/build/flagsmith-engine/utils/hashing/index.d.ts +1 -0
  51. package/build/flagsmith-engine/utils/hashing/index.js +56 -0
  52. package/build/flagsmith-engine/utils/index.d.ts +1 -0
  53. package/build/flagsmith-engine/utils/index.js +14 -0
  54. package/build/index.d.ts +2 -0
  55. package/build/index.js +7 -0
  56. package/build/sdk/analytics.d.ts +28 -0
  57. package/build/sdk/analytics.js +81 -0
  58. package/build/sdk/errors.d.ts +4 -0
  59. package/build/sdk/errors.js +9 -0
  60. package/build/sdk/index.d.ts +99 -0
  61. package/build/sdk/index.js +221 -0
  62. package/build/sdk/models.d.ts +55 -0
  63. package/build/sdk/models.js +102 -0
  64. package/build/sdk/polling_manager.d.ts +9 -0
  65. package/build/sdk/polling_manager.js +31 -0
  66. package/build/sdk/utils.d.ts +12 -0
  67. package/build/sdk/utils.js +45 -0
  68. package/example/README.md +8 -14
  69. package/example/package-lock.json +12 -12
  70. package/example/package.json +4 -4
  71. package/example/server/api/index.js +19 -22
  72. package/example/server/index.js +4 -9
  73. package/flagsmith-engine/environments/integrations/models.ts +4 -0
  74. package/flagsmith-engine/environments/models.ts +50 -0
  75. package/flagsmith-engine/environments/util.ts +29 -0
  76. package/flagsmith-engine/features/constants.ts +4 -0
  77. package/flagsmith-engine/features/models.ts +105 -0
  78. package/flagsmith-engine/features/util.ts +38 -0
  79. package/flagsmith-engine/identities/models.ts +60 -0
  80. package/flagsmith-engine/identities/traits/models.ts +9 -0
  81. package/flagsmith-engine/identities/util.ts +30 -0
  82. package/flagsmith-engine/index.ts +92 -0
  83. package/flagsmith-engine/organisations/models.ts +25 -0
  84. package/flagsmith-engine/organisations/util.ts +11 -0
  85. package/flagsmith-engine/projects/models.ts +23 -0
  86. package/flagsmith-engine/projects/util.ts +18 -0
  87. package/flagsmith-engine/segments/constants.ts +31 -0
  88. package/flagsmith-engine/segments/evaluators.ts +72 -0
  89. package/flagsmith-engine/segments/models.ts +103 -0
  90. package/flagsmith-engine/segments/util.ts +29 -0
  91. package/flagsmith-engine/utils/collections.ts +14 -0
  92. package/flagsmith-engine/utils/hashing/index.ts +57 -0
  93. package/flagsmith-engine/utils/index.ts +10 -0
  94. package/index.ts +3 -0
  95. package/jest.config.js +5 -0
  96. package/package.json +24 -3
  97. package/sdk/analytics.ts +88 -0
  98. package/sdk/errors.ts +2 -0
  99. package/sdk/index.ts +282 -0
  100. package/sdk/models.ts +143 -0
  101. package/sdk/polling_manager.ts +31 -0
  102. package/sdk/utils.ts +45 -0
  103. package/tests/engine/e2e/engine.test.ts +51 -0
  104. package/tests/engine/engine-tests/engine-test-data/data/environment_n9fbf9h3v4fFgH3U3ngWhb.json +12393 -0
  105. package/tests/engine/engine-tests/engine-test-data/readme.md +30 -0
  106. package/tests/engine/unit/egine.test.ts +96 -0
  107. package/tests/engine/unit/environments/builder.test.ts +148 -0
  108. package/tests/engine/unit/environments/models.test.ts +49 -0
  109. package/tests/engine/unit/features/models.test.ts +72 -0
  110. package/tests/engine/unit/identities/identities_builders.test.ts +85 -0
  111. package/tests/engine/unit/identities/identities_models.test.ts +105 -0
  112. package/tests/engine/unit/organization/models.test.ts +12 -0
  113. package/tests/engine/unit/segments/segments_model.test.ts +101 -0
  114. package/tests/engine/unit/segments/util.ts +151 -0
  115. package/tests/engine/unit/utils.ts +114 -0
  116. package/tests/index.js +0 -0
  117. package/tests/sdk/analytics.test.ts +43 -0
  118. package/tests/sdk/data/environment.json +33 -0
  119. package/tests/sdk/data/flags.json +20 -0
  120. package/tests/sdk/data/identities.json +29 -0
  121. package/tests/sdk/flagsmith.test.ts +184 -0
  122. package/tests/sdk/polling.test.ts +34 -0
  123. package/tests/sdk/utils.ts +39 -0
  124. package/tsconfig.json +19 -0
  125. package/flagsmith-core.js +0 -238
  126. package/index.d.ts +0 -78
  127. package/index.js +0 -5
package/sdk/models.ts ADDED
@@ -0,0 +1,143 @@
1
+ import { FeatureStateModel } from '../flagsmith-engine/features/models';
2
+ import { AnalyticsProcessor } from './analytics';
3
+ import { FlagsmithClientError } from './errors';
4
+
5
+ export class BaseFlag {
6
+ enabled: boolean;
7
+ value: string | number | boolean | undefined;
8
+ isDefault: boolean;
9
+
10
+ constructor(
11
+ value: string | number | boolean | undefined,
12
+ enabled: boolean,
13
+ isDefault: boolean
14
+ ) {
15
+ this.value = value;
16
+ this.enabled = enabled;
17
+ this.isDefault = isDefault;
18
+ }
19
+ }
20
+
21
+ export class DefaultFlag extends BaseFlag {
22
+ constructor(value: string | number | boolean | undefined, enabled: boolean) {
23
+ super(value, enabled, true);
24
+ }
25
+ }
26
+
27
+ export class Flag extends BaseFlag {
28
+ featureId: number;
29
+ featureName: string;
30
+
31
+ constructor(params: {
32
+ value: string | number | boolean | undefined;
33
+ enabled: boolean;
34
+ isDefault?: boolean;
35
+ featureId: number;
36
+ featureName: string;
37
+ }) {
38
+ super(params.value, params.enabled, !!params.isDefault);
39
+ this.featureId = params.featureId;
40
+ this.featureName = params.featureName;
41
+ }
42
+
43
+ static fromFeatureStateModel(
44
+ fsm: FeatureStateModel,
45
+ identityId: number | string | undefined
46
+ ): Flag {
47
+ return new Flag({
48
+ value: fsm.getValue(identityId),
49
+ enabled: fsm.enabled,
50
+ featureId: fsm.feature.id,
51
+ featureName: fsm.feature.name
52
+ });
53
+ }
54
+
55
+ static fromAPIFlag(flagData: any): Flag {
56
+ return new Flag({
57
+ enabled: flagData['enabled'],
58
+ value: flagData['feature_state_value'] || flagData['value'],
59
+ featureId: flagData['feature']['id'],
60
+ featureName: flagData['feature']['name']
61
+ });
62
+ }
63
+ }
64
+
65
+ export class Flags {
66
+ flags: { [key: string]: Flag } = {};
67
+ defaultFlagHandler?: (featureName: string) => DefaultFlag;
68
+ analyticsProcessor?: AnalyticsProcessor;
69
+
70
+ constructor(data: {
71
+ flags: { [key: string]: Flag };
72
+ defaultFlagHandler?: (v: string) => DefaultFlag;
73
+ analyticsProcessor?: AnalyticsProcessor;
74
+ }) {
75
+ this.flags = data.flags;
76
+ this.defaultFlagHandler = data.defaultFlagHandler;
77
+ this.analyticsProcessor = data.analyticsProcessor;
78
+ }
79
+
80
+ static fromFeatureStateModels(data: {
81
+ featureStates: FeatureStateModel[];
82
+ analyticsProcessor?: AnalyticsProcessor;
83
+ defaultFlagHandler?: (f: string) => DefaultFlag;
84
+ identityID?: string | number;
85
+ }): Flags {
86
+ const flags: { [key: string]: any } = {};
87
+ for (const fs of data.featureStates) {
88
+ flags[fs.feature.name] = Flag.fromFeatureStateModel(fs, data.identityID);
89
+ }
90
+ return new Flags({
91
+ flags: flags,
92
+ defaultFlagHandler: data.defaultFlagHandler,
93
+ analyticsProcessor: data.analyticsProcessor
94
+ });
95
+ }
96
+
97
+ static fromAPIFlags(data: {
98
+ apiFlags: { [key: string]: any }[];
99
+ analyticsProcessor?: AnalyticsProcessor;
100
+ defaultFlagHandler?: (v: string) => DefaultFlag;
101
+ }): Flags {
102
+ const flags: { [key: string]: any } = {};
103
+
104
+ for (const flagData of data.apiFlags) {
105
+ flags[flagData['feature']['name']] = Flag.fromAPIFlag(flagData);
106
+ }
107
+
108
+ return new Flags({
109
+ flags: flags,
110
+ defaultFlagHandler: data.defaultFlagHandler,
111
+ analyticsProcessor: data.analyticsProcessor
112
+ });
113
+ }
114
+
115
+ allFlags(): Flag[] {
116
+ return Object.values(this.flags);
117
+ }
118
+
119
+ getFlag(featureName: string): BaseFlag {
120
+ const flag = this.flags[featureName];
121
+
122
+ if (!flag) {
123
+ if (this.defaultFlagHandler) {
124
+ return this.defaultFlagHandler(featureName);
125
+ }
126
+ throw new FlagsmithClientError(`Feature does not exist: ${featureName}, implement defaultFlagHandler to handle this case.`);
127
+ }
128
+
129
+ if (this.analyticsProcessor && flag.featureId) {
130
+ this.analyticsProcessor.trackFeature(flag.featureId);
131
+ }
132
+
133
+ return flag;
134
+ }
135
+
136
+ getFeatureValue(featureName: string): any {
137
+ return this.getFlag(featureName).value;
138
+ }
139
+
140
+ isFeatureEnabled(featureName: string): boolean {
141
+ return this.getFlag(featureName).enabled;
142
+ }
143
+ }
@@ -0,0 +1,31 @@
1
+ import { Flagsmith } from '.';
2
+
3
+ export class EnvironmentDataPollingManager {
4
+ private interval?: NodeJS.Timer;
5
+ private main: Flagsmith;
6
+ private refreshIntervalSeconds: number;
7
+
8
+ constructor(main: Flagsmith, refreshIntervalSeconds: number) {
9
+ this.main = main;
10
+ this.refreshIntervalSeconds = refreshIntervalSeconds;
11
+ }
12
+
13
+ start() {
14
+ const updateEnvironment = () => {
15
+ if(this.interval) clearInterval(this.interval)
16
+ this.interval = setInterval(async () => {
17
+ await this.main.updateEnvironment();
18
+ }, this.refreshIntervalSeconds * 1000);
19
+ };
20
+ // todo: this call should be awaited for getIdentityFlags/getEnvironmentFlags when enableLocalEvaluation is true
21
+ this.main.updateEnvironment()
22
+ updateEnvironment()
23
+ }
24
+
25
+ stop() {
26
+ if (!this.interval) {
27
+ return;
28
+ }
29
+ clearInterval(this.interval);
30
+ }
31
+ }
package/sdk/utils.ts ADDED
@@ -0,0 +1,45 @@
1
+ import fetch, { Response } from 'node-fetch';
2
+ // @ts-ignore
3
+ if (typeof fetch.default !== 'undefined') fetch = fetch.default;
4
+
5
+ export function generateIdentitiesData(identifier: string, traits?: { [key: string]: any }) {
6
+ const traitsGenerated = Object.values(traits || {}).map(trait => ({
7
+ trait_key: trait[0],
8
+ trait_value: trait[1]
9
+ }));
10
+ return {
11
+ identifier: identifier,
12
+ traits: traitsGenerated
13
+ };
14
+ }
15
+
16
+ export const delay = (ms: number) =>
17
+ new Promise(resolve => setTimeout(() => resolve(undefined), ms));
18
+
19
+ export const retryFetch = (
20
+ url: string,
21
+ fetchOptions = {},
22
+ retries = 3,
23
+ retryDelay = 1000,
24
+ timeout: number
25
+ ): Promise<Response> => {
26
+ return new Promise((resolve, reject) => {
27
+ // check for timeout
28
+ if (timeout) setTimeout(() => reject('error: timeout'), timeout);
29
+
30
+ const wrapper = (n: number) => {
31
+ fetch(url, fetchOptions)
32
+ .then(res => resolve(res))
33
+ .catch(async err => {
34
+ if (n > 0) {
35
+ await delay(retryDelay);
36
+ wrapper(--n);
37
+ } else {
38
+ reject(err);
39
+ }
40
+ });
41
+ };
42
+
43
+ wrapper(retries);
44
+ });
45
+ };
@@ -0,0 +1,51 @@
1
+ import { readFileSync } from 'fs';
2
+ import { getIdentityFeatureStates } from '../../../flagsmith-engine';
3
+ import { EnvironmentModel } from '../../../flagsmith-engine/environments/models';
4
+ import { buildEnvironmentModel } from '../../../flagsmith-engine/environments/util';
5
+ import { IdentityModel } from '../../../flagsmith-engine/identities/models';
6
+ import { buildIdentityModel } from '../../../flagsmith-engine/identities/util';
7
+
8
+ function extractTestCases(
9
+ filePath: string
10
+ ): {
11
+ environment: EnvironmentModel;
12
+ identity: IdentityModel;
13
+ response: any;
14
+ }[] {
15
+ const data = JSON.parse(readFileSync(filePath, 'utf-8'));
16
+ const environmentModel = buildEnvironmentModel(data['environment']);
17
+ const test_data = data['identities_and_responses'].map((test_case: any) => {
18
+ const identity = buildIdentityModel(test_case['identity']);
19
+
20
+ return {
21
+ environment: environmentModel,
22
+ identity: identity,
23
+ response: test_case['response']
24
+ };
25
+ });
26
+ return test_data;
27
+ }
28
+
29
+ test('Test Engine', () => {
30
+ const testCases = extractTestCases(
31
+ __dirname + '/../engine-tests/engine-test-data/data/environment_n9fbf9h3v4fFgH3U3ngWhb.json'
32
+ );
33
+ for (const testCase of testCases) {
34
+ const engine_response = getIdentityFeatureStates(testCase.environment, testCase.identity);
35
+ const sortedEngineFlags = engine_response.sort((a, b) =>
36
+ a.feature.name > b.feature.name ? 1 : -1
37
+ );
38
+ const sortedAPIFlags = testCase.response['flags'].sort((a: any, b: any) =>
39
+ a.feature.name > b.feature.name ? 1 : -1
40
+ );
41
+
42
+ expect(sortedEngineFlags.length).toBe(sortedAPIFlags.length);
43
+
44
+ for (let i = 0; i < sortedEngineFlags.length; i++) {
45
+ expect(sortedEngineFlags[i].getValue(testCase.identity.djangoID)).toBe(
46
+ sortedAPIFlags[i]['feature_state_value']
47
+ );
48
+ expect(sortedEngineFlags[i].enabled).toBe(sortedAPIFlags[i]['enabled']);
49
+ }
50
+ }
51
+ });