@affectively/dash 5.0.1 → 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,21 @@
1
+ /**
2
+ * Aeon Integration Configuration
3
+ *
4
+ * Configuration for Aeon sync features in Dash.
5
+ * All features enabled by default.
6
+ */
7
+ export interface AeonConfig {
8
+ /** Enable delta sync optimization (70-80% bandwidth reduction) */
9
+ enableDeltaSync: boolean;
10
+ /** Enable rich presence tracking (cursors, sections, activity) */
11
+ enableRichPresence: boolean;
12
+ /** Enable offline operation queuing */
13
+ enableOfflineQueue: boolean;
14
+ /** Bytes threshold before falling back to full sync instead of delta */
15
+ deltaThreshold: number;
16
+ /** Maximum operations to hold in offline queue */
17
+ maxOfflineQueueSize: number;
18
+ /** Maximum retries for failed offline operations */
19
+ maxOfflineRetries: number;
20
+ }
21
+ export declare const defaultAeonConfig: AeonConfig;
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Aeon Integration Configuration
3
+ *
4
+ * Configuration for Aeon sync features in Dash.
5
+ * All features enabled by default.
6
+ */
7
+ export const defaultAeonConfig = {
8
+ enableDeltaSync: true,
9
+ enableRichPresence: true,
10
+ enableOfflineQueue: true,
11
+ deltaThreshold: 1000,
12
+ maxOfflineQueueSize: 1000,
13
+ maxOfflineRetries: 3,
14
+ };
@@ -0,0 +1,62 @@
1
+ /**
2
+ * Delta Adapter for Dash
3
+ *
4
+ * Wraps Aeon's DeltaSyncOptimizer to provide delta compression
5
+ * for Yjs document updates. Achieves 70-80% bandwidth reduction.
6
+ */
7
+ import { type DeltaOperation, type DeltaStats } from '@affectively/aeon';
8
+ /**
9
+ * Adapter that wraps Yjs updates with delta compression
10
+ */
11
+ export declare class DashDeltaAdapter {
12
+ private optimizer;
13
+ private roomId;
14
+ private updateCounter;
15
+ constructor(roomId: string, threshold?: number);
16
+ /**
17
+ * Wrap a Yjs update with delta compression
18
+ * Returns a delta payload ready for transmission
19
+ */
20
+ wrapUpdate(update: Uint8Array, origin?: string): DeltaPayload;
21
+ /**
22
+ * Unwrap a delta payload back to Yjs update
23
+ */
24
+ unwrapDelta(payload: DeltaPayload): Uint8Array;
25
+ /**
26
+ * Encode delta payload for wire transmission
27
+ */
28
+ encodePayload(payload: DeltaPayload): Uint8Array;
29
+ /**
30
+ * Decode delta payload from wire
31
+ */
32
+ decodePayload(data: Uint8Array): DeltaPayload;
33
+ /**
34
+ * Get compression statistics
35
+ */
36
+ getStats(): DeltaStats;
37
+ /**
38
+ * Reset statistics
39
+ */
40
+ resetStats(): void;
41
+ /**
42
+ * Get memory footprint estimate
43
+ */
44
+ getMemoryEstimate(): number;
45
+ /**
46
+ * Clear operation history
47
+ */
48
+ clearHistory(): void;
49
+ }
50
+ /**
51
+ * Delta payload structure for wire transmission
52
+ */
53
+ export interface DeltaPayload {
54
+ type: 'delta';
55
+ delta: DeltaOperation;
56
+ originalSize: number;
57
+ deltaSize: number;
58
+ }
59
+ /**
60
+ * Check if a payload is a delta payload
61
+ */
62
+ export declare function isDeltaPayload(data: unknown): data is DeltaPayload;
@@ -0,0 +1,98 @@
1
+ /**
2
+ * Delta Adapter for Dash
3
+ *
4
+ * Wraps Aeon's DeltaSyncOptimizer to provide delta compression
5
+ * for Yjs document updates. Achieves 70-80% bandwidth reduction.
6
+ */
7
+ import { DeltaSyncOptimizer, } from '@affectively/aeon';
8
+ /**
9
+ * Adapter that wraps Yjs updates with delta compression
10
+ */
11
+ export class DashDeltaAdapter {
12
+ optimizer;
13
+ roomId;
14
+ updateCounter = 0;
15
+ constructor(roomId, threshold = 1000) {
16
+ this.roomId = roomId;
17
+ this.optimizer = new DeltaSyncOptimizer(threshold);
18
+ }
19
+ /**
20
+ * Wrap a Yjs update with delta compression
21
+ * Returns a delta payload ready for transmission
22
+ */
23
+ wrapUpdate(update, origin) {
24
+ const operationId = `${this.roomId}-${this.updateCounter++}`;
25
+ // Create an Aeon Operation from the Yjs update
26
+ const operation = {
27
+ id: operationId,
28
+ type: 'update',
29
+ sessionId: this.roomId,
30
+ data: {
31
+ update: Array.from(update),
32
+ origin: origin ?? 'local',
33
+ },
34
+ status: 'pending',
35
+ createdAt: Date.now(),
36
+ };
37
+ const delta = this.optimizer.computeDelta(operation);
38
+ return {
39
+ type: 'delta',
40
+ delta,
41
+ originalSize: update.byteLength,
42
+ deltaSize: new TextEncoder().encode(JSON.stringify(delta)).byteLength,
43
+ };
44
+ }
45
+ /**
46
+ * Unwrap a delta payload back to Yjs update
47
+ */
48
+ unwrapDelta(payload) {
49
+ const operation = this.optimizer.decompressDelta(payload.delta);
50
+ return new Uint8Array(operation.data.update);
51
+ }
52
+ /**
53
+ * Encode delta payload for wire transmission
54
+ */
55
+ encodePayload(payload) {
56
+ return new TextEncoder().encode(JSON.stringify(payload));
57
+ }
58
+ /**
59
+ * Decode delta payload from wire
60
+ */
61
+ decodePayload(data) {
62
+ return JSON.parse(new TextDecoder().decode(data));
63
+ }
64
+ /**
65
+ * Get compression statistics
66
+ */
67
+ getStats() {
68
+ return this.optimizer.getStats();
69
+ }
70
+ /**
71
+ * Reset statistics
72
+ */
73
+ resetStats() {
74
+ this.optimizer.resetStats();
75
+ }
76
+ /**
77
+ * Get memory footprint estimate
78
+ */
79
+ getMemoryEstimate() {
80
+ return this.optimizer.getMemoryEstimate();
81
+ }
82
+ /**
83
+ * Clear operation history
84
+ */
85
+ clearHistory() {
86
+ this.optimizer.resetStats();
87
+ }
88
+ }
89
+ /**
90
+ * Check if a payload is a delta payload
91
+ */
92
+ export function isDeltaPayload(data) {
93
+ return (typeof data === 'object' &&
94
+ data !== null &&
95
+ 'type' in data &&
96
+ data.type === 'delta' &&
97
+ 'delta' in data);
98
+ }
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Aeon Integration for Dash
3
+ *
4
+ * Provides enhanced sync capabilities through Aeon:
5
+ * - Delta compression (70-80% bandwidth reduction)
6
+ * - Rich presence tracking (cursors, sections, activity)
7
+ * - Offline operation queuing with persistence
8
+ * - Schema versioning and migrations
9
+ */
10
+ export { defaultAeonConfig, type AeonConfig } from './config.js';
11
+ export { DashDeltaAdapter, isDeltaPayload, type DeltaPayload, } from './delta-adapter.js';
12
+ export { DashPresenceAdapter, type PresenceEvents, } from './presence-adapter.js';
13
+ export { DashOfflineAdapter, type ProcessQueueResult, type OfflineQueueEvents, } from './offline-adapter.js';
14
+ export { DashSchemaAdapter, createSchemaAdapter, } from './schema-adapter.js';
15
+ export type { DeltaOperation, DeltaStats, DeltaBatch, } from '@affectively/aeon';
16
+ export type { AgentPresence, } from '@affectively/aeon';
17
+ export type { OfflineOperation, OfflineQueueStats, OperationPriority, } from '@affectively/aeon';
18
+ export type { SchemaVersion, Migration, MigrationResult, MigrationRecord, } from '@affectively/aeon';
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Aeon Integration for Dash
3
+ *
4
+ * Provides enhanced sync capabilities through Aeon:
5
+ * - Delta compression (70-80% bandwidth reduction)
6
+ * - Rich presence tracking (cursors, sections, activity)
7
+ * - Offline operation queuing with persistence
8
+ * - Schema versioning and migrations
9
+ */
10
+ // Configuration
11
+ export { defaultAeonConfig } from './config.js';
12
+ // Delta compression adapter
13
+ export { DashDeltaAdapter, isDeltaPayload, } from './delta-adapter.js';
14
+ // Presence adapter
15
+ export { DashPresenceAdapter, } from './presence-adapter.js';
16
+ // Offline queue adapter
17
+ export { DashOfflineAdapter, } from './offline-adapter.js';
18
+ // Schema versioning adapter
19
+ export { DashSchemaAdapter, createSchemaAdapter, } from './schema-adapter.js';
@@ -0,0 +1,110 @@
1
+ /**
2
+ * Offline Adapter for Dash
3
+ *
4
+ * Wraps Aeon's OfflineOperationQueue to provide offline-first
5
+ * operation queuing for Yjs documents.
6
+ */
7
+ import { type OfflineOperation, type OfflineQueueStats, type OperationPriority } from '@affectively/aeon';
8
+ import * as Y from 'yjs';
9
+ /**
10
+ * Adapter that queues Yjs updates when offline
11
+ */
12
+ export declare class DashOfflineAdapter {
13
+ private queue;
14
+ private doc;
15
+ private roomId;
16
+ private storageKey;
17
+ private isOnline;
18
+ private processingQueue;
19
+ constructor(doc: Y.Doc, roomId: string, maxQueueSize?: number, maxRetries?: number);
20
+ /**
21
+ * Setup network status listeners
22
+ */
23
+ private setupNetworkListeners;
24
+ /**
25
+ * Setup queue event handlers
26
+ */
27
+ private setupQueueEvents;
28
+ /**
29
+ * Queue a Yjs update for later sync
30
+ */
31
+ queueUpdate(update: Uint8Array, priority?: OperationPriority): OfflineOperation;
32
+ /**
33
+ * Check if we should queue (offline) or send directly
34
+ */
35
+ shouldQueue(): boolean;
36
+ /**
37
+ * Process queued operations
38
+ * @param sendFn Function to send updates (typically HybridProvider.send)
39
+ */
40
+ processQueue(sendFn?: (update: Uint8Array) => Promise<void>): Promise<ProcessQueueResult>;
41
+ /**
42
+ * Get queue statistics
43
+ */
44
+ getStats(): OfflineQueueStats;
45
+ /**
46
+ * Get pending operation count
47
+ */
48
+ getPendingCount(): number;
49
+ /**
50
+ * Check if online
51
+ */
52
+ getOnlineStatus(): boolean;
53
+ /**
54
+ * Retry failed operations
55
+ */
56
+ retryFailed(): void;
57
+ /**
58
+ * Clear failed operations
59
+ */
60
+ clearFailed(): void;
61
+ /**
62
+ * Clear all queued operations
63
+ */
64
+ clear(): void;
65
+ /**
66
+ * Subscribe to queue events
67
+ */
68
+ on<E extends keyof OfflineQueueEvents>(event: E, handler: OfflineQueueEvents[E]): void;
69
+ /**
70
+ * Unsubscribe from queue events
71
+ */
72
+ off<E extends keyof OfflineQueueEvents>(event: E, handler: OfflineQueueEvents[E]): void;
73
+ /**
74
+ * Persist queue to localStorage
75
+ */
76
+ private persistQueue;
77
+ /**
78
+ * Load queue from localStorage
79
+ */
80
+ private loadPersistedQueue;
81
+ /**
82
+ * Clear persisted queue
83
+ */
84
+ private clearPersistedQueue;
85
+ /**
86
+ * Cleanup
87
+ */
88
+ destroy(): void;
89
+ }
90
+ /**
91
+ * Result of processing the queue
92
+ */
93
+ export interface ProcessQueueResult {
94
+ synced: number;
95
+ failed: number;
96
+ }
97
+ /**
98
+ * Offline queue event types
99
+ */
100
+ export interface OfflineQueueEvents {
101
+ 'operation-added': (operation: OfflineOperation) => void;
102
+ 'operation-synced': (operation: OfflineOperation) => void;
103
+ 'operation-failed': (operation: OfflineOperation, error: Error) => void;
104
+ 'queue-empty': () => void;
105
+ 'sync-started': () => void;
106
+ 'sync-completed': (stats: {
107
+ synced: number;
108
+ failed: number;
109
+ }) => void;
110
+ }
@@ -0,0 +1,223 @@
1
+ /**
2
+ * Offline Adapter for Dash
3
+ *
4
+ * Wraps Aeon's OfflineOperationQueue to provide offline-first
5
+ * operation queuing for Yjs documents.
6
+ */
7
+ import { OfflineOperationQueue, } from '@affectively/aeon';
8
+ import * as Y from 'yjs';
9
+ const STORAGE_KEY_PREFIX = 'dash-offline-queue-';
10
+ /**
11
+ * Adapter that queues Yjs updates when offline
12
+ */
13
+ export class DashOfflineAdapter {
14
+ queue;
15
+ doc;
16
+ roomId;
17
+ storageKey;
18
+ isOnline = true;
19
+ processingQueue = false;
20
+ constructor(doc, roomId, maxQueueSize = 1000, maxRetries = 3) {
21
+ this.queue = new OfflineOperationQueue(maxQueueSize, maxRetries);
22
+ this.doc = doc;
23
+ this.roomId = roomId;
24
+ this.storageKey = `${STORAGE_KEY_PREFIX}${roomId}`;
25
+ // Load persisted queue on startup
26
+ this.loadPersistedQueue();
27
+ // Setup network status listeners
28
+ this.setupNetworkListeners();
29
+ // Setup queue event handlers
30
+ this.setupQueueEvents();
31
+ }
32
+ /**
33
+ * Setup network status listeners
34
+ */
35
+ setupNetworkListeners() {
36
+ if (typeof window !== 'undefined') {
37
+ this.isOnline = navigator.onLine;
38
+ window.addEventListener('online', () => {
39
+ this.isOnline = true;
40
+ this.processQueue();
41
+ });
42
+ window.addEventListener('offline', () => {
43
+ this.isOnline = false;
44
+ });
45
+ }
46
+ }
47
+ /**
48
+ * Setup queue event handlers
49
+ */
50
+ setupQueueEvents() {
51
+ this.queue.on('operation-synced', (operation) => {
52
+ this.persistQueue();
53
+ });
54
+ this.queue.on('operation-failed', (operation, error) => {
55
+ console.warn(`[DashOfflineAdapter] Operation ${operation.id} failed: ${error.message}`);
56
+ this.persistQueue();
57
+ });
58
+ this.queue.on('queue-empty', () => {
59
+ this.clearPersistedQueue();
60
+ });
61
+ }
62
+ /**
63
+ * Queue a Yjs update for later sync
64
+ */
65
+ queueUpdate(update, priority = 'normal') {
66
+ const operation = this.queue.enqueue('update', { update: Array.from(update) }, this.roomId, priority);
67
+ this.persistQueue();
68
+ return operation;
69
+ }
70
+ /**
71
+ * Check if we should queue (offline) or send directly
72
+ */
73
+ shouldQueue() {
74
+ return !this.isOnline;
75
+ }
76
+ /**
77
+ * Process queued operations
78
+ * @param sendFn Function to send updates (typically HybridProvider.send)
79
+ */
80
+ async processQueue(sendFn) {
81
+ if (!this.isOnline || this.processingQueue) {
82
+ return { synced: 0, failed: 0 };
83
+ }
84
+ this.processingQueue = true;
85
+ let synced = 0;
86
+ let failed = 0;
87
+ try {
88
+ while (true) {
89
+ const batch = this.queue.getNextBatch(10);
90
+ if (batch.length === 0)
91
+ break;
92
+ const ids = batch.map((op) => op.id);
93
+ this.queue.markSyncing(ids);
94
+ for (const operation of batch) {
95
+ try {
96
+ const update = new Uint8Array(operation.data.update);
97
+ if (sendFn) {
98
+ // Send via provided function
99
+ await sendFn(update);
100
+ }
101
+ else {
102
+ // Apply locally if no send function
103
+ Y.applyUpdate(this.doc, update);
104
+ }
105
+ this.queue.markSynced(operation.id);
106
+ synced++;
107
+ }
108
+ catch (error) {
109
+ this.queue.markFailed(operation.id, error);
110
+ failed++;
111
+ }
112
+ }
113
+ }
114
+ }
115
+ finally {
116
+ this.processingQueue = false;
117
+ this.persistQueue();
118
+ }
119
+ return { synced, failed };
120
+ }
121
+ /**
122
+ * Get queue statistics
123
+ */
124
+ getStats() {
125
+ return this.queue.getStats();
126
+ }
127
+ /**
128
+ * Get pending operation count
129
+ */
130
+ getPendingCount() {
131
+ return this.queue.getPendingCount();
132
+ }
133
+ /**
134
+ * Check if online
135
+ */
136
+ getOnlineStatus() {
137
+ return this.isOnline;
138
+ }
139
+ /**
140
+ * Retry failed operations
141
+ */
142
+ retryFailed() {
143
+ this.queue.retryFailed();
144
+ this.persistQueue();
145
+ }
146
+ /**
147
+ * Clear failed operations
148
+ */
149
+ clearFailed() {
150
+ this.queue.clearFailed();
151
+ this.persistQueue();
152
+ }
153
+ /**
154
+ * Clear all queued operations
155
+ */
156
+ clear() {
157
+ this.queue.clear();
158
+ this.clearPersistedQueue();
159
+ }
160
+ /**
161
+ * Subscribe to queue events
162
+ */
163
+ on(event, handler) {
164
+ this.queue.on(event, handler);
165
+ }
166
+ /**
167
+ * Unsubscribe from queue events
168
+ */
169
+ off(event, handler) {
170
+ this.queue.off(event, handler);
171
+ }
172
+ // ============================================
173
+ // Persistence
174
+ // ============================================
175
+ /**
176
+ * Persist queue to localStorage
177
+ */
178
+ persistQueue() {
179
+ if (typeof localStorage !== 'undefined') {
180
+ try {
181
+ const operations = this.queue.export();
182
+ localStorage.setItem(this.storageKey, JSON.stringify(operations));
183
+ }
184
+ catch (error) {
185
+ console.warn('[DashOfflineAdapter] Failed to persist queue:', error);
186
+ }
187
+ }
188
+ }
189
+ /**
190
+ * Load queue from localStorage
191
+ */
192
+ loadPersistedQueue() {
193
+ if (typeof localStorage !== 'undefined') {
194
+ try {
195
+ const stored = localStorage.getItem(this.storageKey);
196
+ if (stored) {
197
+ const operations = JSON.parse(stored);
198
+ this.queue.import(operations);
199
+ }
200
+ }
201
+ catch (error) {
202
+ console.warn('[DashOfflineAdapter] Failed to load persisted queue:', error);
203
+ }
204
+ }
205
+ }
206
+ /**
207
+ * Clear persisted queue
208
+ */
209
+ clearPersistedQueue() {
210
+ if (typeof localStorage !== 'undefined') {
211
+ localStorage.removeItem(this.storageKey);
212
+ }
213
+ }
214
+ /**
215
+ * Cleanup
216
+ */
217
+ destroy() {
218
+ this.queue.clear();
219
+ if (typeof window !== 'undefined') {
220
+ // Note: Can't easily remove anonymous listeners, but they'll be GC'd
221
+ }
222
+ }
223
+ }
@@ -0,0 +1,114 @@
1
+ /**
2
+ * Presence Adapter for Dash
3
+ *
4
+ * Bridges Yjs awareness protocol with Aeon's AgentPresenceManager.
5
+ * Provides rich presence features: cursor tracking, active sections, roles.
6
+ */
7
+ import { type AgentPresence } from '@affectively/aeon';
8
+ import type { Awareness } from 'y-protocols/awareness';
9
+ /**
10
+ * Adapter that syncs Yjs awareness with Aeon presence
11
+ */
12
+ export declare class DashPresenceAdapter {
13
+ private aeonPresence;
14
+ private yjsAwareness;
15
+ private localAgentId;
16
+ private roomId;
17
+ constructor(roomId: string, awareness: Awareness);
18
+ /**
19
+ * Sync Yjs awareness updates to Aeon presence
20
+ */
21
+ private setupYjsToAeonSync;
22
+ /**
23
+ * Sync Aeon presence updates back to Yjs awareness
24
+ */
25
+ private setupAeonToYjsSync;
26
+ /**
27
+ * Update local cursor position
28
+ */
29
+ updateCursor(x: number, y: number, path: string): void;
30
+ /**
31
+ * Update local active section
32
+ */
33
+ updateActiveSection(section: string): void;
34
+ /**
35
+ * Update local user info
36
+ */
37
+ updateUserInfo(name: string, role?: AgentPresence['role']): void;
38
+ /**
39
+ * Get all online agents
40
+ */
41
+ getOnlineAgents(): AgentPresence[];
42
+ /**
43
+ * Get all agents (including offline)
44
+ */
45
+ getAllAgents(): AgentPresence[];
46
+ /**
47
+ * Get presence for specific agent
48
+ */
49
+ getPresence(agentId: string): AgentPresence | undefined;
50
+ /**
51
+ * Get agents in a specific section
52
+ */
53
+ getAgentsInSection(section: string): AgentPresence[];
54
+ /**
55
+ * Get presence statistics
56
+ */
57
+ getStats(): {
58
+ total: number;
59
+ online: number;
60
+ away: number;
61
+ offline: number;
62
+ reconnecting: number;
63
+ byRole: Record<string, number>;
64
+ };
65
+ /**
66
+ * Get local agent ID
67
+ */
68
+ getLocalAgentId(): string;
69
+ /**
70
+ * Subscribe to presence events
71
+ */
72
+ on<E extends keyof PresenceEvents>(event: E, handler: PresenceEvents[E]): void;
73
+ /**
74
+ * Unsubscribe from presence events
75
+ */
76
+ off<E extends keyof PresenceEvents>(event: E, handler: PresenceEvents[E]): void;
77
+ /**
78
+ * Cleanup
79
+ */
80
+ destroy(): void;
81
+ }
82
+ /**
83
+ * Presence event types
84
+ */
85
+ export interface PresenceEvents {
86
+ presence_updated: (data: {
87
+ agentId: string;
88
+ presence: AgentPresence;
89
+ }) => void;
90
+ agent_joined: (data: {
91
+ agentId: string;
92
+ presence: AgentPresence;
93
+ }) => void;
94
+ agent_left: (data: {
95
+ agentId: string;
96
+ presence: AgentPresence;
97
+ }) => void;
98
+ cursor_updated: (data: {
99
+ agentId: string;
100
+ cursorPosition: {
101
+ x: number;
102
+ y: number;
103
+ path: string;
104
+ };
105
+ }) => void;
106
+ section_updated: (data: {
107
+ agentId: string;
108
+ activeSection: string;
109
+ }) => void;
110
+ status_updated: (data: {
111
+ agentId: string;
112
+ status: AgentPresence['status'];
113
+ }) => void;
114
+ }