moonopenfeature

    Provider-neutral synchronous OpenFeature SDK core for MoonBit

    openfeature
    feature-flags
    sdk
    provider
    hooks
    Download zip
    Version
    0.1.1
    License
    Apache-2.0
    Last updated
    22 days ago
    Downloads
    31

    No dependencies

    #MoonOpenFeature

    用 MoonBit 实现的可复用、同步、与供应方无关的功能开关 SDK 核心。 应用通过统一接口获取类型化结果、默认值与诊断,并管理上下文、钩子、供应方域和业务事件跟踪,无需在每个调用点绑定某个开关引擎。 开关决策由供应方(Provider)负责;本 SDK 不是规则引擎。

    版本:chenliyi-cly/moonopenfeature@0.1.1。许可证:Apache-2.0。

    #适用场景

    • 多租户后端:为不同域绑定不同的解析供应方,运行时替换某个域的供应方;共享供应方只在最后一个绑定移除后释放。
    • 命令行、嵌入式和 WASM 应用:注入宿主管理的供应方,复用 Boolean、Int64、Double、String、JSON 五类值接口,以及一致的默认值和错误模型。
    • 平台与可观测性集成:通过有序钩子观察开关评估,跟踪业务事件,不自行实现开关规则,也不绑定特定分析厂商。

    三个可运行示例提供完整的下游工作流和断言,无需网络服务或凭据配置。

    #安装与使用

    安装指定版本:

    moon add chenliyi-cly/moonopenfeature@0.1.1

    在应用的 moon.pkg 中导入根包:

    import {
    "chenliyi-cly/moonopenfeature" @feature,
    }

    供应方回调是应用决策与 SDK 之间的集成边界。下面的代码可放入应用入口函数中:

    let sdk = @feature.Sdk::new()
    let provider = @feature.Provider::new("application", (key, kind, default, context) => {
    if key == "checkout-v2" && kind == Boolean {
    @feature.Resolution::success(Boolean(context.targeting_key() == Some("demo")), reason="STATIC")
    } else {
    @feature.Resolution::failure(default, @feature.SdkError::new(FlagNotFound, "unknown key"))
    }
    })
    sdk.set_provider(provider).unwrap()
    let client = sdk.client(name="checkout", version="1")
    client.set_context(@feature.EvaluationContext::empty().with_targeting_key("demo"))
    let result = client.boolean_details("checkout-v2", false)
    println(result.details.to_json().stringify())
    sdk.shutdown().unwrap()

    示例中的简单决策属于应用注入的供应方,不是 SDK 内置规则模型。 克隆仓库后,在仓库根目录运行三个完整示例:

    moon run examples/multitenant --deny-warn moon run examples/typed_provider --deny-warn moon run examples/observability --deny-warn

    #公共 API 与数据约束

    API约束与行为
    FlagValue / FlagType五种精确类型,不隐式转换数值;Int64 序列化为 JSON 时保留精确十进制表示
    EvaluationContext经校验的 JSON 属性与可选 targeting key;不可变的按键覆盖,以及深拷贝访问器
    Resolution / EvaluationDetails包含 reason、variant、错误和标量 metadata;类型不匹配或供应方失败时返回调用者默认值
    Sdk / ClientSDK 实例相互独立;支持默认供应方回退、显式域绑定,以及共享供应方的生命周期管理
    Hook / HookPlanbefore 正序,after/error/finally 逆序;按支持的类型筛选;每个钩子、每次评估使用独立 JSON 暂存数据
    Provider使用 Result 回调与显式生命周期事件;初始化和关闭只执行一次;可选择限制为单域绑定
    TrackingEvent可选有限数值和 JSON 详情;Client.track 记录错误而不抛错,track_checked 是返回 Result 的扩展接口

    上下文覆盖优先级由低到高为:SDK < 显式事务上下文 < 客户端 < 本次调用 < before 钩子。 before 钩子的覆盖只对当前评估有效。业务事件跟踪只合并前四层上下文,不执行评估钩子。

    钩子执行前会冻结本次使用的注册列表和 hints。before 或 after 失败时,继续执行 error 和 finally,并返回默认值。 error/finally 观察者自身的失败只进入诊断,不替换原始结果。 SDK 不捕获回调 panic;回调应通过 Result/Resolution 报告预期错误。

    #供应方生命周期与事件

    没有初始化回调时,注册操作将供应方置为 Ready。 存在初始化回调时,供应方必须自行发出 Ready/Error 事件;返回 Ok 不会自动产生 Ready。 返回 Err 会防御性地更新错误状态,但不会自动合成事件,这是已记录的规范差异。 Ready/Error/Stale 处理器看到的是更新后的状态;配置变更通知不改变就绪状态。 后注册的状态处理器接收当前状态通知,而非历史配置事件。

    客户端是跟随当前域绑定的句柄,不是构造时的供应方快照。 替换供应方不会移除客户端事件处理器,后续评估会使用新供应方。 SDK 在调用用户观察者前冻结事件所属域,避免观察者替换绑定影响当前事件路由。 API 级观察者对每个供应方事件只接收一次通知。 供应方实例可以在同一个 SDK 内由多个域共享,但不支持跨 SDK 共享。

    #本地验证与 CI

    moon info moon fmt --check python3 scripts/verify.py # 三个可执行目标的验证,以及 native 静态检查 python3 scripts/verify.py --native # 同时构建、测试和运行 native,需要本机 C 编译器

    CI 工作流覆盖接口一致性、格式、严格检查、构建、测试,以及三个示例的运行,目标包括 wasm-gc、wasm、js 和 native。 官方 Linux 工具链及核心库下载的 SHA-256 固定在 scripts/toolchain-lock.json 中。 如果官方下载地址中的内容发生变化,校验会失败,必须审查后显式更新锁定信息;滚动下载地址并不等于不可变版本地址。

    本地 Windows 使用 moon 0.1.20261012 / moonc v0.10.15+943464bfa,wasm-gc、wasm、js 各通过 81 项测试。 native 本地仅完成严格静态检查;其构建、测试和示例运行由 Linux CI 验证,不宣称在缺少 C 编译器的本机执行过。 仓库中的公共接口文件由工具生成,不手工维护。 更多约束见架构说明、安全边界和规范符合性矩阵。

    #功能边界与来源

    这是参考 OpenFeature Specification v0.9.0 和 Go SDK v1.19.0 独立重写的 MoonBit 同步动态上下文子集,不是完整符合规范的 SDK,也不是 OpenFeature 官方发行版。 不提供规则/百分比分流、远程传输或配置、认证、厂商适配器、异步生命周期、隐式任务局部事务传播、Datetime 值、静态上下文协调,以及任意类型的钩子数据。 JSON 深度上限为 64,校验边界会拒绝循环结构和非有限数值。 SDK 关闭后不可重新启用;需要重新构造 SDK 才能恢复服务。

    有限范围语义查重记录包含相邻开关引擎,以及 MoonFlagKit 计划中的 OpenFeature 接口。 在已记录检索范围内未发现成熟直接替代,不表示所有公开、私有或未发布项目都已被穷尽检查。 应用可以通过自己的供应方适配已有引擎;本 SDK 不应扩展为这些引擎的重复实现。

    #许可证与归属

    本项目未复制上游实现代码或上游测试夹具。

    BeforeResult

    pub(all) struct BeforeResult {
    context : EvaluationContext
    error : SdkError?
    }

    Client

    pub struct Client {
    // private fields
    }

    Client::add_hook

    fn Client::add_hook(self : Client, hook : Hook) -> Unit

    Client::boolean_details

    fn Client::boolean_details(self : Client, key : String, default : Bool, options? : EvaluationOptions) -> Evaluation[Bool]

    Client::boolean_value

    fn Client::boolean_value(self : Client, key : String, default : Bool, options? : EvaluationOptions) -> Bool

    Client::clear_hooks

    fn Client::clear_hooks(self : Client) -> Unit

    Client::context

    fn Client::context(self : Client) -> EvaluationContext

    Client::evaluate

    fn Client::evaluate(self : Client, key : String, default : FlagValue, options? : EvaluationOptions) -> EvaluationDetails

    Client::float_details

    fn Client::float_details(self : Client, key : String, default : Double, options? : EvaluationOptions) -> Evaluation[Double]

    Client::float_value

    fn Client::float_value(self : Client, key : String, default : Double, options? : EvaluationOptions) -> Double

    Client::hooks

    fn Client::hooks(self : Client) -> Array[Hook]

    Client::integer_details

    fn Client::integer_details(self : Client, key : String, default : Int64, options? : EvaluationOptions) -> Evaluation[Int64]

    Client::integer_value

    fn Client::integer_value(self : Client, key : String, default : Int64, options? : EvaluationOptions) -> Int64

    Client::metadata

    fn Client::metadata(self : Client) -> ClientMetadata

    Client::off

    fn Client::off(self : Client, id : Int) -> Bool

    Client::on

    fn Client::on(self : Client, event_type : EventType, callback : (ProviderEvent) -> Result[Unit, SdkError]) -> Int

    Client::provider_status

    fn Client::provider_status(self : Client) -> ProviderStatus

    Client::set_context

    fn Client::set_context(self : Client, context : EvaluationContext) -> Unit

    Client::string_details

    fn Client::string_details(self : Client, key : String, default : String, options? : EvaluationOptions) -> Evaluation[String]

    Client::string_value

    fn Client::string_value(self : Client, key : String, default : String, options? : EvaluationOptions) -> String

    Client::structure_details

    fn Client::structure_details(self : Client, key : String, default : Json, options? : EvaluationOptions) -> Evaluation[Json]

    Client::structure_value

    fn Client::structure_value(self : Client, key : String, default : Json, options? : EvaluationOptions) -> Json

    Client::track

    fn Client::track(self : Client, event : TrackingEvent, context? : EvaluationContext, transaction_context? : EvaluationContext) -> Unit

    Standard no-throw tracking: optional provider failures are diagnostics, not evaluation failures. Use track_checked when an integration needs a Result.

    Client::track_checked

    fn Client::track_checked(self : Client, event : TrackingEvent, context? : EvaluationContext, transaction_context? : EvaluationContext) -> Result[Unit, SdkError]

    ClientMetadata

    pub(all) struct ClientMetadata {
    name : String
    version : String
    domain : String
    }

    ErrorCode

    pub(all) enum ErrorCode {
    ProviderNotReady
    ProviderFatal
    FlagNotFound
    ParseError
    TypeMismatch
    TargetingKeyMissing
    InvalidContext
    General
    } derive(Eq)

    Standard errors plus a bounded-input extension. Provider errors never escape as exceptions.

    ErrorCode::equal

    fn ErrorCode::equal(ErrorCode, ErrorCode) -> Bool

    ErrorCode::name

    fn ErrorCode::name(self : ErrorCode) -> String

    ErrorCode::not_equal

    fn ErrorCode::not_equal(x : ErrorCode, y : ErrorCode) -> Bool

    ErrorCode::parse

    fn ErrorCode::parse(name : String) -> ErrorCode?

    Evaluation

    pub(all) struct Evaluation[T] {
    value : T
    details : EvaluationDetails
    }

    Typed results retain the shared diagnostic model without casting numeric types.

    Evaluation::is_error

    fn[T] Evaluation::is_error(self : Evaluation[T]) -> Bool

    EvaluationContext

    pub struct EvaluationContext {
    // private fields
    }

    Immutable-by-interface evaluation context. Constructors and accessors copy JSON recursively; neither a caller's map nor a returned map aliases storage.

    EvaluationContext::attributes

    fn EvaluationContext::attributes(self : EvaluationContext) -> Map[String, Json]

    EvaluationContext::copy

    EvaluationContext::empty

    EvaluationContext::from_attributes

    fn EvaluationContext::from_attributes(attributes : Map[String, Json]) -> Result[EvaluationContext, SdkError]

    Keys, including the empty string, are allowed. Validate the whole envelope so to_json() also satisfies the maximum JSON depth of 64.

    EvaluationContext::from_json

    fn EvaluationContext::from_json(value : Json) -> Result[EvaluationContext, SdkError]

    Read an already parsed JSON object. The reserved targetingKey, when present, must be a string (null is not equivalent to absence). Other fields are copied as attributes. No string parsing, coercion or recursive object merging occurs.

    EvaluationContext::get

    fn EvaluationContext::get(self : EvaluationContext, key : String) -> Json?

    EvaluationContext::merge

    Later attributes replace earlier values atomically, without recursive JSON object merging. A present later targeting key wins, including an empty key; an absent later targeting key preserves the earlier key.

    EvaluationContext::targeting_key

    fn EvaluationContext::targeting_key(self : EvaluationContext) -> String?

    EvaluationContext::to_json

    The wire format is a flat object; targetingKey is a reserved wire field. Attribute keys are unrestricted in memory. If an attribute has that spelling and a targeting key is present, the targeting key wins in the wire object. from_json always interprets that field as the targeting key, not an attribute.

    EvaluationContext::with_attribute

    fn EvaluationContext::with_attribute(self : EvaluationContext, key : String, value : Json) -> Result[EvaluationContext, SdkError]

    Validate before copying or modifying anything; failure leaves self intact.

    EvaluationContext::with_targeting_key

    fn EvaluationContext::with_targeting_key(self : EvaluationContext, targeting_key : String) -> EvaluationContext

    A replacement, not an in-place mutation. Empty targeting keys are legal.

    EvaluationContext::without_attribute

    fn EvaluationContext::without_attribute(self : EvaluationContext, key : String) -> EvaluationContext

    EvaluationContext::without_targeting_key

    fn EvaluationContext::without_targeting_key(self : EvaluationContext) -> EvaluationContext

    EvaluationDetails

    pub(all) struct EvaluationDetails {
    flag_key : String
    value : FlagValue
    reason : String
    variant : String?
    error : SdkError?
    metadata : FlagMetadata
    }

    EvaluationDetails::copy

    EvaluationDetails::from_resolution

    fn EvaluationDetails::from_resolution(flag_key : String, resolution : Resolution) -> EvaluationDetails

    EvaluationDetails::is_error

    fn EvaluationDetails::is_error(self : EvaluationDetails) -> Bool

    EvaluationDetails::to_json

    EvaluationOptions

    pub struct EvaluationOptions {
    // private fields
    }

    EvaluationOptions::context

    EvaluationOptions::copy

    EvaluationOptions::hints

    EvaluationOptions::hooks

    EvaluationOptions::new

    fn EvaluationOptions::new(context? : EvaluationContext, transaction_context? : EvaluationContext, hooks? : Array[Hook], hints? : EvaluationContext) -> EvaluationOptions

    EvaluationOptions::transaction_context

    fn EvaluationOptions::transaction_context(self : EvaluationOptions) -> EvaluationContext

    EventBus

    pub struct EventBus {
    // private fields
    }

    A synchronous, ordered event bus. Listener snapshots make changes in callbacks effective for the next event, not partway through the current dispatch.

    EventBus::clear

    fn EventBus::clear(self : EventBus) -> Unit

    EventBus::emit

    fn EventBus::emit(self : EventBus, event : ProviderEvent) -> Unit

    EventBus::listener_count

    fn EventBus::listener_count(self : EventBus) -> Int

    EventBus::new

    fn EventBus::new() -> EventBus

    EventBus::off

    fn EventBus::off(self : EventBus, id : Int) -> Bool

    EventBus::on

    fn EventBus::on(self : EventBus, callback : (ProviderEvent) -> Result[Unit, SdkError], event_type? : EventType?, domain? : String?) -> Int

    EventBus::take_diagnostics

    fn EventBus::take_diagnostics(self : EventBus) -> Array[SdkError]

    EventListener

    type EventListener

    EventType

    pub(all) enum EventType {
    ProviderReady
    ProviderError
    ProviderStale
    ProviderConfigurationChanged
    } derive(Eq)

    EventType::equal

    fn EventType::equal(EventType, EventType) -> Bool

    EventType::matches_status

    fn EventType::matches_status(self : EventType, status : ProviderStatus) -> Bool

    Only events corresponding to the current state receive immediate delivery. Configuration changes are notifications, not a replayable lifecycle state.

    EventType::name

    fn EventType::name(self : EventType) -> String

    EventType::not_equal

    fn EventType::not_equal(x : EventType, y : EventType) -> Bool

    FlagMetadata

    pub struct FlagMetadata {
    // private fields
    }

    Scalar-only flag metadata, separate from structured evaluation context.

    FlagMetadata::copy

    FlagMetadata::empty

    fn FlagMetadata::empty() -> FlagMetadata

    FlagMetadata::from_values

    fn FlagMetadata::from_values(values : Map[String, Json]) -> Result[FlagMetadata, SdkError]

    FlagMetadata::get

    fn FlagMetadata::get(self : FlagMetadata, key : String) -> Json?

    FlagMetadata::merge

    fn FlagMetadata::merge(self : FlagMetadata, later : FlagMetadata) -> FlagMetadata

    FlagMetadata::to_json

    fn FlagMetadata::to_json(self : FlagMetadata) -> Json

    FlagMetadata::values

    fn FlagMetadata::values(self : FlagMetadata) -> Map[String, Json]

    FlagMetadata::with_value

    fn FlagMetadata::with_value(self : FlagMetadata, key : String, value : Json) -> Result[FlagMetadata, SdkError]

    FlagType

    pub(all) enum FlagType {
    Boolean
    Integer
    Float
    String
    Structure
    } derive(Eq)

    The five OpenFeature flag types. Integers remain distinct from floating point.

    FlagType::equal

    fn FlagType::equal(FlagType, FlagType) -> Bool

    FlagType::not_equal

    fn FlagType::not_equal(x : FlagType, y : FlagType) -> Bool

    FlagValue

    pub(all) enum FlagValue {
    Boolean(Bool)
    Integer(Int64)
    Float(Double)
    String(String)
    Structure(Json)
    }

    Structure flags accept JSON objects, arrays and null, not JSON scalars. Raw enum construction does not validate or take ownership of mutable JSON. Providers and callbacks must validate their result before publishing it and use copy() when crossing an ownership boundary.

    FlagValue::as_boolean

    fn FlagValue::as_boolean(self : FlagValue) -> Bool?

    Accessors perform exact type checks, never coercion or numeric narrowing.

    FlagValue::as_float

    fn FlagValue::as_float(self : FlagValue) -> Double?

    FlagValue::as_integer

    fn FlagValue::as_integer(self : FlagValue) -> Int64?

    FlagValue::as_string

    fn FlagValue::as_string(self : FlagValue) -> String?

    FlagValue::as_structure

    fn FlagValue::as_structure(self : FlagValue) -> Json?

    The returned JSON is detached, including nested objects and arrays.

    FlagValue::copy

    fn FlagValue::copy(self : FlagValue) -> FlagValue

    Copy a validated flag, detaching every mutable JSON container. Invalid JSON trees are replaced by null; call validate() to report errors.

    FlagValue::flag_type

    fn FlagValue::flag_type(self : FlagValue) -> FlagType

    FlagValue::to_json

    fn FlagValue::to_json(self : FlagValue) -> Json

    Serialize a validated flag. Int64's original decimal representation avoids loss of precision when JSON is stringified, despite Json's Double payload. This is not a validation API: call validate() before serialization.

    FlagValue::validate

    fn FlagValue::validate(self : FlagValue) -> Result[Unit, SdkError]

    Hook

    pub struct Hook {
    // private fields
    }

    Hook callbacks return errors explicitly. Exceptions/panics from user callbacks are not caught; integrations must use this Result contract.

    Hook::name

    fn Hook::name(self : Hook) -> String

    Hook::new

    fn Hook::new(name : String, types? : Array[FlagType]?, before? : (HookContext, EvaluationContext) -> Result[EvaluationContext?, SdkError], after? : (HookContext, EvaluationDetails, EvaluationContext) -> Result[Unit, SdkError], error? : (HookContext, SdkError, EvaluationContext) -> Result[Unit, SdkError], on_finally? : (HookContext, EvaluationDetails, EvaluationContext) -> Result[Unit, SdkError]) -> Hook

    Hook::supports

    fn Hook::supports(self : Hook, kind : FlagType) -> Bool

    HookContext

    pub(all) struct HookContext {
    flag_key : String
    flag_type : FlagType
    default_value : FlagValue
    evaluation_context : EvaluationContext
    client : ClientMetadata
    provider_name : String
    data : HookData
    }

    HookContext::copy

    fn HookContext::copy(self : HookContext) -> HookContext

    HookContext::with_context

    fn HookContext::with_context(self : HookContext, context : EvaluationContext) -> HookContext

    HookData

    pub struct HookData {
    // private fields
    }

    Per-hook, per-evaluation scratch data. JSON values are validated and copied; unlike immutable hints, this store deliberately survives across hook stages. Arbitrary MoonBit objects are not supported by this portable data contract.

    HookData::clear

    fn HookData::clear(self : HookData) -> Unit

    HookData::get

    fn HookData::get(self : HookData, key : String) -> Json?

    HookData::keys

    fn HookData::keys(self : HookData) -> Array[String]

    HookData::new

    fn HookData::new() -> HookData

    HookData::remove

    fn HookData::remove(self : HookData, key : String) -> Bool

    HookData::set

    fn HookData::set(self : HookData, key : String, value : Json) -> Result[Unit, SdkError]

    HookPlan

    pub struct HookPlan {
    // private fields
    }

    An invocation freezes its hooks before any user callbacks execute.

    HookPlan::after

    fn HookPlan::after(self : HookPlan, context : HookContext, details : EvaluationDetails, hints : EvaluationContext) -> Result[Unit, SdkError]

    HookPlan::before

    fn HookPlan::before(self : HookPlan, context : HookContext, hints : EvaluationContext) -> BeforeResult

    HookPlan::length

    fn HookPlan::length(self : HookPlan) -> Int

    HookPlan::names

    fn HookPlan::names(self : HookPlan) -> Array[String]

    HookPlan::new

    fn HookPlan::new(kind : FlagType, api : Array[Hook], client : Array[Hook], invocation : Array[Hook], provider : Array[Hook]) -> HookPlan

    HookPlan::on_error

    fn HookPlan::on_error(self : HookPlan, context : HookContext, error : SdkError, hints : EvaluationContext) -> Array[SdkError]

    Errors raised by error/finally observers are collected, not recursively propagated into evaluation. One broken observer cannot suppress the others.

    HookPlan::run_finally

    fn HookPlan::run_finally(self : HookPlan, context : HookContext, details : EvaluationDetails, hints : EvaluationContext) -> Array[SdkError]

    Provider

    pub struct Provider {
    // private fields
    }

    Synchronous callback adapter, not a flag decision engine. Lifecycle events are explicit: an initializer must emit_ready itself, even when it returns Ok. Fields are package-visible but hidden from consumers of the package.

    Provider::add_hook

    fn Provider::add_hook(self : Provider, hook : Hook) -> Unit

    Provider::binding_domain

    fn Provider::binding_domain(self : Provider) -> String?

    The first SDK binding domain, available to initialize callbacks. Standalone initialization has no SDK domain; binding records it before callbacks run.

    Provider::emit_configuration_changed

    fn Provider::emit_configuration_changed(self : Provider, flags : Array[String]) -> Unit

    Configuration notifications do not change readiness. Flags are copied by ProviderEvent and EventBus; shutdown suppresses subsequent notifications.

    Provider::emit_error

    fn Provider::emit_error(self : Provider, error : SdkError) -> Unit

    Provider::emit_ready

    fn Provider::emit_ready(self : Provider) -> Unit

    Fatal and Shutdown are terminal. Recoverable Error and Stale may become Ready again. Repeated Ready is ignored rather than emitting a duplicate.

    Provider::emit_stale

    fn Provider::emit_stale(self : Provider, message : String) -> Unit

    Staleness cannot recover Fatal or Shutdown. Repeat notifications in Stale are allowed so a changed diagnostic message can be delivered.

    Provider::events

    fn Provider::events(self : Provider) -> EventBus

    Return the same bus so consumers may register and remove observers.

    Provider::hooks

    fn Provider::hooks(self : Provider) -> Array[Hook]

    The registration list is detached; immutable Hook callbacks are shared.

    Provider::initialize

    fn Provider::initialize(self : Provider, context : EvaluationContext) -> Result[Unit, SdkError]

    At most one attempt, including failed and reentrant attempts. Set the guard before entering user code. Only an absent callback synthesizes Ready. Returned errors update state without inventing an event. Terminal state set inside a callback cannot be undone by its eventual return value.

    Provider::is_domain_scoped

    fn Provider::is_domain_scoped(self : Provider) -> Bool

    Provider::is_initialized

    fn Provider::is_initialized(self : Provider) -> Bool

    Records an initialization attempt, not readiness or successful completion.

    Provider::name

    fn Provider::name(self : Provider) -> String

    Provider::new

    fn Provider::new(name : String, resolver : (String, FlagType, FlagValue, EvaluationContext) -> Resolution, domain_scoped? : Bool, initialize? : (Provider, EvaluationContext) -> Result[Unit, SdkError]?, shutdown? : (Provider) -> Result[Unit, SdkError]?, track? : (TrackingEvent, EvaluationContext) -> Result[Unit, SdkError]?, context_changed? : (Provider, EvaluationContext, EvaluationContext) -> Result[Unit, SdkError]?) -> Provider

    Provider::on_context_changed

    fn Provider::on_context_changed(self : Provider, old : EvaluationContext, new : EvaluationContext) -> Result[Unit, SdkError]

    Context notifications likewise leave state/events to the callback. Both contexts are independently snapshotted, even when old and new are identical.

    Provider::resolve

    fn Provider::resolve(self : Provider, key : String, kind : FlagType, default : FlagValue, context : EvaluationContext) -> Resolution

    Default validation belongs to the caller. The resolver sees snapshots; checked validates its output and detaches mutable values and metadata.

    Provider::shutdown

    fn Provider::shutdown(self : Provider) -> Result[Unit, SdkError]

    Terminal state is committed before the callback. Reentrant or repeated shutdown is a successful no-op, including after a callback returns Err. There is no invented shutdown event in the OpenFeature event vocabulary.

    Provider::status

    fn Provider::status(self : Provider) -> ProviderStatus

    Provider::track

    fn Provider::track(self : Provider, event : TrackingEvent, context : EvaluationContext) -> Result[Unit, SdkError]

    Tracking is an independent optional callback, not flag evaluation: it is not gated on readiness and does not synthesize lifecycle state or events.

    ProviderEvent

    pub(all) struct ProviderEvent {
    event_type : EventType
    provider_name : String
    domain : String?
    message : String
    error : SdkError?
    flags : Array[String]
    }

    ProviderEvent::copy

    ProviderEvent::new

    fn ProviderEvent::new(event_type : EventType, provider_name : String, domain? : String?, message? : String, error? : SdkError?, flags? : Array[String]) -> ProviderEvent

    ProviderStatus

    pub(all) enum ProviderStatus {
    NotReady
    Ready
    Stale
    Error
    Fatal
    Shutdown
    } derive(Eq)

    Lifecycle states are deliberately distinct from the last evaluation error.

    ProviderStatus::equal

    ProviderStatus::evaluation_error

    fn ProviderStatus::evaluation_error(self : ProviderStatus) -> SdkError?

    ProviderStatus::name

    fn ProviderStatus::name(self : ProviderStatus) -> String

    ProviderStatus::not_equal

    fn ProviderStatus::not_equal(x : ProviderStatus, y : ProviderStatus) -> Bool

    Resolution

    pub(all) struct Resolution {
    value : FlagValue
    reason : String
    variant : String?
    error : SdkError?
    metadata : FlagMetadata
    }

    Providers return this transport-independent resolution; the client validates its value and substitutes the caller default on every error path.

    Resolution::checked

    fn Resolution::checked(self : Resolution, expected : FlagType, default : FlagValue) -> Resolution

    Resolution::failure

    fn Resolution::failure(default : FlagValue, error : SdkError) -> Resolution

    Resolution::success

    fn Resolution::success(value : FlagValue, reason? : String, variant? : String?, metadata? : FlagMetadata) -> Resolution

    Sdk

    pub struct Sdk {
    // private fields
    }

    SDK instances are independent. They do not consult process-global configuration.

    Sdk::add_hook

    fn Sdk::add_hook(self : Sdk, hook : Hook) -> Unit

    Sdk::clear_domain

    fn Sdk::clear_domain(self : Sdk, domain : String) -> Result[Unit, SdkError]

    Sdk::clear_hooks

    fn Sdk::clear_hooks(self : Sdk) -> Unit

    Sdk::client

    fn Sdk::client(self : Sdk, domain? : String, name? : String, version? : String) -> Client

    Clients are live domain handles, not snapshots of the provider at construction.

    Sdk::context

    fn Sdk::context(self : Sdk) -> EvaluationContext

    Sdk::events

    fn Sdk::events(self : Sdk) -> EventBus

    Sdk::hooks

    fn Sdk::hooks(self : Sdk) -> Array[Hook]

    Sdk::is_closed

    fn Sdk::is_closed(self : Sdk) -> Bool

    Sdk::new

    fn Sdk::new() -> Sdk

    Sdk::off

    fn Sdk::off(self : Sdk, id : Int) -> Bool

    Sdk::on

    fn Sdk::on(self : Sdk, event_type : EventType, callback : (ProviderEvent) -> Result[Unit, SdkError]) -> Int

    API-wide observers receive each provider event once, unlike the detailed domain fan-out bus returned by events(). Late state replay uses the default provider; configuration notifications are never replayed.

    Sdk::provider

    fn Sdk::provider(self : Sdk, domain? : String) -> Provider

    Sdk::provider_status

    fn Sdk::provider_status(self : Sdk, domain? : String) -> ProviderStatus

    Sdk::registered_domains

    fn Sdk::registered_domains(self : Sdk) -> Array[String]

    Sdk::set_context

    fn Sdk::set_context(self : Sdk, context : EvaluationContext) -> Result[Unit, SdkError]

    Sdk::set_provider

    fn Sdk::set_provider(self : Sdk, provider : Provider, domain? : String) -> Result[Unit, SdkError]

    Replacing a domain never shuts down a provider still used by another domain. Failed initialization leaves the new binding's error/not-ready state observable.

    Sdk::shutdown

    fn Sdk::shutdown(self : Sdk) -> Result[Unit, SdkError]

    Sdk::take_diagnostics

    fn Sdk::take_diagnostics(self : Sdk) -> Array[SdkError]

    SdkError

    pub(all) struct SdkError {
    code : ErrorCode
    message : String
    } derive(Eq)

    SdkError::equal

    fn SdkError::equal(SdkError, SdkError) -> Bool

    SdkError::new

    fn SdkError::new(code : ErrorCode, message : String) -> SdkError

    SdkError::not_equal

    fn SdkError::not_equal(x : SdkError, y : SdkError) -> Bool

    SdkError::to_json

    fn SdkError::to_json(self : SdkError) -> Json

    StandardReason

    pub(all) enum StandardReason {
    Static
    Default
    TargetingMatch
    Split
    Cached
    Disabled
    Unknown
    Stale
    Error
    } derive(Eq)

    Reasons are open strings in OpenFeature. These helpers name standard reasons without discarding provider-defined extensions.

    StandardReason::equal

    StandardReason::name

    fn StandardReason::name(self : StandardReason) -> String

    StandardReason::not_equal

    fn StandardReason::not_equal(x : StandardReason, y : StandardReason) -> Bool

    StandardReason::parse

    fn StandardReason::parse(name : String) -> StandardReason?

    TrackingEvent

    pub(all) struct TrackingEvent {
    key : String
    value : Double?
    details : EvaluationContext
    }

    A tracking payload. The nonempty key constraint is a local safety extension to the specification. Public field construction bypasses constructor checks; use new for validated events and copy at callback ownership boundaries.

    TrackingEvent::copy

    TrackingEvent::new

    fn TrackingEvent::new(key : String, value? : Double?, details? : EvaluationContext) -> Result[TrackingEvent, SdkError]

    TrackingEvent::to_json

    fn TrackingEvent::to_json(self : TrackingEvent) -> Json

    Serialize a validated payload; absent value is omitted, not null. Context JSON preserves its targeting key and snapshots nested detail containers.

    TrackingEvent::validate

    fn TrackingEvent::validate(self : TrackingEvent) -> Result[Unit, SdkError]

    Validate raw public construction as well as constructor-created events. Providers must not receive non-finite measures through a bypassed factory.

    copy_json

    fn copy_json(value : Json) -> Json

    Deep-copy a validated JSON tree, preserving numeric repr and all keys. Validate first when errors must be reported. Invalid inputs fail closed to null rather than recursing forever or returning an aliased mutable value.

    validate_json

    fn validate_json(value : Json) -> Result[Unit, SdkError]

    Validate an in-memory JSON tree, not JSON text. The root has depth zero; values reached by at most 64 object/array edges are accepted. Cycles are checked by container identity on the active path, so shared acyclic subtrees remain legal. Non-finite numbers and malformed numeric repr are rejected.