Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
114 changes: 110 additions & 4 deletions doc/guides/persistence.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,18 @@ icon: database
order: 98
---

Ndk comes with several database offerings. The simplest is the `MemCacheManager` which is an in-memory cache. This is useful for testing and small applications.
NDK keeps two local stores side by side:

- the **event cache** (`CacheManager`) — Nostr events, contact lists, metadata, NIP-05 records, relay sets, fetched ranges, etc.
- the **blob cache** (`BlobCacheManager`) — binary payloads from Blossom servers, content-addressed by SHA-256.

Both are pluggable: pick the backend that fits your platform, or supply your own.

> **Tip:** keep these databases dedicated to NDK and spin up a secondary database for your own app data.

## Event cache

The simplest backend is `MemCacheManager`, an in-memory cache useful for testing and small apps.

Available databases:

Expand All @@ -12,9 +23,7 @@ Available databases:
- [`SembastCacheManager`](https://pub.dev/packages/sembast_cache_manager)
- [`DriftCacheManager`](https://pub.dev/packages/ndk_drift)

If you want your own database, you need to implement the `CacheManager` interface. Contributions for more database implementations are welcome!

Its recommended to use the database only for ndk and spin up a secondary db for your own app data.
If you want your own database, implement the `CacheManager` interface. Contributions for more backends are welcome.

```dart objectbox example
import 'package:ndk/ndk.dart';
Expand Down Expand Up @@ -103,3 +112,100 @@ class NostrNoteModel extends NostrNote {
...
}
```

## Blob cache

Conceptually a *local Blossom server* without the network layer: the API mirrors a remote Blossom server's surface (`saveBlob`, `getBlob`, `hasBlob`, `listBlobs`, `removeBlob`) and reuses the same entities (`BlobDescriptor`, `BlobResponse`).

If you don't configure `blobCache`, NDK falls back to an in-memory `IdbBlobCacheManager` (one per `Ndk`, lost on process exit). Pass your own factory for persistence.

### Native: persistent cache

Use `idb_io` (or [`idb_sqflite`](https://pub.dev/packages/idb_sqflite) for cross-process safety):

```dart
import 'package:idb_shim/idb_io.dart';
import 'package:ndk/ndk.dart';

final ndk = Ndk(
NdkConfig(
eventVerifier: Bip340EventVerifier(),
cache: MemCacheManager(),
blobCache: IdbBlobCacheManager(
factory: getIdbFactoryIo()!,
dbName: 'my_app_blob_cache',
),
),
);
```

### Web: persistent cache

```dart
import 'package:idb_shim/idb_browser.dart';
import 'package:ndk/ndk.dart';

final ndk = Ndk(
NdkConfig(
eventVerifier: Bip340EventVerifier(),
cache: MemCacheManager(),
blobCache: IdbBlobCacheManager(
factory: getIdbFactory()!,
dbName: 'my_app_blob_cache',
),
),
);
```

### Opting out

```dart
final ndk = Ndk(
NdkConfig(
eventVerifier: Bip340EventVerifier(),
cache: MemCacheManager(),
blobCache: const NoopBlobCacheManager(),
),
);
```

### How Blossom uses the cache

Once configured, the cache is consulted automatically by [Blossom](/usecases/blossom.md):

| Operation | Cache behaviour |
|---|---|
| `getBlob(sha256)` | check cache → on miss, fetch from server → save to cache |
| `downloadBlobToFile(...)` | check cache → on hit write bytes to file ; on miss stream from server (no auto-cache, would defeat streaming) |
| `uploadBlob(data)` | save to cache **before** the upload (local-first — the cache reflects what the user has, regardless of server outcome) |
| `uploadBlobFromFile(...)` | no auto-cache (streaming) |
| `deleteBlob(sha256)` | invalidates the cached entry |

`getBlob` and `uploadBlob` accept `cacheWrite: false` to skip the save step for one-off operations:

```dart
await ndk.blossom.getBlob(
sha256: hash,
serverUrls: [...],
cacheWrite: false,
);
```

### Direct cache access

The cache is exposed via `ndk.config.blobCache`:

```dart
final cache = ndk.config.blobCache!;

await cache.saveBlob(data: bytes, mimeType: 'image/png');
final all = await cache.listBlobs();
final size = await cache.getTotalSize();
await cache.removeBlob(sha);
```

### Implementing your own backend

Implement `BlobCacheManager` directly and pass it as `blobCache`.

> **Heads up:** the cache has no eviction or quota — it grows indefinitely. Apps caching large media should plan their own cleanup.
24 changes: 16 additions & 8 deletions doc/usecases/blossom.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,43 +24,51 @@ The auth events get automatically signed and are valid for:

upload a blob, if serverMediaOptimisation is set to `true` the `/media` endpoint is used.

:::code source="../../packages/ndk/lib/domain_layer/usecases/files/blossom.dart" language="dart" range="46-58" title="" :::
:::code source="../../packages/ndk/lib/domain_layer/usecases/files/blossom.dart" language="dart" range="87-95" title="" :::

#### getBlob

Download the blob and use fallback if the blob is not found or the server is offline.

:::code source="../../packages/ndk/lib/domain_layer/usecases/files/blossom.dart" language="dart" range="97-105" title="" :::
:::code source="../../packages/ndk/lib/domain_layer/usecases/files/blossom.dart" language="dart" range="289-296" title="" :::

#### checkBlob

!!!
if you have a video player that uses a url you can use check to get a valid url first. Example can be found in NDK demo app
!!!

:::code source="../../packages/ndk/lib/domain_layer/usecases/files/blossom.dart" language="dart" range="148-159" title="" :::
:::code source="../../packages/ndk/lib/domain_layer/usecases/files/blossom.dart" language="dart" range="430-436" title="" :::

#### getBlobStream

Similar to `getBlob`, it streams the data, which is helpful for video files.

:::code source="../../packages/ndk/lib/domain_layer/usecases/files/blossom.dart" language="dart" range="202-211" title="" :::
:::code source="../../packages/ndk/lib/domain_layer/usecases/files/blossom.dart" language="dart" range="483-490" title="" :::

#### listBlobs

:::code source="../../packages/ndk/lib/domain_layer/usecases/files/blossom.dart" language="dart" range="254-264" title="" :::
:::code source="../../packages/ndk/lib/domain_layer/usecases/files/blossom.dart" language="dart" range="538-545" title="" :::

#### deleteBlob

:::code source="../../packages/ndk/lib/domain_layer/usecases/files/blossom.dart" language="dart" range="301-308" title="" :::
:::code source="../../packages/ndk/lib/domain_layer/usecases/files/blossom.dart" language="dart" range="591-595" title="" :::

#### directDownload

:::code source="../../packages/ndk/lib/domain_layer/usecases/files/blossom.dart" language="dart" range="341-344" title="" :::
:::code source="../../packages/ndk/lib/domain_layer/usecases/files/blossom.dart" language="dart" range="631-635" title="" :::

#### report

:::code source="../../packages/ndk/lib/domain_layer/usecases/files/blossom.dart" language="dart" range="348-362" title="" :::
:::code source="../../packages/ndk/lib/domain_layer/usecases/files/blossom.dart" language="dart" range="654-661" title="" :::

### Local cache

`getBlob`, `downloadBlobToFile`, `uploadBlob` and `deleteBlob` integrate transparently with the local `BlobCacheManager`. By default everything you fetch or upload is kept in a per-`Ndk` in-memory store; configure persistence (or opt out entirely) via `NdkConfig.blobCache`.

Both `getBlob` and `uploadBlob` accept `cacheWrite: false` for one-off operations that should not pollute the local store.

[!ref](/guides/persistence.md)

### methods - BlossomUserServerList

Expand Down
Loading
Loading