@openclaw/nostr 2026.5.2 → 2026.5.3-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/api.js +532 -0
- package/dist/channel-DfEqBtUh.js +1466 -0
- package/dist/channel-plugin-api.js +2 -0
- package/dist/config-schema-DIk4jlBg.js +64 -0
- package/dist/default-relays-DLwdWOTu.js +4 -0
- package/dist/inbound-direct-dm-runtime-22bZWcIW.js +2 -0
- package/dist/index.js +84 -0
- package/dist/runtime-api.js +2 -0
- package/dist/setup-api.js +2 -0
- package/dist/setup-entry.js +11 -0
- package/dist/setup-plugin-api.js +165 -0
- package/dist/setup-surface-DxAaUTyC.js +336 -0
- package/dist/test-api.js +2 -0
- package/package.json +15 -6
- package/api.ts +0 -10
- package/channel-plugin-api.ts +0 -1
- package/index.ts +0 -97
- package/runtime-api.ts +0 -6
- package/setup-api.ts +0 -1
- package/setup-entry.ts +0 -9
- package/setup-plugin-api.ts +0 -3
- package/src/channel-api.ts +0 -15
- package/src/channel.inbound.test.ts +0 -176
- package/src/channel.outbound.test.ts +0 -128
- package/src/channel.setup.ts +0 -231
- package/src/channel.test.ts +0 -519
- package/src/channel.ts +0 -207
- package/src/config-schema.ts +0 -98
- package/src/default-relays.ts +0 -1
- package/src/gateway.ts +0 -302
- package/src/inbound-direct-dm-runtime.ts +0 -1
- package/src/metrics.ts +0 -458
- package/src/nostr-bus.fuzz.test.ts +0 -360
- package/src/nostr-bus.inbound.test.ts +0 -526
- package/src/nostr-bus.integration.test.ts +0 -472
- package/src/nostr-bus.test.ts +0 -190
- package/src/nostr-bus.ts +0 -789
- package/src/nostr-key-utils.ts +0 -94
- package/src/nostr-profile-core.ts +0 -134
- package/src/nostr-profile-http-runtime.ts +0 -6
- package/src/nostr-profile-http.test.ts +0 -632
- package/src/nostr-profile-http.ts +0 -594
- package/src/nostr-profile-import.test.ts +0 -119
- package/src/nostr-profile-import.ts +0 -262
- package/src/nostr-profile-url-safety.ts +0 -21
- package/src/nostr-profile.fuzz.test.ts +0 -430
- package/src/nostr-profile.test.ts +0 -412
- package/src/nostr-profile.ts +0 -144
- package/src/nostr-state-store.test.ts +0 -237
- package/src/nostr-state-store.ts +0 -223
- package/src/runtime.ts +0 -9
- package/src/seen-tracker.ts +0 -289
- package/src/session-route.ts +0 -25
- package/src/setup-surface.ts +0 -265
- package/src/test-fixtures.ts +0 -45
- package/src/types.ts +0 -117
- package/test/setup.ts +0 -5
- package/test-api.ts +0 -1
- package/tsconfig.json +0 -16
package/src/metrics.ts
DELETED
|
@@ -1,458 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Comprehensive metrics system for Nostr bus observability.
|
|
3
|
-
* Provides clear insight into what's happening with events, relays, and operations.
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
// ============================================================================
|
|
7
|
-
// Metric Types
|
|
8
|
-
// ============================================================================
|
|
9
|
-
|
|
10
|
-
type EventMetricName =
|
|
11
|
-
| "event.received"
|
|
12
|
-
| "event.processed"
|
|
13
|
-
| "event.duplicate"
|
|
14
|
-
| "event.rejected.invalid_shape"
|
|
15
|
-
| "event.rejected.wrong_kind"
|
|
16
|
-
| "event.rejected.stale"
|
|
17
|
-
| "event.rejected.future"
|
|
18
|
-
| "event.rejected.rate_limited"
|
|
19
|
-
| "event.rejected.invalid_signature"
|
|
20
|
-
| "event.rejected.oversized_ciphertext"
|
|
21
|
-
| "event.rejected.oversized_plaintext"
|
|
22
|
-
| "event.rejected.decrypt_failed"
|
|
23
|
-
| "event.rejected.self_message";
|
|
24
|
-
|
|
25
|
-
type RelayMetricName =
|
|
26
|
-
| "relay.connect"
|
|
27
|
-
| "relay.disconnect"
|
|
28
|
-
| "relay.reconnect"
|
|
29
|
-
| "relay.error"
|
|
30
|
-
| "relay.message.event"
|
|
31
|
-
| "relay.message.eose"
|
|
32
|
-
| "relay.message.closed"
|
|
33
|
-
| "relay.message.notice"
|
|
34
|
-
| "relay.message.ok"
|
|
35
|
-
| "relay.message.auth"
|
|
36
|
-
| "relay.circuit_breaker.open"
|
|
37
|
-
| "relay.circuit_breaker.close"
|
|
38
|
-
| "relay.circuit_breaker.half_open";
|
|
39
|
-
|
|
40
|
-
type RateLimitMetricName = "rate_limit.per_sender" | "rate_limit.global";
|
|
41
|
-
|
|
42
|
-
type DecryptMetricName = "decrypt.success" | "decrypt.failure";
|
|
43
|
-
|
|
44
|
-
type MemoryMetricName = "memory.seen_tracker_size" | "memory.rate_limiter_entries";
|
|
45
|
-
|
|
46
|
-
export type MetricName =
|
|
47
|
-
| EventMetricName
|
|
48
|
-
| RelayMetricName
|
|
49
|
-
| RateLimitMetricName
|
|
50
|
-
| DecryptMetricName
|
|
51
|
-
| MemoryMetricName;
|
|
52
|
-
|
|
53
|
-
type RelayMetrics = {
|
|
54
|
-
connects: number;
|
|
55
|
-
disconnects: number;
|
|
56
|
-
reconnects: number;
|
|
57
|
-
errors: number;
|
|
58
|
-
messagesReceived: {
|
|
59
|
-
event: number;
|
|
60
|
-
eose: number;
|
|
61
|
-
closed: number;
|
|
62
|
-
notice: number;
|
|
63
|
-
ok: number;
|
|
64
|
-
auth: number;
|
|
65
|
-
};
|
|
66
|
-
circuitBreakerState: "closed" | "open" | "half_open";
|
|
67
|
-
circuitBreakerOpens: number;
|
|
68
|
-
circuitBreakerCloses: number;
|
|
69
|
-
};
|
|
70
|
-
|
|
71
|
-
// ============================================================================
|
|
72
|
-
// Metric Event
|
|
73
|
-
// ============================================================================
|
|
74
|
-
|
|
75
|
-
export interface MetricEvent {
|
|
76
|
-
/** Metric name (e.g., "event.received", "relay.connect") */
|
|
77
|
-
name: MetricName;
|
|
78
|
-
/** Metric value (usually 1 for counters, or a measured value) */
|
|
79
|
-
value: number;
|
|
80
|
-
/** Unix timestamp in milliseconds */
|
|
81
|
-
timestamp: number;
|
|
82
|
-
/** Optional labels for additional context */
|
|
83
|
-
labels?: Record<string, string | number>;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
type OnMetricCallback = (event: MetricEvent) => void;
|
|
87
|
-
|
|
88
|
-
// ============================================================================
|
|
89
|
-
// Metrics Snapshot (for getMetrics())
|
|
90
|
-
// ============================================================================
|
|
91
|
-
|
|
92
|
-
export interface MetricsSnapshot {
|
|
93
|
-
/** Total events received (before any filtering) */
|
|
94
|
-
eventsReceived: number;
|
|
95
|
-
/** Events successfully processed */
|
|
96
|
-
eventsProcessed: number;
|
|
97
|
-
/** Duplicate events skipped */
|
|
98
|
-
eventsDuplicate: number;
|
|
99
|
-
/** Events rejected by reason */
|
|
100
|
-
eventsRejected: {
|
|
101
|
-
invalidShape: number;
|
|
102
|
-
wrongKind: number;
|
|
103
|
-
stale: number;
|
|
104
|
-
future: number;
|
|
105
|
-
rateLimited: number;
|
|
106
|
-
invalidSignature: number;
|
|
107
|
-
oversizedCiphertext: number;
|
|
108
|
-
oversizedPlaintext: number;
|
|
109
|
-
decryptFailed: number;
|
|
110
|
-
selfMessage: number;
|
|
111
|
-
};
|
|
112
|
-
|
|
113
|
-
/** Relay stats by URL */
|
|
114
|
-
relays: Record<string, RelayMetrics>;
|
|
115
|
-
|
|
116
|
-
/** Rate limiting stats */
|
|
117
|
-
rateLimiting: {
|
|
118
|
-
perSenderHits: number;
|
|
119
|
-
globalHits: number;
|
|
120
|
-
};
|
|
121
|
-
|
|
122
|
-
/** Decrypt stats */
|
|
123
|
-
decrypt: {
|
|
124
|
-
success: number;
|
|
125
|
-
failure: number;
|
|
126
|
-
};
|
|
127
|
-
|
|
128
|
-
/** Memory/capacity stats */
|
|
129
|
-
memory: {
|
|
130
|
-
seenTrackerSize: number;
|
|
131
|
-
rateLimiterEntries: number;
|
|
132
|
-
};
|
|
133
|
-
|
|
134
|
-
/** Snapshot timestamp */
|
|
135
|
-
snapshotAt: number;
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
// ============================================================================
|
|
139
|
-
// Metrics Collector
|
|
140
|
-
// ============================================================================
|
|
141
|
-
|
|
142
|
-
export interface NostrMetrics {
|
|
143
|
-
/** Emit a metric event */
|
|
144
|
-
emit: (name: MetricName, value?: number, labels?: Record<string, string | number>) => void;
|
|
145
|
-
|
|
146
|
-
/** Get current metrics snapshot */
|
|
147
|
-
getSnapshot: () => MetricsSnapshot;
|
|
148
|
-
|
|
149
|
-
/** Reset all metrics to zero */
|
|
150
|
-
reset: () => void;
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
/**
|
|
154
|
-
* Create a metrics collector instance.
|
|
155
|
-
* Optionally pass an onMetric callback to receive real-time metric events.
|
|
156
|
-
*/
|
|
157
|
-
export function createMetrics(onMetric?: OnMetricCallback): NostrMetrics {
|
|
158
|
-
// Counters
|
|
159
|
-
let eventsReceived = 0;
|
|
160
|
-
let eventsProcessed = 0;
|
|
161
|
-
let eventsDuplicate = 0;
|
|
162
|
-
const eventsRejected = {
|
|
163
|
-
invalidShape: 0,
|
|
164
|
-
wrongKind: 0,
|
|
165
|
-
stale: 0,
|
|
166
|
-
future: 0,
|
|
167
|
-
rateLimited: 0,
|
|
168
|
-
invalidSignature: 0,
|
|
169
|
-
oversizedCiphertext: 0,
|
|
170
|
-
oversizedPlaintext: 0,
|
|
171
|
-
decryptFailed: 0,
|
|
172
|
-
selfMessage: 0,
|
|
173
|
-
};
|
|
174
|
-
|
|
175
|
-
// Per-relay stats
|
|
176
|
-
const relays = new Map<string, RelayMetrics>();
|
|
177
|
-
|
|
178
|
-
// Rate limiting stats
|
|
179
|
-
const rateLimiting = {
|
|
180
|
-
perSenderHits: 0,
|
|
181
|
-
globalHits: 0,
|
|
182
|
-
};
|
|
183
|
-
|
|
184
|
-
// Decrypt stats
|
|
185
|
-
const decrypt = {
|
|
186
|
-
success: 0,
|
|
187
|
-
failure: 0,
|
|
188
|
-
};
|
|
189
|
-
|
|
190
|
-
// Memory stats (updated via gauge-style metrics)
|
|
191
|
-
const memory = {
|
|
192
|
-
seenTrackerSize: 0,
|
|
193
|
-
rateLimiterEntries: 0,
|
|
194
|
-
};
|
|
195
|
-
|
|
196
|
-
function getOrCreateRelay(url: string) {
|
|
197
|
-
let relay = relays.get(url);
|
|
198
|
-
if (!relay) {
|
|
199
|
-
relay = {
|
|
200
|
-
connects: 0,
|
|
201
|
-
disconnects: 0,
|
|
202
|
-
reconnects: 0,
|
|
203
|
-
errors: 0,
|
|
204
|
-
messagesReceived: {
|
|
205
|
-
event: 0,
|
|
206
|
-
eose: 0,
|
|
207
|
-
closed: 0,
|
|
208
|
-
notice: 0,
|
|
209
|
-
ok: 0,
|
|
210
|
-
auth: 0,
|
|
211
|
-
},
|
|
212
|
-
circuitBreakerState: "closed",
|
|
213
|
-
circuitBreakerOpens: 0,
|
|
214
|
-
circuitBreakerCloses: 0,
|
|
215
|
-
};
|
|
216
|
-
relays.set(url, relay);
|
|
217
|
-
}
|
|
218
|
-
return relay;
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
function emit(
|
|
222
|
-
name: MetricName,
|
|
223
|
-
value: number = 1,
|
|
224
|
-
labels?: Record<string, string | number>,
|
|
225
|
-
): void {
|
|
226
|
-
// Fire callback if provided
|
|
227
|
-
if (onMetric) {
|
|
228
|
-
onMetric({
|
|
229
|
-
name,
|
|
230
|
-
value,
|
|
231
|
-
timestamp: Date.now(),
|
|
232
|
-
labels,
|
|
233
|
-
});
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
// Update internal counters
|
|
237
|
-
const relayUrl = labels?.relay as string | undefined;
|
|
238
|
-
|
|
239
|
-
switch (name) {
|
|
240
|
-
// Event metrics
|
|
241
|
-
case "event.received":
|
|
242
|
-
eventsReceived += value;
|
|
243
|
-
break;
|
|
244
|
-
case "event.processed":
|
|
245
|
-
eventsProcessed += value;
|
|
246
|
-
break;
|
|
247
|
-
case "event.duplicate":
|
|
248
|
-
eventsDuplicate += value;
|
|
249
|
-
break;
|
|
250
|
-
case "event.rejected.invalid_shape":
|
|
251
|
-
eventsRejected.invalidShape += value;
|
|
252
|
-
break;
|
|
253
|
-
case "event.rejected.wrong_kind":
|
|
254
|
-
eventsRejected.wrongKind += value;
|
|
255
|
-
break;
|
|
256
|
-
case "event.rejected.stale":
|
|
257
|
-
eventsRejected.stale += value;
|
|
258
|
-
break;
|
|
259
|
-
case "event.rejected.future":
|
|
260
|
-
eventsRejected.future += value;
|
|
261
|
-
break;
|
|
262
|
-
case "event.rejected.rate_limited":
|
|
263
|
-
eventsRejected.rateLimited += value;
|
|
264
|
-
break;
|
|
265
|
-
case "event.rejected.invalid_signature":
|
|
266
|
-
eventsRejected.invalidSignature += value;
|
|
267
|
-
break;
|
|
268
|
-
case "event.rejected.oversized_ciphertext":
|
|
269
|
-
eventsRejected.oversizedCiphertext += value;
|
|
270
|
-
break;
|
|
271
|
-
case "event.rejected.oversized_plaintext":
|
|
272
|
-
eventsRejected.oversizedPlaintext += value;
|
|
273
|
-
break;
|
|
274
|
-
case "event.rejected.decrypt_failed":
|
|
275
|
-
eventsRejected.decryptFailed += value;
|
|
276
|
-
break;
|
|
277
|
-
case "event.rejected.self_message":
|
|
278
|
-
eventsRejected.selfMessage += value;
|
|
279
|
-
break;
|
|
280
|
-
|
|
281
|
-
// Relay metrics
|
|
282
|
-
case "relay.connect":
|
|
283
|
-
if (relayUrl) {
|
|
284
|
-
getOrCreateRelay(relayUrl).connects += value;
|
|
285
|
-
}
|
|
286
|
-
break;
|
|
287
|
-
case "relay.disconnect":
|
|
288
|
-
if (relayUrl) {
|
|
289
|
-
getOrCreateRelay(relayUrl).disconnects += value;
|
|
290
|
-
}
|
|
291
|
-
break;
|
|
292
|
-
case "relay.reconnect":
|
|
293
|
-
if (relayUrl) {
|
|
294
|
-
getOrCreateRelay(relayUrl).reconnects += value;
|
|
295
|
-
}
|
|
296
|
-
break;
|
|
297
|
-
case "relay.error":
|
|
298
|
-
if (relayUrl) {
|
|
299
|
-
getOrCreateRelay(relayUrl).errors += value;
|
|
300
|
-
}
|
|
301
|
-
break;
|
|
302
|
-
case "relay.message.event":
|
|
303
|
-
if (relayUrl) {
|
|
304
|
-
getOrCreateRelay(relayUrl).messagesReceived.event += value;
|
|
305
|
-
}
|
|
306
|
-
break;
|
|
307
|
-
case "relay.message.eose":
|
|
308
|
-
if (relayUrl) {
|
|
309
|
-
getOrCreateRelay(relayUrl).messagesReceived.eose += value;
|
|
310
|
-
}
|
|
311
|
-
break;
|
|
312
|
-
case "relay.message.closed":
|
|
313
|
-
if (relayUrl) {
|
|
314
|
-
getOrCreateRelay(relayUrl).messagesReceived.closed += value;
|
|
315
|
-
}
|
|
316
|
-
break;
|
|
317
|
-
case "relay.message.notice":
|
|
318
|
-
if (relayUrl) {
|
|
319
|
-
getOrCreateRelay(relayUrl).messagesReceived.notice += value;
|
|
320
|
-
}
|
|
321
|
-
break;
|
|
322
|
-
case "relay.message.ok":
|
|
323
|
-
if (relayUrl) {
|
|
324
|
-
getOrCreateRelay(relayUrl).messagesReceived.ok += value;
|
|
325
|
-
}
|
|
326
|
-
break;
|
|
327
|
-
case "relay.message.auth":
|
|
328
|
-
if (relayUrl) {
|
|
329
|
-
getOrCreateRelay(relayUrl).messagesReceived.auth += value;
|
|
330
|
-
}
|
|
331
|
-
break;
|
|
332
|
-
case "relay.circuit_breaker.open":
|
|
333
|
-
if (relayUrl) {
|
|
334
|
-
const r = getOrCreateRelay(relayUrl);
|
|
335
|
-
r.circuitBreakerState = "open";
|
|
336
|
-
r.circuitBreakerOpens += value;
|
|
337
|
-
}
|
|
338
|
-
break;
|
|
339
|
-
case "relay.circuit_breaker.close":
|
|
340
|
-
if (relayUrl) {
|
|
341
|
-
const r = getOrCreateRelay(relayUrl);
|
|
342
|
-
r.circuitBreakerState = "closed";
|
|
343
|
-
r.circuitBreakerCloses += value;
|
|
344
|
-
}
|
|
345
|
-
break;
|
|
346
|
-
case "relay.circuit_breaker.half_open":
|
|
347
|
-
if (relayUrl) {
|
|
348
|
-
getOrCreateRelay(relayUrl).circuitBreakerState = "half_open";
|
|
349
|
-
}
|
|
350
|
-
break;
|
|
351
|
-
|
|
352
|
-
// Rate limiting
|
|
353
|
-
case "rate_limit.per_sender":
|
|
354
|
-
rateLimiting.perSenderHits += value;
|
|
355
|
-
break;
|
|
356
|
-
case "rate_limit.global":
|
|
357
|
-
rateLimiting.globalHits += value;
|
|
358
|
-
break;
|
|
359
|
-
|
|
360
|
-
// Decrypt
|
|
361
|
-
case "decrypt.success":
|
|
362
|
-
decrypt.success += value;
|
|
363
|
-
break;
|
|
364
|
-
case "decrypt.failure":
|
|
365
|
-
decrypt.failure += value;
|
|
366
|
-
break;
|
|
367
|
-
|
|
368
|
-
// Memory (gauge-style - value replaces, not adds)
|
|
369
|
-
case "memory.seen_tracker_size":
|
|
370
|
-
memory.seenTrackerSize = value;
|
|
371
|
-
break;
|
|
372
|
-
case "memory.rate_limiter_entries":
|
|
373
|
-
memory.rateLimiterEntries = value;
|
|
374
|
-
break;
|
|
375
|
-
}
|
|
376
|
-
}
|
|
377
|
-
|
|
378
|
-
function getSnapshot(): MetricsSnapshot {
|
|
379
|
-
// Convert relay map to object
|
|
380
|
-
const relaysObj: MetricsSnapshot["relays"] = {};
|
|
381
|
-
for (const [url, stats] of relays) {
|
|
382
|
-
relaysObj[url] = { ...stats, messagesReceived: { ...stats.messagesReceived } };
|
|
383
|
-
}
|
|
384
|
-
|
|
385
|
-
return {
|
|
386
|
-
eventsReceived,
|
|
387
|
-
eventsProcessed,
|
|
388
|
-
eventsDuplicate,
|
|
389
|
-
eventsRejected: { ...eventsRejected },
|
|
390
|
-
relays: relaysObj,
|
|
391
|
-
rateLimiting: { ...rateLimiting },
|
|
392
|
-
decrypt: { ...decrypt },
|
|
393
|
-
memory: { ...memory },
|
|
394
|
-
snapshotAt: Date.now(),
|
|
395
|
-
};
|
|
396
|
-
}
|
|
397
|
-
|
|
398
|
-
function reset(): void {
|
|
399
|
-
eventsReceived = 0;
|
|
400
|
-
eventsProcessed = 0;
|
|
401
|
-
eventsDuplicate = 0;
|
|
402
|
-
Object.assign(eventsRejected, {
|
|
403
|
-
invalidShape: 0,
|
|
404
|
-
wrongKind: 0,
|
|
405
|
-
stale: 0,
|
|
406
|
-
future: 0,
|
|
407
|
-
rateLimited: 0,
|
|
408
|
-
invalidSignature: 0,
|
|
409
|
-
oversizedCiphertext: 0,
|
|
410
|
-
oversizedPlaintext: 0,
|
|
411
|
-
decryptFailed: 0,
|
|
412
|
-
selfMessage: 0,
|
|
413
|
-
});
|
|
414
|
-
relays.clear();
|
|
415
|
-
rateLimiting.perSenderHits = 0;
|
|
416
|
-
rateLimiting.globalHits = 0;
|
|
417
|
-
decrypt.success = 0;
|
|
418
|
-
decrypt.failure = 0;
|
|
419
|
-
memory.seenTrackerSize = 0;
|
|
420
|
-
memory.rateLimiterEntries = 0;
|
|
421
|
-
}
|
|
422
|
-
|
|
423
|
-
return { emit, getSnapshot, reset };
|
|
424
|
-
}
|
|
425
|
-
|
|
426
|
-
/**
|
|
427
|
-
* Create a no-op metrics instance (for when metrics are disabled).
|
|
428
|
-
*/
|
|
429
|
-
export function createNoopMetrics(): NostrMetrics {
|
|
430
|
-
const emptySnapshot: MetricsSnapshot = {
|
|
431
|
-
eventsReceived: 0,
|
|
432
|
-
eventsProcessed: 0,
|
|
433
|
-
eventsDuplicate: 0,
|
|
434
|
-
eventsRejected: {
|
|
435
|
-
invalidShape: 0,
|
|
436
|
-
wrongKind: 0,
|
|
437
|
-
stale: 0,
|
|
438
|
-
future: 0,
|
|
439
|
-
rateLimited: 0,
|
|
440
|
-
invalidSignature: 0,
|
|
441
|
-
oversizedCiphertext: 0,
|
|
442
|
-
oversizedPlaintext: 0,
|
|
443
|
-
decryptFailed: 0,
|
|
444
|
-
selfMessage: 0,
|
|
445
|
-
},
|
|
446
|
-
relays: {},
|
|
447
|
-
rateLimiting: { perSenderHits: 0, globalHits: 0 },
|
|
448
|
-
decrypt: { success: 0, failure: 0 },
|
|
449
|
-
memory: { seenTrackerSize: 0, rateLimiterEntries: 0 },
|
|
450
|
-
snapshotAt: 0,
|
|
451
|
-
};
|
|
452
|
-
|
|
453
|
-
return {
|
|
454
|
-
emit: () => {},
|
|
455
|
-
getSnapshot: () => ({ ...emptySnapshot, snapshotAt: Date.now() }),
|
|
456
|
-
reset: () => {},
|
|
457
|
-
};
|
|
458
|
-
}
|