@atlaspack/feature-flags 2.12.1-dev.3398 → 2.12.1-dev.3443
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/lib/index.js +37 -2
- package/lib/types.d.ts +21 -2
- package/package.json +3 -3
- package/src/index.js +72 -2
- package/src/types.js +23 -2
- package/test/feature-flags.test.js +85 -1
package/lib/index.js
CHANGED
|
@@ -5,15 +5,20 @@ Object.defineProperty(exports, "__esModule", {
|
|
|
5
5
|
});
|
|
6
6
|
exports.DEFAULT_FEATURE_FLAGS = void 0;
|
|
7
7
|
exports.getFeatureFlag = getFeatureFlag;
|
|
8
|
+
exports.runWithConsistencyCheck = runWithConsistencyCheck;
|
|
8
9
|
exports.setFeatureFlags = setFeatureFlags;
|
|
9
10
|
// We need to do these gymnastics as we don't want flow-to-ts to touch DEFAULT_FEATURE_FLAGS,
|
|
10
11
|
// but we want to export FeatureFlags for Flow
|
|
11
12
|
const DEFAULT_FEATURE_FLAGS = exports.DEFAULT_FEATURE_FLAGS = {
|
|
13
|
+
exampleConsistencyCheckFeature: 'OLD',
|
|
12
14
|
exampleFeature: false,
|
|
13
15
|
atlaspackV3: false,
|
|
14
16
|
useWatchmanWatcher: false,
|
|
15
17
|
importRetry: false,
|
|
16
|
-
|
|
18
|
+
fixQuadraticCacheInvalidation: 'OLD',
|
|
19
|
+
fastOptimizeInlineRequires: false,
|
|
20
|
+
useLmdbJsLite: false,
|
|
21
|
+
conditionalBundlingApi: false
|
|
17
22
|
};
|
|
18
23
|
let featureFlagValues = {
|
|
19
24
|
...DEFAULT_FEATURE_FLAGS
|
|
@@ -22,5 +27,35 @@ function setFeatureFlags(flags) {
|
|
|
22
27
|
featureFlagValues = flags;
|
|
23
28
|
}
|
|
24
29
|
function getFeatureFlag(flagName) {
|
|
25
|
-
|
|
30
|
+
const value = featureFlagValues[flagName];
|
|
31
|
+
return value === true || value === 'NEW';
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Run a function with a consistency check.
|
|
35
|
+
*/
|
|
36
|
+
function runWithConsistencyCheck(flag, oldFn, newFn, diffFn, report) {
|
|
37
|
+
const value = featureFlagValues[flag];
|
|
38
|
+
if (!value || value === false || value === 'OLD') {
|
|
39
|
+
return oldFn();
|
|
40
|
+
}
|
|
41
|
+
if (value === true || value === 'NEW') {
|
|
42
|
+
return newFn();
|
|
43
|
+
}
|
|
44
|
+
const oldStartTime = performance.now();
|
|
45
|
+
const oldResult = oldFn();
|
|
46
|
+
const oldExecutionTimeMs = performance.now() - oldStartTime;
|
|
47
|
+
const newStartTime = performance.now();
|
|
48
|
+
const newResult = newFn();
|
|
49
|
+
const newExecutionTimeMs = performance.now() - newStartTime;
|
|
50
|
+
const diff = diffFn(oldResult, newResult);
|
|
51
|
+
report({
|
|
52
|
+
isDifferent: diff.isDifferent,
|
|
53
|
+
oldExecutionTimeMs,
|
|
54
|
+
newExecutionTimeMs,
|
|
55
|
+
custom: diff.custom
|
|
56
|
+
}, oldResult, newResult);
|
|
57
|
+
if (value === 'NEW_AND_CHECK') {
|
|
58
|
+
return newResult;
|
|
59
|
+
}
|
|
60
|
+
return oldResult;
|
|
26
61
|
}
|
package/lib/types.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
export type FeatureFlags = {
|
|
2
2
|
// This feature flag mostly exists to test the feature flag system, and doesn't have any build/runtime effect
|
|
3
3
|
readonly exampleFeature: boolean;
|
|
4
|
+
readonly exampleConsistencyCheckFeature: ConsistencyCheckFeatureFlagValue;
|
|
4
5
|
|
|
5
6
|
/**
|
|
6
7
|
* Rust backed requests
|
|
@@ -18,7 +19,25 @@ export type FeatureFlags = {
|
|
|
18
19
|
importRetry: boolean;
|
|
19
20
|
|
|
20
21
|
/**
|
|
21
|
-
* Enable
|
|
22
|
+
* Enable Rust based LMDB wrapper library
|
|
22
23
|
*/
|
|
23
|
-
|
|
24
|
+
useLmdbJsLite: boolean;
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Fixes quadratic cache invalidation issue
|
|
28
|
+
*/
|
|
29
|
+
fixQuadraticCacheInvalidation: ConsistencyCheckFeatureFlagValue;
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Enable rust based inline requires optimization
|
|
33
|
+
*/
|
|
34
|
+
fastOptimizeInlineRequires: boolean;
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Enables an experimental "conditional bundling" API - this allows the use of `importCond` syntax
|
|
38
|
+
* in order to have (consumer) feature flag driven bundling. This feature is very experimental,
|
|
39
|
+
* and requires server-side support.
|
|
40
|
+
*/
|
|
41
|
+
conditionalBundlingApi: boolean;
|
|
24
42
|
};
|
|
43
|
+
export type ConsistencyCheckFeatureFlagValue = "NEW" | "OLD" | "NEW_AND_CHECK" | "OLD_AND_CHECK";
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@atlaspack/feature-flags",
|
|
3
|
-
"version": "2.12.1-dev.
|
|
3
|
+
"version": "2.12.1-dev.3443+d1170cfc7",
|
|
4
4
|
"description": "Provides internal feature-flags for the atlaspack codebase.",
|
|
5
|
-
"license": "MIT",
|
|
5
|
+
"license": "(MIT OR Apache-2.0)",
|
|
6
6
|
"publishConfig": {
|
|
7
7
|
"access": "public"
|
|
8
8
|
},
|
|
@@ -20,5 +20,5 @@
|
|
|
20
20
|
"engines": {
|
|
21
21
|
"node": ">= 16.0.0"
|
|
22
22
|
},
|
|
23
|
-
"gitHead": "
|
|
23
|
+
"gitHead": "d1170cfc79beb290b2a066f472f68f71f7d7cb23"
|
|
24
24
|
}
|
package/src/index.js
CHANGED
|
@@ -6,11 +6,15 @@ import type {FeatureFlags as _FeatureFlags} from './types';
|
|
|
6
6
|
export type FeatureFlags = _FeatureFlags;
|
|
7
7
|
|
|
8
8
|
export const DEFAULT_FEATURE_FLAGS: FeatureFlags = {
|
|
9
|
+
exampleConsistencyCheckFeature: 'OLD',
|
|
9
10
|
exampleFeature: false,
|
|
10
11
|
atlaspackV3: false,
|
|
11
12
|
useWatchmanWatcher: false,
|
|
12
13
|
importRetry: false,
|
|
13
|
-
|
|
14
|
+
fixQuadraticCacheInvalidation: 'OLD',
|
|
15
|
+
fastOptimizeInlineRequires: false,
|
|
16
|
+
useLmdbJsLite: false,
|
|
17
|
+
conditionalBundlingApi: false,
|
|
14
18
|
};
|
|
15
19
|
|
|
16
20
|
let featureFlagValues: FeatureFlags = {...DEFAULT_FEATURE_FLAGS};
|
|
@@ -20,5 +24,71 @@ export function setFeatureFlags(flags: FeatureFlags) {
|
|
|
20
24
|
}
|
|
21
25
|
|
|
22
26
|
export function getFeatureFlag(flagName: $Keys<FeatureFlags>): boolean {
|
|
23
|
-
|
|
27
|
+
const value = featureFlagValues[flagName];
|
|
28
|
+
return value === true || value === 'NEW';
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export type DiffResult<CustomDiagnostic> = {|
|
|
32
|
+
isDifferent: boolean,
|
|
33
|
+
custom: CustomDiagnostic,
|
|
34
|
+
|};
|
|
35
|
+
|
|
36
|
+
export type Diagnostic<CustomDiagnostic> = {|
|
|
37
|
+
isDifferent: boolean,
|
|
38
|
+
oldExecutionTimeMs: number,
|
|
39
|
+
newExecutionTimeMs: number,
|
|
40
|
+
custom: CustomDiagnostic,
|
|
41
|
+
|};
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Run a function with a consistency check.
|
|
45
|
+
*/
|
|
46
|
+
export function runWithConsistencyCheck<Result, CustomDiagnostic>(
|
|
47
|
+
flag: string,
|
|
48
|
+
oldFn: () => Result,
|
|
49
|
+
newFn: () => Result,
|
|
50
|
+
diffFn: (
|
|
51
|
+
oldResult: Result,
|
|
52
|
+
newResult: Result,
|
|
53
|
+
) => DiffResult<CustomDiagnostic>,
|
|
54
|
+
report: (
|
|
55
|
+
diagnostic: Diagnostic<CustomDiagnostic>,
|
|
56
|
+
oldResult: Result,
|
|
57
|
+
newResult: Result,
|
|
58
|
+
) => void,
|
|
59
|
+
): Result {
|
|
60
|
+
const value = featureFlagValues[flag];
|
|
61
|
+
if (!value || value === false || value === 'OLD') {
|
|
62
|
+
return oldFn();
|
|
63
|
+
}
|
|
64
|
+
if (value === true || value === 'NEW') {
|
|
65
|
+
return newFn();
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const oldStartTime = performance.now();
|
|
69
|
+
const oldResult = oldFn();
|
|
70
|
+
const oldExecutionTimeMs = performance.now() - oldStartTime;
|
|
71
|
+
|
|
72
|
+
const newStartTime = performance.now();
|
|
73
|
+
const newResult = newFn();
|
|
74
|
+
const newExecutionTimeMs = performance.now() - newStartTime;
|
|
75
|
+
|
|
76
|
+
const diff = diffFn(oldResult, newResult);
|
|
77
|
+
|
|
78
|
+
report(
|
|
79
|
+
{
|
|
80
|
+
isDifferent: diff.isDifferent,
|
|
81
|
+
oldExecutionTimeMs,
|
|
82
|
+
newExecutionTimeMs,
|
|
83
|
+
custom: diff.custom,
|
|
84
|
+
},
|
|
85
|
+
oldResult,
|
|
86
|
+
newResult,
|
|
87
|
+
);
|
|
88
|
+
|
|
89
|
+
if (value === 'NEW_AND_CHECK') {
|
|
90
|
+
return newResult;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return oldResult;
|
|
24
94
|
}
|
package/src/types.js
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
export type FeatureFlags = {|
|
|
4
4
|
// This feature flag mostly exists to test the feature flag system, and doesn't have any build/runtime effect
|
|
5
5
|
+exampleFeature: boolean,
|
|
6
|
+
+exampleConsistencyCheckFeature: ConsistencyCheckFeatureFlagValue,
|
|
6
7
|
/**
|
|
7
8
|
* Rust backed requests
|
|
8
9
|
*/
|
|
@@ -16,7 +17,27 @@ export type FeatureFlags = {|
|
|
|
16
17
|
*/
|
|
17
18
|
importRetry: boolean,
|
|
18
19
|
/**
|
|
19
|
-
* Enable
|
|
20
|
+
* Enable Rust based LMDB wrapper library
|
|
20
21
|
*/
|
|
21
|
-
|
|
22
|
+
useLmdbJsLite: boolean,
|
|
23
|
+
/**
|
|
24
|
+
* Fixes quadratic cache invalidation issue
|
|
25
|
+
*/
|
|
26
|
+
fixQuadraticCacheInvalidation: ConsistencyCheckFeatureFlagValue,
|
|
27
|
+
/**
|
|
28
|
+
* Enable rust based inline requires optimization
|
|
29
|
+
*/
|
|
30
|
+
fastOptimizeInlineRequires: boolean,
|
|
31
|
+
/**
|
|
32
|
+
* Enables an experimental "conditional bundling" API - this allows the use of `importCond` syntax
|
|
33
|
+
* in order to have (consumer) feature flag driven bundling. This feature is very experimental,
|
|
34
|
+
* and requires server-side support.
|
|
35
|
+
*/
|
|
36
|
+
conditionalBundlingApi: boolean,
|
|
22
37
|
|};
|
|
38
|
+
|
|
39
|
+
export type ConsistencyCheckFeatureFlagValue =
|
|
40
|
+
| 'NEW'
|
|
41
|
+
| 'OLD'
|
|
42
|
+
| 'NEW_AND_CHECK'
|
|
43
|
+
| 'OLD_AND_CHECK';
|
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
// @flow strict
|
|
2
2
|
import assert from 'assert';
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
getFeatureFlag,
|
|
5
|
+
DEFAULT_FEATURE_FLAGS,
|
|
6
|
+
setFeatureFlags,
|
|
7
|
+
runWithConsistencyCheck,
|
|
8
|
+
} from '../src';
|
|
9
|
+
import sinon from 'sinon';
|
|
4
10
|
|
|
5
11
|
describe('feature-flag test', () => {
|
|
6
12
|
beforeEach(() => {
|
|
@@ -18,4 +24,82 @@ describe('feature-flag test', () => {
|
|
|
18
24
|
setFeatureFlags({...DEFAULT_FEATURE_FLAGS, exampleFeature: true});
|
|
19
25
|
assert.equal(getFeatureFlag('exampleFeature'), true);
|
|
20
26
|
});
|
|
27
|
+
|
|
28
|
+
describe('consistency checks', () => {
|
|
29
|
+
it('runs the old function if the flag is off', () => {
|
|
30
|
+
setFeatureFlags({
|
|
31
|
+
...DEFAULT_FEATURE_FLAGS,
|
|
32
|
+
exampleConsistencyCheckFeature: 'OLD',
|
|
33
|
+
});
|
|
34
|
+
const result = runWithConsistencyCheck(
|
|
35
|
+
'exampleConsistencyCheckFeature',
|
|
36
|
+
() => 'old',
|
|
37
|
+
() => 'new',
|
|
38
|
+
sinon.spy(),
|
|
39
|
+
sinon.spy(),
|
|
40
|
+
);
|
|
41
|
+
assert.equal(result, 'old');
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it('runs the new function if the flag is on', () => {
|
|
45
|
+
setFeatureFlags({
|
|
46
|
+
...DEFAULT_FEATURE_FLAGS,
|
|
47
|
+
exampleConsistencyCheckFeature: 'NEW',
|
|
48
|
+
});
|
|
49
|
+
const result = runWithConsistencyCheck(
|
|
50
|
+
'exampleConsistencyCheckFeature',
|
|
51
|
+
() => 'old',
|
|
52
|
+
() => 'new',
|
|
53
|
+
sinon.spy(),
|
|
54
|
+
sinon.spy(),
|
|
55
|
+
);
|
|
56
|
+
assert.equal(result, 'new');
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it('diffs old and new values if there is a diff value', () => {
|
|
60
|
+
setFeatureFlags({
|
|
61
|
+
...DEFAULT_FEATURE_FLAGS,
|
|
62
|
+
exampleConsistencyCheckFeature: 'OLD_AND_CHECK',
|
|
63
|
+
});
|
|
64
|
+
const reportSpy = sinon.spy();
|
|
65
|
+
const result = runWithConsistencyCheck(
|
|
66
|
+
'exampleConsistencyCheckFeature',
|
|
67
|
+
() => 'old',
|
|
68
|
+
() => 'new',
|
|
69
|
+
() => ({isDifferent: false, custom: 'diff'}),
|
|
70
|
+
reportSpy,
|
|
71
|
+
);
|
|
72
|
+
|
|
73
|
+
assert.equal(result, 'old');
|
|
74
|
+
sinon.assert.calledWith(reportSpy, {
|
|
75
|
+
isDifferent: false,
|
|
76
|
+
oldExecutionTimeMs: sinon.match.number,
|
|
77
|
+
newExecutionTimeMs: sinon.match.number,
|
|
78
|
+
custom: 'diff',
|
|
79
|
+
});
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it('diffs old and new values if there is a diff new value', () => {
|
|
83
|
+
setFeatureFlags({
|
|
84
|
+
...DEFAULT_FEATURE_FLAGS,
|
|
85
|
+
exampleConsistencyCheckFeature: 'NEW_AND_CHECK',
|
|
86
|
+
});
|
|
87
|
+
const reportSpy = sinon.spy();
|
|
88
|
+
const result = runWithConsistencyCheck(
|
|
89
|
+
'exampleConsistencyCheckFeature',
|
|
90
|
+
() => 'old',
|
|
91
|
+
() => 'new',
|
|
92
|
+
() => ({isDifferent: true, custom: 'diff'}),
|
|
93
|
+
reportSpy,
|
|
94
|
+
);
|
|
95
|
+
|
|
96
|
+
assert.equal(result, 'new');
|
|
97
|
+
sinon.assert.calledWith(reportSpy, {
|
|
98
|
+
isDifferent: true,
|
|
99
|
+
oldExecutionTimeMs: sinon.match.number,
|
|
100
|
+
newExecutionTimeMs: sinon.match.number,
|
|
101
|
+
custom: 'diff',
|
|
102
|
+
});
|
|
103
|
+
});
|
|
104
|
+
});
|
|
21
105
|
});
|