Skip to content

Commit fd59af9

Browse files
author
Marvin Zhang
committed
refactor: Simplify initialization logic in ProjectDevlogManager and DatabaseProjectManager
- Removed unnecessary initialized flags and replaced them with promises to handle initialization state. - Updated ensureInitialized methods to await initialization when necessary. - Improved logging during initialization processes for better traceability. feat: Introduce DatabaseService for streamlined database connection management - Added DatabaseService to manage TypeORM DataSource lifecycle. - Replaced complex storage factory with direct DataSource management. - Implemented auto-initialization and connection testing features. feat: Implement DevlogService for simplified devlog operations - Created DevlogService to handle business logic for devlog entries using TypeORM repositories. - Replaced ProjectDevlogManager with a cleaner service-based approach. - Added methods for CRUD operations, statistics retrieval, and chat session management. refactor: Update API routes to use numeric IDs for project and devlog parameters - Changed parameter types from string to number for better type safety. - Removed isDefault property from project context as it is no longer needed. chore: Update service exports in index.ts - Exported new DevlogService and DatabaseService from the services index.
1 parent 722c141 commit fd59af9

File tree

17 files changed

+1135
-90
lines changed

17 files changed

+1135
-90
lines changed

packages/core/src/entities/chat-devlog-link.entity.ts

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,4 +39,78 @@ export class ChatDevlogLinkEntity {
3939

4040
@Column({ type: 'varchar', length: 255, name: 'created_by' })
4141
createdBy!: string;
42+
43+
/**
44+
* Convert entity to ChatDevlogLink interface
45+
*/
46+
toChatDevlogLink(): import('../types/index.js').ChatDevlogLink {
47+
return {
48+
sessionId: this.sessionId,
49+
devlogId: this.devlogId,
50+
confidence: this.confidence,
51+
reason: this.reason,
52+
evidence: this.parseJsonField(this.evidence, {}),
53+
confirmed: this.confirmed,
54+
createdAt: this.createdAt,
55+
createdBy: this.createdBy,
56+
};
57+
}
58+
59+
/**
60+
* Create entity from ChatDevlogLink interface
61+
*/
62+
static fromChatDevlogLink(
63+
link: import('../types/index.js').ChatDevlogLink,
64+
): ChatDevlogLinkEntity {
65+
const entity = new ChatDevlogLinkEntity();
66+
67+
entity.sessionId = link.sessionId;
68+
entity.devlogId = link.devlogId;
69+
entity.confidence = link.confidence;
70+
entity.reason = link.reason;
71+
entity.evidence = entity.stringifyJsonField(link.evidence || {});
72+
entity.confirmed = link.confirmed;
73+
entity.createdAt = link.createdAt;
74+
entity.createdBy = link.createdBy;
75+
76+
return entity;
77+
}
78+
79+
/**
80+
* Helper method for JSON field parsing (database-specific)
81+
*/
82+
private parseJsonField<T>(value: any, defaultValue: T): T {
83+
if (value === null || value === undefined) {
84+
return defaultValue;
85+
}
86+
87+
// For SQLite, values are stored as text and need parsing
88+
if (getStorageType() === 'sqlite' && typeof value === 'string') {
89+
try {
90+
return JSON.parse(value);
91+
} catch {
92+
return defaultValue;
93+
}
94+
}
95+
96+
// For PostgreSQL and MySQL, JSON fields are handled natively
97+
return value;
98+
}
99+
100+
/**
101+
* Helper method for JSON field stringification (database-specific)
102+
*/
103+
private stringifyJsonField(value: any): any {
104+
if (value === null || value === undefined) {
105+
return value;
106+
}
107+
108+
// For SQLite, we need to stringify JSON data
109+
if (getStorageType() === 'sqlite') {
110+
return typeof value === 'string' ? value : JSON.stringify(value);
111+
}
112+
113+
// For PostgreSQL and MySQL, return the object directly
114+
return value;
115+
}
42116
}

packages/core/src/entities/chat-message.entity.ts

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,4 +40,76 @@ export class ChatMessageEntity {
4040

4141
@Column({ type: 'text', nullable: true, name: 'search_content' })
4242
searchContent?: string;
43+
44+
/**
45+
* Convert entity to ChatMessage interface
46+
*/
47+
toChatMessage(): import('../types/index.js').ChatMessage {
48+
return {
49+
id: this.id,
50+
sessionId: this.sessionId,
51+
role: this.role,
52+
content: this.content,
53+
timestamp: this.timestamp,
54+
sequence: this.sequence,
55+
metadata: this.parseJsonField(this.metadata, {}),
56+
searchContent: this.searchContent,
57+
};
58+
}
59+
60+
/**
61+
* Create entity from ChatMessage interface
62+
*/
63+
static fromChatMessage(message: import('../types/index.js').ChatMessage): ChatMessageEntity {
64+
const entity = new ChatMessageEntity();
65+
66+
entity.id = message.id;
67+
entity.sessionId = message.sessionId;
68+
entity.role = message.role;
69+
entity.content = message.content;
70+
entity.timestamp = message.timestamp;
71+
entity.sequence = message.sequence;
72+
entity.metadata = entity.stringifyJsonField(message.metadata || {});
73+
entity.searchContent = message.searchContent;
74+
75+
return entity;
76+
}
77+
78+
/**
79+
* Helper method for JSON field parsing (database-specific)
80+
*/
81+
private parseJsonField<T>(value: any, defaultValue: T): T {
82+
if (value === null || value === undefined) {
83+
return defaultValue;
84+
}
85+
86+
// For SQLite, values are stored as text and need parsing
87+
if (getStorageType() === 'sqlite' && typeof value === 'string') {
88+
try {
89+
return JSON.parse(value);
90+
} catch {
91+
return defaultValue;
92+
}
93+
}
94+
95+
// For PostgreSQL and MySQL, JSON fields are handled natively
96+
return value;
97+
}
98+
99+
/**
100+
* Helper method for JSON field stringification (database-specific)
101+
*/
102+
private stringifyJsonField(value: any): any {
103+
if (value === null || value === undefined) {
104+
return value;
105+
}
106+
107+
// For SQLite, we need to stringify JSON data
108+
if (getStorageType() === 'sqlite') {
109+
return typeof value === 'string' ? value : JSON.stringify(value);
110+
}
111+
112+
// For PostgreSQL and MySQL, return the object directly
113+
return value;
114+
}
43115
}

packages/core/src/entities/chat-session.entity.ts

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,4 +60,93 @@ export class ChatSessionEntity {
6060

6161
@Column({ type: 'boolean', default: false })
6262
archived!: boolean;
63+
64+
@JsonColumn({ default: getStorageType() === 'sqlite' ? '[]' : [], name: 'linked_devlogs' })
65+
linkedDevlogs!: number[];
66+
67+
/**
68+
* Convert entity to ChatSession interface
69+
*/
70+
toChatSession(): import('../types/index.js').ChatSession {
71+
return {
72+
id: this.id,
73+
agent: this.agent,
74+
timestamp: this.timestamp,
75+
workspace: this.workspace,
76+
workspacePath: this.workspacePath,
77+
title: this.title,
78+
status: this.status,
79+
messageCount: this.messageCount,
80+
duration: this.duration,
81+
metadata: this.parseJsonField(this.metadata, {}),
82+
tags: this.parseJsonField(this.tags, []),
83+
importedAt: this.importedAt,
84+
updatedAt: this.updatedAt,
85+
linkedDevlogs: this.parseJsonField(this.linkedDevlogs, []),
86+
archived: this.archived,
87+
};
88+
}
89+
90+
/**
91+
* Create entity from ChatSession interface
92+
*/
93+
static fromChatSession(session: import('../types/index.js').ChatSession): ChatSessionEntity {
94+
const entity = new ChatSessionEntity();
95+
96+
entity.id = session.id;
97+
entity.agent = session.agent;
98+
entity.timestamp = session.timestamp;
99+
entity.workspace = session.workspace;
100+
entity.workspacePath = session.workspacePath;
101+
entity.title = session.title;
102+
entity.status = session.status || 'imported';
103+
entity.messageCount = session.messageCount || 0;
104+
entity.duration = session.duration;
105+
entity.metadata = entity.stringifyJsonField(session.metadata || {});
106+
entity.tags = entity.stringifyJsonField(session.tags || []);
107+
entity.importedAt = session.importedAt;
108+
entity.updatedAt = session.updatedAt;
109+
entity.linkedDevlogs = entity.stringifyJsonField(session.linkedDevlogs || []);
110+
entity.archived = session.archived || false;
111+
112+
return entity;
113+
}
114+
115+
/**
116+
* Helper method for JSON field parsing (database-specific)
117+
*/
118+
private parseJsonField<T>(value: any, defaultValue: T): T {
119+
if (value === null || value === undefined) {
120+
return defaultValue;
121+
}
122+
123+
// For SQLite, values are stored as text and need parsing
124+
if (getStorageType() === 'sqlite' && typeof value === 'string') {
125+
try {
126+
return JSON.parse(value);
127+
} catch {
128+
return defaultValue;
129+
}
130+
}
131+
132+
// For PostgreSQL and MySQL, JSON fields are handled natively
133+
return value;
134+
}
135+
136+
/**
137+
* Helper method for JSON field stringification (database-specific)
138+
*/
139+
private stringifyJsonField(value: any): any {
140+
if (value === null || value === undefined) {
141+
return value;
142+
}
143+
144+
// For SQLite, we need to stringify JSON data
145+
if (getStorageType() === 'sqlite') {
146+
return typeof value === 'string' ? value : JSON.stringify(value);
147+
}
148+
149+
// For PostgreSQL and MySQL, return the object directly
150+
return value;
151+
}
63152
}

packages/core/src/entities/devlog-entry.entity.ts

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,4 +89,95 @@ export class DevlogEntryEntity {
8989

9090
@JsonColumn({ default: getStorageType() === 'sqlite' ? '[]' : [], name: 'acceptance_criteria' })
9191
acceptanceCriteria!: string[];
92+
93+
/**
94+
* Convert entity to DevlogEntry interface
95+
*/
96+
toDevlogEntry(): import('../types/index.js').DevlogEntry {
97+
return {
98+
id: this.id,
99+
key: this.key,
100+
title: this.title,
101+
type: this.type,
102+
description: this.description,
103+
status: this.status,
104+
priority: this.priority,
105+
createdAt: this.createdAt.toISOString(),
106+
updatedAt: this.updatedAt.toISOString(),
107+
closedAt: this.closedAt?.toISOString(),
108+
archived: this.archived,
109+
assignee: this.assignee,
110+
projectId: this.projectId,
111+
acceptanceCriteria: this.parseJsonField(this.acceptanceCriteria, []),
112+
businessContext: this.businessContext,
113+
technicalContext: this.technicalContext,
114+
// Related entities will be loaded separately when needed
115+
notes: [],
116+
dependencies: [],
117+
};
118+
}
119+
120+
/**
121+
* Create entity from DevlogEntry interface
122+
*/
123+
static fromDevlogEntry(entry: import('../types/index.js').DevlogEntry): DevlogEntryEntity {
124+
const entity = new DevlogEntryEntity();
125+
126+
if (entry.id) entity.id = entry.id;
127+
entity.key = entry.key || '';
128+
entity.title = entry.title;
129+
entity.type = entry.type;
130+
entity.description = entry.description;
131+
entity.status = entry.status;
132+
entity.priority = entry.priority;
133+
entity.createdAt = new Date(entry.createdAt);
134+
entity.updatedAt = new Date(entry.updatedAt);
135+
if (entry.closedAt) entity.closedAt = new Date(entry.closedAt);
136+
entity.archived = entry.archived || false;
137+
entity.assignee = entry.assignee;
138+
entity.projectId = entry.projectId;
139+
entity.acceptanceCriteria = entity.stringifyJsonField(entry.acceptanceCriteria || []);
140+
entity.businessContext = entry.businessContext;
141+
entity.technicalContext = entry.technicalContext;
142+
143+
return entity;
144+
}
145+
146+
/**
147+
* Helper method for JSON field parsing (database-specific)
148+
*/
149+
private parseJsonField<T>(value: any, defaultValue: T): T {
150+
if (value === null || value === undefined) {
151+
return defaultValue;
152+
}
153+
154+
// For SQLite, values are stored as text and need parsing
155+
if (getStorageType() === 'sqlite' && typeof value === 'string') {
156+
try {
157+
return JSON.parse(value);
158+
} catch {
159+
return defaultValue;
160+
}
161+
}
162+
163+
// For PostgreSQL and MySQL, JSON fields are handled natively
164+
return value;
165+
}
166+
167+
/**
168+
* Helper method for JSON field stringification (database-specific)
169+
*/
170+
private stringifyJsonField(value: any): any {
171+
if (value === null || value === undefined) {
172+
return value;
173+
}
174+
175+
// For SQLite, we need to stringify JSON data
176+
if (getStorageType() === 'sqlite') {
177+
return typeof value === 'string' ? value : JSON.stringify(value);
178+
}
179+
180+
// For PostgreSQL and MySQL, return the object directly
181+
return value;
182+
}
92183
}

0 commit comments

Comments
 (0)