fix: meta diff code deduplication
This commit is contained in:
@@ -77,6 +77,45 @@ export interface TableStats {
|
|||||||
export class MetadataDiffEngine extends EventEmitter {
|
export class MetadataDiffEngine extends EventEmitter {
|
||||||
private currentProjectId = 'default';
|
private currentProjectId = 'default';
|
||||||
|
|
||||||
|
private async runSyncLoop(
|
||||||
|
postIds: string[],
|
||||||
|
onProgress: ((percent: number, message: string) => void) | undefined,
|
||||||
|
processPost: (postId: string) => Promise<boolean>,
|
||||||
|
errorMessage: (postId: string) => string
|
||||||
|
): Promise<{ success: number; failed: number }> {
|
||||||
|
const total = postIds.length;
|
||||||
|
let success = 0;
|
||||||
|
let failed = 0;
|
||||||
|
|
||||||
|
for (let i = 0; i < postIds.length; i++) {
|
||||||
|
const postId = postIds[i];
|
||||||
|
try {
|
||||||
|
const processed = await processPost(postId);
|
||||||
|
if (processed) {
|
||||||
|
success++;
|
||||||
|
} else {
|
||||||
|
failed++;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error(errorMessage(postId), error);
|
||||||
|
failed++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Report progress every 10 posts or on last post
|
||||||
|
if (onProgress && (i % 10 === 0 || i === postIds.length - 1)) {
|
||||||
|
const percent = Math.round(((i + 1) / total) * 100);
|
||||||
|
onProgress(percent, `Synced ${i + 1} of ${total} posts...`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Yield to event loop every 20 posts
|
||||||
|
if (i % 20 === 0) {
|
||||||
|
await new Promise(resolve => setImmediate(resolve));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return { success, failed };
|
||||||
|
}
|
||||||
|
|
||||||
setProjectContext(projectId: string): void {
|
setProjectContext(projectId: string): void {
|
||||||
this.currentProjectId = projectId;
|
this.currentProjectId = projectId;
|
||||||
}
|
}
|
||||||
@@ -325,37 +364,12 @@ export class MetadataDiffEngine extends EventEmitter {
|
|||||||
onProgress?: (percent: number, message: string) => void
|
onProgress?: (percent: number, message: string) => void
|
||||||
): Promise<{ success: number; failed: number }> {
|
): Promise<{ success: number; failed: number }> {
|
||||||
const postEngine = getPostEngine();
|
const postEngine = getPostEngine();
|
||||||
const total = postIds.length;
|
return this.runSyncLoop(
|
||||||
let success = 0;
|
postIds,
|
||||||
let failed = 0;
|
onProgress,
|
||||||
|
async (postId) => postEngine.syncPublishedPostFile(postId),
|
||||||
for (let i = 0; i < postIds.length; i++) {
|
(postId) => `[MetadataDiffEngine] Failed to sync post ${postId} to file:`
|
||||||
const postId = postIds[i];
|
);
|
||||||
try {
|
|
||||||
const synced = await postEngine.syncPublishedPostFile(postId);
|
|
||||||
if (synced) {
|
|
||||||
success++;
|
|
||||||
} else {
|
|
||||||
failed++;
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error(`[MetadataDiffEngine] Failed to sync post ${postId} to file:`, error);
|
|
||||||
failed++;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Report progress every 10 posts or on last post
|
|
||||||
if (onProgress && (i % 10 === 0 || i === postIds.length - 1)) {
|
|
||||||
const percent = Math.round(((i + 1) / total) * 100);
|
|
||||||
onProgress(percent, `Synced ${i + 1} of ${total} posts...`);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Yield to event loop every 20 posts
|
|
||||||
if (i % 20 === 0) {
|
|
||||||
await new Promise(resolve => setImmediate(resolve));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return { success, failed };
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -368,13 +382,10 @@ export class MetadataDiffEngine extends EventEmitter {
|
|||||||
onProgress?: (percent: number, message: string) => void
|
onProgress?: (percent: number, message: string) => void
|
||||||
): Promise<{ success: number; failed: number }> {
|
): Promise<{ success: number; failed: number }> {
|
||||||
const db = this.getDb();
|
const db = this.getDb();
|
||||||
const total = postIds.length;
|
return this.runSyncLoop(
|
||||||
let success = 0;
|
postIds,
|
||||||
let failed = 0;
|
onProgress,
|
||||||
|
async (postId) => {
|
||||||
for (let i = 0; i < postIds.length; i++) {
|
|
||||||
const postId = postIds[i];
|
|
||||||
try {
|
|
||||||
// Get the post from DB to get file path
|
// Get the post from DB to get file path
|
||||||
const dbPost = await db
|
const dbPost = await db
|
||||||
.select()
|
.select()
|
||||||
@@ -383,15 +394,13 @@ export class MetadataDiffEngine extends EventEmitter {
|
|||||||
.get();
|
.get();
|
||||||
|
|
||||||
if (!dbPost || !dbPost.filePath) {
|
if (!dbPost || !dbPost.filePath) {
|
||||||
failed++;
|
return false;
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Read file metadata
|
// Read file metadata
|
||||||
const fileData = await readPostFile(dbPost.filePath);
|
const fileData = await readPostFile(dbPost.filePath);
|
||||||
if (!fileData) {
|
if (!fileData) {
|
||||||
failed++;
|
return false;
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build update object based on field or all fields
|
// Build update object based on field or all fields
|
||||||
@@ -421,25 +430,10 @@ export class MetadataDiffEngine extends EventEmitter {
|
|||||||
.set(updateData)
|
.set(updateData)
|
||||||
.where(eq(posts.id, postId));
|
.where(eq(posts.id, postId));
|
||||||
|
|
||||||
success++;
|
return true;
|
||||||
} catch (error) {
|
},
|
||||||
console.error(`[MetadataDiffEngine] Failed to sync post ${postId} to DB:`, error);
|
(postId) => `[MetadataDiffEngine] Failed to sync post ${postId} to DB:`
|
||||||
failed++;
|
);
|
||||||
}
|
|
||||||
|
|
||||||
// Report progress every 10 posts or on last post
|
|
||||||
if (onProgress && (i % 10 === 0 || i === postIds.length - 1)) {
|
|
||||||
const percent = Math.round(((i + 1) / total) * 100);
|
|
||||||
onProgress(percent, `Synced ${i + 1} of ${total} posts...`);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Yield to event loop every 20 posts
|
|
||||||
if (i % 20 === 0) {
|
|
||||||
await new Promise(resolve => setImmediate(resolve));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return { success, failed };
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -491,6 +491,29 @@ Content`);
|
|||||||
expect(mockSyncPublishedPostFile).toHaveBeenCalledWith('post-1');
|
expect(mockSyncPublishedPostFile).toHaveBeenCalledWith('post-1');
|
||||||
expect(mockSyncPublishedPostFile).toHaveBeenCalledWith('post-2');
|
expect(mockSyncPublishedPostFile).toHaveBeenCalledWith('post-2');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should report progress on first and final items based on cadence', async () => {
|
||||||
|
const postIds = Array.from({ length: 11 }, (_, i) => `post-${i + 1}`);
|
||||||
|
const onProgress = vi.fn();
|
||||||
|
|
||||||
|
await engine.syncDbToFile(postIds, onProgress);
|
||||||
|
|
||||||
|
expect(onProgress).toHaveBeenCalledTimes(2);
|
||||||
|
expect(onProgress).toHaveBeenNthCalledWith(1, 9, 'Synced 1 of 11 posts...');
|
||||||
|
expect(onProgress).toHaveBeenNthCalledWith(2, 100, 'Synced 11 of 11 posts...');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should keep processing and count failures when sync throws or returns false', async () => {
|
||||||
|
mockSyncPublishedPostFile
|
||||||
|
.mockResolvedValueOnce(true)
|
||||||
|
.mockResolvedValueOnce(false)
|
||||||
|
.mockRejectedValueOnce(new Error('sync failure'));
|
||||||
|
|
||||||
|
const result = await engine.syncDbToFile(['post-1', 'post-2', 'post-3']);
|
||||||
|
|
||||||
|
expect(result).toEqual({ success: 1, failed: 2 });
|
||||||
|
expect(mockSyncPublishedPostFile).toHaveBeenCalledTimes(3);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('syncFileToDb', () => {
|
describe('syncFileToDb', () => {
|
||||||
@@ -529,5 +552,83 @@ Content here`);
|
|||||||
// Verify the database update was called
|
// Verify the database update was called
|
||||||
expect(mockLocalDb.update).toHaveBeenCalled();
|
expect(mockLocalDb.update).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should report progress on first and final items based on cadence', async () => {
|
||||||
|
const postIds = Array.from({ length: 11 }, (_, i) => `post-${i + 1}`);
|
||||||
|
|
||||||
|
mockPostsGetQueue = postIds.map((postId) => ({
|
||||||
|
id: postId,
|
||||||
|
projectId: 'test-project',
|
||||||
|
title: `Post ${postId}`,
|
||||||
|
slug: postId,
|
||||||
|
status: 'published',
|
||||||
|
filePath: `/mock/userData/posts/2024/01/${postId}.md`,
|
||||||
|
tags: '[]',
|
||||||
|
categories: '[]',
|
||||||
|
createdAt: new Date('2024-01-15'),
|
||||||
|
updatedAt: new Date('2024-01-15'),
|
||||||
|
}));
|
||||||
|
|
||||||
|
for (const postId of postIds) {
|
||||||
|
mockFileData.set(`/mock/userData/posts/2024/01/${postId}.md`, `---\nid: ${postId}\nprojectId: test-project\ntitle: "${postId}"\nslug: ${postId}\nstatus: published\ntags: []\ncategories: []\n---\nContent`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const onProgress = vi.fn();
|
||||||
|
await engine.syncFileToDb(postIds, undefined, onProgress);
|
||||||
|
|
||||||
|
expect(onProgress).toHaveBeenCalledTimes(2);
|
||||||
|
expect(onProgress).toHaveBeenNthCalledWith(1, 9, 'Synced 1 of 11 posts...');
|
||||||
|
expect(onProgress).toHaveBeenNthCalledWith(2, 100, 'Synced 11 of 11 posts...');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should continue after missing file path and file read failures', async () => {
|
||||||
|
const postIds = ['post-1', 'post-2', 'post-3'];
|
||||||
|
|
||||||
|
mockPostsGetQueue = [
|
||||||
|
{
|
||||||
|
id: 'post-1',
|
||||||
|
projectId: 'test-project',
|
||||||
|
title: 'Post 1',
|
||||||
|
slug: 'post-1',
|
||||||
|
status: 'published',
|
||||||
|
filePath: null,
|
||||||
|
tags: '[]',
|
||||||
|
categories: '[]',
|
||||||
|
createdAt: new Date('2024-01-15'),
|
||||||
|
updatedAt: new Date('2024-01-15'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'post-2',
|
||||||
|
projectId: 'test-project',
|
||||||
|
title: 'Post 2',
|
||||||
|
slug: 'post-2',
|
||||||
|
status: 'published',
|
||||||
|
filePath: '/mock/userData/posts/2024/01/post-2.md',
|
||||||
|
tags: '[]',
|
||||||
|
categories: '[]',
|
||||||
|
createdAt: new Date('2024-01-15'),
|
||||||
|
updatedAt: new Date('2024-01-15'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'post-3',
|
||||||
|
projectId: 'test-project',
|
||||||
|
title: 'Post 3',
|
||||||
|
slug: 'post-3',
|
||||||
|
status: 'published',
|
||||||
|
filePath: '/mock/userData/posts/2024/01/post-3.md',
|
||||||
|
tags: '[]',
|
||||||
|
categories: '[]',
|
||||||
|
createdAt: new Date('2024-01-15'),
|
||||||
|
updatedAt: new Date('2024-01-15'),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
mockFileData.set('/mock/userData/posts/2024/01/post-3.md', `---\nid: post-3\nprojectId: test-project\ntitle: "Post 3"\nslug: post-3\nstatus: published\ntags: ["from-file"]\ncategories: []\n---\nContent`);
|
||||||
|
|
||||||
|
const result = await engine.syncFileToDb(postIds, 'tags');
|
||||||
|
|
||||||
|
expect(result).toEqual({ success: 1, failed: 2 });
|
||||||
|
expect(mockLocalDb.update).toHaveBeenCalledTimes(1);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user