From c0eddb953f52f929358cd5ab04fd8c25dd979cf2 Mon Sep 17 00:00:00 2001 From: hugo Date: Sun, 22 Feb 2026 08:48:55 +0100 Subject: [PATCH] fix: more speedup hopefully --- src/main/engine/BlogGenerationEngine.ts | 33 +++++++++++++++++++++-- src/main/engine/PreviewServer.ts | 8 ++++++ tests/engine/BlogGenerationEngine.test.ts | 27 +++++++++++++++++++ 3 files changed, 66 insertions(+), 2 deletions(-) diff --git a/src/main/engine/BlogGenerationEngine.ts b/src/main/engine/BlogGenerationEngine.ts index 73b3b39..9082e09 100644 --- a/src/main/engine/BlogGenerationEngine.ts +++ b/src/main/engine/BlogGenerationEngine.ts @@ -679,7 +679,7 @@ export class BlogGenerationEngine { reportUnitProgress('Assets copied'); } - const renderRoute = this.createSharedRouteRenderer(options, maxPostsPerPage); + const renderRoute = this.createSharedRouteRenderer(options, maxPostsPerPage, publishedPosts); let pagesGenerated = 0; @@ -1285,7 +1285,7 @@ export class BlogGenerationEngine { const htmlDir = path.join(options.dataDir, 'html'); await fs.mkdir(htmlDir, { recursive: true }); - const renderRoute = this.createSharedRouteRenderer(options, maxPostsPerPage); + const renderRoute = this.createSharedRouteRenderer(options, maxPostsPerPage, publishedPosts); const onPageGenerated = (_message: string) => { // no-op for applyValidation }; @@ -1440,6 +1440,7 @@ export class BlogGenerationEngine { private createSharedRouteRenderer( options: BlogGenerationOptions, maxPostsPerPage: number, + publishedPostsForLookup: PostData[] = [], ): (pathname: string) => Promise { const metadata: ProjectMetadata = { name: options.projectName, @@ -1464,6 +1465,16 @@ export class BlogGenerationEngine { const postsByFilterPromiseCache = new Map>(); const publishedSnapshotByIdPromiseCache = new Map>(); type PostFilterInput = Parameters[0]; + const publishedBySlugIndex = new Map(); + + for (const post of publishedPostsForLookup) { + const existing = publishedBySlugIndex.get(post.slug); + if (existing) { + existing.push(post); + } else { + publishedBySlugIndex.set(post.slug, [post]); + } + } const serializeFilter = (filter: PostFilterInput): string => { const normalizeValue = (value: unknown): unknown => { @@ -1506,6 +1517,24 @@ export class BlogGenerationEngine { publishedSnapshotByIdPromiseCache.set(postId, promise); return promise; }, + findPublishedBySlug: async (slug: string, dateFilter?: { year: number; month: number }) => { + const candidates = publishedBySlugIndex.get(slug); + if (!candidates || candidates.length === 0) { + return null; + } + + if (!dateFilter) { + return candidates[0] ?? null; + } + + const match = candidates.find((candidate) => { + const createdAt = candidate.createdAt; + return createdAt.getFullYear() === dateFilter.year + && createdAt.getMonth() === dateFilter.month; + }); + + return match ?? null; + }, getPost: (postId: string) => this.postEngine.getPost(postId), hasPublishedVersion: (postId: string) => this.postEngine.hasPublishedVersion(postId), setProjectContext: (projectId: string, dataDir?: string) => { diff --git a/src/main/engine/PreviewServer.ts b/src/main/engine/PreviewServer.ts index 39ad72a..e59c727 100644 --- a/src/main/engine/PreviewServer.ts +++ b/src/main/engine/PreviewServer.ts @@ -36,6 +36,7 @@ interface PostEngineContract { getPost: (id: string) => Promise; hasPublishedVersion: (id: string) => Promise; getPublishedVersion: (id: string) => Promise; + findPublishedBySlug?: (slug: string, dateFilter?: { year: number; month: number }) => Promise; setProjectContext: (projectId: string, dataDir?: string) => void; } @@ -522,6 +523,13 @@ export class PreviewServer { private async findPublishedPostBySlug(slug: string, dateFilter?: { year: number; month: number }): Promise { if (!slug) return null; + if (this.postEngine.findPublishedBySlug) { + const directMatch = await this.postEngine.findPublishedBySlug(slug, dateFilter); + if (directMatch) { + return directMatch; + } + } + const filter: PostFilter = { ...(dateFilter ? { year: dateFilter.year, month: dateFilter.month } : {}), }; diff --git a/tests/engine/BlogGenerationEngine.test.ts b/tests/engine/BlogGenerationEngine.test.ts index d913cea..a9a84c7 100644 --- a/tests/engine/BlogGenerationEngine.test.ts +++ b/tests/engine/BlogGenerationEngine.test.ts @@ -613,6 +613,33 @@ describe('BlogGenerationEngine', () => { expect(mockMediaEngine.getAllMedia).toHaveBeenCalledTimes(1); }); + it('avoids per-route snapshot queries for single-post generation', async () => { + const posts: PostData[] = []; + for (let i = 0; i < 6; i += 1) { + posts.push(makePost({ + id: `single-${i}`, + slug: `single-${i}`, + createdAt: new Date(`2025-${String(i + 1).padStart(2, '0')}-15T10:00:00Z`), + })); + } + + setupPosts(posts); + + const { BlogGenerationEngine } = await import('../../src/main/engine/BlogGenerationEngine'); + const engine = new BlogGenerationEngine(); + + await engine.generate({ + projectId: 'test', + projectName: 'Test Blog', + dataDir: tempDir, + baseUrl: 'https://example.com', + sections: ['single'], + }, vi.fn()); + + const filteredCallCount = mockPostEngine.getPostsFiltered.mock.calls.length; + expect(filteredCallCount).toBeLessThanOrEqual(8); + }); + it('validates sitemap against html folder without rendering missing pages', async () => { const posts = [ makePost({