@wasserstoff/tribes-sdk
Version:
SDK for integrating with Tribes by Astrix platform on any EVM compatible chain
241 lines (240 loc) • 8.2 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.BaseModule = void 0;
const ethers_1 = require("ethers");
const core_1 = require("../types/core");
const errors_1 = require("../types/errors");
/**
* Base class for all SDK modules with caching support
*/
class BaseModule {
/**
* Create a new module instance
* @param provider Provider for read operations
* @param config SDK configuration
*/
constructor(provider, config) {
this.signer = null;
// Use unknown for cached value type
this.cache = new Map();
this.currentBlockNumber = 0;
// Default cache settings
this.DEFAULT_CACHE_TIME = 30000; // 30 seconds
this.provider = provider;
this.config = config;
// Create block listener function
this.blockListener = (blockNumber) => {
this.currentBlockNumber = blockNumber;
// Invalidate cache entries that depend on block number
this.invalidateBlockBasedCache(blockNumber);
};
// Set up block monitoring for cache invalidation
this.setupBlockMonitoring();
}
/**
* Sets up block monitoring to invalidate cache based on new blocks
*/
setupBlockMonitoring() {
// Clean up any existing subscription
this.cleanupBlockSubscription();
// Start monitoring blocks
this.provider.on('block', this.blockListener);
// Initialize current block number
this.provider.getBlockNumber()
.then(blockNumber => {
this.currentBlockNumber = blockNumber;
})
.catch(error => {
this.log(`Error getting initial block number: ${error.message}`);
});
}
/**
* Invalidates cache entries that are bound to specific blocks
*/
invalidateBlockBasedCache(currentBlock) {
for (const [key, entry] of this.cache.entries()) {
// If this entry has a blockNumber and it's not the current block
if (entry.blockNumber && entry.blockNumber < currentBlock) {
this.cache.delete(key);
}
}
}
/**
* Clean up block subscription
*/
cleanupBlockSubscription() {
if (this.blockListener) {
this.provider.off('block', this.blockListener);
}
}
/**
* Set the signer for write operations
* @param signer Ethers signer object
*/
setSigner(signer) {
this.signer = signer;
}
/**
* Check if a signer is connected
*/
requireSigner() {
if (!this.signer) {
throw new errors_1.AstrixSDKError(core_1.ErrorType.CONNECTION_ERROR, 'Signer is required for this operation. Please connect a wallet.');
}
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
return this.signer;
}
/**
* Get a contract instance
* @param address Contract address
* @param abi Contract ABI
* @param useSigner Whether to use the signer or provider
*/
getContract(address, abi, useSigner = false) {
if (useSigner) {
const signer = this.requireSigner();
return new ethers_1.ethers.Contract(address, abi, signer);
}
return new ethers_1.ethers.Contract(address, abi, this.provider);
}
/**
* Get cached data or fetch it if not available
* @param key Cache key
* @param fetchFn Function to fetch data if not in cache
* @param options Cache options
*/
async getWithCache(key, fetchFn, options) {
const cacheKey = this.buildCacheKey(key);
const cachedItem = this.cache.get(cacheKey);
// Determine whether to use cache
const shouldUseCache = this.shouldUseCache(cachedItem, options);
if (shouldUseCache) {
return cachedItem.value;
}
// Cache miss or expired, fetch fresh data
try {
const result = await fetchFn();
// Store in cache
this.cache.set(cacheKey, {
value: result,
timestamp: Date.now(),
blockNumber: options?.blockBased ? this.currentBlockNumber : undefined
});
return result;
}
catch (error) {
this.handleError(error, `Failed to fetch data for cache key: ${cacheKey}`);
throw error;
}
}
/**
* Determine if cached data should be used
*/
shouldUseCache(cachedItem, options) {
if (!cachedItem)
return false;
// If caching is disabled
if (options?.disabled)
return false;
// If block-based caching is used
if (options?.blockBased && cachedItem.blockNumber !== undefined) {
return cachedItem.blockNumber === this.currentBlockNumber;
}
// Time-based caching
const maxAge = options?.maxAge || this.config.cache?.defaultMaxAge || this.DEFAULT_CACHE_TIME;
const now = Date.now();
const age = now - cachedItem.timestamp;
return age < maxAge;
}
/**
* Build a standardized cache key
*/
buildCacheKey(key) {
const chainId = this.config.chainId || 'unknown';
return `${chainId}:${key}`;
}
/**
* Invalidate a specific cache entry
* @param key Cache key to invalidate
*/
invalidateCache(key) {
const cacheKey = this.buildCacheKey(key);
this.cache.delete(cacheKey);
}
/**
* Invalidate cache entries that match a pattern
* @param pattern Pattern to match cache keys against
*/
invalidateCacheByPattern(pattern) {
const prefix = this.config.chainId ? `${this.config.chainId}:` : '';
const fullPattern = `${prefix}${pattern}`;
for (const key of this.cache.keys()) {
if (key.includes(fullPattern)) {
this.cache.delete(key);
}
}
}
/**
* Clear all cached data
*/
clearCache() {
this.cache.clear();
}
/**
* Handle errors by wrapping them in AstrixSDKError
* @param error Original error
* @param message Error message
* @param type Error type
*/
handleError(error, message = 'Operation failed', type = core_1.ErrorType.CONTRACT_ERROR) {
if (error instanceof errors_1.AstrixSDKError) {
throw error;
}
let errorMessage = message;
let code = undefined; // Initialize code explicitly
// Try to extract code and reason from ethers error
if (typeof error === 'object' && error !== null) {
const ethersError = error;
if (ethersError.code) {
code = ethersError.code;
}
else {
// Optionally set a default SDK error code string if needed, otherwise leave undefined
code = 'SDK_ERROR_FALLBACK'; // Example fallback code string
}
if (typeof ethersError.reason === 'string') {
errorMessage += `: ${ethersError.reason}`;
}
else if (typeof ethersError.message === 'string') {
errorMessage += `: ${ethersError.message}`;
}
}
else {
code = 'SDK_ERROR_FALLBACK'; // Fallback code string for non-object errors
}
throw new errors_1.AstrixSDKError(type, errorMessage, code, error); // Pass the extracted/fallback code
}
/**
* Log a message if verbose mode is enabled
* @param message Message to log
* @param data Optional data to log
*/
log(_message, _data) {
// Logging is currently disabled within the SDK itself.
// Consuming applications can implement their own logging if needed.
// if (this.config.verbose) {
// console.log(`[Astrix SDK] ${message}`);
// if (data !== undefined) {
// console.log(data);
// }
// }
}
/**
* Dispose of resources and subscriptions
*/
dispose() {
this.cleanupBlockSubscription();
this.clearCache();
}
}
exports.BaseModule = BaseModule;