Skip to content

[Multi-provider] Gaps identified relative to js-sdk reference implementation #109

@jonathannorris

Description

@jonathannorris

Context

We conducted a cross-SDK comparison of all MultiProvider implementations using the js-sdk as the reference. The Swift MultiProvider is functional for basic use cases, but we identified several gaps relative to the reference implementation.

Note: Some gaps (like shutdown propagation) are blocked by SDK-level prerequisites that are tracked separately (#97).

Gaps

1. Aggregate event/status tracking (High)

The MultiProvider merges child provider event streams via Publishers.MergeMany, which forwards raw events directly to subscribers with no aggregation. If one child provider emits .error while another is .ready, both events pass through independently and there is no composite status. Consumers have no way to know the overall health of the MultiProvider.

Expected behavior:

  • Maintain a per-provider status map
  • Compute an aggregate status using "worst-wins" precedence: FATAL > NOT_READY > ERROR > STALE > RECONCILING > READY
  • Only emit an event when the aggregate status actually changes (deduplication)
  • Always forward .configurationChanged events as a pass-through regardless of status changes

Reference: js-sdk status-tracker.ts, dotnet-sdk HandleProviderEventAsync / DetermineAggregateStatus, kotlin-sdk handleProviderEvent / calculateAggregateStatus

2. Per-provider hook execution during evaluation (High)

The hooks property returns an empty array. The strategy calls provider evaluation methods directly, bypassing any hooks the child providers may define. If a child provider returns hooks via its hooks property, those hooks are not executed.

Expected behavior:

  • Before evaluating a child provider, run its before hooks with an isolated copy of the hook context
  • On success: run after hooks
  • On error: run error hooks
  • Always: run finally hooks
  • Hook context must be isolated per-provider to prevent cross-provider mutation

Reference: js-sdk hook-executor.ts, go-sdk isolation.go, dotnet-sdk ProviderExtensions.EvaluateAsync

3. Tracking event forwarding (High)

The MultiProvider relies on the default no-op track() extension on FeatureProvider. Tracking events are not forwarded to child providers.

Expected behavior:

  • Iterate over child providers and forward track() calls
  • Skip providers that are not in a ready/active state
  • Errors from individual track() calls should be caught and logged, not propagated

Reference: js-sdk multi-provider.ts track(), dotnet-sdk MultiProvider.cs Track()

4. ComparisonStrategy (Medium)

Only FirstMatchStrategy and FirstSuccessfulStrategy exist. There is no ComparisonStrategy for evaluating all providers and comparing results (useful for migration validation and consistency checks).

Expected behavior:

  • Evaluate all providers (ideally in parallel)
  • If all providers agree on the value, return it
  • If providers disagree, call an optional onMismatch callback and return the designated fallback provider's result
  • If any provider errors, collect and report all errors
  • Constructor accepts a fallbackProvider and optional onMismatch callback

Reference: js-sdk comparison-strategy.ts, go-sdk comparison_strategy.go, dotnet-sdk ComparisonStrategy.cs

5. Unique provider naming with deduplication (Low)

There is no unique naming or deduplication for child providers. If two providers share the same metadata name, there is no disambiguation. This affects event attribution and debugging.

Expected behavior:

  • If multiple providers share the same metadata-derived name, auto-deduplicate with a numeric suffix (name-1, name-2, etc.)
  • Expose child provider metadata keyed by unique name

Reference: js-sdk registerProviders(), dotnet-sdk RegisterProviders(), kotlin-sdk toChildFeatureProviders()

Blocked / Deferred

  • Shutdown propagation: Requires SDK-level shutdown support first (Add Shutdown #97)

Spec Reference

https://openfeature.dev/specification/appendix-a/#multi-provider

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions