Skip to content

feat: blob cache manager#606

Open
nogringo wants to merge 4 commits into
masterfrom
feat/blob-cache-manager
Open

feat: blob cache manager#606
nogringo wants to merge 4 commits into
masterfrom
feat/blob-cache-manager

Conversation

@nogringo
Copy link
Copy Markdown
Collaborator

@nogringo nogringo commented May 10, 2026

Like Events Cache Manager but for blobs

Summary by CodeRabbit

Release Notes

  • New Features

    • Added local blob caching for Blossom file operations, reducing network calls for repeated blob access.
    • Introduced configurable cache managers with persistent and opt-out options.
    • Added cacheWrite parameter to blob operations for granular caching control.
  • Documentation

    • Enhanced persistence guide with blob cache configuration examples.
    • Updated Blossom usage documentation with local cache behavior details.
  • Tests

    • Added comprehensive test coverage for blob caching functionality.

Review Change Stack

@nogringo nogringo requested review from 1-leo and frnandu May 10, 2026 15:35
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 10, 2026

📝 Walkthrough

Walkthrough

This PR adds a pluggable local blob caching layer to NDK for Blossom file operations. It introduces a BlobCacheManager interface with IndexedDB and no-op implementations, integrates cache-aware methods into Blossom blob operations, and provides per-instance in-memory caching by default.

Changes

Blob Cache Feature Implementation

Layer / File(s) Summary
Data Models & Serialization
packages/ndk/lib/domain_layer/entities/blossom_blobs.dart
BlobDescriptor and BlobNip94 add toJson() methods for serialization, size parsing becomes nullable, and dimenssions field is corrected to dimensions.
Cache Manager Interface
packages/ndk/lib/domain_layer/repositories/blob_cache_manager.dart
New abstract BlobCacheManager interface defines async operations for saving, retrieving, and managing cached blobs by SHA-256 key.
IndexedDB Implementation
packages/ndk/lib/data_layer/repositories/blob_cache/idb_blob_cache_manager.dart
IdbBlobCacheManager persists blobs and metadata to IndexedDB with dual object stores, lazy database initialization, and transaction-based operations.
No-op Implementation
packages/ndk/lib/data_layer/repositories/blob_cache/noop_blob_cache_manager.dart
NoopBlobCacheManager computes blob descriptors without persisting bytes, enabling opt-out of caching.
Blossom Integration
packages/ndk/lib/domain_layer/usecases/files/blossom.dart
Blossom accepts injected BlobCacheManager and FileIO, adds cacheWrite flags to uploadBlob/getBlob, and implements cache-first reads for downloadBlobToFile and cache invalidation for deleteBlob.
Configuration & Wiring
packages/ndk/lib/presentation_layer/ndk_config.dart, packages/ndk/lib/presentation_layer/init.dart
NdkConfig accepts optional blobCache, Initialization creates per-instance in-memory IndexedDB cache by default and injects into Blossom.
Library Exports
packages/ndk/lib/ndk.dart
Exports BlobCacheManager, IdbBlobCacheManager, and NoopBlobCacheManager in public API.
Dependencies
packages/ndk/pubspec.yaml
Adds idb_shim (^2.9.1) for cross-platform IndexedDB support.
Documentation
doc/guides/persistence.md, doc/usecases/blossom.md
Comprehensive guides for configuring blob cache on native/web platforms, opting out, and understanding cache behavior for each blob operation.
Test Coverage
packages/ndk/test/data_layer/blob_cache/*, packages/ndk/test/entities/blob_nip94_test.dart, packages/ndk/test/usecases/files/blossom_blob_cache_test.dart, packages/ndk/test/usecases/files/blossom_test.dart
Tests for cache implementations, entity serialization round-tripping, Blossom integration with cache hits/misses/eviction, and opt-out verification.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Suggested labels

enhancement

Suggested reviewers

  • frnandu
  • 1-leo

Poem

🐰 Blobs now sleep in local cache so fine,
IndexedDB stores them, line by line,
Fetch them swift without the network call,
Or opt out quiet—NoopBlobCacheManager for all! ✨

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title 'feat: blob cache manager' accurately summarizes the main change—a new blob cache manager feature mirroring the Events Cache Manager for blob data, as confirmed by the PR objectives and comprehensive changes across multiple files.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/blob-cache-manager

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

Copy link
Copy Markdown

@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

🧹 Nitpick comments (1)
packages/ndk/lib/data_layer/repositories/blob_cache/idb_blob_cache_manager.dart (1)

181-200: 💤 Low value

Verify getTotalSize handles null sizes correctly.

Line 196 only sums size when it's an int, which correctly handles the new nullable size behavior from BlobDescriptor. Blobs with unparseable or missing sizes are excluded from the total.

Consideration: Document whether this is the intended behavior. Alternatively, you could:

  • Count missing sizes as 0 (restore old behavior)
  • Track missing sizes separately and log a warning
  • Fail fast if any descriptor lacks a size

The current approach (skip nulls) seems reasonable for a cache manager, but explicit documentation would help.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@packages/ndk/lib/data_layer/repositories/blob_cache/idb_blob_cache_manager.dart`
around lines 181 - 200, getTotalSize currently ignores non-int or missing `size`
fields from stored descriptors, which is ambiguous; update the getTotalSize
implementation in idb_blob_cache_manager.dart (function getTotalSize, using
_kMetadataStore and BlobDescriptor semantics) to treat missing or non-int sizes
as 0 and add a clarifying comment above the loop documenting this behavior so
totals remain deterministic for cache accounting. Ensure you still only sum
sizes when they parse to int (coerce or default to 0) and do not attempt to load
payloads.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/ndk/lib/domain_layer/repositories/blob_cache_manager.dart`:
- Around line 19-33: The saveBlob contract currently trusts the caller-provided
sha256 which can enable cache poisoning; update the saveBlob implementation to
compute the SHA-256 of the provided data and compare it to the sha256 parameter
(when given), failing (throwing/returning an error) on mismatch, or
alternatively add an explicit verification flag (e.g., verifySha256) defaulting
to true so callers can opt out for performance; ensure the code path that
updates/returns BlobDescriptor enforces this verification and documents the
behavior for callers of saveBlob and consumers of BlobDescriptor.

In `@packages/ndk/lib/domain_layer/usecases/files/blossom.dart`:
- Around line 342-347: The cache backend exceptions (e.g., from
_blobCache.saveBlob) are currently allowed to abort the primary network flows in
getBlob and deleteBlob; change both code paths so cache failures are non-fatal:
wrap calls to _blobCache.saveBlob (in getBlob) and any _blobCache.delete* calls
(in deleteBlob) in try/catch blocks, log a warning/error on failure, and do not
rethrow so the successful network download or server delete still completes;
ensure the server/network operation remains the primary awaited action and cache
cleanup is best-effort only.

---

Nitpick comments:
In
`@packages/ndk/lib/data_layer/repositories/blob_cache/idb_blob_cache_manager.dart`:
- Around line 181-200: getTotalSize currently ignores non-int or missing `size`
fields from stored descriptors, which is ambiguous; update the getTotalSize
implementation in idb_blob_cache_manager.dart (function getTotalSize, using
_kMetadataStore and BlobDescriptor semantics) to treat missing or non-int sizes
as 0 and add a clarifying comment above the loop documenting this behavior so
totals remain deterministic for cache accounting. Ensure you still only sum
sizes when they parse to int (coerce or default to 0) and do not attempt to load
payloads.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 0708f4d7-dee0-468d-8345-5d48d429b65e

📥 Commits

Reviewing files that changed from the base of the PR and between de9efbd and b3dcb76.

📒 Files selected for processing (16)
  • doc/guides/persistence.md
  • doc/usecases/blossom.md
  • packages/ndk/lib/data_layer/repositories/blob_cache/idb_blob_cache_manager.dart
  • packages/ndk/lib/data_layer/repositories/blob_cache/noop_blob_cache_manager.dart
  • packages/ndk/lib/domain_layer/entities/blossom_blobs.dart
  • packages/ndk/lib/domain_layer/repositories/blob_cache_manager.dart
  • packages/ndk/lib/domain_layer/usecases/files/blossom.dart
  • packages/ndk/lib/ndk.dart
  • packages/ndk/lib/presentation_layer/init.dart
  • packages/ndk/lib/presentation_layer/ndk_config.dart
  • packages/ndk/pubspec.yaml
  • packages/ndk/test/data_layer/blob_cache/idb_blob_cache_manager_test.dart
  • packages/ndk/test/data_layer/blob_cache/noop_blob_cache_manager_test.dart
  • packages/ndk/test/entities/blob_nip94_test.dart
  • packages/ndk/test/usecases/files/blossom_blob_cache_test.dart
  • packages/ndk/test/usecases/files/blossom_test.dart

Comment on lines +19 to +33
/// Persist a blob keyed by its SHA-256.
///
/// If [sha256] is omitted it is computed from [data]. Pass it when
/// already known (e.g. right after a Blossom GET) to avoid hashing
/// twice. The caller is trusted not to lie about a provided hash.
///
/// Re-saving an existing blob updates the descriptor metadata and
/// keeps the existing bytes.
Future<BlobDescriptor> saveBlob({
required Uint8List data,
String? sha256,
String? mimeType,
String? sourceUrl,
BlobNip94? nip94,
});
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Consider verifying provided SHA-256 hashes.

Line 23 states "The caller is trusted not to lie about a provided hash." This trust assumption could enable cache poisoning if an untrusted caller provides an incorrect SHA-256 for blob data. An attacker could:

  • Store blob A under the hash of blob B
  • Cause cache hits to return incorrect content
  • Potentially bypass content validation in downstream code

Recommendation: Consider one of:

  1. Always compute and verify the hash, ignoring the provided value (safest but less performant)
  2. Add a verification mode/flag for security-sensitive contexts
  3. Document the security implications and require callers to only pass pre-verified hashes from trusted sources (e.g., immediately after downloading from a Blossom server)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/ndk/lib/domain_layer/repositories/blob_cache_manager.dart` around
lines 19 - 33, The saveBlob contract currently trusts the caller-provided sha256
which can enable cache poisoning; update the saveBlob implementation to compute
the SHA-256 of the provided data and compare it to the sha256 parameter (when
given), failing (throwing/returning an error) on mismatch, or alternatively add
an explicit verification flag (e.g., verifySha256) defaulting to true so callers
can opt out for performance; ensure the code path that updates/returns
BlobDescriptor enforces this verification and documents the behavior for callers
of saveBlob and consumers of BlobDescriptor.

Comment on lines +342 to +347
if (cacheWrite) {
await _blobCache.saveBlob(
data: response.data,
sha256: sha256,
mimeType: response.mimeType,
);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Make cache failures non-fatal in network success paths.

Line 342 and Line 596 currently let cache exceptions abort primary operations. A cache backend error can make getBlob fail after a successful download, and can block deleteBlob from even reaching the server.

Proposed fix
@@
-    if (cacheWrite) {
-      await _blobCache.saveBlob(
-        data: response.data,
-        sha256: sha256,
-        mimeType: response.mimeType,
-      );
-    }
+    if (cacheWrite) {
+      try {
+        await _blobCache.saveBlob(
+          data: response.data,
+          sha256: sha256,
+          mimeType: response.mimeType,
+        );
+      } catch (_) {
+        // best-effort cache write; do not fail successful network read
+      }
+    }
@@
-    await _blobCache.removeBlob(sha256);
+    try {
+      await _blobCache.removeBlob(sha256);
+    } catch (_) {
+      // best-effort cache invalidation; continue with remote delete
+    }

Also applies to: 596-597

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/ndk/lib/domain_layer/usecases/files/blossom.dart` around lines 342 -
347, The cache backend exceptions (e.g., from _blobCache.saveBlob) are currently
allowed to abort the primary network flows in getBlob and deleteBlob; change
both code paths so cache failures are non-fatal: wrap calls to
_blobCache.saveBlob (in getBlob) and any _blobCache.delete* calls (in
deleteBlob) in try/catch blocks, log a warning/error on failure, and do not
rethrow so the successful network download or server delete still completes;
ensure the server/network operation remains the primary awaited action and cache
cleanup is best-effort only.

@codecov
Copy link
Copy Markdown

codecov Bot commented May 10, 2026

Codecov Report

❌ Patch coverage is 97.94521% with 3 lines in your changes missing coverage. Please review.
✅ Project coverage is 74.21%. Comparing base (de9efbd) to head (b3dcb76).
⚠️ Report is 80 commits behind head on master.

Files with missing lines Patch % Lines
...epositories/blob_cache/idb_blob_cache_manager.dart 96.59% 3 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master     #606      +/-   ##
==========================================
+ Coverage   73.87%   74.21%   +0.33%     
==========================================
  Files         205      207       +2     
  Lines       10901    11040     +139     
==========================================
+ Hits         8053     8193     +140     
+ Misses       2848     2847       -1     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@frnandu frnandu changed the title Feat/blob cache manager feat: blob cache manager May 14, 2026
@frnandu frnandu added this to the 0.9.0 milestone May 14, 2026
Copy link
Copy Markdown
Collaborator

@frnandu frnandu left a comment

Choose a reason for hiding this comment

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

Confirm that there are some kind of memory limits in the idb_shim lib , otherwise the cache will grow without bounds. Provide documentation on how to configure such limits

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