Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Auto-Initialize the API #23

Open
wants to merge 2 commits into
base: main
Choose a base branch
from

Conversation

cainspencerm
Copy link

This allows developers to call the API without manually initializing. This does not prevent manual initialization from occurring, nor does it override it. This also shows a proper error if the API cannot initialize, instead of showing non-informative failed API responses.

Additions

Initialization Tracking

A boolean flag is added to OpenAPI (and its type OpenAPIConfig). The flag defaults to false, entertaining a lazy auto-initialization strategy. This is a better initialization strategy than immediate initialization, since it gives the developer the option to initialize manually before using the API. Further, lazy auto-initialization prevents creating a token unnecessarily, should the developer choose to manually initialize.

Auto-Initialization on Request

When request is called (aka __request as used in the generated API file), initialization is verified via the boolean flag. If the configuration has not yet been initialized, initialization occurs. The request then proceeds as normal. This impacts performance minimally, as it merely shifts the time of initialization to the beginning of the first request and adds a single boolean check otherwise.

Benefits

The current state of the package (v0.9.0) causes silent and misleading failures surrounding initialization. If init is not called before the first API request, the package will throw an Invalid URL error due to the default URL value https://{your_subdomain}.kinde.com. This doesn't suggest that the environment isn't correctly set up, nor does it suggest that init has not yet been called.

With auto-initialization, init will be called regardless, leading to more informed debugging when the environment is not set up properly. Instead of Invalid URL, developers might see kindeDomain or env KINDE_DOMAIN is not set.

Further, init is boilerplate for developers that store the domain and other parameters in the environment. It is helpful to the development process to handle it in the background. However, leaving it available for manual initialization when needed is good for the manually-set use case and backwards compatibility.

Checklist

This allows developers to call the API without manually initializing. This does not prevent manual initializtion from occurring, nor does it override it. This also shows a proper error if the API cannot initialize, instead of showing non-informative failed API responses.
Copy link
Contributor

coderabbitai bot commented Oct 24, 2024

Walkthrough

The changes introduce a new optional property INITIALIZED to the OpenAPIConfig type and the OpenAPI constant, initialized to false. The request function is modified to check if the configuration is initialized, invoking init() if it is not. The init function in lib/config.ts is updated to set OpenAPI.INITIALIZED to true upon initialization. Additionally, the readme.md file is updated to reflect these changes, simplifying usage examples and adding a new section for manual API initialization.

Changes

File Change Summary
lib/api/core/OpenAPI.ts Added optional property INITIALIZED: boolean; to OpenAPIConfig and initialized OpenAPI.INITIALIZED to false.
lib/api/core/request.ts Modified request function to check config.INITIALIZED and call init() if false. Retained error handling in catchErrorCodes.
lib/config.ts Updated init function to set OpenAPI.INITIALIZED to true after merging configuration values.
readme.md Modified usage examples to remove init from imports, added a new section for manual API initialization.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant API
    participant Config

    User->>API: Call API function
    API->>Config: Check if INITIALIZED
    alt Not Initialized
        Config->>API: Call init()
        API->>Config: Merge configuration
        Config->>API: Set INITIALIZED to true
    end
    API->>API: Proceed with API request
Loading

Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Outside diff range and nitpick comments (5)
readme.md (3)

31-34: Add a note about auto-initialization behavior.

While the simplified example is good, it would be helpful to add a brief note explaining that the API will auto-initialize on the first request using environment variables. This helps users understand the behavior and requirements.

 import { Users } from "@kinde/management-api-js";
 
+// The API will auto-initialize on first request using environment variables
 const { users } = await Users.getUsers();

48-58: Enhance the manual initialization section with more context.

While the example is clear, it would be beneficial to:

  1. Explain when manual initialization might be preferred over auto-initialization
  2. Clarify how manual initialization relates to environment variables
  3. Note that manual initialization takes precedence over auto-initialization
 ### Manually Initializing the API
 
+Manual initialization allows you to:
+- Override environment variables with different credentials
+- Initialize the API before any requests are made
+- Have more control over the initialization timing
+
+Note: Manual initialization takes precedence over auto-initialization.
+
 ```js
 import { init } from "@kinde/management-api-js";
 

31-31: Add section about error handling improvements.

The PR introduces improved error handling with clearer messages when API initialization fails. Consider adding a section about common errors and their meanings to help developers with debugging.

+## Error Handling
+
+The SDK now provides clearer error messages when initialization fails. Common errors include:
+- "Environment variables not set": Check that KINDE_DOMAIN, KINDE_MANAGEMENT_CLIENT_ID, and KINDE_MANAGEMENT_CLIENT_SECRET are properly configured
+- "Invalid credentials": Verify that your client ID and secret are correct
+
 import { Users } from "@kinde/management-api-js";
lib/config.ts (1)

Line range hint 1-63: Consider enhancing security and validation

The current implementation could benefit from additional security measures and validation:

  1. Sensitive configuration values should be redacted in logs
  2. Additional validation for required fields (clientId, clientSecret, audience)
  3. The TokenStore interface could be better utilized for token management

Consider implementing these improvements:

  1. Add input validation for all required fields
  2. Implement secure logging that redacts sensitive information
  3. Add TypeScript strict type checking for configuration objects

Would you like me to provide a detailed implementation for any of these suggestions?

lib/api/core/request.ts (1)

Line range hint 345-351: Update JSDoc to document auto-initialization behavior.

The function documentation should be updated to reflect the auto-initialization feature and its implications.

Consider updating the JSDoc:

 /**
  * Request method
  * @param config The OpenAPI configuration object
  * @param options The request options from the service
+ * @description This method automatically initializes the API if not already initialized.
+ *              The initialization is thread-safe and handles concurrent requests properly.
+ *              Manual initialization is still supported and will be respected if performed.
  * @returns CancelablePromise<T>
- * @throws ApiError
+ * @throws ApiError When the request fails or initialization fails
  */
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Files that changed from the base of the PR and between a903f25 and d7026ee.

📒 Files selected for processing (4)
  • lib/api/core/OpenAPI.ts (2 hunks)
  • lib/api/core/request.ts (2 hunks)
  • lib/config.ts (1 hunks)
  • readme.md (1 hunks)
🔇 Additional comments (4)
lib/api/core/OpenAPI.ts (2)

57-57: LGTM! Clean implementation of initialization tracking.

The default value of false aligns perfectly with the lazy initialization strategy, allowing for both automatic and manual initialization paths while maintaining backward compatibility.


40-40: Verify if INITIALIZED should be marked as optional.

The INITIALIZED property in OpenAPIConfig type is not marked as optional. While this ensures type safety by requiring the property to be present, consider if it should be marked as optional (INITIALIZED?: boolean) to maintain flexibility for partial configurations.

✅ Verification successful

The INITIALIZED property should remain non-optional

The implementation shows that INITIALIZED is a critical flag used for ensuring proper initialization state. In request.ts, there's an explicit check if (!config.INITIALIZED) that triggers initialization, making this a required runtime safeguard. Making it optional would weaken this type safety mechanism and go against the PR's objective of reliable initialization tracking.

Key evidence:

  • Used in request<T>() function for mandatory initialization check
  • Defined as a core property in the main OpenAPI constant
  • Serves as a required runtime safeguard across the codebase
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check if there are any existing implementations of OpenAPIConfig 
# that might break with this non-optional property

# Search for OpenAPIConfig implementations
rg -l "implements.*OpenAPIConfig|:.*OpenAPIConfig|as.*OpenAPIConfig"

Length of output: 118


Script:

#!/bin/bash
# Let's examine the actual implementations to understand how OpenAPIConfig is used
rg -A 5 -B 5 "implements.*OpenAPIConfig|:.*OpenAPIConfig|as.*OpenAPIConfig" lib/api/core/OpenAPI.ts lib/api/core/request.ts

Length of output: 2899

readme.md (1)

39-46: LGTM!

The example clearly demonstrates how to pass parameters while maintaining consistency with the auto-initialization approach.

lib/api/core/request.ts (1)

352-356: ⚠️ Potential issue

Add synchronization and error handling for initialization.

The current implementation has potential issues:

  1. Race condition: Multiple concurrent requests could trigger initialization simultaneously
  2. Silent failures: No error handling for init() failures
  3. Side effects: Initialization effects are not properly managed

Consider implementing this safer approach:

   // Verify that config has been initialized
-  if (!config.INITIALIZED) {
-    init();
-  }
+  if (!config.INITIALIZED) {
+    try {
+      // Use initialization lock to prevent race conditions
+      if (!config.initializationPromise) {
+        config.initializationPromise = init();
+      }
+      await config.initializationPromise;
+    } catch (error) {
+      throw new ApiError(
+        options,
+        { status: 500, ok: false },
+        `API initialization failed: ${error.message}`
+      );
+    }
+  }

This change:

  • Prevents race conditions using a promise-based lock
  • Properly handles initialization failures
  • Maintains consistent error reporting

Let's verify the initialization handling in the codebase:

Comment on lines +65 to +67

// Mark OpenAPI as initialized
OpenAPI.INITIALIZED = true;
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Consider improving initialization safety and documentation

The initialization tracking addition needs some improvements:

  1. The function should be synchronized to prevent race conditions during parallel initialization attempts
  2. The INITIALIZED flag should only be set after ensuring all initialization steps succeed
  3. The JSDoc should document the initialization state behavior

Here's a suggested implementation:

 /**
  * Initializes the integration with Kinde, using either provided configuration
  * or default values from `kindeConfig`.
  *
  * @param {Object} [config] - Optional configuration object for Kinde.
  * @param {string} [config.kindeDomain] - The domain of your Kinde account.
  * @param {string} [config.clientId] - Your Kinde M2M client ID.
  * @param {string} [config.clientSecret] - Your Kinde M2M client secret.
+ * @throws {Error} If initialization fails due to missing configuration or invalid state
+ * @returns {Promise<void>} - Resolves when initialization is complete
  */
-export const init = (
+export const init = async (
   config: Pick<
     configType,
     "kindeDomain" | "clientId" | "clientSecret"
   > = kindeConfig,
-) => {
+) => {
+  // Prevent parallel initialization
+  if (OpenAPI.INITIALIZED) {
+    return;
+  }

   if (!process.env.KINDE_DOMAIN && config.kindeDomain === "") {
     throw new Error("kindeDomain or env KINDE_DOMAIN is not set");
   }

   _merge(
     kindeConfig,
     {
       clientId: process.env.KINDE_MANAGEMENT_CLIENT_ID,
       clientSecret: process.env.KINDE_MANAGEMENT_CLIENT_SECRET,
       audience: process.env.KINDE_DOMAIN + "/api",
       kindeDomain: process.env.KINDE_DOMAIN,
     },
     config,
   );

   _merge(OpenAPI, {
     BASE: kindeConfig.kindeDomain,
     TOKEN: async () => {
       return await getToken();
     },
   });

+  try {
+    // Verify the configuration by attempting to get an initial token
+    await getToken();
+    OpenAPI.INITIALIZED = true;
+  } catch (error) {
+    throw new Error(`Failed to initialize: ${error.message}`);
+  }
 };
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// Mark OpenAPI as initialized
OpenAPI.INITIALIZED = true;
/**
* Initializes the integration with Kinde, using either provided configuration
* or default values from `kindeConfig`.
*
* @param {Object} [config] - Optional configuration object for Kinde.
* @param {string} [config.kindeDomain] - The domain of your Kinde account.
* @param {string} [config.clientId] - Your Kinde M2M client ID.
* @param {string} [config.clientSecret] - Your Kinde M2M client secret.
* @throws {Error} If initialization fails due to missing configuration or invalid state
* @returns {Promise<void>} - Resolves when initialization is complete
*/
export const init = async (
config: Pick<
configType,
"kindeDomain" | "clientId" | "clientSecret"
> = kindeConfig,
) => {
// Prevent parallel initialization
if (OpenAPI.INITIALIZED) {
return;
}
if (!process.env.KINDE_DOMAIN && config.kindeDomain === "") {
throw new Error("kindeDomain or env KINDE_DOMAIN is not set");
}
_merge(
kindeConfig,
{
clientId: process.env.KINDE_MANAGEMENT_CLIENT_ID,
clientSecret: process.env.KINDE_MANAGEMENT_CLIENT_SECRET,
audience: process.env.KINDE_DOMAIN + "/api",
kindeDomain: process.env.KINDE_DOMAIN,
},
config,
);
_merge(OpenAPI, {
BASE: kindeConfig.kindeDomain,
TOKEN: async () => {
return await getToken();
},
});
try {
// Verify the configuration by attempting to get an initial token
await getToken();
OpenAPI.INITIALIZED = true;
} catch (error) {
throw new Error(`Failed to initialize: ${error.message}`);
}
};

@DanielRivers
Copy link
Contributor

DanielRivers commented Oct 24, 2024

Hi @cainspencerm,

Thanks for raising this. Unfortunately the code under /api is generated code from our openAPI spec, so an alternative method would have to be found as it will get overwritten when the spec is refreshed.

The initial implementation of this package didn't have an init method, the initialisation was setup from the main.js, this worked great in next.js, however this was not the case in all meta frameworks, as with Nuxt the OpenAPI was never initialised.

init was born and auto initialisation was removed to keep consistency, happy to push forward to look at a way which will work for all environments.

@cainspencerm
Copy link
Author

Ah, I assumed incorrectly that .gen files were the only ones being generated.

@cainspencerm
Copy link
Author

cainspencerm commented Oct 25, 2024

Are you suggesting that the current changes on this pull request wouldn't work with Nuxt?

If it could work, what do you think about tacking in a script at the end of spec:generate? For instance, in package.json:

"spec:generate": "npx @hey-api/openapi-ts && node scripts/restore-auto-init.js",

Then, scripts/restore-auto-init.js could re-implement these changes. Does the code generated in api/core stay consistent?

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.

2 participants