@sschepis/magazine 0.1.0
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.
- package/LICENSE +21 -0
- package/README.md +167 -0
- package/dist/magazine.cjs.js +5 -0
- package/dist/magazine.cjs.js.map +1 -0
- package/dist/magazine.es.js +4149 -0
- package/dist/magazine.es.js.map +1 -0
- package/dist/magazine.umd.js +5 -0
- package/dist/magazine.umd.js.map +1 -0
- package/package.json +74 -0
- package/src/components/ConfigManager.js +202 -0
- package/src/components/ConfigSchema.js +506 -0
- package/src/components/DataCompressor.js +378 -0
- package/src/components/DataTransformer.js +1172 -0
- package/src/components/DebugUtils.js +657 -0
- package/src/components/EventFilter.js +474 -0
- package/src/components/EventMetadata.js +273 -0
- package/src/components/EventStore.js +443 -0
- package/src/components/EventSyncer.js +353 -0
- package/src/components/Logger.js +299 -0
- package/src/components/NetworkManager.js +461 -0
- package/src/components/Paginator.js +343 -0
- package/src/index.js +571 -0
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* EventMetadata - Enriches events with additional blockchain metadata
|
|
3
|
+
*/
|
|
4
|
+
export default class EventMetadata {
|
|
5
|
+
constructor(networkManager, logger) {
|
|
6
|
+
this.networkManager = networkManager;
|
|
7
|
+
this.logger = logger.child({ context: 'EventMetadata' });
|
|
8
|
+
this.logger.info('EventMetadata initialized');
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Enrich event with metadata
|
|
13
|
+
* @param {Object} event - Event to enrich
|
|
14
|
+
* @param {Object} options - Enrichment options
|
|
15
|
+
* @param {boolean} options.includeBlock - Include block data
|
|
16
|
+
* @param {boolean} options.includeTransaction - Include transaction data
|
|
17
|
+
* @param {boolean} options.includeTimestamp - Include block timestamp
|
|
18
|
+
* @param {boolean} options.includeGasCost - Include gas cost
|
|
19
|
+
* @returns {Promise<Object>} Enriched event
|
|
20
|
+
*/
|
|
21
|
+
async enrichEvent(event, options = {}) {
|
|
22
|
+
const {
|
|
23
|
+
includeBlock = true,
|
|
24
|
+
includeTransaction = true,
|
|
25
|
+
includeTimestamp = true,
|
|
26
|
+
includeGasCost = true
|
|
27
|
+
} = options;
|
|
28
|
+
|
|
29
|
+
try {
|
|
30
|
+
const enrichedEvent = { ...event };
|
|
31
|
+
const promises = [];
|
|
32
|
+
|
|
33
|
+
// Fetch block data if needed
|
|
34
|
+
if ((includeBlock || includeTimestamp) && event.blockNumber) {
|
|
35
|
+
promises.push(
|
|
36
|
+
this.networkManager.provider.getBlock(event.blockNumber)
|
|
37
|
+
.then(block => {
|
|
38
|
+
if (block) {
|
|
39
|
+
if (includeBlock) {
|
|
40
|
+
enrichedEvent.block = {
|
|
41
|
+
number: block.number,
|
|
42
|
+
hash: block.hash,
|
|
43
|
+
parentHash: block.parentHash,
|
|
44
|
+
miner: block.miner,
|
|
45
|
+
difficulty: block.difficulty?.toString(),
|
|
46
|
+
gasLimit: block.gasLimit.toString(),
|
|
47
|
+
gasUsed: block.gasUsed.toString()
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
if (includeTimestamp) {
|
|
51
|
+
enrichedEvent.timestamp = block.timestamp;
|
|
52
|
+
enrichedEvent.date = new Date(block.timestamp * 1000).toISOString();
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
})
|
|
56
|
+
.catch(err => {
|
|
57
|
+
this.logger.warn('Failed to fetch block data', {
|
|
58
|
+
blockNumber: event.blockNumber,
|
|
59
|
+
error: err.message
|
|
60
|
+
});
|
|
61
|
+
})
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Fetch transaction data if needed
|
|
66
|
+
if ((includeTransaction || includeGasCost) && event.transactionHash) {
|
|
67
|
+
promises.push(
|
|
68
|
+
Promise.all([
|
|
69
|
+
this.networkManager.provider.getTransaction(event.transactionHash),
|
|
70
|
+
includeGasCost ? this.networkManager.provider.getTransactionReceipt(event.transactionHash) : null
|
|
71
|
+
]).then(([tx, receipt]) => {
|
|
72
|
+
if (tx) {
|
|
73
|
+
if (includeTransaction) {
|
|
74
|
+
enrichedEvent.transaction = {
|
|
75
|
+
hash: tx.hash,
|
|
76
|
+
from: tx.from,
|
|
77
|
+
to: tx.to,
|
|
78
|
+
value: tx.value.toString(),
|
|
79
|
+
nonce: tx.nonce,
|
|
80
|
+
gasLimit: tx.gasLimit.toString(),
|
|
81
|
+
gasPrice: tx.gasPrice?.toString(),
|
|
82
|
+
maxFeePerGas: tx.maxFeePerGas?.toString(),
|
|
83
|
+
maxPriorityFeePerGas: tx.maxPriorityFeePerGas?.toString(),
|
|
84
|
+
data: tx.data.length > 66 ? tx.data.substring(0, 66) + '...' : tx.data
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
if (includeGasCost && receipt) {
|
|
88
|
+
const gasUsed = receipt.gasUsed;
|
|
89
|
+
const gasPrice = tx.gasPrice || tx.maxFeePerGas || 0n;
|
|
90
|
+
const gasCost = gasUsed * gasPrice;
|
|
91
|
+
|
|
92
|
+
enrichedEvent.gas = {
|
|
93
|
+
used: gasUsed.toString(),
|
|
94
|
+
price: gasPrice.toString(),
|
|
95
|
+
cost: gasCost.toString(),
|
|
96
|
+
costInEther: this._formatEther(gasCost)
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}).catch(err => {
|
|
101
|
+
this.logger.warn('Failed to fetch transaction data', {
|
|
102
|
+
transactionHash: event.transactionHash,
|
|
103
|
+
error: err.message
|
|
104
|
+
});
|
|
105
|
+
})
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Wait for all enrichment operations
|
|
110
|
+
await Promise.all(promises);
|
|
111
|
+
|
|
112
|
+
// Add metadata about the enrichment
|
|
113
|
+
enrichedEvent._metadata = {
|
|
114
|
+
enrichedAt: new Date().toISOString(),
|
|
115
|
+
enrichmentOptions: options
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
this.logger.debug('Event enriched with metadata', {
|
|
119
|
+
eventName: event.eventName,
|
|
120
|
+
blockNumber: event.blockNumber,
|
|
121
|
+
metadataAdded: Object.keys(enrichedEvent).filter(k => !event.hasOwnProperty(k))
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
return enrichedEvent;
|
|
125
|
+
|
|
126
|
+
} catch (error) {
|
|
127
|
+
this.logger.error('Failed to enrich event', {
|
|
128
|
+
event,
|
|
129
|
+
error: error.message
|
|
130
|
+
});
|
|
131
|
+
// Return original event on error
|
|
132
|
+
return event;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Enrich multiple events in batch
|
|
138
|
+
* @param {Array} events - Events to enrich
|
|
139
|
+
* @param {Object} options - Enrichment options
|
|
140
|
+
* @param {number} options.batchSize - Number of events to process concurrently
|
|
141
|
+
* @returns {Promise<Array>} Enriched events
|
|
142
|
+
*/
|
|
143
|
+
async enrichEvents(events, options = {}) {
|
|
144
|
+
const { batchSize = 10, ...enrichOptions } = options;
|
|
145
|
+
|
|
146
|
+
return this.logger.time('enrichEvents', async () => {
|
|
147
|
+
const enrichedEvents = [];
|
|
148
|
+
|
|
149
|
+
// Process in batches to avoid overwhelming the provider
|
|
150
|
+
let failureCount = 0;
|
|
151
|
+
for (let i = 0; i < events.length; i += batchSize) {
|
|
152
|
+
const batch = events.slice(i, i + batchSize);
|
|
153
|
+
const results = await Promise.allSettled(
|
|
154
|
+
batch.map(event => this.enrichEvent(event, enrichOptions))
|
|
155
|
+
);
|
|
156
|
+
for (let j = 0; j < results.length; j++) {
|
|
157
|
+
if (results[j].status === 'fulfilled') {
|
|
158
|
+
enrichedEvents.push(results[j].value);
|
|
159
|
+
} else {
|
|
160
|
+
failureCount++;
|
|
161
|
+
this.logger.warn('Failed to enrich event, using original', {
|
|
162
|
+
error: results[j].reason?.message,
|
|
163
|
+
blockNumber: batch[j]?.blockNumber
|
|
164
|
+
});
|
|
165
|
+
enrichedEvents.push(batch[j]);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
this.logger.debug('Enriched event batch', {
|
|
170
|
+
batchIndex: Math.floor(i / batchSize),
|
|
171
|
+
batchSize: batch.length,
|
|
172
|
+
failures: failureCount,
|
|
173
|
+
totalProgress: `${enrichedEvents.length}/${events.length}`
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
this.logger.info('Completed event enrichment', {
|
|
178
|
+
totalEvents: events.length,
|
|
179
|
+
enrichedEvents: enrichedEvents.length,
|
|
180
|
+
failures: failureCount
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
return enrichedEvents;
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Calculate statistics for a set of events
|
|
189
|
+
* @param {Array} events - Events to analyze
|
|
190
|
+
* @returns {Object} Event statistics
|
|
191
|
+
*/
|
|
192
|
+
calculateStatistics(events) {
|
|
193
|
+
if (!Array.isArray(events) || events.length === 0) {
|
|
194
|
+
return {
|
|
195
|
+
count: 0,
|
|
196
|
+
blockRange: { min: null, max: null },
|
|
197
|
+
timeRange: { min: null, max: null },
|
|
198
|
+
gasStats: { total: '0', average: '0', min: '0', max: '0' }
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
const blockNumbers = events
|
|
203
|
+
.map(e => e.blockNumber)
|
|
204
|
+
.filter(n => n != null)
|
|
205
|
+
.sort((a, b) => a - b);
|
|
206
|
+
|
|
207
|
+
const timestamps = events
|
|
208
|
+
.map(e => e.timestamp)
|
|
209
|
+
.filter(t => t != null)
|
|
210
|
+
.sort((a, b) => a - b);
|
|
211
|
+
|
|
212
|
+
const gasCosts = events
|
|
213
|
+
.map(e => e.gas?.cost)
|
|
214
|
+
.filter(c => c != null)
|
|
215
|
+
.map(c => BigInt(c));
|
|
216
|
+
|
|
217
|
+
const stats = {
|
|
218
|
+
count: events.length,
|
|
219
|
+
blockRange: {
|
|
220
|
+
min: blockNumbers[0] || null,
|
|
221
|
+
max: blockNumbers[blockNumbers.length - 1] || null,
|
|
222
|
+
span: blockNumbers.length > 0 ? blockNumbers[blockNumbers.length - 1] - blockNumbers[0] : 0
|
|
223
|
+
},
|
|
224
|
+
timeRange: {
|
|
225
|
+
min: timestamps[0] || null,
|
|
226
|
+
max: timestamps[timestamps.length - 1] || null,
|
|
227
|
+
span: timestamps.length > 0 ? timestamps[timestamps.length - 1] - timestamps[0] : 0
|
|
228
|
+
}
|
|
229
|
+
};
|
|
230
|
+
|
|
231
|
+
if (gasCosts.length > 0) {
|
|
232
|
+
const totalGas = gasCosts.reduce((sum, cost) => sum + cost, 0n);
|
|
233
|
+
const avgGas = totalGas / BigInt(gasCosts.length);
|
|
234
|
+
const sortedGas = [...gasCosts].sort((a, b) => a < b ? -1 : 1);
|
|
235
|
+
|
|
236
|
+
stats.gasStats = {
|
|
237
|
+
total: totalGas.toString(),
|
|
238
|
+
average: avgGas.toString(),
|
|
239
|
+
min: sortedGas[0].toString(),
|
|
240
|
+
max: sortedGas[sortedGas.length - 1].toString(),
|
|
241
|
+
totalInEther: this._formatEther(totalGas)
|
|
242
|
+
};
|
|
243
|
+
} else {
|
|
244
|
+
stats.gasStats = { total: '0', average: '0', min: '0', max: '0' };
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// Event type distribution
|
|
248
|
+
const eventTypes = {};
|
|
249
|
+
events.forEach(e => {
|
|
250
|
+
if (e.eventName) {
|
|
251
|
+
eventTypes[e.eventName] = (eventTypes[e.eventName] || 0) + 1;
|
|
252
|
+
}
|
|
253
|
+
});
|
|
254
|
+
stats.eventTypes = eventTypes;
|
|
255
|
+
|
|
256
|
+
this.logger.debug('Calculated event statistics', stats);
|
|
257
|
+
return stats;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* Format wei to ether string
|
|
262
|
+
* @private
|
|
263
|
+
*/
|
|
264
|
+
_formatEther(wei) {
|
|
265
|
+
try {
|
|
266
|
+
const weiString = wei.toString();
|
|
267
|
+
const ethValue = Number(weiString) / 1e18;
|
|
268
|
+
return ethValue.toFixed(6) + ' ETH';
|
|
269
|
+
} catch {
|
|
270
|
+
return '0 ETH';
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
}
|