Gathering detailed insights and metrics for mobx-openapi-stores
Gathering detailed insights and metrics for mobx-openapi-stores
Gathering detailed insights and metrics for mobx-openapi-stores
Gathering detailed insights and metrics for mobx-openapi-stores
A MobX-based store implementation for OpenAPI generated clients
npm install mobx-openapi-stores
Typescript
Module System
Node Version
NPM Version
TypeScript (100%)
Total Downloads
0
Last Day
0
Last Week
0
Last Month
0
Last Year
0
MIT License
22 Commits
1 Branches
2 Contributors
Updated on May 08, 2025
Latest Version
0.1.0
Package Id
mobx-openapi-stores@0.1.0
Unpacked Size
308.38 kB
Size
48.00 kB
File Count
187
NPM Version
11.3.0
Node Version
23.4.0
Published on
May 08, 2025
Cumulative downloads
Total Downloads
Last Day
0%
NaN
Compared to previous day
Last Week
0%
NaN
Compared to previous week
Last Month
0%
NaN
Compared to previous month
Last Year
0%
NaN
Compared to previous year
mobx-openapi-stores
provides a set of base MobX stores designed to simplify the integration of OpenAPI-generated API clients into your frontend applications. These stores offer structured ways to manage API interactions, loading states, and common data patterns like single entities, collections, and grouped objects. Errors will automatically be processed and logged to the console.
apiCall
method for interacting with your API client.1npm install mobx-openapi-stores mobx 2# or 3yarn add mobx-openapi-stores mobx
This package provides the following core store classes:
LoadingStore
: Manages a simple boolean loading state.ApiStore
: Manages an API client instance and API call flows.SingleStore
: Manages a single observable entity.CollectionStore
: Manages a collection (array) of observable entities.CrudCollectionStore
: Extends CollectionStore
with helper methods for common API-driven CRUD operations.ObjectStore
: Manages a dictionary-like observable object, useful for grouping entities.LoadingStore
The most basic store, providing a simple isLoading
observable property and setIsLoading
action.
Purpose: To be extended by other stores that need to track loading states for asynchronous operations.
Key Members:
isLoading: boolean
(computed): True if an operation is in progress.setIsLoading(loading: boolean)
(action): Sets the loading state.Example:
1import { LoadingStore } from 'mobx-openapi-stores'; 2import { makeObservable, action } from 'mobx'; 3 4class MyCustomLoadingComponentStore extends LoadingStore { 5 constructor() { 6 super(); 7 // makeObservable for custom actions if any 8 } 9 10 async performSomeTask() { 11 this.setIsLoading(true); 12 try { 13 // ... your async logic ... 14 await new Promise(resolve => setTimeout(resolve, 1000)); 15 } finally { 16 this.setIsLoading(false); 17 } 18 } 19} 20 21const store = new MyCustomLoadingComponentStore(); 22// autorun(() => console.log(store.isLoading)); 23// store.performSomeTask();
ApiStore<TApi, TConfig>
Extends LoadingStore
. Manages an instance of your OpenAPI-generated API client and provides a standardized way to make API calls.
Purpose: Base for stores that interact with an API. Handles API client initialization and provides an apiCall
method that automatically manages loading states.
Generics:
TApi
: The type of your generated API client (e.g., PetApi
).TConfig
: The configuration type for your TApi
client (e.g., Configuration
from the generated client).Key Members:
api: TApi | null
(observable): The API client instance.apiIsSet: boolean
(computed): True if the API client is initialized.setApi(api: TApi)
(action): Sets the API client.initApi(config: TConfig)
(action, must be implemented by subclasses): Initializes and sets the API client.apiCall(endpoint: keyof TApi, args: any)
(flow): Makes an API call using the specified endpoint method on this.api
.Example:
1import { ApiStore } from 'mobx-openapi-stores'; 2import { makeObservable, action } from 'mobx'; 3// Assuming you have a PetApi generated by OpenAPI 4import { PetApi, Configuration as PetApiConfig, Pet } from './your-api-client'; 5 6class PetApiService extends ApiStore<PetApi, PetApiConfig> { 7 constructor() { 8 super('PetApiService'); // Pass a name for easier debugging 9 // initApi is bound, other actions might need makeObservable if added here 10 } 11 12 // Implementation of the abstract-like method 13 initApi(config: PetApiConfig) { 14 this.setApi(new PetApi(config)); 15 } 16 17 async fetchPetById(petId: number): Promise<Pet | undefined> { 18 if (!this.apiIsSet) { 19 console.error("API not initialized"); 20 return undefined; 21 } 22 // 'getPetById' is a method on your PetApi 23 // The second argument matches the parameters of PetApi.getPetById 24 return await this.apiCall('getPetById', { petId }); 25 } 26} 27 28// const petStore = new PetApiService(); 29// petStore.initApi(new PetApiConfig({ basePath: "http://localhost:3000/api" })); 30// petStore.fetchPetById(1).then(pet => console.log(pet));
SingleStore<TApi, TSingle>
Extends ApiStore
. Manages a single observable entity, referred to as current
.
Purpose: Useful for scenarios like managing a selected item, user profile, or any standalone piece of data that might be fetched from an API.
Generics:
TApi
: Inherited from ApiStore
.TSingle
: The type of the single entity being managed (must have an id
property).Key Members:
_current: TSingle | null
(observable): The internal storage for the current entity.current: TSingle | null
(computed): Provides access to _current
.setCurrent(item: TSingle | null)
(action): Sets the current entity.isCurrentSet: boolean
(computed): True if _current
is not null.clearCurrent()
(action): Sets _current
to null.editCurrent(updatedFields: Partial<TSingle>)
(action): Merges updates into the current
entity.Example:
1import { SingleStore } from 'mobx-openapi-stores'; 2import { makeObservable, action } from 'mobx'; 3import { UserApi, Configuration as UserApiConfig, User } from './your-api-client'; // Assuming User type 4 5class UserProfileStore extends SingleStore<UserApi, User> { 6 constructor() { 7 super('UserProfileStore'); 8 // makeObservable for custom actions 9 } 10 11 initApi(config: UserApiConfig) { 12 this.setApi(new UserApi(config)); 13 } 14 15 async fetchUserProfile(userId: string) { 16 const user = await this.apiCall('getUserById', { userId }); // Assuming getUserById endpoint 17 if (user) { 18 this.setCurrent(user as User); 19 } 20 } 21 22 updateUserName(newName: string) { 23 if (this.current) { 24 this.editCurrent({ name: newName }); 25 // Optionally, call API to persist change: 26 // this.apiCall('updateUser', { userId: this.current.id, userDto: { name: newName }}); 27 } 28 } 29} 30 31// const profileStore = new UserProfileStore(); 32// profileStore.initApi(new UserApiConfig({ basePath: "/api" })); 33// profileStore.fetchUserProfile('user123');
CollectionStore<TApi, TSingle, TCollection>
Extends SingleStore
. Manages a collection (array) of observable entities. The current
item from SingleStore
can be used to represent a selected item from this collection.
Purpose: Ideal for managing lists of items, such as products, posts, todos, etc.
Generics:
TApi
: Inherited.TSingle
: The type of individual items in the collection (must have an id
).TCollection
: The type of the collection itself, defaults to TSingle[]
.Key Members:
_collection: TCollection
(observable): The internal observable array.collection: TCollection
(computed): Accessor for _collection
.setCollection(newCollection: TCollection)
(action): Replaces the entire collection.addItem(newItem: TSingle, setCurrent?: boolean)
(action): Adds an item.setItem(item: TSingle, setCurrent?: boolean)
(action): Updates an existing item or adds it if not present.editItem(updatedItem: TSingle, setCurrent?: boolean)
(action): Merges updates into an existing item by ID.removeItem(id: TSingle['id'])
(action): Removes an item by ID.getById(id: TSingle['id']): TSingle | undefined
: Retrieves an item by ID (checks current
first, then collection
).Example:
1import { CollectionStore } from 'mobx-openapi-stores'; 2import { makeObservable, action } from 'mobx'; 3import { ProductApi, Configuration as ProductApiConfig, Product } from './your-api-client'; 4 5class ProductListStore extends CollectionStore<ProductApi, Product> { 6 constructor() { 7 super('ProductListStore'); 8 // makeObservable for custom actions 9 } 10 11 initApi(config: ProductApiConfig) { 12 this.setApi(new ProductApi(config)); 13 } 14 15 async fetchProducts() { 16 // Assuming 'listProducts' is an endpoint that returns Product[] 17 const products = await this.apiCall('listProducts', {}); 18 if (products) { 19 this.setCollection(products as Product[]); 20 } 21 } 22 23 selectProduct(productId: string) { 24 const product = this.getById(productId); 25 if (product) { 26 this.setCurrent(product); // Sets the selected product as 'current' 27 } 28 } 29} 30 31// const productStore = new ProductListStore(); 32// productStore.initApi(new ProductApiConfig({ basePath: "/api" })); 33// productStore.fetchProducts();
CrudCollectionStore<TApi, TSingle>
Extends CollectionStore<TApi, TSingle>
. This store provides a foundation for collections that require API-driven CRUD (Create, Read, Update, Delete) operations. Instead of direct CRUD methods, it offers protected helper methods (e.g., _fetchAll
, _create
) that handle the API interaction and state updates. Subclasses implement public-facing methods that call these protected helpers, often using MobX flow
for asynchronous actions.
Purpose: Simplifies creating stores for collections that need API-based CRUD, by handling common logic for API calls and local state management (collection and current item updates).
Generics:
TApi
: The type of your generated API client (e.g., BudgetApi
). Inherited from CollectionStore
.TSingle
: The type of individual items in the collection (must have an id
). Inherited from CollectionStore
.TCollection
: The type of the collection, defaults to TSingle[]
. Inherited from CollectionStore
.Key Protected Helper Methods (to be called by subclass methods):
_fetchAll(endpoint: keyof TApi, params: any): Promise<TSingle[] | undefined>
: Fetches all items using the specified API endpoint
and params
. Updates this.collection
on success._fetch(endpoint: keyof TApi, params: any): Promise<TSingle | undefined>
: Fetches a single item by ID using the specified API endpoint
and params
. Sets this.current
and updates the item in this.collection
on success._create(endpoint: keyof TApi, params: any): Promise<TSingle | undefined>
: Creates an item using the specified API endpoint
and params
. Adds the new item to this.collection
and sets it as this.current
on success._update(endpoint: keyof TApi, params: any): Promise<TSingle | undefined>
: Updates an item using the specified API endpoint
and params
. Updates the item in this.collection
and this.current
(if it's the same item) on success._delete(endpoint: keyof TApi, params: any): Promise<void | undefined>
: Deletes an item using the specified API endpoint
and params
. Removes the item from this.collection
and clears this.current
if it was the deleted item.ApiResult<TApi, TEndpointName>
Utility Type:
This package exports an ApiResult
utility type: type ApiResult<TApi extends ApiType, TEndpointName extends keyof TApi> = ThenArg<ReturnType<TApi[TEndpointName]>>;
(where ThenArg
extracts the resolved type of a Promise).
It's used to strongly type the return values of your flow
-wrapped API calls, representing the data returned by the specific API endpoint.
Example (BudgetStore):
1import { 2 Configuration, 3 CreateBudgetDto, 4 Budget, 5 BudgetApi, 6 UpdateBudgetDto, 7} from './your-api-client'; // Assuming these types from your generated API client 8import { CrudCollectionStore, type ApiResult } from 'mobx-openapi-stores'; 9import { action, flow, makeObservable } from 'mobx'; 10import { toFlowGeneratorFunction } from 'to-flow-generator-function'; // Utility to help with MobX flow typing 11 12// Define TApi for convenience if used multiple times 13type Api = BudgetApi; 14 15export class BudgetStore extends CrudCollectionStore<Api, Budget> { 16 constructor() { 17 super('BudgetStore'); // Name for debugging 18 makeObservable(this, { 19 // initApi is action.bound in ApiStore, no need to repeat unless overriding 20 // Public facing methods are MobX flows 21 fetchAll: flow, 22 fetchById: flow, 23 createBudget: flow, 24 updateBudget: flow, 25 deleteBudget: flow, 26 }); 27 } 28 29 // Must be implemented by subclass of ApiStore 30 initApi(config: Configuration) { 31 this.setApi(new BudgetApi(config)); 32 } 33 34 fetchAll = flow<ApiResult<Api, 'budgetControllerFindAllV1'>, []>( 35 toFlowGeneratorFunction(async () => { 36 // 'budgetControllerFindAllV1' is the method name on your BudgetApi 37 // Second argument is the parameters object for that endpoint 38 return await this._fetchAll('budgetControllerFindAllV1', {}); 39 }), 40 ); 41 42 fetchById = flow< 43 ApiResult<Api, 'budgetControllerFindOneV1'>, 44 [id: Budget['id']] 45 >( 46 toFlowGeneratorFunction(async (id: Budget['id']) => { 47 return await this._fetch('budgetControllerFindOneV1', { id }); 48 }), 49 ); 50 51 createBudget = flow< 52 ApiResult<Api, 'budgetControllerCreateV1'>, 53 [budgetDto: CreateBudgetDto] 54 >( 55 toFlowGeneratorFunction(async (budgetDto: CreateBudgetDto) => { 56 return await this._create('budgetControllerCreateV1', { 57 createBudgetDto: budgetDto, // Parameter name must match the API client method 58 }); 59 }), 60 ); 61 62 updateBudget = flow< 63 ApiResult<Api, 'budgetControllerUpdateV1'>, 64 [id: Budget['id'], budgetDto: UpdateBudgetDto] 65 >( 66 toFlowGeneratorFunction( 67 async (id: Budget['id'], budgetDto: UpdateBudgetDto) => { 68 return await this._update('budgetControllerUpdateV1', { 69 id, // Parameter name must match 70 updateBudgetDto: budgetDto, // Parameter name must match 71 }); 72 }, 73 ), 74 ); 75 76 deleteBudget = flow< 77 ApiResult<Api, 'budgetControllerRemoveV1'>, 78 [id: Budget['id']] 79 >( 80 toFlowGeneratorFunction(async (id: Budget['id']) => { 81 return await this._delete('budgetControllerRemoveV1', { id }); 82 }), 83 ); 84} 85 86// const budgetStore = new BudgetStore(); 87// budgetStore.initApi(new Configuration({ basePath: "/api" })); 88// budgetStore.fetchAll();
Note on flow
Type Annotations:
Explicit type annotations for flow
(e.g., flow<ReturnType, [Arg1Type, Arg2Type]>
) along with toFlowGeneratorFunction
are recommended for complex asynchronous actions. They enhance type safety and editor autocompletion, especially when TypeScript's type inference for generator functions might be incomplete. However, for simpler flows or if type inference works reliably, they might not always be strictly necessary.
ObjectStore<TApi, TKey, TTarget, TType, TObject>
Extends SingleStore
(where TSingle
from SingleStore
corresponds to TTarget
here). Manages a dictionary-like observable object (_object
) where each key maps to either a single entity or a collection of entities.
Purpose: Useful for grouping items by a common key, e.g., tasks by project ID, comments by post ID. The current
item from SingleStore
refers to an individual TTarget
item.
Generics:
TApi
: Inherited.TKey
: The type for the keys of the main observable object (e.g., string
, number
).TTarget
: The type of the individual items being stored (must have an id
).TType
: 'single' | 'collection'
- Specifies if entries are single TTarget
items or TTarget[]
. Defaults to 'collection'
.TObject
: The overall shape of the observable object.Key Members:
_object: TObject
(observable): The internal observable object map.object: TObject
(computed): Accessor for _object
.getEntryById(id: TKey): TObject[TKey] | undefined
: Retrieves the entry (item or collection) for a key.setEntry(id: TKey, item: TObject[TKey])
: Sets or updates an entry.removeEntry(id: TKey)
: Removes an entry.entryIsSet(id: TKey): boolean
: Checks if an entry exists.TType
is 'collection'
:
addItem(entryId: TKey, item: TTarget)
: Adds item
to the collection at entryId
.editItem(itemId: TTarget['id'], itemUpdateData: TTarget)
: Updates item
within its collection.removeItem(itemId: TTarget['id'], entryId?: TKey)
: Removes item
from its collection.getItemById(itemId: TTarget['id']): TTarget | undefined
: Finds an item across all collections.getEntryIdByItemId(itemId: TTarget['id']): TKey | undefined
: Finds the entry key for an item.Example (TType = 'collection'):
1import { ObjectStore } from 'mobx-openapi-stores'; 2import { makeObservable, action } from 'mobx'; 3import { CommentApi, Configuration as CommentApiConfig, Comment, Post } from './your-api-client'; // Assuming types 4 5// Store to manage comments grouped by post ID 6class PostCommentsStore extends ObjectStore<CommentApi, Post['id'], Comment, 'collection'> { 7 constructor() { 8 super('PostCommentsStore'); 9 // makeObservable for custom actions 10 } 11 12 initApi(config: CommentApiConfig) { 13 this.setApi(new CommentApi(config)); 14 } 15 16 async fetchCommentsForPost(postId: Post['id']) { 17 // Assuming 'listCommentsByPostId' endpoint 18 const comments = await this.apiCall('listCommentsByPostId', { postId }); 19 if (comments) { 20 this.setEntry(postId, comments as Comment[]); 21 } 22 } 23 24 async addComment(postId: Post['id'], commentData: { text: string }) { 25 // Assuming 'createComment' endpoint that takes postId and comment data 26 const newComment = await this.apiCall('createComment', { postId, commentDto: commentData }); 27 if (newComment) { 28 if (!this.entryIsSet(postId)) { 29 this.setEntry(postId, []); // Initialize if first comment for this post 30 } 31 this.addItem(postId, newComment as Comment); 32 } 33 } 34 35 // current (from SingleStore) can be used to manage a currently selected/focused comment 36 selectComment(comment: Comment) { 37 this.setCurrent(comment); 38 } 39} 40 41// const commentsStore = new PostCommentsStore(); 42// commentsStore.initApi(new CommentApiConfig({ basePath: "/api" })); 43// commentsStore.fetchCommentsForPost('post123');
makeObservable
: Remember to call makeObservable
in your subclasses for any new action
, computed
, flow
, or observable
properties you define. Base store properties are already made observable.initApi
: Subclasses extending ApiStore
(or stores derived from it) must implement initApi(config: TConfig)
to instantiate and set their specific API client.apiCall
method (and protected helpers in CrudCollectionStore
) include basic error handling for API client initialization and manage loading states. You may want to add more specific error handling in your consuming code.name
string to the constructor of stores extending ApiStore
(e.g., super('MyStoreName')
). This is used in error messages for easier debugging (e.g., "MyStoreName Api is not set").These stores are heavily reliant on TypeScript generics. Understanding how to provide the correct types for TApi
, TSingle
, TConfig
, etc., is crucial for leveraging their type-safety benefits. Refer to your OpenAPI-generated client for the specific types to use.
Contributions are welcome! Please feel free to submit issues and pull requests.
This project is licensed under the MIT License - see the LICENSE.md.
No vulnerabilities found.
No security vulnerabilities found.