Skip to content

Commit 5fd4c00

Browse files
committed
feat(sync): Introduce IntegrationService for synchronization with external systems and enhance ConfigurationManager with sync strategy support
1 parent bc55ff7 commit 5fd4c00

File tree

6 files changed

+341
-358
lines changed

6 files changed

+341
-358
lines changed

packages/core/src/configuration-manager.ts

Lines changed: 75 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,17 @@ import * as os from "os";
1313
import { EnterpriseIntegration } from "@devlog/types";
1414
import { StorageConfig } from "./storage/storage-provider.js";
1515

16+
export interface SyncStrategy {
17+
sourceOfTruth: 'local' | 'external' | 'manual';
18+
conflictResolution: 'local-wins' | 'external-wins' | 'most-recent' | 'manual';
19+
syncInterval?: number; // minutes
20+
autoSync?: boolean;
21+
}
22+
1623
export interface DevlogConfig {
1724
storage: StorageConfig;
1825
integrations?: EnterpriseIntegration;
26+
syncStrategy?: SyncStrategy;
1927
workspaceRoot?: string;
2028
workspaceId?: string; // Added for workspace identification
2129
}
@@ -77,15 +85,6 @@ export class ConfigurationManager {
7785
* Detect the best storage configuration automatically
7886
*/
7987
async detectBestStorage(): Promise<StorageConfig> {
80-
// Check for enterprise integrations first
81-
const integrations = await this.detectEnterpriseIntegrations();
82-
if (integrations && this.hasEnterpriseIntegrations(integrations)) {
83-
return {
84-
type: "enterprise",
85-
options: { integrations }
86-
};
87-
}
88-
8988
// Check for database environment variables
9089
if (process.env.DATABASE_URL) {
9190
const dbUrl = process.env.DATABASE_URL;
@@ -152,27 +151,6 @@ export class ConfigurationManager {
152151
bestFor: string[];
153152
}> {
154153
return [
155-
{
156-
type: "enterprise",
157-
name: "Enterprise Integration",
158-
description: "Store devlogs directly in enterprise tools (Jira, ADO, GitHub)",
159-
pros: [
160-
"No local storage duplication",
161-
"Integrated with existing workflows",
162-
"Automatic synchronization",
163-
"Enterprise-grade security"
164-
],
165-
cons: [
166-
"Requires enterprise tool access",
167-
"Limited offline capability",
168-
"Dependent on external services"
169-
],
170-
bestFor: [
171-
"Teams using Jira, Azure DevOps, or GitHub Issues",
172-
"Enterprise environments",
173-
"Distributed teams"
174-
]
175-
},
176154
{
177155
type: "sqlite",
178156
name: "SQLite Database",
@@ -181,7 +159,8 @@ export class ConfigurationManager {
181159
"Fast performance",
182160
"Full-text search",
183161
"ACID transactions",
184-
"No server required"
162+
"No server required",
163+
"Works offline"
185164
],
186165
cons: [
187166
"Single-user access",
@@ -191,7 +170,8 @@ export class ConfigurationManager {
191170
bestFor: [
192171
"Individual developers",
193172
"Local development",
194-
"Fast prototyping"
173+
"Fast prototyping",
174+
"Offline work"
195175
]
196176
},
197177
{
@@ -202,7 +182,8 @@ export class ConfigurationManager {
202182
"Multi-user support",
203183
"Advanced querying",
204184
"Scalable",
205-
"Full ACID compliance"
185+
"Full ACID compliance",
186+
"Concurrent access"
206187
],
207188
cons: [
208189
"Requires database server",
@@ -212,7 +193,8 @@ export class ConfigurationManager {
212193
bestFor: [
213194
"Team collaboration",
214195
"Production deployments",
215-
"Web applications"
196+
"Web applications",
197+
"Multi-user environments"
216198
]
217199
},
218200
{
@@ -223,7 +205,8 @@ export class ConfigurationManager {
223205
"Wide adoption",
224206
"Good performance",
225207
"Multi-user support",
226-
"Full-text search"
208+
"Full-text search",
209+
"Mature ecosystem"
227210
],
228211
cons: [
229212
"Requires database server",
@@ -233,27 +216,82 @@ export class ConfigurationManager {
233216
bestFor: [
234217
"Web applications",
235218
"Existing MySQL infrastructure",
236-
"Team collaboration"
219+
"Team collaboration",
220+
"LAMP stack projects"
237221
]
238222
}
239223
];
240224
}
241225

226+
/**
227+
* Detect the best sync strategy based on environment and integrations
228+
*/
229+
async detectSyncStrategy(integrations?: EnterpriseIntegration): Promise<SyncStrategy> {
230+
// Default sync strategy
231+
const defaultStrategy: SyncStrategy = {
232+
sourceOfTruth: 'local',
233+
conflictResolution: 'local-wins',
234+
syncInterval: 15, // 15 minutes
235+
autoSync: true
236+
};
237+
238+
// If no integrations configured, return minimal strategy
239+
if (!integrations || !this.hasEnterpriseIntegrations(integrations)) {
240+
return {
241+
sourceOfTruth: 'local',
242+
conflictResolution: 'local-wins',
243+
autoSync: false
244+
};
245+
}
246+
247+
// Check for environment-specific sync preferences
248+
if (process.env.DEVLOG_SYNC_STRATEGY) {
249+
const strategy = process.env.DEVLOG_SYNC_STRATEGY.toLowerCase();
250+
switch (strategy) {
251+
case 'external':
252+
return {
253+
...defaultStrategy,
254+
sourceOfTruth: 'external',
255+
conflictResolution: 'external-wins'
256+
};
257+
case 'manual':
258+
return {
259+
...defaultStrategy,
260+
sourceOfTruth: 'manual',
261+
conflictResolution: 'manual',
262+
autoSync: false
263+
};
264+
}
265+
}
266+
267+
// Configure sync interval from environment
268+
if (process.env.DEVLOG_SYNC_INTERVAL) {
269+
const interval = parseInt(process.env.DEVLOG_SYNC_INTERVAL);
270+
if (!isNaN(interval) && interval > 0) {
271+
defaultStrategy.syncInterval = interval;
272+
}
273+
}
274+
275+
return defaultStrategy;
276+
}
277+
242278
private async createDefaultConfig(): Promise<DevlogConfig> {
243279
const storage = await this.detectBestStorage();
244280
const integrations = await this.detectEnterpriseIntegrations();
281+
const syncStrategy = await this.detectSyncStrategy(integrations);
245282
const detectedRoot = await this.detectProjectRoot();
246283
const workspace = await this.getWorkspaceStructure(detectedRoot || undefined);
247284

248285
return {
249286
storage,
250287
integrations,
288+
syncStrategy,
251289
workspaceRoot: detectedRoot || this.workspaceRoot,
252290
workspaceId: path.basename(workspace.workspaceDir)
253291
};
254292
}
255293

256-
private async detectEnterpriseIntegrations(): Promise<EnterpriseIntegration | undefined> {
294+
async detectEnterpriseIntegrations(): Promise<EnterpriseIntegration | undefined> {
257295
const integrations: EnterpriseIntegration = {};
258296

259297
// Check for Jira configuration

packages/core/src/devlog-manager.ts

Lines changed: 36 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -39,24 +39,29 @@ export interface DiscoveryResult {
3939
}
4040

4141
import { StorageProvider, StorageConfig, StorageProviderFactory } from "./storage/storage-provider.js";
42-
import { ConfigurationManager } from "./configuration-manager.js";
42+
import { ConfigurationManager, SyncStrategy } from "./configuration-manager.js";
43+
import { IntegrationService } from "./integration-service.js";
4344

4445
export interface DevlogManagerOptions {
4546
workspaceRoot?: string;
4647
storage?: StorageConfig;
4748
integrations?: EnterpriseIntegration;
49+
syncStrategy?: SyncStrategy;
4850
}
4951

5052
export class DevlogManager {
5153
private storageProvider!: StorageProvider;
54+
private integrationService!: IntegrationService;
5255
private readonly workspaceRoot: string;
5356
private readonly integrations?: EnterpriseIntegration;
57+
private readonly syncStrategy?: SyncStrategy;
5458
private readonly configManager: ConfigurationManager;
5559
private initialized = false;
5660

5761
constructor(private options: DevlogManagerOptions = {}) {
5862
this.workspaceRoot = options.workspaceRoot || process.cwd();
5963
this.integrations = options.integrations;
64+
this.syncStrategy = options.syncStrategy;
6065
this.configManager = new ConfigurationManager(this.workspaceRoot);
6166
}
6267

@@ -69,6 +74,17 @@ export class DevlogManager {
6974
const storageConfig = await this.determineStorageConfig();
7075
this.storageProvider = await StorageProviderFactory.create(storageConfig);
7176
await this.storageProvider.initialize();
77+
78+
// Initialize integration service if integrations are configured
79+
const integrations = this.integrations || (await this.configManager.detectEnterpriseIntegrations());
80+
const syncStrategy = this.syncStrategy || (await this.configManager.detectSyncStrategy(integrations));
81+
82+
this.integrationService = new IntegrationService(
83+
this.storageProvider,
84+
integrations,
85+
syncStrategy
86+
);
87+
7288
this.initialized = true;
7389
}
7490

@@ -131,7 +147,8 @@ export class DevlogManager {
131147
}
132148
};
133149

134-
await this.storageProvider.save(entry);
150+
// Save with integration sync
151+
await this.integrationService.saveWithSync(entry);
135152
return entry;
136153
}
137154

@@ -183,7 +200,7 @@ export class DevlogManager {
183200
updated.notes.push(note);
184201
}
185202

186-
await this.storageProvider.save(updated);
203+
await this.integrationService.saveWithSync(updated);
187204
return updated;
188205
}
189206

@@ -211,7 +228,7 @@ export class DevlogManager {
211228
updatedAt: new Date().toISOString()
212229
};
213230

214-
await this.storageProvider.save(updated);
231+
await this.integrationService.saveWithSync(updated);
215232
return updated;
216233
}
217234

@@ -439,6 +456,21 @@ export class DevlogManager {
439456
};
440457
}
441458

459+
/**
460+
* Manually sync a devlog entry with external systems
461+
*/
462+
async syncDevlog(id: string): Promise<DevlogEntry | null> {
463+
await this.ensureInitialized();
464+
return await this.integrationService.syncEntry(id);
465+
}
466+
467+
/**
468+
* Get sync status for a devlog entry
469+
*/
470+
getSyncStatus(entry: DevlogEntry) {
471+
return this.integrationService.getSyncStatus(entry);
472+
}
473+
442474
// Private methods
443475

444476
private async ensureInitialized(): Promise<void> {
@@ -452,14 +484,6 @@ export class DevlogManager {
452484
return this.options.storage;
453485
}
454486

455-
// If we have enterprise integrations, prioritize them in the configuration manager
456-
if (this.integrations && this.hasEnterpriseIntegrations()) {
457-
return {
458-
type: "enterprise",
459-
options: { integrations: this.integrations }
460-
};
461-
}
462-
463487
// Use ConfigurationManager to detect the best storage configuration
464488
// This will automatically handle ~/.devlog folder creation and usage
465489
return await this.configManager.detectBestStorage();

packages/core/src/index.ts

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,19 +5,24 @@ export {
55
type DiscoveredDevlogEntry,
66
type DiscoveryResult
77
} from "./devlog-manager.js";
8-
export { ConfigurationManager, type DevlogConfig } from "./configuration-manager.js";
8+
export { ConfigurationManager, type DevlogConfig, type SyncStrategy } from "./configuration-manager.js";
99

10-
// Storage Providers
10+
// Integration Service
11+
export {
12+
IntegrationService,
13+
type SyncStatus,
14+
type ConflictData
15+
} from "./integration-service.js";
16+
17+
// Storage Providers
1118
export {
1219
StorageProviderFactory,
1320
type StorageProvider,
14-
type StorageConfig,
15-
type EnterpriseStorageProvider
21+
type StorageConfig
1622
} from "./storage/storage-provider.js";
1723
export { SQLiteStorageProvider } from "./storage/sqlite-storage.js";
1824
export { PostgreSQLStorageProvider } from "./storage/postgresql-storage.js";
1925
export { MySQLStorageProvider } from "./storage/mysql-storage.js";
20-
export { EnterpriseStorageAdapter } from "./storage/enterprise-storage.js";
2126
export { EnterpriseSync } from "./integrations/enterprise-sync.js";
2227
export { DevlogUtils } from "./utils/devlog-utils.js";
2328

0 commit comments

Comments
 (0)