@forge/feature-flags-node 2.1.9-next.0-experimental-2682d7a → 2.1.9-next.0-experimental-44a932f
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 +25 -29
- package/out/data-adapter.d.ts +17 -0
- package/out/data-adapter.d.ts.map +1 -0
- package/out/data-adapter.js +121 -0
- package/out/index.d.ts +40 -18
- package/out/index.d.ts.map +1 -1
- package/out/index.js +58 -99
- package/package.json +3 -2
- package/out/evaluator.d.ts +0 -13
- package/out/evaluator.d.ts.map +0 -1
- package/out/evaluator.js +0 -95
- package/out/types/index.d.ts +0 -94
- package/out/types/index.d.ts.map +0 -1
- package/out/types/index.js +0 -14
- package/out/utils/conditions.d.ts +0 -3
- package/out/utils/conditions.d.ts.map +0 -1
- package/out/utils/conditions.js +0 -78
- package/out/utils/hash.d.ts +0 -2
- package/out/utils/hash.d.ts.map +0 -1
- package/out/utils/hash.js +0 -10
package/CHANGELOG.md
CHANGED
|
@@ -1,14 +1,10 @@
|
|
|
1
1
|
# @forge/feature-flags-node
|
|
2
2
|
|
|
3
|
-
## 2.1.9-next.0-experimental-
|
|
4
|
-
|
|
5
|
-
### Major Changes
|
|
6
|
-
|
|
7
|
-
- af6f22c: Rule evaluator implementation change
|
|
3
|
+
## 2.1.9-next.0-experimental-44a932f
|
|
8
4
|
|
|
9
5
|
### Patch Changes
|
|
10
6
|
|
|
11
|
-
- @forge/api@7.0.1-next.0-experimental-
|
|
7
|
+
- @forge/api@7.0.1-next.0-experimental-44a932f
|
|
12
8
|
|
|
13
9
|
## 2.1.9-next.0
|
|
14
10
|
|
package/README.md
CHANGED
|
@@ -47,6 +47,7 @@ export const handler = async (payload, context) => {
|
|
|
47
47
|
capabilitySetValue = "capabilityAdvanced";
|
|
48
48
|
}
|
|
49
49
|
|
|
50
|
+
|
|
50
51
|
// Initialize the feature flags SDK
|
|
51
52
|
const featureFlags = new ForgeFeatureFlags();
|
|
52
53
|
await featureFlags.initialize({
|
|
@@ -55,7 +56,7 @@ export const handler = async (payload, context) => {
|
|
|
55
56
|
|
|
56
57
|
// Define a user with all possible attributes that will be used in the feature flag rules
|
|
57
58
|
const user = {
|
|
58
|
-
identifiers:
|
|
59
|
+
identifiers:{
|
|
59
60
|
accountId: context?.principal?.accountId,
|
|
60
61
|
},
|
|
61
62
|
attributes: {
|
|
@@ -70,14 +71,11 @@ export const handler = async (payload, context) => {
|
|
|
70
71
|
// Check a feature flag (synchronous after initialization)
|
|
71
72
|
const isEnabled = featureFlags.checkFlag(user, "new-feature");
|
|
72
73
|
|
|
73
|
-
//
|
|
74
|
-
const
|
|
75
|
-
|
|
76
|
-
// Get all available flag IDs
|
|
77
|
-
const allFlagIds = featureFlags.getAllFlagIds();
|
|
74
|
+
// Get multiple flags at once (synchronous)
|
|
75
|
+
const flags = featureFlags.getFeatureFlags(user, ["feature-a", "feature-b"]);
|
|
78
76
|
|
|
79
|
-
// Shutdown when done
|
|
80
|
-
featureFlags.shutdown();
|
|
77
|
+
// Shutdown when done
|
|
78
|
+
await featureFlags.shutdown();
|
|
81
79
|
}
|
|
82
80
|
|
|
83
81
|
```
|
|
@@ -101,10 +99,10 @@ export const handler = async (payload, context) => {
|
|
|
101
99
|
|
|
102
100
|
// Define a user
|
|
103
101
|
const user = {
|
|
104
|
-
identifiers:
|
|
102
|
+
identifiers:{
|
|
105
103
|
installContext: context?.installContext,
|
|
106
104
|
},
|
|
107
|
-
|
|
105
|
+
custom: {
|
|
108
106
|
issues: 4
|
|
109
107
|
}
|
|
110
108
|
};
|
|
@@ -112,8 +110,8 @@ export const handler = async (payload, context) => {
|
|
|
112
110
|
// Use feature flags...
|
|
113
111
|
const isEnabled = featureFlags.checkFlag(user, "new-feature");
|
|
114
112
|
|
|
115
|
-
// Shutdown when done
|
|
116
|
-
featureFlags.shutdown();
|
|
113
|
+
// Shutdown when done
|
|
114
|
+
await featureFlags.shutdown();
|
|
117
115
|
}
|
|
118
116
|
```
|
|
119
117
|
|
|
@@ -163,32 +161,30 @@ interface ForgeFeatureFlagConfig {
|
|
|
163
161
|
}
|
|
164
162
|
```
|
|
165
163
|
|
|
166
|
-
#### `checkFlag(user:
|
|
167
|
-
Checks if a feature flag is enabled for the given user.
|
|
168
|
-
|
|
169
|
-
#### `getFlag(flagId: string): FeatureFlag | undefined`
|
|
170
|
-
Gets a specific flag configuration. Useful for debugging purposes.
|
|
171
|
-
|
|
172
|
-
#### `getAllFlagIds(): string[]`
|
|
173
|
-
Returns an array of all available flag IDs.
|
|
164
|
+
#### `checkFlag(user: ForgeUser, flagName: string): boolean`
|
|
165
|
+
Checks if a feature flag is enabled for the given user. **Synchronous** after initialization.
|
|
174
166
|
|
|
175
|
-
#### `
|
|
176
|
-
|
|
167
|
+
#### `getFeatureFlags(user: ForgeUser, flagNames: string[]): Record<string, boolean>`
|
|
168
|
+
Gets multiple feature flags at once. **Synchronous** after initialization.
|
|
177
169
|
|
|
178
|
-
#### `
|
|
179
|
-
|
|
170
|
+
#### `shutdown(): Promise<void>`
|
|
171
|
+
Shuts down the feature flags service and cleans up resources.
|
|
180
172
|
|
|
181
173
|
#### `isInitialized(): boolean`
|
|
182
174
|
Checks if the service is initialized.
|
|
183
175
|
|
|
184
|
-
#### `shutdown(): void`
|
|
185
|
-
Shuts down the feature flags service, stops polling, and cleans up resources. **Synchronous**.
|
|
186
|
-
|
|
187
176
|
### User Interface
|
|
188
177
|
|
|
189
178
|
```typescript
|
|
190
|
-
interface
|
|
191
|
-
|
|
179
|
+
interface FeatureFlagUser {
|
|
180
|
+
custom?: Record<string, string | number>;
|
|
181
|
+
attributes?: {
|
|
182
|
+
installContext?: string;
|
|
183
|
+
accountId?: string;
|
|
184
|
+
appVersion?: string;
|
|
185
|
+
license?: string;
|
|
186
|
+
capabilitySet?: string;
|
|
187
|
+
};
|
|
192
188
|
identifiers?: {
|
|
193
189
|
installContext?: string;
|
|
194
190
|
accountId?: string;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { IDataAdapter, AdapterResponse } from 'statsig-node';
|
|
2
|
+
export declare class ForgeDataAdapter implements IDataAdapter {
|
|
3
|
+
private readonly pollingIntervalMs;
|
|
4
|
+
private readonly pollingEnabled;
|
|
5
|
+
private readonly cache;
|
|
6
|
+
private metrics;
|
|
7
|
+
private tags;
|
|
8
|
+
constructor(pollingIntervalMs?: number, pollingEnabled?: boolean);
|
|
9
|
+
get(key: string): Promise<AdapterResponse>;
|
|
10
|
+
private fetchFromAtlassianServers;
|
|
11
|
+
set(key: string, value: string): Promise<void>;
|
|
12
|
+
initialize(): Promise<void>;
|
|
13
|
+
shutdown(): Promise<void>;
|
|
14
|
+
supportsPollingUpdatesFor(key: string): boolean;
|
|
15
|
+
getPollingIntervalMs(): number;
|
|
16
|
+
}
|
|
17
|
+
//# sourceMappingURL=data-adapter.d.ts.map
|
|
@@ -0,0 +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;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"}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ForgeDataAdapter = void 0;
|
|
4
|
+
const api_1 = require("@forge/api");
|
|
5
|
+
class ForgeDataAdapter {
|
|
6
|
+
pollingIntervalMs;
|
|
7
|
+
pollingEnabled;
|
|
8
|
+
cache = new Map();
|
|
9
|
+
metrics = (0, api_1.__getRuntime)()?.metrics;
|
|
10
|
+
tags = {
|
|
11
|
+
forgeEnv: (0, api_1.__getRuntime)()?.appContext?.environmentType || 'unknown',
|
|
12
|
+
appId: (0, api_1.__getRuntime)()?.appContext?.appId || 'unknown'
|
|
13
|
+
};
|
|
14
|
+
constructor(pollingIntervalMs = 60000, pollingEnabled = true) {
|
|
15
|
+
this.pollingIntervalMs = pollingIntervalMs;
|
|
16
|
+
this.pollingEnabled = pollingEnabled;
|
|
17
|
+
}
|
|
18
|
+
async get(key) {
|
|
19
|
+
try {
|
|
20
|
+
this.metrics?.counter('forge.feature-flags.data-adapter.get', this.tags).incr();
|
|
21
|
+
const data = await this.fetchFromAtlassianServers();
|
|
22
|
+
if (data) {
|
|
23
|
+
const serializedData = JSON.stringify(data);
|
|
24
|
+
this.cache.set(key, serializedData);
|
|
25
|
+
this.metrics?.counter('forge.feature-flags.data-adapter.get.success', this.tags).incr();
|
|
26
|
+
return {
|
|
27
|
+
result: serializedData,
|
|
28
|
+
time: Date.now()
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
const cachedValue = this.cache.get(key);
|
|
32
|
+
if (cachedValue) {
|
|
33
|
+
this.metrics?.counter('forge.feature-flags.data-adapter.get.cached', this.tags).incr();
|
|
34
|
+
return {
|
|
35
|
+
result: cachedValue,
|
|
36
|
+
time: Date.now()
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
this.metrics?.counter('forge.feature-flags.data-adapter.get.empty', this.tags).incr();
|
|
40
|
+
return {
|
|
41
|
+
result: undefined,
|
|
42
|
+
time: Date.now()
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
catch {
|
|
46
|
+
this.metrics?.counter('forge.feature-flags.data-adapter.get.failure', this.tags).incr();
|
|
47
|
+
const cachedValue = this.cache.get(key);
|
|
48
|
+
if (cachedValue) {
|
|
49
|
+
this.metrics?.counter('forge.feature-flags.data-adapter.get.cached', this.tags).incr();
|
|
50
|
+
return {
|
|
51
|
+
result: cachedValue,
|
|
52
|
+
time: Date.now()
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
return {
|
|
56
|
+
result: undefined,
|
|
57
|
+
time: Date.now()
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
async fetchFromAtlassianServers() {
|
|
62
|
+
const runtime = (0, api_1.__getRuntime)();
|
|
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();
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
69
|
+
const remainingTime = runtime?.lambdaContext?.getRemainingTimeInMillis?.();
|
|
70
|
+
if (remainingTime !== undefined && remainingTime < 5000) {
|
|
71
|
+
this.metrics
|
|
72
|
+
?.counter('forge.feature-flags.data-adapter.fetchFromAtlassianServers.remainingTimeComplete', this.tags)
|
|
73
|
+
.incr();
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
76
|
+
const response = await (0, api_1.__fetchProduct)({ provider: 'app', remote: 'feature-flags', type: 'feature-flags' })('/', {
|
|
77
|
+
method: 'GET',
|
|
78
|
+
redirect: 'follow',
|
|
79
|
+
headers: {
|
|
80
|
+
'Content-Type': 'application/json'
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
if (!response.ok) {
|
|
84
|
+
throw new Error(`HTTP error! status: ${response.status}`);
|
|
85
|
+
}
|
|
86
|
+
return response.json();
|
|
87
|
+
}
|
|
88
|
+
async set(key, value) {
|
|
89
|
+
try {
|
|
90
|
+
this.cache.set(key, value);
|
|
91
|
+
}
|
|
92
|
+
catch { }
|
|
93
|
+
}
|
|
94
|
+
async initialize() { }
|
|
95
|
+
async shutdown() {
|
|
96
|
+
this.cache.clear();
|
|
97
|
+
}
|
|
98
|
+
supportsPollingUpdatesFor(key) {
|
|
99
|
+
if (!this.pollingEnabled) {
|
|
100
|
+
return false;
|
|
101
|
+
}
|
|
102
|
+
try {
|
|
103
|
+
const runtime = (0, api_1.__getRuntime)();
|
|
104
|
+
if (runtime?.proxy?.tokenExpiry && runtime?.proxy?.tokenExpiry * 1000 < Date.now()) {
|
|
105
|
+
return false;
|
|
106
|
+
}
|
|
107
|
+
const remainingTime = runtime?.lambdaContext?.getRemainingTimeInMillis?.();
|
|
108
|
+
if (remainingTime !== undefined && remainingTime < 10000) {
|
|
109
|
+
return false;
|
|
110
|
+
}
|
|
111
|
+
return true;
|
|
112
|
+
}
|
|
113
|
+
catch {
|
|
114
|
+
return false;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
getPollingIntervalMs() {
|
|
118
|
+
return this.pollingIntervalMs;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
exports.ForgeDataAdapter = ForgeDataAdapter;
|
package/out/index.d.ts
CHANGED
|
@@ -1,24 +1,46 @@
|
|
|
1
|
-
|
|
1
|
+
export interface ForgeFeatureFlagConfig {
|
|
2
|
+
environment?: 'development' | 'staging' | 'production';
|
|
3
|
+
}
|
|
4
|
+
export interface FeatureFlagUser {
|
|
5
|
+
userID?: string;
|
|
6
|
+
custom?: Record<string, string | number>;
|
|
7
|
+
attributes?: {
|
|
8
|
+
installContext?: string;
|
|
9
|
+
accountId?: string;
|
|
10
|
+
appVersion?: string;
|
|
11
|
+
license?: string;
|
|
12
|
+
capabilitySet?: string;
|
|
13
|
+
};
|
|
14
|
+
identifiers?: {
|
|
15
|
+
installContext?: string;
|
|
16
|
+
accountId?: string;
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
export interface Counter {
|
|
20
|
+
incr(): void;
|
|
21
|
+
decr(): void;
|
|
22
|
+
}
|
|
23
|
+
export interface TimerStop {
|
|
24
|
+
stop(): void;
|
|
25
|
+
}
|
|
26
|
+
export interface Timer {
|
|
27
|
+
measure(): TimerStop;
|
|
28
|
+
}
|
|
29
|
+
export interface Metrics {
|
|
30
|
+
counter(name: string, tags?: Record<string, string>): Counter;
|
|
31
|
+
timing(name: string, tags?: Record<string, string>): Timer;
|
|
32
|
+
}
|
|
2
33
|
export declare class ForgeFeatureFlags {
|
|
3
|
-
private flags;
|
|
4
|
-
private evaluator;
|
|
5
34
|
private initialized;
|
|
6
|
-
private
|
|
7
|
-
private
|
|
8
|
-
private
|
|
9
|
-
private readonly metrics;
|
|
10
|
-
private readonly tags;
|
|
35
|
+
private dataAdapter;
|
|
36
|
+
private metrics;
|
|
37
|
+
private tags;
|
|
11
38
|
initialize(config?: ForgeFeatureFlagConfig): Promise<void>;
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
getAllFlagIds(): string[];
|
|
16
|
-
refresh(): Promise<void>;
|
|
17
|
-
private startPolling;
|
|
18
|
-
private stopPolling;
|
|
19
|
-
private ensureInitialized;
|
|
39
|
+
checkFlag(user: FeatureFlagUser, flagName: string): boolean;
|
|
40
|
+
getFeatureFlags(user: FeatureFlagUser, flagNames: string[]): Record<string, boolean>;
|
|
41
|
+
shutdown(): Promise<void>;
|
|
20
42
|
isInitialized(): boolean;
|
|
21
|
-
|
|
22
|
-
|
|
43
|
+
private ensureInitialized;
|
|
44
|
+
private convertUser;
|
|
23
45
|
}
|
|
24
46
|
//# sourceMappingURL=index.d.ts.map
|
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,eAAe;IAC9B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC,CAAC;IACzC,UAAU,CAAC,EAAE;QACX,cAAc,CAAC,EAAE,MAAM,CAAC;QACxB,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,UAAU,CAAC,EAAE,MAAM,CAAC;QACpB,OAAO,CAAC,EAAE,MAAM,CAAC;QACjB,aAAa,CAAC,EAAE,MAAM,CAAC;KACxB,CAAC;IACF,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;IAwCpE,SAAS,CAAC,IAAI,EAAE,eAAe,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO;IAS3D,eAAe,CAAC,IAAI,EAAE,eAAe,EAAE,SAAS,EAAE,MAAM,EAAE,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IAe9E,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
|
@@ -1,131 +1,90 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.ForgeFeatureFlags = void 0;
|
|
4
|
+
const tslib_1 = require("tslib");
|
|
5
|
+
const statsig_node_1 = tslib_1.__importDefault(require("statsig-node"));
|
|
6
|
+
const data_adapter_1 = require("./data-adapter");
|
|
4
7
|
const api_1 = require("@forge/api");
|
|
5
|
-
const evaluator_1 = require("./evaluator");
|
|
6
|
-
const DEFAULT_CONFIG = { environment: 'development' };
|
|
7
8
|
class ForgeFeatureFlags {
|
|
8
|
-
flags = new Map();
|
|
9
|
-
evaluator;
|
|
10
9
|
initialized = false;
|
|
11
|
-
|
|
12
|
-
pollingTimer;
|
|
13
|
-
lastFetchTime = 0;
|
|
10
|
+
dataAdapter = null;
|
|
14
11
|
metrics = (0, api_1.__getRuntime)()?.metrics;
|
|
15
12
|
tags = {
|
|
16
13
|
forgeEnv: (0, api_1.__getRuntime)()?.appContext?.environmentType || 'unknown',
|
|
17
14
|
appId: (0, api_1.__getRuntime)()?.appContext?.appId || 'unknown'
|
|
18
15
|
};
|
|
19
|
-
async initialize(config =
|
|
16
|
+
async initialize(config = {}) {
|
|
17
|
+
if (this.initialized) {
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
20
|
this.metrics?.counter('forge.feature-flags.initialize', this.tags).incr();
|
|
21
21
|
const timer = this.metrics?.timing('forge.feature-flags.initialize', this.tags).measure();
|
|
22
22
|
try {
|
|
23
|
-
|
|
24
|
-
this.initialized = true;
|
|
25
|
-
this.metrics?.counter('forge.feature-flags.initialize.success', this.tags).incr();
|
|
26
|
-
if (this.pollingIntervalMs && (0, api_1.__getRuntime)()?.lambdaContext?.getRemainingTimeInMillis?.() > 60000) {
|
|
27
|
-
this.startPolling();
|
|
28
|
-
}
|
|
29
|
-
this.evaluator = new evaluator_1.Evaluator({ environment: config.environment });
|
|
30
|
-
}
|
|
31
|
-
catch {
|
|
32
|
-
this.initialized = false;
|
|
33
|
-
this.metrics?.counter('forge.feature-flags.initialize.failure', this.tags).incr();
|
|
34
|
-
console.error('Failed to initialize Forge Feature Flags SDK');
|
|
35
|
-
}
|
|
36
|
-
finally {
|
|
37
|
-
timer?.stop();
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
async fetchConfigurations() {
|
|
41
|
-
try {
|
|
42
|
-
const runtime = (0, api_1.__getRuntime)();
|
|
43
|
-
if (runtime?.proxy?.tokenExpiry && runtime?.proxy?.tokenExpiry * 1000 < Date.now()) {
|
|
44
|
-
this.metrics?.counter('forge.feature-flags.fetchFromAtlassianServers.tokenExpired', this.tags).incr();
|
|
45
|
-
throw new Error('Cannot fetch feature flags: authentication token has expired');
|
|
46
|
-
}
|
|
47
|
-
const remainingTime = runtime?.lambdaContext?.getRemainingTimeInMillis?.();
|
|
48
|
-
if (remainingTime !== undefined && remainingTime < 5000) {
|
|
49
|
-
this.metrics?.counter('forge.feature-flags.fetchFromAtlassianServers.remainingTimeComplete', this.tags).incr();
|
|
50
|
-
throw new Error('Cannot fetch feature flags: insufficient remaining execution time');
|
|
51
|
-
}
|
|
52
|
-
const response = await (0, api_1.__fetchProduct)({ provider: 'app', remote: 'feature-flags', type: 'feature-flags' })('/', {
|
|
53
|
-
method: 'GET',
|
|
54
|
-
redirect: 'follow',
|
|
55
|
-
headers: {
|
|
56
|
-
'Content-Type': 'application/json',
|
|
57
|
-
'x-b3-traceid': runtime?.tracing?.traceId,
|
|
58
|
-
'x-b3-spanid': runtime?.tracing?.spanId
|
|
59
|
-
}
|
|
60
|
-
});
|
|
61
|
-
if (!response.ok) {
|
|
62
|
-
throw new Error(`HTTP error! status: ${response.status}`);
|
|
63
|
-
}
|
|
64
|
-
const data = await response.json();
|
|
65
|
-
this.flags = new Map(data?.feature_flags?.map((flag) => [flag.flagId, flag]));
|
|
66
|
-
this.lastFetchTime = Date.now();
|
|
23
|
+
statsig_node_1.default.shutdown();
|
|
67
24
|
}
|
|
68
|
-
catch
|
|
69
|
-
|
|
70
|
-
|
|
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();
|
|
71
30
|
}
|
|
31
|
+
this.dataAdapter = new data_adapter_1.ForgeDataAdapter(60000, !disableRulesetsSync);
|
|
32
|
+
const statsigOptions = {
|
|
33
|
+
environment: { tier: config.environment || 'development' },
|
|
34
|
+
disableRulesetsSync: disableRulesetsSync,
|
|
35
|
+
rulesetsSyncIntervalMs: 60000,
|
|
36
|
+
initStrategyForIDLists: 'none',
|
|
37
|
+
disableIdListsSync: true,
|
|
38
|
+
initTimeoutMs: 3000,
|
|
39
|
+
localMode: true,
|
|
40
|
+
disableAllLogging: true,
|
|
41
|
+
disableDiagnostics: true,
|
|
42
|
+
dataAdapter: this.dataAdapter
|
|
43
|
+
};
|
|
44
|
+
await statsig_node_1.default.initialize('secret-forge-internal-key', statsigOptions);
|
|
45
|
+
this.initialized = true;
|
|
46
|
+
timer?.stop();
|
|
47
|
+
this.metrics?.counter('forge.feature-flags.initialize.success', this.tags).incr();
|
|
72
48
|
}
|
|
73
|
-
checkFlag(user,
|
|
49
|
+
checkFlag(user, flagName) {
|
|
74
50
|
this.ensureInitialized();
|
|
75
51
|
this.metrics?.counter('forge.feature-flags.checkFlag', this.tags).incr();
|
|
76
|
-
|
|
77
|
-
if (!flag) {
|
|
78
|
-
return defaultValue;
|
|
79
|
-
}
|
|
80
|
-
const result = this.evaluator.evaluate(user, flag);
|
|
81
|
-
return result.value;
|
|
82
|
-
}
|
|
83
|
-
getFlag(flagId) {
|
|
84
|
-
this.metrics?.counter('forge.feature-flags.getFlag', this.tags).incr();
|
|
85
|
-
return this.flags.get(flagId);
|
|
86
|
-
}
|
|
87
|
-
getAllFlagIds() {
|
|
88
|
-
this.metrics?.counter('forge.feature-flags.getAllFlagIds', this.tags).incr();
|
|
89
|
-
return Array.from(this.flags.keys());
|
|
90
|
-
}
|
|
91
|
-
async refresh() {
|
|
92
|
-
this.metrics?.counter('forge.feature-flags.refresh', this.tags).incr();
|
|
93
|
-
await this.fetchConfigurations();
|
|
94
|
-
}
|
|
95
|
-
startPolling() {
|
|
96
|
-
this.stopPolling();
|
|
97
|
-
this.pollingTimer = setInterval(async () => {
|
|
98
|
-
try {
|
|
99
|
-
await this.fetchConfigurations();
|
|
100
|
-
}
|
|
101
|
-
catch (error) {
|
|
102
|
-
console.error('Error during polling:', error);
|
|
103
|
-
}
|
|
104
|
-
}, this.pollingIntervalMs);
|
|
52
|
+
return statsig_node_1.default.checkGate(this.convertUser(user), flagName);
|
|
105
53
|
}
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
54
|
+
getFeatureFlags(user, flagNames) {
|
|
55
|
+
this.ensureInitialized();
|
|
56
|
+
this.metrics?.counter('forge.feature-flags.getFeatureFlags', this.tags).incr();
|
|
57
|
+
const results = {};
|
|
58
|
+
for (const flagName of flagNames) {
|
|
59
|
+
results[flagName] = statsig_node_1.default.checkGate(this.convertUser(user), flagName);
|
|
110
60
|
}
|
|
61
|
+
return results;
|
|
111
62
|
}
|
|
112
|
-
|
|
63
|
+
async shutdown() {
|
|
113
64
|
if (!this.initialized) {
|
|
114
|
-
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
statsig_node_1.default.shutdown();
|
|
68
|
+
if (this.dataAdapter) {
|
|
69
|
+
await this.dataAdapter.shutdown();
|
|
115
70
|
}
|
|
71
|
+
this.initialized = false;
|
|
72
|
+
this.metrics?.counter('forge.feature-flags.shutdown', this.tags).incr();
|
|
116
73
|
}
|
|
117
74
|
isInitialized() {
|
|
118
75
|
return this.initialized;
|
|
119
76
|
}
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
77
|
+
ensureInitialized() {
|
|
78
|
+
if (!this.initialized) {
|
|
79
|
+
throw new Error('ForgeFeatureFlags not initialized. Call initialize() first.');
|
|
80
|
+
}
|
|
123
81
|
}
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
82
|
+
convertUser(user) {
|
|
83
|
+
return {
|
|
84
|
+
userID: user.userID,
|
|
85
|
+
custom: { ...(user.custom || {}), ...(user.attributes || {}) },
|
|
86
|
+
customIDs: user.identifiers || {}
|
|
87
|
+
};
|
|
129
88
|
}
|
|
130
89
|
}
|
|
131
90
|
exports.ForgeFeatureFlags = ForgeFeatureFlags;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@forge/feature-flags-node",
|
|
3
|
-
"version": "2.1.9-next.0-experimental-
|
|
3
|
+
"version": "2.1.9-next.0-experimental-44a932f",
|
|
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,8 @@
|
|
|
15
15
|
"@types/node": "20.19.1"
|
|
16
16
|
},
|
|
17
17
|
"dependencies": {
|
|
18
|
-
"@forge/api": "^7.0.1-next.0-experimental-
|
|
18
|
+
"@forge/api": "^7.0.1-next.0-experimental-44a932f",
|
|
19
|
+
"statsig-node": "^6.4.3"
|
|
19
20
|
},
|
|
20
21
|
"publishConfig": {
|
|
21
22
|
"registry": "https://packages.atlassian.com/api/npm/npm-public/"
|
package/out/evaluator.d.ts
DELETED
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
import { FeatureFlag, User, EvaluationResult, Environment } from './types';
|
|
2
|
-
export declare class Evaluator {
|
|
3
|
-
private readonly environment;
|
|
4
|
-
constructor(options: {
|
|
5
|
-
environment: Environment;
|
|
6
|
-
});
|
|
7
|
-
evaluate(user: User, flag: FeatureFlag): EvaluationResult;
|
|
8
|
-
private evaluateOverrides;
|
|
9
|
-
private evaluateRule;
|
|
10
|
-
private evaluatePassPercentage;
|
|
11
|
-
private getUserId;
|
|
12
|
-
}
|
|
13
|
-
//# sourceMappingURL=evaluator.d.ts.map
|
package/out/evaluator.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"evaluator.d.ts","sourceRoot":"","sources":["../src/evaluator.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,IAAI,EAAkB,gBAAgB,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAc3F,qBAAa,SAAS;IACpB,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAc;gBAC9B,OAAO,EAAE;QAAE,WAAW,EAAE,WAAW,CAAA;KAAE;IAM1C,QAAQ,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,WAAW,GAAG,gBAAgB;IA0ChE,OAAO,CAAC,iBAAiB;IA4BzB,OAAO,CAAC,YAAY;IAkBpB,OAAO,CAAC,sBAAsB;IAsC9B,OAAO,CAAC,SAAS;CAGlB"}
|
package/out/evaluator.js
DELETED
|
@@ -1,95 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.Evaluator = void 0;
|
|
4
|
-
const hash_1 = require("./utils/hash");
|
|
5
|
-
const conditions_1 = require("./utils/conditions");
|
|
6
|
-
const api_1 = require("@forge/api");
|
|
7
|
-
const CONDITION_SEGMENT_COUNT = 10000;
|
|
8
|
-
class Evaluator {
|
|
9
|
-
environment;
|
|
10
|
-
constructor(options) {
|
|
11
|
-
this.environment = options.environment;
|
|
12
|
-
}
|
|
13
|
-
evaluate(user, flag) {
|
|
14
|
-
if (!flag.isEnabled) {
|
|
15
|
-
return {
|
|
16
|
-
value: false,
|
|
17
|
-
ruleId: null,
|
|
18
|
-
reason: 'disabled'
|
|
19
|
-
};
|
|
20
|
-
}
|
|
21
|
-
const overrideResult = this.evaluateOverrides(user, flag?.overrides);
|
|
22
|
-
if (overrideResult) {
|
|
23
|
-
return overrideResult;
|
|
24
|
-
}
|
|
25
|
-
const rulesForEnv = [...flag.rules]
|
|
26
|
-
.filter((rule) => rule.env.includes(this.environment))
|
|
27
|
-
.sort((a, b) => a.order - b.order);
|
|
28
|
-
for (const rule of rulesForEnv) {
|
|
29
|
-
if (this.evaluateRule(user, rule, flag)) {
|
|
30
|
-
return {
|
|
31
|
-
value: rule.returnValue.value,
|
|
32
|
-
ruleId: rule.name,
|
|
33
|
-
reason: 'rule_match'
|
|
34
|
-
};
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
return {
|
|
38
|
-
value: false,
|
|
39
|
-
ruleId: null,
|
|
40
|
-
reason: 'default'
|
|
41
|
-
};
|
|
42
|
-
}
|
|
43
|
-
evaluateOverrides(user, overrides) {
|
|
44
|
-
if (!overrides) {
|
|
45
|
-
return null;
|
|
46
|
-
}
|
|
47
|
-
const overridesForEnv = [...overrides]
|
|
48
|
-
.filter((override) => override.env.includes(this.environment))
|
|
49
|
-
.sort((a, b) => a.order - b.order);
|
|
50
|
-
for (const override of overridesForEnv) {
|
|
51
|
-
const userValue = user?.attributes?.[override.attribute];
|
|
52
|
-
if (userValue !== undefined && override.values.includes(String(userValue))) {
|
|
53
|
-
return {
|
|
54
|
-
value: override.returnValue.value,
|
|
55
|
-
ruleId: `override:${override.attribute}`,
|
|
56
|
-
reason: 'override'
|
|
57
|
-
};
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
return null;
|
|
61
|
-
}
|
|
62
|
-
evaluateRule(user, rule, flag) {
|
|
63
|
-
const conditionResults = rule.conditions.map((condition) => (0, conditions_1.evaluateCondition)(user, condition));
|
|
64
|
-
const conditionsPassed = conditionResults.every((result) => result === true);
|
|
65
|
-
if (!conditionsPassed) {
|
|
66
|
-
return false;
|
|
67
|
-
}
|
|
68
|
-
return this.evaluatePassPercentage(user, rule, flag);
|
|
69
|
-
}
|
|
70
|
-
evaluatePassPercentage(user, rule, flag) {
|
|
71
|
-
if (rule.passPercentage === 0) {
|
|
72
|
-
return false;
|
|
73
|
-
}
|
|
74
|
-
if (rule.passPercentage === 100) {
|
|
75
|
-
return true;
|
|
76
|
-
}
|
|
77
|
-
const userId = this.getUserId(user, flag.idType);
|
|
78
|
-
if (!userId) {
|
|
79
|
-
return false;
|
|
80
|
-
}
|
|
81
|
-
const appId = (0, api_1.__getRuntime)()?.appContext?.appId || 'unknown';
|
|
82
|
-
const hashInput = `${appId}.${flag.flagId}.${rule.name}.${userId}`;
|
|
83
|
-
const hash = (0, hash_1.computeHash)(hashInput);
|
|
84
|
-
const bucket = Number(hash % BigInt(CONDITION_SEGMENT_COUNT));
|
|
85
|
-
if (rule.passPercentage === undefined) {
|
|
86
|
-
console.warn(`passPercentage is undefined for rule '${rule.name}' in flag '${flag.flagId}'. Defaulting to false.`);
|
|
87
|
-
return false;
|
|
88
|
-
}
|
|
89
|
-
return bucket < rule.passPercentage * 100;
|
|
90
|
-
}
|
|
91
|
-
getUserId(user, idType) {
|
|
92
|
-
return user?.identifiers?.[idType];
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
exports.Evaluator = Evaluator;
|
package/out/types/index.d.ts
DELETED
|
@@ -1,94 +0,0 @@
|
|
|
1
|
-
export interface ForgeFeatureFlagConfig {
|
|
2
|
-
environment: Environment;
|
|
3
|
-
}
|
|
4
|
-
export interface User {
|
|
5
|
-
attributes?: Record<string, string | number>;
|
|
6
|
-
identifiers?: {
|
|
7
|
-
installContext?: string;
|
|
8
|
-
accountId?: string;
|
|
9
|
-
};
|
|
10
|
-
}
|
|
11
|
-
export interface Counter {
|
|
12
|
-
incr(): void;
|
|
13
|
-
decr(): void;
|
|
14
|
-
}
|
|
15
|
-
export interface TimerStop {
|
|
16
|
-
stop(): void;
|
|
17
|
-
}
|
|
18
|
-
export interface Timer {
|
|
19
|
-
measure(): TimerStop;
|
|
20
|
-
}
|
|
21
|
-
export interface Metrics {
|
|
22
|
-
counter(name: string, tags?: Record<string, string>): Counter;
|
|
23
|
-
timing(name: string, tags?: Record<string, string>): Timer;
|
|
24
|
-
}
|
|
25
|
-
export declare type Environment = 'development' | 'staging' | 'production';
|
|
26
|
-
export declare type IdType = 'installContext' | 'accountId';
|
|
27
|
-
export declare type FlagStatus = 'implementing' | 'enabled' | 'disabled' | 'launching' | 'tidying';
|
|
28
|
-
export declare type ConditionType = 'everyone' | 'attribute' | 'segment' | 'custom';
|
|
29
|
-
export declare enum FeatureFlagConditionOperator {
|
|
30
|
-
anyOf = "is any of",
|
|
31
|
-
noneOf = "is none of",
|
|
32
|
-
contains = "contains",
|
|
33
|
-
equals = "is equal to",
|
|
34
|
-
gt = "is greater than",
|
|
35
|
-
lt = "is less than",
|
|
36
|
-
version_gt = "is version greater than",
|
|
37
|
-
version_lt = "is version less than"
|
|
38
|
-
}
|
|
39
|
-
export declare type RuleConditionOperator = 'AND' | 'OR';
|
|
40
|
-
export interface ReturnValue {
|
|
41
|
-
type: string;
|
|
42
|
-
value: boolean;
|
|
43
|
-
}
|
|
44
|
-
export interface Condition {
|
|
45
|
-
type: string;
|
|
46
|
-
order: number;
|
|
47
|
-
field?: string;
|
|
48
|
-
operator?: string;
|
|
49
|
-
values?: string | string[];
|
|
50
|
-
}
|
|
51
|
-
export interface Rule {
|
|
52
|
-
name: string;
|
|
53
|
-
order: number;
|
|
54
|
-
passPercentage?: number;
|
|
55
|
-
conditions: Condition[];
|
|
56
|
-
env: string[];
|
|
57
|
-
returnValue: ReturnValue;
|
|
58
|
-
}
|
|
59
|
-
export interface Override {
|
|
60
|
-
attribute: string;
|
|
61
|
-
values: string[];
|
|
62
|
-
env: string[];
|
|
63
|
-
order: number;
|
|
64
|
-
returnValue: ReturnValue;
|
|
65
|
-
}
|
|
66
|
-
export interface FeatureFlag {
|
|
67
|
-
flagId: string;
|
|
68
|
-
name: string;
|
|
69
|
-
description?: string;
|
|
70
|
-
status?: string;
|
|
71
|
-
isEnabled: boolean;
|
|
72
|
-
rules: Rule[];
|
|
73
|
-
idType: string;
|
|
74
|
-
createdBy: string;
|
|
75
|
-
createdAt: number;
|
|
76
|
-
updatedAt: number;
|
|
77
|
-
updatedBy?: string;
|
|
78
|
-
overrides?: Override[];
|
|
79
|
-
}
|
|
80
|
-
export interface EvaluationResult {
|
|
81
|
-
value: boolean;
|
|
82
|
-
ruleId: string | null;
|
|
83
|
-
reason: 'override' | 'rule_match' | 'default' | 'disabled';
|
|
84
|
-
}
|
|
85
|
-
export interface EvaluatedFlag {
|
|
86
|
-
value: boolean;
|
|
87
|
-
ruleId: string | null;
|
|
88
|
-
}
|
|
89
|
-
export interface InitializeResponse {
|
|
90
|
-
flags: Record<string, EvaluatedFlag>;
|
|
91
|
-
timestamp: number;
|
|
92
|
-
hash: string;
|
|
93
|
-
}
|
|
94
|
-
//# sourceMappingURL=index.d.ts.map
|
package/out/types/index.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/types/index.ts"],"names":[],"mappings":"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"}
|
package/out/types/index.js
DELETED
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.FeatureFlagConditionOperator = void 0;
|
|
4
|
-
var FeatureFlagConditionOperator;
|
|
5
|
-
(function (FeatureFlagConditionOperator) {
|
|
6
|
-
FeatureFlagConditionOperator["anyOf"] = "is any of";
|
|
7
|
-
FeatureFlagConditionOperator["noneOf"] = "is none of";
|
|
8
|
-
FeatureFlagConditionOperator["contains"] = "contains";
|
|
9
|
-
FeatureFlagConditionOperator["equals"] = "is equal to";
|
|
10
|
-
FeatureFlagConditionOperator["gt"] = "is greater than";
|
|
11
|
-
FeatureFlagConditionOperator["lt"] = "is less than";
|
|
12
|
-
FeatureFlagConditionOperator["version_gt"] = "is version greater than";
|
|
13
|
-
FeatureFlagConditionOperator["version_lt"] = "is version less than";
|
|
14
|
-
})(FeatureFlagConditionOperator = exports.FeatureFlagConditionOperator || (exports.FeatureFlagConditionOperator = {}));
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"conditions.d.ts","sourceRoot":"","sources":["../../src/utils/conditions.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAgC,IAAI,EAAE,MAAM,UAAU,CAAC;AAKzE,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,IAAI,EAAE,SAAS,EAAE,SAAS,GAAG,OAAO,CAe3E"}
|
package/out/utils/conditions.js
DELETED
|
@@ -1,78 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.evaluateCondition = void 0;
|
|
4
|
-
const types_1 = require("../types");
|
|
5
|
-
function evaluateCondition(user, condition) {
|
|
6
|
-
switch (condition.type) {
|
|
7
|
-
case 'everyone':
|
|
8
|
-
return true;
|
|
9
|
-
case 'attribute':
|
|
10
|
-
case 'custom_field': {
|
|
11
|
-
if (!condition.field || !condition.operator) {
|
|
12
|
-
return false;
|
|
13
|
-
}
|
|
14
|
-
const userValue = user.attributes?.[condition.field];
|
|
15
|
-
return evaluateOperator(userValue, condition.values || [], condition.operator);
|
|
16
|
-
}
|
|
17
|
-
default:
|
|
18
|
-
return false;
|
|
19
|
-
}
|
|
20
|
-
}
|
|
21
|
-
exports.evaluateCondition = evaluateCondition;
|
|
22
|
-
function evaluateOperator(userValue, targetValues, operator) {
|
|
23
|
-
switch (operator) {
|
|
24
|
-
case types_1.FeatureFlagConditionOperator.anyOf:
|
|
25
|
-
if (Array.isArray(targetValues)) {
|
|
26
|
-
return targetValues.some((tv) => String(tv) === String(userValue));
|
|
27
|
-
}
|
|
28
|
-
return false;
|
|
29
|
-
case types_1.FeatureFlagConditionOperator.noneOf:
|
|
30
|
-
if (Array.isArray(targetValues)) {
|
|
31
|
-
return !targetValues.some((tv) => String(tv) === String(userValue));
|
|
32
|
-
}
|
|
33
|
-
return false;
|
|
34
|
-
case types_1.FeatureFlagConditionOperator.contains:
|
|
35
|
-
if (Array.isArray(targetValues)) {
|
|
36
|
-
return targetValues.some((v) => String(userValue).includes(String(v)));
|
|
37
|
-
}
|
|
38
|
-
return false;
|
|
39
|
-
case types_1.FeatureFlagConditionOperator.equals:
|
|
40
|
-
return String(userValue) === String(targetValues);
|
|
41
|
-
case types_1.FeatureFlagConditionOperator.gt: {
|
|
42
|
-
const userNum = Number(userValue);
|
|
43
|
-
const targetNum = Number(targetValues);
|
|
44
|
-
if (Number.isNaN(userNum) || Number.isNaN(targetNum)) {
|
|
45
|
-
return false;
|
|
46
|
-
}
|
|
47
|
-
return userNum > targetNum;
|
|
48
|
-
}
|
|
49
|
-
case types_1.FeatureFlagConditionOperator.lt: {
|
|
50
|
-
const userNum = Number(userValue);
|
|
51
|
-
const targetNum = Number(targetValues);
|
|
52
|
-
if (Number.isNaN(userNum) || Number.isNaN(targetNum)) {
|
|
53
|
-
return false;
|
|
54
|
-
}
|
|
55
|
-
return userNum < targetNum;
|
|
56
|
-
}
|
|
57
|
-
case types_1.FeatureFlagConditionOperator.version_gt:
|
|
58
|
-
return compareVersions(String(userValue), String(targetValues)) > 0;
|
|
59
|
-
case types_1.FeatureFlagConditionOperator.version_lt:
|
|
60
|
-
return compareVersions(String(userValue), String(targetValues)) < 0;
|
|
61
|
-
default:
|
|
62
|
-
return false;
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
function compareVersions(version1, version2) {
|
|
66
|
-
const v1Parts = version1.split('.').map(Number);
|
|
67
|
-
const v2Parts = version2.split('.').map(Number);
|
|
68
|
-
const maxLength = Math.max(v1Parts.length, v2Parts.length);
|
|
69
|
-
for (let i = 0; i < maxLength; i++) {
|
|
70
|
-
const v1Part = v1Parts[i] || 0;
|
|
71
|
-
const v2Part = v2Parts[i] || 0;
|
|
72
|
-
if (v1Part > v2Part)
|
|
73
|
-
return 1;
|
|
74
|
-
if (v1Part < v2Part)
|
|
75
|
-
return -1;
|
|
76
|
-
}
|
|
77
|
-
return 0;
|
|
78
|
-
}
|
package/out/utils/hash.d.ts
DELETED
package/out/utils/hash.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"hash.d.ts","sourceRoot":"","sources":["../../src/utils/hash.ts"],"names":[],"mappings":"AAQA,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAQjD"}
|
package/out/utils/hash.js
DELETED
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.computeHash = void 0;
|
|
4
|
-
const node_crypto_1 = require("node:crypto");
|
|
5
|
-
function computeHash(input) {
|
|
6
|
-
const hash = (0, node_crypto_1.createHash)('sha256').update(input).digest();
|
|
7
|
-
const hashValue = hash.readBigUInt64BE(0);
|
|
8
|
-
return hashValue;
|
|
9
|
-
}
|
|
10
|
-
exports.computeHash = computeHash;
|