@@ -20,6 +20,9 @@ class FlaggingProvider extends DatadogNodeServerProvider {
2020 /** @type {{ start: Function, stop: Function } | undefined } */
2121 #configurationSource
2222
23+ /** @type {import('@datadog/openfeature-node-server').UniversalFlagConfigurationV1 | undefined } */
24+ #ffeConfig
25+
2326 /**
2427 * @param {import('../tracer') } tracer - Datadog tracer instance
2528 * @param {import('../config/config-base') } config - Tracer configuration object
@@ -48,6 +51,115 @@ class FlaggingProvider extends DatadogNodeServerProvider {
4851 this . #configurationSource?. start ( )
4952 }
5053
54+ /**
55+ * Stores the current configuration and updates the base provider.
56+ *
57+ * @param {import('@datadog/openfeature-node-server').UniversalFlagConfigurationV1 | undefined } configuration
58+ * @returns {void }
59+ */
60+ setConfiguration ( configuration ) {
61+ this . #ffeConfig = configuration
62+ super . setConfiguration ( configuration )
63+ }
64+
65+ /**
66+ * Resolves a boolean flag and normalizes its canonical result.
67+ *
68+ * @param {string } flagKey
69+ * @param {boolean } defaultValue
70+ * @param {import('@openfeature/server-sdk').EvaluationContext } context
71+ * @param {import('@openfeature/server-sdk').Logger } logger
72+ * @returns {Promise<import('@openfeature/server-sdk').ResolutionDetails<boolean>> }
73+ */
74+ resolveBooleanEvaluation ( flagKey , defaultValue , context , logger ) {
75+ return super . resolveBooleanEvaluation ( flagKey , defaultValue , context , logger )
76+ . then ( result => this . #normalizeResolution( flagKey , result ) )
77+ }
78+
79+ /**
80+ * Resolves a string flag and normalizes its canonical result.
81+ *
82+ * @param {string } flagKey
83+ * @param {string } defaultValue
84+ * @param {import('@openfeature/server-sdk').EvaluationContext } context
85+ * @param {import('@openfeature/server-sdk').Logger } logger
86+ * @returns {Promise<import('@openfeature/server-sdk').ResolutionDetails<string>> }
87+ */
88+ resolveStringEvaluation ( flagKey , defaultValue , context , logger ) {
89+ return super . resolveStringEvaluation ( flagKey , defaultValue , context , logger )
90+ . then ( result => this . #normalizeResolution( flagKey , result ) )
91+ }
92+
93+ /**
94+ * Resolves a number flag and normalizes its canonical result.
95+ *
96+ * @param {string } flagKey
97+ * @param {number } defaultValue
98+ * @param {import('@openfeature/server-sdk').EvaluationContext } context
99+ * @param {import('@openfeature/server-sdk').Logger } logger
100+ * @returns {Promise<import('@openfeature/server-sdk').ResolutionDetails<number>> }
101+ */
102+ resolveNumberEvaluation ( flagKey , defaultValue , context , logger ) {
103+ return super . resolveNumberEvaluation ( flagKey , defaultValue , context , logger )
104+ . then ( result => this . #normalizeResolution( flagKey , result ) )
105+ }
106+
107+ /**
108+ * Resolves an object flag and normalizes its canonical result.
109+ *
110+ * @template {import('@openfeature/server-sdk').JsonValue } T
111+ * @param {string } flagKey
112+ * @param {T } defaultValue
113+ * @param {import('@openfeature/server-sdk').EvaluationContext } context
114+ * @param {import('@openfeature/server-sdk').Logger } logger
115+ * @returns {Promise<import('@openfeature/server-sdk').ResolutionDetails<T>> }
116+ */
117+ resolveObjectEvaluation ( flagKey , defaultValue , context , logger ) {
118+ return super . resolveObjectEvaluation ( flagKey , defaultValue , context , logger )
119+ . then ( result => this . #normalizeResolution( flagKey , result ) )
120+ }
121+
122+ /**
123+ * Converts provider results to the canonical FFE reason contract.
124+ *
125+ * @template {import('@openfeature/server-sdk').FlagValue } T
126+ * @param {string } flagKey
127+ * @param {import('@openfeature/server-sdk').ResolutionDetails<T> } result
128+ * @returns {import('@openfeature/server-sdk').ResolutionDetails<T> }
129+ */
130+ #normalizeResolution ( flagKey , result ) {
131+ if ( result ?. reason !== 'TARGETING_MATCH' && result ?. reason !== 'DEFAULT' ) {
132+ return result
133+ }
134+
135+ const allocations = this . #ffeConfig?. flags ?. [ flagKey ] ?. allocations
136+ if ( ! Array . isArray ( allocations ) ) {
137+ return result
138+ }
139+
140+ const allocation = allocations . find ( item => item . key === result . flagMetadata ?. allocationKey )
141+ if ( ! allocation || allocation . rules ?. length || ! Array . isArray ( allocation . splits ) ) {
142+ return result
143+ }
144+
145+ const flag = this . #ffeConfig. flags [ flagKey ]
146+ const selectedSplit = allocation . splits . find ( split => {
147+ const variant = flag . variations ?. [ split . variationKey ]
148+ return variant ?. key === result . variant || split . variationKey === result . variant
149+ } )
150+ if ( ! selectedSplit ) {
151+ return result
152+ }
153+
154+ const hasTimeBounds = allocation . startAt !== undefined || allocation . endAt !== undefined
155+ if ( hasTimeBounds && allocation . splits . length === 1 && ! selectedSplit . shards ?. length ) {
156+ return { ...result , reason : 'DEFAULT' }
157+ }
158+
159+ const reason = selectedSplit ?. shards ?. length ? 'SPLIT' : 'STATIC'
160+ return { ...result , reason }
161+ }
162+
51163 /**
52164 * Called when the provider is shut down.
53165 * Cleans up resources including channel subscriptions.
0 commit comments