Skip to content

window.nostrdb Package

The window.nostrdb.js package is a polyfill that implements the NIP-DB specification, providing a window.nostrdb API for storing and querying Nostr events locally in web browsers.

Overview

window.nostrdb.js polyfills the window.nostrdb API, which browser extensions like nostr-bucket can provide. When no extension is present, this package uses IndexedDB as a fallback, giving applications a consistent API for local event storage.

The package integrates seamlessly with Applesauce's event store system, providing persistent local caching that improves application performance and enables offline functionality.

Complete API Documentation

For complete API documentation, see the window.nostrdb.js repository.

Key Features

  • Browser Extension or IndexedDB: Uses browser extension if available, falls back to IndexedDB
  • NIP-DB Compliant: Implements the complete window.nostrdb interface
  • Real-time Subscriptions: Subscribe to events with live updates
  • User Lookup: Search for user profiles with configurable providers (Primal, Vertex, Relatr)
  • TypeScript Support: Full TypeScript definitions included

Installation

sh
npm install window.nostrdb.js
sh
yarn install window.nostrdb.js
sh
pnpm install window.nostrdb.js

Or via CDN:

html
<script type="module" src="https://unpkg.com/window.nostrdb.js"></script>

Basic Usage

Setting Up with Applesauce

Import the package to polyfill window.nostrdb, then integrate with Applesauce's event loaders:

js
import { EventStore } from "applesauce-core";
import { persistEventsToCache } from "applesauce-core/helpers";
import { createEventLoaderForStore } from "applesauce-loaders/loaders";
import { RelayPool } from "applesauce-relay";
import "window.nostrdb.js";

// Create your event store and relay pool
const eventStore = new EventStore();
const pool = new RelayPool();

// Create a cache request function for event loaders
const cacheRequest = (filters) => window.nostrdb.filters(filters);

// Automatically persist new events to the cache
persistEventsToCache(eventStore, (events) => Promise.allSettled(events.map((event) => window.nostrdb.add(event))));

// Use the cache with event loaders
createEventLoaderForStore(eventStore, pool, {
  cacheRequest,
  lookupRelays: ["wss://purplepag.es/", "wss://index.hzrd149.com/"],
});

How It Works

  1. Polyfill Initialization: Import polyfills window.nostrdb if not already provided by a browser extension
  2. Backend Selection: Automatically uses browser extension or falls back to IndexedDB
  3. Cache Integration: The cacheRequest function uses window.nostrdb.filters() which returns a Promise<NostrEvent[]> - this queries the cache before event loaders query relays
  4. Event Persistence: persistEventsToCache() automatically saves new events to the cache as they're added to the event store

Integration Patterns

With Event Loaders

The most common use case is integrating the cache with Applesauce's event loaders:

js
import { EventStore } from "applesauce-core";
import { Filter } from "applesauce-core/helpers";
import { persistEventsToCache } from "applesauce-core/helpers";
import { createEventLoaderForStore } from "applesauce-loaders/loaders";
import { RelayPool } from "applesauce-relay";
import "window.nostrdb.js";

const eventStore = new EventStore();
const pool = new RelayPool();

// Create cache request function
const cacheRequest = (filters: Filter[]) => window.nostrdb.filters(filters);

// Persist events to cache
persistEventsToCache(eventStore, (events) =>
  Promise.allSettled(events.map((event) => window.nostrdb.add(event)))
);

// Use cache with event loader
createEventLoaderForStore(eventStore, pool, {
  cacheRequest,
  lookupRelays: ["wss://relay.example.com"],
});

With Timeline Loaders

You can also use the cache with timeline loaders:

js
import { createTimelineLoader } from "applesauce-loaders/loaders";
import "window.nostrdb.js";

const cacheRequest = (filters) => window.nostrdb.filters(filters);

// Persist events to cache
persistEventsToCache(eventStore, (events) => Promise.allSettled(events.map((event) => window.nostrdb.add(event))));

// Create timeline loader with cache
const timeline = createTimelineLoader(
  pool,
  ["wss://relay.example.com"],
  { kinds: [1] },
  { eventStore, cache: cacheRequest },
);

Configuration

You can configure the backend and lookup providers before importing:

js
// Use local relay backend (connects to nostr-bucket extension)
window.nostrdbConfig = {
  localRelays: ["ws://localhost:4869/"],
  lookupProviders: ["primal", "relatr"], // Order of providers to try
};
import "window.nostrdb.js";

If no local relay is available, the library automatically falls back to IndexedDB.

API Reference

The window.nostrdb API provides methods for working with events:

js
// Add event
await window.nostrdb.add(event);

// Get event by ID
const event = await window.nostrdb.event(eventId);

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

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

// Subscribe to events
const subscription = window.nostrdb.subscribe([{ kinds: [1] }], {
  event: (event) => console.log(event),
  error: (error) => console.error(error),
  complete: () => console.log("Done"),
});
subscription.close();

// Search for users
const users = await window.nostrdb.lookup("satoshi");

Limitations

  • Browser Only: Only works in browser environments, not in Node.js
  • Storage Limits: When using IndexedDB fallback, subject to browser storage quotas
  • Extension Dependent: Full features require nostr-bucket browser extension or IndexedDB fallback

Or via CDN:

html
<script type="module" src="https://unpkg.com/window.nostrdb.js"></script>

Setup

Import the package to polyfill window.nostrdb:

tsx
import "window.nostrdb.js";

Optionally configure the backend before importing:

tsx
// Use local relay backend (connects to nostr-bucket)
window.nostrdbConfig = {
  localRelays: ["ws://localhost:4869/"],
};
import "window.nostrdb.js";

If no local relay is available, the library automatically falls back to IndexedDB.

Usage with Applesauce

Create a cache request function for event loaders:

tsx
const cacheRequest = (filters: Filter[]) => window.nostrdb.filters(filters);

createEventLoaderForStore(eventStore, pool, {
  cacheRequest,
  lookupRelays: ["wss://purplepag.es/"],
});

Automatically persist new events:

tsx
persistEventsToCache(eventStore, (events) => Promise.allSettled(events.map((e) => window.nostrdb.add(e))));

Use with timeline loaders:

tsx
const loader = createTimelineLoader(
  pool,
  ["wss://relay.damus.io/"],
  { kinds: [1] },
  { cache: cacheRequest, eventStore, limit: 50 },
);

loader().subscribe();

Direct API Usage

The window.nostrdb API provides several methods for working with events:

tsx
// Add event
await window.nostrdb.add(event);

// Get event by ID
const event = await window.nostrdb.event(eventId);

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

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

// Subscribe to events
const sub = window.nostrdb.subscribe([{ kinds: [1] }], {
  event: (e) => console.log(e),
});
sub.close();

Integration

EventStore + window.nostrdb:

tsx
const cacheRequest = (filters: Filter[]) => window.nostrdb.filters(filters);
createEventLoaderForStore(eventStore, pool, { cacheRequest });

persistEventsToCache(eventStore, (events) => Promise.allSettled(events.map((e) => window.nostrdb.add(e))));

Check if event came from cache:

tsx
import { isFromCache } from "applesauce-core/helpers";

const fromCache = isFromCache(event);

Best Practices

Use event loaders for automatic cache fallback:

tsx
// ✅ Good - loader checks cache first, then relays
createEventLoaderForStore(eventStore, pool, { cacheRequest });
eventStore.getEvent(id);

// ❌ Bad - manual cache checking
const cached = await window.nostrdb.filters([{ ids: [id] }]);
if (!cached.length) {
  // fetch from relay...
}

Persist all events automatically:

tsx
// ✅ Good - centralized persistence
persistEventsToCache(eventStore, (events) => Promise.allSettled(events.map((e) => window.nostrdb.add(e))));

// ❌ Bad - manual persistence scattered
eventStore.add(event);
window.nostrdb.add(event); // Easy to forget