data flow - saltict/Demo-Docs GitHub Wiki
Comprehensive overview of data flow patterns, request/response cycles, and integration points within the SubWallet Services SDK.
📖 Navigation
- Request/Response Flow
- Configuration Flow
- Error Flow
- Service Integration
- Data Transformation
- Caching and Optimization
%%{init: {'theme':'dark', 'themeVariables': {
'primaryColor': '#ff6b6b',
'primaryTextColor': '#fff',
'primaryBorderColor': '#ff6b6b',
'lineColor': '#ffa726',
'sectionBkColor': '#2d3748',
'altSectionBkColor': '#1a202c',
'gridColor': '#4a5568',
'secondaryColor': '#4a5568',
'tertiaryColor': '#2d3748'
}}}%%
sequenceDiagram
participant Client
participant SDK
participant ServiceAPI
participant BaseAPI
participant HTTPClient
participant SubWalletServices
Client->>SDK: Method Call
SDK->>ServiceAPI: Route to Service
ServiceAPI->>ServiceAPI: Validate Parameters
ServiceAPI->>BaseAPI: Format Request
BaseAPI->>BaseAPI: Add Headers & Config
BaseAPI->>HTTPClient: Execute Request
HTTPClient->>SubWalletServices: HTTP Request
SubWalletServices-->>HTTPClient: HTTP Response
HTTPClient-->>BaseAPI: Raw Response
BaseAPI->>BaseAPI: Process Response
BaseAPI->>BaseAPI: Handle Errors
BaseAPI-->>ServiceAPI: Typed Response
ServiceAPI-->>SDK: Service Result
SDK-->>Client: Final Result
// Client initiates request
const priceHistory = await sdk.priceHistoryApi.getPriceHistory('DOT', '1W');
// PriceHistoryApi processes the request
export class PriceHistoryApi extends BaseApi {
async getPriceHistory(token: string, type: Timeframe) {
// Parameter validation
if (!token || !type) {
throw new ValidationError('Token and type are required');
}
// Route to BaseApi
return this.get<HistoryTokenPriceJSON>({
params: { token, type },
path: 'price-history'
});
}
}
// BaseApi handles the HTTP communication
export class BaseApi {
async get<T>(options: GetOptions): Promise<T> {
return this.request<T>({
method: 'GET',
...options
});
}
async request<T>(options: RequestOptions): Promise<T> {
// Build request URL with parameters
let requestUrl = `${this.config.baseUrl}/${options.path}`;
// Add query parameters
if (options.params) {
const queryParams = new URLSearchParams(options.params).toString();
requestUrl += `?${queryParams}`;
}
// Execute HTTP request with configuration
const response = await fetch(requestUrl, {
method: options.method ?? 'GET',
headers: {
'Content-Type': 'application/json',
...this.config.headers,
...options.headers
},
body: options.data ? JSON.stringify(options.data) : undefined
});
// Process response
return this.processResponse<T>(response);
}
}
%%{init: {'theme':'dark', 'themeVariables': {
'primaryColor': '#ff6b6b',
'primaryTextColor': '#fff',
'primaryBorderColor': '#ff6b6b',
'lineColor': '#ffa726',
'sectionBkColor': '#2d3748',
'altSectionBkColor': '#1a202c',
'gridColor': '#4a5568',
'secondaryColor': '#4a5568',
'tertiaryColor': '#2d3748'
}}}%%
graph TD
A[Constants Module] --> B[Default Config]
B --> C[SDK Instance Config]
D[Client updateConfig] --> C
E[Environment Variables] --> C
C --> F[API Configuration]
F --> G[Service Headers]
F --> H[BalanceDetectionApi]
F --> I[PriceHistoryApi]
F --> J[SwapApi]
F --> K[XcmApi]
F --> L[CardanoApi]
G --> M[Platform Headers]
G --> N[Version Headers]
G --> O[Custom Headers]
// Client updates configuration
sdk.updateConfig({
platform: 'mobile',
baseUrl: 'https://staging-api.subwallet.app'
});
// SDK processes the update
updateConfig(newConfig: Partial<SDKOption>) {
// Merge with existing configuration
this.config = {
...this.config,
...newConfig
};
// Regenerate API configuration
const apiConfig = this.apiConfig;
// Update all services
this.serviceList.forEach((service) => {
service.updateConfig(apiConfig);
});
}
// Dynamic header generation based on configuration
get headers() {
return {
'SW-SDK-ChainList-Version': this.config.chainListVersion,
'SW-SDK-Platform': this.config.platform,
'SW-SDK-Version': this.config.sdkVersion
};
}
get apiConfig(): ApiConfig {
return {
baseUrl: this.config.baseUrl,
headers: this.headers
};
}
%%{init: {'theme':'dark', 'themeVariables': {
'primaryColor': '#ff6b6b',
'primaryTextColor': '#fff',
'primaryBorderColor': '#ff6b6b',
'lineColor': '#ffa726',
'sectionBkColor': '#2d3748',
'altSectionBkColor': '#1a202c',
'gridColor': '#4a5568',
'secondaryColor': '#4a5568',
'tertiaryColor': '#2d3748'
}}}%%
graph TD
A[Error Source] --> B{Error Type}
B --> C[Network Error]
B --> D[HTTP Error]
B --> E[API Error]
B --> F[Validation Error]
C --> G[Connection Issues]
C --> H[Timeout]
C --> I[DNS Resolution]
D --> J[4xx Client Errors]
D --> K[5xx Server Errors]
E --> L[Business Logic Errors]
E --> M[Service Unavailable]
F --> N[Parameter Validation]
F --> O[Type Validation]
G --> P[SdkError Creation]
H --> P
I --> P
J --> P
K --> P
L --> P
M --> P
N --> P
O --> P
P --> Q[Error Enrichment]
Q --> R[Client Notification]
// Error processing in BaseApi
async request<T>(options: RequestOptions): Promise<T> {
try {
const response = await fetch(requestUrl, fetchOptions);
// Handle HTTP errors
if (!response.ok) {
const errorText = await response.text();
throw new SdkError(errorText, {
code: response.status,
type: ErrorType.REQUEST_ERROR
});
}
const apiResponse = await response.json() as SWApiResponse<T>;
// Handle API errors
if (apiResponse.error) {
throw new SdkError(apiResponse.error.message || 'Unknown error', {
code: apiResponse.error.code
});
}
return apiResponse.data || apiResponse.result;
} catch (error) {
// Transform and re-throw with context
throw new Error(`Failed to fetch data: ${(error as Error).message}`);
}
}
%%{init: {'theme':'dark', 'themeVariables': {
'primaryColor': '#ff6b6b',
'primaryTextColor': '#fff',
'primaryBorderColor': '#ff6b6b',
'lineColor': '#ffa726',
'sectionBkColor': '#2d3748',
'altSectionBkColor': '#1a202c',
'gridColor': '#4a5568',
'secondaryColor': '#4a5568',
'tertiaryColor': '#2d3748'
}}}%%
graph LR
subgraph "Client Layer"
A[Application Code]
end
subgraph "SDK Layer"
B[SubWalletServiceSDK]
C[Service Registry]
end
subgraph "Service Layer"
D[BalanceDetectionApi]
E[PriceHistoryApi]
F[SwapApi]
G[XcmApi]
H[CardanoApi]
end
subgraph "Infrastructure"
I[BaseApi]
J[HTTP Client]
K[Error Handler]
end
subgraph "External Services"
L[SubWallet Backend]
M[Blockchain Networks]
N[Price Feeds]
end
A --> B
B --> C
C --> D
C --> E
C --> F
C --> G
C --> H
D --> I
E --> I
F --> I
G --> I
H --> I
I --> J
I --> K
J --> L
J --> M
J --> N
Some operations may involve multiple services working together:
// Example: Portfolio balance with price data
async getPortfolioValue(addresses: string[], chains: string[]) {
// 1. Get balances from BalanceDetectionApi
const balances = await sdk.balanceDetectionApi.getBalances({
addresses,
chains
});
// 2. Get current prices for detected tokens
const pricePromises = balances.tokens.map(token =>
sdk.priceHistoryApi.getCurrentPrice(token.symbol)
);
const prices = await Promise.all(pricePromises);
// 3. Calculate total portfolio value
return balances.tokens.map((token, index) => ({
...token,
price: prices[index],
value: token.balance * prices[index].current
}));
}
%%{init: {'theme':'dark', 'themeVariables': {
'primaryColor': '#ff6b6b',
'primaryTextColor': '#fff',
'primaryBorderColor': '#ff6b6b',
'lineColor': '#ffa726',
'sectionBkColor': '#2d3748',
'altSectionBkColor': '#1a202c',
'gridColor': '#4a5568',
'secondaryColor': '#4a5568',
'tertiaryColor': '#2d3748'
}}}%%
graph LR
A[Raw HTTP Response] --> B[JSON Parsing]
B --> C[Schema Validation]
C --> D[Type Transformation]
D --> E[Data Normalization]
E --> F[Client Response]
G[Error Detection] --> H[Error Classification]
H --> I[Error Enrichment]
I --> J[Error Response]
B --> G
C --> G
D --> G
// Raw API response
interface RawPriceResponse {
history: Array<{
time: number;
value: number;
}>;
}
// Transformed client response
interface PriceChartPoint {
time: number;
value: number;
formatted?: {
time: string;
value: string;
};
}
// Transformation logic
private transformPriceHistory(raw: RawPriceResponse): HistoryTokenPriceJSON {
return {
history: raw.history.map(point => ({
time: point.time,
value: point.value,
formatted: {
time: new Date(point.time).toISOString(),
value: `$${point.value.toFixed(2)}`
}
}))
};
}
// Raw balance response normalization
interface RawBalanceResponse {
balances: Array<{
address: string;
chain: string;
tokens: Array<{
symbol: string;
balance: string;
decimals: number;
}>;
}>;
}
// Client-friendly format
interface NormalizedBalance {
address: string;
chain: string;
tokens: Array<{
symbol: string;
balance: bigint;
displayBalance: string;
decimals: number;
}>;
}
%%{init: {'theme':'dark', 'themeVariables': {
'primaryColor': '#ff6b6b',
'primaryTextColor': '#fff',
'primaryBorderColor': '#ff6b6b',
'lineColor': '#ffa726',
'sectionBkColor': '#2d3748',
'altSectionBkColor': '#1a202c',
'gridColor': '#4a5568',
'secondaryColor': '#4a5568',
'tertiaryColor': '#2d3748'
}}}%%
graph TD
A[Request Initiated] --> B{Cache Check}
B -->|Hit| C[Return Cached Data]
B -->|Miss| D[Execute Request]
D --> E[Network Request]
E --> F[Response Processing]
F --> G[Cache Storage]
G --> H[Return Fresh Data]
I[Cache Invalidation] --> J[TTL Expiry]
I --> K[Manual Invalidation]
I --> L[Error-based Invalidation]
J --> B
K --> B
L --> B
// HTTP client with connection reuse
class OptimizedHttpClient {
private connectionPool: Map<string, Connection> = new Map();
async request(url: string, options: RequestOptions) {
const connection = this.getOrCreateConnection(url);
return connection.request(options);
}
private getOrCreateConnection(url: string): Connection {
const baseUrl = new URL(url).origin;
if (!this.connectionPool.has(baseUrl)) {
this.connectionPool.set(baseUrl, new Connection(baseUrl));
}
return this.connectionPool.get(baseUrl)!;
}
}
// Batch multiple requests to the same endpoint
class BatchingLayer {
private pendingRequests: Map<string, Promise<any>> = new Map();
async batchRequest<T>(key: string, requestFn: () => Promise<T>): Promise<T> {
if (this.pendingRequests.has(key)) {
return this.pendingRequests.get(key) as Promise<T>;
}
const promise = requestFn();
this.pendingRequests.set(key, promise);
// Clean up after request completes
promise.finally(() => {
this.pendingRequests.delete(key);
});
return promise;
}
}
// Service-level caching implementation
class CachingService {
private cache: Map<string, CacheEntry> = new Map();
async cachedRequest<T>(
key: string,
requestFn: () => Promise<T>,
ttl: number = 60000
): Promise<T> {
const cached = this.cache.get(key);
if (cached && Date.now() - cached.timestamp < ttl) {
return cached.data as T;
}
const fresh = await requestFn();
this.cache.set(key, {
data: fresh,
timestamp: Date.now()
});
return fresh;
}
}
🔗 Related Documentation