Skip to content

feat(flags): FlagsClient.setConfiguration + context matching [PR2] (FFL-2688)#1331

Open
btthomas wants to merge 3 commits into
blake.thomas/FFL-2666-PR1from
blake.thomas/FFL-2666-PR2
Open

feat(flags): FlagsClient.setConfiguration + context matching [PR2] (FFL-2688)#1331
btthomas wants to merge 3 commits into
blake.thomas/FFL-2666-PR1from
blake.thomas/FFL-2666-PR2

Conversation

@btthomas

@btthomas btthomas commented Jul 7, 2026

Copy link
Copy Markdown

Offline feature flags — related PRs


Summary

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 (from configurationFromString) 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.
  • Context matching normalizes the wire context through the same processing as the active context before comparing; a configuration with no embedded context is context-agnostic and matches any context.
  • Failure modes: a context mismatch serves no values and reports INVALID_CONTEXT; an empty or unusable configuration reports PROVIDER_NOT_READY (distinct from FLAG_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 and tsc are clean.

btthomas and others added 3 commits July 7, 2026 20:55
…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>
@btthomas btthomas force-pushed the blake.thomas/FFL-2666-PR2 branch from 5a7a06e to 8cce4b5 Compare July 8, 2026 00:55
@btthomas btthomas force-pushed the blake.thomas/FFL-2666-PR1 branch from de2a6ec to 2d20825 Compare July 8, 2026 00:56
@btthomas btthomas marked this pull request as ready for review July 8, 2026 01:33
@btthomas btthomas requested a review from a team as a code owner July 8, 2026 01:33
@aarsilv aarsilv requested a review from Copilot July 8, 2026 04:39

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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() and FlagsClient.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.

Comment on lines +125 to +135
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.
Comment on lines +185 to +198
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 => {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants