OpenAPI Typescript Type Inferencing Lost Across Package Boundaries
Introduction
In modern software development, especially when dealing with microservices and distributed systems, maintaining type safety across different modules and packages is crucial. This article delves into a specific issue encountered while using openapi-typescript
to generate type bindings for an HTTP ledger API. The core challenge lies in the loss of type inferencing when importing a ledger client, which combines generated schema types with openapi-fetch
, into other packages. This problem leads to response objects falling back to the any
type, diminishing the benefits of strong typing. We will explore the nuances of this issue, potential causes, and strategies to mitigate it, ensuring robust type safety throughout your application.
The Problem: Type Loss Across Package Boundaries
When developing applications that interact with APIs, generating type bindings from API specifications like OpenAPI/Swagger is a common practice. Tools like openapi-typescript
significantly streamline this process by automatically creating TypeScript types from your API definitions. These generated types are then used to ensure that your client-side code correctly interacts with the API, catching potential errors at compile-time rather than runtime. However, a recurring issue arises when these types are used across different packages or modules within a larger project. The expected type inferencing, which should provide strong typing for response objects, sometimes fails, resulting in the dreaded any
type. This section will dissect the problem, providing a clear understanding of how and why this type loss occurs, particularly in the context of a ledger client built using openapi-typescript
and openapi-fetch
.
Understanding the Setup
To fully grasp the issue, let's first outline a typical setup where this problem manifests. Imagine a project structured with multiple packages: a core/ledger-client
package and one or more client application packages. The core/ledger-client
package is responsible for handling all interactions with the ledger API. It uses openapi-typescript
to generate TypeScript types directly from the API's OpenAPI specification. These generated types represent the shape of the data that the API expects and returns. The package then combines these types with openapi-fetch
, a library that provides a type-safe way to make HTTP requests, ensuring that the request and response bodies adhere to the generated types. This combination creates a strongly-typed HTTP client specifically for the ledger API. The intention is that any component within the application that needs to interact with the ledger does so through this client, benefiting from the type safety it provides.
The Scenario: Importing the Ledger Client
The problem arises when this core/ledger-client
package is imported into other parts of the application, such as a UI component or a service layer. Ideally, when a method from the ledger client is called, the TypeScript compiler should be able to infer the return type based on the generated types. For instance, if an API endpoint is defined to return a User
object with specific properties, the client should return a promise that resolves to a User
object, allowing developers to access properties like user.id
or user.name
with full type safety. However, in practice, this is not always the case. Instead of inferring the correct type, the response object often defaults to any
, meaning that all type information is lost. This forces developers to either manually cast the response to the correct type or, worse, work with untyped data, which can lead to runtime errors and defeats the purpose of using TypeScript in the first place.
The Impact of Type Loss
The consequences of this type loss are significant. Firstly, it undermines the primary benefit of using TypeScript: compile-time type checking. When response objects are typed as any
, the compiler cannot verify that the code is using the data correctly. This means that errors such as accessing a non-existent property or passing data of the wrong type are not caught until runtime, making debugging more difficult and increasing the risk of unexpected behavior in production. Secondly, it reduces developer productivity. Without type information, developers must rely on documentation or guesswork to understand the structure of the response data. This not only slows down development but also makes the code harder to maintain and refactor. Lastly, it can lead to a fragile codebase. When types are not enforced, it becomes easier to introduce subtle bugs that are hard to detect, making the application more prone to errors and less resilient to change. This issue, therefore, needs a robust solution to ensure the integrity and maintainability of the application.
Potential Causes of Type Inferencing Loss
Several factors can contribute to the loss of type inferencing when using openapi-typescript
across package boundaries. Identifying these potential causes is crucial for diagnosing and resolving the issue effectively. Let's delve into some of the most common reasons why type information might be lost in this context, including TypeScript configuration issues, problems with module resolution, and intricacies of how openapi-typescript
and openapi-fetch
interact.
1. TypeScript Configuration Issues
One of the primary culprits behind type inferencing problems is the TypeScript configuration itself. TypeScript relies on a tsconfig.json
file to understand how to compile the project, including which files to include, how to resolve modules, and what compiler options to use. Incorrect or inconsistent settings in the tsconfig.json
files across different packages can lead to type information being lost during compilation. For example, if one package uses strict type checking (strict: true
) while another does not, the stricter package might not be able to correctly infer types from the less strict one. Similarly, if the compilerOptions
such as moduleResolution
, target
, or lib
are inconsistent, it can cause discrepancies in how types are resolved and interpreted.
Common Configuration Mistakes
- Inconsistent
compilerOptions
: Ensure that settings likemoduleResolution
(e.g.,node
,classic
),target
(e.g.,es5
,esnext
), andlib
(e.g.,es2015
,dom
) are consistent across alltsconfig.json
files in your project. Inconsistencies can lead to different interpretations of type definitions. - Incorrect
include
andexclude
: Verify that theinclude
andexclude
arrays in yourtsconfig.json
files correctly specify which files should be included in the compilation process and which should be ignored. Incorrect settings can result in type definition files being excluded, leading to type errors or loss of type information. - Missing or Misconfigured
baseUrl
andpaths
: If you are using path aliases to simplify imports (e.g.,@core/ledger-client
), ensure that thebaseUrl
andpaths
options in yourtsconfig.json
are correctly configured. Misconfigurations can prevent TypeScript from resolving modules correctly, leading to type errors. - Conflicting
strict
settings: Thestrict
option intsconfig.json
enables a set of strict type-checking behaviors. If some packages havestrict: true
while others havestrict: false
, it can lead to type inconsistencies. It's generally recommended to enable strict mode for the entire project to ensure consistent type checking.
2. Module Resolution Problems
Module resolution is the process by which TypeScript (and Node.js) figures out where to find imported modules. Problems in this area can often lead to type inferencing issues. TypeScript supports different module resolution strategies, such as Node
and Classic
, and the chosen strategy can significantly impact how types are resolved. For instance, if your project uses a monorepo structure with multiple packages, TypeScript needs to be able to correctly resolve modules across these packages. If the module resolution is not set up correctly, TypeScript might fail to find the type definition files (.d.ts
files) generated by openapi-typescript
, resulting in type loss.
Common Module Resolution Issues
- Incorrect
moduleResolution
: ThemoduleResolution
option intsconfig.json
should be set toNode
for most modern projects that use npm or yarn for package management. If it's set toClassic
or another value, TypeScript might not resolve modules correctly, especially in a monorepo setup. - Missing or Incorrect
types
field inpackage.json
: If yourcore/ledger-client
package includes apackage.json
file, ensure that it has atypes
field that points to the main type definition file (usually a.d.ts
file). This helps TypeScript locate the type definitions for the package. - Path Aliases and Module Resolution: When using path aliases (e.g.,
@core/ledger-client
) in your imports, ensure that yourtsconfig.json
file'scompilerOptions.paths
property is correctly configured. This tells TypeScript how to resolve these aliases to the correct module paths. - Monorepo-Specific Issues: In a monorepo, each package might have its own
tsconfig.json
file. Ensure that thereferences
orcomposite
options are used correctly to enable TypeScript to build and resolve types across packages. Tools like Yarn Workspaces or Lerna can help manage these relationships.
3. Interaction Between openapi-typescript
and openapi-fetch
The way openapi-typescript
and openapi-fetch
are used together can also contribute to type inferencing issues. openapi-typescript
generates TypeScript types from OpenAPI specifications, while openapi-fetch
is designed to make type-safe HTTP requests using these generated types. However, the integration between these two libraries is not always seamless, and certain usage patterns can lead to type loss.
Potential Integration Problems
- Incorrect Type Mapping:
openapi-typescript
might generate types that are not perfectly compatible withopenapi-fetch
. For example, if the OpenAPI specification defines a response body as a complex object, the generated TypeScript type might not fully capture the structure of the object, leading to type mismatches when used withopenapi-fetch
. - Generic Type Inference:
openapi-fetch
relies heavily on generic type inference to provide type safety. If the generic types are not correctly propagated or inferred, the response type might fall back toany
. This can happen if the function signatures or return types are not properly annotated. - Asynchronous Operations: The asynchronous nature of HTTP requests can sometimes complicate type inference. If the return type of an asynchronous function is not explicitly specified, TypeScript might struggle to infer the correct type, especially when dealing with complex generic types.
4. Circular Dependencies
Circular dependencies occur when two or more modules depend on each other, creating a circular import chain. This can confuse the TypeScript compiler and lead to type resolution issues. When circular dependencies exist, TypeScript might not be able to fully resolve the types in one or more of the involved modules, resulting in type loss.
Identifying and Resolving Circular Dependencies
- Using Dependency Analysis Tools: Tools like
madge
orcircular-dependency-plugin
(for Webpack) can help you identify circular dependencies in your project. - Refactoring Code: The best way to resolve circular dependencies is to refactor your code to eliminate them. This might involve breaking up large modules into smaller ones, moving shared functionality into a separate utility module, or using dependency injection to decouple modules.
- Lazy Loading: In some cases, you can use lazy loading or dynamic imports to break circular dependencies. This allows you to load modules only when they are needed, avoiding the circular import chain during initial module loading.
5. TypeScript Version Mismatches
Using different TypeScript versions across your project or having a version that is incompatible with openapi-typescript
or openapi-fetch
can lead to unexpected type errors and loss of type inferencing. TypeScript evolves rapidly, with each new version introducing bug fixes, performance improvements, and changes to the type system. If your project uses an outdated version of TypeScript, it might not be able to correctly interpret the generated types from openapi-typescript
or work seamlessly with openapi-fetch
.
Ensuring TypeScript Version Compatibility
- Consistent TypeScript Version: Ensure that all packages in your project use the same version of TypeScript. This can be managed using package managers like npm or yarn, which allow you to specify the TypeScript version as a dependency in your
package.json
file. - Upgrading TypeScript: If you are using an older version of TypeScript, consider upgrading to the latest stable version. Newer versions often include improvements to type inference and compatibility with libraries like
openapi-typescript
andopenapi-fetch
. - Checking Library Compatibility: Before upgrading TypeScript, check the compatibility matrix of
openapi-typescript
andopenapi-fetch
to ensure that the new TypeScript version is supported. Library documentation or release notes usually provide this information.
By understanding these potential causes, you can systematically investigate and address the issue of type loss across package boundaries when using openapi-typescript
. The next section will explore practical strategies for resolving these issues and ensuring robust type safety in your application.
Strategies to Restore Type Inferencing
Having identified the potential causes of type loss across package boundaries, it’s crucial to explore practical strategies for restoring type inferencing. This section will detail actionable steps you can take to ensure that your TypeScript code maintains strong typing, especially when using openapi-typescript
and openapi-fetch
in a multi-package project. These strategies encompass TypeScript configuration adjustments, module resolution fixes, code refactoring techniques, and best practices for integrating these libraries.
1. Refine TypeScript Configuration
As discussed earlier, the TypeScript configuration plays a pivotal role in how types are inferred and resolved. Ensuring that your tsconfig.json
files are correctly set up is the first step towards restoring type inferencing. This involves carefully reviewing and adjusting compiler options, include/exclude settings, and path mappings.
Step-by-Step Configuration Refinement
-
Consistency Across
tsconfig.json
Files: Start by ensuring that alltsconfig.json
files in your project share consistentcompilerOptions
. This is particularly important for settings likemoduleResolution
,target
,lib
, andstrict
. Inconsistencies can lead to different interpretations of type definitions across packages. -
Enable Strict Mode: Enable strict mode by setting
"strict": true
in yourtsconfig.json
. This option enables a set of strict type-checking behaviors that can help catch potential type errors early on. While it might initially reveal more type errors, it ultimately leads to a more robust and maintainable codebase. -
Correct
include
andexclude
Settings: Verify that theinclude
andexclude
arrays in yourtsconfig.json
files accurately specify which files should be included in the compilation process. Ensure that type definition files (.d.ts
) are included and that any unnecessary files are excluded to improve compilation performance. -
Configure
baseUrl
andpaths
: If you’re using path aliases (e.g.,@core/ledger-client
) to simplify imports, make sure that thebaseUrl
andpaths
options in yourtsconfig.json
are correctly configured. This tells TypeScript how to resolve these aliases to the correct module paths. For example:{ "compilerOptions": { "baseUrl": ".", "paths": { "@core/ledger-client": ["packages/core/ledger-client/src"] } } }
-
Use
references
in Monorepos: In a monorepo setup, use thereferences
option in yourtsconfig.json
files to specify dependencies between packages. This allows TypeScript to build and resolve types across packages correctly. For example, if your client application depends on thecore/ledger-client
package, your client application’stsconfig.json
should include:{ "compilerOptions": { "composite": true }, "references": [ { "path": "../core/ledger-client" } ] }
2. Resolve Module Resolution Issues
Correct module resolution is crucial for TypeScript to find and interpret type definitions. Ensuring that TypeScript can correctly locate modules, especially in a multi-package project, is essential for restoring type inferencing.
Steps to Fix Module Resolution
-
Set
moduleResolution
toNode
: In most modern projects using npm or yarn, themoduleResolution
option intsconfig.json
should be set toNode
. This tells TypeScript to use the Node.js module resolution algorithm, which is the standard for most JavaScript projects. -
types
Field inpackage.json
: If yourcore/ledger-client
package includes apackage.json
file, ensure that it has atypes
field that points to the main type definition file (.d.ts
). This helps TypeScript locate the type definitions for the package. For example:{ "name": "@core/ledger-client", "version": "1.0.0", "main": "dist/index.js", "types": "dist/index.d.ts" }
-
Verify Path Aliases: Double-check that path aliases are correctly configured in your
tsconfig.json
file’scompilerOptions.paths
property. Misconfigurations can prevent TypeScript from resolving modules correctly. -
Monorepo Configuration: In a monorepo, ensure that you are using either
references
or thecomposite
option in yourtsconfig.json
files to enable TypeScript to build and resolve types across packages. Tools like Yarn Workspaces or Lerna can help manage these relationships.
3. Enhance Code Structure and Type Annotations
The structure of your code and the use of explicit type annotations can significantly impact type inferencing. Refactoring your code to improve clarity and adding type annotations where necessary can help TypeScript infer types more effectively.
Code Improvement Techniques
-
Explicit Type Annotations: Add explicit type annotations to function return types and variable declarations, especially when dealing with complex types or when type inference is failing. This provides TypeScript with more information and can help it infer types correctly. For example:
async function fetchUser(userId: string): Promise<User> { // ... }
-
Refactor Asynchronous Operations: Pay close attention to asynchronous operations, as they can sometimes complicate type inference. Ensure that the return types of asynchronous functions are explicitly specified. Use
async/await
syntax to make asynchronous code easier to read and reason about. -
Simplify Complex Types: If you have complex types that are difficult to infer, consider breaking them down into smaller, more manageable types. This can make it easier for TypeScript to infer types correctly.
-
Avoid
any
Type: Minimize the use of theany
type as it bypasses TypeScript’s type checking. Instead, try to use more specific types or create custom type definitions to represent your data structures.
4. Optimize openapi-typescript
and openapi-fetch
Integration
Proper integration of openapi-typescript
and openapi-fetch
is crucial for maintaining type safety. This involves ensuring that the generated types are correctly used with openapi-fetch
and that any potential type mismatches are addressed.
Best Practices for Integration
-
Correct Type Mapping: Review the types generated by
openapi-typescript
to ensure they accurately reflect your API’s schema. If there are discrepancies, you might need to adjust your OpenAPI specification or customize the type generation process. -
Generic Type Propagation: Ensure that generic types are correctly propagated when using
openapi-fetch
. This involves correctly specifying the generic type parameters in function signatures and return types. For example:import { FetchInstance } from 'openapi-fetch'; import type { paths } from './generated/openapi'; const api = openapiFetch() as FetchInstance<paths>; async function getUser(userId: string) { const response = await api.get('/users/{userId}', { params: { userId } }); if (response.error) { throw new Error(response.error.message); } return response.data; }
-
Use Type Guards: When dealing with union types or potentially undefined values, use type guards to narrow down the type before accessing properties. This helps TypeScript understand the type more precisely and avoids potential runtime errors.
5. Eliminate Circular Dependencies
Circular dependencies can wreak havoc on type resolution. Identifying and eliminating them is crucial for restoring type inferencing.
Techniques to Remove Circular Dependencies
- Dependency Analysis Tools: Use tools like
madge
orcircular-dependency-plugin
(for Webpack) to identify circular dependencies in your project. - Refactor Code: Refactor your code to break circular dependencies. This might involve:
- Breaking up large modules into smaller ones.
- Moving shared functionality into a separate utility module.
- Using dependency injection to decouple modules.
- Lazy Loading: In some cases, you can use lazy loading or dynamic imports to break circular dependencies. This allows you to load modules only when they are needed, avoiding the circular import chain during initial module loading.
6. Manage TypeScript Version Consistency
Ensuring that you are using a consistent and compatible TypeScript version across your project is vital for avoiding type-related issues.
Steps for Version Management
- Consistent TypeScript Version: Ensure that all packages in your project use the same version of TypeScript. This can be managed using package managers like npm or yarn by specifying the TypeScript version as a dependency in your
package.json
file. - Upgrade TypeScript: If you are using an older version of TypeScript, consider upgrading to the latest stable version. Newer versions often include improvements to type inference and compatibility with libraries like
openapi-typescript
andopenapi-fetch
. - Check Library Compatibility: Before upgrading TypeScript, check the compatibility matrix of
openapi-typescript
andopenapi-fetch
to ensure that the new TypeScript version is supported.
By implementing these strategies, you can effectively restore type inferencing across package boundaries, ensuring a more robust and maintainable codebase. The next section will provide a step-by-step guide to debugging type inferencing issues, helping you pinpoint the root cause of type loss in your project.
Debugging Type Inferencing Issues: A Step-by-Step Guide
When faced with type inferencing problems, a systematic debugging approach is essential to pinpoint the root cause and apply the appropriate solutions. This section provides a step-by-step guide to help you debug type inferencing issues in your project, particularly when using openapi-typescript
across package boundaries. By following these steps, you can efficiently identify the source of type loss and implement the necessary fixes.
Step 1: Reproduce the Issue
The first step in debugging any problem is to reproduce the issue consistently. This means identifying the exact scenario in which type inferencing fails and ensuring that the problem occurs reliably. This will allow you to test your fixes effectively.
How to Reproduce
- Identify the Code: Pinpoint the specific code where type inferencing is lost. This usually involves tracing the flow of data from the
core/ledger-client
package to the client application package where the type is expected. - Create a Minimal Reproduction: If possible, create a minimal reproduction of the issue. This involves isolating the relevant code and removing any unnecessary complexity. A smaller code sample makes it easier to understand the problem and test potential solutions.
- Document the Steps: Write down the exact steps required to reproduce the issue. This will help you verify that your fixes are effective and can also be useful for others who might encounter the same problem.
Step 2: Inspect TypeScript Configuration
Once you can reproduce the issue, the next step is to inspect your TypeScript configuration. As discussed earlier, incorrect or inconsistent settings in your tsconfig.json
files are a common cause of type inferencing problems.
Configuration Inspection Checklist
- Consistent
compilerOptions
: Check that thecompilerOptions
in yourtsconfig.json
files are consistent across all packages. Pay particular attention to settings likemoduleResolution
,target
,lib
, andstrict
. - Strict Mode: Ensure that
"strict": true
is set in yourtsconfig.json
to enable strict type checking. include
andexclude
: Verify that theinclude
andexclude
arrays are correctly configured to include necessary files and exclude unnecessary ones.baseUrl
andpaths
: If you’re using path aliases, ensure thatbaseUrl
andpaths
are set up correctly.- Monorepo Configuration: In a monorepo, check that you’re using
references
or thecomposite
option to enable TypeScript to build and resolve types across packages.
Step 3: Analyze Module Resolution
If the TypeScript configuration looks correct, the next step is to analyze module resolution. Problems with module resolution can prevent TypeScript from finding type definition files, leading to type loss.
Module Resolution Analysis Steps
moduleResolution
: Verify thatmoduleResolution
is set toNode
in yourtsconfig.json
.types
Field inpackage.json
: Ensure that yourcore/ledger-client
package has apackage.json
file with atypes
field pointing to the main type definition file (.d.ts
).- Path Aliases: Double-check that path aliases are correctly configured in your
tsconfig.json
. - Monorepo Structure: In a monorepo, ensure that TypeScript can correctly resolve modules across packages using
references
or thecomposite
option.
Step 4: Examine Code Structure and Type Annotations
If module resolution is not the issue, examine your code structure and type annotations. Inadequate or incorrect type annotations can prevent TypeScript from inferring types correctly.
Code and Annotation Review
- Explicit Type Annotations: Add explicit type annotations to function return types and variable declarations, especially where type inference is failing.
- Asynchronous Operations: Pay attention to asynchronous functions and ensure that their return types are explicitly specified.
- Complex Types: If you’re using complex types, consider breaking them down into smaller, more manageable types.
- Avoid
any
: Minimize the use of theany
type and use more specific types instead.
Step 5: Investigate openapi-typescript
and openapi-fetch
Integration
If the problem persists, investigate the integration between openapi-typescript
and openapi-fetch
. Issues in how these libraries are used together can lead to type loss.
Integration Analysis Checklist
- Type Mapping: Review the types generated by
openapi-typescript
to ensure they accurately reflect your API’s schema. - Generic Type Propagation: Ensure that generic types are correctly propagated when using
openapi-fetch
. - Type Guards: Use type guards to narrow down types when dealing with union types or potentially undefined values.
Step 6: Check for Circular Dependencies
Circular dependencies can also cause type resolution issues. Use a dependency analysis tool to check for circular dependencies in your project.
Circular Dependency Check
- Dependency Analysis Tools: Use tools like
madge
orcircular-dependency-plugin
to identify circular dependencies. - Refactor Code: If circular dependencies are found, refactor your code to eliminate them.
Step 7: Verify TypeScript Version Compatibility
Finally, verify that you’re using a consistent and compatible TypeScript version across your project.
Version Compatibility Check
- Consistent Version: Ensure that all packages in your project use the same TypeScript version.
- Up-to-Date Version: Consider upgrading to the latest stable TypeScript version.
- Library Compatibility: Check the compatibility matrix of
openapi-typescript
andopenapi-fetch
to ensure that your TypeScript version is supported.
By following this step-by-step guide, you can systematically debug type inferencing issues and restore strong typing in your application. The final section will summarize the key strategies and provide additional tips for maintaining type safety in your projects.
Conclusion: Maintaining Type Safety in Multi-Package Projects
Maintaining type safety in multi-package projects, especially when using tools like openapi-typescript
and openapi-fetch
, requires a comprehensive approach. The loss of type inferencing across package boundaries can lead to significant challenges, including runtime errors, reduced developer productivity, and a more fragile codebase. However, by understanding the potential causes of these issues and implementing effective strategies, you can ensure robust type safety throughout your application.
Key Strategies for Type Safety
Throughout this article, we’ve explored several key strategies for maintaining type safety in multi-package projects. Here’s a recap of the most important steps:
- Refine TypeScript Configuration: Ensure that your
tsconfig.json
files are consistent across all packages, with strict mode enabled and correct settings formoduleResolution
,target
,lib
,include
,exclude
,baseUrl
, andpaths
. - Resolve Module Resolution Issues: Verify that TypeScript can correctly locate modules, especially in monorepo setups. Use the
types
field inpackage.json
and configure path aliases as needed. - Enhance Code Structure and Type Annotations: Use explicit type annotations, especially for function return types and asynchronous operations. Simplify complex types and minimize the use of the
any
type. - Optimize
openapi-typescript
andopenapi-fetch
Integration: Ensure that generated types accurately reflect your API’s schema, propagate generic types correctly, and use type guards when necessary. - Eliminate Circular Dependencies: Identify and remove circular dependencies using dependency analysis tools and refactoring techniques.
- Manage TypeScript Version Consistency: Use a consistent and compatible TypeScript version across your project, and consider upgrading to the latest stable version.
Additional Tips for Maintaining Type Safety
In addition to the strategies outlined above, here are some additional tips for maintaining type safety in your projects:
- Continuous Integration: Integrate type checking into your continuous integration (CI) pipeline. This ensures that type errors are caught early in the development process.
- Code Reviews: Include type safety as part of your code review process. Encourage reviewers to look for potential type errors and inconsistencies.
- Documentation: Document your API’s types and data structures clearly. This helps developers understand how to use the API correctly and avoid type-related issues.
- Testing: Write unit tests that specifically check type-related behavior. This can help catch subtle type errors that might not be apparent during development.
- Stay Updated: Keep up-to-date with the latest versions of TypeScript,
openapi-typescript
, andopenapi-fetch
. Newer versions often include bug fixes and improvements to type inference.
By consistently applying these strategies and tips, you can build more robust, maintainable, and error-free applications. Type safety is not just a feature; it’s a fundamental aspect of building high-quality software. Embracing strong typing practices will lead to a more enjoyable and productive development experience, and ultimately, better software for your users.