Skip to content

Commit 56edd32

Browse files
committed
refactor: update devlog and project services to use singleton pattern and improve instance management
1 parent 7b6c19a commit 56edd32

File tree

11 files changed

+83
-97
lines changed

11 files changed

+83
-97
lines changed

packages/cli/src/index.ts

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,19 +11,18 @@ import { Command } from 'commander';
1111
import chalk from 'chalk';
1212
import Table from 'cli-table3';
1313
import ora from 'ora';
14-
import { resolve } from 'path';
1514
import ProgressBar from 'progress';
1615
import {
1716
ChatStatistics,
1817
CopilotParser,
1918
SearchResult,
20-
ProjectDataContainer,
19+
WorkspaceDataContainer,
2120
} from '@codervisor/devlog-ai';
2221
import { DevlogApiClient, ChatImportRequest } from './api/devlog-api-client.js';
2322
import {
24-
convertProjectDataToCoreFormat,
25-
extractProjectInfo,
2623
validateConvertedData,
24+
convertWorkspaceDataToCoreFormat,
25+
extractWorkspaceInfo,
2726
} from './utils/data-mapper.js';
2827
import {
2928
displayError,
@@ -88,7 +87,7 @@ async function setupApiClient(options: BaseCommandOptions): Promise<DevlogApiCli
8887
}
8988

9089
function getProjectId(options: BaseCommandOptions, config: ConfigOptions): string {
91-
const projectId = options.project || config.project || process.env.DEVLOG_PROJECT;
90+
const projectId = options.project || process.env.DEVLOG_PROJECT;
9291
if (!projectId) {
9392
displayError(
9493
'configuration',
@@ -160,7 +159,9 @@ program
160159
}
161160

162161
// Convert AI package data to Core package format
163-
const convertedData = convertProjectDataToCoreFormat(projectData as ProjectDataContainer);
162+
const convertedData = convertWorkspaceDataToCoreFormat(
163+
projectData as WorkspaceDataContainer,
164+
);
164165

165166
// Validate the converted data
166167
if (!validateConvertedData(convertedData)) {
@@ -174,7 +175,7 @@ program
174175
sessions: convertedData.sessions,
175176
messages: convertedData.messages,
176177
source: options.source,
177-
projectInfo: extractProjectInfo(projectData as ProjectDataContainer),
178+
workspaceInfo: extractWorkspaceInfo(projectData as WorkspaceDataContainer),
178179
};
179180

180181
// Start import

packages/core/src/services/devlog-service.ts

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,15 +19,45 @@ import type {
1919
import { DevlogEntryEntity } from '../entities/index.js';
2020
import { createDataSource } from '../utils/typeorm-config.js';
2121

22+
interface DevlogServiceInstance {
23+
service: DevlogService;
24+
createdAt: number;
25+
}
26+
2227
export class DevlogService {
28+
private static instances: Map<number, DevlogServiceInstance> = new Map();
29+
private static readonly TTL_MS = 5 * 60 * 1000; // 5 minutes TTL
2330
private database: DataSource;
2431
private devlogRepository: Repository<DevlogEntryEntity>;
2532

26-
constructor(private projectId?: number) {
33+
private constructor(private projectId?: number) {
2734
this.database = createDataSource();
2835
this.devlogRepository = this.database.getRepository(DevlogEntryEntity);
2936
}
3037

38+
/**
39+
* Get singleton instance for specific projectId with TTL. If TTL expired, create new instance.
40+
*/
41+
static getInstance(projectId?: number): DevlogService {
42+
const instanceKey = projectId || 0; // Use 0 for undefined projectId
43+
const now = Date.now();
44+
const existingInstance = DevlogService.instances.get(instanceKey);
45+
46+
if (
47+
!existingInstance ||
48+
now - existingInstance.createdAt > DevlogService.TTL_MS
49+
) {
50+
const newService = new DevlogService(projectId);
51+
DevlogService.instances.set(instanceKey, {
52+
service: newService,
53+
createdAt: now
54+
});
55+
return newService;
56+
}
57+
58+
return existingInstance.service;
59+
}
60+
3161
async get(id: DevlogId): Promise<DevlogEntry | null> {
3262
const entity = await this.devlogRepository.findOne({ where: { id } });
3363

packages/core/src/services/project-service.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
import { DataSource, Repository } from 'typeorm';
99
import type { ProjectMetadata } from '../types/project.js';
1010
import { ProjectEntity } from '../entities/project.entity.js';
11-
import { createDataSource } from '@/utils/typeorm-config';
11+
import { createDataSource } from '../utils/typeorm-config.js';
1212

1313
export class ProjectService {
1414
private static instance: ProjectService | null = null;

packages/web/.devlog/devlog.sqlite

Whitespace-only changes.

packages/web/app/api/projects/[id]/devlogs/[devlogId]/route.ts

Lines changed: 10 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import { NextRequest, NextResponse } from 'next/server';
2-
import { getProjectManager } from '../../../../../lib/project-manager';
3-
import { createDevlogService } from '../../../../../lib/devlog-service';
2+
import { DevlogService, ProjectService } from '@codervisor/devlog-core';
43

54
// Mark this route as dynamic to prevent static generation
65
export const dynamic = 'force-dynamic';
@@ -11,13 +10,13 @@ export async function GET(
1110
{ params }: { params: { id: number; devlogId: number } },
1211
) {
1312
try {
14-
const projectManager = await getProjectManager();
15-
const project = await projectManager.get(params.id);
16-
13+
const projectService = ProjectService.getInstance();
14+
const project = await projectService.get(params.id);
1715
if (!project) {
1816
return NextResponse.json({ error: 'Project not found' }, { status: 404 });
1917
}
2018

19+
const devlogService = DevlogService.getInstance(params.id);
2120
const entry = await devlogService.get(params.devlogId);
2221

2322
if (!entry) {
@@ -37,25 +36,19 @@ export async function PUT(
3736
{ params }: { params: { id: number; devlogId: number } },
3837
) {
3938
try {
40-
const projectManager = await getProjectManager();
41-
const project = await projectManager.get(params.id);
42-
39+
const projectService = ProjectService.getInstance();
40+
const project = await projectService.get(params.id);
4341
if (!project) {
4442
return NextResponse.json({ error: 'Project not found' }, { status: 404 });
4543
}
4644

4745
const data = await request.json();
4846

49-
// Create project-aware devlog service
50-
const devlogService = await createDevlogService({
51-
projectId: params.id,
52-
project,
53-
});
47+
const devlogService = DevlogService.getInstance(params.id);
5448

5549
// Verify entry exists and belongs to project
5650
const existingEntry = await devlogService.get(params.devlogId);
5751
if (!existingEntry) {
58-
await devlogService.dispose();
5952
return NextResponse.json({ error: 'Devlog entry not found' }, { status: 404 });
6053
}
6154

@@ -70,8 +63,6 @@ export async function PUT(
7063

7164
await devlogService.save(updatedEntry);
7265

73-
await devlogService.dispose();
74-
7566
return NextResponse.json(updatedEntry);
7667
} catch (error) {
7768
console.error('Error updating devlog:', error);
@@ -86,30 +77,23 @@ export async function DELETE(
8677
{ params }: { params: { id: number; devlogId: number } },
8778
) {
8879
try {
89-
const projectManager = await getProjectManager();
90-
const project = await projectManager.get(params.id);
80+
const projectService = ProjectService.getInstance();
81+
const project = await projectService.get(params.id);
9182

9283
if (!project) {
9384
return NextResponse.json({ error: 'Project not found' }, { status: 404 });
9485
}
9586

96-
// Create project-aware devlog service
97-
const devlogService = await createDevlogService({
98-
projectId: params.id,
99-
project,
100-
});
87+
const devlogService = DevlogService.getInstance(params.id);
10188

10289
// Verify entry exists and belongs to project
10390
const existingEntry = await devlogService.get(params.devlogId);
10491
if (!existingEntry) {
105-
await devlogService.dispose();
10692
return NextResponse.json({ error: 'Devlog entry not found' }, { status: 404 });
10793
}
10894

10995
await devlogService.delete(params.devlogId);
11096

111-
await devlogService.dispose();
112-
11397
return NextResponse.json({ success: true });
11498
} catch (error) {
11599
console.error('Error deleting devlog:', error);

packages/web/app/api/projects/[id]/devlogs/batch/delete/route.ts

Lines changed: 5 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
11
import { NextRequest, NextResponse } from 'next/server';
2-
import { getProjectManager } from '../../../../../../lib/project-manager';
3-
import { createDevlogService } from '../../../../../../lib/devlog-service';
2+
import { DevlogService, ProjectService } from '@codervisor/devlog-core';
43

54
// Mark this route as dynamic to prevent static generation
65
export const dynamic = 'force-dynamic';
76

87
// POST /api/projects/[id]/devlogs/batch/delete - Batch delete devlog entries
98
export async function POST(request: NextRequest, { params }: { params: { id: number } }) {
109
try {
11-
const projectManager = await getProjectManager();
12-
const project = await projectManager.get(params.id);
10+
const projectService = ProjectService.getInstance();
11+
await projectService.initialize();
12+
const project = await projectService.get(params.id);
1313

1414
if (!project) {
1515
return NextResponse.json({ error: 'Project not found' }, { status: 404 });
@@ -24,11 +24,7 @@ export async function POST(request: NextRequest, { params }: { params: { id: num
2424
);
2525
}
2626

27-
// Create project-aware devlog service
28-
const devlogService = await createDevlogService({
29-
projectId: params.id,
30-
project,
31-
});
27+
const devlogService = DevlogService.getInstance(params.id);
3228

3329
const deletedIds = [];
3430
const errors = [];
@@ -54,8 +50,6 @@ export async function POST(request: NextRequest, { params }: { params: { id: num
5450
}
5551
}
5652

57-
await devlogService.dispose();
58-
5953
return NextResponse.json({
6054
success: true,
6155
deleted: deletedIds,

packages/web/app/api/projects/[id]/devlogs/batch/note/route.ts

Lines changed: 5 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
11
import { NextRequest, NextResponse } from 'next/server';
2-
import { getProjectManager } from '../../../../../../lib/project-manager';
3-
import { createDevlogService } from '../../../../../../lib/devlog-service';
2+
import { DevlogService, ProjectService } from '@codervisor/devlog-core';
43

54
// Mark this route as dynamic to prevent static generation
65
export const dynamic = 'force-dynamic';
76

87
// POST /api/projects/[id]/devlogs/batch/note - Batch add notes to devlog entries
98
export async function POST(request: NextRequest, { params }: { params: { id: number } }) {
109
try {
11-
const projectManager = await getProjectManager();
12-
const project = await projectManager.get(params.id);
10+
const projectService = ProjectService.getInstance();
11+
await projectService.initialize();
12+
const project = await projectService.get(params.id);
1313

1414
if (!project) {
1515
return NextResponse.json({ error: 'Project not found' }, { status: 404 });
@@ -24,11 +24,7 @@ export async function POST(request: NextRequest, { params }: { params: { id: num
2424
);
2525
}
2626

27-
// Create project-aware devlog service
28-
const devlogService = await createDevlogService({
29-
projectId: params.id,
30-
project,
31-
});
27+
const devlogService = DevlogService.getInstance(params.id);
3228

3329
const updatedEntries = [];
3430
const errors = [];
@@ -68,8 +64,6 @@ export async function POST(request: NextRequest, { params }: { params: { id: num
6864
}
6965
}
7066

71-
await devlogService.dispose();
72-
7367
return NextResponse.json({
7468
success: true,
7569
updated: updatedEntries,

packages/web/app/api/projects/[id]/devlogs/batch/update/route.ts

Lines changed: 5 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
11
import { NextRequest, NextResponse } from 'next/server';
2-
import { getProjectManager } from '../../../../../../lib/project-manager';
3-
import { createDevlogService } from '../../../../../../lib/devlog-service';
2+
import { DevlogService, ProjectService } from '@codervisor/devlog-core';
43

54
// Mark this route as dynamic to prevent static generation
65
export const dynamic = 'force-dynamic';
76

87
// POST /api/projects/[id]/devlogs/batch/update - Batch update devlog entries
98
export async function POST(request: NextRequest, { params }: { params: { id: number } }) {
109
try {
11-
const projectManager = await getProjectManager();
12-
const project = await projectManager.get(params.id);
10+
const projectService = ProjectService.getInstance();
11+
await projectService.initialize();
12+
const project = await projectService.get(params.id);
1313

1414
if (!project) {
1515
return NextResponse.json({ error: 'Project not found' }, { status: 404 });
@@ -24,11 +24,7 @@ export async function POST(request: NextRequest, { params }: { params: { id: num
2424
);
2525
}
2626

27-
// Create project-aware devlog service
28-
const devlogService = await createDevlogService({
29-
projectId: params.id,
30-
project,
31-
});
27+
const devlogService = DevlogService.getInstance(params.id);
3228

3329
const updatedEntries = [];
3430
const errors = [];
@@ -62,8 +58,6 @@ export async function POST(request: NextRequest, { params }: { params: { id: num
6258
}
6359
}
6460

65-
await devlogService.dispose();
66-
6761
return NextResponse.json({
6862
success: true,
6963
updated: updatedEntries,

packages/web/app/api/projects/[id]/devlogs/route.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ export async function GET(request: NextRequest, { params }: { params: { id: numb
1515
}
1616

1717
// Create project-aware devlog service
18-
const devlogService = new DevlogService();
18+
const devlogService = DevlogService.getInstance(params.id);
1919

2020
// Parse query parameters for filtering
2121
const url = new URL(request.url);
@@ -82,7 +82,7 @@ export async function POST(request: NextRequest, { params }: { params: { id: num
8282
const data = await request.json();
8383

8484
// Create project-aware devlog service
85-
const devlogService = new DevlogService();
85+
const devlogService = DevlogService.getInstance(params.id);
8686

8787
// Add required fields if missing
8888
const now = new Date().toISOString();

packages/web/app/api/projects/[id]/devlogs/stats/overview/route.ts

Lines changed: 5 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,23 @@
11
import { NextRequest, NextResponse } from 'next/server';
2-
import { getProjectManager } from '../../../../../../lib/project-manager';
3-
import { createDevlogService } from '../../../../../../lib/devlog-service';
2+
import { DevlogService, ProjectService } from '@codervisor/devlog-core';
43

54
// Mark this route as dynamic to prevent static generation
65
export const dynamic = 'force-dynamic';
76

87
// GET /api/projects/[id]/devlogs/stats/overview - Get overview statistics
98
export async function GET(request: NextRequest, { params }: { params: { id: number } }) {
109
try {
11-
const projectManager = await getProjectManager();
12-
const project = await projectManager.get(params.id);
10+
const projectService = ProjectService.getInstance();
11+
await projectService.initialize();
12+
const project = await projectService.get(params.id);
1313

1414
if (!project) {
1515
return NextResponse.json({ error: 'Project not found' }, { status: 404 });
1616
}
1717

18-
// Create project-aware devlog service
19-
const devlogService = await createDevlogService({
20-
projectId: params.id,
21-
project,
22-
});
23-
18+
const devlogService = DevlogService.getInstance(params.id);
2419
const stats = await devlogService.getStats();
2520

26-
await devlogService.dispose();
27-
2821
return NextResponse.json(stats);
2922
} catch (error) {
3023
console.error('Error fetching devlog stats:', error);

0 commit comments

Comments
 (0)