@cranberry-money/shared-services
Version:
Platform-agnostic API services with pure functions and dependency injection. Includes auth, portfolios, instruments, countries, sectors, and more.
266 lines (191 loc) โข 7.77 kB
Markdown
# -money/shared-services
**Platform-agnostic service functions** that work with any axios-compatible API client.
[](https://badge.fury.io/js/%40cranberry-money%2Fshared-services)
[](https://www.typescriptlang.org/)
## ๐ฆ Package Overview
This package provides reusable service functions for the Blueberry platform ecosystem. Services are pure functions that accept an API client, allowing the same business logic to work across web browsers, mobile apps, and testing environments.
### Core Philosophy
- **One service implementation, multiple platforms**: Write once, use everywhere
- **Platform owns authentication**: Each platform handles auth its own way (cookies, tokens, etc.)
- **Simple and functional**: No complex patterns, just functions that call APIs
- **Fully typed**: Complete TypeScript support with types from `-money/shared-types`
## ๐ Quick Start
### Installation
```bash
npm install -money/shared-services
```
### Basic Usage
#### Web Application (with cookies)
```typescript
// src/services/webApiClient.ts
import axios from 'axios';
export const webApiClient = axios.create({
baseURL: import.meta.env.VITE_API_URL,
withCredentials: true, // Use cookies for auth
});
```
```typescript
// src/services/portfolios.ts
import { webApiClient } from './webApiClient';
import { createPortfolioService } from '-money/shared-services';
// Create your platform-specific service
export const portfolioService = createPortfolioService(webApiClient);
// Use in your application
const portfolios = await portfolioService.getAll();
const portfolio = await portfolioService.create({ name: 'My Portfolio' });
```
#### Mobile Application (with tokens)
```typescript
// src/services/mobileApiClient.ts
import axios from 'axios';
import * as SecureStore from 'expo-secure-store';
export const mobileApiClient = axios.create({
baseURL: process.env.API_URL,
});
// Add auth token to requests
mobileApiClient.interceptors.request.use(async config => {
const token = await SecureStore.getItemAsync('authToken');
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
return config;
});
```
```typescript
// src/services/portfolios.ts
import { mobileApiClient } from './mobileApiClient';
import { createPortfolioService } from '-money/shared-services';
export const portfolioService = createPortfolioService(mobileApiClient);
```
## ๐ Service Patterns
### Pattern 1: Service Factory (Recommended)
Group related operations together:
```typescript
import { createPortfolioService } from '-money/shared-services';
import { webApiClient } from './webApiClient'; // or mobileApiClient for mobile
const portfolioService = createPortfolioService(webApiClient);
// All portfolio operations in one object
await portfolioService.getAll({ page: 1 });
await portfolioService.getByUuid('123');
await portfolioService.create({ name: 'Tech Portfolio' });
await portfolioService.update('123', { name: 'Updated Portfolio' });
await portfolioService.delete('123');
```
### Pattern 2: Individual Functions
For better tree-shaking and selective imports:
```typescript
import { getPortfolios, createPortfolio } from '-money/shared-services';
import { webApiClient } from './webApiClient'; // or mobileApiClient for mobile
// Create platform-specific functions
const getMyPortfolios = getPortfolios(webApiClient);
const createMyPortfolio = createPortfolio(webApiClient);
// Use them
await getMyPortfolios({ page: 1 });
await createMyPortfolio({ name: 'New Portfolio' });
```
## ๐งช Testing
Services are easy to test with mock API clients:
```typescript
import { createPortfolioService } from '-money/shared-services';
describe('Portfolio Service', () => {
const mockApiClient = {
get: jest.fn(),
post: jest.fn(),
patch: jest.fn(),
delete: jest.fn(),
};
const portfolioService = createPortfolioService(mockApiClient);
it('should fetch portfolios', async () => {
mockApiClient.get.mockResolvedValue({
data: { results: [], count: 0 },
});
const result = await portfolioService.getAll();
expect(mockApiClient.get).toHaveBeenCalledWith('/api/portfolios/', {
params: undefined,
});
});
});
```
## ๐ React Query Integration
Works seamlessly with React Query:
```typescript
import { useQuery, useMutation, useQueryClient } from '/react-query';
import { portfolioService } from '/portfolios';
export const usePortfolios = (params?) => {
return useQuery({
queryKey: ['portfolios', params],
queryFn: () => portfolioService.getAll(params),
staleTime: 5 * 60 * 1000,
});
};
export const useCreatePortfolio = () => {
const queryClient = useQueryClient();
return useMutation({
mutationFn: portfolioService.create,
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['portfolios'] });
},
});
};
```
## ๐ Available Services
### Financial Services
- `createPortfolioService` - Portfolio management
- `createAccountService` - Account operations
- `createTradeService` - Trading operations
- `createInstrumentService` - Financial instruments
- `createWithdrawalService` - Withdrawal requests
### Banking Services
- `createBankService` - Bank management
- `createCashAccountService` - Cash accounts
- `createTransactionService` - Transactions
### User & Compliance
- `createAuthService` - Authentication
- `createUserService` - User profiles
- `createDocumentService` - Documents
- `createTaxResidencyService` - Tax information
- `createInvestmentPreferenceService` - Investment preferences
### Reference Data
- `createCountryService` - Country data
- `createIndustryService` - Industries
- `createSectorService` - Sectors
- `createStockExchangeService` - Stock exchanges
## ๐ฆ API Client Requirements
Your API client must implement this interface:
```typescript
interface ApiClient {
get<T>(url: string, config?: AxiosRequestConfig): Promise<AxiosResponse<T>>;
post<T>(url: string, data?: any, config?: AxiosRequestConfig): Promise<AxiosResponse<T>>;
put<T>(url: string, data?: any, config?: AxiosRequestConfig): Promise<AxiosResponse<T>>;
patch<T>(url: string, data?: any, config?: AxiosRequestConfig): Promise<AxiosResponse<T>>;
delete<T>(url: string, config?: AxiosRequestConfig): Promise<AxiosResponse<T>>;
}
```
Any axios instance automatically satisfies this interface.
## ๐ Related Packages
- `-money/shared-types` - TypeScript type definitions
- `-money/shared-constants` - API endpoints and business constants
- `-money/shared-utils` - Utility functions for formatting and validation
## ๐ก Migration from Local Services
If you're currently using local service implementations in your app:
### Before (Local Implementation)
```typescript
// src/services/portfolios.ts
import webApiClient from './webApiClient';
export const getPortfolios = (params?) => {
return webApiClient.get('/api/portfolios/', { params });
};
```
### After (Shared Implementation)
```typescript
// src/services/portfolios.ts
import webApiClient from './webApiClient';
import { createPortfolioService } from '-money/shared-services';
export const portfolioService = createPortfolioService(webApiClient);
// or
export const { getAll: getPortfolios } = createPortfolioService(webApiClient);
```
## ๐ License
MIT License - see the [LICENSE](LICENSE) file for details.
---
For complete documentation and architecture details, see the [Cranberry Development Guide](../../docs/CRANBERRY-DEVELOPMENT-GUIDE.md).