Update docs

This commit is contained in:
Jon Staab
2026-06-10 14:12:47 -07:00
parent a33af11b1b
commit dbd043f105
35 changed files with 164 additions and 179 deletions
+2 -2
View File
@@ -52,8 +52,8 @@ pin(tag: string[]): Promise<Thunk>
```typescript ```typescript
type SendWrappedOptions = Omit<ThunkOptions, "event" | "relays"> & { type SendWrappedOptions = Omit<ThunkOptions, "event" | "relays"> & {
template: EventTemplate event: EventTemplate
pubkeys: string[] recipients: string[]
} }
sendWrapped(options: SendWrappedOptions): Promise<MergedThunk> sendWrapped(options: SendWrappedOptions): Promise<MergedThunk>
+1 -1
View File
@@ -52,7 +52,7 @@ const thunk = publishThunk({
thunk.controller.abort() thunk.controller.abort()
// Some commands are included // Some commands are included
const thunk = follow('97c70a44366a6535c145b333f973ea86dfdc2d7a99da618c40c64705ad98e322') const thunk = follow(['p', '97c70a44366a6535c145b333f973ea86dfdc2d7a99da618c40c64705ad98e322'])
// Load events as a promise // Load events as a promise
const events = await load({ const events = await load({
+8 -12
View File
@@ -61,14 +61,14 @@ Several common collections are built-in and ready for use:
profiles profilesByPubkey deriveProfile loadProfile profiles profilesByPubkey deriveProfile loadProfile
// Lists // Lists
follows followsByPubkey deriveFollows loadFollows followLists followListsByPubkey deriveFollowList loadFollowList
mutes mutesByPubkey deriveMutes loadMutes muteLists muteListsByPubkey deriveMuteList loadMuteList
pins pinsByPubkey derivePins loadPins pinLists pinListsByPubkey derivePinList loadPinList
// Relays // Relays
relays relaysByUrl deriveRelay loadRelay relays relaysByUrl deriveRelay loadRelay
relayLists relayListsByPubkey deriveRelayLists loadRelayLists relayLists relayListsByPubkey deriveRelayList loadRelayList
messagingRelayLists messagingRelayListsByPubkey deriveMessagingRelayLists loadMessagingRelayLists messagingRelayLists messagingRelayListsByPubkey deriveMessagingRelayList loadMessagingRelayList
// Identity // Identity
handles handlesByNip05 deriveHandle loadHandle handles handlesByNip05 deriveHandle loadHandle
@@ -97,24 +97,20 @@ const name = deriveProfileDisplay(pubkey)
Several modules provide user-specific derived stores that automatically load data for the currently signed-in user: Several modules provide user-specific derived stores that automatically load data for the currently signed-in user:
```typescript ```typescript
import { userProfile, userFollows, userMutes, userPins } from '@welshman/app' import { userProfile, userFollowList, userMuteList, userPinList } from '@welshman/app'
userProfile.subscribe(profile => { userProfile.subscribe(profile => {
// Current user's profile data // Current user's profile data
}) })
userFollows.subscribe(follows => { userFollowList.subscribe(follows => {
// Current user's follow list // Current user's follow list
}) })
``` ```
### Repository Integration ### Repository Integration
All events from subscriptions are automatically: Events from subscriptions are automatically tracked to their source relay and saved to the repository, unless they are DVM-kind or ephemeral events (which are discarded). WRAP (kind 1059) events are handled separately and only processed when `shouldUnwrap` is set to `true`.
- Saved to the repository
- Tracked to their source relay
- Checked against deletion status
The repository serves as an intelligent cache layer, making subsequent queries for the same data faster. The repository serves as an intelligent cache layer, making subsequent queries for the same data faster.
+3 -2
View File
@@ -63,10 +63,11 @@ Several thunk factories are provided for common or more complicated scenarios li
- `unfollow(value: string)` - `unfollow(value: string)`
- `follow(tag: string[])` - `follow(tag: string[])`
- `unmute(value: string)` - `unmute(value: string)`
- `mute(tag: string[])` - `mutePublicly(tag: string[])`
- `mutePrivately(tag: string[])`
- `unpin(value: string)` - `unpin(value: string)`
- `pin(tag: string[])` - `pin(tag: string[])`
- `sendWrapped({template, pubkeys, ...options}: SendWrappedOptions)` - `sendWrapped({event, recipients, ...options}: SendWrappedOptions)`
- `manageRelay(url: string, request: ManagementRequest)` - `manageRelay(url: string, request: ManagementRequest)`
- `createRoom(url: string, room: RoomMeta)` - `createRoom(url: string, room: RoomMeta)`
- `deleteRoom(url: string, room: RoomMeta)` - `deleteRoom(url: string, room: RoomMeta)`
+1 -1
View File
@@ -174,7 +174,7 @@ getNip55().then(signerApps => {
A fun feature of nostr is that you can log in as other people, and see what nostr is like from their perspective (minus encrypted data or course). A fun feature of nostr is that you can log in as other people, and see what nostr is like from their perspective (minus encrypted data or course).
```typescript ```typescript
import {loginWithPubkey} from "@welshman/signer" import {loginWithPubkey} from "@welshman/app"
// Log in as hodlbod // Log in as hodlbod
loginWithPubkey("97c70a44366a6535c145b333f973ea86dfdc2d7a99da618c40c64705ad98e322") loginWithPubkey("97c70a44366a6535c145b333f973ea86dfdc2d7a99da618c40c64705ad98e322")
+31 -38
View File
@@ -2,25 +2,6 @@
The User Data module provides utilities for loading and managing user-specific data like profiles, follows, mutes, pins, and relay selections. It includes both reactive stores and manual loading functions. The User Data module provides utilities for loading and managing user-specific data like profiles, follows, mutes, pins, and relay selections. It includes both reactive stores and manual loading functions.
## Types
### UserDataLoader
```typescript
type UserDataLoader = (pubkey: string, relays?: string[], force?: boolean) => unknown
```
Function type for loading user data with optional relay specification and force refresh.
### MakeUserDataOptions
```typescript
type MakeUserDataOptions<T> = {
mapStore: Readable<Map<string, T>>
loadItem: UserDataLoader
}
```
Configuration for creating user data stores.
## User Data Stores ## User Data Stores
These reactive stores automatically load and cache user data: These reactive stores automatically load and cache user data:
@@ -30,56 +11,68 @@ These reactive stores automatically load and cache user data:
export const userProfile: Store<Profile | undefined> export const userProfile: Store<Profile | undefined>
// User follows list // User follows list
export const userFollows: Store<List | undefined> export const userFollowList: Store<List | undefined>
// User mutes list // User mutes list
export const userMutes: Store<List | undefined> export const userMuteList: Store<List | undefined>
// User pins list // User pins list
export const userPins: Store<List | undefined> export const userPinList: Store<List | undefined>
// User relay selections // User relay selections
export const userRelayLists: Store<List | undefined> export const userRelayList: Store<List | undefined>
// User messaging relay selections // User messaging relay selections
export const userMessagingRelayLists: Store<List | undefined> export const userMessagingRelayList: Store<List | undefined>
// User blossom servers // User blossom servers
export const userBlossomServers: Store<List | undefined> export const userBlossomServerList: Store<List | undefined>
``` ```
## Manual Loading Functions ## Manual Loading Functions
These functions allow manual loading of user data with optional relay specification and force refresh: These functions load user data for the currently signed-in user with optional relay hints:
```typescript ```typescript
// Load user profile // Load user profile
function loadUserProfile(relays?: string[], force?: boolean): Promise<void> function loadUserProfile(relays?: string[]): Promise<void>
// Load user follows // Load user follows
function loadUserFollows(relays?: string[], force?: boolean): Promise<void> function loadUserFollowList(relays?: string[]): Promise<void>
// Load user mutes // Load user mutes
function loadUserMutes(relays?: string[], force?: boolean): Promise<void> function loadUserMuteList(relays?: string[]): Promise<void>
// Load user pins // Load user pins
function loadUserPins(relays?: string[], force?: boolean): Promise<void> function loadUserPinList(relays?: string[]): Promise<void>
// Load user relay selections // Load user relay selections
function loadUserRelayLists(relays?: string[], force?: boolean): Promise<void> function loadUserRelayList(relays?: string[]): Promise<void>
// Load user messaging relay selections // Load user messaging relay selections
function loadUserMessagingRelayLists(relays?: string[], force?: boolean): Promise<void> function loadUserMessagingRelayList(relays?: string[]): Promise<void>
// Load user blossom servers // Load user blossom servers
function loadUserBlossomServers(relays?: string[], force?: boolean): Promise<void> function loadUserBlossomServerList(relays?: string[]): Promise<void>
```
Force-reload variants bypass the cache and always fetch fresh data:
```typescript
function forceLoadUserProfile(relays?: string[]): Promise<void>
function forceLoadUserFollowList(relays?: string[]): Promise<void>
function forceLoadUserMuteList(relays?: string[]): Promise<void>
function forceLoadUserPinList(relays?: string[]): Promise<void>
function forceLoadUserRelayList(relays?: string[]): Promise<void>
function forceLoadUserMessagingRelayList(relays?: string[]): Promise<void>
function forceLoadUserBlossomServerList(relays?: string[]): Promise<void>
``` ```
## Usage Examples ## Usage Examples
### Using Reactive Stores ### Using Reactive Stores
```typescript ```typescript
import { userProfile, userFollows } from '@welshman/app' import { userProfile, userFollowList } from '@welshman/app'
// Subscribe to user profile changes // Subscribe to user profile changes
userProfile.subscribe(profile => { userProfile.subscribe(profile => {
@@ -89,18 +82,18 @@ userProfile.subscribe(profile => {
}) })
// Get current follows list // Get current follows list
const follows = userFollows.get() const follows = userFollowList.get()
``` ```
### Manual Loading ### Manual Loading
```typescript ```typescript
import { loadUserMutes, loadUserRelayLists } from '@welshman/app' import { loadUserMuteList, forceLoadUserRelayList } from '@welshman/app'
// Load user mutes from specific relays // Load user mutes from specific relays
await loadUserMutes(['wss://relay1.com', 'wss://relay2.com']) await loadUserMuteList(['wss://relay1.com', 'wss://relay2.com'])
// Force refresh user relay selections // Force refresh user relay selections
await loadUserRelayLists([], true) await forceLoadUserRelayList([])
// Load from default relays // Load from default relays
await loadUserProfile() await loadUserProfile()
+1 -1
View File
@@ -53,7 +53,7 @@ followersByPubkey: Readable<Map<string, Set<string>>>
mutersByPubkey: Readable<Map<string, Set<string>>> mutersByPubkey: Readable<Map<string, Set<string>>>
// The full WOT graph with scores (pubkey → score) // The full WOT graph with scores (pubkey → score)
wotGraph: Readable<Map<string, number>> wotGraph: Writable<Map<string, number>>
// The maximum WOT score in the graph // The maximum WOT score in the graph
maxWot: Readable<number> maxWot: Readable<number>
+2 -2
View File
@@ -11,6 +11,7 @@ Defines all supported content types:
- `Cashu` - Cashu token strings - `Cashu` - Cashu token strings
- `Code` - Code blocks and inline code - `Code` - Code blocks and inline code
- `Ellipsis` - Truncation indicators - `Ellipsis` - Truncation indicators
- `Email` - Email addresses
- `Emoji` - Custom emoji references - `Emoji` - Custom emoji references
- `Event` - Event references (note/nevent) - `Event` - Event references (note/nevent)
- `Invoice` - Lightning invoices - `Invoice` - Lightning invoices
@@ -57,13 +58,12 @@ reduceLinks(content: Parsed[]) => Parsed[]
## Type Guards ## Type Guards
Utility functions to check parsed element types: Utility functions to check parsed element types:
- `isAddress(parsed)`, `isCashu(parsed)`, `isCode(parsed)`, etc. - `isAddress(parsed)`, `isCashu(parsed)`, `isCode(parsed)`, `isEmail(parsed)`, etc.
- `isImage(parsed)` - special check for image links - `isImage(parsed)` - special check for image links
## Utilities ## Utilities
- `urlIsMedia(url)` - Checks if URL points to media file - `urlIsMedia(url)` - Checks if URL points to media file
- `fromNostrURI(s)` - Removes nostr: protocol prefix
## Example Usage ## Example Usage
+4 -4
View File
@@ -25,8 +25,8 @@ type RenderOptions = {
## Built-in Renderers ## Built-in Renderers
- `makeTextRenderer` - renders an array of `Parsed` elements as text - `makeTextRenderer` - creates a `Renderer` configured for plain-text output
- `makeHtmlRenderer` - renders an array of `Parsed` elements as HTML - `makeHtmlRenderer` - creates a `Renderer` configured for HTML output
## Main Functions ## Main Functions
@@ -54,7 +54,7 @@ const html = renderAsHtml(parsed, {
}).toString() }).toString()
// Result: // Result:
// Check out this cool #nostr client!<br> // Check out this cool #nostr client!
// Visit <a href="https://njump.me/nprofile1...">npub1jlrs53p...</a> for more info<br> // Visit <a href="https://njump.me/nprofile1...">npub1jlrs53p...</a> for more info
// <a href="https://github.com/coracle-social/welshman" class="custom-link">github.com/coracle-social/welshman</a> // <a href="https://github.com/coracle-social/welshman" class="custom-link">github.com/coracle-social/welshman</a>
``` ```
+3 -3
View File
@@ -32,10 +32,10 @@ export class FeedController {
load(limit: number): Promise<void> load(limit: number): Promise<void>
// Get listener function (memoized) // Get listener function (memoized)
getListener(): Promise<() => Promise<void>> getListener(): Promise<() => () => void>
// Listen for new events in the feed // Listen for new events in the feed; returns an unsubscribe function
listen(): Promise<void> listen(): () => Promise<void>
} }
``` ```
+9 -11
View File
@@ -18,31 +18,29 @@ Read the spec on [the NIPs repository](https://github.com/nostr-protocol/nips/bl
```javascript ```javascript
// Define a feed using set operations // Define a feed using set operations
const feed = intersectionFeed( const feed = makeIntersectionFeed(
unionFeed( makeUnionFeed(
dvmFeed({ makeDVMFeed({
kind: 5300, kind: 5300,
pubkey: '19b78ccfa7c5e31e6bacbb3f2a1703f64b62017702e584440bf29a7e16263e8c', pubkey: '19b78ccfa7c5e31e6bacbb3f2a1703f64b62017702e584440bf29a7e16263e8c',
}), }),
listFeed("10003:19ba654f26afd4930fd3d51baf4e26f1413b7aeec7190cd6c0cdf4d2f14cec6b:"), makeListFeed("10003:19ba654f26afd4930fd3d51baf4e26f1413b7aeec7190cd6c0cdf4d2f14cec6b:"),
) ),
wotFeed({min: 0.1}), makeWOTFeed({min: 0.1}),
scopeFeed("global"), makeGlobalFeed(),
) )
// Create a controller, providing required context via FeedOptions // Create a controller, providing required context via FeedOptions
const controller = new FeedController({ const controller = new FeedController({
feed, feed,
request,
requestDVM,
getPubkeysForScope, getPubkeysForScope,
getPubkeysForWOTRange, getPubkeysForWOTRange,
onEvent: event => console.log("Event", event), onEvent: event => console.log("Event", event),
onExhausted: () => console.log("Exhausted"), onExhausted: () => console.log("Exhausted"),
}) })
// Load notes using the feed // Load notes using the feed — events are delivered via the onEvent callback above
const events = await controller.load(10) await controller.load(10)
``` ```
## Installation ## Installation
+1 -1
View File
@@ -32,6 +32,6 @@ emitter.on('*', (eventType, ...args) => {
// Emit an event - triggers both listeners // Emit an event - triggers both listeners
emitter.emit('message', 'Hello world'); emitter.emit('message', 'Hello world');
// Output: // Output:
// Event: message ['Hello world']
// Message: Hello world // Message: Hello world
// Event: message ['Hello world']
``` ```
+3 -3
View File
@@ -40,9 +40,9 @@ cache.set('c', 3);
console.log(cache.get('a')); // 1 console.log(cache.get('a')); // 1
// Adding 'd' will evict 'b' (least recently used) // Adding 'd' will evict 'a' (its stale key entry is at the front of the tracking queue)
cache.set('d', 4); cache.set('d', 4);
console.log(cache.has('b')); // false console.log(cache.has('b')); // true
console.log(cache.has('a')); // true (recently accessed) console.log(cache.has('a')); // false
``` ```
+3 -1
View File
@@ -8,6 +8,7 @@ The `TaskQueue` class provides a simple queue processing system with batched ope
// Task queue options // Task queue options
export type TaskQueueOptions<Item> = { export type TaskQueueOptions<Item> = {
batchSize: number; batchSize: number;
batchDelay: number;
processItem: (item: Item) => unknown; processItem: (item: Item) => unknown;
}; };
@@ -17,7 +18,7 @@ export declare class TaskQueue<Item> {
push(item: Item): void; push(item: Item): void;
remove(item: Item): void; remove(item: Item): void;
subscribe(subscriber: (item: Item) => void): () => void; subscribe(subscriber: (item: Item) => void): () => void;
process(): Promise<void>; process(): void;
stop(): void; stop(): void;
start(): void; start(): void;
clear(): void; clear(): void;
@@ -32,6 +33,7 @@ import { TaskQueue } from '@welshman/lib';
// Create a task queue that processes 3 items at a time // Create a task queue that processes 3 items at a time
const queue = new TaskQueue({ const queue = new TaskQueue({
batchSize: 3, batchSize: 3,
batchDelay: 0,
processItem: async (message: string) => { processItem: async (message: string) => {
console.log('Processing:', message); console.log('Processing:', message);
// Simulate async work // Simulate async work
+15 -14
View File
@@ -35,17 +35,17 @@ type PartialUser = MakeOptional<User, 'email' | 'age'>;
## Basic functional programming utilities ## Basic functional programming utilities
```typescript ```typescript
// Null or undefined // Undefined or T
export type Nil = null | undefined; export type Maybe<T> = T | undefined;
// Check whether something is null or undefined // Check whether something is not undefined
export declare const isNil: <T>(x: T, ...args: unknown[]) => boolean; export declare const isDefined: <T>(x: T, ...args: unknown[]) => boolean;
// Check whether something is not null or undefined // Check whether something is undefined
export declare const isNotNil: <T>(x: T, ...args: unknown[]) => x is (T & null) | (T & {}) | (T & undefined); export declare const isUndefined: <T>(x: T, ...args: unknown[]) => boolean;
// Assert that a nullable type is not null or undefined // Assert that a value is not undefined
export declare const assertNotNil: <T>(x: T, ...args: unknown[]) => NonNullable<T>; export declare const assertDefined: <T>(x: T, ...args: unknown[]) => NonNullable<T>;
// Function that does nothing and returns undefined // Function that does nothing and returns undefined
export declare const noop: (...args: unknown[]) => undefined; export declare const noop: (...args: unknown[]) => undefined;
@@ -215,7 +215,7 @@ export declare const drop: <T>(n: number, xs: Iterable<T>) => T[];
// Returns first n elements of array // Returns first n elements of array
export declare const take: <T>(n: number, xs: Iterable<T>) => T[]; export declare const take: <T>(n: number, xs: Iterable<T>) => T[];
// Concatenates multiple arrays, filtering out null/undefined // Concatenates multiple arrays, skipping undefined array arguments
export declare const concat: <T>(...xs: T[][]) => T[]; export declare const concat: <T>(...xs: T[][]) => T[];
// Prepends element to array // Prepends element to array
@@ -417,7 +417,7 @@ export declare const yieldThread: () => any;
// Poll options for condition checking // Poll options for condition checking
type PollOptions = { type PollOptions = {
signal: AbortSignal; signal: AbortSignal;
condition: () => boolean; condition: () => unknown;
interval?: number; interval?: number;
}; };
@@ -468,6 +468,7 @@ export declare const setJson: (k: string, v: any) => void;
// Options for fetch requests // Options for fetch requests
type FetchOpts = { type FetchOpts = {
method?: string; method?: string;
signal?: AbortSignal;
headers?: Record<string, string | boolean>; headers?: Record<string, string | boolean>;
body?: string | FormData; body?: string | FormData;
}; };
@@ -497,8 +498,8 @@ export declare const ellipsize: (s: string, l: number, suffix?: string) => strin
// Displays a list of items with oxford commas and a chosen conjunction // Displays a list of items with oxford commas and a chosen conjunction
export declare const displayList: <T>(xs: T[], conj?: string, n?: number) => string; export declare const displayList: <T>(xs: T[], conj?: string, n?: number) => string;
// Generates a hash string from input string // Generates a hash number from input string
export declare const hash: (s: string) => string; export declare const hash: (s: string) => number;
``` ```
## Curried utilities for working with collections ## Curried utilities for working with collections
@@ -564,8 +565,8 @@ export declare const hexToBech32: (prefix: string, hex: string) => `${Lowercase<
// Converts bech32 string to hex format // Converts bech32 string to hex format
export declare const bech32ToHex: (b32: string) => string; export declare const bech32ToHex: (b32: string) => string;
// Converts an array buffer to hex format // Converts an array buffer or Uint8Array to hex format
export declare const bytesToHex: (buffer: ArrayBuffer) => string; export declare const bytesToHex: (buffer: ArrayBuffer | Uint8Array) => string;
// Converts a hex string to a Uint8Array buffer // Converts a hex string to a Uint8Array buffer
export declare const hexToBytes: (hex: string) => Uint8Array; export declare const hexToBytes: (hex: string) => Uint8Array;
+1 -1
View File
@@ -27,7 +27,7 @@ The `netContext` global provides sensible defaults:
import {netContext} from '@welshman/net' import {netContext} from '@welshman/net'
// Override event validation // Override event validation
netContext.isEventValid: (event, url) => { netContext.isEventValid = (event, url) => {
return event.kind < 30000 && verifyEvent(event) return event.kind < 30000 && verifyEvent(event)
} }
+4 -3
View File
@@ -5,6 +5,7 @@ Type definitions and utilities for Nostr protocol messages.
## Relay Message Types ## Relay Message Types
**Enums:** **Enums:**
- `RelayMessageType.Notice` - Human-readable notice from relay
- `RelayMessageType.Auth` - Authentication challenge - `RelayMessageType.Auth` - Authentication challenge
- `RelayMessageType.Closed` - Subscription closed - `RelayMessageType.Closed` - Subscription closed
- `RelayMessageType.Eose` - End of stored events - `RelayMessageType.Eose` - End of stored events
@@ -15,11 +16,11 @@ Type definitions and utilities for Nostr protocol messages.
**Type Definitions:** **Type Definitions:**
- `RelayMessage` - Base relay message type - `RelayMessage` - Base relay message type
- `RelayAuth`, `RelayClosed`, `RelayEose`, `RelayEvent`, `RelayNegErr`, `RelayNegMsg`, `RelayOk` - Specific message types - `RelayNotice`, `RelayAuth`, `RelayClosed`, `RelayEose`, `RelayEvent`, `RelayNegErr`, `RelayNegMsg`, `RelayOk` - Specific message types
- `RelayAuthPayload`, `RelayClosedPayload`, etc. - Payload types for each message - `RelayNoticePayload`, `RelayAuthPayload`, `RelayClosedPayload`, etc. - Payload types for each message
**Type Guards:** **Type Guards:**
- `isRelayAuth()`, `isRelayClosed()`, `isRelayEose()`, `isRelayEvent()`, `isRelayNegErr()`, `isRelayNegMsg()`, `isRelayOk()` - `isRelayNotice()`, `isRelayAuth()`, `isRelayClosed()`, `isRelayEose()`, `isRelayEvent()`, `isRelayNegErr()`, `isRelayNegMsg()`, `isRelayOk()`
## Client Message Types ## Client Message Types
+4
View File
@@ -13,6 +13,10 @@ The contract for socket policies. Takes a Socket object and returns a cleanup fu
## Built-in Policies ## Built-in Policies
### `socketPolicyPing`
Sends a PING message every 30 seconds when the socket is open and has been idle (no send or receive activity in the last 30 seconds). Keeps connections alive through NAT/firewall idle timeouts.
### `socketPolicyAuthBuffer` ### `socketPolicyAuthBuffer`
Buffers messages during authentication flow and replays them after successful auth. Buffers messages during authentication flow and replays them after successful auth.
+1 -6
View File
@@ -12,16 +12,11 @@ A connection pool that creates and manages Socket instances for different relay
- `static get()` - Returns the singleton pool instance - `static get()` - Returns the singleton pool instance
- `has(url)` - Checks if a socket exists for the given URL - `has(url)` - Checks if a socket exists for the given URL
- `get(url)` - Gets or creates a socket for the given URL - `get(url)` - Gets or creates a socket for the given URL
- `makeSocket(url)` - Creates a Socket for the given URL, using `PoolOptions.makeSocket` if provided, otherwise applies `defaultSocketPolicies`
- `subscribe(callback)` - Subscribes to new socket creation events - `subscribe(callback)` - Subscribes to new socket creation events
- `remove(url)` - Removes and cleans up a socket - `remove(url)` - Removes and cleans up a socket
- `clear()` - Removes all sockets from the pool - `clear()` - Removes all sockets from the pool
## Functions
### makeSocket(url, policies)
Creates a new Socket instance with the given URL and applies default policies.
## Example ## Example
```typescript ```typescript
+5 -3
View File
@@ -16,7 +16,9 @@ Requests events from a single relay using the given filters. Returns a promise t
- `context?` - Adapter context - `context?` - Adapter context
- `autoClose?` - Auto-close subscription after EOSE - `autoClose?` - Auto-close subscription after EOSE
- Validation functions: `isEventValid`, `isEventDeleted` - Validation functions: `isEventValid`, `isEventDeleted`
- Callback functions: `onEvent`, `onDeleted`, `onInvalid`, `onFiltered`, `onDuplicate`, `onDisconnect`, `onEose`, `onClose` - Callback functions: `onEvent`, `onDeleted`, `onInvalid`, `onFiltered`, `onDuplicate`, `onDisconnect`, `onEose`, `onClosed`, `onClose`
- `onClosed?: (message: string, url: string) => void` - Called when the relay sends a CLOSED message, receiving the close reason and relay URL
- `onClose?: () => void` - Called when the subscription is fully closed
### request(options) ### request(options)
@@ -38,9 +40,9 @@ Creates a batched loader function that delays and combines requests for efficien
- `threshold?` - Relay completion threshold - `threshold?` - Relay completion threshold
- Validation functions and context options - Validation functions and context options
### load(filters, relays, options) ### load(options)
Pre-configured loader with 200ms delay, 3s timeout, and 0.5 threshold. Pre-configured loader with 200ms delay, 3s timeout, and 0.5 threshold. Takes a `LoadOptions` object containing `relays`, `filters`, and optional callback fields (`onEvent`, `onDisconnect`, `onEose`, `onClose`) and an optional `signal`.
## Example ## Example
+5 -5
View File
@@ -31,7 +31,7 @@ const router = Router.get()
// Get relays for reading events from specific pubkeys // Get relays for reading events from specific pubkeys
const readRelays = router.FromPubkeys(['pubkey1', 'pubkey2']).getUrls() const readRelays = router.FromPubkeys(['pubkey1', 'pubkey2']).getUrls()
// Get relays for publishing an event (author's outbox + mentions' messaginges) // Get relays for publishing (author's outbox + mentions' inbox/read relays)
const publishRelays = router.PublishEvent(event).getUrls() const publishRelays = router.PublishEvent(event).getUrls()
// Try hard to find a quoted note with maximal fallbacks // Try hard to find a quoted note with maximal fallbacks
@@ -74,8 +74,8 @@ The main class for relay selection. Configure it once with your relay discovery
**Scenario Methods:** **Scenario Methods:**
- `FromRelays(relays)` - Use specific relays - `FromRelays(relays)` - Use specific relays
- `ForUser()` / `FromUser()` / `UserMessaging()` - User's read/write/messaging relays - `ForUser()` / `FromUser()` / `MessagesForUser()` - User's read/write/messaging relays
- `ForPubkey(pubkey)` / `FromPubkey(pubkey)` / `PubkeyMessaging(pubkey)` - Pubkey's relays - `ForPubkey(pubkey)` / `FromPubkey(pubkey)` / `MessagesForPubkey(pubkey)` - Pubkey's relays
- `ForPubkeys(pubkeys)` / `FromPubkeys(pubkeys)` - Multiple pubkeys' relays - `ForPubkeys(pubkeys)` / `FromPubkeys(pubkeys)` - Multiple pubkeys' relays
- `Event(event)` - Relays for an event's author - `Event(event)` - Relays for an event's author
- `PublishEvent(event)` - Relays for publishing (author + mentions) - `PublishEvent(event)` - Relays for publishing (author + mentions)
@@ -106,8 +106,8 @@ Functions that determine how many fallback relays to add:
`getFilterSelections(filters)` automatically chooses appropriate relays based on filter content: `getFilterSelections(filters)` automatically chooses appropriate relays based on filter content:
- Search filters → search relays - Search filters → search relays
- Wrap events → user's messaging - Wrap events → user's messaging
- Profile/relay kinds → indexer relays - Profile/relay/follow/messaging-relay kinds → indexer relays
- Author filters → authors' relays - Author filters → authors' relays
- Everything else → user's relays (low weight) - All filters → user's read relays (low-weight baseline, always fires)
Returns `RelaysAndFilters[]` with optimized relay-filter combinations. Returns `RelaysAndFilters[]` with optimized relay-filter combinations.
+1
View File
@@ -15,6 +15,7 @@ interface ISigner {
// Core signing functionality // Core signing functionality
sign: SignWithOptions sign: SignWithOptions
getPubkey: () => Promise<string> getPubkey: () => Promise<string>
cleanup?: () => Promise<void>
// Encryption capabilities // Encryption capabilities
nip04: { nip04: {
+1 -2
View File
@@ -29,8 +29,7 @@ The NIP-01 implementation extends the base interface with two static utility met
### Usage Example ### Usage Example
```typescript ```typescript
import { ISigner } from './interfaces' import { ISigner, Nip01Signer } from '@welshman/signer'
import { Nip01Signer } from './signers/nip01'
// Using the standard interface // Using the standard interface
const signer: ISigner = new Nip01Signer(mySecret) const signer: ISigner = new Nip01Signer(mySecret)
+2 -2
View File
@@ -30,7 +30,7 @@ const signer = new Nip07Signer()
```typescript ```typescript
import { Nip07Signer, getNip07 } from '@welshman/signer' import { Nip07Signer, getNip07 } from '@welshman/signer'
import { createEvent, NOTE } from '@welshman/util' import { makeEvent, NOTE } from '@welshman/util'
async function example() { async function example() {
// Check for NIP-07 provider // Check for NIP-07 provider
@@ -47,7 +47,7 @@ async function example() {
console.log('Public key:', pubkey) console.log('Public key:', pubkey)
// Create and sign an event (will prompt user) // Create and sign an event (will prompt user)
const event = createEvent(NOTE, { const event = makeEvent(NOTE, {
content: "Hello via browser extension!", content: "Hello via browser extension!",
tags: [["t", "test"]] tags: [["t", "test"]]
}) })
+11 -10
View File
@@ -16,13 +16,13 @@ import {
Nip46Broker, Nip46Broker,
Nip46Signer Nip46Signer
} from '@welshman/signer' } from '@welshman/signer'
import { createEvent, NOTE } from '@welshman/util' import { makeEvent, NOTE } from '@welshman/util'
async function connectToRemoteSigner() { async function connectToRemoteSigner() {
// Initial setup // Initial setup
const clientSecret = makeSecret() const clientSecret = makeSecret()
const relays = ['wss://relay.example.com'] const relays = ['wss://relay.example.com']
const broker = Nip46Broker.get({ relays, clientSecret }) const broker = new Nip46Broker({ relays, clientSecret })
const signer = new Nip46Signer(broker) const signer = new Nip46Signer(broker)
// Generate connection URL // Generate connection URL
@@ -36,9 +36,10 @@ async function connectToRemoteSigner() {
try { try {
// Wait for connection // Wait for connection
const abortController = new AbortController()
const response = await broker.waitForNostrconnect( const response = await broker.waitForNostrconnect(
ncUrl, ncUrl,
new AbortController() abortController.signal
) )
// Store signer info for later // Store signer info for later
@@ -46,7 +47,7 @@ async function connectToRemoteSigner() {
localStorage.setItem('bunkerUrl', bunkerUrl) localStorage.setItem('bunkerUrl', bunkerUrl)
// Use the signer // Use the signer
const event = createEvent(NOTE, { const event = makeEvent(NOTE, {
content: "Signed with remote signer!", content: "Signed with remote signer!",
tags: [["t", "test"]] tags: [["t", "test"]]
}) })
@@ -72,7 +73,7 @@ async function reconnect() {
relays relays
} = Nip46Broker.parseBunkerUrl(bunkerUrl) } = Nip46Broker.parseBunkerUrl(bunkerUrl)
const broker = Nip46Broker.get({ const broker = new Nip46Broker({
relays, relays,
clientSecret: makeSecret(), clientSecret: makeSecret(),
signerPubkey, signerPubkey,
@@ -88,8 +89,8 @@ async function reconnect() {
### Constructor and Factory ### Constructor and Factory
```typescript ```typescript
// Recommended: use the singleton factory // Direct instantiation
const broker = Nip46Broker.get({ new Nip46Broker({
relays: string[], relays: string[],
clientSecret: string, clientSecret: string,
connectSecret?: string, connectSecret?: string,
@@ -97,8 +98,8 @@ const broker = Nip46Broker.get({
algorithm?: "nip04" | "nip44" algorithm?: "nip04" | "nip44"
}) })
// Direct instantiation (not recommended) // Static factory: create a broker from a bunker:// URL
new Nip46Broker(params) Nip46Broker.fromBunkerUrl(url: string): Nip46Broker
``` ```
### Connection Methods ### Connection Methods
@@ -110,7 +111,7 @@ broker.makeNostrconnectUrl(metadata: Record<string, string>): Promise<string>
// Wait for connection approval // Wait for connection approval
broker.waitForNostrconnect( broker.waitForNostrconnect(
url: string, url: string,
abort?: AbortController signal: AbortSignal
): Promise<Nip46ResponseWithResult> ): Promise<Nip46ResponseWithResult>
// Get bunker URL for later reconnection // Get bunker URL for later reconnection
+2 -2
View File
@@ -51,7 +51,7 @@ Creates a new signer instance that will communicate with the specified native ap
```typescript ```typescript
import { Nip55Signer, getNip55 } from '@welshman/signer' import { Nip55Signer, getNip55 } from '@welshman/signer'
import { createEvent, NOTE } from '@welshman/util' import { makeEvent, NOTE } from '@welshman/util'
async function example() { async function example() {
try { try {
@@ -69,7 +69,7 @@ async function example() {
console.log('Public key:', pubkey) console.log('Public key:', pubkey)
// Sign an event // Sign an event
const event = createEvent(NOTE, { const event = makeEvent(NOTE, {
content: "Hello from native app!", content: "Hello from native app!",
tags: [["t", "test"]] tags: [["t", "test"]]
}) })
+15 -21
View File
@@ -14,23 +14,20 @@ The `Nip59` class provides utilities for implementing the Gift Wrap protocol (NI
```typescript ```typescript
import { Nip59 } from '@welshman/signer' import { Nip59 } from '@welshman/signer'
import { createEvent, DIRECT_MESSAGE } from '@welshman/util' import { makeEvent, DIRECT_MESSAGE } from '@welshman/util'
// Create a NIP-59 instance from any signer // Create a NIP-59 instance from any signer
const nip59 = Nip59.fromSigner(mySigner) const nip59 = Nip59.fromSigner(mySigner)
// Wrap an event // Wrap an event — returns the gift-wrap SignedEvent to publish
const rumor = await nip59.wrap( const wrappedEvent = await nip59.wrap(
recipientPubkey, recipientPubkey,
createEvent(DIRECT_MESSAGE, { makeEvent(DIRECT_MESSAGE, {
content: "Secret message", content: "Secret message",
tags: [["p", recipientPubkey]] tags: [["p", recipientPubkey]]
}) })
) )
// The wrapped event to publish
const wrappedEvent = rumor.wrap
// Unwrap a received event // Unwrap a received event
const unwrapped = await nip59.unwrap(receivedWrappedEvent) const unwrapped = await nip59.unwrap(receivedWrappedEvent)
``` ```
@@ -51,11 +48,12 @@ export const wrap = async (
template: StampedEvent, template: StampedEvent,
tags: string[][] = [] tags: string[][] = []
) => { ) => {
const rumor = await getRumor(signer, template) const author = await signer.getPubkey()
const rumor = await prep(template, author)
const seal = await getSeal(signer, pubkey, rumor) const seal = await getSeal(signer, pubkey, rumor)
const wrap = await getWrap(wrapper, pubkey, seal, tags) const wrap = await getWrap(wrapper, pubkey, seal, tags)
return Object.assign(rumor, {wrap}) return wrap
} }
``` ```
@@ -79,20 +77,20 @@ class Nip59 {
* @param pubkey Recipient's public key * @param pubkey Recipient's public key
* @param template The event to wrap * @param template The event to wrap
* @param tags Additional tags for the wrap event (optional) * @param tags Additional tags for the wrap event (optional)
* @returns Promise<UnwrappedEvent> Original event and its wrapped version * @returns Promise<SignedEvent> The gift-wrap event to publish
*/ */
wrap( wrap(
pubkey: string, pubkey: string,
template: StampedEvent, template: StampedEvent,
tags?: string[][] tags?: string[][]
): Promise<UnwrappedEvent> ): Promise<SignedEvent>
/** /**
* Unwraps a received wrapped event * Unwraps a received wrapped event
* @param event The wrapped event to decrypt * @param event The wrapped event to decrypt
* @returns Promise<UnwrappedEvent> The original unwrapped event * @returns Promise<HashedEvent> The original unwrapped event
*/ */
unwrap(event: SignedEvent): Promise<UnwrappedEvent> unwrap(event: SignedEvent): Promise<HashedEvent>
/** /**
* Creates a new instance with a specific wrapper signer * Creates a new instance with a specific wrapper signer
@@ -109,24 +107,20 @@ class Nip59 {
```typescript ```typescript
import { Nip59, Nip01Signer } from '@welshman/signer' import { Nip59, Nip01Signer } from '@welshman/signer'
import { createEvent, DIRECT_MESSAGE } from '@welshman/util' import { makeEvent, DIRECT_MESSAGE } from '@welshman/util'
async function example() { async function example() {
// Create NIP-59 instance // Create NIP-59 instance
const signer = new Nip01Signer(mySecret) const signer = new Nip01Signer(mySecret)
const nip59 = Nip59.fromSigner(signer) const nip59 = Nip59.fromSigner(signer)
// Create and wrap an event // Create and wrap an event — returns the gift-wrap SignedEvent to publish
const event = createEvent(DIRECT_MESSAGE, { const event = makeEvent(DIRECT_MESSAGE, {
content: "Secret message", content: "Secret message",
tags: [["p", recipientPubkey]] tags: [["p", recipientPubkey]]
}) })
const rumor = await nip59.wrap(recipientPubkey, event) const wrappedEvent = await nip59.wrap(recipientPubkey, event)
// rumor contains:
// - The original event (rumor)
// - The wrapped version to publish (rumor.wrap)
// Later, unwrap a received event // Later, unwrap a received event
const unwrapped = await nip59.unwrap(receivedEvent) const unwrapped = await nip59.unwrap(receivedEvent)
+2 -1
View File
@@ -15,7 +15,8 @@ A utility package providing welshman-specific svelte store functionality and uti
## Quick Example ## Quick Example
```typescript ```typescript
import {Repository, NAMED_PEOPLE, TrustedEvent, PublishedList, readList} from '@welshman/util' import {Repository} from '@welshman/net'
import {NAMED_PEOPLE, TrustedEvent, PublishedList, readList} from '@welshman/util'
import {deriveItemsByKey} from '@welshman/store' import {deriveItemsByKey} from '@welshman/store'
const repository = new Repository() const repository = new Repository()
+8 -12
View File
@@ -13,16 +13,16 @@ deriveEventsById(options: {
}): Readable<Map<string, TrustedEvent>> }): Readable<Map<string, TrustedEvent>>
// Convert events by ID to array // Convert events by ID to array
deriveEvents(eventsByIdStore: Readable<Map<string, TrustedEvent>>): Readable<TrustedEvent[]> deriveEvents(options: { repository: Repository, filters: Filter[], includeDeleted?: boolean }): Readable<TrustedEvent[]>
// Sort events ascending by created_at // Sort events ascending by created_at
deriveEventsAsc(eventsStore: Readable<TrustedEvent[]>): Readable<TrustedEvent[]> deriveEventsAsc(eventsByIdStore: Readable<Map<string, TrustedEvent>>): Readable<TrustedEvent[]>
// Sort events descending by created_at // Sort events descending by created_at
deriveEventsDesc(eventsStore: Readable<TrustedEvent[]>): Readable<TrustedEvent[]> deriveEventsDesc(eventsByIdStore: Readable<Map<string, TrustedEvent>>): Readable<TrustedEvent[]>
// Derive single event by ID or address // Derive single event by ID or address
deriveEvent(repository: Repository, idOrAddress: string): Readable<TrustedEvent | undefined> makeDeriveEvent(options: { repository: Repository, includeDeleted?: boolean, onDerive?: (filters: Filter[], ...args: any[]) => void }): (idOrAddress: string, ...args: any[]) => Readable<TrustedEvent | undefined>
// Track if event is deleted // Track if event is deleted
deriveIsDeleted(repository: Repository, event: TrustedEvent): Readable<boolean> deriveIsDeleted(repository: Repository, event: TrustedEvent): Readable<boolean>
@@ -52,14 +52,14 @@ makeDeriveItem<T>(
// Create cached loader with staleness checking and exponential backoff // Create cached loader with staleness checking and exponential backoff
makeLoadItem<T>( makeLoadItem<T>(
loadItem: (key: string, ...args: any[]) => Promise<unknown>, loadItem: (key: string, ...args: any[]) => Promise<unknown>,
getItem: (key: string) => T | undefined, getItem: (key: string, ...args: any[]) => T | undefined,
options?: {getFetched?, setFetched?, timeout?} options?: {getFetched?, setFetched?, timeout?}
): (key: string, ...args: any[]) => Promise<T | undefined> ): (key: string, ...args: any[]) => Promise<T | undefined>
// Create loader that always fetches fresh data // Create loader that always fetches fresh data
makeForceLoadItem<T>( makeForceLoadItem<T>(
loadItem: (key: string, ...args: any[]) => Promise<unknown>, loadItem: (key: string, ...args: any[]) => Promise<unknown>,
getItem: (key: string) => T | undefined getItem: (key: string, ...args: any[]) => T | undefined
): (key: string, ...args: any[]) => Promise<T | undefined> ): (key: string, ...args: any[]) => Promise<T | undefined>
// Optimized getter that switches to subscription when hot // Optimized getter that switches to subscription when hot
@@ -70,17 +70,13 @@ getter<T>(store: Readable<T>, options?: {threshold?: number}): () => T
```typescript ```typescript
import {Repository} from "@welshman/net" import {Repository} from "@welshman/net"
import {deriveEventsById, deriveEvents, deriveItemsByKey, deriveItems} from "@welshman/store" import {deriveEvents, deriveItemsByKey, deriveItems} from "@welshman/store"
import {readProfile, PROFILE} from "@welshman/util" import {readProfile, PROFILE} from "@welshman/util"
const repository = new Repository() const repository = new Repository()
// Reactive store of text notes // Reactive store of text notes
const noteEventsById = deriveEventsById({ const notes = deriveEvents({ repository, filters: [{kinds: [1], limit: 100}] })
repository,
filters: [{kinds: [1], limit: 100}]
})
const notes = deriveEvents(noteEventsById)
// Reactive store of profiles indexed by pubkey // Reactive store of profiles indexed by pubkey
const profilesByPubkey = deriveItemsByKey({ const profilesByPubkey = deriveItemsByKey({
+2 -10
View File
@@ -38,15 +38,9 @@ export type SignedEvent = HashedEvent & {
sig: string; sig: string;
}; };
// Wrapped event (NIP-59) // Event that may or may not be signed
export type UnwrappedEvent = HashedEvent & {
wrap: SignedEvent;
};
// Event that can be either signed or wrapped
export type TrustedEvent = HashedEvent & { export type TrustedEvent = HashedEvent & {
sig?: string; sig?: string;
wrap?: SignedEvent;
}; };
``` ```
@@ -72,8 +66,6 @@ export declare const isStampedEvent: (e: StampedEvent) => e is StampedEvent;
export declare const isOwnedEvent: (e: OwnedEvent) => e is OwnedEvent; export declare const isOwnedEvent: (e: OwnedEvent) => e is OwnedEvent;
export declare const isHashedEvent: (e: HashedEvent) => e is HashedEvent; export declare const isHashedEvent: (e: HashedEvent) => e is HashedEvent;
export declare const isSignedEvent: (e: TrustedEvent) => e is SignedEvent; export declare const isSignedEvent: (e: TrustedEvent) => e is SignedEvent;
export declare const isUnwrappedEvent: (e: TrustedEvent) => e is UnwrappedEvent;
export declare const isTrustedEvent: (e: TrustedEvent) => e is TrustedEvent;
``` ```
### Event Utilities ### Event Utilities
@@ -88,7 +80,7 @@ export declare const getIdOrAddress: (e: HashedEvent) => string;
export declare const getIdAndAddress: (e: HashedEvent) => string[]; export declare const getIdAndAddress: (e: HashedEvent) => string[];
// Event deduplication by id or address // Event deduplication by id or address
export declare const deduplicateEvents: (e: TrustedEvent) => TrustedEvent[]; export declare const deduplicateEvents: (events: TrustedEvent[]) => TrustedEvent[];
// Event type checking // Event type checking
export declare const isEphemeral: (e: EventTemplate) => boolean; export declare const isEphemeral: (e: EventTemplate) => boolean;
+2 -1
View File
@@ -79,7 +79,8 @@ const handlers = readHandlers(event)
```typescript ```typescript
// Get unique handler identifier // Get unique handler identifier
const key = getHandlerKey(handler) const key = getHandlerKey(handler)
// => "1:30023:note-viewer" (kind:pubkey:identifier) // => "1:31990:pubkey:identifier" (handler-kind:address)
// where address is the "kind:pubkey:identifier" of the handler event
// Display handler name // Display handler name
const name = displayHandler(handler, "Unknown Handler") const name = displayHandler(handler, "Unknown Handler")
+6 -1
View File
@@ -32,13 +32,18 @@ export type ManagementRequest = {
method: ManagementMethod method: ManagementMethod
params: string[] params: string[]
} }
export type ManagementResponse = {
result?: any
error?: string
}
``` ```
## Functions ## Functions
```typescript ```typescript
// Sends a management request to a relay // Sends a management request to a relay
export declare const sendManagementRequest: (url: string, request: ManagementRequest, authEvent: SignedEvent) => Promise<any> export declare const sendManagementRequest: (url: string, request: ManagementRequest, authEvent: SignedEvent) => Promise<ManagementResponse>
``` ```
## Example ## Example
+1 -1
View File
@@ -6,7 +6,7 @@ Implementation of NIP-98 HTTP Authentication for authenticating HTTP requests wi
```typescript ```typescript
// Creates an HTTP auth event for authenticating requests // Creates an HTTP auth event for authenticating requests
export declare const makeHttpAuth: (url: string, method?: string, body?: string) => Promise<Event> export declare const makeHttpAuth: (url: string, method?: string, body?: string) => Promise<StampedEvent>
// Creates Authorization header from signed HTTP auth event // Creates Authorization header from signed HTTP auth event
export declare const makeHttpAuthHeader: (event: SignedEvent) => string export declare const makeHttpAuthHeader: (event: SignedEvent) => string
+3 -1
View File
@@ -11,6 +11,8 @@ The `Relay` module provides utilities for working with Nostr relays, including U
export enum RelayMode { export enum RelayMode {
Read = "read", Read = "read",
Write = "write", Write = "write",
Search = "search",
Blocked = "blocked",
Messaging = "messaging" Messaging = "messaging"
} }
@@ -26,7 +28,7 @@ export type RelayProfile = {
version?: string; version?: string;
negentropy?: number; negentropy?: number;
description?: string; description?: string;
supported_nips?: number[]; supported_nips?: string[];
limitation?: { limitation?: {
min_pow_difficulty?: number; min_pow_difficulty?: number;
payment_required?: boolean; payment_required?: boolean;
+1 -1
View File
@@ -44,7 +44,7 @@ export declare const hrpToMillisat: (hrpString: string) => bigint;
export declare const getInvoiceAmount: (bolt11: string) => number; export declare const getInvoiceAmount: (bolt11: string) => number;
// Convert lightning address or URL to LNURL // Convert lightning address or URL to LNURL
export declare const getLnUrl: (address: string) => string | null; export declare const getLnUrl: (address: string) => string | undefined;
``` ```
### Zap Validation ### Zap Validation