Skip to content

feat: support for asynchronous initialization#122

Merged
cre8ivejp merged 22 commits into
masterfrom
feat/async-init
Oct 21, 2025
Merged

feat: support for asynchronous initialization#122
cre8ivejp merged 22 commits into
masterfrom
feat/async-init

Conversation

@duyhungtnn
Copy link
Copy Markdown
Collaborator

@duyhungtnn duyhungtnn commented Aug 29, 2025

Add Asynchronous Initialization Support to Bucketeer SDK Client

This pull request introduces asynchronous initialization support for the Bucketeer SDK client, solving a critical reliability issue during startup.

Problem Solved

Previously, when creating a Bucketeer client with initializeBKTClient() and enabling the local cache, we had no way to determine when the SDK was actually ready to serve accurate feature flag evaluations. The client would return immediately, but feature flag and segment data might still be loading from the server in the background, leading to:

  • Inconsistent evaluation results during startup
  • No way to handle initialization failures gracefully
  • Race conditions between client usage and data loading

Solution

Added a new waitForInitialization() method to the Bucketeer client interface that allows applications to:

  • Wait for readiness: Ensure all cache processors have completed their initial data sync before proceeding
  • Handle timeouts gracefully: Set maximum wait times and handle cases where initialization takes longer than expected
  • Catch initialization failures: Detect and respond to network or server errors during startup

Usage Pattern

const client = initializeBKTClient(config);

try {
  // Wait up to 5 seconds for SDK to be ready
  await client.waitForInitialization({ timeoutMs: 5000 });
  // Now safe to perform feature flag evaluations
  const result = await client.booleanVariation(user, 'feature-flag', false);
} catch (error) {
  if (error.message.includes('timeout')) {
    console.warn('SDK initialization is taking longer than expected, but may still succeed');
    // SDK can still be used, but initial cache sync is not yet complete
  } else {
    console.error('SDK initialization failed:', error.message);
  }
}

@duyhungtnn duyhungtnn self-assigned this Aug 29, 2025
@duyhungtnn
Copy link
Copy Markdown
Collaborator Author

duyhungtnn commented Aug 29, 2025

Why a new method waitForInitialization() Instead of Promise<Bucketeer> with the method initializeBKTClient()?

  1. Non-Singleton Design in Server SDK
    In server environments, we typically need multiple client instances because:

    • Multi-tenancy: Different tenants/customers need isolated feature flag contexts
    • Different configurations: Different services or modules might need different API keys, endpoints, or feature tags
    • Environment isolation: Dev/staging/prod environments running on the same server
    • User segmentation: Different client instances for different user groups or regions
    // Multiple clients for different contexts
    const clientA = initializeBKTClient(configForTenantA);
    const clientB = initializeBKTClient(configForTenantB);
    const clientC = initializeBKTClient(configForDifferentRegion);
  2. Error Handling Problem with Promise
    If initializeBKTClient() returned Promise, what happens when initialization fails?

  // This would be problematic:
  try {
    const client = await initializeBKTClient(config); // What if this fails?
  } catch (error) {
    // Now you have NO client instance at all!
    // You can't:
    // - Use default values
    // - Retry initialization later
    // - Fall back to cached data
    // - Continue with degraded functionality
  }
  1. Graceful Degradation
    This design allows for graceful degradation:

const client = initializeBKTClient(config); // Always succeeds, gives you a client

  try {
    await client.waitForInitialization({ timeoutMs: 5000 });
    // Great! Use real feature flags
  } catch (error) {
    // Still have a working client that can:
    // - Return default values
    // - Log the error
    // - Retry later
    // - Use fallback logic
  }
  
  // Client is always usable regardless of initialization success
  const result = client.booleanVariation(user, 'feature', false);

@duyhungtnn duyhungtnn force-pushed the fix/no-spead-after-defaults-lint branch 3 times, most recently from 05f5303 to d268fbb Compare September 3, 2025 16:11
@duyhungtnn duyhungtnn force-pushed the feat/async-init branch 2 times, most recently from 6738a6e to 21b4b02 Compare September 3, 2025 16:17
Base automatically changed from fix/no-spead-after-defaults-lint to master September 5, 2025 03:43
@duyhungtnn duyhungtnn marked this pull request as ready for review September 9, 2025 07:24
@duyhungtnn duyhungtnn requested a review from Copilot September 9, 2025 07:24

This comment was marked as outdated.

@duyhungtnn duyhungtnn requested a review from Copilot September 9, 2025 07:34
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull Request Overview

This pull request adds asynchronous initialization support to the Bucketeer SDK client, enabling applications to wait for cache processors to complete their initial data synchronization before proceeding with feature flag evaluations. This improves reliability during startup by preventing race conditions between client usage and data loading.

  • Add new waitForInitialization() method to the client interface with configurable timeout
  • Refactor cache processors to support async start() and stop() methods for initialization control
  • Introduce comprehensive error handling system with structured error types and conversion utilities

Reviewed Changes

Copilot reviewed 24 out of 24 changed files in this pull request and generated 11 comments.

Show a summary per file
File Description
src/index.ts Adds waitForInitialization method to Bucketeer interface with comprehensive documentation
src/client.ts Implements waitForInitialization method and updates client initialization to track async processor startup
src/objects/errors.ts Introduces structured error hierarchy with BKTBaseError and specific error types for different status codes
src/objects/metricsEvent.ts Refactors error handling to use structured error types and moves helper functions
src/cache/processor/*.ts Updates cache processors to async start/stop methods for proper initialization control
src/tests/**/*.ts Adds comprehensive test coverage for new initialization functionality and error handling
e2e/**/*.ts Updates end-to-end tests to use waitForInitialization instead of manual delays

Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.

Comment thread src/objects/metricsEvent.ts
Comment thread src/objects/metricsEvent.ts
Comment thread src/objects/metricsEvent.ts
Comment thread src/objects/metricsEvent.ts
Comment thread src/objects/metricsEvent.ts
Comment thread src/objects/metricsEvent.ts
Comment thread src/objects/metricsEvent.ts
Comment thread src/objects/metricsEvent.ts
Comment thread src/cache/processor/featureFlagCacheProcessor.ts Outdated
Comment thread src/__tests__/client_local_evaluation.ts
@duyhungtnn
Copy link
Copy Markdown
Collaborator Author

@cre8ivejp please help me to take a look

cre8ivejp
cre8ivejp previously approved these changes Oct 21, 2025
Copy link
Copy Markdown
Member

@cre8ivejp cre8ivejp left a comment

Choose a reason for hiding this comment

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

Nice work!

@cre8ivejp cre8ivejp changed the title feat: async initialization feat: support for async initialization Oct 21, 2025
@cre8ivejp cre8ivejp changed the title feat: support for async initialization feat: support for synchronous initialization Oct 21, 2025
The 'fix/no-spead-after-defaults-lint' branch was removed from the workflow trigger list, so the workflow now only runs on pull requests to 'master'.
Updated the test to expect ForbiddenError instead of InvalidStatusError when using a random API key. This aligns the test with the current error handling implementation.
Moved CustomError and createNodeJSError to a shared test utility file and updated imports in error_to_metrics_event test. Added comprehensive tests for toBKTError conversion logic. Changed initializationPromise property from private to public in featureFlagCacheProcessor and segmentUsersCacheProcessor for improved accessibility in tests.
Deleted the InitializationPromise utility class and its associated test files. This change removes initialization state management logic and its test coverage, likely due to refactoring or deprecation.
Changed the return type of defaultInitialize from BKTClientImpl to Bucketeer for improved type consistency. Also removed outdated compatibility comments in client.ts.
Updated calls to processor.stop() to use await in tests and client implementation, ensuring proper handling of asynchronous cleanup. This change improves reliability when stopping processors that may perform asynchronous operations.
Renamed internal methods in featureFlagCacheProcessor and segmentUsersCacheProcessor for clarity and improved polling schedule management. Added comprehensive AVA unit tests for feature flag and segment users cache processors to validate start/stop behavior and error handling. Also improved code formatting and error handling in client.ts for better readability and maintainability.
Expanded client_wait_for_initialization.ts with detailed test cases covering timeout, successful initialization, and processor failure scenarios. Also fixed minor import issues in client_local_evaluation.ts and removed unnecessary catch in client.ts to allow proper error propagation during initialization.
Improved readability of stubbed processor start methods by reformatting calls to sandbox.stub. Added several placeholder edge case tests for client initialization scenarios, including handling of null processors, null initializationAsync, immediate returns, and timeout behavior.
Added validation to ensure cache processors and local evaluator are provided when local evaluation is enabled, throwing an error if not. Expanded and refactored tests for client initialization, including cases for missing processors, multiple calls to waitForInitialization, and timeout handling.
Introduces a new AVA test to verify that local evaluation works correctly without explicitly calling waitForInitialization. The test waits manually before running assertions and checks boolean variation results and evaluation details.
Corrects the import path for InvalidStatusError in client_wait_for_initialization test file to reference the correct location in the project structure.
Replaced '==' with '===' in status code comparisons within error handling and metrics event functions for improved type safety and consistency.
Cleaned up code style by removing unnecessary semicolons after closing braces in featureFlagCacheProcessor and segmentUsersCacheProcessor.
Replaced incorrect 'sino' import with 'sinon' in client_local_evaluation test file and updated all references accordingly. Also fixed a minor formatting issue in featureFlagCacheProcessor by removing an extra space after 'finally'.
@cre8ivejp cre8ivejp merged commit 9c3693e into master Oct 21, 2025
8 checks passed
@cre8ivejp cre8ivejp deleted the feat/async-init branch October 21, 2025 03:50
@cre8ivejp cre8ivejp changed the title feat: support for synchronous initialization feat: support for asynchronous initialization Oct 21, 2025
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