@mergedapp/feature-flags 0.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/README.md +651 -0
- package/dist/cjs/cli/audit.js +117 -0
- package/dist/cjs/cli/cleanup.js +105 -0
- package/dist/cjs/cli/config-loader.js +102 -0
- package/dist/cjs/cli/generate.js +194 -0
- package/dist/cjs/cli/parse-args.js +18 -0
- package/dist/cjs/cli.js +46 -0
- package/dist/cjs/client.js +505 -0
- package/dist/cjs/errors.js +24 -0
- package/dist/cjs/index.js +13 -0
- package/dist/cjs/jwt.js +85 -0
- package/dist/cjs/nestjs/bindings.js +36 -0
- package/dist/cjs/nestjs/constants.js +7 -0
- package/dist/cjs/nestjs/context.js +28 -0
- package/dist/cjs/nestjs/decorators.js +50 -0
- package/dist/cjs/nestjs/errors.js +25 -0
- package/dist/cjs/nestjs/evaluator.js +87 -0
- package/dist/cjs/nestjs/guard.js +67 -0
- package/dist/cjs/nestjs/interceptor.js +56 -0
- package/dist/cjs/nestjs/module.js +70 -0
- package/dist/cjs/nestjs/service.js +54 -0
- package/dist/cjs/nestjs/types.js +2 -0
- package/dist/cjs/nestjs.js +26 -0
- package/dist/cjs/openfeature/context.js +166 -0
- package/dist/cjs/openfeature/hooks.js +31 -0
- package/dist/cjs/openfeature/server-provider.js +107 -0
- package/dist/cjs/openfeature/server.js +13 -0
- package/dist/cjs/openfeature/shared.js +83 -0
- package/dist/cjs/openfeature/web-provider.js +156 -0
- package/dist/cjs/openfeature/web.js +13 -0
- package/dist/cjs/package.json +3 -0
- package/dist/cjs/persistence.js +249 -0
- package/dist/cjs/react/hooks.js +86 -0
- package/dist/cjs/react/provider.js +106 -0
- package/dist/cjs/react.js +7 -0
- package/dist/cjs/remote-evaluator.js +162 -0
- package/dist/cjs/types.js +2 -0
- package/dist/cli/audit.d.ts +3 -0
- package/dist/cli/audit.js +114 -0
- package/dist/cli/cleanup.d.ts +3 -0
- package/dist/cli/cleanup.js +102 -0
- package/dist/cli/config-loader.d.ts +26 -0
- package/dist/cli/config-loader.js +66 -0
- package/dist/cli/generate.d.ts +3 -0
- package/dist/cli/generate.js +191 -0
- package/dist/cli/parse-args.d.ts +1 -0
- package/dist/cli/parse-args.js +15 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +45 -0
- package/dist/client.d.ts +67 -0
- package/dist/client.js +501 -0
- package/dist/errors.d.ts +15 -0
- package/dist/errors.js +18 -0
- package/dist/index.cjs +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +3 -0
- package/dist/jwt.d.ts +20 -0
- package/dist/jwt.js +78 -0
- package/dist/nestjs/bindings.d.ts +5 -0
- package/dist/nestjs/bindings.js +33 -0
- package/dist/nestjs/constants.d.ts +4 -0
- package/dist/nestjs/constants.js +4 -0
- package/dist/nestjs/context.d.ts +12 -0
- package/dist/nestjs/context.js +24 -0
- package/dist/nestjs/decorators.d.ts +4 -0
- package/dist/nestjs/decorators.js +45 -0
- package/dist/nestjs/errors.d.ts +12 -0
- package/dist/nestjs/errors.js +20 -0
- package/dist/nestjs/evaluator.d.ts +17 -0
- package/dist/nestjs/evaluator.js +83 -0
- package/dist/nestjs/guard.d.ts +19 -0
- package/dist/nestjs/guard.js +63 -0
- package/dist/nestjs/interceptor.d.ts +10 -0
- package/dist/nestjs/interceptor.js +53 -0
- package/dist/nestjs/module.d.ts +6 -0
- package/dist/nestjs/module.js +67 -0
- package/dist/nestjs/service.d.ts +30 -0
- package/dist/nestjs/service.js +51 -0
- package/dist/nestjs/types.d.ts +100 -0
- package/dist/nestjs/types.js +1 -0
- package/dist/nestjs.cjs +1 -0
- package/dist/nestjs.d.ts +10 -0
- package/dist/nestjs.js +9 -0
- package/dist/openfeature/context.d.ts +10 -0
- package/dist/openfeature/context.js +160 -0
- package/dist/openfeature/hooks.d.ts +6 -0
- package/dist/openfeature/hooks.js +27 -0
- package/dist/openfeature/server-provider.d.ts +20 -0
- package/dist/openfeature/server-provider.js +102 -0
- package/dist/openfeature/server.cjs +1 -0
- package/dist/openfeature/server.d.ts +3 -0
- package/dist/openfeature/server.js +3 -0
- package/dist/openfeature/shared.d.ts +37 -0
- package/dist/openfeature/shared.js +74 -0
- package/dist/openfeature/web-provider.d.ts +27 -0
- package/dist/openfeature/web-provider.js +151 -0
- package/dist/openfeature/web.cjs +1 -0
- package/dist/openfeature/web.d.ts +3 -0
- package/dist/openfeature/web.js +3 -0
- package/dist/persistence.d.ts +39 -0
- package/dist/persistence.js +203 -0
- package/dist/react/hooks.d.ts +52 -0
- package/dist/react/hooks.js +78 -0
- package/dist/react/provider.d.ts +71 -0
- package/dist/react/provider.js +99 -0
- package/dist/react.cjs +1 -0
- package/dist/react.d.ts +2 -0
- package/dist/react.js +2 -0
- package/dist/remote-evaluator.d.ts +28 -0
- package/dist/remote-evaluator.js +158 -0
- package/dist/types.d.ts +56 -0
- package/dist/types.js +1 -0
- package/featureflags.config.schema.json +38 -0
- package/package.json +107 -0
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.MergedOpenFeatureServerProvider = void 0;
|
|
4
|
+
exports.createOpenFeatureServerProvider = createOpenFeatureServerProvider;
|
|
5
|
+
const server_sdk_1 = require("@openfeature/server-sdk");
|
|
6
|
+
const remote_evaluator_js_1 = require("../remote-evaluator.js");
|
|
7
|
+
const shared_js_1 = require("./shared.js");
|
|
8
|
+
class MergedOpenFeatureServerProvider {
|
|
9
|
+
events = new server_sdk_1.OpenFeatureEventEmitter();
|
|
10
|
+
metadata;
|
|
11
|
+
runsOn = "server";
|
|
12
|
+
config;
|
|
13
|
+
evaluator;
|
|
14
|
+
evaluationCache = new Map();
|
|
15
|
+
constructor(config) {
|
|
16
|
+
this.config = config;
|
|
17
|
+
this.evaluator = new remote_evaluator_js_1.RemoteFeatureFlagEvaluator(config);
|
|
18
|
+
this.metadata = {
|
|
19
|
+
name: config.metadataName ?? "@mergedapp/feature-flags/openfeature-server",
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
async initialize() {
|
|
23
|
+
this.events.emit("PROVIDER_READY");
|
|
24
|
+
}
|
|
25
|
+
async onClose() {
|
|
26
|
+
this.evaluationCache.clear();
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
async resolveBooleanEvaluation(flagKey, defaultValue, context, logger) {
|
|
30
|
+
return this.resolveEvaluation({
|
|
31
|
+
context,
|
|
32
|
+
defaultValue,
|
|
33
|
+
expectedType: "BOOLEAN",
|
|
34
|
+
flagKey,
|
|
35
|
+
logger,
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
async resolveStringEvaluation(flagKey, defaultValue, context, logger) {
|
|
39
|
+
return this.resolveEvaluation({
|
|
40
|
+
context,
|
|
41
|
+
defaultValue,
|
|
42
|
+
expectedType: "STRING",
|
|
43
|
+
flagKey,
|
|
44
|
+
logger,
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
async resolveNumberEvaluation(flagKey, defaultValue, context, logger) {
|
|
48
|
+
return this.resolveEvaluation({
|
|
49
|
+
context,
|
|
50
|
+
defaultValue,
|
|
51
|
+
expectedType: "NUMBER",
|
|
52
|
+
flagKey,
|
|
53
|
+
logger,
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
async resolveObjectEvaluation(flagKey, defaultValue, context, logger) {
|
|
57
|
+
return this.resolveEvaluation({
|
|
58
|
+
context,
|
|
59
|
+
defaultValue,
|
|
60
|
+
expectedType: "OBJECT",
|
|
61
|
+
flagKey,
|
|
62
|
+
logger,
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
async resolveEvaluation(params) {
|
|
66
|
+
try {
|
|
67
|
+
const mappedContext = (0, shared_js_1.resolveOpenFeatureContext)({ config: this.config, context: params.context });
|
|
68
|
+
const cacheKey = JSON.stringify(mappedContext ?? null);
|
|
69
|
+
let evaluationPromise = this.evaluationCache.get(cacheKey);
|
|
70
|
+
if (!evaluationPromise) {
|
|
71
|
+
evaluationPromise = this.evaluator.evaluate(mappedContext).finally(() => {
|
|
72
|
+
this.evaluationCache.delete(cacheKey);
|
|
73
|
+
});
|
|
74
|
+
this.evaluationCache.set(cacheKey, evaluationPromise);
|
|
75
|
+
}
|
|
76
|
+
const evaluation = await evaluationPromise;
|
|
77
|
+
const flag = (0, shared_js_1.findEvaluatedFlag)(evaluation.flags, params.flagKey, this.config.flagIds);
|
|
78
|
+
if (!flag) {
|
|
79
|
+
return (0, shared_js_1.resolveMissingFlagEvaluation)({
|
|
80
|
+
defaultValue: params.defaultValue,
|
|
81
|
+
flagKey: params.flagKey,
|
|
82
|
+
logger: params.logger,
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
if (!(0, shared_js_1.matchesExpectedType)(flag, params.expectedType)) {
|
|
86
|
+
return (0, shared_js_1.resolveTypeMismatchEvaluation)({
|
|
87
|
+
actualFlag: flag,
|
|
88
|
+
defaultValue: params.defaultValue,
|
|
89
|
+
expectedType: params.expectedType,
|
|
90
|
+
logger: params.logger,
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
return (0, shared_js_1.resolveSuccessfulEvaluation)({
|
|
94
|
+
defaultValue: params.defaultValue,
|
|
95
|
+
flag,
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
catch (error) {
|
|
99
|
+
params.logger.error(`Failed evaluating feature flag (${params.flagKey}).`, error);
|
|
100
|
+
throw error;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
exports.MergedOpenFeatureServerProvider = MergedOpenFeatureServerProvider;
|
|
105
|
+
function createOpenFeatureServerProvider(config) {
|
|
106
|
+
return new MergedOpenFeatureServerProvider(config);
|
|
107
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.MergedOpenFeatureServerProvider = exports.createOpenFeatureServerProvider = exports.mergeFeatureFlagEvaluationContexts = exports.createDefaultOpenFeatureContextMapper = exports.DEFAULT_TARGETING_KEY_ATTRIBUTE = exports.createStaticContextHook = exports.createLoggingHook = void 0;
|
|
4
|
+
var hooks_js_1 = require("./hooks.js");
|
|
5
|
+
Object.defineProperty(exports, "createLoggingHook", { enumerable: true, get: function () { return hooks_js_1.createLoggingHook; } });
|
|
6
|
+
Object.defineProperty(exports, "createStaticContextHook", { enumerable: true, get: function () { return hooks_js_1.createStaticContextHook; } });
|
|
7
|
+
var context_js_1 = require("./context.js");
|
|
8
|
+
Object.defineProperty(exports, "DEFAULT_TARGETING_KEY_ATTRIBUTE", { enumerable: true, get: function () { return context_js_1.DEFAULT_TARGETING_KEY_ATTRIBUTE; } });
|
|
9
|
+
Object.defineProperty(exports, "createDefaultOpenFeatureContextMapper", { enumerable: true, get: function () { return context_js_1.createDefaultOpenFeatureContextMapper; } });
|
|
10
|
+
Object.defineProperty(exports, "mergeFeatureFlagEvaluationContexts", { enumerable: true, get: function () { return context_js_1.mergeFeatureFlagEvaluationContexts; } });
|
|
11
|
+
var server_provider_js_1 = require("./server-provider.js");
|
|
12
|
+
Object.defineProperty(exports, "createOpenFeatureServerProvider", { enumerable: true, get: function () { return server_provider_js_1.createOpenFeatureServerProvider; } });
|
|
13
|
+
Object.defineProperty(exports, "MergedOpenFeatureServerProvider", { enumerable: true, get: function () { return server_provider_js_1.MergedOpenFeatureServerProvider; } });
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.resolveOpenFeatureContext = resolveOpenFeatureContext;
|
|
4
|
+
exports.createOpenFeatureMetadata = createOpenFeatureMetadata;
|
|
5
|
+
exports.findEvaluatedFlag = findEvaluatedFlag;
|
|
6
|
+
exports.resolveSuccessfulEvaluation = resolveSuccessfulEvaluation;
|
|
7
|
+
exports.resolveMissingFlagEvaluation = resolveMissingFlagEvaluation;
|
|
8
|
+
exports.resolveTypeMismatchEvaluation = resolveTypeMismatchEvaluation;
|
|
9
|
+
exports.matchesExpectedType = matchesExpectedType;
|
|
10
|
+
const core_1 = require("@openfeature/core");
|
|
11
|
+
const context_js_1 = require("./context.js");
|
|
12
|
+
function resolveOpenFeatureContext(params) {
|
|
13
|
+
const mapper = params.config.contextMapper ??
|
|
14
|
+
(0, context_js_1.createDefaultOpenFeatureContextMapper)({
|
|
15
|
+
targetingKeyAttribute: params.config.targetingKeyAttribute,
|
|
16
|
+
});
|
|
17
|
+
return (0, context_js_1.mergeFeatureFlagEvaluationContexts)(params.config.evaluationContext ?? null, params.context ? mapper(params.context) : null);
|
|
18
|
+
}
|
|
19
|
+
function createOpenFeatureMetadata(params) {
|
|
20
|
+
return {
|
|
21
|
+
id: params.flag.id,
|
|
22
|
+
name: params.flag.name,
|
|
23
|
+
type: params.flag.type,
|
|
24
|
+
teamId: params.flag.teamId ?? "",
|
|
25
|
+
enabled: params.flag.enabled,
|
|
26
|
+
source: params.runtimeStatus?.source ?? "network",
|
|
27
|
+
isStale: params.runtimeStatus?.isStale ?? false,
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
function findEvaluatedFlag(flags, flagKey, flagIds) {
|
|
31
|
+
const resolvedId = flagIds?.[flagKey];
|
|
32
|
+
if (resolvedId) {
|
|
33
|
+
const matchedByResolvedId = flags.find((flag) => flag.id === resolvedId);
|
|
34
|
+
if (matchedByResolvedId) {
|
|
35
|
+
return matchedByResolvedId;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return flags.find((flag) => flag.id === flagKey) ?? flags.find((flag) => flag.name === flagKey);
|
|
39
|
+
}
|
|
40
|
+
function resolveSuccessfulEvaluation(params) {
|
|
41
|
+
return {
|
|
42
|
+
value: params.flag.value,
|
|
43
|
+
variant: params.flag.name,
|
|
44
|
+
flagMetadata: createOpenFeatureMetadata(params),
|
|
45
|
+
reason: resolveResolutionReason(params),
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
function resolveResolutionReason(params) {
|
|
49
|
+
if (!params.flag.enabled) {
|
|
50
|
+
return core_1.StandardResolutionReasons.DISABLED;
|
|
51
|
+
}
|
|
52
|
+
if (params.runtimeStatus?.isStale) {
|
|
53
|
+
return core_1.StandardResolutionReasons.STALE;
|
|
54
|
+
}
|
|
55
|
+
if (params.runtimeStatus?.source === "persisted") {
|
|
56
|
+
return core_1.StandardResolutionReasons.CACHED;
|
|
57
|
+
}
|
|
58
|
+
return core_1.StandardResolutionReasons.TARGETING_MATCH;
|
|
59
|
+
}
|
|
60
|
+
function resolveMissingFlagEvaluation(params) {
|
|
61
|
+
params.logger?.warn(`Feature flag (${params.flagKey}) was not found.`);
|
|
62
|
+
return {
|
|
63
|
+
value: params.defaultValue,
|
|
64
|
+
reason: core_1.StandardResolutionReasons.ERROR,
|
|
65
|
+
errorCode: core_1.ErrorCode.FLAG_NOT_FOUND,
|
|
66
|
+
errorMessage: `Feature flag (${params.flagKey}) was not found.`,
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
function resolveTypeMismatchEvaluation(params) {
|
|
70
|
+
const message = `Feature flag (${params.actualFlag.name}) resolved as ${params.actualFlag.type}, expected ${params.expectedType}.`;
|
|
71
|
+
params.logger?.warn(message);
|
|
72
|
+
return {
|
|
73
|
+
value: params.defaultValue,
|
|
74
|
+
variant: params.actualFlag.name,
|
|
75
|
+
flagMetadata: createOpenFeatureMetadata({ flag: params.actualFlag }),
|
|
76
|
+
reason: core_1.StandardResolutionReasons.ERROR,
|
|
77
|
+
errorCode: core_1.ErrorCode.TYPE_MISMATCH,
|
|
78
|
+
errorMessage: message,
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
function matchesExpectedType(flag, expectedType) {
|
|
82
|
+
return flag.type === expectedType;
|
|
83
|
+
}
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.MergedOpenFeatureWebProvider = void 0;
|
|
4
|
+
exports.createOpenFeatureWebProvider = createOpenFeatureWebProvider;
|
|
5
|
+
const web_sdk_1 = require("@openfeature/web-sdk");
|
|
6
|
+
const client_js_1 = require("../client.js");
|
|
7
|
+
const shared_js_1 = require("./shared.js");
|
|
8
|
+
class MergedOpenFeatureWebProvider {
|
|
9
|
+
events = new web_sdk_1.OpenFeatureEventEmitter();
|
|
10
|
+
metadata;
|
|
11
|
+
runsOn = "client";
|
|
12
|
+
client;
|
|
13
|
+
ownsClient;
|
|
14
|
+
config;
|
|
15
|
+
currentFlagsSignature = "";
|
|
16
|
+
unsubscribe = null;
|
|
17
|
+
constructor(config) {
|
|
18
|
+
this.config = config;
|
|
19
|
+
this.ownsClient = !config.client;
|
|
20
|
+
this.client =
|
|
21
|
+
config.client ??
|
|
22
|
+
new client_js_1.MergedFeatureFlags({
|
|
23
|
+
apiUrl: config.apiUrl,
|
|
24
|
+
clientKey: config.clientKey,
|
|
25
|
+
environmentId: config.environmentId,
|
|
26
|
+
organizationId: config.organizationId,
|
|
27
|
+
teamId: config.teamId,
|
|
28
|
+
publicKey: config.publicKey,
|
|
29
|
+
evaluationContext: config.evaluationContext,
|
|
30
|
+
refreshInterval: config.refreshInterval,
|
|
31
|
+
});
|
|
32
|
+
this.metadata = {
|
|
33
|
+
name: config.metadataName ?? "@mergedapp/feature-flags/openfeature-web",
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
async initialize(context) {
|
|
37
|
+
if (context) {
|
|
38
|
+
this.client.setEvaluationContext((0, shared_js_1.resolveOpenFeatureContext)({ config: this.config, context }));
|
|
39
|
+
}
|
|
40
|
+
await this.client.initialize();
|
|
41
|
+
let previousFlags = this.client.getAllFlags();
|
|
42
|
+
this.currentFlagsSignature = serializeFlags(previousFlags);
|
|
43
|
+
this.unsubscribe = this.client.subscribe(() => {
|
|
44
|
+
const nextFlags = this.client.getAllFlags();
|
|
45
|
+
const nextSignature = serializeFlags(nextFlags);
|
|
46
|
+
if (nextSignature !== this.currentFlagsSignature) {
|
|
47
|
+
this.currentFlagsSignature = nextSignature;
|
|
48
|
+
this.events.emit(web_sdk_1.ClientProviderEvents.ConfigurationChanged, {
|
|
49
|
+
flagsChanged: getChangedFlagNames(previousFlags, nextFlags),
|
|
50
|
+
});
|
|
51
|
+
previousFlags = nextFlags;
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
async onClose() {
|
|
56
|
+
this.unsubscribe?.();
|
|
57
|
+
this.unsubscribe = null;
|
|
58
|
+
if (this.ownsClient) {
|
|
59
|
+
this.client.destroy();
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
async onContextChange(_oldContext, newContext) {
|
|
63
|
+
this.client.setEvaluationContext((0, shared_js_1.resolveOpenFeatureContext)({ config: this.config, context: newContext }));
|
|
64
|
+
await this.client.refresh();
|
|
65
|
+
}
|
|
66
|
+
resolveBooleanEvaluation(flagKey, defaultValue, _context) {
|
|
67
|
+
return this.resolveEvaluation({
|
|
68
|
+
defaultValue,
|
|
69
|
+
expectedType: "BOOLEAN",
|
|
70
|
+
flagKey,
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
resolveStringEvaluation(flagKey, defaultValue, _context) {
|
|
74
|
+
return this.resolveEvaluation({
|
|
75
|
+
defaultValue,
|
|
76
|
+
expectedType: "STRING",
|
|
77
|
+
flagKey,
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
resolveNumberEvaluation(flagKey, defaultValue, _context) {
|
|
81
|
+
return this.resolveEvaluation({
|
|
82
|
+
defaultValue,
|
|
83
|
+
expectedType: "NUMBER",
|
|
84
|
+
flagKey,
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
resolveObjectEvaluation(flagKey, defaultValue, _context) {
|
|
88
|
+
return this.resolveEvaluation({
|
|
89
|
+
defaultValue,
|
|
90
|
+
expectedType: "OBJECT",
|
|
91
|
+
flagKey,
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
resolveEvaluation(params) {
|
|
95
|
+
const flag = (0, shared_js_1.findEvaluatedFlag)(this.client.getAllFlags(), params.flagKey, this.config.flagIds);
|
|
96
|
+
if (!flag) {
|
|
97
|
+
return {
|
|
98
|
+
...(0, shared_js_1.resolveMissingFlagEvaluation)(params),
|
|
99
|
+
reason: this.client.getStatus().source === "defaults" ? web_sdk_1.StandardResolutionReasons.DEFAULT : undefined,
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
if (!(0, shared_js_1.matchesExpectedType)(flag, params.expectedType)) {
|
|
103
|
+
return (0, shared_js_1.resolveTypeMismatchEvaluation)({
|
|
104
|
+
actualFlag: flag,
|
|
105
|
+
defaultValue: params.defaultValue,
|
|
106
|
+
expectedType: params.expectedType,
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
return {
|
|
110
|
+
...(0, shared_js_1.resolveSuccessfulEvaluation)({
|
|
111
|
+
defaultValue: params.defaultValue,
|
|
112
|
+
flag,
|
|
113
|
+
runtimeStatus: this.client.getStatus(),
|
|
114
|
+
}),
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
exports.MergedOpenFeatureWebProvider = MergedOpenFeatureWebProvider;
|
|
119
|
+
function serializeFlags(flags) {
|
|
120
|
+
return JSON.stringify(flags.map((flag) => ({
|
|
121
|
+
enabled: flag.enabled,
|
|
122
|
+
id: flag.id,
|
|
123
|
+
name: flag.name,
|
|
124
|
+
type: flag.type,
|
|
125
|
+
value: flag.value,
|
|
126
|
+
})));
|
|
127
|
+
}
|
|
128
|
+
function getChangedFlagNames(previousFlags, nextFlags) {
|
|
129
|
+
const previousById = new Map(previousFlags.map((flag) => [flag.id, serializeComparableFlag(flag)]));
|
|
130
|
+
const nextById = new Map(nextFlags.map((flag) => [flag.id, serializeComparableFlag(flag)]));
|
|
131
|
+
const changed = new Set();
|
|
132
|
+
for (const [id, nextEntry] of nextById) {
|
|
133
|
+
const previousEntry = previousById.get(id);
|
|
134
|
+
if (!previousEntry ||
|
|
135
|
+
previousEntry.flag.enabled !== nextEntry.flag.enabled ||
|
|
136
|
+
previousEntry.flag.name !== nextEntry.flag.name ||
|
|
137
|
+
previousEntry.serializedValue !== nextEntry.serializedValue) {
|
|
138
|
+
changed.add(nextEntry.flag.name);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
for (const [id, previousEntry] of previousById) {
|
|
142
|
+
if (!nextById.has(id)) {
|
|
143
|
+
changed.add(previousEntry.flag.name);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
return Array.from(changed);
|
|
147
|
+
}
|
|
148
|
+
function serializeComparableFlag(flag) {
|
|
149
|
+
return {
|
|
150
|
+
flag,
|
|
151
|
+
serializedValue: JSON.stringify(flag.value),
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
function createOpenFeatureWebProvider(config) {
|
|
155
|
+
return new MergedOpenFeatureWebProvider(config);
|
|
156
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.MergedOpenFeatureWebProvider = exports.createOpenFeatureWebProvider = exports.mergeFeatureFlagEvaluationContexts = exports.createDefaultOpenFeatureContextMapper = exports.DEFAULT_TARGETING_KEY_ATTRIBUTE = exports.createStaticContextHook = exports.createLoggingHook = void 0;
|
|
4
|
+
var hooks_js_1 = require("./hooks.js");
|
|
5
|
+
Object.defineProperty(exports, "createLoggingHook", { enumerable: true, get: function () { return hooks_js_1.createLoggingHook; } });
|
|
6
|
+
Object.defineProperty(exports, "createStaticContextHook", { enumerable: true, get: function () { return hooks_js_1.createStaticContextHook; } });
|
|
7
|
+
var context_js_1 = require("./context.js");
|
|
8
|
+
Object.defineProperty(exports, "DEFAULT_TARGETING_KEY_ATTRIBUTE", { enumerable: true, get: function () { return context_js_1.DEFAULT_TARGETING_KEY_ATTRIBUTE; } });
|
|
9
|
+
Object.defineProperty(exports, "createDefaultOpenFeatureContextMapper", { enumerable: true, get: function () { return context_js_1.createDefaultOpenFeatureContextMapper; } });
|
|
10
|
+
Object.defineProperty(exports, "mergeFeatureFlagEvaluationContexts", { enumerable: true, get: function () { return context_js_1.mergeFeatureFlagEvaluationContexts; } });
|
|
11
|
+
var web_provider_js_1 = require("./web-provider.js");
|
|
12
|
+
Object.defineProperty(exports, "createOpenFeatureWebProvider", { enumerable: true, get: function () { return web_provider_js_1.createOpenFeatureWebProvider; } });
|
|
13
|
+
Object.defineProperty(exports, "MergedOpenFeatureWebProvider", { enumerable: true, get: function () { return web_provider_js_1.MergedOpenFeatureWebProvider; } });
|
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.serializeCanonicalValue = serializeCanonicalValue;
|
|
37
|
+
exports.createDefaultFeatureFlagRuntimeStatus = createDefaultFeatureFlagRuntimeStatus;
|
|
38
|
+
exports.buildSnapshotScopeParts = buildSnapshotScopeParts;
|
|
39
|
+
exports.createPersistedFeatureFlagSnapshot = createPersistedFeatureFlagSnapshot;
|
|
40
|
+
exports.doesSnapshotMatchScope = doesSnapshotMatchScope;
|
|
41
|
+
exports.isBrowserPersistenceAvailable = isBrowserPersistenceAvailable;
|
|
42
|
+
exports.createLocalStorageFeatureFlagSnapshotStore = createLocalStorageFeatureFlagSnapshotStore;
|
|
43
|
+
exports.createFileFeatureFlagSnapshotStore = createFileFeatureFlagSnapshotStore;
|
|
44
|
+
exports.resolveSnapshotStore = resolveSnapshotStore;
|
|
45
|
+
exports.getSnapshotKeyPrefix = getSnapshotKeyPrefix;
|
|
46
|
+
exports.decodeJwtPayload = decodeJwtPayload;
|
|
47
|
+
const DEFAULT_SNAPSHOT_KEY_PREFIX = "merged-feature-flags";
|
|
48
|
+
const SNAPSHOT_SCHEMA_VERSION = 1;
|
|
49
|
+
const textEncoder = new TextEncoder();
|
|
50
|
+
const textDecoder = new TextDecoder();
|
|
51
|
+
let nodeModulePromise = null;
|
|
52
|
+
async function getNodeModules() {
|
|
53
|
+
if (!nodeModulePromise) {
|
|
54
|
+
nodeModulePromise = Promise.all([
|
|
55
|
+
Promise.resolve().then(() => __importStar(require("node:crypto"))),
|
|
56
|
+
Promise.resolve().then(() => __importStar(require("node:fs/promises"))),
|
|
57
|
+
Promise.resolve().then(() => __importStar(require("node:os"))),
|
|
58
|
+
Promise.resolve().then(() => __importStar(require("node:path"))),
|
|
59
|
+
]).then(([crypto, fs, os, path]) => ({ crypto, fs, os, path }));
|
|
60
|
+
}
|
|
61
|
+
return nodeModulePromise;
|
|
62
|
+
}
|
|
63
|
+
function normalizeValue(value) {
|
|
64
|
+
if (Array.isArray(value)) {
|
|
65
|
+
return value.map((item) => normalizeValue(item));
|
|
66
|
+
}
|
|
67
|
+
if (value && typeof value === "object") {
|
|
68
|
+
const entries = Object.entries(value)
|
|
69
|
+
.sort(([left], [right]) => left.localeCompare(right))
|
|
70
|
+
.map(([key, entryValue]) => [key, normalizeValue(entryValue)]);
|
|
71
|
+
return Object.fromEntries(entries);
|
|
72
|
+
}
|
|
73
|
+
return value;
|
|
74
|
+
}
|
|
75
|
+
function serializeCanonicalValue(value) {
|
|
76
|
+
return JSON.stringify(normalizeValue(value));
|
|
77
|
+
}
|
|
78
|
+
function createDefaultFeatureFlagRuntimeStatus() {
|
|
79
|
+
return {
|
|
80
|
+
source: "defaults",
|
|
81
|
+
isStale: false,
|
|
82
|
+
lastSuccessfulRefreshAt: null,
|
|
83
|
+
tokenExpiresAt: null,
|
|
84
|
+
lastError: null,
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
async function sha256Hex(value) {
|
|
88
|
+
if (globalThis.crypto?.subtle) {
|
|
89
|
+
const digest = await globalThis.crypto.subtle.digest("SHA-256", textEncoder.encode(value));
|
|
90
|
+
return Array.from(new Uint8Array(digest), (byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
91
|
+
}
|
|
92
|
+
const { crypto } = await getNodeModules();
|
|
93
|
+
return crypto.createHash("sha256").update(value).digest("hex");
|
|
94
|
+
}
|
|
95
|
+
async function buildSnapshotScopeParts(params) {
|
|
96
|
+
const contextHash = await sha256Hex(serializeCanonicalValue(params.config.evaluationContext ?? null));
|
|
97
|
+
const clientKeyFingerprint = await sha256Hex(params.config.clientKey);
|
|
98
|
+
const prefix = params.keyPrefix?.trim() || DEFAULT_SNAPSHOT_KEY_PREFIX;
|
|
99
|
+
const scopeMaterial = serializeCanonicalValue({
|
|
100
|
+
apiUrl: params.config.apiUrl.replace(/\/$/, ""),
|
|
101
|
+
organizationId: params.config.organizationId.trim(),
|
|
102
|
+
environmentId: params.config.environmentId.trim(),
|
|
103
|
+
teamId: params.config.teamId?.trim() || null,
|
|
104
|
+
clientKeyFingerprint,
|
|
105
|
+
contextHash,
|
|
106
|
+
});
|
|
107
|
+
return {
|
|
108
|
+
scopeKey: `${prefix}:${await sha256Hex(scopeMaterial)}`,
|
|
109
|
+
organizationId: params.config.organizationId.trim(),
|
|
110
|
+
environmentId: params.config.environmentId.trim(),
|
|
111
|
+
teamId: params.config.teamId?.trim() || null,
|
|
112
|
+
clientKeyFingerprint,
|
|
113
|
+
contextHash,
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
function buildSnapshotRecord(params) {
|
|
117
|
+
return {
|
|
118
|
+
schemaVersion: SNAPSHOT_SCHEMA_VERSION,
|
|
119
|
+
scopeKey: params.scope.scopeKey,
|
|
120
|
+
organizationId: params.scope.organizationId,
|
|
121
|
+
environmentId: params.scope.environmentId,
|
|
122
|
+
teamId: params.scope.teamId,
|
|
123
|
+
clientKeyFingerprint: params.scope.clientKeyFingerprint,
|
|
124
|
+
contextHash: params.scope.contextHash,
|
|
125
|
+
token: params.token,
|
|
126
|
+
publicKeyPem: params.publicKeyPem,
|
|
127
|
+
fetchedAt: params.fetchedAt,
|
|
128
|
+
tokenExpiresAt: params.tokenExpiresAt,
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
function createPersistedFeatureFlagSnapshot(params) {
|
|
132
|
+
return buildSnapshotRecord(params);
|
|
133
|
+
}
|
|
134
|
+
function doesSnapshotMatchScope(params) {
|
|
135
|
+
return (params.snapshot.schemaVersion === SNAPSHOT_SCHEMA_VERSION &&
|
|
136
|
+
params.snapshot.scopeKey === params.scope.scopeKey &&
|
|
137
|
+
params.snapshot.organizationId === params.scope.organizationId &&
|
|
138
|
+
params.snapshot.environmentId === params.scope.environmentId &&
|
|
139
|
+
params.snapshot.teamId === params.scope.teamId &&
|
|
140
|
+
params.snapshot.clientKeyFingerprint === params.scope.clientKeyFingerprint &&
|
|
141
|
+
params.snapshot.contextHash === params.scope.contextHash);
|
|
142
|
+
}
|
|
143
|
+
function isBrowserPersistenceAvailable() {
|
|
144
|
+
return typeof window !== "undefined" && typeof window.localStorage !== "undefined";
|
|
145
|
+
}
|
|
146
|
+
function createLocalStorageFeatureFlagSnapshotStore() {
|
|
147
|
+
return {
|
|
148
|
+
async load(key) {
|
|
149
|
+
if (!isBrowserPersistenceAvailable()) {
|
|
150
|
+
return null;
|
|
151
|
+
}
|
|
152
|
+
try {
|
|
153
|
+
const raw = window.localStorage.getItem(key);
|
|
154
|
+
return raw ? JSON.parse(raw) : null;
|
|
155
|
+
}
|
|
156
|
+
catch {
|
|
157
|
+
return null;
|
|
158
|
+
}
|
|
159
|
+
},
|
|
160
|
+
async save(key, snapshot) {
|
|
161
|
+
if (!isBrowserPersistenceAvailable()) {
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
try {
|
|
165
|
+
window.localStorage.setItem(key, JSON.stringify(snapshot));
|
|
166
|
+
}
|
|
167
|
+
catch {
|
|
168
|
+
}
|
|
169
|
+
},
|
|
170
|
+
async remove(key) {
|
|
171
|
+
if (!isBrowserPersistenceAvailable()) {
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
try {
|
|
175
|
+
window.localStorage.removeItem(key);
|
|
176
|
+
}
|
|
177
|
+
catch {
|
|
178
|
+
}
|
|
179
|
+
},
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
function sanitizeKeyForFilename(key) {
|
|
183
|
+
return encodeURIComponent(key);
|
|
184
|
+
}
|
|
185
|
+
function createFileFeatureFlagSnapshotStore(params) {
|
|
186
|
+
return {
|
|
187
|
+
async load(key) {
|
|
188
|
+
const { fs, os, path } = await getNodeModules();
|
|
189
|
+
const rootDirectory = params?.rootDirectory ?? path.join(os.tmpdir(), DEFAULT_SNAPSHOT_KEY_PREFIX);
|
|
190
|
+
const filePath = path.join(rootDirectory, `${sanitizeKeyForFilename(key)}.json`);
|
|
191
|
+
try {
|
|
192
|
+
const raw = await fs.readFile(filePath, "utf-8");
|
|
193
|
+
return JSON.parse(raw);
|
|
194
|
+
}
|
|
195
|
+
catch {
|
|
196
|
+
return null;
|
|
197
|
+
}
|
|
198
|
+
},
|
|
199
|
+
async save(key, snapshot) {
|
|
200
|
+
const { fs, os, path } = await getNodeModules();
|
|
201
|
+
const rootDirectory = params?.rootDirectory ?? path.join(os.tmpdir(), DEFAULT_SNAPSHOT_KEY_PREFIX);
|
|
202
|
+
const filePath = path.join(rootDirectory, `${sanitizeKeyForFilename(key)}.json`);
|
|
203
|
+
const tempFilePath = path.join(rootDirectory, `${sanitizeKeyForFilename(key)}.${Date.now()}.${Math.random().toString(16).slice(2)}.tmp`);
|
|
204
|
+
await fs.mkdir(rootDirectory, { recursive: true });
|
|
205
|
+
await fs.writeFile(tempFilePath, JSON.stringify(snapshot), "utf-8");
|
|
206
|
+
await fs.rename(tempFilePath, filePath);
|
|
207
|
+
},
|
|
208
|
+
async remove(key) {
|
|
209
|
+
const { fs, os, path } = await getNodeModules();
|
|
210
|
+
const rootDirectory = params?.rootDirectory ?? path.join(os.tmpdir(), DEFAULT_SNAPSHOT_KEY_PREFIX);
|
|
211
|
+
const filePath = path.join(rootDirectory, `${sanitizeKeyForFilename(key)}.json`);
|
|
212
|
+
try {
|
|
213
|
+
await fs.rm(filePath, { force: true });
|
|
214
|
+
}
|
|
215
|
+
catch {
|
|
216
|
+
}
|
|
217
|
+
},
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
async function resolveSnapshotStore(params) {
|
|
221
|
+
const persistence = params.config.snapshotPersistence;
|
|
222
|
+
if (persistence === false) {
|
|
223
|
+
return null;
|
|
224
|
+
}
|
|
225
|
+
if (persistence?.store) {
|
|
226
|
+
return persistence.store;
|
|
227
|
+
}
|
|
228
|
+
if (isBrowserPersistenceAvailable()) {
|
|
229
|
+
return createLocalStorageFeatureFlagSnapshotStore();
|
|
230
|
+
}
|
|
231
|
+
return createFileFeatureFlagSnapshotStore();
|
|
232
|
+
}
|
|
233
|
+
function getSnapshotKeyPrefix(params) {
|
|
234
|
+
const persistence = params.config.snapshotPersistence;
|
|
235
|
+
return persistence?.keyPrefix?.trim() || DEFAULT_SNAPSHOT_KEY_PREFIX;
|
|
236
|
+
}
|
|
237
|
+
function decodeJwtPayload(token) {
|
|
238
|
+
const segments = token.split(".");
|
|
239
|
+
if (segments.length !== 3) {
|
|
240
|
+
throw new Error("JWT must contain exactly three segments.");
|
|
241
|
+
}
|
|
242
|
+
const payloadSegment = segments[1];
|
|
243
|
+
const normalized = payloadSegment.replace(/-/g, "+").replace(/_/g, "/");
|
|
244
|
+
const padded = normalized.padEnd(Math.ceil(normalized.length / 4) * 4, "=");
|
|
245
|
+
const payloadBytes = typeof atob === "function"
|
|
246
|
+
? Uint8Array.from(atob(padded), (character) => character.charCodeAt(0))
|
|
247
|
+
: Uint8Array.from(Buffer.from(padded, "base64"));
|
|
248
|
+
return JSON.parse(textDecoder.decode(payloadBytes));
|
|
249
|
+
}
|