UNPKG

@nostr-dev-kit/ndk

Version:

NDK - Nostr Development Kit. Includes AI Guardrails to catch common mistakes during development.

472 lines (409 loc) 14.4 kB
import type NDK from "src/index.js"; import type { NDKEvent, NDKEventId } from "../events/index.js"; import type { NDKRelay } from "../relay/index.js"; import type { NDKRelayInformation } from "../relay/nip11.js"; import type { NDKFilter, NDKSubscription } from "../subscription/index.js"; import type { NDKNutzapState } from "../types.js"; import type { Hexpubkey, ProfilePointer } from "../user/index.js"; import type { NDKUserProfile } from "../user/profile.js"; import type { NDKLnUrlData } from "../zapper/ln.js"; export type NDKCacheEntry<T> = T & { cachedAt?: number; }; /** * Cache module definition for packages to extend the cache with their own data structures. * Packages define their schemas, indexes, and migrations. */ export interface CacheModuleDefinition { /** * Unique namespace for this module (e.g., "messages", "wallet", "sync") */ namespace: string; /** * Current version of this module's schema */ version: number; /** * Collection definitions for this module */ collections: { [name: string]: { /** * Primary key field name */ primaryKey: string; /** * Fields to create indexes on for efficient querying */ indexes?: string[]; /** * Optional schema definition (for validation or TypeScript generation) */ schema?: Record<string, any>; /** * Compound indexes for multi-field queries */ compoundIndexes?: Array<string[]>; }; }; /** * Migration functions keyed by version number * Version 1 is the initial setup, 2+ are upgrades */ migrations: { [version: number]: (adapter: CacheModuleMigrationContext) => Promise<void>; }; } /** * Migration context passed to module migration functions */ export interface CacheModuleMigrationContext { /** * Get a collection by name within the module's namespace */ getCollection(name: string): Promise<CacheModuleCollection<any>>; /** * Create a new collection */ createCollection(name: string, definition: CacheModuleDefinition["collections"][string]): Promise<void>; /** * Delete a collection */ deleteCollection(name: string): Promise<void>; /** * Add an index to a collection */ addIndex(collection: string, field: string | string[]): Promise<void>; /** * Current version being migrated from */ fromVersion: number; /** * Target version being migrated to */ toVersion: number; } /** * Collection interface for module data access */ export interface CacheModuleCollection<T> { /** * Get an item by its primary key */ get(id: string): Promise<T | null>; /** * Get multiple items by their primary keys */ getMany(ids: string[]): Promise<T[]>; /** * Save an item (upsert) */ save(item: T): Promise<void>; /** * Save multiple items (bulk upsert) */ saveMany(items: T[]): Promise<void>; /** * Delete an item by its primary key */ delete(id: string): Promise<void>; /** * Delete multiple items by their primary keys */ deleteMany(ids: string[]): Promise<void>; /** * Query items by a single field */ findBy(field: string, value: any): Promise<T[]>; /** * Query items with multiple conditions */ where(conditions: Record<string, any>): Promise<T[]>; /** * Get all items in the collection */ all(): Promise<T[]>; /** * Count items matching conditions */ count(conditions?: Record<string, any>): Promise<number>; /** * Clear all items from the collection */ clear(): Promise<void>; } /** * Storage interface for cache modules that bridges to NDKCacheAdapter */ export interface CacheModuleStorage { /** * Register a cache module with the adapter */ registerModule(module: CacheModuleDefinition): Promise<void>; /** * Get a collection from a module */ getCollection<T>(namespace: string, collection: string): Promise<CacheModuleCollection<T>>; /** * Check if a module is registered */ hasModule(namespace: string): boolean; /** * Get the current version of a module */ getModuleVersion(namespace: string): Promise<number>; } export interface NDKCacheAdapter { /** * Whether this cache adapter is expected to be fast. * If this is true, the cache will be queried before the relays. * When this is false, the cache will be queried in addition to the relays. */ locking: boolean; /** * Weather the cache is ready. */ ready?: boolean; initializeAsync?(ndk: NDK): Promise<void>; initialize?(ndk: NDK): void; /** * Either synchronously or asynchronously queries the cache. * * Cache adapters that return values synchronously should return an array of events. * Asynchronous cache adapters should call the subscription.eventReceived method for each event. */ query(subscription: NDKSubscription): NDKEvent[] | Promise<NDKEvent[]>; setEvent(event: NDKEvent, filters: NDKFilter[], relay?: NDKRelay): Promise<void>; /** * Called when we receive a duplicate event from a relay. * This associates the relay with an existing cached event without re-processing the event data. * @param event - The duplicate event received. * @param relay - The relay that sent the duplicate event. */ setEventDup?(event: NDKEvent, relay: NDKRelay): Promise<void> | void; /** * Called when an event is deleted by the client. * Cache adapters should remove the event from their cache. * @param eventIds - The ids of the events that were deleted. */ deleteEventIds?(eventIds: NDKEventId[]): Promise<void>; /** * Fetches a profile from the cache synchronously. * @param pubkey - The pubkey of the profile to fetch. * @returns The profile, or null if it is not in the cache. */ fetchProfileSync?(pubkey: Hexpubkey): NDKCacheEntry<NDKUserProfile> | null; /** * Retrieve all profiles from the cache synchronously. * @returns A map of pubkeys to profiles. */ getAllProfilesSync?(): Map<Hexpubkey, NDKCacheEntry<NDKUserProfile>>; /** * Special purpose */ fetchProfile?(pubkey: Hexpubkey): Promise<NDKCacheEntry<NDKUserProfile> | null>; saveProfile?(pubkey: Hexpubkey, profile: NDKUserProfile): void | Promise<void>; /** * Fetches profiles that match the given filter. * @param filter - Either a filter function or a filter descriptor object * @returns NDKUserProfiles that match the filter. * @example * // Using a filter function * const searchFunc = (pubkey, profile) => profile.name.toLowerCase().includes("alice"); * const allAliceProfiles = await cache.getProfiles(searchFunc); * * @example * // Using a filter descriptor (supported by some cache adapters like cache-sqlite-wasm) * const aliceProfiles = await cache.getProfiles({ field: 'name', contains: 'alice' }); * // Or search multiple fields * const aliceProfiles = await cache.getProfiles({ fields: ['name', 'displayName'], contains: 'alice' }); */ getProfiles?: ( filter: ((pubkey: Hexpubkey, profile: NDKUserProfile) => boolean) | { field?: string; fields?: string[]; contains: string }, ) => Promise<Map<Hexpubkey, NDKUserProfile> | undefined>; loadNip05?(nip05: string, maxAgeForMissing?: number): Promise<ProfilePointer | null | "missing">; saveNip05?(nip05: string, profile: ProfilePointer | null): void; /** * Fetches a user's LNURL data from the cache. * @param pubkey The pubkey of the user to fetch the LNURL data for. * @param maxAgeInSecs The maximum age of the data in seconds. * @param maxAgeForMissing The maximum age of the data in seconds if it is missing before it returns that it should be refetched. * @returns The LNURL data, null if it is not in the cache and under the maxAgeForMissing, or "missing" if it should be refetched. */ loadUsersLNURLDoc?( pubkey: Hexpubkey, maxAgeInSecs?: number, maxAgeForMissing?: number, ): Promise<NDKLnUrlData | null | "missing">; saveUsersLNURLDoc?(pubkey: Hexpubkey, doc: NDKLnUrlData | null): void; /** * Updates information about the relay. */ updateRelayStatus?(relayUrl: WebSocket["url"], info: NDKCacheRelayInfo): void | Promise<void>; /** * Fetches information about the relay. */ getRelayStatus?(relayUrl: WebSocket["url"]): NDKCacheRelayInfo | undefined | Promise<NDKCacheRelayInfo | undefined>; /** * Tracks a publishing event. * @param event * @param relayUrls List of relays that the event will be published to. */ addUnpublishedEvent?(event: NDKEvent, relayUrls: WebSocket["url"][]): void | Promise<void>; /** * Fetches all unpublished events. */ getUnpublishedEvents?(): Promise<{ event: NDKEvent; relays?: WebSocket["url"][]; lastTryAt?: number }[]>; /** * Removes an unpublished event. */ discardUnpublishedEvent?(eventId: NDKEventId): void | Promise<void>; /** * Called when the cache is ready. */ onReady?(callback: () => void): void; /** * Get a decrypted event from the cache by its wrapper ID. * @param wrapperId - The ID of the gift-wrapped event (kind 1059). * @returns The decrypted rumor event, or null if it doesn't exist. */ getDecryptedEvent?(wrapperId: NDKEventId): NDKEvent | null | Promise<NDKEvent | null>; /** * Store a decrypted event in the cache. * @param wrapperId - The ID of the gift-wrapped event (kind 1059) to use as the cache key. * @param decryptedEvent - The decrypted rumor event to store. */ addDecryptedEvent?(wrapperId: NDKEventId, decryptedEvent: NDKEvent): void | Promise<void>; /** * Cleans up the cache. This is called when the user logs out. */ clear?(): Promise<void>; /** * Gets all nutzap states from the cache. * @returns A map of event IDs to nutzap states. */ getAllNutzapStates?(): Promise<Map<NDKEventId, NDKNutzapState>>; /** * Sets the state of a nutzap in the cache. * @param id The ID of the nutzap event. * @param stateChange The partial state change to apply. */ setNutzapState?(id: NDKEventId, stateChange: Partial<NDKNutzapState>): Promise<void>; /** * Generic key-value cache storage for packages. * Packages should namespace their keys (e.g., "wallet:mint:info:https://mint.url"). * @param namespace The namespace for the data (e.g., "wallet", "sync") * @param key The key within the namespace * @param maxAgeInSecs Maximum age of cached data in seconds (optional) * @returns The cached data, or undefined if not cached or expired */ getCacheData?<T>(namespace: string, key: string, maxAgeInSecs?: number): Promise<T | undefined>; /** * Generic key-value cache storage for packages. * @param namespace The namespace for the data (e.g., "wallet", "sync") * @param key The key within the namespace * @param data The data to cache */ setCacheData?<T>(namespace: string, key: string, data: T): Promise<void>; /** * Cache module support - Register a module with its schema and migrations * @param module Module definition with schema, indexes, and migrations * @returns Promise that resolves when the module is registered and migrations are complete */ registerModule?(module: CacheModuleDefinition): Promise<void>; /** * Cache module support - Get a collection from a registered module * @param namespace Module namespace * @param collection Collection name within the module * @returns Collection interface for data operations */ getModuleCollection?<T>(namespace: string, collection: string): Promise<CacheModuleCollection<T>>; } /** * Relay metadata and statistics stored in cache. * * This type supports both core connection tracking and extensible package-specific metadata. * Packages can store custom data using the metadata field with namespacing. * * @example * ```typescript * // Core connection tracking * await cache.updateRelayStatus(url, { * lastConnectedAt: Date.now(), * consecutiveFailures: 0 * }); * * // Package-specific metadata (sync package) * await cache.updateRelayStatus(url, { * metadata: { * sync: { * supportsNegentropy: false, * lastChecked: Date.now() * } * } * }); * * // Package-specific metadata (auth/rate limiting) * await cache.updateRelayStatus(url, { * metadata: { * auth: { * token: 'AUTH_TOKEN_HERE', * expiresAt: Date.now() + 3600000 * }, * rateLimit: { * requestCount: 42, * windowStart: Date.now(), * maxPerWindow: 100 * } * } * }); * * // NIP-11 caching * await cache.updateRelayStatus(url, { * nip11: { * data: relayInfo, * fetchedAt: Date.now() * } * }); * ``` */ export type NDKCacheRelayInfo = { /** * Timestamp of last successful connection */ lastConnectedAt?: number; /** * Don't attempt connection before this timestamp */ dontConnectBefore?: number; /** * Number of consecutive connection failures */ consecutiveFailures?: number; /** * Timestamp of last connection failure */ lastFailureAt?: number; /** * Cached NIP-11 relay information document */ nip11?: { data: NDKRelayInformation; fetchedAt: number; }; /** * Package-specific metadata (namespaced). * * Packages should use their package name as the namespace key. * * @example * ```typescript * metadata: { * sync: { supportsNegentropy: false, lastChecked: 1234567890 }, * auth: { token: 'abc123', expiresAt: 1234567890 }, * rateLimit: { requestCount: 42, windowStart: 1234567890, maxPerWindow: 100 } * } * ``` */ metadata?: Record<string, Record<string, unknown>>; };