npm package: @servicetitan/tanstack-query-mobx
Description: TanStack Query (React Query) integration for MobX stores with react-ioc dependency injection. Provides declarative data fetching, caching, synchronization, and state management with automatic query deduplication and observable state.
Latest version: 4.5.1

# @servicetitan/tanstack-query-mobx

TanStack Query integration for MobX stores with `@servicetitan/react-ioc` dependency injection. Provides declarative data fetching, caching, and state management with automatic query deduplication.

## Installation

```bash
npm install @servicetitan/tanstack-query-mobx
```

## Quick Start

**1. Provide the Query Client**

```tsx
import { getQueryClient } from '@servicetitan/tanstack-query-mobx';

export const App = provide({
    singletons: [getQueryClient()],
})(observer(() => <YourApp />));
```

**2. Create a Store**

```tsx
import { QueryApiStore, QueryApiOptions } from '@servicetitan/tanstack-query-mobx';

@injectable()
export class JobsStore extends QueryApiStore<Job[]> {
    @inject(JobsApi) private api?: JobsApi;

    get queryOptions(): QueryApiOptions<Job[]> {
        return {
            queryKey: ['scheduling', 'jobs'],
            queryFn: async () => (await this.api?.getJobs())?.data ?? [],
        };
    }
}
```

**3. Use in Components**

```tsx
const [{ data, isLoading }] = useDependencies(JobsStore);
```

## Key Concepts

**Query Keys** - Arrays that uniquely identify queries. The first element **must** be a namespace that scopes the cache (e.g., `['scheduling', 'jobs', ...]`), which keeps caches isolated—especially important with shared `page` and `app` clients. Subsequent elements identify the specific resource and parameters. Changes trigger automatic refetch:

```tsx
queryKey: ['scheduling', 'jobs'],           // module + resource
queryKey: ['scheduling', 'jobs', jobId],    // module + resource + parameter (refetches when jobId changes)
```

**Observable State** - `data`, `initialized`, `isLoading`, `isFetching`, `isError`, `error`

**Stale Time** - Default 10 minutes. Set `staleTime: 0` for always fresh, `Infinity` for never stale.

## Common Patterns

**Mutations** ([MutationApi reference](./src/utils/mutation.api.ts))

```tsx
snooze = this.addMutation<void, { id: number }>(() => ({
    mutationFn: async arg => (await this.api?.snooze(arg.id))?.data,
    invalidatedQueries: [['scheduling', 'tasks']], // Refetch after success
}));

await this.snooze.runMutation({ id: 123 });
```

See `runMutation()` JSDoc for advanced options like custom `onSuccess`/`onError` handlers.

**Multiple Queries** ([QueryApiStore reference](./src/utils/query-api.store.ts))

Your primary query should use the `queryOptions` getter (shown in Quick Start). Use `addQuery()` only for secondary queries alongside it:

```tsx
stats = this.addQuery<Stats>(() => ({
    queryKey: ['scheduling', 'stats'],
    queryFn: async () => (await this.api?.getStats())?.data,
}));
```

Use `addQuery()` in your store to manage multiple independent queries. Each query has its own lifecycle and state.

**Shared Clients (Micro-Frontends)**

For micro-frontend architectures where multiple app instances need to share query cache, use shared clients at the provider or query level.

**Client types:**

- `'page'` - Uses reference counting. Disposes when the last component using `getQueryClient('page')` unmounts.
- `'app'` - Persists until browser refresh/close. Only available via `globalClient` option.

See [getQueryClient](./src/utils/get-query-client.ts) JSDoc for provider-level examples and [QueryApiOptions.globalClient](./src/utils/query.api.ts) JSDoc for query-level examples.

**Data-Driven Queries** ([QueryApiOptions reference](./src/utils/query.api.ts))

```tsx
queryKey: ['scheduling', 'logs', jobId],  // Auto-refetches when jobId changes
enabled: jobId > 0,         // Only runs when condition is true
```

MobX reactions automatically track `queryKey` dependencies. When `jobId` changes, the query refetches.

## API Reference

All classes have comprehensive TSDoc comments - use your IDE's intellisense or read the source.

### Core Classes

**[QueryApiStore](./src/utils/query-api.store.ts)** - Base class for query stores

- Extend this to create stores that fetch and cache data
- Provides observable `data`, `isLoading`, `error`, and `initialized` properties
- Use `addQuery()` to add multiple queries and `addMutation()` for mutations
- See JSDoc for full lifecycle methods

**[QueryApi](./src/utils/query.api.ts)** - Individual query manager

- Manages a single TanStack Query with MobX integration
- Usually created via `addQuery()` in QueryApiStore
- Methods: `invalidate()`, `updateQueryData()`, `cancel()`, `refetch()`
- See JSDoc for lifecycle and state management details

**[MutationApi](./src/utils/mutation.api.ts)** - Mutation manager for create/update/delete operations

- Use `runMutation(args, options?)` to execute with automatic query invalidation
- Provides observable `isPending`, `isSuccess`, `isError`, `error` state
- Configure `invalidatedQueries` to automatically refetch after mutation success
- See JSDoc for full configuration options and examples

### Configuration Types

**[QueryApiOptions](./src/utils/query.api.ts)** - Full query configuration extending TanStack Query's QueryObserverOptions

- `queryKey`, `queryFn`, `staleTime`, `enabled`, etc.
- Additional: `disposeQuery`, `autoInitialize`, `onSuccess`, `onError`, `globalClient`

**[MutationApiOptions](./src/utils/mutation.api.ts)** - Full mutation configuration extending TanStack Query's MutationObserverOptions

- `mutationFn`, `onSuccess`, `onError`, etc.
- Additional: `invalidatedQueries`, `globalClient`

See [TanStack Query docs](https://tanstack.com/query/latest/docs) for core query concepts and all standard options.

## Learn More

### Source Code & Documentation

All classes, interfaces, and methods have comprehensive TSDoc comments. Explore the source for detailed API documentation:

**Core Implementations**

- [QueryApiStore.ts](./src/utils/query-api.store.ts) - Main store base class with `addQuery()` and `addMutation()`
- [QueryApi.ts](./src/utils/query.api.ts) - Query lifecycle, state management, and invalidation
- [MutationApi.ts](./src/utils/mutation.api.ts) - Mutation operations with automatic invalidation
- [getQueryClient.ts](./src/utils/get-query-client.ts) - Query client factory with scoping options

**Configuration Types**

- [QueryApiOptions](./src/utils/query.api.ts#L30) - Complete query configuration interface
- [MutationApiOptions](./src/utils/mutation.api.ts#L25) - Complete mutation configuration interface

**Utilities**

- [client-helpers.ts](./src/utils/client-helpers.ts) - Shared client management for micro-frontends
- [QueryClientStore.ts](./src/utils/query-client.store.ts) - Query client lifecycle and invalidation helpers

### External Resources

- **[TanStack Query Documentation](https://tanstack.com/query/latest/docs)** - Core concepts: queries, mutations, caching, invalidation
- **[MobX Documentation](https://mobx.js.org/)** - Understanding observables and reactions
- **[@servicetitan/react-ioc](https://github.com/servicetitan/react-ioc)** - Dependency injection pattern

## Testing

```tsx
import { ContainerBuilder } from '@servicetitan/tanstack-query-mobx/test';

const { container, initialize } = new ContainerBuilder().add(JobsStore).add(JobsApi).build();

jest.spyOn(container.get(JobsApi), 'getJobs').mockResolvedValue({
    data: [{ id: 1, title: 'Job' }],
});

await initialize(); // Runs lifecycle + waits for initialized
expect(container.get(JobsStore).data).toHaveLength(1);
```

### Testing Utilities

All testing utilities have comprehensive JSDoc comments with examples. Explore the source for detailed API documentation:

**[ContainerBuilder](./src/test/mock-adapters/container-builder.ts)** - Test container builder for dependency injection

- Simplifies setting up stores and dependencies for testing
- Methods: `add()`, `build()`, `initialize()`, `addQueryClient()`
- Automatically waits for all stores and queries to initialize

**[waitFor](./src/test/mock-adapters/container-builder.ts)** - MobX reaction-based wait helper

- Waits for observable conditions to become true
- Returns promise that resolves when condition is met

**[SpyBuilder](./src/test/mock-adapters/spy-builder.ts)** - Abstract base class for mocking API services

- Create mock API services with jest spies
- Methods: `add()`, `getSpy()`
- Utilities: `createAxiosResponse()`, `createAxiosError()`

## License

Part of the `@servicetitan/anvil-uikit-contrib` monorepo.