@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,378 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DataCompressor - Handles data compression and decompression for storage optimization
|
|
3
|
+
*/
|
|
4
|
+
import { gzip, ungzip } from 'pako';
|
|
5
|
+
|
|
6
|
+
const isNode = typeof process !== 'undefined' && process.versions != null && process.versions.node != null;
|
|
7
|
+
|
|
8
|
+
function toBase64(uint8Array) {
|
|
9
|
+
if (isNode) {
|
|
10
|
+
return Buffer.from(uint8Array).toString('base64');
|
|
11
|
+
}
|
|
12
|
+
return btoa(String.fromCharCode.apply(null, uint8Array));
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function fromBase64(base64String) {
|
|
16
|
+
if (isNode) {
|
|
17
|
+
const buf = Buffer.from(base64String, 'base64');
|
|
18
|
+
return new Uint8Array(buf.buffer, buf.byteOffset, buf.byteLength);
|
|
19
|
+
}
|
|
20
|
+
const binaryString = atob(base64String);
|
|
21
|
+
const bytes = new Uint8Array(binaryString.length);
|
|
22
|
+
for (let i = 0; i < binaryString.length; i++) {
|
|
23
|
+
bytes[i] = binaryString.charCodeAt(i);
|
|
24
|
+
}
|
|
25
|
+
return bytes;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export default class DataCompressor {
|
|
29
|
+
constructor(logger) {
|
|
30
|
+
this.logger = logger.child({ context: 'DataCompressor' });
|
|
31
|
+
this.logger.info('DataCompressor initialized');
|
|
32
|
+
|
|
33
|
+
// Compression strategies
|
|
34
|
+
this.strategies = {
|
|
35
|
+
none: {
|
|
36
|
+
compress: (data) => data,
|
|
37
|
+
decompress: (data) => data,
|
|
38
|
+
ratio: 1
|
|
39
|
+
},
|
|
40
|
+
json: {
|
|
41
|
+
compress: (data) => this._compressJSON(data),
|
|
42
|
+
decompress: (data) => this._decompressJSON(data),
|
|
43
|
+
ratio: 0.7 // Approximate
|
|
44
|
+
},
|
|
45
|
+
gzip: {
|
|
46
|
+
compress: (data) => this._compressGzip(data),
|
|
47
|
+
decompress: (data) => this._decompressGzip(data),
|
|
48
|
+
ratio: 0.3 // Approximate
|
|
49
|
+
},
|
|
50
|
+
optimized: {
|
|
51
|
+
compress: (data) => this._compressOptimized(data),
|
|
52
|
+
decompress: (data) => this._decompressOptimized(data),
|
|
53
|
+
ratio: 0.4 // Approximate
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
this.defaultStrategy = 'optimized';
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Compress data using specified strategy
|
|
62
|
+
* @param {any} data - Data to compress
|
|
63
|
+
* @param {string} strategy - Compression strategy
|
|
64
|
+
* @returns {Object} Compressed data with metadata
|
|
65
|
+
*/
|
|
66
|
+
compress(data, strategy = this.defaultStrategy) {
|
|
67
|
+
try {
|
|
68
|
+
const startTime = Date.now();
|
|
69
|
+
const originalSize = this._calculateSize(data);
|
|
70
|
+
|
|
71
|
+
if (!this.strategies[strategy]) {
|
|
72
|
+
this.logger.warn('Unknown compression strategy, using default', { strategy });
|
|
73
|
+
strategy = this.defaultStrategy;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const compressed = this.strategies[strategy].compress(data);
|
|
77
|
+
const compressedSize = this._calculateSize(compressed);
|
|
78
|
+
const compressionRatio = compressedSize / originalSize;
|
|
79
|
+
const duration = Date.now() - startTime;
|
|
80
|
+
|
|
81
|
+
const result = {
|
|
82
|
+
data: compressed,
|
|
83
|
+
metadata: {
|
|
84
|
+
strategy,
|
|
85
|
+
originalSize,
|
|
86
|
+
compressedSize,
|
|
87
|
+
compressionRatio: compressionRatio.toFixed(3),
|
|
88
|
+
spaceSaved: `${((1 - compressionRatio) * 100).toFixed(1)}%`,
|
|
89
|
+
compressionTime: duration,
|
|
90
|
+
timestamp: new Date().toISOString()
|
|
91
|
+
}
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
this.logger.debug('Data compressed', result.metadata);
|
|
95
|
+
return result;
|
|
96
|
+
|
|
97
|
+
} catch (error) {
|
|
98
|
+
this.logger.error('Compression failed', { strategy, error: error.message });
|
|
99
|
+
throw error;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Decompress data
|
|
105
|
+
* @param {Object} compressedData - Compressed data with metadata
|
|
106
|
+
* @returns {any} Original data
|
|
107
|
+
*/
|
|
108
|
+
decompress(compressedData) {
|
|
109
|
+
try {
|
|
110
|
+
if (!compressedData.metadata || !compressedData.metadata.strategy) {
|
|
111
|
+
this.logger.warn('No compression metadata found, assuming no compression');
|
|
112
|
+
return compressedData;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const { strategy } = compressedData.metadata;
|
|
116
|
+
const startTime = Date.now();
|
|
117
|
+
|
|
118
|
+
if (!this.strategies[strategy]) {
|
|
119
|
+
throw new Error(`Unknown compression strategy: ${strategy}`);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const decompressed = this.strategies[strategy].decompress(compressedData.data);
|
|
123
|
+
const duration = Date.now() - startTime;
|
|
124
|
+
|
|
125
|
+
this.logger.debug('Data decompressed', {
|
|
126
|
+
strategy,
|
|
127
|
+
decompressionTime: duration,
|
|
128
|
+
originalSize: compressedData.metadata.originalSize
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
return decompressed;
|
|
132
|
+
|
|
133
|
+
} catch (error) {
|
|
134
|
+
this.logger.error('Decompression failed', { error: error.message });
|
|
135
|
+
throw error;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Compress events for storage
|
|
141
|
+
* @param {Array} events - Events to compress
|
|
142
|
+
* @param {Object} options - Compression options
|
|
143
|
+
* @returns {Object} Compressed events
|
|
144
|
+
*/
|
|
145
|
+
compressEvents(events, options = {}) {
|
|
146
|
+
const { strategy = this.defaultStrategy, chunkSize = 100 } = options;
|
|
147
|
+
|
|
148
|
+
return this.logger.time('compressEvents', () => {
|
|
149
|
+
// Group events into chunks for better compression
|
|
150
|
+
const chunks = [];
|
|
151
|
+
for (let i = 0; i < events.length; i += chunkSize) {
|
|
152
|
+
chunks.push(events.slice(i, i + chunkSize));
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// Compress each chunk
|
|
156
|
+
const compressedChunks = chunks.map((chunk, index) => {
|
|
157
|
+
const compressed = this.compress(chunk, strategy);
|
|
158
|
+
return {
|
|
159
|
+
...compressed,
|
|
160
|
+
metadata: {
|
|
161
|
+
...compressed.metadata,
|
|
162
|
+
chunkIndex: index,
|
|
163
|
+
eventCount: chunk.length
|
|
164
|
+
}
|
|
165
|
+
};
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
// Calculate total compression stats
|
|
169
|
+
const totalOriginal = compressedChunks.reduce((sum, c) => sum + c.metadata.originalSize, 0);
|
|
170
|
+
const totalCompressed = compressedChunks.reduce((sum, c) => sum + c.metadata.compressedSize, 0);
|
|
171
|
+
|
|
172
|
+
const result = {
|
|
173
|
+
chunks: compressedChunks,
|
|
174
|
+
metadata: {
|
|
175
|
+
totalEvents: events.length,
|
|
176
|
+
chunkCount: chunks.length,
|
|
177
|
+
strategy,
|
|
178
|
+
totalOriginalSize: totalOriginal,
|
|
179
|
+
totalCompressedSize: totalCompressed,
|
|
180
|
+
overallCompressionRatio: (totalCompressed / totalOriginal).toFixed(3),
|
|
181
|
+
spaceSaved: `${((1 - totalCompressed / totalOriginal) * 100).toFixed(1)}%`
|
|
182
|
+
}
|
|
183
|
+
};
|
|
184
|
+
|
|
185
|
+
this.logger.info('Events compressed', result.metadata);
|
|
186
|
+
return result;
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Decompress events
|
|
192
|
+
* @param {Object} compressedEvents - Compressed events object
|
|
193
|
+
* @returns {Array} Original events
|
|
194
|
+
*/
|
|
195
|
+
decompressEvents(compressedEvents) {
|
|
196
|
+
return this.logger.time('decompressEvents', () => {
|
|
197
|
+
if (!compressedEvents.chunks || !Array.isArray(compressedEvents.chunks)) {
|
|
198
|
+
throw new Error('Invalid compressed events format');
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
const events = [];
|
|
202
|
+
|
|
203
|
+
// Decompress each chunk
|
|
204
|
+
compressedEvents.chunks.forEach(chunk => {
|
|
205
|
+
const decompressedChunk = this.decompress(chunk);
|
|
206
|
+
events.push(...decompressedChunk);
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
this.logger.info('Events decompressed', {
|
|
210
|
+
totalEvents: events.length,
|
|
211
|
+
chunks: compressedEvents.chunks.length
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
return events;
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* JSON compression - removes whitespace and uses short keys
|
|
220
|
+
* @private
|
|
221
|
+
*/
|
|
222
|
+
_compressJSON(data) {
|
|
223
|
+
// Create key mapping for common event fields
|
|
224
|
+
const keyMap = {
|
|
225
|
+
eventName: 'e',
|
|
226
|
+
blockNumber: 'b',
|
|
227
|
+
transactionHash: 't',
|
|
228
|
+
address: 'a',
|
|
229
|
+
args: 'r',
|
|
230
|
+
topics: 'p',
|
|
231
|
+
data: 'd',
|
|
232
|
+
logIndex: 'l',
|
|
233
|
+
transactionIndex: 'i',
|
|
234
|
+
blockHash: 'h',
|
|
235
|
+
removed: 'm'
|
|
236
|
+
};
|
|
237
|
+
|
|
238
|
+
const compressed = this._replaceKeys(data, keyMap);
|
|
239
|
+
return {
|
|
240
|
+
d: compressed,
|
|
241
|
+
k: keyMap
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* JSON decompression
|
|
247
|
+
* @private
|
|
248
|
+
*/
|
|
249
|
+
_decompressJSON(compressed) {
|
|
250
|
+
if (!compressed.d || !compressed.k) {
|
|
251
|
+
return compressed;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// Reverse the key mapping
|
|
255
|
+
const reverseMap = {};
|
|
256
|
+
Object.entries(compressed.k).forEach(([k, v]) => {
|
|
257
|
+
reverseMap[v] = k;
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
return this._replaceKeys(compressed.d, reverseMap);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* Gzip compression
|
|
265
|
+
* @private
|
|
266
|
+
*/
|
|
267
|
+
_compressGzip(data) {
|
|
268
|
+
const jsonString = JSON.stringify(data);
|
|
269
|
+
const compressed = gzip(jsonString);
|
|
270
|
+
return toBase64(compressed);
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* Gzip decompression
|
|
275
|
+
* @private
|
|
276
|
+
*/
|
|
277
|
+
_decompressGzip(compressed) {
|
|
278
|
+
const bytes = fromBase64(compressed);
|
|
279
|
+
const decompressed = ungzip(bytes);
|
|
280
|
+
const jsonString = new TextDecoder().decode(decompressed);
|
|
281
|
+
return JSON.parse(jsonString);
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
/**
|
|
285
|
+
* Optimized compression - combines JSON and gzip
|
|
286
|
+
* @private
|
|
287
|
+
*/
|
|
288
|
+
_compressOptimized(data) {
|
|
289
|
+
// First apply JSON compression
|
|
290
|
+
const jsonCompressed = this._compressJSON(data);
|
|
291
|
+
// Then apply gzip
|
|
292
|
+
return this._compressGzip(jsonCompressed);
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
/**
|
|
296
|
+
* Optimized decompression
|
|
297
|
+
* @private
|
|
298
|
+
*/
|
|
299
|
+
_decompressOptimized(compressed) {
|
|
300
|
+
// First decompress gzip
|
|
301
|
+
const gzipDecompressed = this._decompressGzip(compressed);
|
|
302
|
+
// Then decompress JSON
|
|
303
|
+
return this._decompressJSON(gzipDecompressed);
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
/**
|
|
307
|
+
* Replace keys in object recursively
|
|
308
|
+
* @private
|
|
309
|
+
*/
|
|
310
|
+
_replaceKeys(obj, keyMap) {
|
|
311
|
+
if (Array.isArray(obj)) {
|
|
312
|
+
return obj.map(item => this._replaceKeys(item, keyMap));
|
|
313
|
+
} else if (obj !== null && typeof obj === 'object') {
|
|
314
|
+
const result = {};
|
|
315
|
+
Object.entries(obj).forEach(([key, value]) => {
|
|
316
|
+
const newKey = keyMap[key] || key;
|
|
317
|
+
result[newKey] = this._replaceKeys(value, keyMap);
|
|
318
|
+
});
|
|
319
|
+
return result;
|
|
320
|
+
}
|
|
321
|
+
return obj;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
/**
|
|
325
|
+
* Calculate size of data in bytes
|
|
326
|
+
* @private
|
|
327
|
+
*/
|
|
328
|
+
_calculateSize(data) {
|
|
329
|
+
const jsonString = JSON.stringify(data);
|
|
330
|
+
if (isNode) {
|
|
331
|
+
return Buffer.byteLength(jsonString, 'utf8');
|
|
332
|
+
}
|
|
333
|
+
return new Blob([jsonString]).size;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
/**
|
|
337
|
+
* Analyze compression efficiency for different strategies
|
|
338
|
+
* @param {any} data - Data to analyze
|
|
339
|
+
* @returns {Object} Analysis results
|
|
340
|
+
*/
|
|
341
|
+
analyzeCompressionStrategies(data) {
|
|
342
|
+
const results = {};
|
|
343
|
+
|
|
344
|
+
Object.keys(this.strategies).forEach(strategy => {
|
|
345
|
+
try {
|
|
346
|
+
const compressed = this.compress(data, strategy);
|
|
347
|
+
results[strategy] = {
|
|
348
|
+
originalSize: compressed.metadata.originalSize,
|
|
349
|
+
compressedSize: compressed.metadata.compressedSize,
|
|
350
|
+
compressionRatio: compressed.metadata.compressionRatio,
|
|
351
|
+
spaceSaved: compressed.metadata.spaceSaved,
|
|
352
|
+
compressionTime: compressed.metadata.compressionTime
|
|
353
|
+
};
|
|
354
|
+
} catch (error) {
|
|
355
|
+
results[strategy] = {
|
|
356
|
+
error: error.message
|
|
357
|
+
};
|
|
358
|
+
}
|
|
359
|
+
});
|
|
360
|
+
|
|
361
|
+
// Find best strategy
|
|
362
|
+
let bestStrategy = 'none';
|
|
363
|
+
let bestRatio = 1;
|
|
364
|
+
|
|
365
|
+
Object.entries(results).forEach(([strategy, result]) => {
|
|
366
|
+
if (!result.error && parseFloat(result.compressionRatio) < bestRatio) {
|
|
367
|
+
bestRatio = parseFloat(result.compressionRatio);
|
|
368
|
+
bestStrategy = strategy;
|
|
369
|
+
}
|
|
370
|
+
});
|
|
371
|
+
|
|
372
|
+
return {
|
|
373
|
+
strategies: results,
|
|
374
|
+
recommendation: bestStrategy,
|
|
375
|
+
bestCompressionRatio: bestRatio
|
|
376
|
+
};
|
|
377
|
+
}
|
|
378
|
+
}
|