@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
package/src/index.js
ADDED
|
@@ -0,0 +1,571 @@
|
|
|
1
|
+
// magazine - a decentralized library for ethereum event logging
|
|
2
|
+
// magazine uses gun.js to store events in a decentralized manner
|
|
3
|
+
// events are stored in a graph structure, where each event is a node
|
|
4
|
+
// ethers.js is used to interact with the ethereum blockchain
|
|
5
|
+
// gun.js is used to store events in a decentralized manner
|
|
6
|
+
//
|
|
7
|
+
// Usage:
|
|
8
|
+
// 1. create a new magazine
|
|
9
|
+
// 2. sync history from the blockchain to the magazine
|
|
10
|
+
// 3. publish the magazine
|
|
11
|
+
// 4. subscribe to the magazine
|
|
12
|
+
// 5. read events from the magazine
|
|
13
|
+
|
|
14
|
+
// Import required libraries and components
|
|
15
|
+
import ConfigManager from './components/ConfigManager.js';
|
|
16
|
+
import NetworkManager from './components/NetworkManager.js';
|
|
17
|
+
import EventStore from './components/EventStore.js';
|
|
18
|
+
import EventSyncer from './components/EventSyncer.js';
|
|
19
|
+
import EventMetadata from './components/EventMetadata.js';
|
|
20
|
+
import DebugUtils from './components/DebugUtils.js';
|
|
21
|
+
import DataTransformer from './components/DataTransformer.js';
|
|
22
|
+
import Logger from './components/Logger.js';
|
|
23
|
+
|
|
24
|
+
// Define Magazine class
|
|
25
|
+
export default class Magazine {
|
|
26
|
+
constructor(config) {
|
|
27
|
+
// Initialize configuration manager and validate config
|
|
28
|
+
this.configManager = new ConfigManager(config);
|
|
29
|
+
const validatedConfig = this.configManager.getAll();
|
|
30
|
+
|
|
31
|
+
// Initialize logger
|
|
32
|
+
this.logger = new Logger({
|
|
33
|
+
context: 'Magazine',
|
|
34
|
+
level: validatedConfig.logLevel || 'info'
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
this.logger.info('Initializing Magazine', {
|
|
38
|
+
name: validatedConfig.name,
|
|
39
|
+
address: validatedConfig.address,
|
|
40
|
+
networkId: validatedConfig.networkId
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
// Initialize retry operation as a shared utility
|
|
44
|
+
this.retryOptions = this.configManager.getRetryOptions();
|
|
45
|
+
|
|
46
|
+
// Initialize modular components with dependency injection
|
|
47
|
+
this.networkManager = new NetworkManager(validatedConfig, this.retryOperation.bind(this));
|
|
48
|
+
this.eventStore = new EventStore(validatedConfig.gun, this.retryOperation.bind(this));
|
|
49
|
+
this.eventSyncer = new EventSyncer(
|
|
50
|
+
this.networkManager,
|
|
51
|
+
this.eventStore,
|
|
52
|
+
this.configManager,
|
|
53
|
+
this.retryOperation.bind(this)
|
|
54
|
+
);
|
|
55
|
+
|
|
56
|
+
// Initialize EventMetadata component
|
|
57
|
+
this.eventMetadata = new EventMetadata(this.networkManager, this.logger);
|
|
58
|
+
|
|
59
|
+
// Initialize DebugUtils component
|
|
60
|
+
this.debugUtils = new DebugUtils(this, this.logger);
|
|
61
|
+
|
|
62
|
+
// Initialize DataTransformer component
|
|
63
|
+
this.dataTransformer = new DataTransformer(
|
|
64
|
+
this.eventStore.getGunInstance(),
|
|
65
|
+
{
|
|
66
|
+
logLevel: validatedConfig.logLevel,
|
|
67
|
+
enableAutoTransform: validatedConfig.enableAutoTransform !== false
|
|
68
|
+
}
|
|
69
|
+
);
|
|
70
|
+
|
|
71
|
+
// Expose configuration for backward compatibility
|
|
72
|
+
this.name = validatedConfig.name;
|
|
73
|
+
this.address = validatedConfig.address;
|
|
74
|
+
this.networkId = validatedConfig.networkId;
|
|
75
|
+
this.abi = validatedConfig.abi;
|
|
76
|
+
|
|
77
|
+
this.logger.info('Magazine initialized successfully', {
|
|
78
|
+
name: this.name,
|
|
79
|
+
address: this.address,
|
|
80
|
+
networkId: this.networkId,
|
|
81
|
+
eventCount: Object.keys(this.networkManager.eventsByTopic).length,
|
|
82
|
+
componentCount: 6,
|
|
83
|
+
debugModeEnabled: this.debugUtils.isDebugMode(),
|
|
84
|
+
dataTransformerEnabled: true
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Get configuration manager for advanced configuration access
|
|
89
|
+
getConfigManager() {
|
|
90
|
+
return this.configManager;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Get network manager for advanced network operations
|
|
94
|
+
getNetworkManager() {
|
|
95
|
+
return this.networkManager;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Get event store for advanced storage operations
|
|
99
|
+
getEventStore() {
|
|
100
|
+
return this.eventStore;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Get event syncer for advanced synchronization operations
|
|
104
|
+
getEventSyncer() {
|
|
105
|
+
return this.eventSyncer;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Get logger for external logging operations
|
|
109
|
+
getLogger() {
|
|
110
|
+
return this.logger;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Set log level dynamically
|
|
114
|
+
setLogLevel(level) {
|
|
115
|
+
this.logger.setLevel(level);
|
|
116
|
+
this.logger.info('Log level updated', { newLevel: level });
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Retry wrapper for async operations
|
|
120
|
+
async retryOperation(operation, context = '') {
|
|
121
|
+
const { maxRetries, retryDelay, timeout } = this.retryOptions;
|
|
122
|
+
let lastError;
|
|
123
|
+
|
|
124
|
+
this.logger.debug('Starting retry operation', { context, maxRetries, timeout });
|
|
125
|
+
|
|
126
|
+
for (let attempt = 1; attempt <= maxRetries; attempt++) {
|
|
127
|
+
try {
|
|
128
|
+
// Add timeout to the operation
|
|
129
|
+
const timeoutPromise = new Promise((_, reject) =>
|
|
130
|
+
setTimeout(() => reject(new Error(`Operation timeout: ${context}`)), timeout)
|
|
131
|
+
);
|
|
132
|
+
|
|
133
|
+
const result = await Promise.race([operation(), timeoutPromise]);
|
|
134
|
+
|
|
135
|
+
if (attempt > 1) {
|
|
136
|
+
this.logger.info('Retry operation succeeded', {
|
|
137
|
+
context,
|
|
138
|
+
attempt,
|
|
139
|
+
maxRetries
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
return result;
|
|
144
|
+
} catch (error) {
|
|
145
|
+
lastError = error;
|
|
146
|
+
|
|
147
|
+
this.logger.warn('Retry operation failed', {
|
|
148
|
+
context,
|
|
149
|
+
attempt,
|
|
150
|
+
maxRetries,
|
|
151
|
+
error: error.message
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
if (attempt < maxRetries) {
|
|
155
|
+
const delay = retryDelay * Math.pow(2, attempt - 1); // Exponential backoff
|
|
156
|
+
this.logger.debug('Retrying operation', {
|
|
157
|
+
context,
|
|
158
|
+
delay: `${delay}ms`,
|
|
159
|
+
nextAttempt: attempt + 1
|
|
160
|
+
});
|
|
161
|
+
await new Promise(resolve => setTimeout(resolve, delay));
|
|
162
|
+
} else {
|
|
163
|
+
this.logger.error('Retry operation exhausted', {
|
|
164
|
+
context,
|
|
165
|
+
totalAttempts: maxRetries,
|
|
166
|
+
finalError: error.message,
|
|
167
|
+
stack: error.stack
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
throw lastError;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// Start syncing events from the blockchain to the magazine
|
|
177
|
+
startSync() {
|
|
178
|
+
this.logger.info('Starting automatic sync');
|
|
179
|
+
this.stopSync();
|
|
180
|
+
|
|
181
|
+
this.syncInterval = setInterval(async () => {
|
|
182
|
+
try {
|
|
183
|
+
await this.syncEvents();
|
|
184
|
+
} catch (error) {
|
|
185
|
+
this.logger.error('Automatic sync error', {
|
|
186
|
+
error: error.message,
|
|
187
|
+
stack: error.stack
|
|
188
|
+
});
|
|
189
|
+
// Continue syncing despite errors
|
|
190
|
+
}
|
|
191
|
+
}, 5000);
|
|
192
|
+
|
|
193
|
+
// Initial sync
|
|
194
|
+
this.syncEvents().catch(error => {
|
|
195
|
+
this.logger.error('Initial sync error', {
|
|
196
|
+
error: error.message,
|
|
197
|
+
stack: error.stack
|
|
198
|
+
});
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
this.logger.info('Automatic sync started', { interval: '5000ms' });
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Start real-time event streaming (requires WebSocket provider)
|
|
206
|
+
*/
|
|
207
|
+
startRealtimeSync() {
|
|
208
|
+
return this.eventSyncer.startRealtimeSync();
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* Stop real-time event streaming
|
|
213
|
+
*/
|
|
214
|
+
stopRealtimeSync() {
|
|
215
|
+
return this.eventSyncer.stopRealtimeSync();
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Destroy the magazine instance, stopping sync and cleaning up listeners
|
|
220
|
+
*/
|
|
221
|
+
destroy() {
|
|
222
|
+
this.stopSync();
|
|
223
|
+
this.stopRealtimeSync();
|
|
224
|
+
this.eventStore.destroy();
|
|
225
|
+
this.logger.info('Magazine instance destroyed');
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// Stop syncing events from the blockchain to the magazine
|
|
229
|
+
stopSync() {
|
|
230
|
+
if (this.syncInterval) {
|
|
231
|
+
this.logger.info('Stopping automatic sync');
|
|
232
|
+
clearInterval(this.syncInterval);
|
|
233
|
+
this.syncInterval = undefined;
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// Sync events from the blockchain to the magazine - delegates to EventSyncer
|
|
238
|
+
async syncEvents() {
|
|
239
|
+
return this.eventSyncer.syncEvents();
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// Sync events from a specific block range
|
|
243
|
+
async syncEventsFromRange(fromBlock, toBlock) {
|
|
244
|
+
return this.eventSyncer.syncEventsFromRange(fromBlock, toBlock);
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// Sync events from a specific transaction
|
|
248
|
+
async syncEventsFromTransaction(txHash) {
|
|
249
|
+
return this.eventSyncer.syncEventsFromTransaction(txHash);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// Get sync status information
|
|
253
|
+
async getSyncStatus() {
|
|
254
|
+
return this.eventSyncer.getSyncStatus();
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// Get the start block number for syncing events - delegates to EventStore
|
|
258
|
+
async getStartBlockNumber() {
|
|
259
|
+
return this.eventStore.getStartBlockNumber();
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// Set the start block number for syncing events - delegates to EventStore
|
|
263
|
+
async setStartBlockNumber(startBlockNumber) {
|
|
264
|
+
this.configManager.validateBlockNumber(startBlockNumber, 'startBlockNumber');
|
|
265
|
+
return this.eventStore.setStartBlockNumber(startBlockNumber);
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// Get events by name with optional filtering
|
|
269
|
+
async getEvents(eventName, filter = {}) {
|
|
270
|
+
// Use the new filtering system
|
|
271
|
+
const result = await this.getAllEvents({
|
|
272
|
+
filter: { ...filter, eventName }
|
|
273
|
+
});
|
|
274
|
+
return result.data;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
// Get all events with optional filtering and pagination
|
|
278
|
+
async getAllEvents(options = {}) {
|
|
279
|
+
return this.eventStore.getAllEvents(options);
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
/**
|
|
283
|
+
* Query events using fluent API
|
|
284
|
+
* @returns {Promise<Object>} Query builder
|
|
285
|
+
* @example
|
|
286
|
+
* const results = await magazine.query()
|
|
287
|
+
* .eventName('Transfer')
|
|
288
|
+
* .blockRange(1000, 2000)
|
|
289
|
+
* .where('value', { gte: 1000 })
|
|
290
|
+
* .orderBy('blockNumber', 'desc')
|
|
291
|
+
* .execute();
|
|
292
|
+
*/
|
|
293
|
+
async query() {
|
|
294
|
+
return this.eventStore.query();
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
/**
|
|
298
|
+
* Get events by transaction hash
|
|
299
|
+
* @param {string} transactionHash - Transaction hash to search for
|
|
300
|
+
* @returns {Promise<Array>} Events from the transaction
|
|
301
|
+
*/
|
|
302
|
+
async getEventsByTransactionHash(transactionHash) {
|
|
303
|
+
if (!transactionHash || typeof transactionHash !== 'string') {
|
|
304
|
+
throw new Error('Transaction hash must be a non-empty string');
|
|
305
|
+
}
|
|
306
|
+
return this.eventStore.getEventsByTransactionHash(transactionHash);
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
/**
|
|
310
|
+
* Get events by block number
|
|
311
|
+
* @param {number} blockNumber - Block number to search for
|
|
312
|
+
* @returns {Promise<Array>} Events from the block
|
|
313
|
+
*/
|
|
314
|
+
async getEventsByBlockNumber(blockNumber) {
|
|
315
|
+
if (!Number.isInteger(blockNumber) || blockNumber < 0) {
|
|
316
|
+
throw new Error('Block number must be a non-negative integer');
|
|
317
|
+
}
|
|
318
|
+
return this.eventStore.getEventsByBlockNumber(blockNumber);
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
/**
|
|
322
|
+
* Create async iterator for events
|
|
323
|
+
* @param {Object} options - Iterator options
|
|
324
|
+
* @param {Object} options.filter - Filter criteria
|
|
325
|
+
* @param {Object} options.sort - Sort options
|
|
326
|
+
* @param {number} options.pageSize - Items per iteration batch
|
|
327
|
+
* @returns {AsyncIterator} Async iterator for events
|
|
328
|
+
* @example
|
|
329
|
+
* for await (const event of magazine.iterateEvents({ pageSize: 50 })) {
|
|
330
|
+
* console.log(event);
|
|
331
|
+
* }
|
|
332
|
+
*/
|
|
333
|
+
async *iterateEvents(options = {}) {
|
|
334
|
+
yield* this.eventStore.iterateEvents(options);
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
// Get network information
|
|
338
|
+
async getNetworkInfo() {
|
|
339
|
+
return this.networkManager.getNetworkInfo();
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
// Validate network and contract
|
|
343
|
+
async validateNetwork() {
|
|
344
|
+
return this.networkManager.validateNetwork();
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
// Get latest block number
|
|
348
|
+
async getLatestBlockNumber() {
|
|
349
|
+
return this.networkManager.getLatestBlockNumber();
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
// Publish the magazine to the decentralized network
|
|
353
|
+
async publish(options = {}) {
|
|
354
|
+
const validatedOptions = this.configManager.validateOptions(options, ['alias', 'url']);
|
|
355
|
+
|
|
356
|
+
// Validate specific option values
|
|
357
|
+
if (validatedOptions.alias && (typeof validatedOptions.alias !== 'string' || validatedOptions.alias.trim().length === 0)) {
|
|
358
|
+
throw new Error('alias must be a non-empty string');
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
if (validatedOptions.url) {
|
|
362
|
+
if (typeof validatedOptions.url !== 'string') {
|
|
363
|
+
throw new Error('url must be a string');
|
|
364
|
+
}
|
|
365
|
+
try {
|
|
366
|
+
new URL(validatedOptions.url);
|
|
367
|
+
} catch {
|
|
368
|
+
throw new Error('url must be a valid URL');
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
const alias = validatedOptions.alias ? validatedOptions.alias : this.name.toLowerCase().replace(/\s/g, '-');
|
|
373
|
+
const url = validatedOptions.url ? validatedOptions.url : `https://gunjs.herokuapp.com/gun`;
|
|
374
|
+
|
|
375
|
+
const magazineData = {
|
|
376
|
+
name: this.name,
|
|
377
|
+
address: this.address,
|
|
378
|
+
networkId: this.networkId,
|
|
379
|
+
abi: this.abi,
|
|
380
|
+
};
|
|
381
|
+
|
|
382
|
+
return this.eventStore.publishMagazine(alias, magazineData, url);
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
// Subscribe to a magazine on the decentralized network
|
|
386
|
+
static async subscribe(magazineId, config) {
|
|
387
|
+
// Validate parameters
|
|
388
|
+
if (!magazineId || typeof magazineId !== 'string') {
|
|
389
|
+
throw new Error('magazineId must be a non-empty string');
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
if (!config || typeof config !== 'object') {
|
|
393
|
+
throw new Error('config must be an object');
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
// Validate required config fields for subscription
|
|
397
|
+
const requiredFields = ['gun'];
|
|
398
|
+
for (const field of requiredFields) {
|
|
399
|
+
if (!config[field]) {
|
|
400
|
+
throw new Error(`Config field '${field}' is required for subscription`);
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
// Create a temporary event store for subscription
|
|
405
|
+
const tempEventStore = new EventStore(config.gun, () => {});
|
|
406
|
+
return tempEventStore.subscribeMagazine(magazineId);
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
// Read events from the magazine - delegates to EventStore
|
|
410
|
+
on(eventName, callback) {
|
|
411
|
+
if (typeof callback !== 'function') {
|
|
412
|
+
throw new Error('callback must be a function');
|
|
413
|
+
}
|
|
414
|
+
const gunInstance = this.eventStore.getGunInstance();
|
|
415
|
+
const eventNode = gunInstance.get('events').get(eventName);
|
|
416
|
+
eventNode.map().on(callback);
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
/**
|
|
420
|
+
* Health check endpoint - performs system health checks
|
|
421
|
+
* @returns {Promise<Object>} Health check results
|
|
422
|
+
*/
|
|
423
|
+
async healthCheck() {
|
|
424
|
+
return this.debugUtils.healthCheck();
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
/**
|
|
428
|
+
* Get performance metrics
|
|
429
|
+
* @returns {Promise<Object>} Performance metrics
|
|
430
|
+
*/
|
|
431
|
+
async getPerformanceMetrics() {
|
|
432
|
+
return this.debugUtils.getPerformanceReport();
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
/**
|
|
436
|
+
* Export debug information
|
|
437
|
+
* @param {Object} options - Export options
|
|
438
|
+
* @returns {Promise<Object>} Debug information
|
|
439
|
+
*/
|
|
440
|
+
async exportDebugInfo(options = {}) {
|
|
441
|
+
return this.debugUtils.exportDebugData();
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
/**
|
|
445
|
+
* Enable or disable debug mode
|
|
446
|
+
* @param {boolean} enabled - Whether to enable debug mode
|
|
447
|
+
*/
|
|
448
|
+
setDebugMode(enabled) {
|
|
449
|
+
this.debugUtils.setDebugMode(enabled);
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
/**
|
|
453
|
+
* Check if debug mode is enabled
|
|
454
|
+
* @returns {boolean} Debug mode status
|
|
455
|
+
*/
|
|
456
|
+
isDebugMode() {
|
|
457
|
+
return this.debugUtils.isDebugMode();
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
/**
|
|
461
|
+
* Get configuration schema documentation
|
|
462
|
+
* @returns {Object} Schema documentation
|
|
463
|
+
*/
|
|
464
|
+
getConfigSchema() {
|
|
465
|
+
return this.configManager.getSchemaDocumentation();
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
/**
|
|
469
|
+
* Generate example configuration
|
|
470
|
+
* @param {Object} options - Generation options
|
|
471
|
+
* @returns {Object} Example configuration
|
|
472
|
+
*/
|
|
473
|
+
static generateExampleConfig(options = {}) {
|
|
474
|
+
const configManager = new ConfigManager({
|
|
475
|
+
name: 'temp',
|
|
476
|
+
address: '0x0000000000000000000000000000000000000000',
|
|
477
|
+
abi: [],
|
|
478
|
+
provider: 'http://localhost:8545',
|
|
479
|
+
networkId: 1
|
|
480
|
+
});
|
|
481
|
+
return configManager.generateExampleConfig(options);
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
/**
|
|
485
|
+
* Validate configuration without creating an instance
|
|
486
|
+
* @static
|
|
487
|
+
* @param {Object} config - Configuration to validate
|
|
488
|
+
* @returns {Object} Validation result
|
|
489
|
+
*/
|
|
490
|
+
static validateConfig(config) {
|
|
491
|
+
return ConfigManager.validateConfiguration(config);
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
/**
|
|
495
|
+
* Get compression statistics from the event store
|
|
496
|
+
* @returns {Promise<Object>} Compression stats
|
|
497
|
+
*/
|
|
498
|
+
async getCompressionStats() {
|
|
499
|
+
return this.eventStore.getCompressionStats();
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
// === Data Transformation API ===
|
|
503
|
+
|
|
504
|
+
/**
|
|
505
|
+
* Create or get a data model for structured storage
|
|
506
|
+
* @param {string} name - Model name
|
|
507
|
+
* @param {Object} schema - Model schema definition
|
|
508
|
+
* @param {Object} options - Model options
|
|
509
|
+
* @returns {Object} DataModel instance
|
|
510
|
+
*/
|
|
511
|
+
model(name, schema, options = {}) {
|
|
512
|
+
return this.dataTransformer.model(name, schema, options);
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
/**
|
|
516
|
+
* Register an event transformer that converts blockchain events to model data
|
|
517
|
+
* @param {string} eventName - Event name to transform
|
|
518
|
+
* @param {string} modelName - Target model name
|
|
519
|
+
* @param {Function} transformFn - Transformation function
|
|
520
|
+
*/
|
|
521
|
+
registerTransformer(eventName, modelName, transformFn) {
|
|
522
|
+
return this.dataTransformer.registerTransformer(eventName, modelName, transformFn);
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
/**
|
|
526
|
+
* Get a registered data model
|
|
527
|
+
* @param {string} name - Model name
|
|
528
|
+
* @returns {Object} DataModel instance
|
|
529
|
+
*/
|
|
530
|
+
getModel(name) {
|
|
531
|
+
return this.dataTransformer.getModel(name);
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
/**
|
|
535
|
+
* Transform an event using registered transformers
|
|
536
|
+
* @param {Object} event - Blockchain event to transform
|
|
537
|
+
* @returns {Promise<Object|null>} Transformed data or null
|
|
538
|
+
*/
|
|
539
|
+
async transformEvent(event) {
|
|
540
|
+
return this.dataTransformer.transformEvent(event);
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
/**
|
|
544
|
+
* Get data transformation statistics
|
|
545
|
+
* @returns {Object} Transformation stats
|
|
546
|
+
*/
|
|
547
|
+
getTransformationStats() {
|
|
548
|
+
return this.dataTransformer.getStats();
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
/**
|
|
552
|
+
* Get all registered models
|
|
553
|
+
* @returns {Map} Map of model name to DataModel instance
|
|
554
|
+
*/
|
|
555
|
+
getModels() {
|
|
556
|
+
return this.dataTransformer.getModels();
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
// Named exports for advanced usage
|
|
561
|
+
export { default as ConfigManager } from './components/ConfigManager.js';
|
|
562
|
+
export { default as NetworkManager } from './components/NetworkManager.js';
|
|
563
|
+
export { default as EventStore } from './components/EventStore.js';
|
|
564
|
+
export { default as EventSyncer } from './components/EventSyncer.js';
|
|
565
|
+
export { default as EventMetadata } from './components/EventMetadata.js';
|
|
566
|
+
export { default as DebugUtils } from './components/DebugUtils.js';
|
|
567
|
+
export { default as DataTransformer, ValidationError, ModelError, DataModel, AggregationPipeline, SchemaMigration } from './components/DataTransformer.js';
|
|
568
|
+
export { default as EventFilter } from './components/EventFilter.js';
|
|
569
|
+
export { default as DataCompressor } from './components/DataCompressor.js';
|
|
570
|
+
export { default as Paginator, PaginationHelper } from './components/Paginator.js';
|
|
571
|
+
export { default as Logger } from './components/Logger.js';
|