@forge/feature-flags-node 1.1.3-next.0 → 1.1.3-next.0-experimental-6a5594a
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 +11 -0
- package/README.md +20 -4
- package/out/data-adapter.d.ts +2 -0
- package/out/data-adapter.d.ts.map +1 -1
- package/out/data-adapter.js +21 -4
- package/out/index.d.ts +22 -1
- package/out/index.d.ts.map +1 -1
- package/out/index.js +24 -3
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,16 @@
|
|
|
1
1
|
# @forge/feature-flags-node
|
|
2
2
|
|
|
3
|
+
## 1.1.3-next.0-experimental-6a5594a
|
|
4
|
+
|
|
5
|
+
### Major Changes
|
|
6
|
+
|
|
7
|
+
- 6a5594a: Added new identifiers for targeting
|
|
8
|
+
|
|
9
|
+
### Patch Changes
|
|
10
|
+
|
|
11
|
+
- Updated dependencies [504cd62]
|
|
12
|
+
- @forge/api@6.1.2-next.0-experimental-6a5594a
|
|
13
|
+
|
|
3
14
|
## 1.1.3-next.0
|
|
4
15
|
|
|
5
16
|
### Patch Changes
|
package/README.md
CHANGED
|
@@ -22,13 +22,15 @@ 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"
|
|
26
26
|
});
|
|
27
27
|
|
|
28
28
|
// Define a user
|
|
29
29
|
const user = {
|
|
30
|
-
|
|
31
|
-
|
|
30
|
+
identifiers:{
|
|
31
|
+
accountId: context?.principal?.accountId,
|
|
32
|
+
},
|
|
33
|
+
attributes: {
|
|
32
34
|
license: context?.license?.isActive
|
|
33
35
|
}
|
|
34
36
|
};
|
|
@@ -62,6 +64,16 @@ export const handler = async (payload, context) => {
|
|
|
62
64
|
environment: getAppContext().environmentType?.toLowerCase() || "development"
|
|
63
65
|
});
|
|
64
66
|
|
|
67
|
+
// Define a user
|
|
68
|
+
const user = {
|
|
69
|
+
identifiers:{
|
|
70
|
+
installContext: context?.installContext,
|
|
71
|
+
},
|
|
72
|
+
custom: {
|
|
73
|
+
issues: 4
|
|
74
|
+
}
|
|
75
|
+
};
|
|
76
|
+
|
|
65
77
|
// Use feature flags...
|
|
66
78
|
const isEnabled = featureFlags.checkFlag(user, "new-feature");
|
|
67
79
|
|
|
@@ -132,8 +144,12 @@ Checks if the service is initialized.
|
|
|
132
144
|
|
|
133
145
|
```typescript
|
|
134
146
|
interface ForgeUser {
|
|
135
|
-
|
|
147
|
+
identifiers?: {
|
|
148
|
+
installContext?: string;
|
|
149
|
+
accountId?: string;
|
|
150
|
+
};
|
|
136
151
|
custom?: Record<string, string | number | boolean>;
|
|
152
|
+
attributes?: Record<string, string | number | boolean>;
|
|
137
153
|
}
|
|
138
154
|
```
|
|
139
155
|
|
package/out/data-adapter.d.ts
CHANGED
|
@@ -3,6 +3,8 @@ 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;
|
|
6
8
|
constructor(pollingIntervalMs?: number, pollingEnabled?: boolean);
|
|
7
9
|
get(key: string): Promise<AdapterResponse>;
|
|
8
10
|
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;AAM7D,qBAAa,gBAAiB,YAAW,YAAY;IASjD,OAAO,CAAC,QAAQ,CAAC,iBAAiB;IAClC,OAAO,CAAC,QAAQ,CAAC,cAAc;IATjC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAkC;IACxD,OAAO,CAAC,OAAO,CAAgE;IAC/E,OAAO,CAAC,IAAI,CAGV;gBAGiB,iBAAiB,GAAE,MAAc,EACjC,cAAc,GAAE,OAAc;IAM3C,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,CAAC;YAoDlC,yBAAyB;IAuCjC,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,16 +6,23 @@ 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
|
+
};
|
|
9
14
|
constructor(pollingIntervalMs = 60000, pollingEnabled = true) {
|
|
10
15
|
this.pollingIntervalMs = pollingIntervalMs;
|
|
11
16
|
this.pollingEnabled = pollingEnabled;
|
|
12
17
|
}
|
|
13
18
|
async get(key) {
|
|
14
19
|
try {
|
|
20
|
+
this.metrics?.counter('forge.feature-flags.data-adapter.get', this.tags).incr();
|
|
15
21
|
const data = await this.fetchFromAtlassianServers();
|
|
16
22
|
if (data) {
|
|
17
23
|
const serializedData = JSON.stringify(data);
|
|
18
24
|
this.cache.set(key, serializedData);
|
|
25
|
+
this.metrics?.counter('forge.feature-flags.data-adapter.get.success', this.tags).incr();
|
|
19
26
|
return {
|
|
20
27
|
result: serializedData,
|
|
21
28
|
time: Date.now()
|
|
@@ -23,19 +30,23 @@ class ForgeDataAdapter {
|
|
|
23
30
|
}
|
|
24
31
|
const cachedValue = this.cache.get(key);
|
|
25
32
|
if (cachedValue) {
|
|
33
|
+
this.metrics?.counter('forge.feature-flags.data-adapter.get.cached', this.tags).incr();
|
|
26
34
|
return {
|
|
27
35
|
result: cachedValue,
|
|
28
36
|
time: Date.now()
|
|
29
37
|
};
|
|
30
38
|
}
|
|
39
|
+
this.metrics?.counter('forge.feature-flags.data-adapter.get.empty', this.tags).incr();
|
|
31
40
|
return {
|
|
32
41
|
result: undefined,
|
|
33
42
|
time: Date.now()
|
|
34
43
|
};
|
|
35
44
|
}
|
|
36
45
|
catch {
|
|
46
|
+
this.metrics?.counter('forge.feature-flags.data-adapter.get.failure', this.tags).incr();
|
|
37
47
|
const cachedValue = this.cache.get(key);
|
|
38
48
|
if (cachedValue) {
|
|
49
|
+
this.metrics?.counter('forge.feature-flags.data-adapter.get.cached', this.tags).incr();
|
|
39
50
|
return {
|
|
40
51
|
result: cachedValue,
|
|
41
52
|
time: Date.now()
|
|
@@ -49,11 +60,17 @@ class ForgeDataAdapter {
|
|
|
49
60
|
}
|
|
50
61
|
async fetchFromAtlassianServers() {
|
|
51
62
|
const runtime = (0, api_1.__getRuntime)();
|
|
52
|
-
if (runtime
|
|
63
|
+
if (runtime?.proxy?.tokenExpiry && runtime?.proxy?.tokenExpiry * 1000 < Date.now()) {
|
|
64
|
+
this.metrics
|
|
65
|
+
?.counter('forge.feature-flags.data-adapter.fetchFromAtlassianServers.tokenExpired', this.tags)
|
|
66
|
+
.incr();
|
|
53
67
|
return null;
|
|
54
68
|
}
|
|
55
|
-
const remainingTime = runtime
|
|
69
|
+
const remainingTime = runtime?.lambdaContext?.getRemainingTimeInMillis?.();
|
|
56
70
|
if (remainingTime !== undefined && remainingTime < 5000) {
|
|
71
|
+
this.metrics
|
|
72
|
+
?.counter('forge.feature-flags.data-adapter.fetchFromAtlassianServers.remainingTimeComplete', this.tags)
|
|
73
|
+
.incr();
|
|
57
74
|
return null;
|
|
58
75
|
}
|
|
59
76
|
const response = await (0, api_1.__fetchProduct)({ provider: 'app', remote: 'feature-flags', type: 'feature-flags' })('/', {
|
|
@@ -84,10 +101,10 @@ class ForgeDataAdapter {
|
|
|
84
101
|
}
|
|
85
102
|
try {
|
|
86
103
|
const runtime = (0, api_1.__getRuntime)();
|
|
87
|
-
if (runtime
|
|
104
|
+
if (runtime?.proxy?.tokenExpiry && runtime?.proxy?.tokenExpiry * 1000 < Date.now()) {
|
|
88
105
|
return false;
|
|
89
106
|
}
|
|
90
|
-
const remainingTime = runtime
|
|
107
|
+
const remainingTime = runtime?.lambdaContext?.getRemainingTimeInMillis?.();
|
|
91
108
|
if (remainingTime !== undefined && remainingTime < 10000) {
|
|
92
109
|
return false;
|
|
93
110
|
}
|
package/out/index.d.ts
CHANGED
|
@@ -2,12 +2,33 @@ 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;
|
|
7
26
|
}
|
|
8
27
|
export declare class ForgeFeatureFlags {
|
|
9
28
|
private initialized;
|
|
10
29
|
private dataAdapter;
|
|
30
|
+
private metrics;
|
|
31
|
+
private tags;
|
|
11
32
|
initialize(config?: ForgeFeatureFlagConfig): Promise<void>;
|
|
12
33
|
checkFlag(user: ForgeUser, flagName: string): boolean;
|
|
13
34
|
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":"AAIA,MAAM,WAAW,sBAAsB;IACrC,WAAW,CAAC,EAAE,aAAa,GAAG,SAAS,GAAG,YAAY,CAAC;CACxD;AAED,MAAM,WAAW,SAAS;IACxB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC,CAAC;IACnD,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC,CAAC;IACvD,WAAW,CAAC,EAAE;QACZ,cAAc,CAAC,EAAE,MAAM,CAAC;QACxB,SAAS,CAAC,EAAE,MAAM,CAAC;KACpB,CAAC;CACH;AAED,MAAM,WAAW,OAAO;IACtB,IAAI,IAAI,IAAI,CAAC;IACb,IAAI,IAAI,IAAI,CAAC;CACd;AACD,MAAM,WAAW,SAAS;IACxB,IAAI,IAAI,IAAI,CAAC;CACd;AACD,MAAM,WAAW,KAAK;IACpB,OAAO,IAAI,SAAS,CAAC;CACtB;AACD,MAAM,WAAW,OAAO;IACtB,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,OAAO,CAAC;IAC9D,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,KAAK,CAAC;CAC5D;AAKD,qBAAa,iBAAiB;IAC5B,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,WAAW,CAAiC;IACpD,OAAO,CAAC,OAAO,CAA2C;IAC1D,OAAO,CAAC,IAAI,CAGV;IAIW,UAAU,CAAC,MAAM,GAAE,sBAA2B,GAAG,OAAO,CAAC,IAAI,CAAC;IAsCpE,SAAS,CAAC,IAAI,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO;IASrD,eAAe,CAAC,IAAI,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,EAAE,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IAexE,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC;IAe/B,aAAa,IAAI,OAAO;IAI/B,OAAO,CAAC,iBAAiB;IAMzB,OAAO,CAAC,WAAW;CAOpB"}
|
package/out/index.js
CHANGED
|
@@ -4,35 +4,54 @@ 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");
|
|
7
8
|
class ForgeFeatureFlags {
|
|
8
9
|
initialized = false;
|
|
9
10
|
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
|
+
};
|
|
10
16
|
async initialize(config = {}) {
|
|
11
17
|
if (this.initialized) {
|
|
12
18
|
return;
|
|
13
19
|
}
|
|
20
|
+
this.metrics?.counter('forge.feature-flags.initialize', this.tags).incr();
|
|
21
|
+
const timer = this.metrics?.timing('forge.feature-flags.initialize', this.tags).measure();
|
|
14
22
|
try {
|
|
15
23
|
statsig_node_1.default.shutdown();
|
|
16
24
|
}
|
|
17
25
|
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
|
+
}
|
|
18
31
|
this.dataAdapter = new data_adapter_1.ForgeDataAdapter(60000, true);
|
|
19
32
|
const statsigOptions = {
|
|
20
33
|
environment: { tier: config.environment || 'development' },
|
|
21
|
-
disableRulesetsSync:
|
|
34
|
+
disableRulesetsSync: disableRulesetsSync,
|
|
22
35
|
disableIdListsSync: true,
|
|
23
36
|
initTimeoutMs: 3000,
|
|
24
37
|
localMode: true,
|
|
38
|
+
disableAllLogging: true,
|
|
39
|
+
disableDiagnostics: true,
|
|
25
40
|
dataAdapter: this.dataAdapter
|
|
26
41
|
};
|
|
27
|
-
await statsig_node_1.default.initialize('forge-internal-key', statsigOptions);
|
|
42
|
+
await statsig_node_1.default.initialize('secret-forge-internal-key', statsigOptions);
|
|
28
43
|
this.initialized = true;
|
|
44
|
+
timer?.stop();
|
|
45
|
+
this.metrics?.counter('forge.feature-flags.initialize.success', this.tags).incr();
|
|
29
46
|
}
|
|
30
47
|
checkFlag(user, flagName) {
|
|
31
48
|
this.ensureInitialized();
|
|
49
|
+
this.metrics?.counter('forge.feature-flags.checkFlag', this.tags).incr();
|
|
32
50
|
return statsig_node_1.default.checkGate(this.convertUser(user), flagName);
|
|
33
51
|
}
|
|
34
52
|
getFeatureFlags(user, flagNames) {
|
|
35
53
|
this.ensureInitialized();
|
|
54
|
+
this.metrics?.counter('forge.feature-flags.getFeatureFlags', this.tags).incr();
|
|
36
55
|
const results = {};
|
|
37
56
|
for (const flagName of flagNames) {
|
|
38
57
|
results[flagName] = statsig_node_1.default.checkGate(this.convertUser(user), flagName);
|
|
@@ -48,6 +67,7 @@ class ForgeFeatureFlags {
|
|
|
48
67
|
await this.dataAdapter.shutdown();
|
|
49
68
|
}
|
|
50
69
|
this.initialized = false;
|
|
70
|
+
this.metrics?.counter('forge.feature-flags.shutdown', this.tags).incr();
|
|
51
71
|
}
|
|
52
72
|
isInitialized() {
|
|
53
73
|
return this.initialized;
|
|
@@ -60,7 +80,8 @@ class ForgeFeatureFlags {
|
|
|
60
80
|
convertUser(user) {
|
|
61
81
|
return {
|
|
62
82
|
userID: user.userID,
|
|
63
|
-
custom: user.custom
|
|
83
|
+
custom: { ...(user.custom || {}), ...(user.attributes || {}) },
|
|
84
|
+
customIDs: user.identifiers || {}
|
|
64
85
|
};
|
|
65
86
|
}
|
|
66
87
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@forge/feature-flags-node",
|
|
3
|
-
"version": "1.1.3-next.0",
|
|
3
|
+
"version": "1.1.3-next.0-experimental-6a5594a",
|
|
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-next.0",
|
|
18
|
+
"@forge/api": "^6.1.2-next.0-experimental-6a5594a",
|
|
19
19
|
"statsig-node": "^6.4.3"
|
|
20
20
|
},
|
|
21
21
|
"publishConfig": {
|