gsd-agent 1.0.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.
Files changed (155) hide show
  1. package/README.md +221 -0
  2. package/bin/cli.js +313 -0
  3. package/dist/auth-flow.d.ts +50 -0
  4. package/dist/auth-flow.d.ts.map +1 -0
  5. package/dist/auth-flow.js +233 -0
  6. package/dist/auth-flow.js.map +1 -0
  7. package/dist/auth.d.ts +42 -0
  8. package/dist/auth.d.ts.map +1 -0
  9. package/dist/auth.js +117 -0
  10. package/dist/auth.js.map +1 -0
  11. package/dist/command-executor.d.ts +44 -0
  12. package/dist/command-executor.d.ts.map +1 -0
  13. package/dist/command-executor.js +193 -0
  14. package/dist/command-executor.js.map +1 -0
  15. package/dist/command-executor.test.d.ts +8 -0
  16. package/dist/command-executor.test.d.ts.map +1 -0
  17. package/dist/command-executor.test.js +87 -0
  18. package/dist/command-executor.test.js.map +1 -0
  19. package/dist/command-queue.d.ts +44 -0
  20. package/dist/command-queue.d.ts.map +1 -0
  21. package/dist/command-queue.js +184 -0
  22. package/dist/command-queue.js.map +1 -0
  23. package/dist/command-queue.test.d.ts +7 -0
  24. package/dist/command-queue.test.d.ts.map +1 -0
  25. package/dist/command-queue.test.js +220 -0
  26. package/dist/command-queue.test.js.map +1 -0
  27. package/dist/config.d.ts +25 -0
  28. package/dist/config.d.ts.map +1 -0
  29. package/dist/config.js +103 -0
  30. package/dist/config.js.map +1 -0
  31. package/dist/conflict-resolver.d.ts +43 -0
  32. package/dist/conflict-resolver.d.ts.map +1 -0
  33. package/dist/conflict-resolver.js +91 -0
  34. package/dist/conflict-resolver.js.map +1 -0
  35. package/dist/conflict-resolver.test.d.ts +7 -0
  36. package/dist/conflict-resolver.test.d.ts.map +1 -0
  37. package/dist/conflict-resolver.test.js +123 -0
  38. package/dist/conflict-resolver.test.js.map +1 -0
  39. package/dist/discovery.d.ts +59 -0
  40. package/dist/discovery.d.ts.map +1 -0
  41. package/dist/discovery.js +180 -0
  42. package/dist/discovery.js.map +1 -0
  43. package/dist/discovery.test.d.ts +8 -0
  44. package/dist/discovery.test.d.ts.map +1 -0
  45. package/dist/discovery.test.js +132 -0
  46. package/dist/discovery.test.js.map +1 -0
  47. package/dist/hash.d.ts +20 -0
  48. package/dist/hash.d.ts.map +1 -0
  49. package/dist/hash.js +35 -0
  50. package/dist/hash.js.map +1 -0
  51. package/dist/hash.test.d.ts +7 -0
  52. package/dist/hash.test.d.ts.map +1 -0
  53. package/dist/hash.test.js +58 -0
  54. package/dist/hash.test.js.map +1 -0
  55. package/dist/index.d.ts +11 -0
  56. package/dist/index.d.ts.map +1 -0
  57. package/dist/index.js +202 -0
  58. package/dist/index.js.map +1 -0
  59. package/dist/integration.test.d.ts +8 -0
  60. package/dist/integration.test.d.ts.map +1 -0
  61. package/dist/integration.test.js +37 -0
  62. package/dist/integration.test.js.map +1 -0
  63. package/dist/logger.d.ts +68 -0
  64. package/dist/logger.d.ts.map +1 -0
  65. package/dist/logger.js +159 -0
  66. package/dist/logger.js.map +1 -0
  67. package/dist/output-streamer.d.ts +27 -0
  68. package/dist/output-streamer.d.ts.map +1 -0
  69. package/dist/output-streamer.js +71 -0
  70. package/dist/output-streamer.js.map +1 -0
  71. package/dist/output-streamer.test.d.ts +7 -0
  72. package/dist/output-streamer.test.d.ts.map +1 -0
  73. package/dist/output-streamer.test.js +90 -0
  74. package/dist/output-streamer.test.js.map +1 -0
  75. package/dist/realtime-subscriber.d.ts +63 -0
  76. package/dist/realtime-subscriber.d.ts.map +1 -0
  77. package/dist/realtime-subscriber.js +201 -0
  78. package/dist/realtime-subscriber.js.map +1 -0
  79. package/dist/realtime-subscriber.test.d.ts +7 -0
  80. package/dist/realtime-subscriber.test.d.ts.map +1 -0
  81. package/dist/realtime-subscriber.test.js +183 -0
  82. package/dist/realtime-subscriber.test.js.map +1 -0
  83. package/dist/reconnection-manager.d.ts +88 -0
  84. package/dist/reconnection-manager.d.ts.map +1 -0
  85. package/dist/reconnection-manager.js +229 -0
  86. package/dist/reconnection-manager.js.map +1 -0
  87. package/dist/reconnection-manager.test.d.ts +8 -0
  88. package/dist/reconnection-manager.test.d.ts.map +1 -0
  89. package/dist/reconnection-manager.test.js +151 -0
  90. package/dist/reconnection-manager.test.js.map +1 -0
  91. package/dist/remote-sync-handler.d.ts +61 -0
  92. package/dist/remote-sync-handler.d.ts.map +1 -0
  93. package/dist/remote-sync-handler.js +197 -0
  94. package/dist/remote-sync-handler.js.map +1 -0
  95. package/dist/remote-sync-handler.test.d.ts +7 -0
  96. package/dist/remote-sync-handler.test.d.ts.map +1 -0
  97. package/dist/remote-sync-handler.test.js +212 -0
  98. package/dist/remote-sync-handler.test.js.map +1 -0
  99. package/dist/retry.d.ts +35 -0
  100. package/dist/retry.d.ts.map +1 -0
  101. package/dist/retry.js +63 -0
  102. package/dist/retry.js.map +1 -0
  103. package/dist/retry.test.d.ts +5 -0
  104. package/dist/retry.test.d.ts.map +1 -0
  105. package/dist/retry.test.js +84 -0
  106. package/dist/retry.test.js.map +1 -0
  107. package/dist/storage-client.d.ts +69 -0
  108. package/dist/storage-client.d.ts.map +1 -0
  109. package/dist/storage-client.js +168 -0
  110. package/dist/storage-client.js.map +1 -0
  111. package/dist/storage-client.test.d.ts +7 -0
  112. package/dist/storage-client.test.d.ts.map +1 -0
  113. package/dist/storage-client.test.js +126 -0
  114. package/dist/storage-client.test.js.map +1 -0
  115. package/dist/supabase.d.ts +82 -0
  116. package/dist/supabase.d.ts.map +1 -0
  117. package/dist/supabase.js +341 -0
  118. package/dist/supabase.js.map +1 -0
  119. package/dist/supabase.test.d.ts +7 -0
  120. package/dist/supabase.test.d.ts.map +1 -0
  121. package/dist/supabase.test.js +273 -0
  122. package/dist/supabase.test.js.map +1 -0
  123. package/dist/sync-engine.d.ts +84 -0
  124. package/dist/sync-engine.d.ts.map +1 -0
  125. package/dist/sync-engine.js +251 -0
  126. package/dist/sync-engine.js.map +1 -0
  127. package/dist/sync-engine.test.d.ts +7 -0
  128. package/dist/sync-engine.test.d.ts.map +1 -0
  129. package/dist/sync-engine.test.js +241 -0
  130. package/dist/sync-engine.test.js.map +1 -0
  131. package/dist/sync-state.d.ts +82 -0
  132. package/dist/sync-state.d.ts.map +1 -0
  133. package/dist/sync-state.js +145 -0
  134. package/dist/sync-state.js.map +1 -0
  135. package/dist/sync-state.test.d.ts +7 -0
  136. package/dist/sync-state.test.d.ts.map +1 -0
  137. package/dist/sync-state.test.js +129 -0
  138. package/dist/sync-state.test.js.map +1 -0
  139. package/dist/types.d.ts +148 -0
  140. package/dist/types.d.ts.map +1 -0
  141. package/dist/types.js +8 -0
  142. package/dist/types.js.map +1 -0
  143. package/dist/types.test.d.ts +7 -0
  144. package/dist/types.test.d.ts.map +1 -0
  145. package/dist/types.test.js +73 -0
  146. package/dist/types.test.js.map +1 -0
  147. package/dist/watcher.d.ts +55 -0
  148. package/dist/watcher.d.ts.map +1 -0
  149. package/dist/watcher.js +214 -0
  150. package/dist/watcher.js.map +1 -0
  151. package/dist/watcher.test.d.ts +8 -0
  152. package/dist/watcher.test.d.ts.map +1 -0
  153. package/dist/watcher.test.js +164 -0
  154. package/dist/watcher.test.js.map +1 -0
  155. package/package.json +58 -0
package/dist/logger.js ADDED
@@ -0,0 +1,159 @@
1
+ /**
2
+ * Structured logging with file rotation for GSD Agent
3
+ *
4
+ * Provides console and file logging with configurable levels, timestamps,
5
+ * and automatic daily log rotation with cleanup of old logs.
6
+ */
7
+ import fs from 'fs';
8
+ import path from 'path';
9
+ const LOG_LEVELS = {
10
+ ERROR: 0,
11
+ WARN: 1,
12
+ INFO: 2,
13
+ DEBUG: 3
14
+ };
15
+ /**
16
+ * Logger class for structured logging
17
+ *
18
+ * Writes to both console and file with timestamps, level prefixes, and optional metadata.
19
+ * Respects configured log level to filter messages.
20
+ */
21
+ export class Logger {
22
+ logLevel;
23
+ logFile;
24
+ logDir;
25
+ rotationDays;
26
+ currentDate;
27
+ constructor(config) {
28
+ this.logLevel = config.log_level;
29
+ this.logFile = config.log_file;
30
+ this.logDir = path.dirname(this.logFile);
31
+ this.rotationDays = config.log_rotation_days;
32
+ this.currentDate = this.getDateString();
33
+ // Create log directory if it doesn't exist
34
+ if (!fs.existsSync(this.logDir)) {
35
+ fs.mkdirSync(this.logDir, { recursive: true });
36
+ }
37
+ // Clean up old logs on initialization
38
+ this.cleanupOldLogs();
39
+ }
40
+ /**
41
+ * Get current date string for log rotation (YYYY-MM-DD)
42
+ */
43
+ getDateString() {
44
+ const now = new Date();
45
+ return now.toISOString().split('T')[0];
46
+ }
47
+ /**
48
+ * Get rotated log file path for a specific date
49
+ */
50
+ getRotatedLogPath(date) {
51
+ const ext = path.extname(this.logFile);
52
+ const base = path.basename(this.logFile, ext);
53
+ return path.join(this.logDir, `${base}-${date}${ext}`);
54
+ }
55
+ /**
56
+ * Check if log rotation is needed and rotate if necessary
57
+ */
58
+ checkRotation() {
59
+ const today = this.getDateString();
60
+ if (today !== this.currentDate) {
61
+ // Rotate: rename current log to dated log
62
+ if (fs.existsSync(this.logFile)) {
63
+ const rotatedPath = this.getRotatedLogPath(this.currentDate);
64
+ try {
65
+ fs.renameSync(this.logFile, rotatedPath);
66
+ }
67
+ catch (error) {
68
+ // Ignore rotation errors
69
+ }
70
+ }
71
+ this.currentDate = today;
72
+ this.cleanupOldLogs();
73
+ }
74
+ }
75
+ /**
76
+ * Delete logs older than rotation_days
77
+ */
78
+ cleanupOldLogs() {
79
+ try {
80
+ const files = fs.readdirSync(this.logDir);
81
+ const now = Date.now();
82
+ const maxAge = this.rotationDays * 24 * 60 * 60 * 1000;
83
+ for (const file of files) {
84
+ if (file.endsWith('.log') && file !== path.basename(this.logFile)) {
85
+ const filePath = path.join(this.logDir, file);
86
+ const stats = fs.statSync(filePath);
87
+ const age = now - stats.mtime.getTime();
88
+ if (age > maxAge) {
89
+ fs.unlinkSync(filePath);
90
+ }
91
+ }
92
+ }
93
+ }
94
+ catch (error) {
95
+ // Ignore cleanup errors
96
+ }
97
+ }
98
+ /**
99
+ * Check if a message at the given level should be logged
100
+ */
101
+ shouldLog(level) {
102
+ return LOG_LEVELS[level] <= LOG_LEVELS[this.logLevel];
103
+ }
104
+ /**
105
+ * Format and write log message to console and file
106
+ */
107
+ log(level, message, meta) {
108
+ if (!this.shouldLog(level)) {
109
+ return;
110
+ }
111
+ this.checkRotation();
112
+ const timestamp = new Date().toISOString();
113
+ const metaStr = meta ? ' ' + JSON.stringify(meta) : '';
114
+ const logLine = `[${timestamp}] [${level}] ${message}${metaStr}\n`;
115
+ // Write to console
116
+ const consoleMethod = level === 'ERROR' ? console.error : console.log;
117
+ consoleMethod(logLine.trim());
118
+ // Write to file
119
+ try {
120
+ fs.appendFileSync(this.logFile, logLine);
121
+ }
122
+ catch (error) {
123
+ // Ignore file write errors
124
+ }
125
+ }
126
+ /**
127
+ * Log error message
128
+ */
129
+ error(message, meta) {
130
+ this.log('ERROR', message, meta);
131
+ }
132
+ /**
133
+ * Log warning message
134
+ */
135
+ warn(message, meta) {
136
+ this.log('WARN', message, meta);
137
+ }
138
+ /**
139
+ * Log info message
140
+ */
141
+ info(message, meta) {
142
+ this.log('INFO', message, meta);
143
+ }
144
+ /**
145
+ * Log debug message
146
+ */
147
+ debug(message, meta) {
148
+ this.log('DEBUG', message, meta);
149
+ }
150
+ }
151
+ /**
152
+ * Create a logger instance with the given configuration
153
+ * @param config SyncConfig containing log settings
154
+ * @returns Logger instance
155
+ */
156
+ export function createLogger(config) {
157
+ return new Logger(config);
158
+ }
159
+ //# sourceMappingURL=logger.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"logger.js","sourceRoot":"","sources":["../src/logger.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Output Batcher for Command Execution
3
+ *
4
+ * Batches command output writes to prevent database flooding.
5
+ * Flushes on 10 lines OR 100ms inactivity, whichever comes first.
6
+ */
7
+ import type { SupabaseClient } from '@supabase/supabase-js';
8
+ /**
9
+ * OutputBatcher accumulates command output and flushes in batches
10
+ */
11
+ export declare class OutputBatcher {
12
+ private batch;
13
+ private flushTimer;
14
+ private supabase;
15
+ constructor(supabase: SupabaseClient);
16
+ /**
17
+ * Add a line to the batch
18
+ * Triggers flush if batch reaches 10 lines, otherwise sets timer for 100ms
19
+ */
20
+ add(commandId: string, lineNumber: number, content: string, stream: 'stdout' | 'stderr'): void;
21
+ /**
22
+ * Flush batch to database
23
+ * Clears batch and timer after insert
24
+ */
25
+ flush(): Promise<void>;
26
+ }
27
+ //# sourceMappingURL=output-streamer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"output-streamer.d.ts","sourceRoot":"","sources":["../src/output-streamer.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAA;AAS3D;;GAEG;AACH,qBAAa,aAAa;IACxB,OAAO,CAAC,KAAK,CAAmB;IAChC,OAAO,CAAC,UAAU,CAA8B;IAChD,OAAO,CAAC,QAAQ,CAAgB;gBAEpB,QAAQ,EAAE,cAAc;IAIpC;;;OAGG;IACH,GAAG,CAAC,SAAS,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,GAAG,QAAQ,GAAG,IAAI;IAoB9F;;;OAGG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;CA6B7B"}
@@ -0,0 +1,71 @@
1
+ /**
2
+ * Output Batcher for Command Execution
3
+ *
4
+ * Batches command output writes to prevent database flooding.
5
+ * Flushes on 10 lines OR 100ms inactivity, whichever comes first.
6
+ */
7
+ /**
8
+ * OutputBatcher accumulates command output and flushes in batches
9
+ */
10
+ export class OutputBatcher {
11
+ batch = [];
12
+ flushTimer = null;
13
+ supabase;
14
+ constructor(supabase) {
15
+ this.supabase = supabase;
16
+ }
17
+ /**
18
+ * Add a line to the batch
19
+ * Triggers flush if batch reaches 10 lines, otherwise sets timer for 100ms
20
+ */
21
+ add(commandId, lineNumber, content, stream) {
22
+ this.batch.push({
23
+ command_id: commandId,
24
+ line_number: lineNumber,
25
+ content,
26
+ stream
27
+ });
28
+ // Flush immediately if batch is full
29
+ if (this.batch.length >= 10) {
30
+ this.flush();
31
+ }
32
+ else {
33
+ // Reset flush timer
34
+ if (this.flushTimer) {
35
+ clearTimeout(this.flushTimer);
36
+ }
37
+ this.flushTimer = setTimeout(() => this.flush(), 100);
38
+ }
39
+ }
40
+ /**
41
+ * Flush batch to database
42
+ * Clears batch and timer after insert
43
+ */
44
+ async flush() {
45
+ if (this.batch.length === 0) {
46
+ return;
47
+ }
48
+ // Copy batch and clear immediately
49
+ const toInsert = [...this.batch];
50
+ this.batch = [];
51
+ // Clear timer
52
+ if (this.flushTimer) {
53
+ clearTimeout(this.flushTimer);
54
+ this.flushTimer = null;
55
+ }
56
+ try {
57
+ const { error } = await this.supabase
58
+ .from('command_output')
59
+ .insert(toInsert);
60
+ if (error) {
61
+ console.error('Failed to insert command output:', error.message);
62
+ // Don't throw - don't break command execution
63
+ }
64
+ }
65
+ catch (error) {
66
+ console.error('Failed to insert command output:', error);
67
+ // Don't throw - don't break command execution
68
+ }
69
+ }
70
+ }
71
+ //# sourceMappingURL=output-streamer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"output-streamer.js","sourceRoot":"","sources":["../src/output-streamer.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAWH;;GAEG;AACH,MAAM,OAAO,aAAa;IAChB,KAAK,GAAiB,EAAE,CAAA;IACxB,UAAU,GAA0B,IAAI,CAAA;IACxC,QAAQ,CAAgB;IAEhC,YAAY,QAAwB;QAClC,IAAI,CAAC,QAAQ,GAAG,QAAQ,CAAA;IAC1B,CAAC;IAED;;;OAGG;IACH,GAAG,CAAC,SAAiB,EAAE,UAAkB,EAAE,OAAe,EAAE,MAA2B;QACrF,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC;YACd,UAAU,EAAE,SAAS;YACrB,WAAW,EAAE,UAAU;YACvB,OAAO;YACP,MAAM;SACP,CAAC,CAAA;QAEF,qCAAqC;QACrC,IAAI,IAAI,CAAC,KAAK,CAAC,MAAM,IAAI,EAAE,EAAE,CAAC;YAC5B,IAAI,CAAC,KAAK,EAAE,CAAA;QACd,CAAC;aAAM,CAAC;YACN,oBAAoB;YACpB,IAAI,IAAI,CAAC,UAAU,EAAE,CAAC;gBACpB,YAAY,CAAC,IAAI,CAAC,UAAU,CAAC,CAAA;YAC/B,CAAC;YACD,IAAI,CAAC,UAAU,GAAG,UAAU,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,KAAK,EAAE,EAAE,GAAG,CAAC,CAAA;QACvD,CAAC;IACH,CAAC;IAED;;;OAGG;IACH,KAAK,CAAC,KAAK;QACT,IAAI,IAAI,CAAC,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YAC5B,OAAM;QACR,CAAC;QAED,mCAAmC;QACnC,MAAM,QAAQ,GAAG,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,CAAA;QAChC,IAAI,CAAC,KAAK,GAAG,EAAE,CAAA;QAEf,cAAc;QACd,IAAI,IAAI,CAAC,UAAU,EAAE,CAAC;YACpB,YAAY,CAAC,IAAI,CAAC,UAAU,CAAC,CAAA;YAC7B,IAAI,CAAC,UAAU,GAAG,IAAI,CAAA;QACxB,CAAC;QAED,IAAI,CAAC;YACH,MAAM,EAAE,KAAK,EAAE,GAAG,MAAM,IAAI,CAAC,QAAQ;iBAClC,IAAI,CAAC,gBAAgB,CAAC;iBACtB,MAAM,CAAC,QAAQ,CAAC,CAAA;YAEnB,IAAI,KAAK,EAAE,CAAC;gBACV,OAAO,CAAC,KAAK,CAAC,kCAAkC,EAAE,KAAK,CAAC,OAAO,CAAC,CAAA;gBAChE,8CAA8C;YAChD,CAAC;QACH,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO,CAAC,KAAK,CAAC,kCAAkC,EAAE,KAAK,CAAC,CAAA;YACxD,8CAA8C;QAChD,CAAC;IACH,CAAC;CACF"}
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Tests for OutputBatcher
3
+ *
4
+ * Verifies batching logic, flush triggers, and database operations.
5
+ */
6
+ export {};
7
+ //# sourceMappingURL=output-streamer.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"output-streamer.test.d.ts","sourceRoot":"","sources":["../src/output-streamer.test.ts"],"names":[],"mappings":"AAAA;;;;GAIG"}
@@ -0,0 +1,90 @@
1
+ /**
2
+ * Tests for OutputBatcher
3
+ *
4
+ * Verifies batching logic, flush triggers, and database operations.
5
+ */
6
+ import { describe, it, expect, beforeEach, vi } from 'vitest';
7
+ import { OutputBatcher } from './output-streamer.js';
8
+ describe('OutputBatcher', () => {
9
+ let mockSupabase;
10
+ let batcher;
11
+ beforeEach(() => {
12
+ // Mock Supabase client
13
+ mockSupabase = {
14
+ from: vi.fn().mockReturnThis(),
15
+ insert: vi.fn().mockResolvedValue({ error: null })
16
+ };
17
+ batcher = new OutputBatcher(mockSupabase);
18
+ });
19
+ it('should accumulate lines in batch', () => {
20
+ batcher.add('cmd-1', 1, 'line 1', 'stdout');
21
+ batcher.add('cmd-1', 2, 'line 2', 'stdout');
22
+ // Batch should have 2 items (not flushed yet)
23
+ expect(batcher['batch'].length).toBe(2);
24
+ });
25
+ it('should flush when batch reaches 10 lines', async () => {
26
+ // Add 10 lines
27
+ for (let i = 1; i <= 10; i++) {
28
+ batcher.add('cmd-1', i, `line ${i}`, 'stdout');
29
+ }
30
+ // Wait for async flush
31
+ await new Promise(resolve => setTimeout(resolve, 10));
32
+ // Verify insert was called
33
+ expect(mockSupabase.from).toHaveBeenCalledWith('command_output');
34
+ expect(mockSupabase.insert).toHaveBeenCalledWith(expect.arrayContaining([
35
+ expect.objectContaining({
36
+ command_id: 'cmd-1',
37
+ line_number: 1,
38
+ content: 'line 1',
39
+ stream: 'stdout'
40
+ })
41
+ ]));
42
+ // Batch should be cleared
43
+ expect(batcher['batch'].length).toBe(0);
44
+ });
45
+ it('should flush after 100ms of inactivity', async () => {
46
+ batcher.add('cmd-1', 1, 'line 1', 'stdout');
47
+ // Wait for flush timer (100ms + buffer)
48
+ await new Promise(resolve => setTimeout(resolve, 150));
49
+ // Verify insert was called
50
+ expect(mockSupabase.from).toHaveBeenCalledWith('command_output');
51
+ expect(mockSupabase.insert).toHaveBeenCalled();
52
+ // Batch should be cleared
53
+ expect(batcher['batch'].length).toBe(0);
54
+ });
55
+ it('should insert batch to database on flush', async () => {
56
+ batcher.add('cmd-1', 1, 'line 1', 'stdout');
57
+ batcher.add('cmd-1', 2, 'line 2', 'stderr');
58
+ await batcher.flush();
59
+ expect(mockSupabase.from).toHaveBeenCalledWith('command_output');
60
+ expect(mockSupabase.insert).toHaveBeenCalledWith([
61
+ {
62
+ command_id: 'cmd-1',
63
+ line_number: 1,
64
+ content: 'line 1',
65
+ stream: 'stdout'
66
+ },
67
+ {
68
+ command_id: 'cmd-1',
69
+ line_number: 2,
70
+ content: 'line 2',
71
+ stream: 'stderr'
72
+ }
73
+ ]);
74
+ });
75
+ it('should clear batch after insert', async () => {
76
+ batcher.add('cmd-1', 1, 'line 1', 'stdout');
77
+ await batcher.flush();
78
+ expect(batcher['batch'].length).toBe(0);
79
+ });
80
+ it('should handle database errors gracefully', async () => {
81
+ // Mock database error
82
+ mockSupabase.insert.mockResolvedValue({
83
+ error: { message: 'Database error' }
84
+ });
85
+ batcher.add('cmd-1', 1, 'line 1', 'stdout');
86
+ // Should not throw
87
+ await expect(batcher.flush()).resolves.not.toThrow();
88
+ });
89
+ });
90
+ //# sourceMappingURL=output-streamer.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"output-streamer.test.js","sourceRoot":"","sources":["../src/output-streamer.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,63 @@
1
+ /**
2
+ * Supabase Realtime subscriber for bidirectional sync
3
+ *
4
+ * Listens for remote file changes via Supabase Realtime postgres_changes events.
5
+ * Detects conflicts when local and remote versions differ.
6
+ * Emits events for remote changes and conflicts.
7
+ */
8
+ import { EventEmitter } from 'events';
9
+ import type { ConflictEvent, SyncConfig } from './types.js';
10
+ import type { Logger } from './logger.js';
11
+ /**
12
+ * RealtimeSubscriber manages Supabase Realtime subscriptions
13
+ *
14
+ * Subscribes to postgres_changes events on the files table filtered by workspace_id.
15
+ * Emits 'remote-change' events when files are inserted, updated, or deleted remotely.
16
+ * Provides conflict detection by comparing local and remote file hashes.
17
+ */
18
+ export declare class RealtimeSubscriber extends EventEmitter {
19
+ private client;
20
+ private config;
21
+ private logger;
22
+ private hashFile;
23
+ private channels;
24
+ constructor(client: any, config: SyncConfig, logger: Logger, hashFile: (filePath: string) => Promise<string>);
25
+ /**
26
+ * Subscribe to Realtime events for a workspace
27
+ * @param workspace_id - Workspace ID to subscribe to
28
+ */
29
+ subscribe(workspace_id: string): void;
30
+ /**
31
+ * Handle Postgres change event from Realtime
32
+ * @param payload - Realtime payload
33
+ */
34
+ private handlePostgresChange;
35
+ /**
36
+ * Detect conflict between local and remote versions
37
+ * @param workspace_id - Workspace ID
38
+ * @param file_path - File path relative to workspace root
39
+ * @param remote_hash - Remote file hash
40
+ * @param remote_content - Remote file content
41
+ * @param local_content - Local file content
42
+ * @returns ConflictEvent if conflict detected, null otherwise
43
+ */
44
+ detectConflict(workspace_id: string, file_path: string, remote_hash: string, remote_content: string, local_content: string): Promise<ConflictEvent | null>;
45
+ /**
46
+ * Unsubscribe from a workspace's Realtime channel
47
+ * @param workspace_id - Workspace ID to unsubscribe from
48
+ */
49
+ unsubscribe(workspace_id: string): Promise<void>;
50
+ /**
51
+ * Unsubscribe from all Realtime channels
52
+ */
53
+ unsubscribeAll(): Promise<void>;
54
+ }
55
+ /**
56
+ * Factory function to create RealtimeSubscriber
57
+ * @param supabase - Supabase client instance
58
+ * @param config - Sync configuration
59
+ * @param logger - Logger instance
60
+ * @returns RealtimeSubscriber instance
61
+ */
62
+ export declare function createRealtimeSubscriber(supabase: any, config: SyncConfig, logger: Logger): RealtimeSubscriber;
63
+ //# sourceMappingURL=realtime-subscriber.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"realtime-subscriber.d.ts","sourceRoot":"","sources":["../src/realtime-subscriber.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,QAAQ,CAAA;AAErC,OAAO,KAAK,EAAqB,aAAa,EAAE,UAAU,EAAE,MAAM,YAAY,CAAA;AAC9E,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AAGzC;;;;;;GAMG;AACH,qBAAa,kBAAmB,SAAQ,YAAY;IAClD,OAAO,CAAC,MAAM,CAAK;IACnB,OAAO,CAAC,MAAM,CAAY;IAC1B,OAAO,CAAC,MAAM,CAAQ;IACtB,OAAO,CAAC,QAAQ,CAAuC;IACvD,OAAO,CAAC,QAAQ,CAA8B;gBAG5C,MAAM,EAAE,GAAG,EACX,MAAM,EAAE,UAAU,EAClB,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC;IAUjD;;;OAGG;IACH,SAAS,CAAC,YAAY,EAAE,MAAM,GAAG,IAAI;IAgCrC;;;OAGG;IACH,OAAO,CAAC,oBAAoB;IA4C5B;;;;;;;;OAQG;IACG,cAAc,CAClB,YAAY,EAAE,MAAM,EACpB,SAAS,EAAE,MAAM,EACjB,WAAW,EAAE,MAAM,EACnB,cAAc,EAAE,MAAM,EACtB,aAAa,EAAE,MAAM,GACpB,OAAO,CAAC,aAAa,GAAG,IAAI,CAAC;IAqDhC;;;OAGG;IACG,WAAW,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAiBtD;;OAEG;IACG,cAAc,IAAI,OAAO,CAAC,IAAI,CAAC;CActC;AAED;;;;;;GAMG;AACH,wBAAgB,wBAAwB,CACtC,QAAQ,EAAE,GAAG,EACb,MAAM,EAAE,UAAU,EAClB,MAAM,EAAE,MAAM,GACb,kBAAkB,CAGpB"}
@@ -0,0 +1,201 @@
1
+ /**
2
+ * Supabase Realtime subscriber for bidirectional sync
3
+ *
4
+ * Listens for remote file changes via Supabase Realtime postgres_changes events.
5
+ * Detects conflicts when local and remote versions differ.
6
+ * Emits events for remote changes and conflicts.
7
+ */
8
+ import { EventEmitter } from 'events';
9
+ import { computeHash, hashFile } from './hash.js';
10
+ /**
11
+ * RealtimeSubscriber manages Supabase Realtime subscriptions
12
+ *
13
+ * Subscribes to postgres_changes events on the files table filtered by workspace_id.
14
+ * Emits 'remote-change' events when files are inserted, updated, or deleted remotely.
15
+ * Provides conflict detection by comparing local and remote file hashes.
16
+ */
17
+ export class RealtimeSubscriber extends EventEmitter {
18
+ client;
19
+ config;
20
+ logger;
21
+ hashFile;
22
+ channels;
23
+ constructor(client, config, logger, hashFile) {
24
+ super();
25
+ this.client = client;
26
+ this.config = config;
27
+ this.logger = logger;
28
+ this.hashFile = hashFile;
29
+ this.channels = new Map();
30
+ }
31
+ /**
32
+ * Subscribe to Realtime events for a workspace
33
+ * @param workspace_id - Workspace ID to subscribe to
34
+ */
35
+ subscribe(workspace_id) {
36
+ const channelName = `workspace-${workspace_id}`;
37
+ // Check if already subscribed
38
+ if (this.channels.has(channelName)) {
39
+ this.logger.warn('Already subscribed to workspace', { workspace_id });
40
+ return;
41
+ }
42
+ this.logger.info('Subscribing to Realtime channel', { workspace_id, channel: channelName });
43
+ // Create Realtime channel with postgres_changes filter
44
+ const channel = this.client
45
+ .channel(channelName)
46
+ .on('postgres_changes', {
47
+ event: '*',
48
+ schema: 'public',
49
+ table: 'files',
50
+ filter: `workspace_id=eq.${workspace_id}`
51
+ }, (payload) => {
52
+ this.handlePostgresChange(payload);
53
+ })
54
+ .subscribe();
55
+ this.channels.set(channelName, channel);
56
+ this.logger.debug('Subscribed to Realtime channel', { workspace_id, channel: channelName });
57
+ }
58
+ /**
59
+ * Handle Postgres change event from Realtime
60
+ * @param payload - Realtime payload
61
+ */
62
+ handlePostgresChange(payload) {
63
+ const { eventType, new: newRecord, old: oldRecord } = payload;
64
+ if (eventType === 'INSERT' || eventType === 'UPDATE') {
65
+ // Emit remote change event for INSERT/UPDATE
66
+ const event = {
67
+ id: newRecord.id,
68
+ workspace_id: newRecord.workspace_id,
69
+ file_path: newRecord.file_path,
70
+ content: newRecord.content,
71
+ content_hash: newRecord.content_hash,
72
+ size: newRecord.size,
73
+ updated_at: newRecord.updated_at,
74
+ storage_url: newRecord.storage_url
75
+ };
76
+ this.logger.debug('Remote change detected', {
77
+ event_type: eventType,
78
+ workspace_id: event.workspace_id,
79
+ file_path: event.file_path
80
+ });
81
+ this.emit('remote-change', event);
82
+ }
83
+ else if (eventType === 'DELETE') {
84
+ // Emit remote change event for DELETE with null content
85
+ const event = {
86
+ id: oldRecord.id,
87
+ workspace_id: oldRecord.workspace_id,
88
+ file_path: oldRecord.file_path,
89
+ content: null,
90
+ content_hash: '',
91
+ size: 0,
92
+ updated_at: new Date().toISOString()
93
+ };
94
+ this.logger.debug('Remote deletion detected', {
95
+ workspace_id: event.workspace_id,
96
+ file_path: event.file_path
97
+ });
98
+ this.emit('remote-change', event);
99
+ }
100
+ }
101
+ /**
102
+ * Detect conflict between local and remote versions
103
+ * @param workspace_id - Workspace ID
104
+ * @param file_path - File path relative to workspace root
105
+ * @param remote_hash - Remote file hash
106
+ * @param remote_content - Remote file content
107
+ * @param local_content - Local file content
108
+ * @returns ConflictEvent if conflict detected, null otherwise
109
+ */
110
+ async detectConflict(workspace_id, file_path, remote_hash, remote_content, local_content) {
111
+ try {
112
+ // Compute local hash
113
+ const local_hash = computeHash(local_content);
114
+ // Compare hashes
115
+ if (local_hash === remote_hash) {
116
+ this.logger.debug('No conflict - hashes match', {
117
+ workspace_id,
118
+ file_path,
119
+ hash: local_hash
120
+ });
121
+ return null;
122
+ }
123
+ // Conflict detected
124
+ this.logger.warn('Conflict detected', {
125
+ workspace_id,
126
+ file_path,
127
+ local_hash,
128
+ remote_hash
129
+ });
130
+ const conflict = {
131
+ workspace_id,
132
+ file_path,
133
+ local_hash,
134
+ remote_hash,
135
+ local_content,
136
+ remote_content,
137
+ detected_at: new Date().toISOString()
138
+ };
139
+ return conflict;
140
+ }
141
+ catch (error) {
142
+ this.logger.error('Error detecting conflict', {
143
+ workspace_id,
144
+ file_path,
145
+ error
146
+ });
147
+ // Default to conflict on error (safer than silent overwrite)
148
+ return {
149
+ workspace_id,
150
+ file_path,
151
+ local_hash: 'error',
152
+ remote_hash,
153
+ local_content,
154
+ remote_content,
155
+ detected_at: new Date().toISOString()
156
+ };
157
+ }
158
+ }
159
+ /**
160
+ * Unsubscribe from a workspace's Realtime channel
161
+ * @param workspace_id - Workspace ID to unsubscribe from
162
+ */
163
+ async unsubscribe(workspace_id) {
164
+ const channelName = `workspace-${workspace_id}`;
165
+ const channel = this.channels.get(channelName);
166
+ if (!channel) {
167
+ this.logger.warn('Channel not found for unsubscribe', { workspace_id });
168
+ return;
169
+ }
170
+ this.logger.info('Unsubscribing from Realtime channel', { workspace_id, channel: channelName });
171
+ await channel.unsubscribe();
172
+ this.channels.delete(channelName);
173
+ this.logger.debug('Unsubscribed from Realtime channel', { workspace_id });
174
+ }
175
+ /**
176
+ * Unsubscribe from all Realtime channels
177
+ */
178
+ async unsubscribeAll() {
179
+ this.logger.info('Unsubscribing from all Realtime channels', {
180
+ channel_count: this.channels.size
181
+ });
182
+ const unsubscribePromises = Array.from(this.channels.keys()).map(channelName => {
183
+ const workspace_id = channelName.replace('workspace-', '');
184
+ return this.unsubscribe(workspace_id);
185
+ });
186
+ await Promise.all(unsubscribePromises);
187
+ this.logger.debug('Unsubscribed from all channels');
188
+ }
189
+ }
190
+ /**
191
+ * Factory function to create RealtimeSubscriber
192
+ * @param supabase - Supabase client instance
193
+ * @param config - Sync configuration
194
+ * @param logger - Logger instance
195
+ * @returns RealtimeSubscriber instance
196
+ */
197
+ export function createRealtimeSubscriber(supabase, config, logger) {
198
+ // Use hashFile from hash module
199
+ return new RealtimeSubscriber(supabase, config, logger, hashFile);
200
+ }
201
+ //# sourceMappingURL=realtime-subscriber.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"realtime-subscriber.js","sourceRoot":"","sources":["../src/realtime-subscriber.ts"],"names":[],"mappings":"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"}