Skip to content

Releases: quonfig/sdk-javascript

v1.4.0

Choose a tag to compare

@jdwyah jdwyah released this 09 Oct 19:52

1.4.0 - 2026-10-09

  • Fix: a failed poll tick is no longer an unhandled promise rejection. During an outage with no
    last-known-good entry, every tick rejected with no handler: Node exits on the first one, and
    browsers report each to error trackers. The loop now keeps the current config, warns once per
    outage, and keeps polling.
  • Fix: one malformed evaluation no longer hangs init() or stops polling. A bad entry (a json
    value sent as a string, or a null value) made the whole payload fail inside the loader, which then
    never settled. Each key is now decoded on its own: the bad key returns undefined and
    getDetails reports reason: "ERROR", errorCode: "TYPE_MISMATCH" (the same path as a malformed
    duration), and the other keys load. A load also always settles if installing a result throws.
  • Fix: the per-leg fetch timeout covers the response body. It was cleared when headers
    arrived, so a server that sent headers and then stalled hung the load with no deadline.
  • Fix: a load replaced by updateContext() can no longer install the previous context's values
    (qfg-goi1.2.7).
    When updateContext() raced an in-flight refresh or poll tick, a leg of the
    older load that still completed was installed under the old context, so the client could serve
    user A's flags while contexts said user B. The aborted older load also counted as a primary
    failure and fired the secondary leg, and its hedge timer kept running. A replaced load now does
    nothing: no install, no secondary leg, no last-known-good, and its promise settles with the load
    that replaced it instead of rejecting with the abort.
  • Fix: updateContext(ctx, true) followed by poll() installs the new context's values
    (qfg-goi1.2.48).
    skipLoad changed the context but not the one the loader fetched for, and
    poll() did not sync it, so the first poll fetch was for the previous context and was installed,
    and written to the last-known-good cache, as the new one's. At the same generation the later,
    correct fetches were dropped as "not newer", so user B saw user A's flags until the next publish,
    and an offline reload for B served A's values as stale. poll() and updateContext(ctx, true)
    now point the loader at the current context, and every leg of a load (and its last-known-good
    fallback) is for the context the load started with.
  • Fix: close() or stopPolling() during the first poll() fetch no longer restarts polling.
    A second poll() call also no longer leaves two loops running.
  • Fix: config keys that match Object.prototype names (for example constructor) are no longer
    found when absent; getDetails reports FLAG_NOT_FOUND.
  • Fix: base64Encode (used for the context in the fetch URL) is UTF-8 in every runtime. Without
    TextEncoder (React Native) it produced Latin-1 for accented text and threw above U+00FF; without
    window (Web/Service Workers, Cloudflare Workers) it threw. Output on modern browsers and Node is
    unchanged.
  • Fix: importing the SDK no longer requires crypto.getRandomValues. The telemetry instance id
    is created on first use and falls back to a non-cryptographic random id.
  • Fix: the last-known-good cache keeps one entry per SDK key. It stored one localStorage entry
    per distinct context and never removed any, which could fill the origin's quota (breaking the
    app's own localStorage writes) and kept the raw context in the key. The entry is now
    quonfig.lkg.v2:<sdkKey> with a hash of the context, served only for the same context. Old
    quonfig.lkg.v1:<sdkKey>:* entries are removed on the first write.
  • Change: shouldLog({loggerPath}) no longer adds quonfig-sdk-logging to the client context.
    It changed every later fetch's context, which let an older config generation replace a newer one
    and changed the inputs to every flag. The browser evaluates server-side once per fetched context,
    so the logger path never selected per-logger rules. Logger paths from the browser no longer appear
    in example contexts.
  • Packaging: removed the module field (it named dist/index.mjs, which was never built;
    bundlers fall back to main) and the lint script (eslint is not installed).

v1.3.0

Choose a tag to compare

@jdwyah jdwyah released this 25 Sep 20:20
  • Telemetry transport policy (qfg-y8je.11). Telemetry POSTs get their own 10s deadline
    (telemetryTimeoutMs); the eval-fetch timeout option no longer applies to telemetry. A failed
    batch (timeout, network error, 408, 429, 5xx) is kept in memory for the life of the page and
    resent byte-for-byte (never merged, so the server dedups a resend of a batch that did land), no
    sooner than 30s after the failure and after any readable Retry-After (up to 10 min). The
    retained queue is capped at 5 batches / 512KB / 5 min (oldest dropped; a batch larger than the
    byte cap is never kept). At most one POST is in flight. 401/403/404 disable telemetry for the page
    with one error; any other 4xx drops that batch with one error. Logging: a failed POST logs at
    debug, one console.warn when data is actually dropped (then a summary at most every 10 min), one
    console.info on recovery. Before this, every non-2xx logged a warning and every failed batch was
    lost.
  • Fix: parallel telemetry POSTs shared one abort timer, so one request finishing cleared the
    other's deadline (it could then hang with no timeout). Each request now owns its timer, cleared
    when it settles.
  • Fix: a telemetry network error was an unhandled promise rejection. It is now caught and
    treated as retryable.
  • Fix: close() left the telemetry timer armed when the final POST failed (it rejected before
    stopping timers). close() now stops polling and the telemetry timer first, sends the current
    window once with a 2s deadline and keepalive, never rejects, and does not resend kept batches.
  • pagehide replaces beforeunload for the final flush: the live window is sent once with
    fetch(..., { keepalive: true }) and a 2s deadline without blocking unload. close() removes the
    listener.
  • Flush interval is now an option (telemetryFlushIntervalMs, default 30s, unchanged).
  • Memory cap: evaluation-summary keys per window drop from 100,000 to 10,000
    (telemetryMaxEvaluationSummaries), the uniform cap across SDKs; a key already in the window
    keeps counting at the cap.
  • New options: telemetryFlushIntervalMs, telemetryTimeoutMs, telemetryMaxRetainedBatches,
    telemetryMaxRetainedBytes, telemetryMaxRetainedAgeMs, telemetryMaxEvaluationSummaries.
    Invalid values (non-finite or <= 0) fall back to the default.
  • TelemetryUploader (the telemetryUploader accessor) gains send(); post() keeps its behavior
    with a per-request deadline, and clearAbortTimeout() is a deprecated no-op. No wire change, no
    removed public API, no new dependencies.

v1.2.1

Choose a tag to compare

@jdwyah jdwyah released this 24 Jul 19:35
  • Fix: eval-summary telemetry counters now report reason as the numeric wire code (1=STATIC 2=TARGETING_MATCH 3=SPLIT), matching the backend SDKs. Previously the counter carried the OpenFeature-style string form (e.g. "TARGETING_MATCH"), which the telemetry ingestion API's stricter validation could reject — evaluation-summary batches containing a rule-matched evaluation could be dropped as a result. The ingestion service now also accepts and normalizes the string form, so existing 0.0.16–1.2.0 clients work without upgrading; this release aligns the SDK with the canonical wire contract. Counters without a reason are unchanged (field omitted). No API changes.

v1.2.0

Choose a tag to compare

@jdwyah jdwyah released this 08 Jul 15:18
  • init() warns when an explicit apiUrls disables failover. The default (and every domain /
    QUONFIG_DOMAIN-derived) API-URL list carries BOTH a primary and a secondary leg, and the SDK
    hedges/fails over between them (§5e). An explicit apiUrls (or the singular apiUrl alias)
    replaces that list wholesale, so a single-entry override silently dropped the secondary and
    disabled automatic failover. init() now logs a one-line WARN pointing the caller at the fix
    (pass both a primary and a secondary URL). The default two-leg list never warns. Diagnostic only;
    no behavior change, no new dependencies. New README Failover & QUONFIG_DOMAIN section documents
    the URL derivation and the failover model.
  • init() warns when timeout <= hedgeDelay. The per-leg fetch timeout must stay above
    hedgeDelay (default 2000ms): if it doesn't, the primary leg is aborted before the hedge timer
    can fire, so the parallel hedge (§5e) silently degrades to error-only sequential failover — the
    secondary is only contacted after the primary fully times out, never concurrently with a
    still-alive-but-slow primary. init() now logs a clear warning naming both effective values, and
    the timeout / hedgeDelay option jsdoc documents the invariant. Diagnostic only; no behavior
    change.
  • Telemetry upload timeout restored to 10s. 1.1.0's hedge lowered the shared per-request timeout
    to 3s for the eval fetch, which also silently clipped background telemetry POSTs from 10s to 3s
    (they shared one DEFAULT_TIMEOUT). Telemetry now uses its own TELEMETRY_TIMEOUT (10s), so a
    slow-but-alive telemetry endpoint no longer drops shape/example data that the eval-latency budget
    was never meant to govern. No API change; the timeout init option still overrides it.

v1.1.0

Choose a tag to compare

@jdwyah jdwyah released this 01 Jul 18:31

Secondary-delivery failover release (qfg-7h5d.2). See CHANGELOG.md: parallel hedge (loader.ts), reject-older install guard via Meta.generation incl. the gen<=0 carve-out (defensive), last-known-good localStorage cache. Peer ranges kept at >=1.0.0. Backward-compatible minor.

v1.0.0

Choose a tag to compare

@jdwyah jdwyah released this 06 Jun 18:57

Stable 1.0.0 release. No API or behavior changes from 0.0.18 — coordinated 1.0.0 version stamp across the entire Quonfig SDK family.

v0.0.18

Choose a tag to compare

@jdwyah jdwyah released this 05 Jun 14:33
  • Conditional polling: the loader now sends If-None-Match on repeat eval-with-context polls and
    honors a 304 Not Modified by keeping the cached evaluations instead of re-downloading the full
    payload. The ETag is stored per-request-URL (which embeds the encoded context) and LRU-bounded to
    16 entries, so a context switch can never replay a stale ETag. Steady-state polling collapses to a
    304 when neither the workspace version nor the context has changed (qfg-iikt).
  • Fix: a 304 now returns the payload cached for that exact context, so the updateContext(A) →
    updateContext(B) → updateContext(A) pattern can no longer leave the previous context's
    evaluations in the single shared config slot and serve the wrong context's values (qfg-iikt).
  • Fix: polling now starts and self-heals even when the very first poll fetch rejects (a startup
    network blip with both primary and secondary briefly unreachable). Previously a rejected bootstrap
    fetch left polling permanently dead — config frozen with no recovery after connectivity returned.
    The recurring loop is now scheduled regardless of the first fetch's outcome, matching the
    steady-state loop's own resilience (qfg-8uw5).

v0.0.17

Choose a tag to compare

@jdwyah jdwyah released this 21 May 17:46
  • Breaking (typing-level): removed the collectLoggerNames init option and its internal
    LoggerAggregator. The server-side telemetry pipeline never consumed the logger-name event (no
    schema entry, no flatten branch, no ClickHouse table), so this was dead client-side cost.
    TypeScript callers passing collectLoggerNames: true | false will get a type error — drop the
    field (qfg-o2fk). Logger-level evaluation via shouldLog({loggerPath, ...}) is unchanged; logger
    paths still flow to the dashboard through the existing example-context telemetry.

v0.0.16

Choose a tag to compare

@jdwyah jdwyah released this 10 May 15:01
  • Added getDetails<T> accessor that returns the resolved value alongside the evaluation reason,
    variant, and flagMetadata for richer client-side telemetry and debugging (qfg-ez8e).
  • Declared engines.node >= 20.9.0 and added a CI matrix to match the supported Node floor across
    the Quonfig SDK family (qfg-y7xh).
  • Added Prettier as the repo formatter and wired up a CI gate so unformatted commits fail the push.