@levrbet/shared 0.2.82 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. package/dist/core/config/urls.js +4 -4
  2. package/dist/core/config/urls.js.map +1 -1
  3. package/dist/core/utils/game.utils.d.ts +2 -0
  4. package/dist/core/utils/game.utils.js +69 -1
  5. package/dist/core/utils/game.utils.js.map +1 -1
  6. package/dist/server/oracle/config/game-periods/providers/lsports.periods.js +3 -3
  7. package/dist/server/oracle/config/game-periods/providers/lsports.periods.js.map +1 -1
  8. package/dist/server/oracle/config/game-periods/providers/optic-odds.periods.d.ts +4 -27
  9. package/dist/server/oracle/config/game-periods/providers/optic-odds.periods.js +4 -27
  10. package/dist/server/oracle/config/game-periods/providers/optic-odds.periods.js.map +1 -1
  11. package/dist/server/oracle/config/game-periods/sports.config.js +8 -73
  12. package/dist/server/oracle/config/game-periods/sports.config.js.map +1 -1
  13. package/dist/server/oracle/config/game-periods/types.d.ts +2 -6
  14. package/dist/server/oracle/config/game-periods/types.js +0 -1
  15. package/dist/server/oracle/config/game-periods/types.js.map +1 -1
  16. package/dist/server/oracle/redis-cache-manager/cache.keys.utils.d.ts +45 -74
  17. package/dist/server/oracle/redis-cache-manager/cache.keys.utils.js +65 -120
  18. package/dist/server/oracle/redis-cache-manager/cache.keys.utils.js.map +1 -1
  19. package/dist/server/oracle/redis-cache-manager/game.query.engine.d.ts +90 -0
  20. package/dist/server/oracle/redis-cache-manager/game.query.engine.js +436 -0
  21. package/dist/server/oracle/redis-cache-manager/game.query.engine.js.map +1 -0
  22. package/dist/server/oracle/redis-cache-manager/index.d.ts +2 -3
  23. package/dist/server/oracle/redis-cache-manager/index.js +2 -3
  24. package/dist/server/oracle/redis-cache-manager/index.js.map +1 -1
  25. package/dist/server/oracle/redis-cache-manager/market.query.engine.d.ts +50 -97
  26. package/dist/server/oracle/redis-cache-manager/market.query.engine.js +310 -467
  27. package/dist/server/oracle/redis-cache-manager/market.query.engine.js.map +1 -1
  28. package/dist/server/oracle/types/providers/game-clock/index.d.ts +1 -1
  29. package/dist/server/oracle/types/providers/game-clock/index.js +1 -1
  30. package/dist/server/oracle/types/providers/game-clock/index.js.map +1 -1
  31. package/dist/server/{utils/game_progress → oracle/types/providers/game-clock}/parser.d.ts +7 -1
  32. package/dist/server/{utils/game_progress → oracle/types/providers/game-clock}/parser.js +13 -2
  33. package/dist/server/oracle/types/providers/game-clock/parser.js.map +1 -0
  34. package/dist/server/utils/index.d.ts +0 -1
  35. package/dist/server/utils/index.js +0 -1
  36. package/dist/server/utils/index.js.map +1 -1
  37. package/package.json +5 -4
  38. package/scripts/setup-prisma.js +0 -0
  39. package/dist/server/oracle/redis-cache-manager/game.cache.service.d.ts +0 -185
  40. package/dist/server/oracle/redis-cache-manager/game.cache.service.js +0 -712
  41. package/dist/server/oracle/redis-cache-manager/game.cache.service.js.map +0 -1
  42. package/dist/server/oracle/redis-cache-manager/game.progress.d.ts +0 -4
  43. package/dist/server/oracle/redis-cache-manager/game.progress.js +0 -27
  44. package/dist/server/oracle/redis-cache-manager/game.progress.js.map +0 -1
  45. package/dist/server/oracle/redis-cache-manager/market.cache.service.d.ts +0 -87
  46. package/dist/server/oracle/redis-cache-manager/market.cache.service.js +0 -139
  47. package/dist/server/oracle/redis-cache-manager/market.cache.service.js.map +0 -1
  48. package/dist/server/utils/game_progress/caclulate.game.progress.d.ts +0 -6
  49. package/dist/server/utils/game_progress/caclulate.game.progress.js +0 -96
  50. package/dist/server/utils/game_progress/caclulate.game.progress.js.map +0 -1
  51. package/dist/server/utils/game_progress/game.utils.d.ts +0 -2
  52. package/dist/server/utils/game_progress/game.utils.js +0 -64
  53. package/dist/server/utils/game_progress/game.utils.js.map +0 -1
  54. package/dist/server/utils/game_progress/index.d.ts +0 -3
  55. package/dist/server/utils/game_progress/index.js +0 -20
  56. package/dist/server/utils/game_progress/index.js.map +0 -1
  57. package/dist/server/utils/game_progress/parser.js.map +0 -1
@@ -3,71 +3,153 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.dropAllIndices = exports.getLatestOddsWithMarket = exports.getLatestOdds = exports.searchOddsLists = exports.addOddsToOddsCache = exports.searchMultipleMarketsWithOdds = exports.searchMultipleMarketsWithOddsFallback = exports.searchMarketsWithOdds = exports.searchMarketsWithOddsFallback = exports.getMarketByGameAndType = exports.searchMarkets = exports.createOddsIndex = exports.createMarketIndex = exports.updateMarketCache = exports.searchMarketsFallback = exports.getOddsList = void 0;
6
+ exports.getLatestOdds = exports.addOddsToOddsCache = exports.getOddsList = exports.getMarketDataCacheWithOdds = exports.getMultipleMarketsWithOdds = exports.getGameMarketsWithOdds = exports.getGameMarkets = exports.updateMarketCache = exports.dropMarketIndex = exports.createMarketIndex = void 0;
7
7
  const winston_1 = __importDefault(require("../../config/winston"));
8
8
  const cache_keys_utils_1 = require("./cache.keys.utils");
9
9
  /**
10
- * Get all odds for a specific game and market type with pagination
11
- * @param redis - Redis client instance
12
- * @param gameUniqueId - Unique game identifier
13
- * @param marketType - Market type
14
- * @param limit - Number of latest odds to return (from the end)
15
- * @returns Array of Odds objects
10
+ * Redis Query Engine using RediSearch
11
+ * Provides advanced querying capabilities for markets and odds using Prisma types
16
12
  */
17
- const getOddsList = async (redis, gameUniqueId, marketType) => {
18
- try {
19
- const key = cache_keys_utils_1.gameToMarketOfOddsInCache;
20
- const field = (0, cache_keys_utils_1.getMarketOddsField)(gameUniqueId, marketType);
21
- const value = await redis.hget(key, field);
22
- if (!value) {
23
- return [];
24
- }
25
- const oddsList = JSON.parse(value);
26
- // Return the last N odds entries
27
- return oddsList;
28
- }
29
- catch (error) {
30
- winston_1.default.error("Error getting odds list", error);
31
- return [];
32
- }
13
+ // Schema versioning for index migration
14
+ const MARKET_INDEX_SCHEMA_VERSION = 1;
15
+ const MARKET_INDEX_SCHEMA = [
16
+ "objectId",
17
+ "TAG",
18
+ "chainId",
19
+ "NUMERIC",
20
+ "gameId",
21
+ "NUMERIC",
22
+ "levrMarketId",
23
+ "TAG",
24
+ "gameMarketId",
25
+ "TAG",
26
+ "isMatured",
27
+ "TAG",
28
+ "marketType",
29
+ "TAG",
30
+ "status",
31
+ "TAG",
32
+ "winner",
33
+ "TAG",
34
+ "levrGameObjectId",
35
+ "TAG",
36
+ ];
37
+ // Pipeline batch size to prevent memory issues
38
+ const PIPELINE_BATCH_SIZE = 500;
39
+ /**
40
+ * Validate if existing index schema matches expected schema
41
+ */
42
+ const validateIndexSchema = (indexInfo) => {
43
+ const attributesIndex = indexInfo.indexOf("attributes");
44
+ if (attributesIndex === -1)
45
+ return false;
46
+ const attributes = indexInfo[attributesIndex + 1];
47
+ if (!Array.isArray(attributes))
48
+ return false;
49
+ // Check if schema has expected number of fields
50
+ const expectedFieldCount = MARKET_INDEX_SCHEMA.length / 2;
51
+ if (attributes.length !== expectedFieldCount)
52
+ return false;
53
+ return true;
33
54
  };
34
- exports.getOddsList = getOddsList;
35
55
  /**
36
- * Fallback market query engine for Redis instances without RediSearch support
37
- * Uses simple hash operations instead of full-text search indexing
56
+ * Helper to safely get value from Record
38
57
  */
39
- const searchMarketsFallback = async (redis, levrGameObjectId, marketType) => {
40
- try {
41
- // Get all market keys matching the pattern
42
- const key = (0, cache_keys_utils_1.getMarketKeyWithGameUniqueIdMarketType)(levrGameObjectId, marketType);
43
- // Fetch and filter markets
44
- const marketJson = await redis.hget(key, "market_json");
45
- try {
46
- const market = JSON.parse(marketJson || "");
47
- return market;
48
- }
49
- catch (e) {
50
- winston_1.default.warn("Failed to parse market from fallback search", e);
51
- }
52
- return null;
53
- }
54
- catch (error) {
55
- winston_1.default.error("Error in market fallback search", error);
58
+ const get = (obj, key) => obj[key] ?? "";
59
+ /**
60
+ * Reconstruct a Market object from Redis hash fields
61
+ */
62
+ const reconstructMarketFromFields = (fields) => {
63
+ const objectId = get(fields, "objectId");
64
+ if (!objectId)
56
65
  return null;
66
+ const marketDetailsJson = get(fields, "marketDetailsJson");
67
+ const marketRiskAllocationJson = get(fields, "marketRiskAllocationJson");
68
+ const providersJson = get(fields, "providersJson");
69
+ const activeProviderJson = get(fields, "activeProviderJson");
70
+ const winner = get(fields, "winner");
71
+ return {
72
+ objectId,
73
+ chainId: Number.parseInt(get(fields, "chainId"), 10) || 0,
74
+ gameId: Number.parseInt(get(fields, "gameId"), 10) || 0,
75
+ levrMarketId: get(fields, "levrMarketId"),
76
+ levrMarketContract: get(fields, "levrMarketContract"),
77
+ gameMarketId: get(fields, "gameMarketId"),
78
+ txHash: get(fields, "txHash"),
79
+ maturedAt: get(fields, "maturedAt") ? new Date(Number.parseInt(get(fields, "maturedAt"), 10)) : null,
80
+ leveraged: get(fields, "leveraged") === "1",
81
+ isMatured: get(fields, "isMatured") === "1",
82
+ normalizationFactor: Number.parseFloat(get(fields, "normalizationFactor")) || 0,
83
+ marketType: get(fields, "marketType"),
84
+ status: get(fields, "status"),
85
+ winner: winner || null,
86
+ levrGameObjectId: get(fields, "levrGameObjectId"),
87
+ createdAt: new Date(Number.parseInt(get(fields, "createdAt"), 10) || 0),
88
+ updatedAt: new Date(Number.parseInt(get(fields, "updatedAt"), 10) || 0),
89
+ marketDetails: marketDetailsJson ? JSON.parse(marketDetailsJson) : null,
90
+ marketRiskAllocation: marketRiskAllocationJson ? JSON.parse(marketRiskAllocationJson) : null,
91
+ providers: providersJson ? JSON.parse(providersJson) : [],
92
+ activeProvider: activeProviderJson ? JSON.parse(activeProviderJson) : null,
93
+ };
94
+ };
95
+ // ============================================================================
96
+ // INDEX MANAGEMENT
97
+ // ============================================================================
98
+ /**
99
+ * Create market index with proper schema and versioning
100
+ * Validates existing index schema and recreates if mismatched
101
+ */
102
+ const createMarketIndex = async (redis) => {
103
+ const indexInfo = await redis.call("FT.INFO", cache_keys_utils_1.market_index_name).catch(() => null);
104
+ if (indexInfo && Array.isArray(indexInfo)) {
105
+ // Validate existing index schema
106
+ if (validateIndexSchema(indexInfo)) {
107
+ winston_1.default.info(`Index ${cache_keys_utils_1.market_index_name} already exists with correct schema (v${MARKET_INDEX_SCHEMA_VERSION})`);
108
+ return;
109
+ }
110
+ // Schema mismatch - drop and recreate
111
+ winston_1.default.warn(`Index ${cache_keys_utils_1.market_index_name} schema mismatch - recreating...`);
112
+ await redis.call("FT.DROPINDEX", cache_keys_utils_1.market_index_name).catch(() => null);
57
113
  }
114
+ // Create new index with correct schema
115
+ await redis.call("FT.CREATE", cache_keys_utils_1.market_index_name, "ON", "HASH", "PREFIX", "1", cache_keys_utils_1.levrMarketsHashKey, "SCHEMA", ...MARKET_INDEX_SCHEMA);
116
+ winston_1.default.info(`Created index: ${cache_keys_utils_1.market_index_name} (v${MARKET_INDEX_SCHEMA_VERSION})`);
58
117
  };
59
- exports.searchMarketsFallback = searchMarketsFallback;
118
+ exports.createMarketIndex = createMarketIndex;
60
119
  /**
61
- * Add a new market to cache with RediSearch indexing
120
+ * Drop market search index
62
121
  * @param redis - Redis client instance
63
- * @param market - Array of Market objects to cache
64
122
  */
65
- const updateMarketCache = async (redis, market) => {
66
- try {
67
- // Use pipeline for batch operations
123
+ const dropMarketIndex = async (redis) => {
124
+ const indices = [cache_keys_utils_1.market_index_name];
125
+ for (const idx of indices) {
126
+ await redis.call("FT.DROPINDEX", idx).catch(() => {
127
+ winston_1.default.warn(`Index ${idx} does not exist`);
128
+ });
129
+ }
130
+ winston_1.default.info("Dropped market index");
131
+ };
132
+ exports.dropMarketIndex = dropMarketIndex;
133
+ // ============================================================================
134
+ // CACHE MANAGEMENT
135
+ // ============================================================================
136
+ /**
137
+ * Add markets to cache with RediSearch indexing
138
+ * @param redis - Redis client instance
139
+ * @param markets - Array of Market objects to cache
140
+ * @returns Result object with success/failed counts
141
+ */
142
+ const updateMarketCache = async (redis, markets) => {
143
+ if (markets.length === 0) {
144
+ return { success: 0, failed: 0, total: 0 };
145
+ }
146
+ let success = 0;
147
+ let failed = 0;
148
+ // Process in batches to prevent memory issues
149
+ for (let i = 0; i < markets.length; i += PIPELINE_BATCH_SIZE) {
150
+ const batch = markets.slice(i, i + PIPELINE_BATCH_SIZE);
68
151
  const pipeline = redis.pipeline();
69
- // Add each market to the pipeline
70
- for (const m of market) {
152
+ for (const m of batch) {
71
153
  const key = (0, cache_keys_utils_1.getMarketKeyWithGameUniqueIdMarketType)(m.levrGameObjectId, m.marketType);
72
154
  pipeline.hset(key, {
73
155
  objectId: m.objectId,
@@ -77,13 +159,13 @@ const updateMarketCache = async (redis, market) => {
77
159
  levrMarketContract: m.levrMarketContract,
78
160
  gameMarketId: m.gameMarketId,
79
161
  txHash: m.txHash,
80
- maturedAt: m.maturedAt ? m.maturedAt.getTime().toString() : "0",
162
+ maturedAt: m.maturedAt ? m.maturedAt.getTime().toString() : "",
81
163
  leveraged: m.leveraged ? "1" : "0",
82
164
  isMatured: m.isMatured ? "1" : "0",
83
165
  normalizationFactor: m.normalizationFactor.toString(),
84
166
  marketType: m.marketType,
85
167
  status: m.status,
86
- winner: m.winner || "",
168
+ winner: m.winner ?? "",
87
169
  levrGameObjectId: m.levrGameObjectId,
88
170
  createdAt: m.createdAt.getTime().toString(),
89
171
  updatedAt: m.updatedAt.getTime().toString(),
@@ -91,484 +173,245 @@ const updateMarketCache = async (redis, market) => {
91
173
  marketRiskAllocationJson: typeof m.marketRiskAllocation === "string" ? m.marketRiskAllocation : JSON.stringify(m.marketRiskAllocation),
92
174
  providersJson: typeof m.providers === "string" ? m.providers : JSON.stringify(m.providers),
93
175
  activeProviderJson: typeof m.activeProvider === "string" ? m.activeProvider : JSON.stringify(m.activeProvider),
94
- market_json: JSON.stringify(m),
95
176
  });
96
177
  }
97
- // await createMarketIndex(redis) // optional if index is working
98
- // Execute all pipeline commands at once
99
- await pipeline.exec();
100
- winston_1.default.info(`Updated ${market.length} markets in cache`);
101
- }
102
- catch (error) {
103
- winston_1.default.error("Error updating market cache", error);
104
- throw error;
105
- }
106
- };
107
- exports.updateMarketCache = updateMarketCache;
108
- /**
109
- * Create market index with proper schema
110
- */
111
- const createMarketIndex = async (redis) => {
112
- try {
113
- // Check if index already exists
114
- const indexExists = await redis.call("FT.INFO", cache_keys_utils_1.market_index_name).catch(() => null);
115
- if (indexExists) {
116
- winston_1.default.info(`Index ${cache_keys_utils_1.market_index_name} already exists`);
117
- return;
178
+ const results = await pipeline.exec();
179
+ if (results) {
180
+ for (const [err] of results) {
181
+ if (err) {
182
+ failed++;
183
+ winston_1.default.error("Pipeline command failed", err);
184
+ }
185
+ else {
186
+ success++;
187
+ }
188
+ }
118
189
  }
119
- // Create new index with correct prefix (only if doesn't exist)
120
- await redis.call("FT.CREATE", cache_keys_utils_1.market_index_name, "ON", "HASH", "PREFIX", "1", "markets_unique_id:", "SCHEMA", "objectId", "TEXT", "chainId", "NUMERIC", "gameId", "NUMERIC", "levrMarketId", "TEXT", "levrMarketContract", "TEXT", "gameMarketId", "TEXT", "txHash", "TEXT", "maturedAt", "NUMERIC", "leveraged", "NUMERIC", "isMatured", "NUMERIC", "normalizationFactor", "NUMERIC", "marketType", "TAG", "status", "TAG", "winner", "TAG", "levrGameObjectId", "TEXT", "createdAt", "NUMERIC", "updatedAt", "NUMERIC");
121
- winston_1.default.info(`Created index: ${cache_keys_utils_1.market_index_name}`);
122
- }
123
- catch (error) {
124
- winston_1.default.error(`Error creating market index`, error);
125
- throw error;
126
190
  }
191
+ winston_1.default.info(`Updated markets in cache: ${success} success, ${failed} failed`);
192
+ return { success, failed, total: markets.length };
127
193
  };
128
- exports.createMarketIndex = createMarketIndex;
194
+ exports.updateMarketCache = updateMarketCache;
129
195
  /**
130
- * Creates a RediSearch index for odds lists by market identifiers
131
- * @param redis - Redis client instance
196
+ * Escape special characters for RediSearch TAG field queries
132
197
  */
133
- const createOddsIndex = async (redis) => {
134
- try {
135
- const indexName = "odds_list_idx";
136
- const indexExists = await redis.call("FT.INFO", indexName).catch(() => null);
137
- if (indexExists) {
138
- winston_1.default.info("Odds list index already exists");
139
- return;
140
- }
141
- await redis.call("FT.CREATE", indexName, "ON", "HASH", "PREFIX", "1", "game_to_market_ids_of_odds_in_cache_", "SCHEMA", "gameMarketId", "TEXT", "marketType", "TAG", "levrGameObjectId", "TEXT");
142
- winston_1.default.info(`Created odds list index: ${indexName}`);
143
- }
144
- catch (error) {
145
- winston_1.default.error("Error creating odds list index", error);
146
- }
198
+ const escapeTagValue = (value) => {
199
+ // Escape special chars: , . < > { } [ ] " ' : ; ! @ # $ % ^ & * ( ) - + = ~
200
+ return value.replace(/[,.<>{}[\]"':;!@#$%^&*()\-+=~|]/g, "\\$&");
147
201
  };
148
- exports.createOddsIndex = createOddsIndex;
149
202
  /**
150
- * Search markets using RediSearch query engine
203
+ * Get markets using RediSearch query engine
151
204
  * @param redis - Redis client instance
152
205
  * @param params - Search parameters
153
- * @returns Array of Market objects
206
+ * @returns Search result with markets array and total count
154
207
  */
155
- const searchMarkets = async (redis, params) => {
156
- try {
157
- const { gameId, levrGameObjectId, marketType, status, isMatured, chainId, limit = 50, offset = 0 } = params;
158
- const filters = [];
159
- if (marketType) {
160
- filters.push(`@marketType:{${marketType}}`);
161
- }
162
- if (status) {
163
- filters.push(`@status:{${status}}`);
164
- }
165
- if (gameId !== undefined) {
166
- filters.push(`@gameId:[${gameId} ${gameId}]`);
167
- }
168
- if (chainId !== undefined) {
169
- filters.push(`@chainId:[${chainId} ${chainId}]`);
170
- }
171
- if (isMatured !== undefined) {
172
- filters.push(`@isMatured:{${isMatured ? "1" : "0"}}`);
173
- }
174
- if (levrGameObjectId) {
175
- filters.push(`@levrGameObjectId:${levrGameObjectId}`);
176
- }
177
- const query = filters.length ? filters.join(" ") : "*";
178
- const result = await redis.call("FT.SEARCH", cache_keys_utils_1.market_index_name, query, "LIMIT", offset, limit);
179
- if (!Array.isArray(result) || result.length === 0) {
180
- return [];
181
- }
182
- const [_, ...items] = result;
183
- const markets = [];
184
- // Process results: [count, key1, [fields1], key2, [fields2], ...]
185
- for (let i = 0; i < items.length; i += 2) {
186
- const fieldsArray = items[i + 1];
187
- if (!Array.isArray(fieldsArray))
188
- continue;
189
- // Convert fields array to object
190
- const obj = {};
191
- for (let j = 0; j < fieldsArray.length; j += 2) {
192
- obj[fieldsArray[j]] = fieldsArray[j + 1];
193
- }
194
- // Reconstruct market from market_json
195
- if (obj.market_json) {
196
- try {
197
- const market = JSON.parse(obj.market_json);
198
- markets.push(market);
199
- }
200
- catch (e) {
201
- winston_1.default.warn("Failed to parse market_json", e);
202
- }
203
- }
208
+ const getGameMarkets = async (redis, params) => {
209
+ const { gameId, levrGameObjectId, marketType, status, isMatured, chainId, limit, offset } = params;
210
+ const filters = [];
211
+ if (marketType) {
212
+ filters.push(`@marketType:{${escapeTagValue(marketType)}}`);
213
+ }
214
+ if (status) {
215
+ filters.push(`@status:{${escapeTagValue(status)}}`);
216
+ }
217
+ if (gameId !== undefined) {
218
+ filters.push(`@gameId:[${gameId} ${gameId}]`);
219
+ }
220
+ if (chainId !== undefined) {
221
+ filters.push(`@chainId:[${chainId} ${chainId}]`);
222
+ }
223
+ if (isMatured !== undefined) {
224
+ filters.push(`@isMatured:{${isMatured ? "1" : "0"}}`);
225
+ }
226
+ if (levrGameObjectId) {
227
+ filters.push(`@levrGameObjectId:{${escapeTagValue(levrGameObjectId)}}`);
228
+ }
229
+ const query = filters.length ? filters.join(" ") : "*";
230
+ // Only add LIMIT if limit is specified
231
+ let result;
232
+ if (limit !== undefined) {
233
+ const offset_ = offset ?? 0;
234
+ result = await redis.call("FT.SEARCH", cache_keys_utils_1.market_index_name, query, "LIMIT", offset_, limit);
235
+ }
236
+ else {
237
+ result = await redis.call("FT.SEARCH", cache_keys_utils_1.market_index_name, query);
238
+ }
239
+ if (!Array.isArray(result) || result.length === 0) {
240
+ return { markets: [], total: 0 };
241
+ }
242
+ const total = typeof result[0] === "number" ? result[0] : 0;
243
+ const items = result.slice(1);
244
+ const markets = [];
245
+ // Process results: [count, key1, [fields1], key2, [fields2], ...]
246
+ for (let i = 0; i < items.length; i += 2) {
247
+ const fieldsArray = items[i + 1];
248
+ if (!Array.isArray(fieldsArray))
249
+ continue;
250
+ // Convert fields array to object
251
+ const fields = {};
252
+ for (let j = 0; j < fieldsArray.length; j += 2) {
253
+ const key = fieldsArray[j];
254
+ const value = fieldsArray[j + 1];
255
+ fields[key] = value;
204
256
  }
205
- return markets;
206
- }
207
- catch (error) {
208
- winston_1.default.error("Error searching markets", error);
209
- return [];
210
- }
211
- };
212
- exports.searchMarkets = searchMarkets;
213
- /**
214
- * Get market by game unique ID and market type directly from hash
215
- * @param redis - Redis client instance
216
- * @param gameUniqueId - Unique game identifier
217
- * @param marketType - Market type
218
- * @returns Market object or null if not found
219
- */
220
- const getMarketByGameAndType = async (redis, gameUniqueId, marketType) => {
221
- try {
222
- const key = (0, cache_keys_utils_1.getMarketKeyWithGameUniqueIdMarketType)(gameUniqueId, marketType);
223
- const marketJson = await redis.hget(key, "market_json");
224
- if (!marketJson) {
225
- return null;
257
+ // Reconstruct market from individual fields
258
+ const market = reconstructMarketFromFields(fields);
259
+ if (market) {
260
+ markets.push(market);
226
261
  }
227
- return JSON.parse(marketJson);
228
- }
229
- catch (error) {
230
- winston_1.default.error("Error retrieving market from hash", error);
231
- return null;
232
262
  }
263
+ return { markets, total };
233
264
  };
234
- exports.getMarketByGameAndType = getMarketByGameAndType;
265
+ exports.getGameMarkets = getGameMarkets;
235
266
  /**
236
- * Search markets with their associated odds - FALLBACK version
237
- * Uses simple hash operations when RediSearch is unavailable
267
+ * Get markets with their associated odds
238
268
  * @param redis - Redis client instance
239
- * @param levrGameObjectId - Unique game identifier
240
- * @param marketType - Market type
241
- * @returns Markets with their odds
269
+ * @param query - Search query parameters
270
+ * @returns Results with markets and their odds
242
271
  */
243
- const searchMarketsWithOddsFallback = async (redis, levrGameObjectId, marketType) => {
244
- try {
245
- // Get markets using fallback
246
- const market = await (0, exports.searchMarketsFallback)(redis, levrGameObjectId, marketType);
247
- if (!market) {
248
- return null;
249
- }
250
- const odds = await (0, exports.getOddsList)(redis, market.levrGameObjectId, market.marketType);
251
- const marketsWithOdds = {
272
+ const getGameMarketsWithOdds = async (redis, query) => {
273
+ const startTime = Date.now();
274
+ // Get markets from search
275
+ const { markets, total } = await (0, exports.getGameMarkets)(redis, query);
276
+ // Enrich each market with its odds
277
+ const marketsWithOdds = await Promise.all(markets.map(async (market) => {
278
+ const odds = await (0, exports.getOddsList)(redis, market.gameMarketId);
279
+ return {
252
280
  ...market,
253
281
  odds,
254
282
  };
255
- return marketsWithOdds;
256
- }
257
- catch (error) {
258
- winston_1.default.error("Error in fallback search markets with odds", error);
259
- return null;
260
- }
283
+ }));
284
+ const duration = Date.now() - startTime;
285
+ return {
286
+ total,
287
+ results: marketsWithOdds,
288
+ duration,
289
+ };
261
290
  };
262
- exports.searchMarketsWithOddsFallback = searchMarketsWithOddsFallback;
291
+ exports.getGameMarketsWithOdds = getGameMarketsWithOdds;
263
292
  /**
264
- * Search markets with their associated odds
293
+ * Get multiple markets with their associated odds using multiple queries
265
294
  * @param redis - Redis client instance
266
- * @param query - Search query parameters
267
- * @returns Search results with markets and their odds
295
+ * @param queries - Array of search query parameters
296
+ * @returns Combined results with markets and their odds
268
297
  */
269
- const searchMarketsWithOdds = async (redis, query) => {
270
- try {
271
- const startTime = Date.now();
272
- // Get markets from search
273
- const markets = await (0, exports.searchMarkets)(redis, query);
298
+ const getMultipleMarketsWithOdds = async (redis, queries) => {
299
+ const startTime = Date.now();
300
+ const allResults = {};
301
+ let totalCount = 0;
302
+ // Execute each query and collect results
303
+ for (const query of queries) {
304
+ const { markets, total } = await (0, exports.getGameMarkets)(redis, query);
305
+ totalCount += total;
274
306
  // Enrich each market with its odds
275
307
  const marketsWithOdds = await Promise.all(markets.map(async (market) => {
276
- const odds = await (0, exports.getOddsList)(redis, market.levrGameObjectId, market.marketType);
308
+ const odds = await (0, exports.getOddsList)(redis, market.gameMarketId);
277
309
  return {
278
310
  ...market,
279
311
  odds,
280
312
  };
281
313
  }));
282
- const duration = Date.now() - startTime;
283
- return {
284
- total: marketsWithOdds.length,
285
- results: marketsWithOdds,
286
- duration,
287
- };
288
- }
289
- catch (error) {
290
- winston_1.default.error("Error searching markets with odds", error);
291
- // Fallback to direct hash lookup if RediSearch fails
292
- return {
293
- total: 0,
294
- results: [],
295
- duration: 0,
296
- };
297
- }
298
- };
299
- exports.searchMarketsWithOdds = searchMarketsWithOdds;
300
- /**
301
- * Search multiple markets with their associated odds - FALLBACK version
302
- * Uses simple hash operations when RediSearch is unavailable
303
- * @param redis - Redis client instance
304
- * @param queries - Array of search query parameters with gameUniqueIdentifier and marketType
305
- * @returns Combined search results with markets and their odds
306
- */
307
- const searchMultipleMarketsWithOddsFallback = async (redis, queries) => {
308
- // generate multiple market keys
309
- const marketKeys = queries.map((q) => (0, cache_keys_utils_1.getMarketKeyWithGameUniqueIdMarketType)(q.gameUniqueIdentifier, q.marketType));
310
- // hmget all of them with market json field
311
- const pipeline = redis.pipeline();
312
- for (const key of marketKeys) {
313
- pipeline.hget(key, "market_json");
314
- }
315
- const responses = await pipeline.exec();
316
- const results = {};
317
- let totalCount = 0;
318
- if (!responses) {
319
- return {
320
- total: 0,
321
- results: {},
322
- duration: 0,
323
- };
324
- }
325
- for (let i = 0; i < responses.length; i++) {
326
- console.log("Processing response for key:", marketKeys[i], "Response:", responses[i]);
327
- const response = responses[i];
328
- if (!response || !Array.isArray(response))
329
- continue;
330
- const [error, marketJson] = response;
331
- if (error) {
332
- winston_1.default.error("Error fetching market in fallback multiple search", error);
333
- continue;
334
- }
335
- if (marketJson && typeof marketJson === "string") {
336
- try {
337
- const market = JSON.parse(marketJson);
338
- const odds = await (0, exports.getOddsList)(redis, market.levrGameObjectId, market.marketType);
339
- const marketWithRelations = {
340
- ...market,
341
- odds,
342
- };
343
- if (!results[market.levrGameObjectId]) {
344
- results[market.levrGameObjectId] = [];
314
+ // Group results by levrGameObjectId
315
+ for (const market of marketsWithOdds) {
316
+ if (market.levrGameObjectId) {
317
+ if (!allResults[market.levrGameObjectId]) {
318
+ allResults[market.levrGameObjectId] = [];
345
319
  }
346
- results[market.levrGameObjectId]?.push(marketWithRelations);
347
- totalCount++;
348
- }
349
- catch (e) {
350
- winston_1.default.warn("Failed to parse market in fallback multiple search", e);
320
+ allResults[market.levrGameObjectId]?.push(market);
351
321
  }
352
322
  }
353
323
  }
324
+ const duration = Date.now() - startTime;
354
325
  return {
355
326
  total: totalCount,
356
- results,
357
- duration: 0,
327
+ results: allResults,
328
+ duration,
358
329
  };
359
330
  };
360
- exports.searchMultipleMarketsWithOddsFallback = searchMultipleMarketsWithOddsFallback;
331
+ exports.getMultipleMarketsWithOdds = getMultipleMarketsWithOdds;
361
332
  /**
362
- * Search multiple markets with their associated odds using multiple queries
333
+ * Get market data with associated odds for multiple games and market types
363
334
  * @param redis - Redis client instance
364
- * @param queries - Array of search query parameters
365
- * @returns Combined search results with markets and their odds
335
+ * @param query - Array of query objects with gameUniqueIdentifier and marketType
336
+ * @returns Map of game IDs to their markets with odds
366
337
  */
367
- const searchMultipleMarketsWithOdds = async (redis, queries) => {
368
- try {
369
- const startTime = Date.now();
370
- const allResults = {};
371
- let totalCount = 0;
372
- // Execute each query and collect results
373
- for (const query of queries) {
374
- const markets = await (0, exports.searchMarkets)(redis, query);
375
- totalCount += markets.length;
376
- // Enrich each market with its odds
377
- const marketsWithOdds = await Promise.all(markets.map(async (market) => {
378
- const odds = await (0, exports.getOddsList)(redis, market.levrGameObjectId, market.marketType);
379
- return {
380
- ...market,
381
- odds,
382
- };
383
- }));
384
- // Group results by levrGameObjectId
385
- marketsWithOdds.forEach((market) => {
386
- if (market.levrGameObjectId) {
387
- if (!allResults[market.levrGameObjectId]) {
388
- allResults[market.levrGameObjectId] = [];
389
- }
390
- allResults[market.levrGameObjectId]?.push(market);
391
- }
392
- });
393
- }
394
- const duration = Date.now() - startTime;
395
- return {
396
- total: totalCount,
397
- results: allResults,
398
- duration,
399
- };
400
- }
401
- catch (error) {
402
- winston_1.default.error("Error searching multiple markets with odds", error);
403
- return {
404
- total: 0,
405
- results: {},
406
- duration: 0,
407
- };
408
- }
338
+ const getMarketDataCacheWithOdds = async (redis, query) => {
339
+ const results = await (0, exports.getMultipleMarketsWithOdds)(redis, query.map((q) => ({
340
+ levrGameObjectId: q.gameUniqueIdentifier,
341
+ marketType: q.marketType,
342
+ })));
343
+ return results?.results || {};
409
344
  };
410
- exports.searchMultipleMarketsWithOdds = searchMultipleMarketsWithOdds;
345
+ exports.getMarketDataCacheWithOdds = getMarketDataCacheWithOdds;
346
+ // ============================================================================
347
+ // ODDS CACHE FUNCTIONS (Redis LIST operations)
348
+ // ============================================================================
411
349
  /**
412
- * Add odds to cache as a simple JSON list with searchable metadata
350
+ * Get odds for a specific gameMarketId with pagination using Redis LIST
413
351
  * @param redis - Redis client instance
414
- * @param gameUniqueId - Unique game identifier
415
- * @param marketType - Market type
416
- * @param odds - Array of Odds objects to cache
352
+ * @param gameMarketId - The game market identifier (key suffix)
353
+ * @param limit - Number of latest odds to return (from the end). Use -1 for all.
354
+ * @returns Array of Odds objects (most recent last)
417
355
  */
418
- const addOddsToOddsCache = async (redis, gameUniqueId, marketType, odds) => {
419
- try {
420
- const key = cache_keys_utils_1.gameToMarketOfOddsInCache;
421
- const field = (0, cache_keys_utils_1.getMarketOddsField)(gameUniqueId, marketType);
422
- // Get existing odds if any
423
- const existingValue = await redis.hget(key, field);
424
- const existingOdds = existingValue ? JSON.parse(existingValue) : [];
425
- // Append new odds to existing list
426
- const allOdds = [...existingOdds, ...odds];
427
- // Store the entire list as JSON and add indexable metadata
428
- // const gameMarketId = odds[0]?.gameMarketId || ""
429
- // Use pipeline to batch metadata updates
430
- const pipeline = redis.pipeline();
431
- pipeline.hset(key, field, JSON.stringify(allOdds));
432
- // pipeline.hset(key, "gameMarketId", gameMarketId)
433
- // pipeline.hset(key, "marketType", marketType)
434
- // pipeline.hset(key, "levrGameObjectId", gameUniqueId)
435
- await pipeline.exec();
436
- // Create index separately (only if it doesn't exist)
437
- // await createOddsIndex(redis)
438
- winston_1.default.info(`Added ${odds.length} odds to cache: ${key}:${field}`);
439
- }
440
- catch (error) {
441
- winston_1.default.error("Error adding odds to cache", error);
442
- throw error;
356
+ const getOddsList = async (redis, gameMarketId, limit = 10) => {
357
+ const key = (0, cache_keys_utils_1.getOddsListKey)(gameMarketId);
358
+ // LRANGE with negative indices: -limit to -1 gets last N elements
359
+ // Use 0 to -1 for all elements
360
+ const start = limit === -1 ? 0 : -limit;
361
+ const rawOdds = await redis.lrange(key, start, -1);
362
+ if (!rawOdds || rawOdds.length === 0) {
363
+ return [];
443
364
  }
444
- };
445
- exports.addOddsToOddsCache = addOddsToOddsCache;
446
- /**
447
- * Search odds lists by market identifiers using RediSearch
448
- * @param redis - Redis client instance
449
- * @param gameMarketId - Game market ID to search
450
- * @param marketType - Market type to search
451
- * @param levrGameObjectId - Levr game object ID to search
452
- * @returns Array of odds lists matching the query
453
- */
454
- const searchOddsLists = async (redis, gameMarketId, marketType, levrGameObjectId) => {
455
- try {
456
- const indexName = "odds_list_idx";
457
- const queryParts = [];
458
- if (gameMarketId) {
459
- queryParts.push(`@gameMarketId:${gameMarketId}`);
460
- }
461
- if (marketType) {
462
- queryParts.push(`@marketType:{${marketType}}`);
463
- }
464
- if (levrGameObjectId) {
465
- queryParts.push(`@levrGameObjectId:${levrGameObjectId}`);
365
+ const odds = [];
366
+ for (const raw of rawOdds) {
367
+ try {
368
+ odds.push(JSON.parse(raw));
466
369
  }
467
- const finalQuery = queryParts.length > 0 ? queryParts.join(" ") : "*";
468
- const result = await redis.call("FT.SEARCH", indexName, finalQuery, "LIMIT", 0, 100);
469
- const [, ...items] = result;
470
- const oddsList = [];
471
- for (let i = 0; i < items.length; i += 2) {
472
- const doc = items[i + 1];
473
- if (Array.isArray(doc)) {
474
- const obj = {};
475
- for (let j = 0; j < doc.length; j += 2) {
476
- obj[doc[j]] = doc[j + 1];
477
- }
478
- // Extract the odds list field
479
- const field = Object.keys(obj).find((k) => k.includes(":"));
480
- if (field && obj[field]) {
481
- const parsed = JSON.parse(obj[field]);
482
- oddsList.push(parsed);
483
- }
484
- }
370
+ catch (e) {
371
+ winston_1.default.warn(`Failed to parse odds entry from list ${key}`, e);
485
372
  }
486
- return oddsList;
487
- }
488
- catch (error) {
489
- winston_1.default.error("Error searching odds lists", error);
490
- return [];
491
373
  }
374
+ return odds;
492
375
  };
493
- exports.searchOddsLists = searchOddsLists;
376
+ exports.getOddsList = getOddsList;
494
377
  /**
495
- * Get the latest odds for a specific game and market type
378
+ * Add odds to cache using Redis LIST (RPUSH)
496
379
  * @param redis - Redis client instance
497
- * @param gameUniqueId - Unique game identifier
498
- * @param marketType - Market type
499
- * @returns Latest Odds object or null if not found
380
+ * @param gameMarketId - The game market identifier (used as key suffix)
381
+ * @param odds - Array of Odds objects to append to the list
500
382
  */
501
- const getLatestOdds = async (redis, gameUniqueId, marketType) => {
502
- try {
503
- const key = cache_keys_utils_1.gameToMarketOfOddsInCache;
504
- const field = (0, cache_keys_utils_1.getMarketOddsField)(gameUniqueId, marketType);
505
- const value = await redis.hget(key, field);
506
- if (!value) {
507
- return null;
508
- }
509
- const oddsList = JSON.parse(value);
510
- if (oddsList.length === 0) {
511
- return null;
512
- }
513
- // Return the last (most recent) odds entry
514
- const lastOdds = oddsList[oddsList.length - 1];
515
- return lastOdds ?? null;
516
- }
517
- catch (error) {
518
- winston_1.default.error("Error getting latest odds", error);
519
- return null;
520
- }
383
+ const addOddsToOddsCache = async (redis, gameMarketId, odds) => {
384
+ if (odds.length === 0) {
385
+ return;
386
+ }
387
+ const key = (0, cache_keys_utils_1.getOddsListKey)(gameMarketId);
388
+ // Serialize each odds entry and push to list
389
+ const serializedOdds = odds.map((o) => JSON.stringify(o));
390
+ // RPUSH appends to the end of the list (newest at the end)
391
+ await redis.rpush(key, ...serializedOdds);
392
+ winston_1.default.info(`Added ${odds.length} odds to cache list: ${key}`);
521
393
  };
522
- exports.getLatestOdds = getLatestOdds;
394
+ exports.addOddsToOddsCache = addOddsToOddsCache;
523
395
  /**
524
- * Get the latest odds with its related market
396
+ * Get the latest odds for a specific gameMarketId
525
397
  * @param redis - Redis client instance
526
- * @param gameUniqueId - Unique game identifier
527
- * @param marketType - Market type
528
- * @returns Odds with related market or null if not found
398
+ * @param gameMarketId - The game market identifier
399
+ * @returns Latest Odds object or null if not found
529
400
  */
530
- const getLatestOddsWithMarket = async (redis, gameUniqueId, marketType) => {
531
- try {
532
- // Get the latest odds
533
- const latestOdds = await (0, exports.getLatestOdds)(redis, gameUniqueId, marketType);
534
- if (!latestOdds) {
535
- return null;
536
- }
537
- // Get the related market
538
- const key = (0, cache_keys_utils_1.getMarketKeyWithGameUniqueIdMarketType)(gameUniqueId, marketType);
539
- const marketData = await redis.hget(key, "market_json");
540
- if (!marketData) {
541
- return latestOdds;
542
- }
543
- const market = JSON.parse(marketData);
544
- return {
545
- ...latestOdds,
546
- market,
547
- };
548
- }
549
- catch (error) {
550
- winston_1.default.error("Error getting latest odds with market", error);
401
+ const getLatestOdds = async (redis, gameMarketId) => {
402
+ const key = (0, cache_keys_utils_1.getOddsListKey)(gameMarketId);
403
+ // LINDEX -1 gets the last element (most recent)
404
+ const raw = await redis.lindex(key, -1);
405
+ if (!raw) {
551
406
  return null;
552
407
  }
553
- };
554
- exports.getLatestOddsWithMarket = getLatestOddsWithMarket;
555
- /**
556
- * Drop all search indices
557
- * @param redis - Redis client instance
558
- */
559
- const dropAllIndices = async (redis) => {
560
408
  try {
561
- const indices = ["market_idx"];
562
- for (const idx of indices) {
563
- await redis.call("FT.DROPINDEX", idx).catch(() => {
564
- winston_1.default.warn(`Index ${idx} does not exist`);
565
- });
566
- }
567
- winston_1.default.info("Dropped search indices");
409
+ return JSON.parse(raw);
568
410
  }
569
- catch (error) {
570
- winston_1.default.error("Error dropping indices", error);
411
+ catch (e) {
412
+ winston_1.default.warn(`Failed to parse latest odds from ${key}`, e);
413
+ return null;
571
414
  }
572
415
  };
573
- exports.dropAllIndices = dropAllIndices;
416
+ exports.getLatestOdds = getLatestOdds;
574
417
  //# sourceMappingURL=market.query.engine.js.map