Skip to content

Add runtime feature flag support#72

Merged
stanleyphu merged 3 commits into
mainfrom
feat/ff-runtime-client-helper
Jun 23, 2026
Merged

Add runtime feature flag support#72
stanleyphu merged 3 commits into
mainfrom
feat/ff-runtime-client-helper

Conversation

@stanleyphu

@stanleyphu stanleyphu commented Jun 22, 2026

Copy link
Copy Markdown
Contributor

Summary

  • add a shared getFeatureFlagsRuntimeClient helper for the WorkOS Feature Flags runtime client
  • allow authkitLoader to populate auth.featureFlags from a configured runtime client
  • add onFeatureFlagsError so applications can report runtime evaluation failures before falling back to JWT feature_flags
  • document token vs runtime-client feature flag source behavior
  • bump @workos-inc/node minimum to ^8.13.0 for runtime-client APIs

Test plan

  • npm run prettier
  • ./node_modules/.bin/prettier README.md --check
  • npm run lint
  • npm run typecheck
  • npm test
  • npm run build

@greptile-apps

greptile-apps Bot commented Jun 22, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR adds runtime feature flag support to authkitLoader so applications can evaluate flags server-side using the WorkOS Feature Flags runtime client instead of relying solely on the feature_flags JWT claim. It also introduces onFeatureFlagsError for production-safe error reporting when the runtime client fails, with automatic fallback to the JWT claim.

  • getFeatureFlagsRuntimeClient — new module-level singleton (via the generalized lazy helper) that creates one FeatureFlagsRuntimeClient per server process; the @workos-inc/node minimum is bumped to ^8.13.0 for the required APIs.
  • getFeatureFlags in session.ts — new async helper called unconditionally after claims extraction; when a runtime client is configured it calls waitUntilReady (optional) then getAllFlags, filters to enabled flags, and falls back to JWT flags on any error while invoking onFeatureFlagsError and/or a debug warn.
  • Tests — four new scenarios cover the happy path, waitUntilReady failure, getAllFlags failure, and debug-mode logging, with proper module isolation in feature-flags.spec.ts.

Confidence Score: 5/5

Safe to merge — the new runtime flag evaluation path has clean fallback semantics and is well-tested, with no changes to the existing auth or session refresh flows.

The feature flag evaluation is fully opt-in, isolated to a new getFeatureFlags helper, and always falls back to the JWT claim on error. Existing session handling is untouched. The test suite covers the happy path, both error modes (waitUntilReady and getAllFlags failures), and debug-mode logging.

No files require special attention.

Important Files Changed

Filename Overview
src/feature-flags.ts New file: exports a module-level singleton getFeatureFlagsRuntimeClient using the existing lazy helper; straightforward and correct.
src/session.ts Adds getFeatureFlags helper with onFeatureFlagsError callback and debug warn; error handling, fallback, and ordering are correct. One // istanbul ignore next comment is now redundant since a test directly covers that branch.
src/utils.ts Generalizes lazy to accept and forward arguments on the first call; subsequent calls return the cached result regardless of arguments. Correct and well-tested.
src/interfaces.ts Adds FeatureFlagsErrorOptions and AuthKitFeatureFlagsOptions interfaces, and extends AuthKitLoaderOptions with featureFlags and onFeatureFlagsError; types are sound.
src/feature-flags.spec.ts Tests singleton memoization for getFeatureFlagsRuntimeClient; uses jest.resetModules and dynamic import to avoid cross-test pollution, which is the right approach.
src/session.spec.ts Adds four new scenarios: runtime-client happy path, waitUntilReady failure fallback, getAllFlags failure fallback, and debug-mode warn. Coverage is thorough for the new code paths.
src/utils.spec.ts New test verifies that arguments are forwarded only on the first call and the cached result is returned on subsequent calls; correctly documents lazy semantics.
src/index.ts Re-exports getFeatureFlagsRuntimeClient from the new module; no issues.

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
    participant Route as Route Loader
    participant AL as authkitLoader
    participant GFF as getFeatureFlags
    participant RC as FeatureFlagsRuntimeClient
    participant CB as onFeatureFlagsError

    Route->>AL: "authkitLoader(args, { featureFlags, onFeatureFlagsError })"
    AL->>AL: getClaimsFromAccessToken to tokenFeatureFlags
    AL->>GFF: "getFeatureFlags({ options, tokenFeatureFlags, user, ... })"

    alt featureFlags option not set
        GFF-->>AL: return tokenFeatureFlags (JWT claims)
    else featureFlags.runtimeClient configured
        alt waitUntilReady enabled
            GFF->>RC: waitUntilReady(timeoutMs?)
        end
        GFF->>RC: "getAllFlags({ userId, organizationId })"
        RC-->>GFF: Record of string to boolean
        GFF->>GFF: "filter enabled=true to string[]"
        GFF-->>AL: return runtimeFeatureFlags

        note over GFF,RC: On any error in try block
        GFF->>CB: "onFeatureFlagsError({ error, request, user, ... })"
        opt debug: true
            GFF->>GFF: console.warn(fallback message)
        end
        GFF-->>AL: return tokenFeatureFlags (fallback)
    end

    AL-->>Route: auth.featureFlags populated
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
    participant Route as Route Loader
    participant AL as authkitLoader
    participant GFF as getFeatureFlags
    participant RC as FeatureFlagsRuntimeClient
    participant CB as onFeatureFlagsError

    Route->>AL: "authkitLoader(args, { featureFlags, onFeatureFlagsError })"
    AL->>AL: getClaimsFromAccessToken to tokenFeatureFlags
    AL->>GFF: "getFeatureFlags({ options, tokenFeatureFlags, user, ... })"

    alt featureFlags option not set
        GFF-->>AL: return tokenFeatureFlags (JWT claims)
    else featureFlags.runtimeClient configured
        alt waitUntilReady enabled
            GFF->>RC: waitUntilReady(timeoutMs?)
        end
        GFF->>RC: "getAllFlags({ userId, organizationId })"
        RC-->>GFF: Record of string to boolean
        GFF->>GFF: "filter enabled=true to string[]"
        GFF-->>AL: return runtimeFeatureFlags

        note over GFF,RC: On any error in try block
        GFF->>CB: "onFeatureFlagsError({ error, request, user, ... })"
        opt debug: true
            GFF->>GFF: console.warn(fallback message)
        end
        GFF-->>AL: return tokenFeatureFlags (fallback)
    end

    AL-->>Route: auth.featureFlags populated
Loading

Reviews (2): Last reviewed commit: "Add feature flag error hook" | Re-trigger Greptile

Comment thread src/session.ts
Comment thread src/session.spec.ts Outdated
@stanleyphu stanleyphu merged commit 60d3197 into main Jun 23, 2026
8 checks passed
@stanleyphu stanleyphu deleted the feat/ff-runtime-client-helper branch June 23, 2026 16:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

2 participants