@forge/feature-flags-node 1.1.3-next.0-experimental-6a5594a → 1.1.3
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/CHANGELOG.md +2 -6
- package/README.md +4 -20
- package/out/data-adapter.d.ts +0 -2
- package/out/data-adapter.d.ts.map +1 -1
- package/out/data-adapter.js +4 -21
- package/out/index.d.ts +1 -22
- package/out/index.d.ts.map +1 -1
- package/out/index.js +3 -24
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,15 +1,11 @@
|
|
|
1
1
|
# @forge/feature-flags-node
|
|
2
2
|
|
|
3
|
-
## 1.1.3
|
|
4
|
-
|
|
5
|
-
### Major Changes
|
|
6
|
-
|
|
7
|
-
- 6a5594a: Added new identifiers for targeting
|
|
3
|
+
## 1.1.3
|
|
8
4
|
|
|
9
5
|
### Patch Changes
|
|
10
6
|
|
|
11
7
|
- Updated dependencies [504cd62]
|
|
12
|
-
- @forge/api@6.1.2
|
|
8
|
+
- @forge/api@6.1.2
|
|
13
9
|
|
|
14
10
|
## 1.1.3-next.0
|
|
15
11
|
|
package/README.md
CHANGED
|
@@ -22,15 +22,13 @@ export const handler = async (payload, context) => {
|
|
|
22
22
|
// Initialize the feature flags service
|
|
23
23
|
const featureFlags = new ForgeFeatureFlags();
|
|
24
24
|
await featureFlags.initialize({
|
|
25
|
-
environment: environmentType?.toLowerCase() || "development"
|
|
25
|
+
environment: environmentType?.toLowerCase() || "development" // Optional, defaults to 'development'
|
|
26
26
|
});
|
|
27
27
|
|
|
28
28
|
// Define a user
|
|
29
29
|
const user = {
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
},
|
|
33
|
-
attributes: {
|
|
30
|
+
userID: context?.principal?.accountId,
|
|
31
|
+
custom: {
|
|
34
32
|
license: context?.license?.isActive
|
|
35
33
|
}
|
|
36
34
|
};
|
|
@@ -64,16 +62,6 @@ export const handler = async (payload, context) => {
|
|
|
64
62
|
environment: getAppContext().environmentType?.toLowerCase() || "development"
|
|
65
63
|
});
|
|
66
64
|
|
|
67
|
-
// Define a user
|
|
68
|
-
const user = {
|
|
69
|
-
identifiers:{
|
|
70
|
-
installContext: context?.installContext,
|
|
71
|
-
},
|
|
72
|
-
custom: {
|
|
73
|
-
issues: 4
|
|
74
|
-
}
|
|
75
|
-
};
|
|
76
|
-
|
|
77
65
|
// Use feature flags...
|
|
78
66
|
const isEnabled = featureFlags.checkFlag(user, "new-feature");
|
|
79
67
|
|
|
@@ -144,12 +132,8 @@ Checks if the service is initialized.
|
|
|
144
132
|
|
|
145
133
|
```typescript
|
|
146
134
|
interface ForgeUser {
|
|
147
|
-
|
|
148
|
-
installContext?: string;
|
|
149
|
-
accountId?: string;
|
|
150
|
-
};
|
|
135
|
+
userID: string;
|
|
151
136
|
custom?: Record<string, string | number | boolean>;
|
|
152
|
-
attributes?: Record<string, string | number | boolean>;
|
|
153
137
|
}
|
|
154
138
|
```
|
|
155
139
|
|
package/out/data-adapter.d.ts
CHANGED
|
@@ -3,8 +3,6 @@ export declare class ForgeDataAdapter implements IDataAdapter {
|
|
|
3
3
|
private readonly pollingIntervalMs;
|
|
4
4
|
private readonly pollingEnabled;
|
|
5
5
|
private readonly cache;
|
|
6
|
-
private metrics;
|
|
7
|
-
private tags;
|
|
8
6
|
constructor(pollingIntervalMs?: number, pollingEnabled?: boolean);
|
|
9
7
|
get(key: string): Promise<AdapterResponse>;
|
|
10
8
|
private fetchFromAtlassianServers;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"data-adapter.d.ts","sourceRoot":"","sources":["../src/data-adapter.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;
|
|
1
|
+
{"version":3,"file":"data-adapter.d.ts","sourceRoot":"","sources":["../src/data-adapter.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAK7D,qBAAa,gBAAiB,YAAW,YAAY;IAIjD,OAAO,CAAC,QAAQ,CAAC,iBAAiB;IAClC,OAAO,CAAC,QAAQ,CAAC,cAAc;IAJjC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAkC;gBAGrC,iBAAiB,GAAE,MAAc,EACjC,cAAc,GAAE,OAAc;IAM3C,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,CAAC;YA+ClC,yBAAyB;IAiCjC,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAU9C,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAK3B,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC;IAQ/B,yBAAyB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO;IA8B/C,oBAAoB,IAAI,MAAM;CAG/B"}
|
package/out/data-adapter.js
CHANGED
|
@@ -6,23 +6,16 @@ class ForgeDataAdapter {
|
|
|
6
6
|
pollingIntervalMs;
|
|
7
7
|
pollingEnabled;
|
|
8
8
|
cache = new Map();
|
|
9
|
-
metrics = (0, api_1.__getRuntime)()?.metrics;
|
|
10
|
-
tags = {
|
|
11
|
-
environment: (0, api_1.__getRuntime)()?.appContext?.environmentType || 'unknown',
|
|
12
|
-
appId: (0, api_1.__getRuntime)()?.appContext?.appId || 'unknown'
|
|
13
|
-
};
|
|
14
9
|
constructor(pollingIntervalMs = 60000, pollingEnabled = true) {
|
|
15
10
|
this.pollingIntervalMs = pollingIntervalMs;
|
|
16
11
|
this.pollingEnabled = pollingEnabled;
|
|
17
12
|
}
|
|
18
13
|
async get(key) {
|
|
19
14
|
try {
|
|
20
|
-
this.metrics?.counter('forge.feature-flags.data-adapter.get', this.tags).incr();
|
|
21
15
|
const data = await this.fetchFromAtlassianServers();
|
|
22
16
|
if (data) {
|
|
23
17
|
const serializedData = JSON.stringify(data);
|
|
24
18
|
this.cache.set(key, serializedData);
|
|
25
|
-
this.metrics?.counter('forge.feature-flags.data-adapter.get.success', this.tags).incr();
|
|
26
19
|
return {
|
|
27
20
|
result: serializedData,
|
|
28
21
|
time: Date.now()
|
|
@@ -30,23 +23,19 @@ class ForgeDataAdapter {
|
|
|
30
23
|
}
|
|
31
24
|
const cachedValue = this.cache.get(key);
|
|
32
25
|
if (cachedValue) {
|
|
33
|
-
this.metrics?.counter('forge.feature-flags.data-adapter.get.cached', this.tags).incr();
|
|
34
26
|
return {
|
|
35
27
|
result: cachedValue,
|
|
36
28
|
time: Date.now()
|
|
37
29
|
};
|
|
38
30
|
}
|
|
39
|
-
this.metrics?.counter('forge.feature-flags.data-adapter.get.empty', this.tags).incr();
|
|
40
31
|
return {
|
|
41
32
|
result: undefined,
|
|
42
33
|
time: Date.now()
|
|
43
34
|
};
|
|
44
35
|
}
|
|
45
36
|
catch {
|
|
46
|
-
this.metrics?.counter('forge.feature-flags.data-adapter.get.failure', this.tags).incr();
|
|
47
37
|
const cachedValue = this.cache.get(key);
|
|
48
38
|
if (cachedValue) {
|
|
49
|
-
this.metrics?.counter('forge.feature-flags.data-adapter.get.cached', this.tags).incr();
|
|
50
39
|
return {
|
|
51
40
|
result: cachedValue,
|
|
52
41
|
time: Date.now()
|
|
@@ -60,17 +49,11 @@ class ForgeDataAdapter {
|
|
|
60
49
|
}
|
|
61
50
|
async fetchFromAtlassianServers() {
|
|
62
51
|
const runtime = (0, api_1.__getRuntime)();
|
|
63
|
-
if (runtime
|
|
64
|
-
this.metrics
|
|
65
|
-
?.counter('forge.feature-flags.data-adapter.fetchFromAtlassianServers.tokenExpired', this.tags)
|
|
66
|
-
.incr();
|
|
52
|
+
if (runtime.proxy?.tokenExpiry && runtime.proxy.tokenExpiry * 1000 < Date.now()) {
|
|
67
53
|
return null;
|
|
68
54
|
}
|
|
69
|
-
const remainingTime = runtime
|
|
55
|
+
const remainingTime = runtime.lambdaContext?.getRemainingTimeInMillis?.();
|
|
70
56
|
if (remainingTime !== undefined && remainingTime < 5000) {
|
|
71
|
-
this.metrics
|
|
72
|
-
?.counter('forge.feature-flags.data-adapter.fetchFromAtlassianServers.remainingTimeComplete', this.tags)
|
|
73
|
-
.incr();
|
|
74
57
|
return null;
|
|
75
58
|
}
|
|
76
59
|
const response = await (0, api_1.__fetchProduct)({ provider: 'app', remote: 'feature-flags', type: 'feature-flags' })('/', {
|
|
@@ -101,10 +84,10 @@ class ForgeDataAdapter {
|
|
|
101
84
|
}
|
|
102
85
|
try {
|
|
103
86
|
const runtime = (0, api_1.__getRuntime)();
|
|
104
|
-
if (runtime
|
|
87
|
+
if (runtime.proxy?.tokenExpiry && runtime.proxy.tokenExpiry * 1000 < Date.now()) {
|
|
105
88
|
return false;
|
|
106
89
|
}
|
|
107
|
-
const remainingTime = runtime
|
|
90
|
+
const remainingTime = runtime.lambdaContext?.getRemainingTimeInMillis?.();
|
|
108
91
|
if (remainingTime !== undefined && remainingTime < 10000) {
|
|
109
92
|
return false;
|
|
110
93
|
}
|
package/out/index.d.ts
CHANGED
|
@@ -2,33 +2,12 @@ export interface ForgeFeatureFlagConfig {
|
|
|
2
2
|
environment?: 'development' | 'staging' | 'production';
|
|
3
3
|
}
|
|
4
4
|
export interface ForgeUser {
|
|
5
|
-
userID
|
|
5
|
+
userID: string;
|
|
6
6
|
custom?: Record<string, string | number | boolean>;
|
|
7
|
-
attributes?: Record<string, string | number | boolean>;
|
|
8
|
-
identifiers?: {
|
|
9
|
-
installContext?: string;
|
|
10
|
-
accountId?: string;
|
|
11
|
-
};
|
|
12
|
-
}
|
|
13
|
-
export interface Counter {
|
|
14
|
-
incr(): void;
|
|
15
|
-
decr(): void;
|
|
16
|
-
}
|
|
17
|
-
export interface TimerStop {
|
|
18
|
-
stop(): void;
|
|
19
|
-
}
|
|
20
|
-
export interface Timer {
|
|
21
|
-
measure(): TimerStop;
|
|
22
|
-
}
|
|
23
|
-
export interface Metrics {
|
|
24
|
-
counter(name: string, tags?: Record<string, string>): Counter;
|
|
25
|
-
timing(name: string, tags?: Record<string, string>): Timer;
|
|
26
7
|
}
|
|
27
8
|
export declare class ForgeFeatureFlags {
|
|
28
9
|
private initialized;
|
|
29
10
|
private dataAdapter;
|
|
30
|
-
private metrics;
|
|
31
|
-
private tags;
|
|
32
11
|
initialize(config?: ForgeFeatureFlagConfig): Promise<void>;
|
|
33
12
|
checkFlag(user: ForgeUser, flagName: string): boolean;
|
|
34
13
|
getFeatureFlags(user: ForgeUser, flagNames: string[]): Record<string, boolean>;
|
package/out/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAGA,MAAM,WAAW,sBAAsB;IACrC,WAAW,CAAC,EAAE,aAAa,GAAG,SAAS,GAAG,YAAY,CAAC;CACxD;AAED,MAAM,WAAW,SAAS;IACxB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC,CAAC;CACpD;AAKD,qBAAa,iBAAiB;IAC5B,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,WAAW,CAAiC;IAKvC,UAAU,CAAC,MAAM,GAAE,sBAA2B,GAAG,OAAO,CAAC,IAAI,CAAC;IA0BpE,SAAS,CAAC,IAAI,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO;IAQrD,eAAe,CAAC,IAAI,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,EAAE,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IAcxE,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC;IAe/B,aAAa,IAAI,OAAO;IAI/B,OAAO,CAAC,iBAAiB;IAMzB,OAAO,CAAC,WAAW;CAMpB"}
|
package/out/index.js
CHANGED
|
@@ -4,54 +4,35 @@ exports.ForgeFeatureFlags = void 0;
|
|
|
4
4
|
const tslib_1 = require("tslib");
|
|
5
5
|
const statsig_node_1 = tslib_1.__importDefault(require("statsig-node"));
|
|
6
6
|
const data_adapter_1 = require("./data-adapter");
|
|
7
|
-
const api_1 = require("@forge/api");
|
|
8
7
|
class ForgeFeatureFlags {
|
|
9
8
|
initialized = false;
|
|
10
9
|
dataAdapter = null;
|
|
11
|
-
metrics = (0, api_1.__getRuntime)()?.metrics;
|
|
12
|
-
tags = {
|
|
13
|
-
environment: (0, api_1.__getRuntime)()?.appContext?.environmentType || 'unknown',
|
|
14
|
-
appId: (0, api_1.__getRuntime)()?.appContext?.appId || 'unknown'
|
|
15
|
-
};
|
|
16
10
|
async initialize(config = {}) {
|
|
17
11
|
if (this.initialized) {
|
|
18
12
|
return;
|
|
19
13
|
}
|
|
20
|
-
this.metrics?.counter('forge.feature-flags.initialize', this.tags).incr();
|
|
21
|
-
const timer = this.metrics?.timing('forge.feature-flags.initialize', this.tags).measure();
|
|
22
14
|
try {
|
|
23
15
|
statsig_node_1.default.shutdown();
|
|
24
16
|
}
|
|
25
17
|
catch { }
|
|
26
|
-
const remainingTime = (0, api_1.__getRuntime)()?.lambdaContext?.getRemainingTimeInMillis?.();
|
|
27
|
-
const disableRulesetsSync = remainingTime && remainingTime < 60000 ? true : false;
|
|
28
|
-
if (disableRulesetsSync) {
|
|
29
|
-
this.metrics?.counter('forge.feature-flags.disableRulesetsSync', this.tags).incr();
|
|
30
|
-
}
|
|
31
18
|
this.dataAdapter = new data_adapter_1.ForgeDataAdapter(60000, true);
|
|
32
19
|
const statsigOptions = {
|
|
33
20
|
environment: { tier: config.environment || 'development' },
|
|
34
|
-
disableRulesetsSync:
|
|
21
|
+
disableRulesetsSync: false,
|
|
35
22
|
disableIdListsSync: true,
|
|
36
23
|
initTimeoutMs: 3000,
|
|
37
24
|
localMode: true,
|
|
38
|
-
disableAllLogging: true,
|
|
39
|
-
disableDiagnostics: true,
|
|
40
25
|
dataAdapter: this.dataAdapter
|
|
41
26
|
};
|
|
42
|
-
await statsig_node_1.default.initialize('
|
|
27
|
+
await statsig_node_1.default.initialize('forge-internal-key', statsigOptions);
|
|
43
28
|
this.initialized = true;
|
|
44
|
-
timer?.stop();
|
|
45
|
-
this.metrics?.counter('forge.feature-flags.initialize.success', this.tags).incr();
|
|
46
29
|
}
|
|
47
30
|
checkFlag(user, flagName) {
|
|
48
31
|
this.ensureInitialized();
|
|
49
|
-
this.metrics?.counter('forge.feature-flags.checkFlag', this.tags).incr();
|
|
50
32
|
return statsig_node_1.default.checkGate(this.convertUser(user), flagName);
|
|
51
33
|
}
|
|
52
34
|
getFeatureFlags(user, flagNames) {
|
|
53
35
|
this.ensureInitialized();
|
|
54
|
-
this.metrics?.counter('forge.feature-flags.getFeatureFlags', this.tags).incr();
|
|
55
36
|
const results = {};
|
|
56
37
|
for (const flagName of flagNames) {
|
|
57
38
|
results[flagName] = statsig_node_1.default.checkGate(this.convertUser(user), flagName);
|
|
@@ -67,7 +48,6 @@ class ForgeFeatureFlags {
|
|
|
67
48
|
await this.dataAdapter.shutdown();
|
|
68
49
|
}
|
|
69
50
|
this.initialized = false;
|
|
70
|
-
this.metrics?.counter('forge.feature-flags.shutdown', this.tags).incr();
|
|
71
51
|
}
|
|
72
52
|
isInitialized() {
|
|
73
53
|
return this.initialized;
|
|
@@ -80,8 +60,7 @@ class ForgeFeatureFlags {
|
|
|
80
60
|
convertUser(user) {
|
|
81
61
|
return {
|
|
82
62
|
userID: user.userID,
|
|
83
|
-
custom:
|
|
84
|
-
customIDs: user.identifiers || {}
|
|
63
|
+
custom: user.custom
|
|
85
64
|
};
|
|
86
65
|
}
|
|
87
66
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@forge/feature-flags-node",
|
|
3
|
-
"version": "1.1.3
|
|
3
|
+
"version": "1.1.3",
|
|
4
4
|
"description": "Feature Flags Node SDK for Atlassian Forge apps running on Node Runtime",
|
|
5
5
|
"author": "Atlassian",
|
|
6
6
|
"license": "SEE LICENSE IN LICENSE.txt",
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
"@types/node": "20.19.1"
|
|
16
16
|
},
|
|
17
17
|
"dependencies": {
|
|
18
|
-
"@forge/api": "^6.1.2
|
|
18
|
+
"@forge/api": "^6.1.2",
|
|
19
19
|
"statsig-node": "^6.4.3"
|
|
20
20
|
},
|
|
21
21
|
"publishConfig": {
|