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
111 changes: 111 additions & 0 deletions DB.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
# NIP-DB: Browser Nostr Event Database Interface

## Abstract

This NIP defines a standard interface for browser extensions that provide local Nostr event storage capabilities to web applications. The interface allows web applications to interact with Nostr events stored locally in the browser through a standardized `window.nostrdb` API.

## Motivation

Browser extensions can provide valuable local storage and caching capabilities for Nostr events, improving performance and enabling offline functionality.

This NIP establishes a common interface that browser extensions can implement to provide Nostr event storage services to web applications.

## Specification

### Interface Definition

Browser extensions implementing this NIP MUST inject a `window.nostrdb` object that implements the following interface:

```typescript
interface IWindowNostrDB {
/** Add an event to the database */
add(event: NostrEvent): Promise<boolean>;

/** Get a single event by ID */
event(id: string): Promise<NostrEvent | undefined>;
Copy link
Member

Choose a reason for hiding this comment

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

This method is not needed. Just call .query

Copy link
Collaborator

@vitorpamplona vitorpamplona Feb 21, 2026

Choose a reason for hiding this comment

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

Agree, the .query can check if the filter is only made by one ID and send it here if this is more performant.

Copy link
Member

Choose a reason for hiding this comment

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

Yes, it's important this interface is minimal for extension implementers. Client code can always wrap it with convenience methods.


/** Get the latest version of a replaceable event */
replaceable(
Copy link
Member

Choose a reason for hiding this comment

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

This method is not needed. Just call .query

Copy link
Collaborator

@vitorpamplona vitorpamplona Feb 21, 2026

Choose a reason for hiding this comment

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

Agree, the .query can check if the filter is only made by one address and send it here if this is more performant.

kind: number,
author: string,
identifier?: string,
): Promise<NostrEvent | undefined>;

/** Count the number of events matching filters */
count(filters: Filter | Filter[]): Promise<number>;

/** Check if the database backend supports features */
supports(): Promise<string[]>;

/** Get events by filters */
query(filters: Filter | Filter[]): Promise<NostrEvent[]>;

/** Subscribe to events in the database based on filters */
subscribe(filters: Filter | Filter[]): AsyncGenerator<NostrEvent>;
}
```

### Feature Detection

The `supports()` method allows web applications to check for optional features:

- `"search"` - NIP-50 full-text search capabilities

### Implementation Requirements

1. **Injection**: The interface MUST be injected into every web page via content scripts
2. **Availability**: The interface MUST be available as `window.nostrdb` after DOM content is loaded
3. **Error Handling**: All methods MUST handle errors gracefully and return appropriate error states
4. **Thread Safety**: The interface MUST be safe to use from multiple contexts

### Usage Examples

#### Basic Event Operations

```javascript
// Add an event
const success = await window.nostrdb.add(nostrEvent);

// Get a specific event
const event = await window.nostrdb.event(eventId);

// Get latest replaceable event
const profile = await window.nostrdb.replaceable(0, pubkey);

// Count events
const count = await window.nostrdb.count({ kinds: [1] });
```

#### Getting Events

```javascript
// Get events matching filters
const events = await window.nostrdb.query([{ kinds: [1] }]);
console.log("Found events:", events);
```

#### Subscribing to Events

```javascript
// Subscribe to events using an async iterator
for await (const event of window.nostrdb.subscribe([{ kinds: [1] }])) {
console.log("New event:", event);
}
```

#### Feature Detection

```javascript
// Get all supported features
const supportedFeatures = await window.nostrdb.supports();

// Check for search support
if (supportedFeatures.includes("search")) {
Comment on lines +102 to +103
Copy link
Collaborator

Choose a reason for hiding this comment

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

Why not just use objects directly?

if (supportedFeatures.search) {
}

Then the UI doesn't need to loop through the array of features and compare strings every time it needs to check.

Copy link
Member

@fiatjaf fiatjaf Feb 25, 2026

Choose a reason for hiding this comment

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

Accessing fields in a hashmap also involves string comparisons, and some hashing too.

Who knows what the JIT compiler does with these "objects", but in theory a single-item array is faster than a single-item hashmap.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Accessing fields in a hashmap also involves string comparisons

Only if there are collisions in each hashcode, otherwise, it's an int comparison. The hashcode is cached after the first usage.

single-item array is faster than a single-item hashmap

Only if you are not accounting for the string comparison.

// Use search functionality
const notes = await window.nostrdb.query({ kinds: [1], search: "nostr" });
}
```

## Reference Implementation

A reference implementation is available at: [nostr-bucket](https://github.com/hzrd149/nostr-bucket)
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@ They exist to document what may be implemented by [Nostr](https://github.com/nos
- [NIP-BE: Nostr BLE Communications Protocol](BE.md)
- [NIP-C0: Code Snippets](C0.md)
- [NIP-C7: Chats](C7.md)
- [NIP-DB: Browser Nostr Event Database Interface](DB.md)
- [NIP-EE: E2EE Messaging using MLS Protocol](EE.md) --- **unrecommended**: superseded by the [Marmot Protocol](https://github.com/marmot-protocol/marmot)

## Event Kinds
Expand Down