@affectively/dash 5.0.0 → 5.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.
@@ -0,0 +1,163 @@
1
+ /**
2
+ * Schema Adapter for Dash
3
+ *
4
+ * Bridges Aeon's SchemaVersionManager with Dash's LensEngine.
5
+ * Provides migration tracking, version compatibility checks, and audit trails.
6
+ */
7
+ import { SchemaVersionManager, MigrationEngine, MigrationTracker, } from '@affectively/aeon';
8
+ /**
9
+ * Adapter that bridges Aeon versioning with Dash's Lens system
10
+ */
11
+ export class DashSchemaAdapter {
12
+ versionManager;
13
+ migrationEngine;
14
+ migrationTracker;
15
+ lensEngine;
16
+ constructor(lensEngine) {
17
+ this.versionManager = new SchemaVersionManager();
18
+ this.migrationEngine = new MigrationEngine();
19
+ this.migrationTracker = new MigrationTracker();
20
+ this.lensEngine = lensEngine;
21
+ }
22
+ /**
23
+ * Register a schema version with associated Lens operations
24
+ */
25
+ registerVersion(version, description, lensOps, breaking = false) {
26
+ const [major, minor, patch] = version.split('.').map(Number);
27
+ // Register with Aeon
28
+ this.versionManager.registerVersion({
29
+ major,
30
+ minor,
31
+ patch,
32
+ timestamp: new Date().toISOString(),
33
+ description,
34
+ breaking,
35
+ });
36
+ // Register Lens if provided
37
+ if (lensOps && major > 1) {
38
+ this.lensEngine.registerLens(major - 1, major, lensOps);
39
+ }
40
+ }
41
+ /**
42
+ * Register a migration for a version upgrade
43
+ */
44
+ registerMigration(id, fromVersion, toVersion, migrate, rollback) {
45
+ const migration = {
46
+ id,
47
+ name: `${fromVersion} -> ${toVersion}`,
48
+ version: toVersion,
49
+ description: `Migrate from ${fromVersion} to ${toVersion}`,
50
+ timestamp: new Date().toISOString(),
51
+ up: migrate,
52
+ down: rollback,
53
+ };
54
+ this.migrationEngine.registerMigration(migration);
55
+ }
56
+ /**
57
+ * Execute a migration on data
58
+ */
59
+ async executeMigration(migrationId, data) {
60
+ const result = await this.migrationEngine.executeMigration(migrationId, data);
61
+ // Track the migration
62
+ const record = {
63
+ id: `record-${Date.now()}`,
64
+ migrationId,
65
+ timestamp: new Date().toISOString(),
66
+ version: result.migrationId,
67
+ direction: 'up',
68
+ status: result.success ? 'applied' : 'failed',
69
+ duration: result.duration,
70
+ itemsAffected: result.itemsAffected,
71
+ errorMessage: result.errors.length > 0 ? result.errors.join(', ') : undefined,
72
+ appliedBy: 'dash-schema-adapter',
73
+ };
74
+ this.migrationTracker.recordMigration(record);
75
+ return result;
76
+ }
77
+ /**
78
+ * Rollback a migration
79
+ */
80
+ async rollbackMigration(migrationId, data) {
81
+ const result = await this.migrationEngine.rollbackMigration(migrationId, data);
82
+ // Track the rollback
83
+ const record = {
84
+ id: `record-${Date.now()}`,
85
+ migrationId,
86
+ timestamp: new Date().toISOString(),
87
+ version: result.migrationId,
88
+ direction: 'down',
89
+ status: result.success ? 'rolled-back' : 'failed',
90
+ duration: result.duration,
91
+ itemsAffected: result.itemsAffected,
92
+ errorMessage: result.errors.length > 0 ? result.errors.join(', ') : undefined,
93
+ appliedBy: 'dash-schema-adapter',
94
+ };
95
+ this.migrationTracker.recordMigration(record);
96
+ return result;
97
+ }
98
+ /**
99
+ * Migrate data using Lens transformations
100
+ */
101
+ migrateWithLens(data, fromVersion, toVersion) {
102
+ return this.lensEngine.migrate(data, fromVersion, toVersion);
103
+ }
104
+ /**
105
+ * Get current version
106
+ */
107
+ getCurrentVersion() {
108
+ return this.versionManager.getCurrentVersion();
109
+ }
110
+ /**
111
+ * Get a specific version by string
112
+ */
113
+ getVersion(version) {
114
+ return this.versionManager.getVersion(version);
115
+ }
116
+ /**
117
+ * Get migration by ID
118
+ */
119
+ getMigration(id) {
120
+ return this.migrationEngine.getMigration(id);
121
+ }
122
+ /**
123
+ * Check if migration is needed
124
+ */
125
+ needsMigration(fromVersion, toVersion) {
126
+ return this.versionManager.canMigrate(fromVersion, toVersion);
127
+ }
128
+ /**
129
+ * Get migration path between versions
130
+ */
131
+ getMigrationPath(fromVersion, toVersion) {
132
+ const from = this.versionManager.parseVersion(fromVersion);
133
+ const to = this.versionManager.parseVersion(toVersion);
134
+ return this.versionManager.getMigrationPath(from, to);
135
+ }
136
+ /**
137
+ * Get pending migrations
138
+ */
139
+ getPendingMigrations() {
140
+ return this.migrationEngine.getPendingMigrations();
141
+ }
142
+ /**
143
+ * Get migration statistics
144
+ */
145
+ getStatistics() {
146
+ return {
147
+ engine: this.migrationEngine.getStatistics(),
148
+ tracker: this.migrationTracker.getStatistics(),
149
+ };
150
+ }
151
+ /**
152
+ * Format version for display
153
+ */
154
+ formatVersion(version) {
155
+ return `${version.major}.${version.minor}.${version.patch}`;
156
+ }
157
+ }
158
+ /**
159
+ * Create a schema adapter from an existing LensEngine
160
+ */
161
+ export function createSchemaAdapter(lensEngine) {
162
+ return new DashSchemaAdapter(lensEngine);
163
+ }
@@ -1,6 +1,10 @@
1
1
  import * as Y from 'yjs';
2
2
  import { Observable } from 'lib0/observable';
3
3
  import * as awarenessProtocol from 'y-protocols/awareness';
4
+ import { type AeonConfig } from './aeon/config.js';
5
+ import { DashDeltaAdapter } from './aeon/delta-adapter.js';
6
+ import { DashPresenceAdapter } from './aeon/presence-adapter.js';
7
+ import { DashOfflineAdapter } from './aeon/offline-adapter.js';
4
8
  export declare class HybridProvider extends Observable<string> {
5
9
  private doc;
6
10
  private ws;
@@ -11,8 +15,13 @@ export declare class HybridProvider extends Observable<string> {
11
15
  awareness: awarenessProtocol.Awareness;
12
16
  private writer;
13
17
  private highFrequencyMode;
14
- constructor(url: string, roomName: string, doc: Y.Doc, { awareness }?: {
15
- awareness?: awarenessProtocol.Awareness | undefined;
18
+ private aeonConfig;
19
+ private deltaAdapter;
20
+ private presenceAdapter;
21
+ private offlineAdapter;
22
+ constructor(url: string, roomName: string, doc: Y.Doc, { awareness, aeonConfig, }?: {
23
+ awareness?: awarenessProtocol.Awareness;
24
+ aeonConfig?: AeonConfig;
16
25
  });
17
26
  private connect;
18
27
  private connectWebSocket;
@@ -25,4 +34,139 @@ export declare class HybridProvider extends Observable<string> {
25
34
  private onDocUpdate;
26
35
  private onAwarenessUpdate;
27
36
  destroy(): void;
37
+ /**
38
+ * Get the delta adapter for compression stats
39
+ */
40
+ getDeltaAdapter(): DashDeltaAdapter | null;
41
+ /**
42
+ * Get the presence adapter for rich presence features
43
+ */
44
+ getPresenceAdapter(): DashPresenceAdapter | null;
45
+ /**
46
+ * Get the offline adapter for queue management
47
+ */
48
+ getOfflineAdapter(): DashOfflineAdapter | null;
49
+ /**
50
+ * Get Aeon configuration
51
+ */
52
+ getAeonConfig(): AeonConfig;
53
+ /**
54
+ * Process offline queue when back online
55
+ */
56
+ processOfflineQueue(): Promise<{
57
+ synced: number;
58
+ failed: number;
59
+ }>;
60
+ /**
61
+ * Get comprehensive connection status
62
+ */
63
+ getConnectionStatus(): ConnectionStatus;
64
+ /**
65
+ * Get all awareness states from connected peers
66
+ */
67
+ getAwarenessStates(): AwarenessState[];
68
+ /**
69
+ * Get the local client ID
70
+ */
71
+ getLocalClientId(): number;
72
+ /**
73
+ * Get connected peer count (excluding local)
74
+ */
75
+ getPeerCount(): number;
76
+ /**
77
+ * Get document state information
78
+ */
79
+ getDocumentState(): DocumentState;
80
+ /**
81
+ * Get a snapshot of the Yjs document for inspection
82
+ */
83
+ getDocumentSnapshot(): DocumentSnapshot;
84
+ /**
85
+ * Set local awareness state for introspection (admin view)
86
+ */
87
+ setLocalAwareness(state: Record<string, unknown>): void;
88
+ /**
89
+ * Get full provider status for debugging
90
+ */
91
+ getProviderStatus(): ProviderStatus;
92
+ /**
93
+ * Get Aeon-specific status
94
+ */
95
+ getAeonStatus(): AeonStatus;
96
+ }
97
+ export interface ConnectionStatus {
98
+ connected: boolean;
99
+ roomName: string;
100
+ url: string;
101
+ websocket: {
102
+ state: 'connecting' | 'open' | 'closing' | 'closed';
103
+ connected: boolean;
104
+ };
105
+ webTransport: {
106
+ connected: boolean;
107
+ highFrequencyMode: boolean;
108
+ };
109
+ transport: 'WebSocket' | 'WebTransport';
110
+ }
111
+ export interface AwarenessState {
112
+ clientId: number;
113
+ state: Record<string, unknown>;
114
+ isLocal: boolean;
115
+ }
116
+ export interface SharedTypeInfo {
117
+ name: string;
118
+ type: 'YMap' | 'YArray' | 'YText' | 'YXmlFragment' | 'unknown';
119
+ size: number;
120
+ }
121
+ export interface DocumentState {
122
+ clientId: number;
123
+ guid: string;
124
+ stateVectorSize: number;
125
+ updateSize: number;
126
+ sharedTypes: SharedTypeInfo[];
127
+ transactionCount: number;
128
+ gcEnabled: boolean;
129
+ }
130
+ export interface DocumentSnapshot {
131
+ timestamp: string;
132
+ roomName: string;
133
+ content: Record<string, unknown>;
134
+ }
135
+ export interface ProviderStatus {
136
+ connection: ConnectionStatus;
137
+ awareness: {
138
+ localClientId: number;
139
+ peerCount: number;
140
+ states: AwarenessState[];
141
+ };
142
+ document: DocumentState;
143
+ aeon: AeonStatus;
144
+ }
145
+ export interface AeonStatus {
146
+ enabled: {
147
+ deltaSync: boolean;
148
+ richPresence: boolean;
149
+ offlineQueue: boolean;
150
+ };
151
+ delta: {
152
+ totalOperations: number;
153
+ totalFull: number;
154
+ totalDelta: number;
155
+ totalOriginalSize: number;
156
+ totalDeltaSize: number;
157
+ averageReductionPercent: number;
158
+ } | null;
159
+ offline: {
160
+ pending: number;
161
+ syncing: number;
162
+ failed: number;
163
+ synced: number;
164
+ totalOperations: number;
165
+ } | null;
166
+ presence: {
167
+ total: number;
168
+ online: number;
169
+ away: number;
170
+ offline: number;
171
+ } | null;
28
172
  }
@@ -1,3 +1,4 @@
1
+ import * as Y from 'yjs';
1
2
  import { Observable } from 'lib0/observable';
2
3
  import * as encoding from 'lib0/encoding';
3
4
  import * as decoding from 'lib0/decoding';
@@ -5,6 +6,17 @@ import * as decoding from 'lib0/decoding';
5
6
  import * as syncProtocol from 'y-protocols/sync';
6
7
  // @ts-ignore
7
8
  import * as awarenessProtocol from 'y-protocols/awareness';
9
+ // Aeon integration
10
+ import { defaultAeonConfig } from './aeon/config.js';
11
+ import { DashDeltaAdapter } from './aeon/delta-adapter.js';
12
+ import { DashPresenceAdapter } from './aeon/presence-adapter.js';
13
+ import { DashOfflineAdapter } from './aeon/offline-adapter.js';
14
+ // Message types
15
+ const MessageType = {
16
+ Sync: 0,
17
+ Awareness: 1,
18
+ Delta: 2, // New: Aeon delta-compressed sync
19
+ };
8
20
  export class HybridProvider extends Observable {
9
21
  doc;
10
22
  ws = null;
@@ -16,12 +28,28 @@ export class HybridProvider extends Observable {
16
28
  writer = null;
17
29
  // "High Frequency" mode uses WebTransport for ephemeral data
18
30
  highFrequencyMode = false;
19
- constructor(url, roomName, doc, { awareness = new awarenessProtocol.Awareness(doc) } = {}) {
31
+ // Aeon integration
32
+ aeonConfig;
33
+ deltaAdapter = null;
34
+ presenceAdapter = null;
35
+ offlineAdapter = null;
36
+ constructor(url, roomName, doc, { awareness = new awarenessProtocol.Awareness(doc), aeonConfig = defaultAeonConfig, } = {}) {
20
37
  super();
21
38
  this.url = url;
22
39
  this.roomName = roomName;
23
40
  this.doc = doc;
24
41
  this.awareness = awareness;
42
+ this.aeonConfig = aeonConfig;
43
+ // Initialize Aeon adapters
44
+ if (aeonConfig.enableDeltaSync) {
45
+ this.deltaAdapter = new DashDeltaAdapter(roomName, aeonConfig.deltaThreshold);
46
+ }
47
+ if (aeonConfig.enableRichPresence) {
48
+ this.presenceAdapter = new DashPresenceAdapter(roomName, awareness);
49
+ }
50
+ if (aeonConfig.enableOfflineQueue) {
51
+ this.offlineAdapter = new DashOfflineAdapter(doc, roomName, aeonConfig.maxOfflineQueueSize, aeonConfig.maxOfflineRetries);
52
+ }
25
53
  this.doc.on('update', this.onDocUpdate.bind(this));
26
54
  this.awareness.on('update', this.onAwarenessUpdate.bind(this));
27
55
  this.connect();
@@ -139,19 +167,28 @@ export class HybridProvider extends Observable {
139
167
  }
140
168
  }
141
169
  handleMessage(buf) {
142
- // Simple protocol: First byte is message type (0=Sync, 1=Awareness)
170
+ // Simple protocol: First byte is message type (0=Sync, 1=Awareness, 2=Delta)
143
171
  // In strict binary mode, we might need a more robust header.
144
172
  // For now assuming Y-protocol encoding.
145
173
  // Note: The Relay DO sends raw messages back.
146
174
  const decoder = decoding.createDecoder(buf);
147
175
  const messageType = decoding.readVarUint(decoder);
148
176
  switch (messageType) {
149
- case 0: // Sync
177
+ case MessageType.Sync:
150
178
  syncProtocol.readSyncMessage(decoder, encoding.createEncoder(), this.doc, this);
151
179
  break;
152
- case 1: // Awareness
180
+ case MessageType.Awareness:
153
181
  awarenessProtocol.applyAwarenessUpdate(this.awareness, decoding.readVarUint8Array(decoder), this);
154
182
  break;
183
+ case MessageType.Delta:
184
+ // Handle Aeon delta-compressed sync
185
+ if (this.deltaAdapter) {
186
+ const deltaPayloadBytes = decoding.readVarUint8Array(decoder);
187
+ const deltaPayload = this.deltaAdapter.decodePayload(deltaPayloadBytes);
188
+ const update = this.deltaAdapter.unwrapDelta(deltaPayload);
189
+ Y.applyUpdate(this.doc, update, this);
190
+ }
191
+ break;
155
192
  }
156
193
  }
157
194
  async send(message) {
@@ -169,10 +206,26 @@ export class HybridProvider extends Observable {
169
206
  onDocUpdate(update, origin) {
170
207
  if (origin === this)
171
208
  return;
172
- const encoder = encoding.createEncoder();
173
- encoding.writeVarUint(encoder, 0); // MessageType.Sync
174
- syncProtocol.writeUpdate(encoder, update);
175
- this.send(encoding.toUint8Array(encoder));
209
+ // Check if we should queue (offline)
210
+ if (this.offlineAdapter?.shouldQueue()) {
211
+ this.offlineAdapter.queueUpdate(update);
212
+ return;
213
+ }
214
+ // Use delta compression if enabled
215
+ if (this.deltaAdapter) {
216
+ const deltaPayload = this.deltaAdapter.wrapUpdate(update, origin);
217
+ const encoder = encoding.createEncoder();
218
+ encoding.writeVarUint(encoder, MessageType.Delta);
219
+ encoding.writeVarUint8Array(encoder, this.deltaAdapter.encodePayload(deltaPayload));
220
+ this.send(encoding.toUint8Array(encoder));
221
+ }
222
+ else {
223
+ // Standard Yjs sync
224
+ const encoder = encoding.createEncoder();
225
+ encoding.writeVarUint(encoder, MessageType.Sync);
226
+ syncProtocol.writeUpdate(encoder, update);
227
+ this.send(encoding.toUint8Array(encoder));
228
+ }
176
229
  }
177
230
  onAwarenessUpdate({ added, updated, removed }, origin) {
178
231
  if (origin === this)
@@ -188,7 +241,237 @@ export class HybridProvider extends Observable {
188
241
  this.awareness.off('update', this.onAwarenessUpdate);
189
242
  this.ws?.close();
190
243
  this.wt?.close();
244
+ // Cleanup Aeon adapters
245
+ this.presenceAdapter?.destroy();
246
+ this.offlineAdapter?.destroy();
191
247
  this.connected = false;
192
248
  super.destroy();
193
249
  }
250
+ // ============================================
251
+ // AEON ADAPTER ACCESSORS
252
+ // ============================================
253
+ /**
254
+ * Get the delta adapter for compression stats
255
+ */
256
+ getDeltaAdapter() {
257
+ return this.deltaAdapter;
258
+ }
259
+ /**
260
+ * Get the presence adapter for rich presence features
261
+ */
262
+ getPresenceAdapter() {
263
+ return this.presenceAdapter;
264
+ }
265
+ /**
266
+ * Get the offline adapter for queue management
267
+ */
268
+ getOfflineAdapter() {
269
+ return this.offlineAdapter;
270
+ }
271
+ /**
272
+ * Get Aeon configuration
273
+ */
274
+ getAeonConfig() {
275
+ return this.aeonConfig;
276
+ }
277
+ /**
278
+ * Process offline queue when back online
279
+ */
280
+ async processOfflineQueue() {
281
+ if (!this.offlineAdapter) {
282
+ return { synced: 0, failed: 0 };
283
+ }
284
+ return this.offlineAdapter.processQueue(async (update) => {
285
+ const encoder = encoding.createEncoder();
286
+ if (this.deltaAdapter) {
287
+ const deltaPayload = this.deltaAdapter.wrapUpdate(update);
288
+ encoding.writeVarUint(encoder, MessageType.Delta);
289
+ encoding.writeVarUint8Array(encoder, this.deltaAdapter.encodePayload(deltaPayload));
290
+ }
291
+ else {
292
+ encoding.writeVarUint(encoder, MessageType.Sync);
293
+ syncProtocol.writeUpdate(encoder, update);
294
+ }
295
+ await this.send(encoding.toUint8Array(encoder));
296
+ });
297
+ }
298
+ // ============================================
299
+ // INTROSPECTION METHODS FOR DASH-STUDIO
300
+ // ============================================
301
+ /**
302
+ * Get comprehensive connection status
303
+ */
304
+ getConnectionStatus() {
305
+ let wsState = 'closed';
306
+ if (this.ws) {
307
+ switch (this.ws.readyState) {
308
+ case WebSocket.CONNECTING:
309
+ wsState = 'connecting';
310
+ break;
311
+ case WebSocket.OPEN:
312
+ wsState = 'open';
313
+ break;
314
+ case WebSocket.CLOSING:
315
+ wsState = 'closing';
316
+ break;
317
+ case WebSocket.CLOSED:
318
+ wsState = 'closed';
319
+ break;
320
+ }
321
+ }
322
+ return {
323
+ connected: this.connected,
324
+ roomName: this.roomName,
325
+ url: this.url,
326
+ websocket: {
327
+ state: wsState,
328
+ connected: this.ws !== null && this.ws.readyState === WebSocket.OPEN
329
+ },
330
+ webTransport: {
331
+ connected: this.wt !== null,
332
+ highFrequencyMode: this.highFrequencyMode
333
+ },
334
+ transport: this.highFrequencyMode ? 'WebTransport' : 'WebSocket'
335
+ };
336
+ }
337
+ /**
338
+ * Get all awareness states from connected peers
339
+ */
340
+ getAwarenessStates() {
341
+ const states = [];
342
+ const awarenessStates = this.awareness.getStates();
343
+ awarenessStates.forEach((state, clientId) => {
344
+ states.push({
345
+ clientId,
346
+ state,
347
+ isLocal: clientId === this.awareness.clientID
348
+ });
349
+ });
350
+ return states;
351
+ }
352
+ /**
353
+ * Get the local client ID
354
+ */
355
+ getLocalClientId() {
356
+ return this.awareness.clientID;
357
+ }
358
+ /**
359
+ * Get connected peer count (excluding local)
360
+ */
361
+ getPeerCount() {
362
+ const states = this.awareness.getStates();
363
+ return Math.max(0, states.size - 1); // Exclude self
364
+ }
365
+ /**
366
+ * Get document state information
367
+ */
368
+ getDocumentState() {
369
+ const stateVector = Y.encodeStateVector(this.doc);
370
+ const update = Y.encodeStateAsUpdate(this.doc);
371
+ // Get shared types info
372
+ const sharedTypes = [];
373
+ this.doc.share.forEach((type, name) => {
374
+ let typeKind = 'unknown';
375
+ let size = 0;
376
+ if (type instanceof Y.Map) {
377
+ typeKind = 'YMap';
378
+ size = type.size;
379
+ }
380
+ else if (type instanceof Y.Array) {
381
+ typeKind = 'YArray';
382
+ size = type.length;
383
+ }
384
+ else if (type instanceof Y.Text) {
385
+ typeKind = 'YText';
386
+ size = type.length;
387
+ }
388
+ else if (type instanceof Y.XmlFragment) {
389
+ typeKind = 'YXmlFragment';
390
+ size = type.length;
391
+ }
392
+ sharedTypes.push({
393
+ name,
394
+ type: typeKind,
395
+ size
396
+ });
397
+ });
398
+ return {
399
+ clientId: this.doc.clientID,
400
+ guid: this.doc.guid,
401
+ stateVectorSize: stateVector.byteLength,
402
+ updateSize: update.byteLength,
403
+ sharedTypes,
404
+ transactionCount: 0, // Yjs doesn't expose this directly
405
+ gcEnabled: this.doc.gc
406
+ };
407
+ }
408
+ /**
409
+ * Get a snapshot of the Yjs document for inspection
410
+ */
411
+ getDocumentSnapshot() {
412
+ const content = {};
413
+ this.doc.share.forEach((type, name) => {
414
+ try {
415
+ if (type instanceof Y.Map) {
416
+ content[name] = type.toJSON();
417
+ }
418
+ else if (type instanceof Y.Array) {
419
+ content[name] = type.toJSON();
420
+ }
421
+ else if (type instanceof Y.Text) {
422
+ content[name] = type.toString();
423
+ }
424
+ else if (type instanceof Y.XmlFragment) {
425
+ content[name] = type.toString();
426
+ }
427
+ else {
428
+ content[name] = '[unsupported type]';
429
+ }
430
+ }
431
+ catch (e) {
432
+ content[name] = '[error reading content]';
433
+ }
434
+ });
435
+ return {
436
+ timestamp: new Date().toISOString(),
437
+ roomName: this.roomName,
438
+ content
439
+ };
440
+ }
441
+ /**
442
+ * Set local awareness state for introspection (admin view)
443
+ */
444
+ setLocalAwareness(state) {
445
+ this.awareness.setLocalState(state);
446
+ }
447
+ /**
448
+ * Get full provider status for debugging
449
+ */
450
+ getProviderStatus() {
451
+ return {
452
+ connection: this.getConnectionStatus(),
453
+ awareness: {
454
+ localClientId: this.awareness.clientID,
455
+ peerCount: this.getPeerCount(),
456
+ states: this.getAwarenessStates()
457
+ },
458
+ document: this.getDocumentState(),
459
+ aeon: this.getAeonStatus()
460
+ };
461
+ }
462
+ /**
463
+ * Get Aeon-specific status
464
+ */
465
+ getAeonStatus() {
466
+ return {
467
+ enabled: {
468
+ deltaSync: this.aeonConfig.enableDeltaSync,
469
+ richPresence: this.aeonConfig.enableRichPresence,
470
+ offlineQueue: this.aeonConfig.enableOfflineQueue,
471
+ },
472
+ delta: this.deltaAdapter?.getStats() ?? null,
473
+ offline: this.offlineAdapter?.getStats() ?? null,
474
+ presence: this.presenceAdapter?.getStats() ?? null,
475
+ };
476
+ }
194
477
  }