Skip to content

refactor: skips parsing JSON cache when serving templates#2566

Merged
stalniy merged 3 commits intomainfrom
refactor/templates3
Jan 26, 2026
Merged

refactor: skips parsing JSON cache when serving templates#2566
stalniy merged 3 commits intomainfrom
refactor/templates3

Conversation

@stalniy
Copy link
Contributor

@stalniy stalniy commented Jan 26, 2026

Why

Currently combined templates cache size is 4.7Mb. JSON.parse is a sync operation and it may block event loop. Because of this liveness helthcheck fails and container is marked as unhealthy

What

  1. Removed obsolete /templates endpoint. It was replaced with /templates-list -> Improve templates API #102
  2. Restored templates-list shape. It supposed to return only basic information about templates but unexpectedly for me it serves the whole template data right now (including big readme and other information which we don't need on the list)
  3. saves templates cache in kind of nd-json format. To have possibility to selectively parse only parts of the cache file, to reduce blocking time by JSON.parse
  4. prebuilt serialized response for /templates-list. So, whenever somebody requests templates, API will just serve a string from RAM. Super fast response (50ms). At the moment, it takes 1-3 seconds on production to serve templates from cache.
Screenshot 2026-01-26 at 08 22 34

Summary by CodeRabbit

  • New Features
    • Persistent on-disk template gallery cache and cache-builder to speed listings and detail retrieval.
    • Per-template in-memory caching for faster repeated loads.
  • Refactor
    • Template list now returns streamlined summaries (id, name, logo, summary); full-templates endpoint removed.
    • Template detail responses standardized as JSON-wrapped data.
    • Internal merging now returns a flat array of categories instead of a wrapped result.
  • Tests
    • Updated tests to align with array-based gallery and cache behavior.

✏️ Tip: You can customize this high-level summary in your review settings.

@stalniy stalniy requested a review from a team as a code owner January 26, 2026 07:23
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 26, 2026

📝 Walkthrough

Walkthrough

Adds a file-backed galleries cache with build/read APIs, makes the template fetcher optional, changes gallery/category shapes to arrays, removes the full-templates endpoint, and updates controllers, routes, schemas, services, scripts, and tests to use the new cache and return shapes.

Changes

Cohort / File(s) Summary
Cache Init Script
apps/api/scripts/buildAkashTemplatesCache.ts
Replaced getTemplateGallery() call with buildTemplateGalleryCache(GetTemplatesListResponseSchema.shape.data) and added import for GetTemplatesListResponseSchema.
Template Gallery Service
apps/api/src/template/services/template-gallery/template-gallery.service.ts
Added filesystem-backed GalleriesCache (#galleriesCachePath, #galleriesCache), per-template parse cache (#parsedTemplates), made templateFetcher nullable, added buildTemplateGalleryCache(categoriesSchema) and getCachedTemplateGallery(), added buildContentRanges(), and adjusted gallery/template retrieval to use cache and ranges.
Gallery Tests
apps/api/src/template/services/template-gallery/template-gallery.service.spec.ts
Tests updated for array-based gallery shape and new public APIs (getCachedTemplateGallery, buildTemplateGalleryCache); added zod validation, cache serialization helpers, and updated expectations.
Template Processor Service
apps/api/src/template/services/template-processor/template-processor.service.ts
mergeTemplateCategories() now returns a flat MergedCategory[] (removed templatesIds bookkeeping); introduced MergedCategory type.
Processor Tests
apps/api/src/template/services/template-processor/template-processor.service.spec.ts
Updated to expect flat Category[]/MergedCategory[] results and removed assertions about templatesIds.
Template Controller
apps/api/src/template/controllers/template/template.controller.ts
Removed getTemplatesFull(); getTemplatesList() now returns JSON string {"data": categories} from cached gallery; getTemplateById() signature/return shape changed to Promise<{ data: Template }> and reads templates via cached gallery ranges.
Templates Router
apps/api/src/template/routes/templates/templates.router.ts
Removed /v1/templates route/OpenAPI registration; /v1/templates-list now sets Content-Type: application/json and returns a TypedResponse<GetTemplatesListResponse,200,"json"> via c.body(...).
Template Schemas
apps/api/src/template/http-schemas/template.schema.ts
Added TemplateSummarySchema, switched TemplateCategorySchema.templates to TemplateSummarySchema[], removed GetTemplatesFullResponseSchema/type, preserved GetTemplatesListResponseSchema and added inferred GetTemplatesListResponse type.
Config / CI
.codecov.yml
Added ignore rule for certain router files and set status.project default to false.

Sequence Diagram(s)

sequenceDiagram
    participant Script as buildAkashTemplatesCache
    participant Gallery as TemplateGalleryService
    participant Fetcher as TemplateFetcherService
    participant Processor as TemplateProcessorService
    participant FS as FileSystem
    participant Controller as TemplateController

    Note over Script,FS: Build cache flow
    Script->>Gallery: buildTemplateGalleryCache(categoriesSchema)
    Gallery->>Fetcher: getTemplatesFromRepo() (if templateFetcher available)
    Fetcher-->>Gallery: raw repo files
    Gallery->>Processor: mergeTemplateCategories(...)
    Processor-->>Gallery: merged Category[]
    Gallery->>Gallery: buildContentRanges() & assemble GalleriesCache
    Gallery->>FS: write GalleriesCache to disk
    FS-->>Gallery: write confirmation
    Gallery-->>Script: GalleriesCache

    Note over Controller,FS: Retrieval & per-id fetch
    Controller->>Gallery: getCachedTemplateGallery()
    Gallery->>FS: read GalleriesCache file
    FS-->>Gallery: GalleriesCache contents
    Gallery->>Gallery: use ranges + `#parsedTemplates` to extract & parse template
    Gallery-->>Controller: { data: Template } or categories payload
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

  • PR #2563 — Modifies TemplateGalleryService and script/controller wiring; closely related to cache build/get APIs used here.
  • PR #2397 — Changes template-gallery caching and cache-file handling; overlaps with filesystem-backed cache logic.

Suggested reviewers

  • baktun14

Poem

"I hopped through code to stash a cache,
Ranges carved and templates cached,
Fetch if given, else read the file,
Categories flattened, neat and agile,
A rabbit's cheer for every patch!" 🐇✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main objective: skipping JSON parsing for cached templates to avoid event loop blocking, which is the core performance optimization driving this refactor.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In `@apps/api/src/template/services/template-gallery/template-gallery.service.ts`:
- Around line 51-54: The cache existence check currently requires both read and
write permissions which breaks on read‑only volumes; update the access call in
the template gallery service so cacheFileExists is computed using only read
permission by calling this.#fs.access(this.#galleriesCachePath,
fsConstants.R_OK) (replace the existing fsConstants.W_OK | fsConstants.R_OK),
keeping the same promise-to-boolean pattern around this.#fs.access and leaving
this.#galleriesCachePath and cacheFileExists unchanged.
- Around line 105-106: The code calls await
this.#fs.writeFile(this.#galleriesCachePath, content) without ensuring the
target directory exists, which causes writeFile to throw on first run; before
writing, ensure the directory containing this.#galleriesCachePath exists by
creating it if missing (use your FS helper on this.#fs or Node fs.mkdir with {
recursive: true }) and then perform the write; update the method that uses
writeFile (referenced symbol: this.#galleriesCachePath and this.#fs.writeFile)
to create the parent directory for the templates/ folder first and only then
call writeFile.
🧹 Nitpick comments (2)
apps/api/src/template/services/template-gallery/template-gallery.service.spec.ts (1)

269-270: Acknowledged technical debt.

The HACK comment is self-documented. Consider creating an issue to track refactoring the test setup to avoid assigning private properties directly. This could be addressed by exposing a factory method or using dependency injection more fully.

Do you want me to open an issue to track refactoring this test setup?

apps/api/src/template/routes/templates/templates.router.ts (1)

29-34: Type cast workaround is acceptable but worth documenting.

The double cast as unknown as TypedResponse<...> bypasses type checking because c.body() returns a raw body response, not a JSON-typed response. This is intentional to avoid re-serialization overhead.

Consider adding a brief inline comment explaining why this cast is needed:

📝 Suggested documentation
 templatesRouter.openapi(getTemplatesListRoute, async function routeGetTemplatesList(c) {
   const result = await container.resolve(TemplateController).getTemplatesList();

   c.header("Content-Type", "application/json");
+  // Cast needed: result is pre-serialized JSON string, bypassing c.json() to avoid re-serialization
   return c.body(result, 200) as unknown as TypedResponse<GetTemplatesListResponse, 200, "json">;
 });

@codecov
Copy link

codecov bot commented Jan 26, 2026

Codecov Report

❌ Patch coverage is 91.46341% with 7 lines in your changes missing coverage. Please review.
✅ Project coverage is 50.73%. Comparing base (03f7d31) to head (6436ae8).
⚠️ Report is 1 commits behind head on main.
✅ All tests successful. No failed tests found.

Files with missing lines Patch % Lines
...mplate/controllers/template/template.controller.ts 0.00% 4 Missing ⚠️
.../src/template/routes/templates/templates.router.ts 33.33% 2 Missing ⚠️
...vices/template-gallery/template-gallery.service.ts 98.59% 1 Missing ⚠️

❌ Your project status has failed because the head coverage (79.71%) is below the target coverage (80.00%). You can increase the head coverage or adjust the target coverage.

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #2566      +/-   ##
==========================================
- Coverage   50.93%   50.73%   -0.21%     
==========================================
  Files        1069     1059      -10     
  Lines       29815    29499     -316     
  Branches     6618     6579      -39     
==========================================
- Hits        15187    14965     -222     
+ Misses      14383    14298      -85     
+ Partials      245      236       -9     
Flag Coverage Δ *Carryforward flag
api 79.71% <91.46%> (+0.16%) ⬆️
deploy-web 31.46% <ø> (ø) Carriedforward from 03f7d31
log-collector ?
notifications 87.94% <ø> (ø) Carriedforward from 03f7d31
provider-console 81.48% <ø> (ø) Carriedforward from 03f7d31
provider-proxy 84.35% <ø> (ø) Carriedforward from 03f7d31

*This pull request uses carry forward flags. Click here to find out more.

Files with missing lines Coverage Δ
...s/api/src/template/http-schemas/template.schema.ts 100.00% <100.00%> (ø)
...s/template-processor/template-processor.service.ts 100.00% <100.00%> (ø)
...vices/template-gallery/template-gallery.service.ts 98.26% <98.59%> (-0.23%) ⬇️
.../src/template/routes/templates/templates.router.ts 64.70% <33.33%> (-0.30%) ⬇️
...mplate/controllers/template/template.controller.ts 44.44% <0.00%> (+8.44%) ⬆️

... and 11 files with indirect coverage changes

🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@stalniy stalniy force-pushed the refactor/templates3 branch from ab8694d to cd858a2 Compare January 26, 2026 10:51
baktun14
baktun14 previously approved these changes Jan 26, 2026
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
apps/api/src/template/controllers/template/template.controller.ts (1)

15-16: Logger context references wrong class name.

The logger context is set to TemplateGalleryService.name but this is TemplateController. This will cause confusing log output.

🔧 Proposed fix
-    logger.setContext(TemplateGalleryService.name);
+    logger.setContext(TemplateController.name);
🤖 Fix all issues with AI agents
In `@apps/api/src/template/services/template-gallery/template-gallery.service.ts`:
- Around line 60-66: Wrap the JSON.parse(metadataString) call in a try-catch
inside the method that reads the galleries cache (the block using
this.#fs.readFile and this.#galleriesCachePath); if parsing fails, catch the
error and throw or return a clearer, contextual error that includes the cache
path and the raw metadataString (or a short preview) so callers can understand
the corrupted metadata, e.g., catch the parse error around
JSON.parse(metadataString) and rethrow a new Error mentioning "Failed to parse
gallery metadata" plus the path/preview and the original error message.
🧹 Nitpick comments (3)
apps/api/src/template/controllers/template/template.controller.ts (1)

23-26: Manual JSON string assembly creates tight coupling.

The {"data":${categories}} construction assumes categories is a valid JSON array string. This is intentional for the performance optimization, but creates implicit coupling with getCachedTemplateGallery() returning properly formatted JSON.

Consider adding a brief comment documenting this invariant for maintainability.

apps/api/src/template/services/template-gallery/template-gallery.service.spec.ts (1)

269-270: Acknowledged tech debt for private property assignment.

The HACK comment at line 269 documents the workaround for testing. Per learnings, consider creating a separate issue to track refactoring the dependency injection approach.

apps/api/scripts/buildAkashTemplatesCache.ts (1)

25-29: Consider adding success logging.

The script logs errors but doesn't confirm successful completion. Adding a success message would help with build pipeline observability.

🔧 Proposed enhancement
-templateGalleryService.buildTemplateGalleryCache(GetTemplatesListResponseSchema.shape.data).catch(err => {
+templateGalleryService.buildTemplateGalleryCache(GetTemplatesListResponseSchema.shape.data).then(() => {
+  console.log("Akash templates cache warmed up successfully");
+}).catch(err => {
   console.error("Encountered an error trying to warm up Akash templates cache");
   console.error(err);
   process.exit(1);
 });

@stalniy stalniy enabled auto-merge January 26, 2026 17:22
baktun14
baktun14 previously approved these changes Jan 26, 2026
@stalniy stalniy disabled auto-merge January 26, 2026 17:23
@stalniy stalniy enabled auto-merge January 26, 2026 17:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

Comments