@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,4149 @@
|
|
|
1
|
+
import * as b from "ethers";
|
|
2
|
+
import N from "gun";
|
|
3
|
+
import "gun/lib/load.js";
|
|
4
|
+
import "gun/lib/open.js";
|
|
5
|
+
import { gzip as x, ungzip as I } from "pako";
|
|
6
|
+
class p {
|
|
7
|
+
constructor(e = {}) {
|
|
8
|
+
this.level = e.level || "info", this.context = e.context || "Magazine", this.enableConsole = e.enableConsole !== !1, this.enableFile = e.enableFile || !1, this.filePath = e.filePath || "./magazine.log", this._fsModule = null, this._fsAvailable = null, this.levels = {
|
|
9
|
+
error: 0,
|
|
10
|
+
warn: 1,
|
|
11
|
+
info: 2,
|
|
12
|
+
debug: 3,
|
|
13
|
+
trace: 4
|
|
14
|
+
}, this.currentLevelValue = this.levels[this.level.toLowerCase()] || this.levels.info, this.enableFile && this._initFileLogging();
|
|
15
|
+
}
|
|
16
|
+
// Create a formatted log entry
|
|
17
|
+
_formatLog(e, t, r = {}) {
|
|
18
|
+
const s = new Date().toISOString(), n = {
|
|
19
|
+
timestamp: s,
|
|
20
|
+
level: e.toUpperCase(),
|
|
21
|
+
context: this.context,
|
|
22
|
+
message: t,
|
|
23
|
+
...r
|
|
24
|
+
};
|
|
25
|
+
return {
|
|
26
|
+
formatted: `[${s}] ${e.toUpperCase()} [${this.context}] ${t}${Object.keys(r).length > 0 ? " " + JSON.stringify(r, this._getCircularReplacer()) : ""}`,
|
|
27
|
+
structured: n
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
// Get a replacer function to handle circular references in JSON.stringify
|
|
31
|
+
_getCircularReplacer() {
|
|
32
|
+
const e = /* @__PURE__ */ new WeakSet();
|
|
33
|
+
return (t, r) => {
|
|
34
|
+
if (typeof r == "object" && r !== null) {
|
|
35
|
+
if (e.has(r))
|
|
36
|
+
return "[Circular]";
|
|
37
|
+
e.add(r);
|
|
38
|
+
}
|
|
39
|
+
return r;
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
// Check if current level allows logging at specified level
|
|
43
|
+
_shouldLog(e) {
|
|
44
|
+
const t = this.levels[e.toLowerCase()];
|
|
45
|
+
return t !== void 0 && t <= this.currentLevelValue;
|
|
46
|
+
}
|
|
47
|
+
// Write log to console
|
|
48
|
+
_writeToConsole(e, t) {
|
|
49
|
+
if (this.enableConsole)
|
|
50
|
+
switch (e.toLowerCase()) {
|
|
51
|
+
case "error":
|
|
52
|
+
console.error(t);
|
|
53
|
+
break;
|
|
54
|
+
case "warn":
|
|
55
|
+
console.warn(t);
|
|
56
|
+
break;
|
|
57
|
+
case "debug":
|
|
58
|
+
case "trace":
|
|
59
|
+
console.debug(t);
|
|
60
|
+
break;
|
|
61
|
+
default:
|
|
62
|
+
console.log(t);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
// Initialize file system module for file logging
|
|
66
|
+
_initFileLogging() {
|
|
67
|
+
if (this._fsAvailable === null)
|
|
68
|
+
try {
|
|
69
|
+
const e = import("fs"), t = import("path");
|
|
70
|
+
Promise.all([e, t]).then(([r, s]) => {
|
|
71
|
+
this._fsModule = r.default || r, this._pathModule = s.default || s, this._fsAvailable = !0;
|
|
72
|
+
}).catch(() => {
|
|
73
|
+
this._fsAvailable = !1;
|
|
74
|
+
});
|
|
75
|
+
} catch {
|
|
76
|
+
this._fsAvailable = !1;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
// Write log entry as a JSON line to the configured file
|
|
80
|
+
_writeToFile(e) {
|
|
81
|
+
if (this.enableFile && this._fsAvailable !== !1 && this._fsModule)
|
|
82
|
+
try {
|
|
83
|
+
const t = JSON.stringify(e, this._getCircularReplacer()) + `
|
|
84
|
+
`;
|
|
85
|
+
this._fsModule.appendFileSync(this.filePath, t, "utf8");
|
|
86
|
+
} catch {
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
// Rotate log file if it exceeds maxSizeBytes. Keeps 1 rotated backup.
|
|
90
|
+
rotateLogs(e = 10 * 1024 * 1024) {
|
|
91
|
+
if (!(!this._fsModule || this._fsAvailable === !1))
|
|
92
|
+
try {
|
|
93
|
+
if (!this._fsModule.existsSync(this.filePath) || this._fsModule.statSync(this.filePath).size < e)
|
|
94
|
+
return;
|
|
95
|
+
const r = this.filePath + ".1";
|
|
96
|
+
this._fsModule.existsSync(r) && this._fsModule.unlinkSync(r), this._fsModule.renameSync(this.filePath, r), this._fsModule.writeFileSync(this.filePath, "", "utf8");
|
|
97
|
+
} catch {
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
// Core logging method
|
|
101
|
+
_log(e, t, r = {}) {
|
|
102
|
+
if (!this._shouldLog(e))
|
|
103
|
+
return;
|
|
104
|
+
const { formatted: s, structured: n } = this._formatLog(e, t, r);
|
|
105
|
+
return this._writeToConsole(e, s), this._writeToFile(n), n;
|
|
106
|
+
}
|
|
107
|
+
// Logging level methods
|
|
108
|
+
error(e, t = {}) {
|
|
109
|
+
return this._log("error", e, t);
|
|
110
|
+
}
|
|
111
|
+
warn(e, t = {}) {
|
|
112
|
+
return this._log("warn", e, t);
|
|
113
|
+
}
|
|
114
|
+
info(e, t = {}) {
|
|
115
|
+
return this._log("info", e, t);
|
|
116
|
+
}
|
|
117
|
+
debug(e, t = {}) {
|
|
118
|
+
return this._log("debug", e, t);
|
|
119
|
+
}
|
|
120
|
+
trace(e, t = {}) {
|
|
121
|
+
return this._log("trace", e, t);
|
|
122
|
+
}
|
|
123
|
+
// Performance logging - measure function execution time
|
|
124
|
+
async time(e, t, r = {}) {
|
|
125
|
+
const s = Date.now(), n = `Starting operation: ${e}`;
|
|
126
|
+
this.debug(n, r);
|
|
127
|
+
try {
|
|
128
|
+
const i = await t(), a = Date.now() - s;
|
|
129
|
+
return this.info(`Completed operation: ${e}`, {
|
|
130
|
+
...r,
|
|
131
|
+
duration: `${a}ms`,
|
|
132
|
+
success: !0
|
|
133
|
+
}), i;
|
|
134
|
+
} catch (i) {
|
|
135
|
+
const a = Date.now() - s;
|
|
136
|
+
throw this.error(`Failed operation: ${e}`, {
|
|
137
|
+
...r,
|
|
138
|
+
duration: `${a}ms`,
|
|
139
|
+
success: !1,
|
|
140
|
+
error: (i == null ? void 0 : i.message) || (i == null ? void 0 : i.toString()) || "Unknown error",
|
|
141
|
+
stack: i == null ? void 0 : i.stack
|
|
142
|
+
}), i;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
// Create a child logger with additional context
|
|
146
|
+
child(e = {}) {
|
|
147
|
+
return new p({
|
|
148
|
+
level: this.level,
|
|
149
|
+
context: this.context,
|
|
150
|
+
enableConsole: this.enableConsole,
|
|
151
|
+
enableFile: this.enableFile,
|
|
152
|
+
filePath: this.filePath,
|
|
153
|
+
...e
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
// Change log level dynamically
|
|
157
|
+
setLevel(e) {
|
|
158
|
+
this.levels[e.toLowerCase()] !== void 0 ? (this.level = e.toLowerCase(), this.currentLevelValue = this.levels[this.level], this.info(`Log level changed to: ${e.toUpperCase()}`)) : this.warn(`Invalid log level: ${e}. Valid levels: ${Object.keys(this.levels).join(", ")}`);
|
|
159
|
+
}
|
|
160
|
+
// Get current configuration
|
|
161
|
+
getConfig() {
|
|
162
|
+
return {
|
|
163
|
+
level: this.level,
|
|
164
|
+
context: this.context,
|
|
165
|
+
enableConsole: this.enableConsole,
|
|
166
|
+
enableFile: this.enableFile,
|
|
167
|
+
filePath: this.filePath
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
// Log system information
|
|
171
|
+
logSystemInfo(e = {}) {
|
|
172
|
+
this.info("System Information", {
|
|
173
|
+
nodeVersion: process.version,
|
|
174
|
+
platform: process.platform,
|
|
175
|
+
arch: process.arch,
|
|
176
|
+
memoryUsage: process.memoryUsage(),
|
|
177
|
+
uptime: process.uptime(),
|
|
178
|
+
...e
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
// Log network request/response
|
|
182
|
+
logNetworkActivity(e, t = {}) {
|
|
183
|
+
this.debug(`Network ${e}`, {
|
|
184
|
+
type: e,
|
|
185
|
+
timestamp: Date.now(),
|
|
186
|
+
...t
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
// Log blockchain interaction
|
|
190
|
+
logBlockchainActivity(e, t = {}) {
|
|
191
|
+
this.info(`Blockchain: ${e}`, {
|
|
192
|
+
operation: e,
|
|
193
|
+
timestamp: Date.now(),
|
|
194
|
+
...t
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
// Log Gun.js database activity
|
|
198
|
+
logDatabaseActivity(e, t = {}) {
|
|
199
|
+
this.debug(`Database: ${e}`, {
|
|
200
|
+
operation: e,
|
|
201
|
+
timestamp: Date.now(),
|
|
202
|
+
...t
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
new p({
|
|
207
|
+
level: "info",
|
|
208
|
+
context: "Magazine"
|
|
209
|
+
});
|
|
210
|
+
class S {
|
|
211
|
+
constructor() {
|
|
212
|
+
this.schema = {
|
|
213
|
+
// Required fields
|
|
214
|
+
required: ["name", "address", "abi", "provider"],
|
|
215
|
+
// Field definitions
|
|
216
|
+
fields: {
|
|
217
|
+
name: {
|
|
218
|
+
type: "string",
|
|
219
|
+
description: "Name of the magazine",
|
|
220
|
+
minLength: 1,
|
|
221
|
+
maxLength: 100,
|
|
222
|
+
pattern: /^[a-zA-Z0-9\s\-_]+$/,
|
|
223
|
+
example: "My Event Magazine"
|
|
224
|
+
},
|
|
225
|
+
address: {
|
|
226
|
+
type: "string",
|
|
227
|
+
description: "Contract address",
|
|
228
|
+
pattern: /^0x[a-fA-F0-9]{40}$/,
|
|
229
|
+
example: "0x1234567890123456789012345678901234567890"
|
|
230
|
+
},
|
|
231
|
+
abi: {
|
|
232
|
+
type: "array",
|
|
233
|
+
description: "Contract ABI",
|
|
234
|
+
minItems: 1,
|
|
235
|
+
items: {
|
|
236
|
+
type: "object",
|
|
237
|
+
required: ["type"],
|
|
238
|
+
properties: {
|
|
239
|
+
type: {
|
|
240
|
+
type: "string",
|
|
241
|
+
enum: ["function", "event", "constructor", "fallback", "receive"]
|
|
242
|
+
},
|
|
243
|
+
name: { type: "string" },
|
|
244
|
+
inputs: { type: "array" },
|
|
245
|
+
outputs: { type: "array" },
|
|
246
|
+
anonymous: { type: "boolean" }
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
},
|
|
250
|
+
provider: {
|
|
251
|
+
type: ["string", "object"],
|
|
252
|
+
description: "Ethereum provider URL or object",
|
|
253
|
+
validate: (e) => typeof e == "string" ? e.startsWith("http://") || e.startsWith("https://") || e.startsWith("ws://") || e.startsWith("wss://") : typeof e == "object" && e !== null,
|
|
254
|
+
example: "http://localhost:8545"
|
|
255
|
+
},
|
|
256
|
+
networkId: {
|
|
257
|
+
type: "number",
|
|
258
|
+
description: "Network ID",
|
|
259
|
+
optional: !0,
|
|
260
|
+
min: 1,
|
|
261
|
+
example: 1
|
|
262
|
+
},
|
|
263
|
+
gun: {
|
|
264
|
+
type: "object",
|
|
265
|
+
description: "Gun.js configuration",
|
|
266
|
+
optional: !0,
|
|
267
|
+
properties: {
|
|
268
|
+
peers: {
|
|
269
|
+
type: "array",
|
|
270
|
+
items: { type: "string" },
|
|
271
|
+
example: ["http://localhost:8765/gun"]
|
|
272
|
+
},
|
|
273
|
+
file: {
|
|
274
|
+
type: "string",
|
|
275
|
+
example: "radata"
|
|
276
|
+
},
|
|
277
|
+
localStorage: {
|
|
278
|
+
type: "boolean",
|
|
279
|
+
default: !0
|
|
280
|
+
},
|
|
281
|
+
radisk: {
|
|
282
|
+
type: "boolean",
|
|
283
|
+
default: !0
|
|
284
|
+
},
|
|
285
|
+
multicast: {
|
|
286
|
+
type: ["boolean", "object"],
|
|
287
|
+
default: !1
|
|
288
|
+
},
|
|
289
|
+
compression: {
|
|
290
|
+
type: "object",
|
|
291
|
+
properties: {
|
|
292
|
+
enabled: {
|
|
293
|
+
type: "boolean",
|
|
294
|
+
default: !0
|
|
295
|
+
},
|
|
296
|
+
strategy: {
|
|
297
|
+
type: "string",
|
|
298
|
+
enum: ["none", "json", "gzip", "optimized"],
|
|
299
|
+
default: "optimized"
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
},
|
|
305
|
+
logLevel: {
|
|
306
|
+
type: "string",
|
|
307
|
+
description: "Logging level",
|
|
308
|
+
optional: !0,
|
|
309
|
+
enum: ["error", "warn", "info", "debug", "trace"],
|
|
310
|
+
default: "info"
|
|
311
|
+
},
|
|
312
|
+
maxRetries: {
|
|
313
|
+
type: "number",
|
|
314
|
+
description: "Maximum retry attempts",
|
|
315
|
+
optional: !0,
|
|
316
|
+
min: 0,
|
|
317
|
+
max: 10,
|
|
318
|
+
default: 3
|
|
319
|
+
},
|
|
320
|
+
retryDelay: {
|
|
321
|
+
type: "number",
|
|
322
|
+
description: "Delay between retries in milliseconds",
|
|
323
|
+
optional: !0,
|
|
324
|
+
min: 100,
|
|
325
|
+
max: 6e4,
|
|
326
|
+
default: 1e3
|
|
327
|
+
},
|
|
328
|
+
timeout: {
|
|
329
|
+
type: "number",
|
|
330
|
+
description: "Operation timeout in milliseconds",
|
|
331
|
+
optional: !0,
|
|
332
|
+
min: 1e3,
|
|
333
|
+
max: 3e5,
|
|
334
|
+
default: 3e4
|
|
335
|
+
},
|
|
336
|
+
batchSize: {
|
|
337
|
+
type: "number",
|
|
338
|
+
description: "Event sync batch size",
|
|
339
|
+
optional: !0,
|
|
340
|
+
min: 1,
|
|
341
|
+
max: 1e4,
|
|
342
|
+
default: 1e3
|
|
343
|
+
},
|
|
344
|
+
startBlockNumber: {
|
|
345
|
+
type: "number",
|
|
346
|
+
description: "Starting block number for sync",
|
|
347
|
+
optional: !0,
|
|
348
|
+
min: 0,
|
|
349
|
+
default: 1
|
|
350
|
+
},
|
|
351
|
+
autoSync: {
|
|
352
|
+
type: "boolean",
|
|
353
|
+
description: "Enable automatic syncing",
|
|
354
|
+
optional: !0,
|
|
355
|
+
default: !1
|
|
356
|
+
},
|
|
357
|
+
syncInterval: {
|
|
358
|
+
type: "number",
|
|
359
|
+
description: "Auto-sync interval in milliseconds",
|
|
360
|
+
optional: !0,
|
|
361
|
+
min: 1e4,
|
|
362
|
+
max: 36e5,
|
|
363
|
+
default: 6e4,
|
|
364
|
+
dependsOn: "autoSync"
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
}, this.errorMessages = {
|
|
368
|
+
required: (e) => `${e} is required`,
|
|
369
|
+
type: (e, t, r) => `${e} must be of type ${t}, got ${r}`,
|
|
370
|
+
minLength: (e, t) => `${e} must be at least ${t} characters long`,
|
|
371
|
+
maxLength: (e, t) => `${e} must be at most ${t} characters long`,
|
|
372
|
+
pattern: (e) => `${e} has invalid format`,
|
|
373
|
+
min: (e, t) => `${e} must be at least ${t}`,
|
|
374
|
+
max: (e, t) => `${e} must be at most ${t}`,
|
|
375
|
+
enum: (e, t) => `${e} must be one of: ${t.join(", ")}`,
|
|
376
|
+
minItems: (e, t) => `${e} must have at least ${t} items`,
|
|
377
|
+
custom: (e, t) => `${e}: ${t}`
|
|
378
|
+
};
|
|
379
|
+
}
|
|
380
|
+
/**
|
|
381
|
+
* Validate configuration against schema
|
|
382
|
+
* @param {Object} config - Configuration to validate
|
|
383
|
+
* @returns {Object} Validation result
|
|
384
|
+
*/
|
|
385
|
+
validate(e) {
|
|
386
|
+
const t = [], r = [], s = {};
|
|
387
|
+
for (const n of this.schema.required)
|
|
388
|
+
(!e.hasOwnProperty(n) || e[n] === void 0 || e[n] === null) && t.push({
|
|
389
|
+
field: n,
|
|
390
|
+
type: "required",
|
|
391
|
+
message: this.errorMessages.required(n)
|
|
392
|
+
});
|
|
393
|
+
for (const [n, i] of Object.entries(this.schema.fields))
|
|
394
|
+
if (e.hasOwnProperty(n)) {
|
|
395
|
+
const a = this._validateField(n, e[n], i);
|
|
396
|
+
a.errors.length > 0 ? t.push(...a.errors) : s[n] = a.value, a.warnings.length > 0 && r.push(...a.warnings);
|
|
397
|
+
} else
|
|
398
|
+
!i.optional && !this.schema.required.includes(n) && i.hasOwnProperty("default") && (s[n] = i.default);
|
|
399
|
+
for (const n of Object.keys(e))
|
|
400
|
+
this.schema.fields.hasOwnProperty(n) || (r.push({
|
|
401
|
+
field: n,
|
|
402
|
+
type: "unknown",
|
|
403
|
+
message: `Unknown configuration field: ${n}`
|
|
404
|
+
}), s[n] = e[n]);
|
|
405
|
+
for (const [n, i] of Object.entries(this.schema.fields))
|
|
406
|
+
i.dependsOn && s[n] !== void 0 && (s[i.dependsOn] || r.push({
|
|
407
|
+
field: n,
|
|
408
|
+
type: "dependency",
|
|
409
|
+
message: `${n} depends on ${i.dependsOn} being set`
|
|
410
|
+
}));
|
|
411
|
+
return {
|
|
412
|
+
valid: t.length === 0,
|
|
413
|
+
errors: t,
|
|
414
|
+
warnings: r,
|
|
415
|
+
validated: s
|
|
416
|
+
};
|
|
417
|
+
}
|
|
418
|
+
/**
|
|
419
|
+
* Validate a single field
|
|
420
|
+
* @private
|
|
421
|
+
*/
|
|
422
|
+
_validateField(e, t, r) {
|
|
423
|
+
const s = [], n = [];
|
|
424
|
+
let i = t;
|
|
425
|
+
const a = Array.isArray(t) ? "array" : typeof t, l = Array.isArray(r.type) ? r.type : [r.type];
|
|
426
|
+
if (!l.includes(a))
|
|
427
|
+
return s.push({
|
|
428
|
+
field: e,
|
|
429
|
+
type: "type",
|
|
430
|
+
message: this.errorMessages.type(e, l.join(" or "), a)
|
|
431
|
+
}), { errors: s, warnings: n, value: t };
|
|
432
|
+
if (r.transform && typeof r.transform == "function" && (i = r.transform(i)), a === "string" && (r.minLength && i.length < r.minLength && s.push({
|
|
433
|
+
field: e,
|
|
434
|
+
type: "minLength",
|
|
435
|
+
message: this.errorMessages.minLength(e, r.minLength)
|
|
436
|
+
}), r.maxLength && i.length > r.maxLength && s.push({
|
|
437
|
+
field: e,
|
|
438
|
+
type: "maxLength",
|
|
439
|
+
message: this.errorMessages.maxLength(e, r.maxLength)
|
|
440
|
+
}), r.pattern && !r.pattern.test(i) && s.push({
|
|
441
|
+
field: e,
|
|
442
|
+
type: "pattern",
|
|
443
|
+
message: this.errorMessages.pattern(e)
|
|
444
|
+
}), r.enum && !r.enum.includes(i) && s.push({
|
|
445
|
+
field: e,
|
|
446
|
+
type: "enum",
|
|
447
|
+
message: this.errorMessages.enum(e, r.enum)
|
|
448
|
+
})), a === "number" && (r.min !== void 0 && i < r.min && s.push({
|
|
449
|
+
field: e,
|
|
450
|
+
type: "min",
|
|
451
|
+
message: this.errorMessages.min(e, r.min)
|
|
452
|
+
}), r.max !== void 0 && i > r.max && s.push({
|
|
453
|
+
field: e,
|
|
454
|
+
type: "max",
|
|
455
|
+
message: this.errorMessages.max(e, r.max)
|
|
456
|
+
})), a === "array" && (r.minItems && i.length < r.minItems && s.push({
|
|
457
|
+
field: e,
|
|
458
|
+
type: "minItems",
|
|
459
|
+
message: this.errorMessages.minItems(e, r.minItems)
|
|
460
|
+
}), r.items && i.forEach((o, c) => {
|
|
461
|
+
const h = this._validateField(
|
|
462
|
+
`${e}[${c}]`,
|
|
463
|
+
o,
|
|
464
|
+
r.items
|
|
465
|
+
);
|
|
466
|
+
s.push(...h.errors), n.push(...h.warnings);
|
|
467
|
+
})), a === "object" && r.properties) {
|
|
468
|
+
for (const [o, c] of Object.entries(r.properties))
|
|
469
|
+
if (i.hasOwnProperty(o)) {
|
|
470
|
+
const h = this._validateField(
|
|
471
|
+
`${e}.${o}`,
|
|
472
|
+
i[o],
|
|
473
|
+
c
|
|
474
|
+
);
|
|
475
|
+
s.push(...h.errors), n.push(...h.warnings);
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
return r.validate && typeof r.validate == "function" && (r.validate(i) || s.push({
|
|
479
|
+
field: e,
|
|
480
|
+
type: "custom",
|
|
481
|
+
message: this.errorMessages.custom(e, "Custom validation failed")
|
|
482
|
+
})), { errors: s, warnings: n, value: i };
|
|
483
|
+
}
|
|
484
|
+
/**
|
|
485
|
+
* Get schema documentation
|
|
486
|
+
* @returns {Object} Schema documentation
|
|
487
|
+
*/
|
|
488
|
+
getDocumentation() {
|
|
489
|
+
const e = {
|
|
490
|
+
description: "Magazine configuration schema",
|
|
491
|
+
required: this.schema.required,
|
|
492
|
+
fields: {}
|
|
493
|
+
};
|
|
494
|
+
for (const [t, r] of Object.entries(this.schema.fields))
|
|
495
|
+
e.fields[t] = {
|
|
496
|
+
type: r.type,
|
|
497
|
+
description: r.description,
|
|
498
|
+
required: this.schema.required.includes(t),
|
|
499
|
+
optional: r.optional,
|
|
500
|
+
default: r.default,
|
|
501
|
+
example: r.example
|
|
502
|
+
}, r.enum && (e.fields[t].enum = r.enum), (r.min !== void 0 || r.max !== void 0) && (e.fields[t].range = {
|
|
503
|
+
min: r.min,
|
|
504
|
+
max: r.max
|
|
505
|
+
}), r.pattern && (e.fields[t].pattern = r.pattern.toString()), r.dependsOn && (e.fields[t].dependsOn = r.dependsOn);
|
|
506
|
+
return e;
|
|
507
|
+
}
|
|
508
|
+
/**
|
|
509
|
+
* Generate example configuration
|
|
510
|
+
* @param {Object} options - Generation options
|
|
511
|
+
* @returns {Object} Example configuration
|
|
512
|
+
*/
|
|
513
|
+
generateExample(e = {}) {
|
|
514
|
+
const { includeOptional: t = !0, useDefaults: r = !0 } = e, s = {};
|
|
515
|
+
for (const [n, i] of Object.entries(this.schema.fields))
|
|
516
|
+
(this.schema.required.includes(n) || t && i.optional) && (i.example !== void 0 ? s[n] = i.example : r && i.default !== void 0 ? s[n] = i.default : s[n] = this._generatePlaceholder(i));
|
|
517
|
+
return s;
|
|
518
|
+
}
|
|
519
|
+
/**
|
|
520
|
+
* Generate placeholder value based on type
|
|
521
|
+
* @private
|
|
522
|
+
*/
|
|
523
|
+
_generatePlaceholder(e) {
|
|
524
|
+
switch (Array.isArray(e.type) ? e.type[0] : e.type) {
|
|
525
|
+
case "string":
|
|
526
|
+
return e.enum ? e.enum[0] : "example-value";
|
|
527
|
+
case "number":
|
|
528
|
+
return e.min !== void 0 ? e.min : 0;
|
|
529
|
+
case "boolean":
|
|
530
|
+
return !1;
|
|
531
|
+
case "array":
|
|
532
|
+
return [];
|
|
533
|
+
case "object":
|
|
534
|
+
return {};
|
|
535
|
+
default:
|
|
536
|
+
return null;
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
class v {
|
|
541
|
+
/**
|
|
542
|
+
* Create a ConfigManager instance
|
|
543
|
+
* @param {Object} config - Configuration object to validate and manage
|
|
544
|
+
* @throws {Error} If configuration validation fails
|
|
545
|
+
*/
|
|
546
|
+
constructor(e) {
|
|
547
|
+
this.schema = new S(), this.logger = new p({
|
|
548
|
+
context: "ConfigManager",
|
|
549
|
+
level: (e == null ? void 0 : e.logLevel) || "info"
|
|
550
|
+
}), this.logger.debug("Initializing ConfigManager with schema validation", {
|
|
551
|
+
config: this._sanitizeConfig(e)
|
|
552
|
+
});
|
|
553
|
+
const t = this.validateConfig(e);
|
|
554
|
+
if (!t.valid) {
|
|
555
|
+
const r = this._formatValidationErrors(t.errors);
|
|
556
|
+
throw this.logger.error("Configuration validation failed", {
|
|
557
|
+
errors: t.errors,
|
|
558
|
+
warnings: t.warnings
|
|
559
|
+
}), new Error(`Configuration validation failed:
|
|
560
|
+
${r}`);
|
|
561
|
+
}
|
|
562
|
+
t.warnings.length > 0 && this.logger.warn("Configuration validation warnings", {
|
|
563
|
+
warnings: t.warnings
|
|
564
|
+
}), this.config = t.validated, this.logger.info("ConfigManager initialized successfully", {
|
|
565
|
+
name: this.config.name,
|
|
566
|
+
address: this.config.address,
|
|
567
|
+
networkId: this.config.networkId,
|
|
568
|
+
maxRetries: this.config.maxRetries,
|
|
569
|
+
retryDelay: this.config.retryDelay,
|
|
570
|
+
timeout: this.config.timeout,
|
|
571
|
+
warningsCount: t.warnings.length
|
|
572
|
+
});
|
|
573
|
+
}
|
|
574
|
+
/**
|
|
575
|
+
* Sanitize config for logging (remove sensitive data)
|
|
576
|
+
* @private
|
|
577
|
+
* @param {Object} config - Configuration object to sanitize
|
|
578
|
+
* @returns {Object} Sanitized configuration object
|
|
579
|
+
*/
|
|
580
|
+
_sanitizeConfig(e) {
|
|
581
|
+
if (!e)
|
|
582
|
+
return e;
|
|
583
|
+
const t = { ...e };
|
|
584
|
+
return t.gun && (t.gun = { ...t.gun }, delete t.gun.peers), t;
|
|
585
|
+
}
|
|
586
|
+
/**
|
|
587
|
+
* Validate configuration using ConfigSchema
|
|
588
|
+
* @param {Object} config - Configuration object to validate
|
|
589
|
+
* @returns {Object} Validation result with validated config
|
|
590
|
+
*/
|
|
591
|
+
validateConfig(e) {
|
|
592
|
+
return !e || typeof e != "object" ? {
|
|
593
|
+
valid: !1,
|
|
594
|
+
errors: [{
|
|
595
|
+
field: "config",
|
|
596
|
+
type: "required",
|
|
597
|
+
message: "Configuration must be a non-null object"
|
|
598
|
+
}],
|
|
599
|
+
warnings: [],
|
|
600
|
+
validated: {}
|
|
601
|
+
} : this.schema.validate(e);
|
|
602
|
+
}
|
|
603
|
+
/**
|
|
604
|
+
* Format validation errors into a readable string
|
|
605
|
+
* @private
|
|
606
|
+
* @param {Array} errors - Array of validation errors
|
|
607
|
+
* @returns {string} Formatted error message
|
|
608
|
+
*/
|
|
609
|
+
_formatValidationErrors(e) {
|
|
610
|
+
return e.map((t) => ` - ${t.field}: ${t.message}`).join(`
|
|
611
|
+
`);
|
|
612
|
+
}
|
|
613
|
+
/**
|
|
614
|
+
* Get schema documentation
|
|
615
|
+
* @returns {Object} Schema documentation object
|
|
616
|
+
*/
|
|
617
|
+
getSchemaDocumentation() {
|
|
618
|
+
return this.schema.getDocumentation();
|
|
619
|
+
}
|
|
620
|
+
/**
|
|
621
|
+
* Generate example configuration
|
|
622
|
+
* @param {Object} options - Generation options
|
|
623
|
+
* @returns {Object} Example configuration object
|
|
624
|
+
*/
|
|
625
|
+
generateExampleConfig(e = {}) {
|
|
626
|
+
return this.schema.generateExample(e);
|
|
627
|
+
}
|
|
628
|
+
/**
|
|
629
|
+
* Validate a configuration object without creating an instance
|
|
630
|
+
* @static
|
|
631
|
+
* @param {Object} config - Configuration to validate
|
|
632
|
+
* @returns {Object} Validation result
|
|
633
|
+
*/
|
|
634
|
+
static validateConfiguration(e) {
|
|
635
|
+
return new S().validate(e);
|
|
636
|
+
}
|
|
637
|
+
/**
|
|
638
|
+
* Validate block number parameters
|
|
639
|
+
* @param {number} blockNumber - Block number to validate
|
|
640
|
+
* @param {string} paramName - Parameter name for error messages
|
|
641
|
+
* @throws {Error} If block number is invalid
|
|
642
|
+
*/
|
|
643
|
+
validateBlockNumber(e, t = "blockNumber") {
|
|
644
|
+
if (!Number.isInteger(e) || e < 0)
|
|
645
|
+
throw new Error(`${t} must be a non-negative integer`);
|
|
646
|
+
}
|
|
647
|
+
/**
|
|
648
|
+
* Validate options object for API methods
|
|
649
|
+
* @param {Object} options - Options object to validate
|
|
650
|
+
* @param {Array} allowedKeys - Array of allowed option keys
|
|
651
|
+
* @returns {Object} Validated options object
|
|
652
|
+
* @throws {Error} If options are invalid
|
|
653
|
+
*/
|
|
654
|
+
validateOptions(e, t = []) {
|
|
655
|
+
if (e !== void 0 && (typeof e != "object" || e === null))
|
|
656
|
+
throw new Error("Options must be an object");
|
|
657
|
+
if (e && t.length > 0) {
|
|
658
|
+
const r = Object.keys(e).filter((s) => !t.includes(s));
|
|
659
|
+
if (r.length > 0)
|
|
660
|
+
throw new Error(`Invalid option keys: ${r.join(", ")}. Allowed: ${t.join(", ")}`);
|
|
661
|
+
}
|
|
662
|
+
return e || {};
|
|
663
|
+
}
|
|
664
|
+
/**
|
|
665
|
+
* Get configuration value by key
|
|
666
|
+
* @param {string} key - Configuration key
|
|
667
|
+
* @returns {*} Configuration value
|
|
668
|
+
*/
|
|
669
|
+
get(e) {
|
|
670
|
+
return this.config[e];
|
|
671
|
+
}
|
|
672
|
+
/**
|
|
673
|
+
* Get all configuration
|
|
674
|
+
* @returns {Object} Complete configuration object
|
|
675
|
+
*/
|
|
676
|
+
getAll() {
|
|
677
|
+
return { ...this.config };
|
|
678
|
+
}
|
|
679
|
+
/**
|
|
680
|
+
* Get retry options for network operations
|
|
681
|
+
* @returns {Object} Retry configuration object
|
|
682
|
+
*/
|
|
683
|
+
getRetryOptions() {
|
|
684
|
+
return {
|
|
685
|
+
maxRetries: this.config.maxRetries,
|
|
686
|
+
retryDelay: this.config.retryDelay,
|
|
687
|
+
timeout: this.config.timeout
|
|
688
|
+
};
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
class _ {
|
|
692
|
+
constructor(e = 500) {
|
|
693
|
+
this.maxSize = e, this.cache = /* @__PURE__ */ new Map();
|
|
694
|
+
}
|
|
695
|
+
get(e) {
|
|
696
|
+
if (!this.cache.has(e))
|
|
697
|
+
return;
|
|
698
|
+
const t = this.cache.get(e);
|
|
699
|
+
return this.cache.delete(e), this.cache.set(e, t), t;
|
|
700
|
+
}
|
|
701
|
+
set(e, t) {
|
|
702
|
+
this.cache.has(e) ? this.cache.delete(e) : this.cache.size >= this.maxSize && this.cache.delete(this.cache.keys().next().value), this.cache.set(e, t);
|
|
703
|
+
}
|
|
704
|
+
clear() {
|
|
705
|
+
this.cache.clear();
|
|
706
|
+
}
|
|
707
|
+
get size() {
|
|
708
|
+
return this.cache.size;
|
|
709
|
+
}
|
|
710
|
+
}
|
|
711
|
+
class $ {
|
|
712
|
+
constructor(e = 25) {
|
|
713
|
+
this.maxPerSecond = e, this.tokens = e, this.lastRefill = Date.now(), this.queue = [];
|
|
714
|
+
}
|
|
715
|
+
async acquire() {
|
|
716
|
+
if (this._refill(), this.tokens > 0) {
|
|
717
|
+
this.tokens--;
|
|
718
|
+
return;
|
|
719
|
+
}
|
|
720
|
+
return new Promise((e) => this.queue.push(e));
|
|
721
|
+
}
|
|
722
|
+
_refill() {
|
|
723
|
+
const e = Date.now(), t = (e - this.lastRefill) / 1e3;
|
|
724
|
+
for (this.tokens = Math.min(this.maxPerSecond, this.tokens + t * this.maxPerSecond), this.lastRefill = e; this.queue.length > 0 && this.tokens > 0; )
|
|
725
|
+
this.tokens--, this.queue.shift()();
|
|
726
|
+
}
|
|
727
|
+
}
|
|
728
|
+
class z {
|
|
729
|
+
constructor(e, t) {
|
|
730
|
+
this.config = e, this.retryOperation = t, this.logger = new p({
|
|
731
|
+
context: "NetworkManager",
|
|
732
|
+
level: (e == null ? void 0 : e.logLevel) || "info"
|
|
733
|
+
}), this.logger.debug("Initializing NetworkManager", {
|
|
734
|
+
provider: e.provider,
|
|
735
|
+
networkId: e.networkId
|
|
736
|
+
}), this.providers = [], this.currentProviderIndex = 0, this._initializeProviders(e.provider), this.provider = this.providers[0], this.interface = new b.Interface(e.abi);
|
|
737
|
+
const r = e.cacheSize || 500;
|
|
738
|
+
this.blockCache = new _(r), this.timestampCache = new _(r), this.logger.debug("LRU caches initialized", { cacheSize: r });
|
|
739
|
+
const s = e.rateLimit || 25;
|
|
740
|
+
this.rateLimiter = new $(s), this.logger.debug("Rate limiter initialized", { maxPerSecond: s }), this.events = {}, this.eventsByTopic = {}, this._initializeEvents(), this.logger.info("NetworkManager initialized successfully", {
|
|
741
|
+
eventCount: Object.keys(this.eventsByTopic).length,
|
|
742
|
+
contractAddress: e.address,
|
|
743
|
+
providerCount: this.providers.length
|
|
744
|
+
});
|
|
745
|
+
}
|
|
746
|
+
/**
|
|
747
|
+
* Initialize providers from config. Accepts a single provider (string or
|
|
748
|
+
* ethers provider object) or an array of providers.
|
|
749
|
+
*/
|
|
750
|
+
_initializeProviders(e) {
|
|
751
|
+
const t = Array.isArray(e) ? e : [e];
|
|
752
|
+
for (const r of t)
|
|
753
|
+
if (typeof r == "string") {
|
|
754
|
+
const s = new b.JsonRpcProvider(r);
|
|
755
|
+
this.providers.push(s), this.logger.debug("Created JsonRpcProvider from URL", { url: r });
|
|
756
|
+
} else if (r && typeof r.getNetwork == "function")
|
|
757
|
+
this.providers.push(r), this.logger.debug("Using provided ethers provider object", {
|
|
758
|
+
providerType: r.constructor.name
|
|
759
|
+
});
|
|
760
|
+
else
|
|
761
|
+
throw new Error("Each provider must be either a string URL or an ethers provider object");
|
|
762
|
+
if (this.providers.length === 0)
|
|
763
|
+
throw new Error("At least one provider must be specified");
|
|
764
|
+
this.logger.debug("Providers initialized", { count: this.providers.length });
|
|
765
|
+
}
|
|
766
|
+
/**
|
|
767
|
+
* Switch to the next provider in the list on failure.
|
|
768
|
+
* Returns true if a new provider was activated, false if all have been tried.
|
|
769
|
+
*/
|
|
770
|
+
_switchProvider(e) {
|
|
771
|
+
const t = this.currentProviderIndex;
|
|
772
|
+
return this.currentProviderIndex = (this.currentProviderIndex + 1) % this.providers.length, this.currentProviderIndex === t && this.providers.length === 1 ? (this.logger.error("Single provider failed, no failover available", {
|
|
773
|
+
error: e.message
|
|
774
|
+
}), !1) : (this.provider = this.providers[this.currentProviderIndex], this.logger.warn("Switching provider due to failure", {
|
|
775
|
+
error: e.message,
|
|
776
|
+
previousIndex: t,
|
|
777
|
+
newIndex: this.currentProviderIndex,
|
|
778
|
+
totalProviders: this.providers.length
|
|
779
|
+
}), !0);
|
|
780
|
+
}
|
|
781
|
+
/**
|
|
782
|
+
* Execute a function through the rate limiter.
|
|
783
|
+
* Acquires a token before executing the call.
|
|
784
|
+
*/
|
|
785
|
+
async _throttledCall(e) {
|
|
786
|
+
return await this.rateLimiter.acquire(), e();
|
|
787
|
+
}
|
|
788
|
+
/**
|
|
789
|
+
* Execute a provider call with failover support.
|
|
790
|
+
* Tries each provider in sequence before giving up.
|
|
791
|
+
*/
|
|
792
|
+
async _callWithFailover(e, t) {
|
|
793
|
+
const r = this.currentProviderIndex;
|
|
794
|
+
let s, n = 0;
|
|
795
|
+
do
|
|
796
|
+
try {
|
|
797
|
+
return await this._throttledCall(() => e(this.provider));
|
|
798
|
+
} catch (i) {
|
|
799
|
+
if (s = i, n++, this.logger.warn("Provider call failed", {
|
|
800
|
+
description: t,
|
|
801
|
+
providerIndex: this.currentProviderIndex,
|
|
802
|
+
attempt: n,
|
|
803
|
+
error: i.message
|
|
804
|
+
}), this.providers.length > 1) {
|
|
805
|
+
if (this._switchProvider(i), this.currentProviderIndex === r)
|
|
806
|
+
break;
|
|
807
|
+
} else
|
|
808
|
+
break;
|
|
809
|
+
}
|
|
810
|
+
while (!0);
|
|
811
|
+
throw s;
|
|
812
|
+
}
|
|
813
|
+
// Initialize event mappings from ABI
|
|
814
|
+
_initializeEvents() {
|
|
815
|
+
let e = 0;
|
|
816
|
+
for (const t of this.config.abi)
|
|
817
|
+
if (t.type === "event") {
|
|
818
|
+
const r = t.name;
|
|
819
|
+
this.events[r] = new b.Interface([t]);
|
|
820
|
+
try {
|
|
821
|
+
const n = this.interface.getEvent(r).topicHash;
|
|
822
|
+
this.eventsByTopic[n] = r, e++, this.logger.debug("Event mapping created", {
|
|
823
|
+
eventName: r,
|
|
824
|
+
topicHash: n
|
|
825
|
+
});
|
|
826
|
+
} catch (s) {
|
|
827
|
+
this.logger.warn("Failed to get topic hash for event", {
|
|
828
|
+
eventName: r,
|
|
829
|
+
error: s.message
|
|
830
|
+
});
|
|
831
|
+
}
|
|
832
|
+
}
|
|
833
|
+
this.logger.debug("Event initialization completed", { eventCount: e });
|
|
834
|
+
}
|
|
835
|
+
// Get the latest block number from the network
|
|
836
|
+
async getLatestBlockNumber() {
|
|
837
|
+
return this.logger.time("getLatestBlockNumber", async () => {
|
|
838
|
+
const e = await this.retryOperation(
|
|
839
|
+
() => this._callWithFailover(
|
|
840
|
+
(t) => t.getBlockNumber(),
|
|
841
|
+
"getLatestBlockNumber"
|
|
842
|
+
),
|
|
843
|
+
"Getting latest block number"
|
|
844
|
+
);
|
|
845
|
+
return this.logger.logNetworkActivity("getLatestBlockNumber", { blockNumber: e }), e;
|
|
846
|
+
});
|
|
847
|
+
}
|
|
848
|
+
// Get logs from the blockchain within a block range
|
|
849
|
+
async getLogs(e, t) {
|
|
850
|
+
return this.logger.time("getLogs", async () => {
|
|
851
|
+
this.logger.logNetworkActivity("getLogs_request", { fromBlock: e, toBlock: t, address: this.config.address });
|
|
852
|
+
const r = await this.retryOperation(
|
|
853
|
+
() => this._callWithFailover(
|
|
854
|
+
(s) => s.getLogs({
|
|
855
|
+
address: this.config.address,
|
|
856
|
+
fromBlock: e,
|
|
857
|
+
toBlock: t
|
|
858
|
+
}),
|
|
859
|
+
"getLogs"
|
|
860
|
+
),
|
|
861
|
+
`Getting logs for blocks ${e}-${t}`
|
|
862
|
+
);
|
|
863
|
+
return this.logger.logNetworkActivity("getLogs_response", {
|
|
864
|
+
fromBlock: e,
|
|
865
|
+
toBlock: t,
|
|
866
|
+
logCount: r.length
|
|
867
|
+
}), r;
|
|
868
|
+
});
|
|
869
|
+
}
|
|
870
|
+
// Parse a raw log into structured event data
|
|
871
|
+
parseLog(e) {
|
|
872
|
+
const t = e.topics[0], r = this.eventsByTopic[t];
|
|
873
|
+
if (!r)
|
|
874
|
+
return this.logger.trace("Unknown event topic", { topicHash: t, availableTopics: Object.keys(this.eventsByTopic) }), null;
|
|
875
|
+
try {
|
|
876
|
+
const s = this.interface.parseLog(e), n = {
|
|
877
|
+
eventName: r,
|
|
878
|
+
parsedLog: s,
|
|
879
|
+
eventData: {
|
|
880
|
+
...s.args,
|
|
881
|
+
blockNumber: e.blockNumber,
|
|
882
|
+
transactionHash: e.transactionHash,
|
|
883
|
+
logIndex: e.logIndex,
|
|
884
|
+
timestamp: Date.now()
|
|
885
|
+
}
|
|
886
|
+
};
|
|
887
|
+
return this.logger.debug("Successfully parsed log", {
|
|
888
|
+
eventName: r,
|
|
889
|
+
blockNumber: e.blockNumber,
|
|
890
|
+
transactionHash: e.transactionHash,
|
|
891
|
+
logIndex: e.logIndex
|
|
892
|
+
}), n;
|
|
893
|
+
} catch (s) {
|
|
894
|
+
return this.logger.warn("Failed to parse log for event", {
|
|
895
|
+
eventName: r,
|
|
896
|
+
blockNumber: e.blockNumber,
|
|
897
|
+
transactionHash: e.transactionHash,
|
|
898
|
+
error: s.message
|
|
899
|
+
}), null;
|
|
900
|
+
}
|
|
901
|
+
}
|
|
902
|
+
// Create unique event key for deduplication
|
|
903
|
+
createEventKey(e) {
|
|
904
|
+
return `${e.transactionHash}-${e.logIndex}`;
|
|
905
|
+
}
|
|
906
|
+
// Get network information
|
|
907
|
+
async getNetworkInfo() {
|
|
908
|
+
return this.retryOperation(async () => {
|
|
909
|
+
const e = await this._callWithFailover(
|
|
910
|
+
(r) => r.getNetwork(),
|
|
911
|
+
"getNetwork"
|
|
912
|
+
), t = await this._callWithFailover(
|
|
913
|
+
(r) => r.getBlockNumber(),
|
|
914
|
+
"getBlockNumber"
|
|
915
|
+
);
|
|
916
|
+
return {
|
|
917
|
+
chainId: Number(e.chainId),
|
|
918
|
+
name: e.name,
|
|
919
|
+
currentBlock: t,
|
|
920
|
+
expectedNetworkId: this.config.networkId
|
|
921
|
+
};
|
|
922
|
+
}, "Getting network information");
|
|
923
|
+
}
|
|
924
|
+
// Validate network connection and configuration
|
|
925
|
+
async validateNetwork() {
|
|
926
|
+
const e = await this.getNetworkInfo();
|
|
927
|
+
if (e.chainId !== this.config.networkId)
|
|
928
|
+
throw new Error(
|
|
929
|
+
`Network mismatch: expected ${this.config.networkId}, got ${e.chainId}`
|
|
930
|
+
);
|
|
931
|
+
return e;
|
|
932
|
+
}
|
|
933
|
+
// Get contract code to verify contract exists
|
|
934
|
+
async validateContract() {
|
|
935
|
+
return this.retryOperation(async () => {
|
|
936
|
+
if (await this._callWithFailover(
|
|
937
|
+
(t) => t.getCode(this.config.address),
|
|
938
|
+
"getCode"
|
|
939
|
+
) === "0x")
|
|
940
|
+
throw new Error(`No contract found at address ${this.config.address}`);
|
|
941
|
+
return !0;
|
|
942
|
+
}, "Validating contract");
|
|
943
|
+
}
|
|
944
|
+
// Get a block by number, with LRU caching
|
|
945
|
+
async getBlock(e) {
|
|
946
|
+
const t = this.blockCache.get(e);
|
|
947
|
+
if (t !== void 0)
|
|
948
|
+
return this.logger.debug("Block cache hit", { blockNumber: e }), t;
|
|
949
|
+
const r = await this.retryOperation(async () => this._callWithFailover(
|
|
950
|
+
(s) => s.getBlock(e),
|
|
951
|
+
`getBlock(${e})`
|
|
952
|
+
), `Getting block ${e}`);
|
|
953
|
+
return r && this.blockCache.set(e, r), r;
|
|
954
|
+
}
|
|
955
|
+
// Get block timestamp, with LRU caching
|
|
956
|
+
async getBlockTimestamp(e) {
|
|
957
|
+
const t = this.timestampCache.get(e);
|
|
958
|
+
if (t !== void 0)
|
|
959
|
+
return this.logger.debug("Timestamp cache hit", { blockNumber: e }), t;
|
|
960
|
+
const r = await this.getBlock(e), s = r ? r.timestamp : null;
|
|
961
|
+
return s !== null && this.timestampCache.set(e, s), s;
|
|
962
|
+
}
|
|
963
|
+
// Get transaction receipt
|
|
964
|
+
async getTransactionReceipt(e) {
|
|
965
|
+
return this.retryOperation(
|
|
966
|
+
() => this._callWithFailover(
|
|
967
|
+
(t) => t.getTransactionReceipt(e),
|
|
968
|
+
"getTransactionReceipt"
|
|
969
|
+
),
|
|
970
|
+
`Getting transaction receipt for ${e}`
|
|
971
|
+
);
|
|
972
|
+
}
|
|
973
|
+
// Get gas price information
|
|
974
|
+
async getGasPrice() {
|
|
975
|
+
return this.retryOperation(
|
|
976
|
+
() => this._callWithFailover(
|
|
977
|
+
(e) => e.getFeeData(),
|
|
978
|
+
"getFeeData"
|
|
979
|
+
),
|
|
980
|
+
"Getting gas price information"
|
|
981
|
+
);
|
|
982
|
+
}
|
|
983
|
+
// Get provider instance for advanced operations
|
|
984
|
+
getProvider() {
|
|
985
|
+
return this.provider;
|
|
986
|
+
}
|
|
987
|
+
// Get ethers interface for manual parsing
|
|
988
|
+
getInterface() {
|
|
989
|
+
return this.interface;
|
|
990
|
+
}
|
|
991
|
+
// Get event mappings
|
|
992
|
+
getEventMappings() {
|
|
993
|
+
return {
|
|
994
|
+
events: { ...this.events },
|
|
995
|
+
eventsByTopic: { ...this.eventsByTopic }
|
|
996
|
+
};
|
|
997
|
+
}
|
|
998
|
+
// Clear all caches
|
|
999
|
+
clearCaches() {
|
|
1000
|
+
this.blockCache.clear(), this.timestampCache.clear(), this.logger.debug("All caches cleared");
|
|
1001
|
+
}
|
|
1002
|
+
// Get cache statistics
|
|
1003
|
+
getCacheStats() {
|
|
1004
|
+
return {
|
|
1005
|
+
blockCacheSize: this.blockCache.size,
|
|
1006
|
+
timestampCacheSize: this.timestampCache.size,
|
|
1007
|
+
maxCacheSize: this.blockCache.maxSize
|
|
1008
|
+
};
|
|
1009
|
+
}
|
|
1010
|
+
}
|
|
1011
|
+
class C {
|
|
1012
|
+
constructor(e) {
|
|
1013
|
+
this.logger = e.child({ context: "EventFilter" }), this.logger.info("EventFilter initialized");
|
|
1014
|
+
}
|
|
1015
|
+
/**
|
|
1016
|
+
* Build filter criteria from user input
|
|
1017
|
+
* @param {Object} options - Filter options
|
|
1018
|
+
* @param {number} options.fromBlock - Starting block number
|
|
1019
|
+
* @param {number} options.toBlock - Ending block number
|
|
1020
|
+
* @param {string} options.eventName - Event name to filter
|
|
1021
|
+
* @param {Object} options.parameters - Event parameter filters
|
|
1022
|
+
* @param {string} options.transactionHash - Filter by transaction hash
|
|
1023
|
+
* @param {string} options.address - Filter by contract address
|
|
1024
|
+
* @returns {Object} Filter criteria
|
|
1025
|
+
*/
|
|
1026
|
+
buildFilter(e = {}) {
|
|
1027
|
+
const t = {};
|
|
1028
|
+
return e.fromBlock !== void 0 && (t.fromBlock = e.fromBlock), e.toBlock !== void 0 && (t.toBlock = e.toBlock), e.eventName && (t.eventName = e.eventName), e.parameters && Object.keys(e.parameters).length > 0 && (t.parameters = e.parameters), e.transactionHash && (t.transactionHash = e.transactionHash), e.address && (t.address = e.address.toLowerCase()), e.$or && (t.$or = e.$or), e.$and && (t.$and = e.$and), e.$nor && (t.$nor = e.$nor), this.logger.debug("Built filter criteria", { filter: t }), t;
|
|
1029
|
+
}
|
|
1030
|
+
/**
|
|
1031
|
+
* Apply filters to an array of events
|
|
1032
|
+
* @param {Array} events - Events to filter
|
|
1033
|
+
* @param {Object} filter - Filter criteria
|
|
1034
|
+
* @returns {Array} Filtered events
|
|
1035
|
+
*/
|
|
1036
|
+
applyFilter(e, t) {
|
|
1037
|
+
if (!Array.isArray(e))
|
|
1038
|
+
return this.logger.warn("Invalid events array provided to applyFilter"), [];
|
|
1039
|
+
let r = [...e];
|
|
1040
|
+
return t.$or && (r = r.filter(
|
|
1041
|
+
(s) => t.$or.some((n) => {
|
|
1042
|
+
const i = this.buildFilter(n);
|
|
1043
|
+
return this.applyFilter([s], i).length > 0;
|
|
1044
|
+
})
|
|
1045
|
+
)), t.$and && (r = r.filter(
|
|
1046
|
+
(s) => t.$and.every((n) => {
|
|
1047
|
+
const i = this.buildFilter(n);
|
|
1048
|
+
return this.applyFilter([s], i).length > 0;
|
|
1049
|
+
})
|
|
1050
|
+
)), t.$nor && (r = r.filter(
|
|
1051
|
+
(s) => t.$nor.every((n) => {
|
|
1052
|
+
const i = this.buildFilter(n);
|
|
1053
|
+
return this.applyFilter([s], i).length === 0;
|
|
1054
|
+
})
|
|
1055
|
+
)), t.fromBlock !== void 0 && (r = r.filter((s) => s.blockNumber >= t.fromBlock)), t.toBlock !== void 0 && (r = r.filter((s) => s.blockNumber <= t.toBlock)), t.eventName && (r = r.filter((s) => s.eventName === t.eventName)), t.transactionHash && (r = r.filter(
|
|
1056
|
+
(s) => s.transactionHash.toLowerCase() === t.transactionHash.toLowerCase()
|
|
1057
|
+
)), t.address && (r = r.filter(
|
|
1058
|
+
(s) => s.address && s.address.toLowerCase() === t.address
|
|
1059
|
+
)), t.parameters && (r = r.filter(
|
|
1060
|
+
(s) => this._matchesParameters(s.args || {}, t.parameters)
|
|
1061
|
+
)), this.logger.debug("Applied filters", {
|
|
1062
|
+
originalCount: e.length,
|
|
1063
|
+
filteredCount: r.length,
|
|
1064
|
+
filter: t
|
|
1065
|
+
}), r;
|
|
1066
|
+
}
|
|
1067
|
+
/**
|
|
1068
|
+
* Check if event parameters match filter criteria (public API)
|
|
1069
|
+
*/
|
|
1070
|
+
matchesParameters(e, t) {
|
|
1071
|
+
return this._matchesParameters(e, t);
|
|
1072
|
+
}
|
|
1073
|
+
/**
|
|
1074
|
+
* Check if event parameters match filter criteria
|
|
1075
|
+
* @private
|
|
1076
|
+
*/
|
|
1077
|
+
_matchesParameters(e, t) {
|
|
1078
|
+
for (const [r, s] of Object.entries(t))
|
|
1079
|
+
if (typeof s == "object" && s !== null) {
|
|
1080
|
+
if (s.exists !== void 0) {
|
|
1081
|
+
const n = e.hasOwnProperty(r);
|
|
1082
|
+
if (s.exists && !n || !s.exists && n)
|
|
1083
|
+
return !1;
|
|
1084
|
+
if (Object.keys(s).filter((a) => a !== "exists").length === 0)
|
|
1085
|
+
continue;
|
|
1086
|
+
}
|
|
1087
|
+
if (!e.hasOwnProperty(r) || s.gte !== void 0 && e[r] < s.gte || s.lte !== void 0 && e[r] > s.lte || s.gt !== void 0 && e[r] <= s.gt || s.lt !== void 0 && e[r] >= s.lt || s.in !== void 0 && !s.in.includes(e[r]) || s.nin !== void 0 && s.nin.includes(e[r]) || s.not !== void 0 && this._matchesParameters(e, { [r]: s.not }) || s.regex !== void 0 && !new RegExp(s.regex).test(String(e[r])))
|
|
1088
|
+
return !1;
|
|
1089
|
+
if (s.size !== void 0) {
|
|
1090
|
+
const n = e[r];
|
|
1091
|
+
if (!Array.isArray(n) || n.length !== s.size)
|
|
1092
|
+
return !1;
|
|
1093
|
+
}
|
|
1094
|
+
} else if (!e.hasOwnProperty(r) || e[r] !== s)
|
|
1095
|
+
return !1;
|
|
1096
|
+
return !0;
|
|
1097
|
+
}
|
|
1098
|
+
/**
|
|
1099
|
+
* Sort events by specified criteria
|
|
1100
|
+
* @param {Array} events - Events to sort
|
|
1101
|
+
* @param {Object} options - Sort options
|
|
1102
|
+
* @param {string} options.by - Field to sort by (blockNumber, timestamp, etc.)
|
|
1103
|
+
* @param {string} options.order - Sort order ('asc' or 'desc')
|
|
1104
|
+
* @returns {Array} Sorted events
|
|
1105
|
+
*/
|
|
1106
|
+
sortEvents(e, t = {}) {
|
|
1107
|
+
const { by: r = "blockNumber", order: s = "asc" } = t, n = [...e].sort((i, a) => {
|
|
1108
|
+
let l = i[r], o = a[r];
|
|
1109
|
+
if (r.includes(".")) {
|
|
1110
|
+
const h = r.split(".");
|
|
1111
|
+
l = h.reduce((g, d) => g == null ? void 0 : g[d], i), o = h.reduce((g, d) => g == null ? void 0 : g[d], a);
|
|
1112
|
+
}
|
|
1113
|
+
if (l === o)
|
|
1114
|
+
return 0;
|
|
1115
|
+
const c = l < o ? -1 : 1;
|
|
1116
|
+
return s === "asc" ? c : -c;
|
|
1117
|
+
});
|
|
1118
|
+
return this.logger.debug("Sorted events", { by: r, order: s, count: n.length }), n;
|
|
1119
|
+
}
|
|
1120
|
+
/**
|
|
1121
|
+
* Group events by specified field
|
|
1122
|
+
* @param {Array} events - Events to group
|
|
1123
|
+
* @param {string} field - Field to group by
|
|
1124
|
+
* @returns {Object} Grouped events
|
|
1125
|
+
*/
|
|
1126
|
+
groupEvents(e, t) {
|
|
1127
|
+
const r = {};
|
|
1128
|
+
for (const s of e) {
|
|
1129
|
+
let n = s[t];
|
|
1130
|
+
t.includes(".") && (n = t.split(".").reduce((a, l) => a == null ? void 0 : a[l], s)), n === void 0 && (n = "undefined"), r[n] || (r[n] = []), r[n].push(s);
|
|
1131
|
+
}
|
|
1132
|
+
return this.logger.debug("Grouped events", {
|
|
1133
|
+
field: t,
|
|
1134
|
+
groups: Object.keys(r).length,
|
|
1135
|
+
totalEvents: e.length
|
|
1136
|
+
}), r;
|
|
1137
|
+
}
|
|
1138
|
+
/**
|
|
1139
|
+
* Create a query builder for fluent API
|
|
1140
|
+
* @param {Array} events - Events to query
|
|
1141
|
+
* @returns {QueryBuilder} Query builder instance
|
|
1142
|
+
*/
|
|
1143
|
+
query(e) {
|
|
1144
|
+
return new O(e, this);
|
|
1145
|
+
}
|
|
1146
|
+
}
|
|
1147
|
+
class O {
|
|
1148
|
+
constructor(e, t) {
|
|
1149
|
+
this.events = e, this.eventFilter = t, this.filters = {}, this.sortOptions = null, this._limit = null, this._skip = null;
|
|
1150
|
+
}
|
|
1151
|
+
/**
|
|
1152
|
+
* Filter by block range
|
|
1153
|
+
*/
|
|
1154
|
+
blockRange(e, t) {
|
|
1155
|
+
return e !== void 0 && (this.filters.fromBlock = e), t !== void 0 && (this.filters.toBlock = t), this;
|
|
1156
|
+
}
|
|
1157
|
+
/**
|
|
1158
|
+
* Filter by event name
|
|
1159
|
+
*/
|
|
1160
|
+
eventName(e) {
|
|
1161
|
+
return this.filters.eventName = e, this;
|
|
1162
|
+
}
|
|
1163
|
+
/**
|
|
1164
|
+
* Filter by transaction hash
|
|
1165
|
+
*/
|
|
1166
|
+
transactionHash(e) {
|
|
1167
|
+
return this.filters.transactionHash = e, this;
|
|
1168
|
+
}
|
|
1169
|
+
/**
|
|
1170
|
+
* Filter by contract address
|
|
1171
|
+
*/
|
|
1172
|
+
address(e) {
|
|
1173
|
+
return this.filters.address = e, this;
|
|
1174
|
+
}
|
|
1175
|
+
/**
|
|
1176
|
+
* Filter by event parameters
|
|
1177
|
+
*/
|
|
1178
|
+
where(e, t) {
|
|
1179
|
+
return this.filters.parameters || (this.filters.parameters = {}), this.filters.parameters[e] = t, this;
|
|
1180
|
+
}
|
|
1181
|
+
/**
|
|
1182
|
+
* Add an $or condition
|
|
1183
|
+
*/
|
|
1184
|
+
orWhere(e, t) {
|
|
1185
|
+
return this.filters.$or || (this.filters.$or = []), this.filters.$or.push({ parameters: { [e]: t } }), this;
|
|
1186
|
+
}
|
|
1187
|
+
/**
|
|
1188
|
+
* Shorthand for exists check on a field
|
|
1189
|
+
*/
|
|
1190
|
+
whereExists(e) {
|
|
1191
|
+
return this.filters.parameters || (this.filters.parameters = {}), this.filters.parameters[e] = { exists: !0 }, this;
|
|
1192
|
+
}
|
|
1193
|
+
/**
|
|
1194
|
+
* Shorthand for regex match on a field
|
|
1195
|
+
*/
|
|
1196
|
+
whereRegex(e, t) {
|
|
1197
|
+
return this.filters.parameters || (this.filters.parameters = {}), this.filters.parameters[e] = { regex: t }, this;
|
|
1198
|
+
}
|
|
1199
|
+
/**
|
|
1200
|
+
* Limit number of results
|
|
1201
|
+
*/
|
|
1202
|
+
limit(e) {
|
|
1203
|
+
return this._limit = e, this;
|
|
1204
|
+
}
|
|
1205
|
+
/**
|
|
1206
|
+
* Skip first n results
|
|
1207
|
+
*/
|
|
1208
|
+
skip(e) {
|
|
1209
|
+
return this._skip = e, this;
|
|
1210
|
+
}
|
|
1211
|
+
/**
|
|
1212
|
+
* Sort results
|
|
1213
|
+
*/
|
|
1214
|
+
orderBy(e, t = "asc") {
|
|
1215
|
+
return this.sortOptions = { by: e, order: t }, this;
|
|
1216
|
+
}
|
|
1217
|
+
/**
|
|
1218
|
+
* Execute query and return results
|
|
1219
|
+
*/
|
|
1220
|
+
execute() {
|
|
1221
|
+
let e = this.eventFilter.applyFilter(this.events, this.filters);
|
|
1222
|
+
return this.sortOptions && (e = this.eventFilter.sortEvents(e, this.sortOptions)), this._skip !== null && (e = e.slice(this._skip)), this._limit !== null && (e = e.slice(0, this._limit)), e;
|
|
1223
|
+
}
|
|
1224
|
+
/**
|
|
1225
|
+
* Execute query and return first result
|
|
1226
|
+
*/
|
|
1227
|
+
first() {
|
|
1228
|
+
const e = this.execute();
|
|
1229
|
+
return e.length > 0 ? e[0] : null;
|
|
1230
|
+
}
|
|
1231
|
+
/**
|
|
1232
|
+
* Execute query and return count
|
|
1233
|
+
*/
|
|
1234
|
+
count() {
|
|
1235
|
+
return this.execute().length;
|
|
1236
|
+
}
|
|
1237
|
+
/**
|
|
1238
|
+
* Execute query and group results
|
|
1239
|
+
*/
|
|
1240
|
+
groupBy(e) {
|
|
1241
|
+
const t = this.execute();
|
|
1242
|
+
return this.eventFilter.groupEvents(t, e);
|
|
1243
|
+
}
|
|
1244
|
+
}
|
|
1245
|
+
class P {
|
|
1246
|
+
constructor(e) {
|
|
1247
|
+
this.logger = e.child({ context: "Paginator" }), this.logger.info("Paginator initialized");
|
|
1248
|
+
}
|
|
1249
|
+
/**
|
|
1250
|
+
* Create a paginated result set
|
|
1251
|
+
* @param {Array} items - Items to paginate
|
|
1252
|
+
* @param {Object} options - Pagination options
|
|
1253
|
+
* @param {number} options.page - Current page (1-based)
|
|
1254
|
+
* @param {number} options.pageSize - Items per page
|
|
1255
|
+
* @param {boolean} options.includeMeta - Include pagination metadata
|
|
1256
|
+
* @returns {Object} Paginated result
|
|
1257
|
+
*/
|
|
1258
|
+
paginate(e, t = {}) {
|
|
1259
|
+
const {
|
|
1260
|
+
page: r = 1,
|
|
1261
|
+
pageSize: s = 100,
|
|
1262
|
+
includeMeta: n = !0
|
|
1263
|
+
} = t;
|
|
1264
|
+
if (!Array.isArray(e))
|
|
1265
|
+
return this.logger.warn("Invalid items array provided to paginate"), this._createEmptyResult(n);
|
|
1266
|
+
if (r < 1 || s < 1)
|
|
1267
|
+
return this.logger.warn("Invalid pagination parameters", { page: r, pageSize: s }), this._createEmptyResult(n);
|
|
1268
|
+
const i = e.length, a = Math.ceil(i / s), l = (r - 1) * s, o = Math.min(l + s, i), c = e.slice(l, o);
|
|
1269
|
+
this.logger.debug("Created paginated result", {
|
|
1270
|
+
page: r,
|
|
1271
|
+
pageSize: s,
|
|
1272
|
+
totalItems: i,
|
|
1273
|
+
totalPages: a,
|
|
1274
|
+
itemsInPage: c.length
|
|
1275
|
+
});
|
|
1276
|
+
const h = {
|
|
1277
|
+
data: c
|
|
1278
|
+
};
|
|
1279
|
+
return n && (h.meta = {
|
|
1280
|
+
page: r,
|
|
1281
|
+
pageSize: s,
|
|
1282
|
+
totalItems: i,
|
|
1283
|
+
totalPages: a,
|
|
1284
|
+
hasNextPage: r < a,
|
|
1285
|
+
hasPreviousPage: r > 1,
|
|
1286
|
+
startIndex: l + 1,
|
|
1287
|
+
// 1-based for display
|
|
1288
|
+
endIndex: o
|
|
1289
|
+
}), h;
|
|
1290
|
+
}
|
|
1291
|
+
/**
|
|
1292
|
+
* Create an async iterator for large datasets
|
|
1293
|
+
* @param {Array|Function} itemsOrGetter - Items array or async function to get items
|
|
1294
|
+
* @param {Object} options - Iterator options
|
|
1295
|
+
* @param {number} options.pageSize - Items per page
|
|
1296
|
+
* @returns {AsyncIterator} Async iterator
|
|
1297
|
+
*/
|
|
1298
|
+
async *iterate(e, t = {}) {
|
|
1299
|
+
const { pageSize: r = 100 } = t;
|
|
1300
|
+
let s = 1, n = !0;
|
|
1301
|
+
for (; n; ) {
|
|
1302
|
+
let i;
|
|
1303
|
+
if (typeof e == "function")
|
|
1304
|
+
i = await e(s, r);
|
|
1305
|
+
else if (Array.isArray(e)) {
|
|
1306
|
+
const a = this.paginate(e, { page: s, pageSize: r, includeMeta: !0 });
|
|
1307
|
+
i = a.data, n = a.meta.hasNextPage;
|
|
1308
|
+
} else {
|
|
1309
|
+
this.logger.error("Invalid itemsOrGetter provided to iterate");
|
|
1310
|
+
return;
|
|
1311
|
+
}
|
|
1312
|
+
if (!i || i.length === 0) {
|
|
1313
|
+
n = !1;
|
|
1314
|
+
break;
|
|
1315
|
+
}
|
|
1316
|
+
for (const a of i)
|
|
1317
|
+
yield a;
|
|
1318
|
+
Array.isArray(e) || (n = i.length === r), s++;
|
|
1319
|
+
}
|
|
1320
|
+
this.logger.debug("Iterator completed", { totalPages: s - 1 });
|
|
1321
|
+
}
|
|
1322
|
+
/**
|
|
1323
|
+
* Create a cursor-based pagination result
|
|
1324
|
+
* @param {Array} items - Items to paginate
|
|
1325
|
+
* @param {Object} options - Cursor pagination options
|
|
1326
|
+
* @param {string} options.cursor - Current cursor position
|
|
1327
|
+
* @param {number} options.limit - Number of items to return
|
|
1328
|
+
* @param {string} options.cursorField - Field to use for cursor (default: 'id')
|
|
1329
|
+
* @param {string} options.direction - Pagination direction ('forward' or 'backward')
|
|
1330
|
+
* @returns {Object} Cursor-paginated result
|
|
1331
|
+
*/
|
|
1332
|
+
paginateCursor(e, t = {}) {
|
|
1333
|
+
const {
|
|
1334
|
+
cursor: r = null,
|
|
1335
|
+
limit: s = 100,
|
|
1336
|
+
cursorField: n = "id",
|
|
1337
|
+
direction: i = "forward"
|
|
1338
|
+
} = t;
|
|
1339
|
+
if (!Array.isArray(e))
|
|
1340
|
+
return this.logger.warn("Invalid items array provided to paginateCursor"), this._createEmptyCursorResult();
|
|
1341
|
+
let a = [...e];
|
|
1342
|
+
r !== null && (a = a.filter((d) => {
|
|
1343
|
+
const m = d[n];
|
|
1344
|
+
return i === "forward" ? m > r : m < r;
|
|
1345
|
+
})), a.sort((d, m) => {
|
|
1346
|
+
const y = d[n], w = m[n];
|
|
1347
|
+
return i === "forward" ? y < w ? -1 : 1 : y > w ? -1 : 1;
|
|
1348
|
+
});
|
|
1349
|
+
const l = a.slice(0, s), o = a.length > s, c = l[0], h = l[l.length - 1], g = {
|
|
1350
|
+
data: l,
|
|
1351
|
+
cursors: {
|
|
1352
|
+
before: c ? c[n] : null,
|
|
1353
|
+
after: h ? h[n] : null,
|
|
1354
|
+
hasNext: o,
|
|
1355
|
+
hasPrevious: r !== null
|
|
1356
|
+
}
|
|
1357
|
+
};
|
|
1358
|
+
return this.logger.debug("Created cursor-paginated result", {
|
|
1359
|
+
cursor: r,
|
|
1360
|
+
limit: s,
|
|
1361
|
+
cursorField: n,
|
|
1362
|
+
direction: i,
|
|
1363
|
+
itemsReturned: l.length,
|
|
1364
|
+
hasMore: o
|
|
1365
|
+
}), g;
|
|
1366
|
+
}
|
|
1367
|
+
/**
|
|
1368
|
+
* Create a page info object for GraphQL-style pagination
|
|
1369
|
+
* @param {Object} paginationMeta - Pagination metadata
|
|
1370
|
+
* @returns {Object} Page info
|
|
1371
|
+
*/
|
|
1372
|
+
createPageInfo(e) {
|
|
1373
|
+
return {
|
|
1374
|
+
hasNextPage: e.hasNextPage || !1,
|
|
1375
|
+
hasPreviousPage: e.hasPreviousPage || !1,
|
|
1376
|
+
startCursor: e.startCursor || null,
|
|
1377
|
+
endCursor: e.endCursor || null,
|
|
1378
|
+
totalCount: e.totalItems || 0
|
|
1379
|
+
};
|
|
1380
|
+
}
|
|
1381
|
+
/**
|
|
1382
|
+
* Calculate optimal page size based on data characteristics
|
|
1383
|
+
* @param {Object} options - Options for calculation
|
|
1384
|
+
* @param {number} options.totalItems - Total number of items
|
|
1385
|
+
* @param {number} options.avgItemSize - Average item size in bytes
|
|
1386
|
+
* @param {number} options.maxMemory - Maximum memory to use (bytes)
|
|
1387
|
+
* @param {number} options.minPageSize - Minimum page size
|
|
1388
|
+
* @param {number} options.maxPageSize - Maximum page size
|
|
1389
|
+
* @returns {number} Optimal page size
|
|
1390
|
+
*/
|
|
1391
|
+
calculateOptimalPageSize(e = {}) {
|
|
1392
|
+
const {
|
|
1393
|
+
totalItems: t = 1e3,
|
|
1394
|
+
avgItemSize: r = 1024,
|
|
1395
|
+
// 1KB default
|
|
1396
|
+
maxMemory: s = 10 * 1024 * 1024,
|
|
1397
|
+
// 10MB default
|
|
1398
|
+
minPageSize: n = 10,
|
|
1399
|
+
maxPageSize: i = 1e3
|
|
1400
|
+
} = e;
|
|
1401
|
+
let a = Math.floor(s / r);
|
|
1402
|
+
return a = Math.max(n, Math.min(i, a)), t < a && (a = t), this.logger.debug("Calculated optimal page size", {
|
|
1403
|
+
totalItems: t,
|
|
1404
|
+
avgItemSize: r,
|
|
1405
|
+
maxMemory: s,
|
|
1406
|
+
optimalSize: a
|
|
1407
|
+
}), a;
|
|
1408
|
+
}
|
|
1409
|
+
/**
|
|
1410
|
+
* Create empty result for error cases
|
|
1411
|
+
* @private
|
|
1412
|
+
*/
|
|
1413
|
+
_createEmptyResult(e) {
|
|
1414
|
+
const t = { data: [] };
|
|
1415
|
+
return e && (t.meta = {
|
|
1416
|
+
page: 1,
|
|
1417
|
+
pageSize: 0,
|
|
1418
|
+
totalItems: 0,
|
|
1419
|
+
totalPages: 0,
|
|
1420
|
+
hasNextPage: !1,
|
|
1421
|
+
hasPreviousPage: !1,
|
|
1422
|
+
startIndex: 0,
|
|
1423
|
+
endIndex: 0
|
|
1424
|
+
}), t;
|
|
1425
|
+
}
|
|
1426
|
+
/**
|
|
1427
|
+
* Create empty cursor result for error cases
|
|
1428
|
+
* @private
|
|
1429
|
+
*/
|
|
1430
|
+
_createEmptyCursorResult() {
|
|
1431
|
+
return {
|
|
1432
|
+
data: [],
|
|
1433
|
+
cursors: {
|
|
1434
|
+
before: null,
|
|
1435
|
+
after: null,
|
|
1436
|
+
hasNext: !1,
|
|
1437
|
+
hasPrevious: !1
|
|
1438
|
+
}
|
|
1439
|
+
};
|
|
1440
|
+
}
|
|
1441
|
+
}
|
|
1442
|
+
class J {
|
|
1443
|
+
/**
|
|
1444
|
+
* Calculate pagination offset
|
|
1445
|
+
*/
|
|
1446
|
+
static calculateOffset(e, t) {
|
|
1447
|
+
return (e - 1) * t;
|
|
1448
|
+
}
|
|
1449
|
+
/**
|
|
1450
|
+
* Calculate total pages
|
|
1451
|
+
*/
|
|
1452
|
+
static calculateTotalPages(e, t) {
|
|
1453
|
+
return Math.ceil(e / t);
|
|
1454
|
+
}
|
|
1455
|
+
/**
|
|
1456
|
+
* Validate page number
|
|
1457
|
+
*/
|
|
1458
|
+
static isValidPage(e, t) {
|
|
1459
|
+
return e >= 1 && e <= t;
|
|
1460
|
+
}
|
|
1461
|
+
/**
|
|
1462
|
+
* Get page numbers for pagination UI
|
|
1463
|
+
*/
|
|
1464
|
+
static getPageNumbers(e, t, r = 5) {
|
|
1465
|
+
const s = [], n = Math.floor(r / 2);
|
|
1466
|
+
let i = Math.max(1, e - n), a = Math.min(t, e + n);
|
|
1467
|
+
e <= n ? a = Math.min(t, r) : e >= t - n && (i = Math.max(1, t - r + 1));
|
|
1468
|
+
for (let l = i; l <= a; l++)
|
|
1469
|
+
s.push(l);
|
|
1470
|
+
return s;
|
|
1471
|
+
}
|
|
1472
|
+
}
|
|
1473
|
+
const k = typeof process < "u" && process.versions != null && process.versions.node != null;
|
|
1474
|
+
function F(u) {
|
|
1475
|
+
return k ? Buffer.from(u).toString("base64") : btoa(String.fromCharCode.apply(null, u));
|
|
1476
|
+
}
|
|
1477
|
+
function L(u) {
|
|
1478
|
+
if (k) {
|
|
1479
|
+
const r = Buffer.from(u, "base64");
|
|
1480
|
+
return new Uint8Array(r.buffer, r.byteOffset, r.byteLength);
|
|
1481
|
+
}
|
|
1482
|
+
const e = atob(u), t = new Uint8Array(e.length);
|
|
1483
|
+
for (let r = 0; r < e.length; r++)
|
|
1484
|
+
t[r] = e.charCodeAt(r);
|
|
1485
|
+
return t;
|
|
1486
|
+
}
|
|
1487
|
+
class D {
|
|
1488
|
+
constructor(e) {
|
|
1489
|
+
this.logger = e.child({ context: "DataCompressor" }), this.logger.info("DataCompressor initialized"), this.strategies = {
|
|
1490
|
+
none: {
|
|
1491
|
+
compress: (t) => t,
|
|
1492
|
+
decompress: (t) => t,
|
|
1493
|
+
ratio: 1
|
|
1494
|
+
},
|
|
1495
|
+
json: {
|
|
1496
|
+
compress: (t) => this._compressJSON(t),
|
|
1497
|
+
decompress: (t) => this._decompressJSON(t),
|
|
1498
|
+
ratio: 0.7
|
|
1499
|
+
// Approximate
|
|
1500
|
+
},
|
|
1501
|
+
gzip: {
|
|
1502
|
+
compress: (t) => this._compressGzip(t),
|
|
1503
|
+
decompress: (t) => this._decompressGzip(t),
|
|
1504
|
+
ratio: 0.3
|
|
1505
|
+
// Approximate
|
|
1506
|
+
},
|
|
1507
|
+
optimized: {
|
|
1508
|
+
compress: (t) => this._compressOptimized(t),
|
|
1509
|
+
decompress: (t) => this._decompressOptimized(t),
|
|
1510
|
+
ratio: 0.4
|
|
1511
|
+
// Approximate
|
|
1512
|
+
}
|
|
1513
|
+
}, this.defaultStrategy = "optimized";
|
|
1514
|
+
}
|
|
1515
|
+
/**
|
|
1516
|
+
* Compress data using specified strategy
|
|
1517
|
+
* @param {any} data - Data to compress
|
|
1518
|
+
* @param {string} strategy - Compression strategy
|
|
1519
|
+
* @returns {Object} Compressed data with metadata
|
|
1520
|
+
*/
|
|
1521
|
+
compress(e, t = this.defaultStrategy) {
|
|
1522
|
+
try {
|
|
1523
|
+
const r = Date.now(), s = this._calculateSize(e);
|
|
1524
|
+
this.strategies[t] || (this.logger.warn("Unknown compression strategy, using default", { strategy: t }), t = this.defaultStrategy);
|
|
1525
|
+
const n = this.strategies[t].compress(e), i = this._calculateSize(n), a = i / s, l = Date.now() - r, o = {
|
|
1526
|
+
data: n,
|
|
1527
|
+
metadata: {
|
|
1528
|
+
strategy: t,
|
|
1529
|
+
originalSize: s,
|
|
1530
|
+
compressedSize: i,
|
|
1531
|
+
compressionRatio: a.toFixed(3),
|
|
1532
|
+
spaceSaved: `${((1 - a) * 100).toFixed(1)}%`,
|
|
1533
|
+
compressionTime: l,
|
|
1534
|
+
timestamp: new Date().toISOString()
|
|
1535
|
+
}
|
|
1536
|
+
};
|
|
1537
|
+
return this.logger.debug("Data compressed", o.metadata), o;
|
|
1538
|
+
} catch (r) {
|
|
1539
|
+
throw this.logger.error("Compression failed", { strategy: t, error: r.message }), r;
|
|
1540
|
+
}
|
|
1541
|
+
}
|
|
1542
|
+
/**
|
|
1543
|
+
* Decompress data
|
|
1544
|
+
* @param {Object} compressedData - Compressed data with metadata
|
|
1545
|
+
* @returns {any} Original data
|
|
1546
|
+
*/
|
|
1547
|
+
decompress(e) {
|
|
1548
|
+
try {
|
|
1549
|
+
if (!e.metadata || !e.metadata.strategy)
|
|
1550
|
+
return this.logger.warn("No compression metadata found, assuming no compression"), e;
|
|
1551
|
+
const { strategy: t } = e.metadata, r = Date.now();
|
|
1552
|
+
if (!this.strategies[t])
|
|
1553
|
+
throw new Error(`Unknown compression strategy: ${t}`);
|
|
1554
|
+
const s = this.strategies[t].decompress(e.data), n = Date.now() - r;
|
|
1555
|
+
return this.logger.debug("Data decompressed", {
|
|
1556
|
+
strategy: t,
|
|
1557
|
+
decompressionTime: n,
|
|
1558
|
+
originalSize: e.metadata.originalSize
|
|
1559
|
+
}), s;
|
|
1560
|
+
} catch (t) {
|
|
1561
|
+
throw this.logger.error("Decompression failed", { error: t.message }), t;
|
|
1562
|
+
}
|
|
1563
|
+
}
|
|
1564
|
+
/**
|
|
1565
|
+
* Compress events for storage
|
|
1566
|
+
* @param {Array} events - Events to compress
|
|
1567
|
+
* @param {Object} options - Compression options
|
|
1568
|
+
* @returns {Object} Compressed events
|
|
1569
|
+
*/
|
|
1570
|
+
compressEvents(e, t = {}) {
|
|
1571
|
+
const { strategy: r = this.defaultStrategy, chunkSize: s = 100 } = t;
|
|
1572
|
+
return this.logger.time("compressEvents", () => {
|
|
1573
|
+
const n = [];
|
|
1574
|
+
for (let c = 0; c < e.length; c += s)
|
|
1575
|
+
n.push(e.slice(c, c + s));
|
|
1576
|
+
const i = n.map((c, h) => {
|
|
1577
|
+
const g = this.compress(c, r);
|
|
1578
|
+
return {
|
|
1579
|
+
...g,
|
|
1580
|
+
metadata: {
|
|
1581
|
+
...g.metadata,
|
|
1582
|
+
chunkIndex: h,
|
|
1583
|
+
eventCount: c.length
|
|
1584
|
+
}
|
|
1585
|
+
};
|
|
1586
|
+
}), a = i.reduce((c, h) => c + h.metadata.originalSize, 0), l = i.reduce((c, h) => c + h.metadata.compressedSize, 0), o = {
|
|
1587
|
+
chunks: i,
|
|
1588
|
+
metadata: {
|
|
1589
|
+
totalEvents: e.length,
|
|
1590
|
+
chunkCount: n.length,
|
|
1591
|
+
strategy: r,
|
|
1592
|
+
totalOriginalSize: a,
|
|
1593
|
+
totalCompressedSize: l,
|
|
1594
|
+
overallCompressionRatio: (l / a).toFixed(3),
|
|
1595
|
+
spaceSaved: `${((1 - l / a) * 100).toFixed(1)}%`
|
|
1596
|
+
}
|
|
1597
|
+
};
|
|
1598
|
+
return this.logger.info("Events compressed", o.metadata), o;
|
|
1599
|
+
});
|
|
1600
|
+
}
|
|
1601
|
+
/**
|
|
1602
|
+
* Decompress events
|
|
1603
|
+
* @param {Object} compressedEvents - Compressed events object
|
|
1604
|
+
* @returns {Array} Original events
|
|
1605
|
+
*/
|
|
1606
|
+
decompressEvents(e) {
|
|
1607
|
+
return this.logger.time("decompressEvents", () => {
|
|
1608
|
+
if (!e.chunks || !Array.isArray(e.chunks))
|
|
1609
|
+
throw new Error("Invalid compressed events format");
|
|
1610
|
+
const t = [];
|
|
1611
|
+
return e.chunks.forEach((r) => {
|
|
1612
|
+
const s = this.decompress(r);
|
|
1613
|
+
t.push(...s);
|
|
1614
|
+
}), this.logger.info("Events decompressed", {
|
|
1615
|
+
totalEvents: t.length,
|
|
1616
|
+
chunks: e.chunks.length
|
|
1617
|
+
}), t;
|
|
1618
|
+
});
|
|
1619
|
+
}
|
|
1620
|
+
/**
|
|
1621
|
+
* JSON compression - removes whitespace and uses short keys
|
|
1622
|
+
* @private
|
|
1623
|
+
*/
|
|
1624
|
+
_compressJSON(e) {
|
|
1625
|
+
const t = {
|
|
1626
|
+
eventName: "e",
|
|
1627
|
+
blockNumber: "b",
|
|
1628
|
+
transactionHash: "t",
|
|
1629
|
+
address: "a",
|
|
1630
|
+
args: "r",
|
|
1631
|
+
topics: "p",
|
|
1632
|
+
data: "d",
|
|
1633
|
+
logIndex: "l",
|
|
1634
|
+
transactionIndex: "i",
|
|
1635
|
+
blockHash: "h",
|
|
1636
|
+
removed: "m"
|
|
1637
|
+
};
|
|
1638
|
+
return {
|
|
1639
|
+
d: this._replaceKeys(e, t),
|
|
1640
|
+
k: t
|
|
1641
|
+
};
|
|
1642
|
+
}
|
|
1643
|
+
/**
|
|
1644
|
+
* JSON decompression
|
|
1645
|
+
* @private
|
|
1646
|
+
*/
|
|
1647
|
+
_decompressJSON(e) {
|
|
1648
|
+
if (!e.d || !e.k)
|
|
1649
|
+
return e;
|
|
1650
|
+
const t = {};
|
|
1651
|
+
return Object.entries(e.k).forEach(([r, s]) => {
|
|
1652
|
+
t[s] = r;
|
|
1653
|
+
}), this._replaceKeys(e.d, t);
|
|
1654
|
+
}
|
|
1655
|
+
/**
|
|
1656
|
+
* Gzip compression
|
|
1657
|
+
* @private
|
|
1658
|
+
*/
|
|
1659
|
+
_compressGzip(e) {
|
|
1660
|
+
const t = JSON.stringify(e), r = x(t);
|
|
1661
|
+
return F(r);
|
|
1662
|
+
}
|
|
1663
|
+
/**
|
|
1664
|
+
* Gzip decompression
|
|
1665
|
+
* @private
|
|
1666
|
+
*/
|
|
1667
|
+
_decompressGzip(e) {
|
|
1668
|
+
const t = L(e), r = I(t), s = new TextDecoder().decode(r);
|
|
1669
|
+
return JSON.parse(s);
|
|
1670
|
+
}
|
|
1671
|
+
/**
|
|
1672
|
+
* Optimized compression - combines JSON and gzip
|
|
1673
|
+
* @private
|
|
1674
|
+
*/
|
|
1675
|
+
_compressOptimized(e) {
|
|
1676
|
+
const t = this._compressJSON(e);
|
|
1677
|
+
return this._compressGzip(t);
|
|
1678
|
+
}
|
|
1679
|
+
/**
|
|
1680
|
+
* Optimized decompression
|
|
1681
|
+
* @private
|
|
1682
|
+
*/
|
|
1683
|
+
_decompressOptimized(e) {
|
|
1684
|
+
const t = this._decompressGzip(e);
|
|
1685
|
+
return this._decompressJSON(t);
|
|
1686
|
+
}
|
|
1687
|
+
/**
|
|
1688
|
+
* Replace keys in object recursively
|
|
1689
|
+
* @private
|
|
1690
|
+
*/
|
|
1691
|
+
_replaceKeys(e, t) {
|
|
1692
|
+
if (Array.isArray(e))
|
|
1693
|
+
return e.map((r) => this._replaceKeys(r, t));
|
|
1694
|
+
if (e !== null && typeof e == "object") {
|
|
1695
|
+
const r = {};
|
|
1696
|
+
return Object.entries(e).forEach(([s, n]) => {
|
|
1697
|
+
const i = t[s] || s;
|
|
1698
|
+
r[i] = this._replaceKeys(n, t);
|
|
1699
|
+
}), r;
|
|
1700
|
+
}
|
|
1701
|
+
return e;
|
|
1702
|
+
}
|
|
1703
|
+
/**
|
|
1704
|
+
* Calculate size of data in bytes
|
|
1705
|
+
* @private
|
|
1706
|
+
*/
|
|
1707
|
+
_calculateSize(e) {
|
|
1708
|
+
const t = JSON.stringify(e);
|
|
1709
|
+
return k ? Buffer.byteLength(t, "utf8") : new Blob([t]).size;
|
|
1710
|
+
}
|
|
1711
|
+
/**
|
|
1712
|
+
* Analyze compression efficiency for different strategies
|
|
1713
|
+
* @param {any} data - Data to analyze
|
|
1714
|
+
* @returns {Object} Analysis results
|
|
1715
|
+
*/
|
|
1716
|
+
analyzeCompressionStrategies(e) {
|
|
1717
|
+
const t = {};
|
|
1718
|
+
Object.keys(this.strategies).forEach((n) => {
|
|
1719
|
+
try {
|
|
1720
|
+
const i = this.compress(e, n);
|
|
1721
|
+
t[n] = {
|
|
1722
|
+
originalSize: i.metadata.originalSize,
|
|
1723
|
+
compressedSize: i.metadata.compressedSize,
|
|
1724
|
+
compressionRatio: i.metadata.compressionRatio,
|
|
1725
|
+
spaceSaved: i.metadata.spaceSaved,
|
|
1726
|
+
compressionTime: i.metadata.compressionTime
|
|
1727
|
+
};
|
|
1728
|
+
} catch (i) {
|
|
1729
|
+
t[n] = {
|
|
1730
|
+
error: i.message
|
|
1731
|
+
};
|
|
1732
|
+
}
|
|
1733
|
+
});
|
|
1734
|
+
let r = "none", s = 1;
|
|
1735
|
+
return Object.entries(t).forEach(([n, i]) => {
|
|
1736
|
+
!i.error && parseFloat(i.compressionRatio) < s && (s = parseFloat(i.compressionRatio), r = n);
|
|
1737
|
+
}), {
|
|
1738
|
+
strategies: t,
|
|
1739
|
+
recommendation: r,
|
|
1740
|
+
bestCompressionRatio: s
|
|
1741
|
+
};
|
|
1742
|
+
}
|
|
1743
|
+
}
|
|
1744
|
+
class E {
|
|
1745
|
+
constructor(e, t, r = null) {
|
|
1746
|
+
var s, n;
|
|
1747
|
+
this.gun = N(e), this.retryOperation = t, this.networkManager = r, this.logger = new p({
|
|
1748
|
+
context: "EventStore",
|
|
1749
|
+
level: (e == null ? void 0 : e.logLevel) || "info"
|
|
1750
|
+
}), this.eventFilter = new C(this.logger), this.paginator = new P(this.logger), this.dataCompressor = new D(this.logger), this.compressionEnabled = ((s = e == null ? void 0 : e.compression) == null ? void 0 : s.enabled) ?? !0, this.compressionStrategy = ((n = e == null ? void 0 : e.compression) == null ? void 0 : n.strategy) ?? "optimized", this._activeListeners = [], this.logger.info("EventStore initialized", {
|
|
1751
|
+
gunConfig: this._sanitizeGunConfig(e),
|
|
1752
|
+
compressionEnabled: this.compressionEnabled,
|
|
1753
|
+
compressionStrategy: this.compressionStrategy
|
|
1754
|
+
});
|
|
1755
|
+
}
|
|
1756
|
+
// Sanitize Gun config for logging
|
|
1757
|
+
_sanitizeGunConfig(e) {
|
|
1758
|
+
if (!e || typeof e != "object")
|
|
1759
|
+
return e;
|
|
1760
|
+
const t = { ...e };
|
|
1761
|
+
return delete t.peers, delete t.localStorage, t;
|
|
1762
|
+
}
|
|
1763
|
+
// Get the start block number for syncing events from the blockchain
|
|
1764
|
+
async getStartBlockNumber() {
|
|
1765
|
+
return this.retryOperation(async () => new Promise((e) => {
|
|
1766
|
+
this.gun.get("config").get("startBlockNumber").once((t) => {
|
|
1767
|
+
e(t || 1);
|
|
1768
|
+
});
|
|
1769
|
+
}), "Getting start block number");
|
|
1770
|
+
}
|
|
1771
|
+
// Set the start block number for syncing events from the blockchain
|
|
1772
|
+
async setStartBlockNumber(e) {
|
|
1773
|
+
return this.retryOperation(async () => new Promise((t, r) => {
|
|
1774
|
+
this.gun.get("config").get("startBlockNumber").put(e, (s) => {
|
|
1775
|
+
s.err ? r(new Error(s.err)) : t(s);
|
|
1776
|
+
});
|
|
1777
|
+
}), `Setting start block number to ${e}`);
|
|
1778
|
+
}
|
|
1779
|
+
// Store an event in the decentralized database
|
|
1780
|
+
async storeEvent(e, t, r) {
|
|
1781
|
+
return this.logger.time("storeEvent", async () => {
|
|
1782
|
+
this.logger.logDatabaseActivity("storeEvent", {
|
|
1783
|
+
eventName: e,
|
|
1784
|
+
eventKey: t,
|
|
1785
|
+
blockNumber: r.blockNumber,
|
|
1786
|
+
transactionHash: r.transactionHash
|
|
1787
|
+
});
|
|
1788
|
+
let s = r;
|
|
1789
|
+
if (this.compressionEnabled) {
|
|
1790
|
+
const n = this.dataCompressor.compress(r, this.compressionStrategy);
|
|
1791
|
+
s = {
|
|
1792
|
+
_compressed: !0,
|
|
1793
|
+
_data: n.data,
|
|
1794
|
+
_metadata: n.metadata
|
|
1795
|
+
};
|
|
1796
|
+
}
|
|
1797
|
+
return this.retryOperation(
|
|
1798
|
+
() => new Promise((n) => {
|
|
1799
|
+
this.gun.get("events").get(e).get(t).put(s, n);
|
|
1800
|
+
}),
|
|
1801
|
+
`Storing event ${e}`
|
|
1802
|
+
);
|
|
1803
|
+
}, { eventName: e, eventKey: t });
|
|
1804
|
+
}
|
|
1805
|
+
// Get events by name with optional filtering
|
|
1806
|
+
async getEvents(e, t = {}) {
|
|
1807
|
+
return this.logger.time("getEvents", async () => (this.logger.logDatabaseActivity("getEvents", {
|
|
1808
|
+
eventName: e,
|
|
1809
|
+
filter: Object.keys(t)
|
|
1810
|
+
}), this.retryOperation(async () => new Promise((r) => {
|
|
1811
|
+
let s = !1;
|
|
1812
|
+
const n = this.gun.get("events").get(e), i = setTimeout(() => {
|
|
1813
|
+
s || (s = !0, this.logger.debug("getEvents timed out, returning empty", { eventName: e }), r([]));
|
|
1814
|
+
}, 3e3);
|
|
1815
|
+
n.open((a) => {
|
|
1816
|
+
if (s)
|
|
1817
|
+
return;
|
|
1818
|
+
s = !0, clearTimeout(i);
|
|
1819
|
+
const l = [];
|
|
1820
|
+
a && typeof a == "object" && Object.entries(a).forEach(([o, c]) => {
|
|
1821
|
+
o === "_" || !c || typeof c != "object" || this._matchesFilter(c, t) && l.push({ key: o, ...c });
|
|
1822
|
+
}), this.logger.debug("Events retrieved", {
|
|
1823
|
+
eventName: e,
|
|
1824
|
+
eventCount: l.length,
|
|
1825
|
+
filterApplied: Object.keys(t).length > 0
|
|
1826
|
+
}), r(l);
|
|
1827
|
+
});
|
|
1828
|
+
}), `Getting events for ${e}`)), { eventName: e });
|
|
1829
|
+
}
|
|
1830
|
+
// Get all events with optional filtering and pagination
|
|
1831
|
+
async getAllEvents(e = {}) {
|
|
1832
|
+
return this.logger.time("getAllEvents", async () => {
|
|
1833
|
+
const { filter: t = {}, pagination: r = {}, sort: s = {} } = e, n = await this._fetchAllEventsMap(), i = [];
|
|
1834
|
+
Object.entries(n).forEach(([l, o]) => {
|
|
1835
|
+
o.forEach((c) => {
|
|
1836
|
+
i.push({
|
|
1837
|
+
...c,
|
|
1838
|
+
eventName: l
|
|
1839
|
+
});
|
|
1840
|
+
});
|
|
1841
|
+
});
|
|
1842
|
+
let a = i;
|
|
1843
|
+
if (Object.keys(t).length > 0) {
|
|
1844
|
+
const l = this.eventFilter.buildFilter(t);
|
|
1845
|
+
a = this.eventFilter.applyFilter(i, l);
|
|
1846
|
+
}
|
|
1847
|
+
if (s.by && (a = this.eventFilter.sortEvents(a, s)), r.page || r.pageSize) {
|
|
1848
|
+
const l = this.paginator.paginate(a, r);
|
|
1849
|
+
return this.logger.debug("Query completed", {
|
|
1850
|
+
totalEvents: i.length,
|
|
1851
|
+
filteredEvents: a.length,
|
|
1852
|
+
returnedEvents: l.data.length
|
|
1853
|
+
}), l;
|
|
1854
|
+
}
|
|
1855
|
+
return {
|
|
1856
|
+
data: a,
|
|
1857
|
+
meta: {
|
|
1858
|
+
totalItems: a.length
|
|
1859
|
+
}
|
|
1860
|
+
};
|
|
1861
|
+
});
|
|
1862
|
+
}
|
|
1863
|
+
// Internal method to fetch all events as a map
|
|
1864
|
+
async _fetchAllEventsMap() {
|
|
1865
|
+
return this.retryOperation(async () => new Promise((e) => {
|
|
1866
|
+
let t = !1;
|
|
1867
|
+
const r = this.gun.get("events"), s = setTimeout(() => {
|
|
1868
|
+
t || (t = !0, this.logger.debug("_fetchAllEventsMap timed out, returning empty"), e({}));
|
|
1869
|
+
}, 5e3);
|
|
1870
|
+
r.open((n) => {
|
|
1871
|
+
if (t)
|
|
1872
|
+
return;
|
|
1873
|
+
t = !0, clearTimeout(s);
|
|
1874
|
+
const i = {};
|
|
1875
|
+
n && typeof n == "object" && Object.entries(n).forEach(([a, l]) => {
|
|
1876
|
+
a === "_" || !l || typeof l != "object" || (i[a] = [], Object.entries(l).forEach(([o, c]) => {
|
|
1877
|
+
if (o === "_" || !c || typeof c != "object")
|
|
1878
|
+
return;
|
|
1879
|
+
let h = c;
|
|
1880
|
+
if (c._compressed && c._data)
|
|
1881
|
+
try {
|
|
1882
|
+
h = this.dataCompressor.decompress({
|
|
1883
|
+
data: c._data,
|
|
1884
|
+
metadata: c._metadata
|
|
1885
|
+
});
|
|
1886
|
+
} catch (g) {
|
|
1887
|
+
this.logger.error("Failed to decompress event", {
|
|
1888
|
+
eventName: a,
|
|
1889
|
+
key: o,
|
|
1890
|
+
error: g.message
|
|
1891
|
+
});
|
|
1892
|
+
}
|
|
1893
|
+
i[a].push({ key: o, ...h });
|
|
1894
|
+
}));
|
|
1895
|
+
}), e(i);
|
|
1896
|
+
});
|
|
1897
|
+
}), "Getting all events");
|
|
1898
|
+
}
|
|
1899
|
+
// Publish magazine metadata to the decentralized network
|
|
1900
|
+
async publishMagazine(e, t, r) {
|
|
1901
|
+
return this.logger.time("publishMagazine", async () => (this.logger.info("Publishing magazine", {
|
|
1902
|
+
alias: e,
|
|
1903
|
+
url: r,
|
|
1904
|
+
magazineData: {
|
|
1905
|
+
name: t.name,
|
|
1906
|
+
address: t.address,
|
|
1907
|
+
networkId: t.networkId
|
|
1908
|
+
}
|
|
1909
|
+
}), this.retryOperation(async () => {
|
|
1910
|
+
const s = this.gun.back(-1).get(e), n = await new Promise((i, a) => {
|
|
1911
|
+
s.put(t, (l) => {
|
|
1912
|
+
l.err ? (this.logger.error("Failed to publish magazine metadata", {
|
|
1913
|
+
alias: e,
|
|
1914
|
+
error: l.err
|
|
1915
|
+
}), a(new Error(l.err))) : (this.logger.debug("Magazine metadata published", { alias: e }), i(l));
|
|
1916
|
+
});
|
|
1917
|
+
});
|
|
1918
|
+
return await new Promise((i, a) => {
|
|
1919
|
+
s.get("url").put(r, (l) => {
|
|
1920
|
+
l.err ? (this.logger.error("Failed to publish magazine URL", {
|
|
1921
|
+
alias: e,
|
|
1922
|
+
url: r,
|
|
1923
|
+
error: l.err
|
|
1924
|
+
}), a(new Error(l.err))) : (this.logger.debug("Magazine URL published", { alias: e, url: r }), i(l));
|
|
1925
|
+
});
|
|
1926
|
+
}), this.logger.info("Magazine published successfully", { alias: e, url: r }), n;
|
|
1927
|
+
}, "Publishing magazine")), { alias: e });
|
|
1928
|
+
}
|
|
1929
|
+
// Subscribe to a magazine on the decentralized network
|
|
1930
|
+
async subscribeMagazine(e) {
|
|
1931
|
+
return this.retryOperation(async () => new Promise((t, r) => {
|
|
1932
|
+
this.gun.get(e).once((s, n) => {
|
|
1933
|
+
s ? t(s) : r(new Error(`Magazine with ID ${e} not found`));
|
|
1934
|
+
});
|
|
1935
|
+
}), `Subscribing to magazine ${e}`);
|
|
1936
|
+
}
|
|
1937
|
+
// Helper method to check if event data matches filter criteria
|
|
1938
|
+
_matchesFilter(e, t) {
|
|
1939
|
+
if (!t || Object.keys(t).length === 0)
|
|
1940
|
+
return !0;
|
|
1941
|
+
for (const [r, s] of Object.entries(t))
|
|
1942
|
+
if (e[r] !== s)
|
|
1943
|
+
return !1;
|
|
1944
|
+
return !0;
|
|
1945
|
+
}
|
|
1946
|
+
// Query events using fluent API
|
|
1947
|
+
async query() {
|
|
1948
|
+
const e = await this._fetchAllEventsMap(), t = [];
|
|
1949
|
+
return Object.entries(e).forEach(([r, s]) => {
|
|
1950
|
+
s.forEach((n) => {
|
|
1951
|
+
t.push({
|
|
1952
|
+
...n,
|
|
1953
|
+
eventName: r
|
|
1954
|
+
});
|
|
1955
|
+
});
|
|
1956
|
+
}), this.eventFilter.query(t);
|
|
1957
|
+
}
|
|
1958
|
+
// Get events by transaction hash
|
|
1959
|
+
async getEventsByTransactionHash(e) {
|
|
1960
|
+
return this.logger.time("getEventsByTransactionHash", async () => {
|
|
1961
|
+
const t = await this.getAllEvents({
|
|
1962
|
+
filter: { transactionHash: e }
|
|
1963
|
+
});
|
|
1964
|
+
return this.logger.debug("Found events by transaction hash", {
|
|
1965
|
+
transactionHash: e,
|
|
1966
|
+
count: t.data.length
|
|
1967
|
+
}), t.data;
|
|
1968
|
+
});
|
|
1969
|
+
}
|
|
1970
|
+
// Get events by block number
|
|
1971
|
+
async getEventsByBlockNumber(e) {
|
|
1972
|
+
return this.logger.time("getEventsByBlockNumber", async () => {
|
|
1973
|
+
const t = await this.getAllEvents({
|
|
1974
|
+
filter: {
|
|
1975
|
+
fromBlock: e,
|
|
1976
|
+
toBlock: e
|
|
1977
|
+
}
|
|
1978
|
+
});
|
|
1979
|
+
return this.logger.debug("Found events by block number", {
|
|
1980
|
+
blockNumber: e,
|
|
1981
|
+
count: t.data.length
|
|
1982
|
+
}), t.data;
|
|
1983
|
+
});
|
|
1984
|
+
}
|
|
1985
|
+
// Create async iterator for events
|
|
1986
|
+
async *iterateEvents(e = {}) {
|
|
1987
|
+
const { filter: t = {}, sort: r = {}, pageSize: s = 100 } = e, i = (await this.getAllEvents({ filter: t, sort: r })).data;
|
|
1988
|
+
yield* this.paginator.iterate(i, { pageSize: s });
|
|
1989
|
+
}
|
|
1990
|
+
// Get compression statistics
|
|
1991
|
+
async getCompressionStats() {
|
|
1992
|
+
const e = await this._fetchAllEventsMap();
|
|
1993
|
+
let t = 0, r = 0, s = 0;
|
|
1994
|
+
return Object.values(e).forEach((n) => {
|
|
1995
|
+
n.forEach((i) => {
|
|
1996
|
+
i._compressed && i._metadata && (t += i._metadata.originalSize || 0, r += i._metadata.compressedSize || 0, s++);
|
|
1997
|
+
});
|
|
1998
|
+
}), {
|
|
1999
|
+
compressedEvents: s,
|
|
2000
|
+
totalOriginalSize: t,
|
|
2001
|
+
totalCompressedSize: r,
|
|
2002
|
+
compressionRatio: t > 0 ? (r / t).toFixed(3) : "1.000",
|
|
2003
|
+
spaceSaved: t > 0 ? `${((1 - r / t) * 100).toFixed(1)}%` : "0%"
|
|
2004
|
+
};
|
|
2005
|
+
}
|
|
2006
|
+
// Clean up all active listeners
|
|
2007
|
+
destroy() {
|
|
2008
|
+
this._activeListeners = [], this.logger.info("EventStore destroyed, listeners cleaned up");
|
|
2009
|
+
}
|
|
2010
|
+
// Get Gun instance for advanced operations
|
|
2011
|
+
getGunInstance() {
|
|
2012
|
+
return this.gun;
|
|
2013
|
+
}
|
|
2014
|
+
}
|
|
2015
|
+
class T {
|
|
2016
|
+
constructor(e, t, r, s) {
|
|
2017
|
+
this.networkManager = e, this.eventStore = t, this.configManager = r, this.retryOperation = s, this.logger = new p({
|
|
2018
|
+
context: "EventSyncer",
|
|
2019
|
+
level: r.get("logLevel") || "info"
|
|
2020
|
+
}), this.batchSize = 1e3, this.lastSyncTime = 0, this.logger.info("EventSyncer initialized", {
|
|
2021
|
+
batchSize: this.batchSize
|
|
2022
|
+
});
|
|
2023
|
+
}
|
|
2024
|
+
// Sync events from the blockchain to the magazine
|
|
2025
|
+
async syncEvents() {
|
|
2026
|
+
if (this._syncing)
|
|
2027
|
+
return this.logger.warn("Sync already in progress, skipping"), 0;
|
|
2028
|
+
this._setSyncing(!0);
|
|
2029
|
+
try {
|
|
2030
|
+
return await this.logger.time("syncEvents", async () => this.retryOperation(async () => {
|
|
2031
|
+
const e = await this.eventStore.getStartBlockNumber(), t = await this.networkManager.getLatestBlockNumber();
|
|
2032
|
+
if (e > t) {
|
|
2033
|
+
this.logger.info("Already up to date", {
|
|
2034
|
+
startBlockNumber: e,
|
|
2035
|
+
latestBlockNumber: t
|
|
2036
|
+
});
|
|
2037
|
+
return;
|
|
2038
|
+
}
|
|
2039
|
+
this.logger.info("Starting event synchronization", {
|
|
2040
|
+
startBlockNumber: e,
|
|
2041
|
+
latestBlockNumber: t,
|
|
2042
|
+
totalBlocks: t - e + 1
|
|
2043
|
+
});
|
|
2044
|
+
let r = e, s = 0;
|
|
2045
|
+
for (; r <= t; ) {
|
|
2046
|
+
const n = Math.min(r + this.batchSize - 1, t), i = await this._processBatch(r, n);
|
|
2047
|
+
s += i, r = n + 1;
|
|
2048
|
+
}
|
|
2049
|
+
return await this.eventStore.setStartBlockNumber(t + 1), this.lastSyncTime = Date.now(), this.logger.info("Event synchronization completed", {
|
|
2050
|
+
totalEventsProcessed: s,
|
|
2051
|
+
newStartBlockNumber: t + 1
|
|
2052
|
+
}), s;
|
|
2053
|
+
}, "Event synchronization"));
|
|
2054
|
+
} finally {
|
|
2055
|
+
this._setSyncing(!1);
|
|
2056
|
+
}
|
|
2057
|
+
}
|
|
2058
|
+
// Process a batch of blocks for events
|
|
2059
|
+
async _processBatch(e, t) {
|
|
2060
|
+
return this.logger.time("processBatch", async () => {
|
|
2061
|
+
this.logger.debug("Processing batch", { fromBlock: e, toBlock: t });
|
|
2062
|
+
const r = await this.networkManager.getLogs(e, t);
|
|
2063
|
+
let s = 0;
|
|
2064
|
+
for (const n of r) {
|
|
2065
|
+
const i = this.networkManager.parseLog(n);
|
|
2066
|
+
if (i)
|
|
2067
|
+
try {
|
|
2068
|
+
const a = this.networkManager.createEventKey(n);
|
|
2069
|
+
await this.eventStore.storeEvent(
|
|
2070
|
+
i.eventName,
|
|
2071
|
+
a,
|
|
2072
|
+
i.eventData
|
|
2073
|
+
), s++, this.logger.trace("Event stored", {
|
|
2074
|
+
eventName: i.eventName,
|
|
2075
|
+
eventKey: a,
|
|
2076
|
+
blockNumber: n.blockNumber
|
|
2077
|
+
});
|
|
2078
|
+
} catch (a) {
|
|
2079
|
+
this.logger.warn("Failed to store event", {
|
|
2080
|
+
eventName: i.eventName,
|
|
2081
|
+
blockNumber: n.blockNumber,
|
|
2082
|
+
transactionHash: n.transactionHash,
|
|
2083
|
+
error: a.message
|
|
2084
|
+
});
|
|
2085
|
+
}
|
|
2086
|
+
}
|
|
2087
|
+
return this.logger.debug("Batch processing completed", {
|
|
2088
|
+
fromBlock: e,
|
|
2089
|
+
toBlock: t,
|
|
2090
|
+
logsFound: r.length,
|
|
2091
|
+
eventsStored: s
|
|
2092
|
+
}), s;
|
|
2093
|
+
}, { fromBlock: e, toBlock: t });
|
|
2094
|
+
}
|
|
2095
|
+
// Sync events from a specific block range
|
|
2096
|
+
async syncEventsFromRange(e, t) {
|
|
2097
|
+
if (this.configManager.validateBlockNumber(e, "fromBlock"), this.configManager.validateBlockNumber(t, "toBlock"), e > t)
|
|
2098
|
+
throw new Error("fromBlock cannot be greater than toBlock");
|
|
2099
|
+
if (this._syncing)
|
|
2100
|
+
return this.logger.warn("Sync already in progress, skipping range sync"), 0;
|
|
2101
|
+
this._setSyncing(!0);
|
|
2102
|
+
try {
|
|
2103
|
+
return await this.logger.time("syncEventsFromRange", async () => this.retryOperation(async () => {
|
|
2104
|
+
this.logger.info("Syncing events from specific range", {
|
|
2105
|
+
fromBlock: e,
|
|
2106
|
+
toBlock: t,
|
|
2107
|
+
totalBlocks: t - e + 1
|
|
2108
|
+
});
|
|
2109
|
+
let r = e, s = 0;
|
|
2110
|
+
for (; r <= t; ) {
|
|
2111
|
+
const n = Math.min(r + this.batchSize - 1, t), i = await this._processBatch(r, n);
|
|
2112
|
+
s += i, r = n + 1;
|
|
2113
|
+
}
|
|
2114
|
+
return this.lastSyncTime = Date.now(), this.logger.info("Range synchronization completed", {
|
|
2115
|
+
fromBlock: e,
|
|
2116
|
+
toBlock: t,
|
|
2117
|
+
totalEventsProcessed: s
|
|
2118
|
+
}), s;
|
|
2119
|
+
}, `Event synchronization for range ${e}-${t}`), { fromBlock: e, toBlock: t });
|
|
2120
|
+
} finally {
|
|
2121
|
+
this._setSyncing(!1);
|
|
2122
|
+
}
|
|
2123
|
+
}
|
|
2124
|
+
// Sync events from a specific transaction
|
|
2125
|
+
async syncEventsFromTransaction(e) {
|
|
2126
|
+
if (!e || typeof e != "string")
|
|
2127
|
+
throw new Error("Transaction hash must be a non-empty string");
|
|
2128
|
+
return this.retryOperation(async () => {
|
|
2129
|
+
const t = await this.networkManager.getTransactionReceipt(e);
|
|
2130
|
+
if (!t)
|
|
2131
|
+
throw new Error(`Transaction ${e} not found`);
|
|
2132
|
+
const r = this.configManager.get("address").toLowerCase(), s = t.logs.filter(
|
|
2133
|
+
(n) => n.address.toLowerCase() === r
|
|
2134
|
+
);
|
|
2135
|
+
for (const n of s) {
|
|
2136
|
+
const i = this.networkManager.parseLog(n);
|
|
2137
|
+
if (i) {
|
|
2138
|
+
const a = this.networkManager.createEventKey(n);
|
|
2139
|
+
await this.eventStore.storeEvent(
|
|
2140
|
+
i.eventName,
|
|
2141
|
+
a,
|
|
2142
|
+
i.eventData
|
|
2143
|
+
);
|
|
2144
|
+
}
|
|
2145
|
+
}
|
|
2146
|
+
return this.lastSyncTime = Date.now(), s.length;
|
|
2147
|
+
}, `Syncing events from transaction ${e}`);
|
|
2148
|
+
}
|
|
2149
|
+
// Get sync status information
|
|
2150
|
+
async getSyncStatus() {
|
|
2151
|
+
return this.retryOperation(async () => {
|
|
2152
|
+
const e = await this.eventStore.getStartBlockNumber(), t = await this.networkManager.getLatestBlockNumber(), r = await this.networkManager.getNetworkInfo();
|
|
2153
|
+
return {
|
|
2154
|
+
nextSyncBlock: e,
|
|
2155
|
+
latestNetworkBlock: t,
|
|
2156
|
+
blocksBehind: Math.max(0, t - e + 1),
|
|
2157
|
+
networkInfo: r,
|
|
2158
|
+
isUpToDate: e > t
|
|
2159
|
+
};
|
|
2160
|
+
}, "Getting sync status");
|
|
2161
|
+
}
|
|
2162
|
+
// Validate network and contract before syncing
|
|
2163
|
+
async validateBeforeSync() {
|
|
2164
|
+
return await this.networkManager.validateNetwork(), await this.networkManager.validateContract(), !0;
|
|
2165
|
+
}
|
|
2166
|
+
// Set batch size for processing (useful for performance tuning)
|
|
2167
|
+
setBatchSize(e) {
|
|
2168
|
+
if (!Number.isInteger(e) || e < 1)
|
|
2169
|
+
throw new Error("Batch size must be a positive integer");
|
|
2170
|
+
this.batchSize = e;
|
|
2171
|
+
}
|
|
2172
|
+
// Get current batch size
|
|
2173
|
+
getBatchSize() {
|
|
2174
|
+
return this.batchSize;
|
|
2175
|
+
}
|
|
2176
|
+
// Reset sync position to a specific block
|
|
2177
|
+
async resetSyncPosition(e) {
|
|
2178
|
+
this.configManager.validateBlockNumber(e, "blockNumber"), await this.eventStore.setStartBlockNumber(e);
|
|
2179
|
+
}
|
|
2180
|
+
// Get events count by name
|
|
2181
|
+
async getEventCount(e) {
|
|
2182
|
+
return (await this.eventStore.getEvents(e)).length;
|
|
2183
|
+
}
|
|
2184
|
+
/**
|
|
2185
|
+
* Start real-time event streaming via WebSocket or EventEmitter provider.
|
|
2186
|
+
*/
|
|
2187
|
+
startRealtimeSync() {
|
|
2188
|
+
const e = this.networkManager.getProvider();
|
|
2189
|
+
if (!e || typeof e.on != "function")
|
|
2190
|
+
throw new Error(
|
|
2191
|
+
"Real-time sync requires a provider that supports event subscriptions (e.g., WebSocketProvider). The current provider does not have an .on() method."
|
|
2192
|
+
);
|
|
2193
|
+
if (this._realtimeListener) {
|
|
2194
|
+
this.logger.warn("Real-time sync is already running, call stopRealtimeSync() first");
|
|
2195
|
+
return;
|
|
2196
|
+
}
|
|
2197
|
+
const t = this.configManager.get("address"), r = { address: t };
|
|
2198
|
+
this.logger.info("Starting real-time event sync", {
|
|
2199
|
+
contractAddress: t,
|
|
2200
|
+
providerType: e.constructor.name
|
|
2201
|
+
});
|
|
2202
|
+
const s = async (n) => {
|
|
2203
|
+
try {
|
|
2204
|
+
const i = this.networkManager.parseLog(n);
|
|
2205
|
+
if (i) {
|
|
2206
|
+
const a = this.networkManager.createEventKey(n);
|
|
2207
|
+
await this.eventStore.storeEvent(
|
|
2208
|
+
i.eventName,
|
|
2209
|
+
a,
|
|
2210
|
+
i.eventData
|
|
2211
|
+
), this.logger.debug("Real-time event stored", {
|
|
2212
|
+
eventName: i.eventName,
|
|
2213
|
+
eventKey: a,
|
|
2214
|
+
blockNumber: n.blockNumber
|
|
2215
|
+
});
|
|
2216
|
+
}
|
|
2217
|
+
} catch (i) {
|
|
2218
|
+
this.logger.warn("Failed to process real-time event", {
|
|
2219
|
+
blockNumber: n.blockNumber,
|
|
2220
|
+
error: i.message
|
|
2221
|
+
});
|
|
2222
|
+
}
|
|
2223
|
+
};
|
|
2224
|
+
e.on(r, s), this._realtimeListener = s, this._realtimeFilter = r, this.logger.info("Real-time event sync started");
|
|
2225
|
+
}
|
|
2226
|
+
/**
|
|
2227
|
+
* Stop real-time event streaming and clean up the listener.
|
|
2228
|
+
*/
|
|
2229
|
+
stopRealtimeSync() {
|
|
2230
|
+
if (!this._realtimeListener) {
|
|
2231
|
+
this.logger.debug("No real-time sync listener to stop");
|
|
2232
|
+
return;
|
|
2233
|
+
}
|
|
2234
|
+
const e = this.networkManager.getProvider();
|
|
2235
|
+
e && typeof e.off == "function" && (e.off(this._realtimeFilter, this._realtimeListener), this.logger.info("Real-time event sync stopped")), this._realtimeListener = null, this._realtimeFilter = null;
|
|
2236
|
+
}
|
|
2237
|
+
// Check if syncer is busy (for preventing concurrent syncs)
|
|
2238
|
+
isSyncing() {
|
|
2239
|
+
return this._syncing || !1;
|
|
2240
|
+
}
|
|
2241
|
+
// Set syncing state
|
|
2242
|
+
_setSyncing(e) {
|
|
2243
|
+
this._syncing = e;
|
|
2244
|
+
}
|
|
2245
|
+
}
|
|
2246
|
+
class A {
|
|
2247
|
+
constructor(e, t) {
|
|
2248
|
+
this.networkManager = e, this.logger = t.child({ context: "EventMetadata" }), this.logger.info("EventMetadata initialized");
|
|
2249
|
+
}
|
|
2250
|
+
/**
|
|
2251
|
+
* Enrich event with metadata
|
|
2252
|
+
* @param {Object} event - Event to enrich
|
|
2253
|
+
* @param {Object} options - Enrichment options
|
|
2254
|
+
* @param {boolean} options.includeBlock - Include block data
|
|
2255
|
+
* @param {boolean} options.includeTransaction - Include transaction data
|
|
2256
|
+
* @param {boolean} options.includeTimestamp - Include block timestamp
|
|
2257
|
+
* @param {boolean} options.includeGasCost - Include gas cost
|
|
2258
|
+
* @returns {Promise<Object>} Enriched event
|
|
2259
|
+
*/
|
|
2260
|
+
async enrichEvent(e, t = {}) {
|
|
2261
|
+
const {
|
|
2262
|
+
includeBlock: r = !0,
|
|
2263
|
+
includeTransaction: s = !0,
|
|
2264
|
+
includeTimestamp: n = !0,
|
|
2265
|
+
includeGasCost: i = !0
|
|
2266
|
+
} = t;
|
|
2267
|
+
try {
|
|
2268
|
+
const a = { ...e }, l = [];
|
|
2269
|
+
return (r || n) && e.blockNumber && l.push(
|
|
2270
|
+
this.networkManager.provider.getBlock(e.blockNumber).then((o) => {
|
|
2271
|
+
var c;
|
|
2272
|
+
o && (r && (a.block = {
|
|
2273
|
+
number: o.number,
|
|
2274
|
+
hash: o.hash,
|
|
2275
|
+
parentHash: o.parentHash,
|
|
2276
|
+
miner: o.miner,
|
|
2277
|
+
difficulty: (c = o.difficulty) == null ? void 0 : c.toString(),
|
|
2278
|
+
gasLimit: o.gasLimit.toString(),
|
|
2279
|
+
gasUsed: o.gasUsed.toString()
|
|
2280
|
+
}), n && (a.timestamp = o.timestamp, a.date = new Date(o.timestamp * 1e3).toISOString()));
|
|
2281
|
+
}).catch((o) => {
|
|
2282
|
+
this.logger.warn("Failed to fetch block data", {
|
|
2283
|
+
blockNumber: e.blockNumber,
|
|
2284
|
+
error: o.message
|
|
2285
|
+
});
|
|
2286
|
+
})
|
|
2287
|
+
), (s || i) && e.transactionHash && l.push(
|
|
2288
|
+
Promise.all([
|
|
2289
|
+
this.networkManager.provider.getTransaction(e.transactionHash),
|
|
2290
|
+
i ? this.networkManager.provider.getTransactionReceipt(e.transactionHash) : null
|
|
2291
|
+
]).then(([o, c]) => {
|
|
2292
|
+
var h, g, d;
|
|
2293
|
+
if (o && (s && (a.transaction = {
|
|
2294
|
+
hash: o.hash,
|
|
2295
|
+
from: o.from,
|
|
2296
|
+
to: o.to,
|
|
2297
|
+
value: o.value.toString(),
|
|
2298
|
+
nonce: o.nonce,
|
|
2299
|
+
gasLimit: o.gasLimit.toString(),
|
|
2300
|
+
gasPrice: (h = o.gasPrice) == null ? void 0 : h.toString(),
|
|
2301
|
+
maxFeePerGas: (g = o.maxFeePerGas) == null ? void 0 : g.toString(),
|
|
2302
|
+
maxPriorityFeePerGas: (d = o.maxPriorityFeePerGas) == null ? void 0 : d.toString(),
|
|
2303
|
+
data: o.data.length > 66 ? o.data.substring(0, 66) + "..." : o.data
|
|
2304
|
+
}), i && c)) {
|
|
2305
|
+
const m = c.gasUsed, y = o.gasPrice || o.maxFeePerGas || 0n, w = m * y;
|
|
2306
|
+
a.gas = {
|
|
2307
|
+
used: m.toString(),
|
|
2308
|
+
price: y.toString(),
|
|
2309
|
+
cost: w.toString(),
|
|
2310
|
+
costInEther: this._formatEther(w)
|
|
2311
|
+
};
|
|
2312
|
+
}
|
|
2313
|
+
}).catch((o) => {
|
|
2314
|
+
this.logger.warn("Failed to fetch transaction data", {
|
|
2315
|
+
transactionHash: e.transactionHash,
|
|
2316
|
+
error: o.message
|
|
2317
|
+
});
|
|
2318
|
+
})
|
|
2319
|
+
), await Promise.all(l), a._metadata = {
|
|
2320
|
+
enrichedAt: new Date().toISOString(),
|
|
2321
|
+
enrichmentOptions: t
|
|
2322
|
+
}, this.logger.debug("Event enriched with metadata", {
|
|
2323
|
+
eventName: e.eventName,
|
|
2324
|
+
blockNumber: e.blockNumber,
|
|
2325
|
+
metadataAdded: Object.keys(a).filter((o) => !e.hasOwnProperty(o))
|
|
2326
|
+
}), a;
|
|
2327
|
+
} catch (a) {
|
|
2328
|
+
return this.logger.error("Failed to enrich event", {
|
|
2329
|
+
event: e,
|
|
2330
|
+
error: a.message
|
|
2331
|
+
}), e;
|
|
2332
|
+
}
|
|
2333
|
+
}
|
|
2334
|
+
/**
|
|
2335
|
+
* Enrich multiple events in batch
|
|
2336
|
+
* @param {Array} events - Events to enrich
|
|
2337
|
+
* @param {Object} options - Enrichment options
|
|
2338
|
+
* @param {number} options.batchSize - Number of events to process concurrently
|
|
2339
|
+
* @returns {Promise<Array>} Enriched events
|
|
2340
|
+
*/
|
|
2341
|
+
async enrichEvents(e, t = {}) {
|
|
2342
|
+
const { batchSize: r = 10, ...s } = t;
|
|
2343
|
+
return this.logger.time("enrichEvents", async () => {
|
|
2344
|
+
var a, l;
|
|
2345
|
+
const n = [];
|
|
2346
|
+
let i = 0;
|
|
2347
|
+
for (let o = 0; o < e.length; o += r) {
|
|
2348
|
+
const c = e.slice(o, o + r), h = await Promise.allSettled(
|
|
2349
|
+
c.map((g) => this.enrichEvent(g, s))
|
|
2350
|
+
);
|
|
2351
|
+
for (let g = 0; g < h.length; g++)
|
|
2352
|
+
h[g].status === "fulfilled" ? n.push(h[g].value) : (i++, this.logger.warn("Failed to enrich event, using original", {
|
|
2353
|
+
error: (a = h[g].reason) == null ? void 0 : a.message,
|
|
2354
|
+
blockNumber: (l = c[g]) == null ? void 0 : l.blockNumber
|
|
2355
|
+
}), n.push(c[g]));
|
|
2356
|
+
this.logger.debug("Enriched event batch", {
|
|
2357
|
+
batchIndex: Math.floor(o / r),
|
|
2358
|
+
batchSize: c.length,
|
|
2359
|
+
failures: i,
|
|
2360
|
+
totalProgress: `${n.length}/${e.length}`
|
|
2361
|
+
});
|
|
2362
|
+
}
|
|
2363
|
+
return this.logger.info("Completed event enrichment", {
|
|
2364
|
+
totalEvents: e.length,
|
|
2365
|
+
enrichedEvents: n.length,
|
|
2366
|
+
failures: i
|
|
2367
|
+
}), n;
|
|
2368
|
+
});
|
|
2369
|
+
}
|
|
2370
|
+
/**
|
|
2371
|
+
* Calculate statistics for a set of events
|
|
2372
|
+
* @param {Array} events - Events to analyze
|
|
2373
|
+
* @returns {Object} Event statistics
|
|
2374
|
+
*/
|
|
2375
|
+
calculateStatistics(e) {
|
|
2376
|
+
if (!Array.isArray(e) || e.length === 0)
|
|
2377
|
+
return {
|
|
2378
|
+
count: 0,
|
|
2379
|
+
blockRange: { min: null, max: null },
|
|
2380
|
+
timeRange: { min: null, max: null },
|
|
2381
|
+
gasStats: { total: "0", average: "0", min: "0", max: "0" }
|
|
2382
|
+
};
|
|
2383
|
+
const t = e.map((a) => a.blockNumber).filter((a) => a != null).sort((a, l) => a - l), r = e.map((a) => a.timestamp).filter((a) => a != null).sort((a, l) => a - l), s = e.map((a) => {
|
|
2384
|
+
var l;
|
|
2385
|
+
return (l = a.gas) == null ? void 0 : l.cost;
|
|
2386
|
+
}).filter((a) => a != null).map((a) => BigInt(a)), n = {
|
|
2387
|
+
count: e.length,
|
|
2388
|
+
blockRange: {
|
|
2389
|
+
min: t[0] || null,
|
|
2390
|
+
max: t[t.length - 1] || null,
|
|
2391
|
+
span: t.length > 0 ? t[t.length - 1] - t[0] : 0
|
|
2392
|
+
},
|
|
2393
|
+
timeRange: {
|
|
2394
|
+
min: r[0] || null,
|
|
2395
|
+
max: r[r.length - 1] || null,
|
|
2396
|
+
span: r.length > 0 ? r[r.length - 1] - r[0] : 0
|
|
2397
|
+
}
|
|
2398
|
+
};
|
|
2399
|
+
if (s.length > 0) {
|
|
2400
|
+
const a = s.reduce((c, h) => c + h, 0n), l = a / BigInt(s.length), o = [...s].sort((c, h) => c < h ? -1 : 1);
|
|
2401
|
+
n.gasStats = {
|
|
2402
|
+
total: a.toString(),
|
|
2403
|
+
average: l.toString(),
|
|
2404
|
+
min: o[0].toString(),
|
|
2405
|
+
max: o[o.length - 1].toString(),
|
|
2406
|
+
totalInEther: this._formatEther(a)
|
|
2407
|
+
};
|
|
2408
|
+
} else
|
|
2409
|
+
n.gasStats = { total: "0", average: "0", min: "0", max: "0" };
|
|
2410
|
+
const i = {};
|
|
2411
|
+
return e.forEach((a) => {
|
|
2412
|
+
a.eventName && (i[a.eventName] = (i[a.eventName] || 0) + 1);
|
|
2413
|
+
}), n.eventTypes = i, this.logger.debug("Calculated event statistics", n), n;
|
|
2414
|
+
}
|
|
2415
|
+
/**
|
|
2416
|
+
* Format wei to ether string
|
|
2417
|
+
* @private
|
|
2418
|
+
*/
|
|
2419
|
+
_formatEther(e) {
|
|
2420
|
+
try {
|
|
2421
|
+
const t = e.toString();
|
|
2422
|
+
return (Number(t) / 1e18).toFixed(6) + " ETH";
|
|
2423
|
+
} catch {
|
|
2424
|
+
return "0 ETH";
|
|
2425
|
+
}
|
|
2426
|
+
}
|
|
2427
|
+
}
|
|
2428
|
+
class R {
|
|
2429
|
+
constructor(e, t) {
|
|
2430
|
+
this.magazine = e, this.logger = t.child({ context: "DebugUtils" }), this.logger.info("DebugUtils initialized"), this.debugMode = !1, this.metrics = {
|
|
2431
|
+
syncOperations: [],
|
|
2432
|
+
queryOperations: [],
|
|
2433
|
+
storageOperations: [],
|
|
2434
|
+
networkCalls: []
|
|
2435
|
+
}, this._logBuffer = [], this._logBufferMax = 200, this._originalMethods = /* @__PURE__ */ new Map(), this._verboseErrors = !1, this.healthThresholds = {
|
|
2436
|
+
syncDelay: 3e5,
|
|
2437
|
+
// 5 minutes
|
|
2438
|
+
errorRate: 0.1,
|
|
2439
|
+
// 10%
|
|
2440
|
+
responseTime: 5e3,
|
|
2441
|
+
// 5 seconds
|
|
2442
|
+
storageUsage: 0.9
|
|
2443
|
+
// 90%
|
|
2444
|
+
};
|
|
2445
|
+
}
|
|
2446
|
+
/**
|
|
2447
|
+
* Perform comprehensive health check
|
|
2448
|
+
* @returns {Promise<Object>} Health check results
|
|
2449
|
+
*/
|
|
2450
|
+
async healthCheck() {
|
|
2451
|
+
const e = Date.now(), t = {
|
|
2452
|
+
status: "healthy",
|
|
2453
|
+
timestamp: new Date().toISOString(),
|
|
2454
|
+
checks: {},
|
|
2455
|
+
issues: []
|
|
2456
|
+
};
|
|
2457
|
+
try {
|
|
2458
|
+
t.checks.network = await this._checkNetwork(), t.checks.database = await this._checkDatabase(), t.checks.sync = await this._checkSyncStatus(), t.checks.storage = await this._checkStorage(), t.checks.performance = this._checkPerformance();
|
|
2459
|
+
const r = [];
|
|
2460
|
+
return Object.entries(t.checks).forEach(([s, n]) => {
|
|
2461
|
+
n.status !== "healthy" && r.push({
|
|
2462
|
+
check: s,
|
|
2463
|
+
status: n.status,
|
|
2464
|
+
message: n.message
|
|
2465
|
+
});
|
|
2466
|
+
}), r.length > 0 && (t.status = r.some((s) => s.status === "critical") ? "critical" : "degraded", t.issues = r), t.duration = Date.now() - e, this.logger.info("Health check completed", {
|
|
2467
|
+
status: t.status,
|
|
2468
|
+
duration: t.duration,
|
|
2469
|
+
issueCount: r.length
|
|
2470
|
+
}), t;
|
|
2471
|
+
} catch (r) {
|
|
2472
|
+
return this.logger.error("Health check failed", { error: r.message }), {
|
|
2473
|
+
status: "error",
|
|
2474
|
+
timestamp: new Date().toISOString(),
|
|
2475
|
+
error: r.message,
|
|
2476
|
+
duration: Date.now() - e
|
|
2477
|
+
};
|
|
2478
|
+
}
|
|
2479
|
+
}
|
|
2480
|
+
/**
|
|
2481
|
+
* Get debug information about the current state
|
|
2482
|
+
* @returns {Promise<Object>} Debug information
|
|
2483
|
+
*/
|
|
2484
|
+
async getDebugInfo() {
|
|
2485
|
+
const e = {
|
|
2486
|
+
timestamp: new Date().toISOString(),
|
|
2487
|
+
configuration: this._getConfigInfo(),
|
|
2488
|
+
network: await this._getNetworkInfo(),
|
|
2489
|
+
storage: await this._getStorageInfo(),
|
|
2490
|
+
performance: this._getPerformanceInfo(),
|
|
2491
|
+
logs: this._getRecentLogs()
|
|
2492
|
+
};
|
|
2493
|
+
return this.logger.debug("Debug info generated"), e;
|
|
2494
|
+
}
|
|
2495
|
+
/**
|
|
2496
|
+
* Record a metric
|
|
2497
|
+
* @param {string} type - Metric type
|
|
2498
|
+
* @param {Object} data - Metric data
|
|
2499
|
+
*/
|
|
2500
|
+
recordMetric(e, t) {
|
|
2501
|
+
const r = {
|
|
2502
|
+
timestamp: Date.now(),
|
|
2503
|
+
...t
|
|
2504
|
+
};
|
|
2505
|
+
this.metrics[e] && (this.metrics[e].push(r), this.metrics[e].length > 1e3 && (this.metrics[e] = this.metrics[e].slice(-1e3)));
|
|
2506
|
+
}
|
|
2507
|
+
/**
|
|
2508
|
+
* Get performance report
|
|
2509
|
+
* @param {Object} options - Report options
|
|
2510
|
+
* @returns {Object} Performance report
|
|
2511
|
+
*/
|
|
2512
|
+
getPerformanceReport(e = {}) {
|
|
2513
|
+
const {
|
|
2514
|
+
timeRange: t = 36e5,
|
|
2515
|
+
// Last hour
|
|
2516
|
+
aggregation: r = "average"
|
|
2517
|
+
} = e, s = Date.now(), n = s - t, i = {};
|
|
2518
|
+
return Object.entries(this.metrics).forEach(([a, l]) => {
|
|
2519
|
+
const o = l.filter((g) => g.timestamp >= n);
|
|
2520
|
+
if (o.length === 0) {
|
|
2521
|
+
i[a] = { count: 0, average: 0, min: 0, max: 0 };
|
|
2522
|
+
return;
|
|
2523
|
+
}
|
|
2524
|
+
const c = o.map((g) => g.duration || 0), h = o.filter((g) => g.error).length;
|
|
2525
|
+
i[a] = {
|
|
2526
|
+
count: o.length,
|
|
2527
|
+
errors: h,
|
|
2528
|
+
errorRate: (h / o.length * 100).toFixed(2) + "%",
|
|
2529
|
+
average: Math.round(c.reduce((g, d) => g + d, 0) / c.length),
|
|
2530
|
+
min: Math.min(...c),
|
|
2531
|
+
max: Math.max(...c),
|
|
2532
|
+
p95: this._calculatePercentile(c, 95),
|
|
2533
|
+
p99: this._calculatePercentile(c, 99)
|
|
2534
|
+
};
|
|
2535
|
+
}), {
|
|
2536
|
+
timeRange: {
|
|
2537
|
+
start: new Date(n).toISOString(),
|
|
2538
|
+
end: new Date(s).toISOString(),
|
|
2539
|
+
duration: t
|
|
2540
|
+
},
|
|
2541
|
+
metrics: i
|
|
2542
|
+
};
|
|
2543
|
+
}
|
|
2544
|
+
/**
|
|
2545
|
+
* Enable debug mode
|
|
2546
|
+
* @param {Object} options - Debug options
|
|
2547
|
+
*/
|
|
2548
|
+
enableDebugMode(e = {}) {
|
|
2549
|
+
const {
|
|
2550
|
+
logLevel: t = "debug",
|
|
2551
|
+
captureMetrics: r = !0,
|
|
2552
|
+
verboseErrors: s = !0
|
|
2553
|
+
} = e;
|
|
2554
|
+
this.magazine.setLogLevel(t), r && this._instrumentMethods(), s && this._enableVerboseErrors(), this.logger.info("Debug mode enabled", e);
|
|
2555
|
+
}
|
|
2556
|
+
/**
|
|
2557
|
+
* Export debug data
|
|
2558
|
+
* @returns {Promise<Object>} Exportable debug data
|
|
2559
|
+
*/
|
|
2560
|
+
async exportDebugData() {
|
|
2561
|
+
const e = {
|
|
2562
|
+
version: "1.0.0",
|
|
2563
|
+
exportedAt: new Date().toISOString(),
|
|
2564
|
+
debugInfo: await this.getDebugInfo(),
|
|
2565
|
+
healthCheck: await this.healthCheck(),
|
|
2566
|
+
performanceReport: this.getPerformanceReport(),
|
|
2567
|
+
recentMetrics: {
|
|
2568
|
+
sync: this.metrics.syncOperations.slice(-100),
|
|
2569
|
+
query: this.metrics.queryOperations.slice(-100),
|
|
2570
|
+
storage: this.metrics.storageOperations.slice(-100),
|
|
2571
|
+
network: this.metrics.networkCalls.slice(-100)
|
|
2572
|
+
}
|
|
2573
|
+
};
|
|
2574
|
+
return this.logger.info("Debug data exported"), e;
|
|
2575
|
+
}
|
|
2576
|
+
/**
|
|
2577
|
+
* Check network connectivity
|
|
2578
|
+
* @private
|
|
2579
|
+
*/
|
|
2580
|
+
async _checkNetwork() {
|
|
2581
|
+
try {
|
|
2582
|
+
const e = Date.now(), t = await this.magazine.getLatestBlockNumber(), r = Date.now() - e;
|
|
2583
|
+
return {
|
|
2584
|
+
status: r < this.healthThresholds.responseTime ? "healthy" : "degraded",
|
|
2585
|
+
latestBlock: t,
|
|
2586
|
+
responseTime: r,
|
|
2587
|
+
message: `Network response time: ${r}ms`
|
|
2588
|
+
};
|
|
2589
|
+
} catch (e) {
|
|
2590
|
+
return {
|
|
2591
|
+
status: "critical",
|
|
2592
|
+
error: e.message,
|
|
2593
|
+
message: "Network connection failed"
|
|
2594
|
+
};
|
|
2595
|
+
}
|
|
2596
|
+
}
|
|
2597
|
+
/**
|
|
2598
|
+
* Check database connectivity
|
|
2599
|
+
* @private
|
|
2600
|
+
*/
|
|
2601
|
+
async _checkDatabase() {
|
|
2602
|
+
try {
|
|
2603
|
+
const e = Date.now(), t = this.magazine.eventStore.getGunInstance();
|
|
2604
|
+
await new Promise((s, n) => {
|
|
2605
|
+
t.get("_health_check").put({
|
|
2606
|
+
timestamp: Date.now()
|
|
2607
|
+
}, (i) => {
|
|
2608
|
+
i.err ? n(new Error(i.err)) : s(i);
|
|
2609
|
+
});
|
|
2610
|
+
});
|
|
2611
|
+
const r = Date.now() - e;
|
|
2612
|
+
return {
|
|
2613
|
+
status: r < 1e3 ? "healthy" : "degraded",
|
|
2614
|
+
responseTime: r,
|
|
2615
|
+
message: `Database response time: ${r}ms`
|
|
2616
|
+
};
|
|
2617
|
+
} catch (e) {
|
|
2618
|
+
return {
|
|
2619
|
+
status: "critical",
|
|
2620
|
+
error: e.message,
|
|
2621
|
+
message: "Database connection failed"
|
|
2622
|
+
};
|
|
2623
|
+
}
|
|
2624
|
+
}
|
|
2625
|
+
/**
|
|
2626
|
+
* Check sync status
|
|
2627
|
+
* @private
|
|
2628
|
+
*/
|
|
2629
|
+
async _checkSyncStatus() {
|
|
2630
|
+
try {
|
|
2631
|
+
const e = this.magazine.eventSyncer.lastSyncTime || 0, t = Date.now() - e;
|
|
2632
|
+
return e === 0 ? {
|
|
2633
|
+
status: "warning",
|
|
2634
|
+
message: "No sync performed yet"
|
|
2635
|
+
} : {
|
|
2636
|
+
status: t < this.healthThresholds.syncDelay ? "healthy" : "warning",
|
|
2637
|
+
lastSync: new Date(e).toISOString(),
|
|
2638
|
+
timeSinceSync: t,
|
|
2639
|
+
message: `Last sync: ${Math.round(t / 1e3)}s ago`
|
|
2640
|
+
};
|
|
2641
|
+
} catch (e) {
|
|
2642
|
+
return {
|
|
2643
|
+
status: "error",
|
|
2644
|
+
error: e.message,
|
|
2645
|
+
message: "Could not check sync status"
|
|
2646
|
+
};
|
|
2647
|
+
}
|
|
2648
|
+
}
|
|
2649
|
+
/**
|
|
2650
|
+
* Check storage status
|
|
2651
|
+
* @private
|
|
2652
|
+
*/
|
|
2653
|
+
async _checkStorage() {
|
|
2654
|
+
try {
|
|
2655
|
+
const e = await this.magazine.getCompressionStats(), t = e.totalCompressedSize / (100 * 1024 * 1024);
|
|
2656
|
+
return {
|
|
2657
|
+
status: t < this.healthThresholds.storageUsage ? "healthy" : "warning",
|
|
2658
|
+
usage: `${(t * 100).toFixed(2)}%`,
|
|
2659
|
+
compressed: e.totalCompressedSize,
|
|
2660
|
+
saved: e.spaceSaved,
|
|
2661
|
+
message: `Storage usage: ${(t * 100).toFixed(2)}%`
|
|
2662
|
+
};
|
|
2663
|
+
} catch (e) {
|
|
2664
|
+
return {
|
|
2665
|
+
status: "error",
|
|
2666
|
+
error: e.message,
|
|
2667
|
+
message: "Could not check storage status"
|
|
2668
|
+
};
|
|
2669
|
+
}
|
|
2670
|
+
}
|
|
2671
|
+
/**
|
|
2672
|
+
* Check performance metrics
|
|
2673
|
+
* @private
|
|
2674
|
+
*/
|
|
2675
|
+
_checkPerformance() {
|
|
2676
|
+
const e = this.getPerformanceReport({ timeRange: 3e5 });
|
|
2677
|
+
let t = "healthy";
|
|
2678
|
+
const r = [];
|
|
2679
|
+
return Object.entries(e.metrics).forEach(([s, n]) => {
|
|
2680
|
+
n.errorRate && parseFloat(n.errorRate) > this.healthThresholds.errorRate * 100 && (t = "degraded", r.push(`High error rate in ${s}: ${n.errorRate}`)), n.average > this.healthThresholds.responseTime && (t = "degraded", r.push(`Slow ${s} operations: ${n.average}ms average`));
|
|
2681
|
+
}), {
|
|
2682
|
+
status: t,
|
|
2683
|
+
metrics: e.metrics,
|
|
2684
|
+
message: r.length > 0 ? r.join(", ") : "Performance metrics within normal range"
|
|
2685
|
+
};
|
|
2686
|
+
}
|
|
2687
|
+
/**
|
|
2688
|
+
* Get configuration info
|
|
2689
|
+
* @private
|
|
2690
|
+
*/
|
|
2691
|
+
_getConfigInfo() {
|
|
2692
|
+
const e = this.magazine.configManager.getAll();
|
|
2693
|
+
return {
|
|
2694
|
+
name: e.name,
|
|
2695
|
+
address: e.address,
|
|
2696
|
+
networkId: e.networkId,
|
|
2697
|
+
logLevel: this.magazine.logger.level,
|
|
2698
|
+
compression: {
|
|
2699
|
+
enabled: this.magazine.eventStore.compressionEnabled,
|
|
2700
|
+
strategy: this.magazine.eventStore.compressionStrategy
|
|
2701
|
+
}
|
|
2702
|
+
};
|
|
2703
|
+
}
|
|
2704
|
+
/**
|
|
2705
|
+
* Get network info
|
|
2706
|
+
* @private
|
|
2707
|
+
*/
|
|
2708
|
+
async _getNetworkInfo() {
|
|
2709
|
+
try {
|
|
2710
|
+
return {
|
|
2711
|
+
...await this.magazine.getNetworkInfo(),
|
|
2712
|
+
connected: !0
|
|
2713
|
+
};
|
|
2714
|
+
} catch (e) {
|
|
2715
|
+
return {
|
|
2716
|
+
connected: !1,
|
|
2717
|
+
error: e.message
|
|
2718
|
+
};
|
|
2719
|
+
}
|
|
2720
|
+
}
|
|
2721
|
+
/**
|
|
2722
|
+
* Get storage info
|
|
2723
|
+
* @private
|
|
2724
|
+
*/
|
|
2725
|
+
async _getStorageInfo() {
|
|
2726
|
+
try {
|
|
2727
|
+
const e = await this.magazine.getCompressionStats();
|
|
2728
|
+
return {
|
|
2729
|
+
eventCount: (await this.magazine.getAllEvents({ pagination: { page: 1, pageSize: 1 } })).meta.totalItems,
|
|
2730
|
+
...e
|
|
2731
|
+
};
|
|
2732
|
+
} catch (e) {
|
|
2733
|
+
return {
|
|
2734
|
+
error: e.message
|
|
2735
|
+
};
|
|
2736
|
+
}
|
|
2737
|
+
}
|
|
2738
|
+
/**
|
|
2739
|
+
* Get performance info
|
|
2740
|
+
* @private
|
|
2741
|
+
*/
|
|
2742
|
+
_getPerformanceInfo() {
|
|
2743
|
+
return this.getPerformanceReport({ timeRange: 36e5 });
|
|
2744
|
+
}
|
|
2745
|
+
/**
|
|
2746
|
+
* Capture a log entry into the circular buffer
|
|
2747
|
+
* @param {Object} entry - Log entry to capture
|
|
2748
|
+
*/
|
|
2749
|
+
captureLog(e) {
|
|
2750
|
+
this._logBuffer.push(e), this._logBuffer.length > this._logBufferMax && this._logBuffer.shift();
|
|
2751
|
+
}
|
|
2752
|
+
/**
|
|
2753
|
+
* Get recent logs from the circular buffer
|
|
2754
|
+
* @param {number} [count=50] - Number of recent log entries to return
|
|
2755
|
+
* @returns {Array} Recent log entries
|
|
2756
|
+
* @private
|
|
2757
|
+
*/
|
|
2758
|
+
_getRecentLogs(e = 50) {
|
|
2759
|
+
const t = Math.min(e, this._logBuffer.length);
|
|
2760
|
+
return this._logBuffer.slice(-t);
|
|
2761
|
+
}
|
|
2762
|
+
/**
|
|
2763
|
+
* Calculate percentile
|
|
2764
|
+
* @private
|
|
2765
|
+
*/
|
|
2766
|
+
_calculatePercentile(e, t) {
|
|
2767
|
+
if (e.length === 0)
|
|
2768
|
+
return 0;
|
|
2769
|
+
const r = [...e].sort((n, i) => n - i), s = Math.ceil(t / 100 * r.length) - 1;
|
|
2770
|
+
return r[s];
|
|
2771
|
+
}
|
|
2772
|
+
/**
|
|
2773
|
+
* Instrument methods for metric collection
|
|
2774
|
+
* @private
|
|
2775
|
+
*/
|
|
2776
|
+
_instrumentMethods() {
|
|
2777
|
+
const e = this.magazine, t = [
|
|
2778
|
+
"syncEvents",
|
|
2779
|
+
"getAllEvents",
|
|
2780
|
+
"getEvents",
|
|
2781
|
+
"query",
|
|
2782
|
+
"transformEvent"
|
|
2783
|
+
];
|
|
2784
|
+
for (const r of t) {
|
|
2785
|
+
if (typeof e[r] != "function") {
|
|
2786
|
+
this.logger.debug(`Skipping instrumentation for missing method: ${r}`);
|
|
2787
|
+
continue;
|
|
2788
|
+
}
|
|
2789
|
+
if (this._originalMethods.has(r))
|
|
2790
|
+
continue;
|
|
2791
|
+
const s = e[r].bind(e);
|
|
2792
|
+
this._originalMethods.set(r, e[r]);
|
|
2793
|
+
const n = this;
|
|
2794
|
+
e[r] = function(...i) {
|
|
2795
|
+
const a = Date.now();
|
|
2796
|
+
let l;
|
|
2797
|
+
r === "syncEvents" ? l = "syncOperations" : r === "getAllEvents" || r === "getEvents" || r === "query" ? l = "queryOperations" : l = "storageOperations";
|
|
2798
|
+
try {
|
|
2799
|
+
const o = s(...i);
|
|
2800
|
+
if (o && typeof o.then == "function")
|
|
2801
|
+
return o.then((h) => {
|
|
2802
|
+
const g = Date.now() - a;
|
|
2803
|
+
return n.recordMetric(l, {
|
|
2804
|
+
method: r,
|
|
2805
|
+
duration: g,
|
|
2806
|
+
success: !0
|
|
2807
|
+
}), n.logger.debug(`${r} completed in ${g}ms`), h;
|
|
2808
|
+
}).catch((h) => {
|
|
2809
|
+
const g = Date.now() - a;
|
|
2810
|
+
throw n.recordMetric(l, {
|
|
2811
|
+
method: r,
|
|
2812
|
+
duration: g,
|
|
2813
|
+
success: !1,
|
|
2814
|
+
error: h.message
|
|
2815
|
+
}), n.logger.debug(`${r} failed after ${g}ms: ${h.message}`), h;
|
|
2816
|
+
});
|
|
2817
|
+
const c = Date.now() - a;
|
|
2818
|
+
return n.recordMetric(l, {
|
|
2819
|
+
method: r,
|
|
2820
|
+
duration: c,
|
|
2821
|
+
success: !0
|
|
2822
|
+
}), n.logger.debug(`${r} completed in ${c}ms`), o;
|
|
2823
|
+
} catch (o) {
|
|
2824
|
+
const c = Date.now() - a;
|
|
2825
|
+
throw n.recordMetric(l, {
|
|
2826
|
+
method: r,
|
|
2827
|
+
duration: c,
|
|
2828
|
+
success: !1,
|
|
2829
|
+
error: o.message
|
|
2830
|
+
}), n.logger.debug(`${r} failed after ${c}ms: ${o.message}`), o;
|
|
2831
|
+
}
|
|
2832
|
+
};
|
|
2833
|
+
}
|
|
2834
|
+
this.logger.debug("Method instrumentation enabled");
|
|
2835
|
+
}
|
|
2836
|
+
/**
|
|
2837
|
+
* Enable verbose error logging
|
|
2838
|
+
* @private
|
|
2839
|
+
*/
|
|
2840
|
+
_enableVerboseErrors() {
|
|
2841
|
+
this._verboseErrors = !0;
|
|
2842
|
+
const e = this.magazine;
|
|
2843
|
+
if (typeof e.retryOperation == "function" && !this._originalRetryOperation) {
|
|
2844
|
+
const t = e.retryOperation.bind(e);
|
|
2845
|
+
this._originalRetryOperation = e.retryOperation;
|
|
2846
|
+
const r = this;
|
|
2847
|
+
e.retryOperation = async function(s, n, ...i) {
|
|
2848
|
+
try {
|
|
2849
|
+
return await t(s, n, ...i);
|
|
2850
|
+
} catch (a) {
|
|
2851
|
+
throw r._verboseErrors && r.logger.error("Verbose error context for retryOperation failure", {
|
|
2852
|
+
operationContext: n,
|
|
2853
|
+
errorMessage: a.message,
|
|
2854
|
+
errorStack: a.stack,
|
|
2855
|
+
errorName: a.name,
|
|
2856
|
+
timestamp: new Date().toISOString()
|
|
2857
|
+
}), a;
|
|
2858
|
+
}
|
|
2859
|
+
};
|
|
2860
|
+
}
|
|
2861
|
+
this.logger.debug("Verbose error logging enabled");
|
|
2862
|
+
}
|
|
2863
|
+
/**
|
|
2864
|
+
* Set debug mode on/off
|
|
2865
|
+
* @param {boolean} enabled - Whether to enable debug mode
|
|
2866
|
+
*/
|
|
2867
|
+
setDebugMode(e) {
|
|
2868
|
+
if (this.debugMode = !!e, this.logger.info(`Debug mode ${e ? "enabled" : "disabled"}`), e) {
|
|
2869
|
+
if (this._instrumentMethods(), this._enableVerboseErrors(), this.logger._log && !this._originalLoggerLog) {
|
|
2870
|
+
this._originalLoggerLog = this.logger._log.bind(this.logger);
|
|
2871
|
+
const t = this;
|
|
2872
|
+
this.logger._log = function(r, s, n) {
|
|
2873
|
+
const i = t._originalLoggerLog(r, s, n);
|
|
2874
|
+
return t.captureLog({
|
|
2875
|
+
timestamp: Date.now(),
|
|
2876
|
+
level: r,
|
|
2877
|
+
message: s,
|
|
2878
|
+
data: n
|
|
2879
|
+
}), i;
|
|
2880
|
+
};
|
|
2881
|
+
}
|
|
2882
|
+
} else
|
|
2883
|
+
this._originalLoggerLog && (this.logger._log = this._originalLoggerLog, this._originalLoggerLog = null);
|
|
2884
|
+
}
|
|
2885
|
+
/**
|
|
2886
|
+
* Check if debug mode is enabled
|
|
2887
|
+
* @returns {boolean} Debug mode status
|
|
2888
|
+
*/
|
|
2889
|
+
isDebugMode() {
|
|
2890
|
+
return !!this.debugMode;
|
|
2891
|
+
}
|
|
2892
|
+
}
|
|
2893
|
+
class B extends Error {
|
|
2894
|
+
constructor(e, t = null) {
|
|
2895
|
+
super(e), this.name = "ValidationError", this.field = t;
|
|
2896
|
+
}
|
|
2897
|
+
}
|
|
2898
|
+
class f extends Error {
|
|
2899
|
+
constructor(e) {
|
|
2900
|
+
super(e), this.name = "ModelError";
|
|
2901
|
+
}
|
|
2902
|
+
}
|
|
2903
|
+
class j {
|
|
2904
|
+
constructor(e, t) {
|
|
2905
|
+
this.modelName = e, this.gun = t, this.migrations = /* @__PURE__ */ new Map(), this.currentVersion = 1;
|
|
2906
|
+
}
|
|
2907
|
+
version(e) {
|
|
2908
|
+
return this.currentVersion = e, this;
|
|
2909
|
+
}
|
|
2910
|
+
addMigration(e, t, r) {
|
|
2911
|
+
return this.migrations.set(`${e}:${t}`, r), this;
|
|
2912
|
+
}
|
|
2913
|
+
async migrate(e) {
|
|
2914
|
+
let t = e._schemaVersion || 1, r = { ...e };
|
|
2915
|
+
for (; t < this.currentVersion; ) {
|
|
2916
|
+
const s = t + 1, n = `${t}:${s}`, i = this.migrations.get(n);
|
|
2917
|
+
if (!i)
|
|
2918
|
+
throw new f(
|
|
2919
|
+
`No migration path from version ${t} to ${s} for model '${this.modelName}'`
|
|
2920
|
+
);
|
|
2921
|
+
r = await i(r), r._schemaVersion = s, t = s;
|
|
2922
|
+
}
|
|
2923
|
+
return r;
|
|
2924
|
+
}
|
|
2925
|
+
needsMigration(e) {
|
|
2926
|
+
return (e._schemaVersion || 1) < this.currentVersion;
|
|
2927
|
+
}
|
|
2928
|
+
}
|
|
2929
|
+
class U {
|
|
2930
|
+
static validate(e, t) {
|
|
2931
|
+
const r = [];
|
|
2932
|
+
for (const s of t.required || [])
|
|
2933
|
+
(!(s in e) || e[s] === null || e[s] === void 0) && r.push(`Field '${s}' is required`);
|
|
2934
|
+
for (const [s, n] of Object.entries(t.properties || {}))
|
|
2935
|
+
if (s in e && e[s] !== null && e[s] !== void 0) {
|
|
2936
|
+
const i = e[s], { type: a, validate: l } = n;
|
|
2937
|
+
if (a && !this.validateType(i, a) && r.push(`Field '${s}' must be of type ${a}`), l && typeof l == "function") {
|
|
2938
|
+
const o = l(i);
|
|
2939
|
+
o !== !0 && r.push(`Field '${s}': ${o || "validation failed"}`);
|
|
2940
|
+
}
|
|
2941
|
+
}
|
|
2942
|
+
return {
|
|
2943
|
+
valid: r.length === 0,
|
|
2944
|
+
errors: r
|
|
2945
|
+
};
|
|
2946
|
+
}
|
|
2947
|
+
static validateType(e, t) {
|
|
2948
|
+
switch (t) {
|
|
2949
|
+
case "string":
|
|
2950
|
+
return typeof e == "string";
|
|
2951
|
+
case "number":
|
|
2952
|
+
return typeof e == "number" && !isNaN(e);
|
|
2953
|
+
case "boolean":
|
|
2954
|
+
return typeof e == "boolean";
|
|
2955
|
+
case "array":
|
|
2956
|
+
return Array.isArray(e);
|
|
2957
|
+
case "object":
|
|
2958
|
+
return typeof e == "object" && e !== null && !Array.isArray(e);
|
|
2959
|
+
case "address":
|
|
2960
|
+
return typeof e == "string" && /^0x[a-fA-F0-9]{40}$/.test(e);
|
|
2961
|
+
case "hash":
|
|
2962
|
+
return typeof e == "string" && /^0x[a-fA-F0-9]{64}$/.test(e);
|
|
2963
|
+
default:
|
|
2964
|
+
return !0;
|
|
2965
|
+
}
|
|
2966
|
+
}
|
|
2967
|
+
}
|
|
2968
|
+
class H {
|
|
2969
|
+
constructor(e, t, r, s, n = {}) {
|
|
2970
|
+
this.name = e, this.schema = t, this.gun = r, this.transformer = s, this.options = {
|
|
2971
|
+
autoId: !0,
|
|
2972
|
+
timestampFields: ["createdAt", "updatedAt"],
|
|
2973
|
+
...n
|
|
2974
|
+
}, this.logger = new p({
|
|
2975
|
+
context: `DataModel:${e}`,
|
|
2976
|
+
level: n.logLevel || "info"
|
|
2977
|
+
}), this.modelNode = this.gun.get("models").get(this.name), this._hooks = {}, this.migration = null, this.logger.info("DataModel initialized", {
|
|
2978
|
+
name: this.name,
|
|
2979
|
+
options: this.options
|
|
2980
|
+
});
|
|
2981
|
+
}
|
|
2982
|
+
/**
|
|
2983
|
+
* Register a lifecycle hook
|
|
2984
|
+
* @param {'pre:save'|'post:save'|'pre:update'|'post:update'|'pre:delete'|'post:delete'} event
|
|
2985
|
+
* @param {Function} fn - Hook function. Pre-hooks receive (data) and can return modified data. Post-hooks receive (result).
|
|
2986
|
+
*/
|
|
2987
|
+
hook(e, t) {
|
|
2988
|
+
return this._hooks[e] || (this._hooks[e] = []), this._hooks[e].push(t), this;
|
|
2989
|
+
}
|
|
2990
|
+
/**
|
|
2991
|
+
* Attach a SchemaMigration instance so documents are auto-migrated on read.
|
|
2992
|
+
* @param {SchemaMigration} schemaMigration
|
|
2993
|
+
* @returns {DataModel} this (for chaining)
|
|
2994
|
+
*/
|
|
2995
|
+
setMigration(e) {
|
|
2996
|
+
return this.migration = e, this;
|
|
2997
|
+
}
|
|
2998
|
+
async _runHooks(e, t) {
|
|
2999
|
+
const r = this._hooks[e] || [];
|
|
3000
|
+
let s = t;
|
|
3001
|
+
for (const n of r) {
|
|
3002
|
+
const i = await n(s);
|
|
3003
|
+
i !== void 0 && (s = i);
|
|
3004
|
+
}
|
|
3005
|
+
return s;
|
|
3006
|
+
}
|
|
3007
|
+
/**
|
|
3008
|
+
* Generate a unique ID for a new document
|
|
3009
|
+
* @returns {string} Unique ID
|
|
3010
|
+
*/
|
|
3011
|
+
generateId() {
|
|
3012
|
+
return `${this.name}_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
|
3013
|
+
}
|
|
3014
|
+
/**
|
|
3015
|
+
* Validate data against schema
|
|
3016
|
+
* @param {Object} data - Data to validate
|
|
3017
|
+
* @throws {ValidationError} If validation fails
|
|
3018
|
+
*/
|
|
3019
|
+
validate(e) {
|
|
3020
|
+
const t = U.validate(e, this.schema);
|
|
3021
|
+
if (!t.valid)
|
|
3022
|
+
throw new B(`Validation failed: ${t.errors.join(", ")}`);
|
|
3023
|
+
}
|
|
3024
|
+
/**
|
|
3025
|
+
* Prepare data for storage (add timestamps, ID, etc.)
|
|
3026
|
+
* @param {Object} data - Raw data
|
|
3027
|
+
* @param {boolean} isUpdate - Whether this is an update operation
|
|
3028
|
+
* @returns {Object} Prepared data
|
|
3029
|
+
*/
|
|
3030
|
+
prepareData(e, t = !1) {
|
|
3031
|
+
const r = { ...e };
|
|
3032
|
+
return this.options.autoId && !r.id && !t && (r.id = this.generateId()), this.options.timestampFields.includes("updatedAt") && (r.updatedAt = new Date().toISOString()), this.options.timestampFields.includes("createdAt") && !t && (r.createdAt = new Date().toISOString()), this.migration && (r._schemaVersion = this.migration.currentVersion), r;
|
|
3033
|
+
}
|
|
3034
|
+
/**
|
|
3035
|
+
* Save a document (create or update)
|
|
3036
|
+
* @param {Object} data - Document data
|
|
3037
|
+
* @returns {Promise<Object>} Saved document
|
|
3038
|
+
*/
|
|
3039
|
+
async save(e) {
|
|
3040
|
+
this.logger.debug("Saving document", { data: e });
|
|
3041
|
+
try {
|
|
3042
|
+
let t = this.prepareData(e);
|
|
3043
|
+
t = await this._runHooks("pre:save", t), this.validate(t);
|
|
3044
|
+
const r = t.id;
|
|
3045
|
+
if (!r)
|
|
3046
|
+
throw new f("Document must have an ID");
|
|
3047
|
+
return await new Promise((s, n) => {
|
|
3048
|
+
this.modelNode.get(r).put(t, (i) => {
|
|
3049
|
+
i.err ? n(new f(`Failed to save document: ${i.err}`)) : s();
|
|
3050
|
+
});
|
|
3051
|
+
}), await this._runHooks("post:save", t), this.logger.info("Document saved", { id: r, model: this.name }), t;
|
|
3052
|
+
} catch (t) {
|
|
3053
|
+
throw this.logger.error("Failed to save document", { error: t.message, data: e }), t;
|
|
3054
|
+
}
|
|
3055
|
+
}
|
|
3056
|
+
/**
|
|
3057
|
+
* Find documents by query
|
|
3058
|
+
* @param {Object} query - Query object
|
|
3059
|
+
* @param {Object} options - Query options
|
|
3060
|
+
* @returns {Promise<Array>} Found documents
|
|
3061
|
+
*/
|
|
3062
|
+
async find(e = {}, t = {}) {
|
|
3063
|
+
this.logger.debug("Finding documents", { query: e, options: t });
|
|
3064
|
+
try {
|
|
3065
|
+
const { limit: r = 100, skip: s = 0 } = t, n = [];
|
|
3066
|
+
if (await new Promise((i) => {
|
|
3067
|
+
let a = 0, l = 0;
|
|
3068
|
+
this.modelNode.map().on((o, c) => {
|
|
3069
|
+
if (o && typeof o == "object" && this.matchesQuery(o, e)) {
|
|
3070
|
+
if (l < s) {
|
|
3071
|
+
l++;
|
|
3072
|
+
return;
|
|
3073
|
+
}
|
|
3074
|
+
a < r && (n.push({ ...o, _key: c }), a++), a >= r && i();
|
|
3075
|
+
}
|
|
3076
|
+
}), setTimeout(i, 1e3);
|
|
3077
|
+
}), this.migration)
|
|
3078
|
+
for (let i = 0; i < n.length; i++)
|
|
3079
|
+
this.migration.needsMigration(n[i]) && (n[i] = await this.migration.migrate(n[i]), await this.save(n[i]));
|
|
3080
|
+
return this.logger.debug("Documents found", { count: n.length, query: e }), n;
|
|
3081
|
+
} catch (r) {
|
|
3082
|
+
throw this.logger.error("Failed to find documents", { error: r.message, query: e }), new f(`Failed to find documents: ${r.message}`);
|
|
3083
|
+
}
|
|
3084
|
+
}
|
|
3085
|
+
/**
|
|
3086
|
+
* Find a single document by query
|
|
3087
|
+
* @param {Object} query - Query object
|
|
3088
|
+
* @returns {Promise<Object|null>} Found document or null
|
|
3089
|
+
*/
|
|
3090
|
+
async findOne(e) {
|
|
3091
|
+
const t = await this.find(e, { limit: 1 });
|
|
3092
|
+
return t.length > 0 ? t[0] : null;
|
|
3093
|
+
}
|
|
3094
|
+
/**
|
|
3095
|
+
* Find a document by ID
|
|
3096
|
+
* @param {string} id - Document ID
|
|
3097
|
+
* @returns {Promise<Object|null>} Found document or null
|
|
3098
|
+
*/
|
|
3099
|
+
async findById(e) {
|
|
3100
|
+
this.logger.debug("Finding document by ID", { id: e });
|
|
3101
|
+
try {
|
|
3102
|
+
let t = await new Promise((r) => {
|
|
3103
|
+
this.modelNode.get(e).once((s) => {
|
|
3104
|
+
r(s && typeof s == "object" ? { ...s, _key: e } : null);
|
|
3105
|
+
});
|
|
3106
|
+
});
|
|
3107
|
+
return t && this.migration && this.migration.needsMigration(t) && (t = await this.migration.migrate(t), await this.save(t)), t;
|
|
3108
|
+
} catch (t) {
|
|
3109
|
+
throw this.logger.error("Failed to find document by ID", { error: t.message, id: e }), new f(`Failed to find document: ${t.message}`);
|
|
3110
|
+
}
|
|
3111
|
+
}
|
|
3112
|
+
/**
|
|
3113
|
+
* Update a document by ID
|
|
3114
|
+
* @param {string} id - Document ID
|
|
3115
|
+
* @param {Object} updateData - Data to update
|
|
3116
|
+
* @returns {Promise<Object>} Updated document
|
|
3117
|
+
*/
|
|
3118
|
+
async update(e, t) {
|
|
3119
|
+
this.logger.debug("Updating document", { id: e, updateData: t });
|
|
3120
|
+
try {
|
|
3121
|
+
const r = await this.findById(e);
|
|
3122
|
+
if (!r)
|
|
3123
|
+
throw new f(`Document with ID ${e} not found`);
|
|
3124
|
+
const s = { ...r, ...t, id: e };
|
|
3125
|
+
let n = this.prepareData(s, !0);
|
|
3126
|
+
return n = await this._runHooks("pre:update", n), this.validate(n), await new Promise((i, a) => {
|
|
3127
|
+
this.modelNode.get(e).put(n, (l) => {
|
|
3128
|
+
l.err ? a(new f(`Failed to update document: ${l.err}`)) : i();
|
|
3129
|
+
});
|
|
3130
|
+
}), await this._runHooks("post:update", n), this.logger.info("Document updated", { id: e, model: this.name }), n;
|
|
3131
|
+
} catch (r) {
|
|
3132
|
+
throw this.logger.error("Failed to update document", { error: r.message, id: e, updateData: t }), r;
|
|
3133
|
+
}
|
|
3134
|
+
}
|
|
3135
|
+
/**
|
|
3136
|
+
* Delete a document by ID
|
|
3137
|
+
* @param {string} id - Document ID
|
|
3138
|
+
* @returns {Promise<boolean>} Success status
|
|
3139
|
+
*/
|
|
3140
|
+
async delete(e) {
|
|
3141
|
+
this.logger.debug("Deleting document", { id: e });
|
|
3142
|
+
try {
|
|
3143
|
+
return await this._runHooks("pre:delete", { id: e }), await new Promise((t, r) => {
|
|
3144
|
+
this.modelNode.get(e).put(null, (s) => {
|
|
3145
|
+
s.err ? r(new f(`Failed to delete document: ${s.err}`)) : t();
|
|
3146
|
+
});
|
|
3147
|
+
}), await this._runHooks("post:delete", { id: e }), this.logger.info("Document deleted", { id: e, model: this.name }), !0;
|
|
3148
|
+
} catch (t) {
|
|
3149
|
+
throw this.logger.error("Failed to delete document", { error: t.message, id: e }), t;
|
|
3150
|
+
}
|
|
3151
|
+
}
|
|
3152
|
+
/**
|
|
3153
|
+
* Count documents matching query
|
|
3154
|
+
* @param {Object} query - Query object
|
|
3155
|
+
* @returns {Promise<number>} Document count
|
|
3156
|
+
*/
|
|
3157
|
+
async count(e = {}) {
|
|
3158
|
+
return (await this.find(e, { limit: 1e4 })).length;
|
|
3159
|
+
}
|
|
3160
|
+
/**
|
|
3161
|
+
* Insert multiple documents
|
|
3162
|
+
* @param {Array<Object>} documents - Array of documents to insert
|
|
3163
|
+
* @returns {Promise<Array<Object>>} Saved documents
|
|
3164
|
+
*/
|
|
3165
|
+
async insertMany(e) {
|
|
3166
|
+
if (!Array.isArray(e))
|
|
3167
|
+
throw new f("insertMany requires an array of documents");
|
|
3168
|
+
const t = [], r = [];
|
|
3169
|
+
for (const s of e)
|
|
3170
|
+
try {
|
|
3171
|
+
t.push(await this.save(s));
|
|
3172
|
+
} catch (n) {
|
|
3173
|
+
r.push({ document: s, error: n.message });
|
|
3174
|
+
}
|
|
3175
|
+
return r.length > 0 && this.logger.warn("Some documents failed to insert", {
|
|
3176
|
+
total: e.length,
|
|
3177
|
+
succeeded: t.length,
|
|
3178
|
+
failed: r.length
|
|
3179
|
+
}), { inserted: t, errors: r };
|
|
3180
|
+
}
|
|
3181
|
+
/**
|
|
3182
|
+
* Update multiple documents matching a query
|
|
3183
|
+
* @param {Object} query - Query to match documents
|
|
3184
|
+
* @param {Object} updateData - Data to update on each matched document
|
|
3185
|
+
* @returns {Promise<Object>} Update results
|
|
3186
|
+
*/
|
|
3187
|
+
async updateMany(e, t) {
|
|
3188
|
+
const r = await this.find(e, { limit: 1e5 }), s = [], n = [];
|
|
3189
|
+
for (const i of r) {
|
|
3190
|
+
const a = i.id || i._key;
|
|
3191
|
+
if (a)
|
|
3192
|
+
try {
|
|
3193
|
+
s.push(await this.update(a, t));
|
|
3194
|
+
} catch (l) {
|
|
3195
|
+
n.push({ id: a, error: l.message });
|
|
3196
|
+
}
|
|
3197
|
+
}
|
|
3198
|
+
return this.logger.info("updateMany completed", {
|
|
3199
|
+
matched: r.length,
|
|
3200
|
+
updated: s.length,
|
|
3201
|
+
failed: n.length
|
|
3202
|
+
}), { matched: r.length, updated: s, errors: n };
|
|
3203
|
+
}
|
|
3204
|
+
/**
|
|
3205
|
+
* Delete multiple documents matching a query
|
|
3206
|
+
* @param {Object} query - Query to match documents for deletion
|
|
3207
|
+
* @returns {Promise<Object>} Deletion results
|
|
3208
|
+
*/
|
|
3209
|
+
async deleteMany(e) {
|
|
3210
|
+
const t = await this.find(e, { limit: 1e5 });
|
|
3211
|
+
let r = 0;
|
|
3212
|
+
const s = [];
|
|
3213
|
+
for (const n of t) {
|
|
3214
|
+
const i = n.id || n._key;
|
|
3215
|
+
if (i)
|
|
3216
|
+
try {
|
|
3217
|
+
await this.delete(i), r++;
|
|
3218
|
+
} catch (a) {
|
|
3219
|
+
s.push({ id: i, error: a.message });
|
|
3220
|
+
}
|
|
3221
|
+
}
|
|
3222
|
+
return this.logger.info("deleteMany completed", {
|
|
3223
|
+
matched: t.length,
|
|
3224
|
+
deleted: r,
|
|
3225
|
+
failed: s.length
|
|
3226
|
+
}), { matched: t.length, deleted: r, errors: s };
|
|
3227
|
+
}
|
|
3228
|
+
/**
|
|
3229
|
+
* Migrate all documents in this model to the current schema version.
|
|
3230
|
+
* @returns {Promise<Object>} Migration results { migrated, errors }
|
|
3231
|
+
*/
|
|
3232
|
+
async migrateAll() {
|
|
3233
|
+
if (!this.migration)
|
|
3234
|
+
throw new f("No migration configured for this model. Call setMigration() first.");
|
|
3235
|
+
const e = await this.find({}, { limit: 1e5 });
|
|
3236
|
+
let t = 0;
|
|
3237
|
+
const r = [];
|
|
3238
|
+
for (const s of e)
|
|
3239
|
+
if (this.migration.needsMigration(s))
|
|
3240
|
+
try {
|
|
3241
|
+
const n = await this.migration.migrate(s);
|
|
3242
|
+
await this.save(n), t++;
|
|
3243
|
+
} catch (n) {
|
|
3244
|
+
r.push({ id: s.id || s._key, error: n.message });
|
|
3245
|
+
}
|
|
3246
|
+
return this.logger.info("migrateAll completed", {
|
|
3247
|
+
total: e.length,
|
|
3248
|
+
migrated: t,
|
|
3249
|
+
failed: r.length
|
|
3250
|
+
}), { total: e.length, migrated: t, errors: r };
|
|
3251
|
+
}
|
|
3252
|
+
/**
|
|
3253
|
+
* Simple query matching (can be enhanced with MongoDB-like operators)
|
|
3254
|
+
* @private
|
|
3255
|
+
* @param {Object} document - Document to test
|
|
3256
|
+
* @param {Object} query - Query object
|
|
3257
|
+
* @returns {boolean} Whether document matches query
|
|
3258
|
+
*/
|
|
3259
|
+
matchesQuery(e, t) {
|
|
3260
|
+
for (const [r, s] of Object.entries(t))
|
|
3261
|
+
if (typeof s == "object" && s !== null)
|
|
3262
|
+
for (const [n, i] of Object.entries(s))
|
|
3263
|
+
switch (n) {
|
|
3264
|
+
case "$gte":
|
|
3265
|
+
if (!(e[r] >= i))
|
|
3266
|
+
return !1;
|
|
3267
|
+
break;
|
|
3268
|
+
case "$lte":
|
|
3269
|
+
if (!(e[r] <= i))
|
|
3270
|
+
return !1;
|
|
3271
|
+
break;
|
|
3272
|
+
case "$gt":
|
|
3273
|
+
if (!(e[r] > i))
|
|
3274
|
+
return !1;
|
|
3275
|
+
break;
|
|
3276
|
+
case "$lt":
|
|
3277
|
+
if (!(e[r] < i))
|
|
3278
|
+
return !1;
|
|
3279
|
+
break;
|
|
3280
|
+
case "$ne":
|
|
3281
|
+
if (e[r] === i)
|
|
3282
|
+
return !1;
|
|
3283
|
+
break;
|
|
3284
|
+
case "$in":
|
|
3285
|
+
if (!Array.isArray(i) || !i.includes(e[r]))
|
|
3286
|
+
return !1;
|
|
3287
|
+
break;
|
|
3288
|
+
case "$nin":
|
|
3289
|
+
if (Array.isArray(i) && i.includes(e[r]))
|
|
3290
|
+
return !1;
|
|
3291
|
+
break;
|
|
3292
|
+
case "$regex":
|
|
3293
|
+
if (!new RegExp(i).test(e[r]))
|
|
3294
|
+
return !1;
|
|
3295
|
+
break;
|
|
3296
|
+
default:
|
|
3297
|
+
if (e[r] !== s)
|
|
3298
|
+
return !1;
|
|
3299
|
+
}
|
|
3300
|
+
else if (e[r] !== s)
|
|
3301
|
+
return !1;
|
|
3302
|
+
return !0;
|
|
3303
|
+
}
|
|
3304
|
+
/**
|
|
3305
|
+
* Create an aggregation pipeline from query results.
|
|
3306
|
+
* @param {Object} [query={}] - Optional query to filter documents before aggregation
|
|
3307
|
+
* @returns {Promise<AggregationPipeline>} A new AggregationPipeline instance
|
|
3308
|
+
*/
|
|
3309
|
+
async aggregate(e = {}) {
|
|
3310
|
+
const t = await this.find(e, { limit: 1e5 });
|
|
3311
|
+
return new M(t);
|
|
3312
|
+
}
|
|
3313
|
+
}
|
|
3314
|
+
class M {
|
|
3315
|
+
constructor(e) {
|
|
3316
|
+
this.data = Array.isArray(e) ? e : [], this.stages = [];
|
|
3317
|
+
}
|
|
3318
|
+
/**
|
|
3319
|
+
* Filter/match stage - filter data before aggregation.
|
|
3320
|
+
* Uses the same query operators as DataModel.matchesQuery.
|
|
3321
|
+
* @param {Object} query - Query object with optional operators ($gte, $lte, etc.)
|
|
3322
|
+
* @returns {AggregationPipeline} this (for chaining)
|
|
3323
|
+
*/
|
|
3324
|
+
match(e) {
|
|
3325
|
+
return this.stages.push({ type: "match", query: e }), this;
|
|
3326
|
+
}
|
|
3327
|
+
/**
|
|
3328
|
+
* Group results by a field.
|
|
3329
|
+
* @param {string} field - Field name to group by
|
|
3330
|
+
* @returns {AggregationPipeline} this (for chaining)
|
|
3331
|
+
*/
|
|
3332
|
+
group(e) {
|
|
3333
|
+
return this.stages.push({ type: "group", field: e }), this;
|
|
3334
|
+
}
|
|
3335
|
+
/**
|
|
3336
|
+
* Sum aggregate on a numeric field.
|
|
3337
|
+
* @param {string} field - Field to sum
|
|
3338
|
+
* @param {string} [alias] - Optional alias for the result key (default: `sum_<field>`)
|
|
3339
|
+
* @returns {AggregationPipeline} this (for chaining)
|
|
3340
|
+
*/
|
|
3341
|
+
sum(e, t) {
|
|
3342
|
+
return this.stages.push({ type: "aggregate", op: "sum", field: e, alias: t || `sum_${e}` }), this;
|
|
3343
|
+
}
|
|
3344
|
+
/**
|
|
3345
|
+
* Average aggregate on a numeric field.
|
|
3346
|
+
* @param {string} field - Field to average
|
|
3347
|
+
* @param {string} [alias] - Optional alias for the result key (default: `avg_<field>`)
|
|
3348
|
+
* @returns {AggregationPipeline} this (for chaining)
|
|
3349
|
+
*/
|
|
3350
|
+
avg(e, t) {
|
|
3351
|
+
return this.stages.push({ type: "aggregate", op: "avg", field: e, alias: t || `avg_${e}` }), this;
|
|
3352
|
+
}
|
|
3353
|
+
/**
|
|
3354
|
+
* Count aggregate.
|
|
3355
|
+
* @param {string} [alias] - Optional alias for the result key (default: `count`)
|
|
3356
|
+
* @returns {AggregationPipeline} this (for chaining)
|
|
3357
|
+
*/
|
|
3358
|
+
count(e) {
|
|
3359
|
+
return this.stages.push({ type: "aggregate", op: "count", alias: e || "count" }), this;
|
|
3360
|
+
}
|
|
3361
|
+
/**
|
|
3362
|
+
* Min aggregate on a numeric field.
|
|
3363
|
+
* @param {string} field - Field to find minimum of
|
|
3364
|
+
* @param {string} [alias] - Optional alias for the result key (default: `min_<field>`)
|
|
3365
|
+
* @returns {AggregationPipeline} this (for chaining)
|
|
3366
|
+
*/
|
|
3367
|
+
min(e, t) {
|
|
3368
|
+
return this.stages.push({ type: "aggregate", op: "min", field: e, alias: t || `min_${e}` }), this;
|
|
3369
|
+
}
|
|
3370
|
+
/**
|
|
3371
|
+
* Max aggregate on a numeric field.
|
|
3372
|
+
* @param {string} field - Field to find maximum of
|
|
3373
|
+
* @param {string} [alias] - Optional alias for the result key (default: `max_<field>`)
|
|
3374
|
+
* @returns {AggregationPipeline} this (for chaining)
|
|
3375
|
+
*/
|
|
3376
|
+
max(e, t) {
|
|
3377
|
+
return this.stages.push({ type: "aggregate", op: "max", field: e, alias: t || `max_${e}` }), this;
|
|
3378
|
+
}
|
|
3379
|
+
/**
|
|
3380
|
+
* Time-series bucketing - groups data by time intervals.
|
|
3381
|
+
* Acts like a group stage but groups by truncated timestamp buckets.
|
|
3382
|
+
* @param {string} timestampField - Field containing a unix timestamp (seconds)
|
|
3383
|
+
* @param {'minute'|'hour'|'day'|'week'|'month'} interval - Bucket interval
|
|
3384
|
+
* @returns {AggregationPipeline} this (for chaining)
|
|
3385
|
+
*/
|
|
3386
|
+
bucket(e, t) {
|
|
3387
|
+
return this.stages.push({ type: "bucket", timestampField: e, interval: t }), this;
|
|
3388
|
+
}
|
|
3389
|
+
/**
|
|
3390
|
+
* Sort results by a field.
|
|
3391
|
+
* @param {string} field - Field to sort by
|
|
3392
|
+
* @param {'asc'|'desc'} [order='asc'] - Sort order
|
|
3393
|
+
* @returns {AggregationPipeline} this (for chaining)
|
|
3394
|
+
*/
|
|
3395
|
+
sort(e, t = "asc") {
|
|
3396
|
+
return this.stages.push({ type: "sort", field: e, order: t }), this;
|
|
3397
|
+
}
|
|
3398
|
+
/**
|
|
3399
|
+
* Limit the number of results returned.
|
|
3400
|
+
* @param {number} n - Maximum number of results
|
|
3401
|
+
* @returns {AggregationPipeline} this (for chaining)
|
|
3402
|
+
*/
|
|
3403
|
+
limit(e) {
|
|
3404
|
+
return this.stages.push({ type: "limit", n: e }), this;
|
|
3405
|
+
}
|
|
3406
|
+
/**
|
|
3407
|
+
* Truncate a Date to the start of the given interval bucket.
|
|
3408
|
+
* @private
|
|
3409
|
+
* @param {Date} date
|
|
3410
|
+
* @param {string} interval
|
|
3411
|
+
* @returns {string} ISO string representing the bucket start
|
|
3412
|
+
*/
|
|
3413
|
+
_truncateDate(e, t) {
|
|
3414
|
+
const r = new Date(e);
|
|
3415
|
+
switch (t) {
|
|
3416
|
+
case "minute":
|
|
3417
|
+
r.setUTCSeconds(0, 0);
|
|
3418
|
+
break;
|
|
3419
|
+
case "hour":
|
|
3420
|
+
r.setUTCMinutes(0, 0, 0);
|
|
3421
|
+
break;
|
|
3422
|
+
case "day":
|
|
3423
|
+
r.setUTCHours(0, 0, 0, 0);
|
|
3424
|
+
break;
|
|
3425
|
+
case "week": {
|
|
3426
|
+
r.setUTCHours(0, 0, 0, 0);
|
|
3427
|
+
const s = r.getUTCDay(), n = s === 0 ? 6 : s - 1;
|
|
3428
|
+
r.setUTCDate(r.getUTCDate() - n);
|
|
3429
|
+
break;
|
|
3430
|
+
}
|
|
3431
|
+
case "month":
|
|
3432
|
+
r.setUTCDate(1), r.setUTCHours(0, 0, 0, 0);
|
|
3433
|
+
break;
|
|
3434
|
+
}
|
|
3435
|
+
return r.toISOString();
|
|
3436
|
+
}
|
|
3437
|
+
/**
|
|
3438
|
+
* Simple query matching, mirroring DataModel.matchesQuery.
|
|
3439
|
+
* @private
|
|
3440
|
+
* @param {Object} document
|
|
3441
|
+
* @param {Object} query
|
|
3442
|
+
* @returns {boolean}
|
|
3443
|
+
*/
|
|
3444
|
+
_matchesQuery(e, t) {
|
|
3445
|
+
for (const [r, s] of Object.entries(t))
|
|
3446
|
+
if (typeof s == "object" && s !== null)
|
|
3447
|
+
for (const [n, i] of Object.entries(s))
|
|
3448
|
+
switch (n) {
|
|
3449
|
+
case "$gte":
|
|
3450
|
+
if (!(e[r] >= i))
|
|
3451
|
+
return !1;
|
|
3452
|
+
break;
|
|
3453
|
+
case "$lte":
|
|
3454
|
+
if (!(e[r] <= i))
|
|
3455
|
+
return !1;
|
|
3456
|
+
break;
|
|
3457
|
+
case "$gt":
|
|
3458
|
+
if (!(e[r] > i))
|
|
3459
|
+
return !1;
|
|
3460
|
+
break;
|
|
3461
|
+
case "$lt":
|
|
3462
|
+
if (!(e[r] < i))
|
|
3463
|
+
return !1;
|
|
3464
|
+
break;
|
|
3465
|
+
case "$ne":
|
|
3466
|
+
if (e[r] === i)
|
|
3467
|
+
return !1;
|
|
3468
|
+
break;
|
|
3469
|
+
case "$in":
|
|
3470
|
+
if (!Array.isArray(i) || !i.includes(e[r]))
|
|
3471
|
+
return !1;
|
|
3472
|
+
break;
|
|
3473
|
+
case "$nin":
|
|
3474
|
+
if (Array.isArray(i) && i.includes(e[r]))
|
|
3475
|
+
return !1;
|
|
3476
|
+
break;
|
|
3477
|
+
case "$regex":
|
|
3478
|
+
if (!new RegExp(i).test(e[r]))
|
|
3479
|
+
return !1;
|
|
3480
|
+
break;
|
|
3481
|
+
default:
|
|
3482
|
+
if (e[r] !== s)
|
|
3483
|
+
return !1;
|
|
3484
|
+
}
|
|
3485
|
+
else if (e[r] !== s)
|
|
3486
|
+
return !1;
|
|
3487
|
+
return !0;
|
|
3488
|
+
}
|
|
3489
|
+
/**
|
|
3490
|
+
* Compute a single aggregate value over an array of documents.
|
|
3491
|
+
* @private
|
|
3492
|
+
* @param {Array} docs - Array of documents
|
|
3493
|
+
* @param {Object} stage - Aggregate stage descriptor
|
|
3494
|
+
* @returns {number} Computed aggregate value
|
|
3495
|
+
*/
|
|
3496
|
+
_computeAggregate(e, t) {
|
|
3497
|
+
const { op: r, field: s } = t;
|
|
3498
|
+
switch (r) {
|
|
3499
|
+
case "count":
|
|
3500
|
+
return e.length;
|
|
3501
|
+
case "sum": {
|
|
3502
|
+
let n = 0;
|
|
3503
|
+
for (const i of e) {
|
|
3504
|
+
const a = Number(i[s]);
|
|
3505
|
+
isNaN(a) || (n += a);
|
|
3506
|
+
}
|
|
3507
|
+
return n;
|
|
3508
|
+
}
|
|
3509
|
+
case "avg": {
|
|
3510
|
+
let n = 0, i = 0;
|
|
3511
|
+
for (const a of e) {
|
|
3512
|
+
const l = Number(a[s]);
|
|
3513
|
+
isNaN(l) || (n += l, i++);
|
|
3514
|
+
}
|
|
3515
|
+
return i > 0 ? n / i : 0;
|
|
3516
|
+
}
|
|
3517
|
+
case "min": {
|
|
3518
|
+
let n = 1 / 0;
|
|
3519
|
+
for (const i of e) {
|
|
3520
|
+
const a = Number(i[s]);
|
|
3521
|
+
!isNaN(a) && a < n && (n = a);
|
|
3522
|
+
}
|
|
3523
|
+
return n === 1 / 0 ? 0 : n;
|
|
3524
|
+
}
|
|
3525
|
+
case "max": {
|
|
3526
|
+
let n = -1 / 0;
|
|
3527
|
+
for (const i of e) {
|
|
3528
|
+
const a = Number(i[s]);
|
|
3529
|
+
!isNaN(a) && a > n && (n = a);
|
|
3530
|
+
}
|
|
3531
|
+
return n === -1 / 0 ? 0 : n;
|
|
3532
|
+
}
|
|
3533
|
+
default:
|
|
3534
|
+
return 0;
|
|
3535
|
+
}
|
|
3536
|
+
}
|
|
3537
|
+
/**
|
|
3538
|
+
* Execute the aggregation pipeline and return results.
|
|
3539
|
+
*
|
|
3540
|
+
* Processing order:
|
|
3541
|
+
* 1. match stages filter the working dataset
|
|
3542
|
+
* 2. group / bucket stages partition data into groups
|
|
3543
|
+
* 3. aggregate stages compute values per group (or globally)
|
|
3544
|
+
* 4. sort / limit are applied to the final result set
|
|
3545
|
+
*
|
|
3546
|
+
* @returns {Object|Array} Aggregation results
|
|
3547
|
+
*/
|
|
3548
|
+
execute() {
|
|
3549
|
+
let e = [...this.data];
|
|
3550
|
+
const t = [], r = [];
|
|
3551
|
+
let s = null, n = null;
|
|
3552
|
+
const i = [];
|
|
3553
|
+
for (const o of this.stages)
|
|
3554
|
+
switch (o.type) {
|
|
3555
|
+
case "match":
|
|
3556
|
+
t.push(o);
|
|
3557
|
+
break;
|
|
3558
|
+
case "group":
|
|
3559
|
+
s = o;
|
|
3560
|
+
break;
|
|
3561
|
+
case "bucket":
|
|
3562
|
+
n = o;
|
|
3563
|
+
break;
|
|
3564
|
+
case "aggregate":
|
|
3565
|
+
r.push(o);
|
|
3566
|
+
break;
|
|
3567
|
+
case "sort":
|
|
3568
|
+
case "limit":
|
|
3569
|
+
i.push(o);
|
|
3570
|
+
break;
|
|
3571
|
+
}
|
|
3572
|
+
for (const o of t)
|
|
3573
|
+
e = e.filter((c) => this._matchesQuery(c, o.query));
|
|
3574
|
+
let a = null;
|
|
3575
|
+
if (n) {
|
|
3576
|
+
a = /* @__PURE__ */ new Map();
|
|
3577
|
+
const { timestampField: o, interval: c } = n;
|
|
3578
|
+
for (const h of e) {
|
|
3579
|
+
const g = h[o];
|
|
3580
|
+
if (g == null)
|
|
3581
|
+
continue;
|
|
3582
|
+
const d = new Date(typeof g == "number" ? g * 1e3 : g);
|
|
3583
|
+
if (isNaN(d.getTime()))
|
|
3584
|
+
continue;
|
|
3585
|
+
const m = this._truncateDate(d, c);
|
|
3586
|
+
a.has(m) || a.set(m, []), a.get(m).push(h);
|
|
3587
|
+
}
|
|
3588
|
+
} else if (s) {
|
|
3589
|
+
a = /* @__PURE__ */ new Map();
|
|
3590
|
+
const { field: o } = s;
|
|
3591
|
+
for (const c of e) {
|
|
3592
|
+
const h = c[o] !== void 0 && c[o] !== null ? String(c[o]) : "__null__";
|
|
3593
|
+
a.has(h) || a.set(h, []), a.get(h).push(c);
|
|
3594
|
+
}
|
|
3595
|
+
}
|
|
3596
|
+
let l;
|
|
3597
|
+
if (a) {
|
|
3598
|
+
l = [];
|
|
3599
|
+
for (const [o, c] of a) {
|
|
3600
|
+
const h = { _id: o === "__null__" ? null : o };
|
|
3601
|
+
for (const g of r)
|
|
3602
|
+
h[g.alias] = this._computeAggregate(c, g);
|
|
3603
|
+
l.push(h);
|
|
3604
|
+
}
|
|
3605
|
+
} else if (r.length > 0) {
|
|
3606
|
+
const o = {};
|
|
3607
|
+
for (const c of r)
|
|
3608
|
+
o[c.alias] = this._computeAggregate(e, c);
|
|
3609
|
+
l = o;
|
|
3610
|
+
} else
|
|
3611
|
+
l = e;
|
|
3612
|
+
if (Array.isArray(l))
|
|
3613
|
+
for (const o of i)
|
|
3614
|
+
if (o.type === "sort") {
|
|
3615
|
+
const { field: c, order: h } = o, g = h === "desc" ? -1 : 1;
|
|
3616
|
+
l.sort((d, m) => {
|
|
3617
|
+
const y = d[c], w = m[c];
|
|
3618
|
+
return y < w ? -1 * g : y > w ? 1 * g : 0;
|
|
3619
|
+
});
|
|
3620
|
+
} else
|
|
3621
|
+
o.type === "limit" && (l = l.slice(0, o.n));
|
|
3622
|
+
return l;
|
|
3623
|
+
}
|
|
3624
|
+
}
|
|
3625
|
+
class V {
|
|
3626
|
+
constructor(e, t = {}) {
|
|
3627
|
+
this.gun = e, this.options = {
|
|
3628
|
+
logLevel: "info",
|
|
3629
|
+
enableAutoTransform: !0,
|
|
3630
|
+
...t
|
|
3631
|
+
}, this.logger = new p({
|
|
3632
|
+
context: "DataTransformer",
|
|
3633
|
+
level: this.options.logLevel
|
|
3634
|
+
}), this.models = /* @__PURE__ */ new Map(), this.transformers = /* @__PURE__ */ new Map(), this.logger.info("DataTransformer initialized", { options: this.options });
|
|
3635
|
+
}
|
|
3636
|
+
/**
|
|
3637
|
+
* Register a data model
|
|
3638
|
+
* @param {string} name - Model name
|
|
3639
|
+
* @param {Object} schema - Model schema
|
|
3640
|
+
* @param {Object} options - Model options
|
|
3641
|
+
* @returns {DataModel} Created model
|
|
3642
|
+
*/
|
|
3643
|
+
model(e, t, r = {}) {
|
|
3644
|
+
if (this.models.has(e))
|
|
3645
|
+
return this.models.get(e);
|
|
3646
|
+
const s = new H(e, t, this.gun, this, r);
|
|
3647
|
+
return this.models.set(e, s), this.logger.info("Model registered", { name: e, schema: t }), s;
|
|
3648
|
+
}
|
|
3649
|
+
/**
|
|
3650
|
+
* Register an event transformer
|
|
3651
|
+
* @param {string} eventName - Event name to transform
|
|
3652
|
+
* @param {string} modelName - Target model name
|
|
3653
|
+
* @param {Function} transformFn - Transformation function
|
|
3654
|
+
*/
|
|
3655
|
+
registerTransformer(e, t, r) {
|
|
3656
|
+
if (!this.models.has(t))
|
|
3657
|
+
throw new f(`Model '${t}' not found. Register the model first.`);
|
|
3658
|
+
this.transformers.set(e, {
|
|
3659
|
+
modelName: t,
|
|
3660
|
+
transformFn: r
|
|
3661
|
+
}), this.logger.info("Transformer registered", { eventName: e, modelName: t });
|
|
3662
|
+
}
|
|
3663
|
+
/**
|
|
3664
|
+
* Transform and store an event
|
|
3665
|
+
* @param {Object} event - Blockchain event
|
|
3666
|
+
* @returns {Promise<Object|null>} Transformed data or null if no transformer
|
|
3667
|
+
*/
|
|
3668
|
+
async transformEvent(e) {
|
|
3669
|
+
if (!e || !e.eventName)
|
|
3670
|
+
return null;
|
|
3671
|
+
const t = this.transformers.get(e.eventName);
|
|
3672
|
+
if (!t)
|
|
3673
|
+
return this.logger.debug("No transformer found for event", { eventName: e.eventName }), null;
|
|
3674
|
+
try {
|
|
3675
|
+
const { modelName: r, transformFn: s } = t, n = this.models.get(r), i = await s(e), a = await n.save(i);
|
|
3676
|
+
return this.logger.info("Event transformed and saved", {
|
|
3677
|
+
eventName: e.eventName,
|
|
3678
|
+
modelName: r,
|
|
3679
|
+
id: a.id
|
|
3680
|
+
}), a;
|
|
3681
|
+
} catch (r) {
|
|
3682
|
+
throw this.logger.error("Failed to transform event", {
|
|
3683
|
+
error: r.message,
|
|
3684
|
+
eventName: e.eventName
|
|
3685
|
+
}), r;
|
|
3686
|
+
}
|
|
3687
|
+
}
|
|
3688
|
+
/**
|
|
3689
|
+
* Get a registered model
|
|
3690
|
+
* @param {string} name - Model name
|
|
3691
|
+
* @returns {DataModel} Model instance
|
|
3692
|
+
*/
|
|
3693
|
+
getModel(e) {
|
|
3694
|
+
const t = this.models.get(e);
|
|
3695
|
+
if (!t)
|
|
3696
|
+
throw new f(`Model '${e}' not found`);
|
|
3697
|
+
return t;
|
|
3698
|
+
}
|
|
3699
|
+
/**
|
|
3700
|
+
* Get all registered models
|
|
3701
|
+
* @returns {Map<string, DataModel>} All models
|
|
3702
|
+
*/
|
|
3703
|
+
getModels() {
|
|
3704
|
+
return new Map(this.models);
|
|
3705
|
+
}
|
|
3706
|
+
/**
|
|
3707
|
+
* Get transformation statistics
|
|
3708
|
+
* @returns {Object} Statistics
|
|
3709
|
+
*/
|
|
3710
|
+
getStats() {
|
|
3711
|
+
return {
|
|
3712
|
+
modelsCount: this.models.size,
|
|
3713
|
+
transformersCount: this.transformers.size,
|
|
3714
|
+
registeredModels: Array.from(this.models.keys()),
|
|
3715
|
+
registeredTransformers: Array.from(this.transformers.keys())
|
|
3716
|
+
};
|
|
3717
|
+
}
|
|
3718
|
+
/**
|
|
3719
|
+
* Create an AggregationPipeline from raw data.
|
|
3720
|
+
* @param {Array} data - Array of documents to aggregate
|
|
3721
|
+
* @returns {AggregationPipeline} A new AggregationPipeline instance
|
|
3722
|
+
*/
|
|
3723
|
+
aggregate(e) {
|
|
3724
|
+
return new M(e);
|
|
3725
|
+
}
|
|
3726
|
+
/**
|
|
3727
|
+
* Create a SchemaMigration instance for a model.
|
|
3728
|
+
* @param {string} modelName - Name of the model
|
|
3729
|
+
* @returns {SchemaMigration} A new SchemaMigration instance
|
|
3730
|
+
*/
|
|
3731
|
+
migration(e) {
|
|
3732
|
+
return new j(e, this.gun);
|
|
3733
|
+
}
|
|
3734
|
+
}
|
|
3735
|
+
class Q {
|
|
3736
|
+
constructor(e) {
|
|
3737
|
+
this.configManager = new v(e);
|
|
3738
|
+
const t = this.configManager.getAll();
|
|
3739
|
+
this.logger = new p({
|
|
3740
|
+
context: "Magazine",
|
|
3741
|
+
level: t.logLevel || "info"
|
|
3742
|
+
}), this.logger.info("Initializing Magazine", {
|
|
3743
|
+
name: t.name,
|
|
3744
|
+
address: t.address,
|
|
3745
|
+
networkId: t.networkId
|
|
3746
|
+
}), this.retryOptions = this.configManager.getRetryOptions(), this.networkManager = new z(t, this.retryOperation.bind(this)), this.eventStore = new E(t.gun, this.retryOperation.bind(this)), this.eventSyncer = new T(
|
|
3747
|
+
this.networkManager,
|
|
3748
|
+
this.eventStore,
|
|
3749
|
+
this.configManager,
|
|
3750
|
+
this.retryOperation.bind(this)
|
|
3751
|
+
), this.eventMetadata = new A(this.networkManager, this.logger), this.debugUtils = new R(this, this.logger), this.dataTransformer = new V(
|
|
3752
|
+
this.eventStore.getGunInstance(),
|
|
3753
|
+
{
|
|
3754
|
+
logLevel: t.logLevel,
|
|
3755
|
+
enableAutoTransform: t.enableAutoTransform !== !1
|
|
3756
|
+
}
|
|
3757
|
+
), this.name = t.name, this.address = t.address, this.networkId = t.networkId, this.abi = t.abi, this.logger.info("Magazine initialized successfully", {
|
|
3758
|
+
name: this.name,
|
|
3759
|
+
address: this.address,
|
|
3760
|
+
networkId: this.networkId,
|
|
3761
|
+
eventCount: Object.keys(this.networkManager.eventsByTopic).length,
|
|
3762
|
+
componentCount: 6,
|
|
3763
|
+
debugModeEnabled: this.debugUtils.isDebugMode(),
|
|
3764
|
+
dataTransformerEnabled: !0
|
|
3765
|
+
});
|
|
3766
|
+
}
|
|
3767
|
+
// Get configuration manager for advanced configuration access
|
|
3768
|
+
getConfigManager() {
|
|
3769
|
+
return this.configManager;
|
|
3770
|
+
}
|
|
3771
|
+
// Get network manager for advanced network operations
|
|
3772
|
+
getNetworkManager() {
|
|
3773
|
+
return this.networkManager;
|
|
3774
|
+
}
|
|
3775
|
+
// Get event store for advanced storage operations
|
|
3776
|
+
getEventStore() {
|
|
3777
|
+
return this.eventStore;
|
|
3778
|
+
}
|
|
3779
|
+
// Get event syncer for advanced synchronization operations
|
|
3780
|
+
getEventSyncer() {
|
|
3781
|
+
return this.eventSyncer;
|
|
3782
|
+
}
|
|
3783
|
+
// Get logger for external logging operations
|
|
3784
|
+
getLogger() {
|
|
3785
|
+
return this.logger;
|
|
3786
|
+
}
|
|
3787
|
+
// Set log level dynamically
|
|
3788
|
+
setLogLevel(e) {
|
|
3789
|
+
this.logger.setLevel(e), this.logger.info("Log level updated", { newLevel: e });
|
|
3790
|
+
}
|
|
3791
|
+
// Retry wrapper for async operations
|
|
3792
|
+
async retryOperation(e, t = "") {
|
|
3793
|
+
const { maxRetries: r, retryDelay: s, timeout: n } = this.retryOptions;
|
|
3794
|
+
let i;
|
|
3795
|
+
this.logger.debug("Starting retry operation", { context: t, maxRetries: r, timeout: n });
|
|
3796
|
+
for (let a = 1; a <= r; a++)
|
|
3797
|
+
try {
|
|
3798
|
+
const l = new Promise(
|
|
3799
|
+
(c, h) => setTimeout(() => h(new Error(`Operation timeout: ${t}`)), n)
|
|
3800
|
+
), o = await Promise.race([e(), l]);
|
|
3801
|
+
return a > 1 && this.logger.info("Retry operation succeeded", {
|
|
3802
|
+
context: t,
|
|
3803
|
+
attempt: a,
|
|
3804
|
+
maxRetries: r
|
|
3805
|
+
}), o;
|
|
3806
|
+
} catch (l) {
|
|
3807
|
+
if (i = l, this.logger.warn("Retry operation failed", {
|
|
3808
|
+
context: t,
|
|
3809
|
+
attempt: a,
|
|
3810
|
+
maxRetries: r,
|
|
3811
|
+
error: l.message
|
|
3812
|
+
}), a < r) {
|
|
3813
|
+
const o = s * Math.pow(2, a - 1);
|
|
3814
|
+
this.logger.debug("Retrying operation", {
|
|
3815
|
+
context: t,
|
|
3816
|
+
delay: `${o}ms`,
|
|
3817
|
+
nextAttempt: a + 1
|
|
3818
|
+
}), await new Promise((c) => setTimeout(c, o));
|
|
3819
|
+
} else
|
|
3820
|
+
this.logger.error("Retry operation exhausted", {
|
|
3821
|
+
context: t,
|
|
3822
|
+
totalAttempts: r,
|
|
3823
|
+
finalError: l.message,
|
|
3824
|
+
stack: l.stack
|
|
3825
|
+
});
|
|
3826
|
+
}
|
|
3827
|
+
throw i;
|
|
3828
|
+
}
|
|
3829
|
+
// Start syncing events from the blockchain to the magazine
|
|
3830
|
+
startSync() {
|
|
3831
|
+
this.logger.info("Starting automatic sync"), this.stopSync(), this.syncInterval = setInterval(async () => {
|
|
3832
|
+
try {
|
|
3833
|
+
await this.syncEvents();
|
|
3834
|
+
} catch (e) {
|
|
3835
|
+
this.logger.error("Automatic sync error", {
|
|
3836
|
+
error: e.message,
|
|
3837
|
+
stack: e.stack
|
|
3838
|
+
});
|
|
3839
|
+
}
|
|
3840
|
+
}, 5e3), this.syncEvents().catch((e) => {
|
|
3841
|
+
this.logger.error("Initial sync error", {
|
|
3842
|
+
error: e.message,
|
|
3843
|
+
stack: e.stack
|
|
3844
|
+
});
|
|
3845
|
+
}), this.logger.info("Automatic sync started", { interval: "5000ms" });
|
|
3846
|
+
}
|
|
3847
|
+
/**
|
|
3848
|
+
* Start real-time event streaming (requires WebSocket provider)
|
|
3849
|
+
*/
|
|
3850
|
+
startRealtimeSync() {
|
|
3851
|
+
return this.eventSyncer.startRealtimeSync();
|
|
3852
|
+
}
|
|
3853
|
+
/**
|
|
3854
|
+
* Stop real-time event streaming
|
|
3855
|
+
*/
|
|
3856
|
+
stopRealtimeSync() {
|
|
3857
|
+
return this.eventSyncer.stopRealtimeSync();
|
|
3858
|
+
}
|
|
3859
|
+
/**
|
|
3860
|
+
* Destroy the magazine instance, stopping sync and cleaning up listeners
|
|
3861
|
+
*/
|
|
3862
|
+
destroy() {
|
|
3863
|
+
this.stopSync(), this.stopRealtimeSync(), this.eventStore.destroy(), this.logger.info("Magazine instance destroyed");
|
|
3864
|
+
}
|
|
3865
|
+
// Stop syncing events from the blockchain to the magazine
|
|
3866
|
+
stopSync() {
|
|
3867
|
+
this.syncInterval && (this.logger.info("Stopping automatic sync"), clearInterval(this.syncInterval), this.syncInterval = void 0);
|
|
3868
|
+
}
|
|
3869
|
+
// Sync events from the blockchain to the magazine - delegates to EventSyncer
|
|
3870
|
+
async syncEvents() {
|
|
3871
|
+
return this.eventSyncer.syncEvents();
|
|
3872
|
+
}
|
|
3873
|
+
// Sync events from a specific block range
|
|
3874
|
+
async syncEventsFromRange(e, t) {
|
|
3875
|
+
return this.eventSyncer.syncEventsFromRange(e, t);
|
|
3876
|
+
}
|
|
3877
|
+
// Sync events from a specific transaction
|
|
3878
|
+
async syncEventsFromTransaction(e) {
|
|
3879
|
+
return this.eventSyncer.syncEventsFromTransaction(e);
|
|
3880
|
+
}
|
|
3881
|
+
// Get sync status information
|
|
3882
|
+
async getSyncStatus() {
|
|
3883
|
+
return this.eventSyncer.getSyncStatus();
|
|
3884
|
+
}
|
|
3885
|
+
// Get the start block number for syncing events - delegates to EventStore
|
|
3886
|
+
async getStartBlockNumber() {
|
|
3887
|
+
return this.eventStore.getStartBlockNumber();
|
|
3888
|
+
}
|
|
3889
|
+
// Set the start block number for syncing events - delegates to EventStore
|
|
3890
|
+
async setStartBlockNumber(e) {
|
|
3891
|
+
return this.configManager.validateBlockNumber(e, "startBlockNumber"), this.eventStore.setStartBlockNumber(e);
|
|
3892
|
+
}
|
|
3893
|
+
// Get events by name with optional filtering
|
|
3894
|
+
async getEvents(e, t = {}) {
|
|
3895
|
+
return (await this.getAllEvents({
|
|
3896
|
+
filter: { ...t, eventName: e }
|
|
3897
|
+
})).data;
|
|
3898
|
+
}
|
|
3899
|
+
// Get all events with optional filtering and pagination
|
|
3900
|
+
async getAllEvents(e = {}) {
|
|
3901
|
+
return this.eventStore.getAllEvents(e);
|
|
3902
|
+
}
|
|
3903
|
+
/**
|
|
3904
|
+
* Query events using fluent API
|
|
3905
|
+
* @returns {Promise<Object>} Query builder
|
|
3906
|
+
* @example
|
|
3907
|
+
* const results = await magazine.query()
|
|
3908
|
+
* .eventName('Transfer')
|
|
3909
|
+
* .blockRange(1000, 2000)
|
|
3910
|
+
* .where('value', { gte: 1000 })
|
|
3911
|
+
* .orderBy('blockNumber', 'desc')
|
|
3912
|
+
* .execute();
|
|
3913
|
+
*/
|
|
3914
|
+
async query() {
|
|
3915
|
+
return this.eventStore.query();
|
|
3916
|
+
}
|
|
3917
|
+
/**
|
|
3918
|
+
* Get events by transaction hash
|
|
3919
|
+
* @param {string} transactionHash - Transaction hash to search for
|
|
3920
|
+
* @returns {Promise<Array>} Events from the transaction
|
|
3921
|
+
*/
|
|
3922
|
+
async getEventsByTransactionHash(e) {
|
|
3923
|
+
if (!e || typeof e != "string")
|
|
3924
|
+
throw new Error("Transaction hash must be a non-empty string");
|
|
3925
|
+
return this.eventStore.getEventsByTransactionHash(e);
|
|
3926
|
+
}
|
|
3927
|
+
/**
|
|
3928
|
+
* Get events by block number
|
|
3929
|
+
* @param {number} blockNumber - Block number to search for
|
|
3930
|
+
* @returns {Promise<Array>} Events from the block
|
|
3931
|
+
*/
|
|
3932
|
+
async getEventsByBlockNumber(e) {
|
|
3933
|
+
if (!Number.isInteger(e) || e < 0)
|
|
3934
|
+
throw new Error("Block number must be a non-negative integer");
|
|
3935
|
+
return this.eventStore.getEventsByBlockNumber(e);
|
|
3936
|
+
}
|
|
3937
|
+
/**
|
|
3938
|
+
* Create async iterator for events
|
|
3939
|
+
* @param {Object} options - Iterator options
|
|
3940
|
+
* @param {Object} options.filter - Filter criteria
|
|
3941
|
+
* @param {Object} options.sort - Sort options
|
|
3942
|
+
* @param {number} options.pageSize - Items per iteration batch
|
|
3943
|
+
* @returns {AsyncIterator} Async iterator for events
|
|
3944
|
+
* @example
|
|
3945
|
+
* for await (const event of magazine.iterateEvents({ pageSize: 50 })) {
|
|
3946
|
+
* console.log(event);
|
|
3947
|
+
* }
|
|
3948
|
+
*/
|
|
3949
|
+
async *iterateEvents(e = {}) {
|
|
3950
|
+
yield* this.eventStore.iterateEvents(e);
|
|
3951
|
+
}
|
|
3952
|
+
// Get network information
|
|
3953
|
+
async getNetworkInfo() {
|
|
3954
|
+
return this.networkManager.getNetworkInfo();
|
|
3955
|
+
}
|
|
3956
|
+
// Validate network and contract
|
|
3957
|
+
async validateNetwork() {
|
|
3958
|
+
return this.networkManager.validateNetwork();
|
|
3959
|
+
}
|
|
3960
|
+
// Get latest block number
|
|
3961
|
+
async getLatestBlockNumber() {
|
|
3962
|
+
return this.networkManager.getLatestBlockNumber();
|
|
3963
|
+
}
|
|
3964
|
+
// Publish the magazine to the decentralized network
|
|
3965
|
+
async publish(e = {}) {
|
|
3966
|
+
const t = this.configManager.validateOptions(e, ["alias", "url"]);
|
|
3967
|
+
if (t.alias && (typeof t.alias != "string" || t.alias.trim().length === 0))
|
|
3968
|
+
throw new Error("alias must be a non-empty string");
|
|
3969
|
+
if (t.url) {
|
|
3970
|
+
if (typeof t.url != "string")
|
|
3971
|
+
throw new Error("url must be a string");
|
|
3972
|
+
try {
|
|
3973
|
+
new URL(t.url);
|
|
3974
|
+
} catch {
|
|
3975
|
+
throw new Error("url must be a valid URL");
|
|
3976
|
+
}
|
|
3977
|
+
}
|
|
3978
|
+
const r = t.alias ? t.alias : this.name.toLowerCase().replace(/\s/g, "-"), s = t.url ? t.url : "https://gunjs.herokuapp.com/gun", n = {
|
|
3979
|
+
name: this.name,
|
|
3980
|
+
address: this.address,
|
|
3981
|
+
networkId: this.networkId,
|
|
3982
|
+
abi: this.abi
|
|
3983
|
+
};
|
|
3984
|
+
return this.eventStore.publishMagazine(r, n, s);
|
|
3985
|
+
}
|
|
3986
|
+
// Subscribe to a magazine on the decentralized network
|
|
3987
|
+
static async subscribe(e, t) {
|
|
3988
|
+
if (!e || typeof e != "string")
|
|
3989
|
+
throw new Error("magazineId must be a non-empty string");
|
|
3990
|
+
if (!t || typeof t != "object")
|
|
3991
|
+
throw new Error("config must be an object");
|
|
3992
|
+
const r = ["gun"];
|
|
3993
|
+
for (const n of r)
|
|
3994
|
+
if (!t[n])
|
|
3995
|
+
throw new Error(`Config field '${n}' is required for subscription`);
|
|
3996
|
+
return new E(t.gun, () => {
|
|
3997
|
+
}).subscribeMagazine(e);
|
|
3998
|
+
}
|
|
3999
|
+
// Read events from the magazine - delegates to EventStore
|
|
4000
|
+
on(e, t) {
|
|
4001
|
+
if (typeof t != "function")
|
|
4002
|
+
throw new Error("callback must be a function");
|
|
4003
|
+
this.eventStore.getGunInstance().get("events").get(e).map().on(t);
|
|
4004
|
+
}
|
|
4005
|
+
/**
|
|
4006
|
+
* Health check endpoint - performs system health checks
|
|
4007
|
+
* @returns {Promise<Object>} Health check results
|
|
4008
|
+
*/
|
|
4009
|
+
async healthCheck() {
|
|
4010
|
+
return this.debugUtils.healthCheck();
|
|
4011
|
+
}
|
|
4012
|
+
/**
|
|
4013
|
+
* Get performance metrics
|
|
4014
|
+
* @returns {Promise<Object>} Performance metrics
|
|
4015
|
+
*/
|
|
4016
|
+
async getPerformanceMetrics() {
|
|
4017
|
+
return this.debugUtils.getPerformanceReport();
|
|
4018
|
+
}
|
|
4019
|
+
/**
|
|
4020
|
+
* Export debug information
|
|
4021
|
+
* @param {Object} options - Export options
|
|
4022
|
+
* @returns {Promise<Object>} Debug information
|
|
4023
|
+
*/
|
|
4024
|
+
async exportDebugInfo(e = {}) {
|
|
4025
|
+
return this.debugUtils.exportDebugData();
|
|
4026
|
+
}
|
|
4027
|
+
/**
|
|
4028
|
+
* Enable or disable debug mode
|
|
4029
|
+
* @param {boolean} enabled - Whether to enable debug mode
|
|
4030
|
+
*/
|
|
4031
|
+
setDebugMode(e) {
|
|
4032
|
+
this.debugUtils.setDebugMode(e);
|
|
4033
|
+
}
|
|
4034
|
+
/**
|
|
4035
|
+
* Check if debug mode is enabled
|
|
4036
|
+
* @returns {boolean} Debug mode status
|
|
4037
|
+
*/
|
|
4038
|
+
isDebugMode() {
|
|
4039
|
+
return this.debugUtils.isDebugMode();
|
|
4040
|
+
}
|
|
4041
|
+
/**
|
|
4042
|
+
* Get configuration schema documentation
|
|
4043
|
+
* @returns {Object} Schema documentation
|
|
4044
|
+
*/
|
|
4045
|
+
getConfigSchema() {
|
|
4046
|
+
return this.configManager.getSchemaDocumentation();
|
|
4047
|
+
}
|
|
4048
|
+
/**
|
|
4049
|
+
* Generate example configuration
|
|
4050
|
+
* @param {Object} options - Generation options
|
|
4051
|
+
* @returns {Object} Example configuration
|
|
4052
|
+
*/
|
|
4053
|
+
static generateExampleConfig(e = {}) {
|
|
4054
|
+
return new v({
|
|
4055
|
+
name: "temp",
|
|
4056
|
+
address: "0x0000000000000000000000000000000000000000",
|
|
4057
|
+
abi: [],
|
|
4058
|
+
provider: "http://localhost:8545",
|
|
4059
|
+
networkId: 1
|
|
4060
|
+
}).generateExampleConfig(e);
|
|
4061
|
+
}
|
|
4062
|
+
/**
|
|
4063
|
+
* Validate configuration without creating an instance
|
|
4064
|
+
* @static
|
|
4065
|
+
* @param {Object} config - Configuration to validate
|
|
4066
|
+
* @returns {Object} Validation result
|
|
4067
|
+
*/
|
|
4068
|
+
static validateConfig(e) {
|
|
4069
|
+
return v.validateConfiguration(e);
|
|
4070
|
+
}
|
|
4071
|
+
/**
|
|
4072
|
+
* Get compression statistics from the event store
|
|
4073
|
+
* @returns {Promise<Object>} Compression stats
|
|
4074
|
+
*/
|
|
4075
|
+
async getCompressionStats() {
|
|
4076
|
+
return this.eventStore.getCompressionStats();
|
|
4077
|
+
}
|
|
4078
|
+
// === Data Transformation API ===
|
|
4079
|
+
/**
|
|
4080
|
+
* Create or get a data model for structured storage
|
|
4081
|
+
* @param {string} name - Model name
|
|
4082
|
+
* @param {Object} schema - Model schema definition
|
|
4083
|
+
* @param {Object} options - Model options
|
|
4084
|
+
* @returns {Object} DataModel instance
|
|
4085
|
+
*/
|
|
4086
|
+
model(e, t, r = {}) {
|
|
4087
|
+
return this.dataTransformer.model(e, t, r);
|
|
4088
|
+
}
|
|
4089
|
+
/**
|
|
4090
|
+
* Register an event transformer that converts blockchain events to model data
|
|
4091
|
+
* @param {string} eventName - Event name to transform
|
|
4092
|
+
* @param {string} modelName - Target model name
|
|
4093
|
+
* @param {Function} transformFn - Transformation function
|
|
4094
|
+
*/
|
|
4095
|
+
registerTransformer(e, t, r) {
|
|
4096
|
+
return this.dataTransformer.registerTransformer(e, t, r);
|
|
4097
|
+
}
|
|
4098
|
+
/**
|
|
4099
|
+
* Get a registered data model
|
|
4100
|
+
* @param {string} name - Model name
|
|
4101
|
+
* @returns {Object} DataModel instance
|
|
4102
|
+
*/
|
|
4103
|
+
getModel(e) {
|
|
4104
|
+
return this.dataTransformer.getModel(e);
|
|
4105
|
+
}
|
|
4106
|
+
/**
|
|
4107
|
+
* Transform an event using registered transformers
|
|
4108
|
+
* @param {Object} event - Blockchain event to transform
|
|
4109
|
+
* @returns {Promise<Object|null>} Transformed data or null
|
|
4110
|
+
*/
|
|
4111
|
+
async transformEvent(e) {
|
|
4112
|
+
return this.dataTransformer.transformEvent(e);
|
|
4113
|
+
}
|
|
4114
|
+
/**
|
|
4115
|
+
* Get data transformation statistics
|
|
4116
|
+
* @returns {Object} Transformation stats
|
|
4117
|
+
*/
|
|
4118
|
+
getTransformationStats() {
|
|
4119
|
+
return this.dataTransformer.getStats();
|
|
4120
|
+
}
|
|
4121
|
+
/**
|
|
4122
|
+
* Get all registered models
|
|
4123
|
+
* @returns {Map} Map of model name to DataModel instance
|
|
4124
|
+
*/
|
|
4125
|
+
getModels() {
|
|
4126
|
+
return this.dataTransformer.getModels();
|
|
4127
|
+
}
|
|
4128
|
+
}
|
|
4129
|
+
export {
|
|
4130
|
+
M as AggregationPipeline,
|
|
4131
|
+
v as ConfigManager,
|
|
4132
|
+
D as DataCompressor,
|
|
4133
|
+
H as DataModel,
|
|
4134
|
+
V as DataTransformer,
|
|
4135
|
+
R as DebugUtils,
|
|
4136
|
+
C as EventFilter,
|
|
4137
|
+
A as EventMetadata,
|
|
4138
|
+
E as EventStore,
|
|
4139
|
+
T as EventSyncer,
|
|
4140
|
+
p as Logger,
|
|
4141
|
+
f as ModelError,
|
|
4142
|
+
z as NetworkManager,
|
|
4143
|
+
J as PaginationHelper,
|
|
4144
|
+
P as Paginator,
|
|
4145
|
+
j as SchemaMigration,
|
|
4146
|
+
B as ValidationError,
|
|
4147
|
+
Q as default
|
|
4148
|
+
};
|
|
4149
|
+
//# sourceMappingURL=magazine.es.js.map
|