UNPKG

wisdom-sdk

Version:

Core business logic and data access layer for prediction markets

447 lines (442 loc) 13.9 kB
'use strict'; var kv = require('@vercel/kv'); // src/kv-store.ts // src/logger.ts var createLogger = () => { const logLevel = process.env.LOG_LEVEL || "info"; const logLevels = { debug: 0, info: 1, warn: 2, error: 3 }; const currentLevelValue = logLevel in logLevels ? logLevels[logLevel] : logLevels.info; const formatLog = (level, obj, msg) => { const timestamp = (/* @__PURE__ */ new Date()).toISOString(); const service = "wisdom-sdk"; const objStr = JSON.stringify(obj); return `[${timestamp}] ${level.toUpperCase()} [${service}] ${msg || ""} ${objStr}`; }; return { debug: (obj, msg) => { if (currentLevelValue <= 0) { console.debug(formatLog("debug", obj, msg)); } }, info: (obj, msg) => { if (currentLevelValue <= 1) { console.info(formatLog("info", obj, msg)); } }, warn: (obj, msg) => { if (currentLevelValue <= 2) { console.warn(formatLog("warn", obj, msg)); } }, error: (obj, msg) => { if (currentLevelValue <= 3) { console.error(formatLog("error", obj, msg)); } }, child: (bindings) => { const childLogger = createLogger(); return { debug: (obj, msg) => childLogger.debug({ ...obj, ...bindings }, msg), info: (obj, msg) => childLogger.info({ ...obj, ...bindings }, msg), warn: (obj, msg) => childLogger.warn({ ...obj, ...bindings }, msg), error: (obj, msg) => childLogger.error({ ...obj, ...bindings }, msg), child: (nestedBindings) => childLogger.child({ ...bindings, ...nestedBindings }) }; } }; }; var logger = createLogger(); function getContextLogger(context) { return logger.child({ context }); } var AppError = class extends Error { constructor({ message, context = "general", code = "INTERNAL_ERROR", originalError, data }) { super(message); this.name = "AppError"; this.context = context; this.code = code; this.originalError = originalError; this.data = data; Error.captureStackTrace(this, this.constructor); } // Logs this error with appropriate context and returns it log() { const contextLogger = getContextLogger(this.context); const logObj = { code: this.code, error: this.message, ...this.originalError && { originalError: this.originalError.message }, ...this.data && { data: this.data } }; contextLogger.error(logObj, this.message); return this; } }; // src/kv-store.ts var kvLogger = logger.child({ context: "kv-store" }); var KV_PREFIXES = { MARKET: "market", MARKET_IDS: "market_ids", USER_MARKETS: "user_markets", MARKET_PARTICIPANTS: "market_participants", MARKET_CATEGORY: "market_category", // Index for markets by category MARKET_STATUS: "market_status", // Index for markets by status PREDICTION: "prediction", USER_PREDICTIONS: "user_predictions", MARKET_PREDICTIONS: "market_predictions", PREDICTION_NFT: "prediction_nft", USER_BALANCE: "user_balance", USER_STATS: "user_stats", LEADERBOARD: "leaderboard", LEADERBOARD_EARNINGS: "leaderboard_earnings", LEADERBOARD_ACCURACY: "leaderboard_accuracy", BUG_REPORT: "bug_report", BUG_REPORT_IDS: "bug_report_ids", USER_BUG_REPORTS: "user_bug_reports", // Transaction custody-related prefixes CUSTODY_TRANSACTION: "custody_transaction", CUSTODY_TRANSACTION_IDS: "custody_transaction_ids", USER_TRANSACTIONS: "user_transactions", SIGNER_TRANSACTIONS: "signer_transactions", MARKET_TRANSACTIONS: "market_transactions", CUSTODY_NFT_RECEIPT: "custody_nft_receipt", // Claim reward transaction prefixes CLAIM_REWARD_TRANSACTION: "claim_reward_transaction", USER_CLAIM_REWARDS: "user_claim_rewards" }; function getKey(entityType, id) { const prefix = KV_PREFIXES[entityType]; if (entityType === "MARKET_IDS" && !id) { return prefix; } return id ? `${prefix}:${id}` : prefix; } async function storeEntity(entityType, id, data) { try { const key = getKey(entityType, id); await kv.kv.set(key, JSON.stringify(data)); return data; } catch (error) { throw new AppError({ message: `Failed to store ${entityType} with ID ${id}`, context: "kv-store", code: "KV_STORE_ERROR", originalError: error instanceof Error ? error : new Error(String(error)), data: { entityType, id, operation: "store" } }).log(); } } async function getEntity(entityType, id) { try { const key = getKey(entityType, id); let data = await kv.kv.get(key); if (!data && entityType === "MARKET") { data = await kv.kv.get(`markets:${id}`); } if (!data) { kvLogger.debug({ entityType, id }, `Entity not found: ${entityType}:${id}`); return null; } if (typeof data !== "string") { return data; } try { return JSON.parse(data); } catch (e) { throw new AppError({ message: `Error parsing JSON for ${entityType} with ID ${id}`, context: "kv-store", code: "KV_JSON_PARSE_ERROR", originalError: e instanceof Error ? e : new Error(String(e)), data: { entityType, id, operation: "parse" } }).log(); } } catch (error) { if (error instanceof AppError) { throw error; } throw new AppError({ message: `Failed to retrieve ${entityType} with ID ${id}`, context: "kv-store", code: "KV_RETRIEVE_ERROR", originalError: error instanceof Error ? error : new Error(String(error)), data: { entityType, id, operation: "get" } }).log(); } } async function deleteEntity(entityType, id) { try { const key = getKey(entityType, id); await kv.kv.del(key); if (entityType === "MARKET") { await kv.kv.del(`markets:${id}`); } return true; } catch (error) { new AppError({ message: `Failed to delete ${entityType} with ID ${id}`, context: "kv-store", code: "KV_DELETE_ERROR", originalError: error instanceof Error ? error : new Error(String(error)), data: { entityType, id, operation: "delete" } }).log(); return false; } } async function addToSet(setType, id, memberId) { try { const key = getKey(setType, id); await kv.kv.sadd(key, memberId); if (setType === "MARKET_IDS") { await kv.kv.sadd("market_ids", memberId); } return true; } catch (error) { new AppError({ message: `Failed to add member ${memberId} to set ${setType}:${id}`, context: "kv-store", code: "KV_SET_ADD_ERROR", originalError: error instanceof Error ? error : new Error(String(error)), data: { setType, id, memberId, operation: "sadd" } }).log(); return false; } } async function removeFromSet(setType, id, memberId) { try { const key = getKey(setType, id); await kv.kv.srem(key, memberId); if (setType === "MARKET_IDS") { await kv.kv.srem("market_ids", memberId); } return true; } catch (error) { new AppError({ message: `Failed to remove member ${memberId} from set ${setType}:${id}`, context: "kv-store", code: "KV_SET_REMOVE_ERROR", originalError: error instanceof Error ? error : new Error(String(error)), data: { setType, id, memberId, operation: "srem" } }).log(); return false; } } async function getSetMembers(setType, id) { try { const key = getKey(setType, id); let members = await kv.kv.smembers(key); if (setType === "MARKET_IDS" && members.length === 0) { const legacyMembers = await kv.kv.smembers("market_ids"); if (legacyMembers.length > 0) { for (const marketId of legacyMembers) { await addToSet("MARKET_IDS", "", marketId); } members = legacyMembers; } } return members; } catch (error) { new AppError({ message: `Failed to get members from set ${setType}:${id}`, context: "kv-store", code: "KV_SET_MEMBERS_ERROR", originalError: error instanceof Error ? error : new Error(String(error)), data: { setType, id, operation: "smembers" } }).log(); return []; } } async function isSetMember(setType, id, memberId) { try { const key = getKey(setType, id); const result = await kv.kv.sismember(key, memberId); return !!result; } catch (error) { console.error(`Error checking set membership for ${setType} with ID ${id}:`, error); return false; } } async function addToSortedSet(setType, memberId, score) { try { const key = getKey(setType); await kv.kv.zadd(key, { score, member: memberId }); return true; } catch (error) { console.error(`Error adding to sorted set ${setType}:`, error); return false; } } async function getTopFromSortedSet(setType, limit = 10, reverse = true) { try { const key = getKey(setType); return await kv.kv.zrange(key, 0, limit - 1, { rev: reverse }); } catch (error) { console.error(`Error getting top members from sorted set ${setType}:`, error); return []; } } async function getScoresFromSortedSet(setType, memberIds) { try { const key = getKey(setType); const result = {}; const batchSize = 50; for (let i = 0; i < memberIds.length; i += batchSize) { const batch = memberIds.slice(i, i + batchSize); const batchScores = await Promise.all( batch.map(async (memberId) => { const score = await kv.kv.zscore(key, memberId); return { memberId, score: score ? Number(score) : null }; }) ); batchScores.forEach(({ memberId, score }) => { if (score !== null) { result[memberId] = score; } }); } return result; } catch (error) { console.error(`Error getting scores from sorted set ${setType}:`, error); return {}; } } async function getKeys(pattern) { try { return await kv.kv.keys(pattern); } catch (error) { console.error(`Error getting keys with pattern ${pattern}:`, error); return []; } } async function keyExists(entityType, id) { try { const key = getKey(entityType, id); const result = await kv.kv.exists(key); return result === 1; } catch (error) { console.error(`Error checking if key exists for ${entityType} with ID ${id}:`, error); return false; } } async function getDebugInfo() { try { const allKeys = await kv.kv.keys("*"); const patterns = [ "market:*", "markets:*", "market_ids", "prediction:*", "predictions:*", "user_predictions:*", "market_predictions:*", "prediction_nft:*", "prediction_nfts:*" ]; const result = { totalKeys: allKeys.length, keysByPattern: {} }; const keysByPattern = result.keysByPattern; for (const pattern of patterns) { const keys = await kv.kv.keys(pattern); keysByPattern[pattern] = { count: keys.length, sample: keys.slice(0, 5) }; } return result; } catch (error) { console.error("Error getting debug info:", error); return { error: String(error) }; } } async function startTransaction() { try { const transaction = kv.kv.multi(); const operations = []; const txObject = { operations, // Add entity to transaction async addEntity(entityType, id, data) { const key = getKey(entityType, id); transaction.set(key, JSON.stringify(data)); operations.push({ type: "entity", entityType, id, data }); }, // Add to set in transaction async addToSetInTransaction(setType, id, memberId) { const key = getKey(setType, id); transaction.sadd(key, memberId); operations.push({ type: "set", entityType: setType, id: memberId }); if (setType === "MARKET_IDS") { transaction.sadd("market_ids", memberId); } }, // Add to sorted set in transaction async addToSortedSetInTransaction(setType, memberId, score) { const key = getKey(setType); transaction.zadd(key, { score, member: memberId }); operations.push({ type: "sortedSet", entityType: setType, id: memberId, data: score }); }, // Execute all queued commands atomically async execute() { try { kvLogger.debug( { operationCount: operations.length }, `Executing transaction with ${operations.length} operations` ); await transaction.exec(); return true; } catch (error) { const appError = new AppError({ message: "Transaction execution failed", context: "kv-store", code: "TRANSACTION_FAILED", originalError: error instanceof Error ? error : new Error(String(error)), data: { operationCount: operations.length } }); appError.log(); return false; } } }; return txObject; } catch (error) { throw new AppError({ message: "Failed to start transaction", context: "kv-store", code: "TRANSACTION_START_ERROR", originalError: error instanceof Error ? error : new Error(String(error)) }).log(); } } exports.KV_PREFIXES = KV_PREFIXES; exports.addToSet = addToSet; exports.addToSortedSet = addToSortedSet; exports.deleteEntity = deleteEntity; exports.getDebugInfo = getDebugInfo; exports.getEntity = getEntity; exports.getKey = getKey; exports.getKeys = getKeys; exports.getScoresFromSortedSet = getScoresFromSortedSet; exports.getSetMembers = getSetMembers; exports.getTopFromSortedSet = getTopFromSortedSet; exports.isSetMember = isSetMember; exports.keyExists = keyExists; exports.removeFromSet = removeFromSet; exports.startTransaction = startTransaction; exports.storeEntity = storeEntity; //# sourceMappingURL=kv-store.cjs.map //# sourceMappingURL=kv-store.cjs.map