feat(flags): FlagsClient.setConfiguration + context matching [PR2] (FFL-2688)#1331
feat(flags): FlagsClient.setConfiguration + context matching [PR2] (FFL-2688)#1331btthomas wants to merge 3 commits into
Conversation
…2688) Add offline configuration loading to the core FlagsClient: - setConfiguration(config): decodes a precomputed configuration into the flag cache. When no context is set yet, it adopts the configuration's embedded context (implicit set) so offline evaluation works with no native fetch. When a context is already set, the configuration's context must match it. - Context matching: new configuration/context.ts normalizes the flat wire context through the same processEvaluationContext as the active context, then deep-compares — a config with no embedded context is context-agnostic. - Mismatch -> serve no values and report INVALID_CONTEXT (added to FlagErrorCode); empty/unsupported/invalid config -> PROVIDER_NOT_READY (not FLAG_NOT_FOUND). - A subsequent native setEvaluationContext fetch supersedes the offline config. Tests cover context normalization/matching and the setConfiguration flows (implicit-set no-fetch, match/mismatch against an explicit context, invalid config, and fetch supersession). fetchPolicy (PR3) and the OpenFeature provider lifecycle (PR4) are unchanged here. The parse-failure vs valid-but-empty distinction (L2) remains for a follow-up. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…tests) Review follow-ups on PR2: - #7: build the processed evaluation context with a Map + Object.fromEntries so reserved keys like "__proto__" (notably a "__proto__": null attribute, which a plain assignment would have used to null the object's prototype) are handled as data without prototype manipulation. - #1: mark the forward-compat seam in applyConfiguration so a future server/rules configuration is not rejected as "invalid" (rules configs are context-agnostic). - #3/#4/#5: document the deferred seams — fetch-failure/staleness fallback and the NEVER re-match belong to the fetch-policy step; the PROVIDER_ERROR provider event belongs to the OpenFeature provider step. - #6: add tests for a context-agnostic configuration, configuration replacement, and the __proto__ context-attribute / proto-null safety cases. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The offline counterpart to setEvaluationContext: records the active context and reconciles a configuration loaded via setConfiguration against it (context matching for a precomputed config) with no native request. This is the core seam an offline OpenFeature provider uses to update context on initialize/onContextChange without ever fetching from the CDN — replacing the previously-planned fetchPolicy flag with a provider-level choice. Tests cover the no-fetch reconcile (match serves, context-change mismatch -> INVALID_CONTEXT) and assert the native fetch is never called. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
5a7a06e to
8cce4b5
Compare
de2a6ec to
2d20825
Compare
There was a problem hiding this comment.
Pull request overview
Adds the core JS-side offline feature-flags capability to the React Native SDK by letting a caller load a precomputed configuration into FlagsClient and evaluate flags without any native/network fetch, including strict context matching.
Changes:
- Introduces
FlagsClient.setConfiguration()andFlagsClient.setEvaluationContextWithoutFetching()plus configuration status handling (INVALID_CONTEXT/PROVIDER_NOT_READY). - Adds wire-context normalization + exact context matching utilities for offline/precomputed configs.
- Hardens evaluation-context attribute handling against prototype-key issues and adds targeted unit tests.
Reviewed changes
Copilot reviewed 8 out of 8 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| packages/core/src/flags/types.ts | Adds INVALID_CONTEXT error code for evaluation failures. |
| packages/core/src/flags/internal.ts | Builds processed context attributes via Map + Object.fromEntries to safely handle reserved keys. |
| packages/core/src/flags/FlagsClient.ts | Implements offline configuration loading, context reconciliation without fetching, and new error surfacing. |
| packages/core/src/flags/configuration/index.ts | Exports new context utilities from the configuration module. |
| packages/core/src/flags/configuration/context.ts | Adds normalizeWireContext + contextMatchesConfiguration for context comparison. |
| packages/core/src/flags/configuration/tests/context.test.ts | Tests wire-context normalization and matching semantics. |
| packages/core/src/flags/tests/internal.test.ts | Tests processEvaluationContext handling of non-primitive and __proto__ attributes. |
| packages/core/src/flags/tests/FlagsClient.test.ts | Adds coverage for offline configuration loading, mismatch handling, replacement, and no-fetch context updates. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| setEvaluationContextWithoutFetching = ( | ||
| context: EvaluationContext | ||
| ): void => { | ||
| this.evaluationContext = processEvaluationContext(context); | ||
|
|
||
| // Re-evaluate a loaded offline configuration against the new context. Readiness | ||
| // when no configuration is loaded yet is the provider's concern. | ||
| if (this.loadedConfiguration) { | ||
| this.applyConfiguration(); | ||
| } | ||
| }; |
| ['__proto__']: 'x' | ||
| }); | ||
|
|
||
| // The reserved key is safely discarded; the prototype is untouched. |
| let decoded: Record<string, FlagCacheEntry>; | ||
| try { | ||
| decoded = decodePrecomputedFlags(precomputed.response); | ||
| } catch (error) { | ||
| this.flagsCache = {}; | ||
| this.configurationStatus = 'invalid'; | ||
| if (error instanceof Error) { | ||
| InternalLog.log( | ||
| `Unsupported flags configuration for '${this.clientName}': ${error.message}`, | ||
| SdkVerbosity.WARN | ||
| ); | ||
| } | ||
| return; | ||
| } |
| * Reconcile the loaded configuration against the active evaluation context and | ||
| * (re)compute the servable flag cache and configuration status. | ||
| */ | ||
| private applyConfiguration = (): void => { |
There was a problem hiding this comment.
As mentioned on the docs PR here: #1326 (comment)
Not propagating the Flags configuration to the native SDK underneath effectively means that offline init does not support hybrid apps, as apps that fetch the Flags config from the native side won't have the same configuration as the JS layer. This introduces an inconsistency that at the very least needs to be accounted and acknowledged for, especially if the initial implementation won't rely on any changes coming from the native SDKs.
Offline feature flags — related PRs
FlagsClient.setConfiguration): feat(flags): FlagsClient.setConfiguration + context matching [PR2] (FFL-2688) #1331 ← you are hereSummary
Adds the core offline feature-flag API to
FlagsClient(@datadog/mobile-react-native): loading a supplied configuration and evaluating it with no network request.setConfiguration(config)— loads a precomputed configuration (fromconfigurationFromString) into the flag cache. When no evaluation context is set, it adopts the context embedded in the configuration, so offline evaluation works immediately with no fetch. When a context is already set, the configuration's context must match it.setEvaluationContextWithoutFetching(context)— records the active context and reconciles the loaded configuration against it, with no native request.INVALID_CONTEXT; an empty or unusable configuration reportsPROVIDER_NOT_READY(distinct fromFLAG_NOT_FOUND).Core-only, no native changes: evaluation and per-flag exposure/RUM tracking already run off the JS-provided flag data.
Tests
The
flags/suite passes (53 tests), covering: order-independent load/context (config-first and context-first), match vs. mismatch, empty/unsupported configurations, config replacement, and the no-fetch context update. Lint andtscare clean.