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
type SendWrappedOptions = Omit<ThunkOptions, "event" | "relays"> & {
template: EventTemplate
pubkeys: string[]
event: EventTemplate
recipients: string[]
}
sendWrapped(options: SendWrappedOptions): Promise<MergedThunk>
+1 -1
View File
@@ -52,7 +52,7 @@ const thunk = publishThunk({
thunk.controller.abort()
// Some commands are included
const thunk = follow('97c70a44366a6535c145b333f973ea86dfdc2d7a99da618c40c64705ad98e322')
const thunk = follow(['p', '97c70a44366a6535c145b333f973ea86dfdc2d7a99da618c40c64705ad98e322'])
// Load events as a promise
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
// Lists
follows followsByPubkey deriveFollows loadFollows
mutes mutesByPubkey deriveMutes loadMutes
pins pinsByPubkey derivePins loadPins
followLists followListsByPubkey deriveFollowList loadFollowList
muteLists muteListsByPubkey deriveMuteList loadMuteList
pinLists pinListsByPubkey derivePinList loadPinList
// Relays
relays relaysByUrl deriveRelay loadRelay
relayLists relayListsByPubkey deriveRelayLists loadRelayLists
messagingRelayLists messagingRelayListsByPubkey deriveMessagingRelayLists loadMessagingRelayLists
relayLists relayListsByPubkey deriveRelayList loadRelayList
messagingRelayLists messagingRelayListsByPubkey deriveMessagingRelayList loadMessagingRelayList
// Identity
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:
```typescript
import { userProfile, userFollows, userMutes, userPins } from '@welshman/app'
import { userProfile, userFollowList, userMuteList, userPinList } from '@welshman/app'
userProfile.subscribe(profile => {
// Current user's profile data
})
userFollows.subscribe(follows => {
userFollowList.subscribe(follows => {
// Current user's follow list
})
```
### Repository Integration
All events from subscriptions are automatically:
- Saved to the repository
- Tracked to their source relay
- Checked against deletion status
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`.
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)`
- `follow(tag: string[])`
- `unmute(value: string)`
- `mute(tag: string[])`
- `mutePublicly(tag: string[])`
- `mutePrivately(tag: string[])`
- `unpin(value: string)`
- `pin(tag: string[])`
- `sendWrapped({template, pubkeys, ...options}: SendWrappedOptions)`
- `sendWrapped({event, recipients, ...options}: SendWrappedOptions)`
- `manageRelay(url: string, request: ManagementRequest)`
- `createRoom(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).
```typescript
import {loginWithPubkey} from "@welshman/signer"
import {loginWithPubkey} from "@welshman/app"
// Log in as hodlbod
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.
## 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
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>
// User follows list
export const userFollows: Store<List | undefined>
export const userFollowList: Store<List | undefined>
// User mutes list
export const userMutes: Store<List | undefined>
export const userMuteList: Store<List | undefined>
// User pins list
export const userPins: Store<List | undefined>
export const userPinList: Store<List | undefined>
// User relay selections
export const userRelayLists: Store<List | undefined>
export const userRelayList: Store<List | undefined>
// User messaging relay selections
export const userMessagingRelayLists: Store<List | undefined>
export const userMessagingRelayList: Store<List | undefined>
// User blossom servers
export const userBlossomServers: Store<List | undefined>
export const userBlossomServerList: Store<List | undefined>
```
## 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
// Load user profile
function loadUserProfile(relays?: string[], force?: boolean): Promise<void>
function loadUserProfile(relays?: string[]): Promise<void>
// Load user follows
function loadUserFollows(relays?: string[], force?: boolean): Promise<void>
function loadUserFollowList(relays?: string[]): Promise<void>
// Load user mutes
function loadUserMutes(relays?: string[], force?: boolean): Promise<void>
function loadUserMuteList(relays?: string[]): Promise<void>
// Load user pins
function loadUserPins(relays?: string[], force?: boolean): Promise<void>
function loadUserPinList(relays?: string[]): Promise<void>
// Load user relay selections
function loadUserRelayLists(relays?: string[], force?: boolean): Promise<void>
function loadUserRelayList(relays?: string[]): Promise<void>
// Load user messaging relay selections
function loadUserMessagingRelayLists(relays?: string[], force?: boolean): Promise<void>
function loadUserMessagingRelayList(relays?: string[]): Promise<void>
// 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
### Using Reactive Stores
```typescript
import { userProfile, userFollows } from '@welshman/app'
import { userProfile, userFollowList } from '@welshman/app'
// Subscribe to user profile changes
userProfile.subscribe(profile => {
@@ -89,18 +82,18 @@ userProfile.subscribe(profile => {
})
// Get current follows list
const follows = userFollows.get()
const follows = userFollowList.get()
```
### Manual Loading
```typescript
import { loadUserMutes, loadUserRelayLists } from '@welshman/app'
import { loadUserMuteList, forceLoadUserRelayList } from '@welshman/app'
// 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
await loadUserRelayLists([], true)
await forceLoadUserRelayList([])
// Load from default relays
await loadUserProfile()
+1 -1
View File
@@ -53,7 +53,7 @@ followersByPubkey: Readable<Map<string, Set<string>>>
mutersByPubkey: Readable<Map<string, Set<string>>>
// 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
maxWot: Readable<number>
+2 -2
View File
@@ -11,6 +11,7 @@ Defines all supported content types:
- `Cashu` - Cashu token strings
- `Code` - Code blocks and inline code
- `Ellipsis` - Truncation indicators
- `Email` - Email addresses
- `Emoji` - Custom emoji references
- `Event` - Event references (note/nevent)
- `Invoice` - Lightning invoices
@@ -57,13 +58,12 @@ reduceLinks(content: Parsed[]) => Parsed[]
## Type Guards
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
## Utilities
- `urlIsMedia(url)` - Checks if URL points to media file
- `fromNostrURI(s)` - Removes nostr: protocol prefix
## Example Usage
+4 -4
View File
@@ -25,8 +25,8 @@ type RenderOptions = {
## Built-in Renderers
- `makeTextRenderer` - renders an array of `Parsed` elements as text
- `makeHtmlRenderer` - renders an array of `Parsed` elements as HTML
- `makeTextRenderer` - creates a `Renderer` configured for plain-text output
- `makeHtmlRenderer` - creates a `Renderer` configured for HTML output
## Main Functions
@@ -54,7 +54,7 @@ const html = renderAsHtml(parsed, {
}).toString()
// Result:
// Check out this cool #nostr client!<br>
// Visit <a href="https://njump.me/nprofile1...">npub1jlrs53p...</a> for more info<br>
// Check out this cool #nostr client!
// 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>
```
+3 -3
View File
@@ -32,10 +32,10 @@ export class FeedController {
load(limit: number): Promise<void>
// Get listener function (memoized)
getListener(): Promise<() => Promise<void>>
getListener(): Promise<() => () => void>
// Listen for new events in the feed
listen(): Promise<void>
// Listen for new events in the feed; returns an unsubscribe function
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
// Define a feed using set operations
const feed = intersectionFeed(
unionFeed(
dvmFeed({
const feed = makeIntersectionFeed(
makeUnionFeed(
makeDVMFeed({
kind: 5300,
pubkey: '19b78ccfa7c5e31e6bacbb3f2a1703f64b62017702e584440bf29a7e16263e8c',
}),
listFeed("10003:19ba654f26afd4930fd3d51baf4e26f1413b7aeec7190cd6c0cdf4d2f14cec6b:"),
)
wotFeed({min: 0.1}),
scopeFeed("global"),
makeListFeed("10003:19ba654f26afd4930fd3d51baf4e26f1413b7aeec7190cd6c0cdf4d2f14cec6b:"),
),
makeWOTFeed({min: 0.1}),
makeGlobalFeed(),
)
// Create a controller, providing required context via FeedOptions
const controller = new FeedController({
feed,
request,
requestDVM,
getPubkeysForScope,
getPubkeysForWOTRange,
onEvent: event => console.log("Event", event),
onExhausted: () => console.log("Exhausted"),
})
// Load notes using the feed
const events = await controller.load(10)
// Load notes using the feed — events are delivered via the onEvent callback above
await controller.load(10)
```
## Installation
+1 -1
View File
@@ -32,6 +32,6 @@ emitter.on('*', (eventType, ...args) => {
// Emit an event - triggers both listeners
emitter.emit('message', 'Hello world');
// Output:
// Event: 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
// 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);
console.log(cache.has('b')); // false
console.log(cache.has('a')); // true (recently accessed)
console.log(cache.has('b')); // true
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
export type TaskQueueOptions<Item> = {
batchSize: number;
batchDelay: number;
processItem: (item: Item) => unknown;
};
@@ -17,7 +18,7 @@ export declare class TaskQueue<Item> {
push(item: Item): void;
remove(item: Item): void;
subscribe(subscriber: (item: Item) => void): () => void;
process(): Promise<void>;
process(): void;
stop(): void;
start(): void;
clear(): void;
@@ -32,6 +33,7 @@ import { TaskQueue } from '@welshman/lib';
// Create a task queue that processes 3 items at a time
const queue = new TaskQueue({
batchSize: 3,
batchDelay: 0,
processItem: async (message: string) => {
console.log('Processing:', message);
// Simulate async work
+15 -14
View File
@@ -35,17 +35,17 @@ type PartialUser = MakeOptional<User, 'email' | 'age'>;
## Basic functional programming utilities
```typescript
// Null or undefined
export type Nil = null | undefined;
// Undefined or T
export type Maybe<T> = T | undefined;
// Check whether something is null or undefined
export declare const isNil: <T>(x: T, ...args: unknown[]) => boolean;
// Check whether something is not undefined
export declare const isDefined: <T>(x: T, ...args: unknown[]) => boolean;
// Check whether something is not null or undefined
export declare const isNotNil: <T>(x: T, ...args: unknown[]) => x is (T & null) | (T & {}) | (T & undefined);
// Check whether something is undefined
export declare const isUndefined: <T>(x: T, ...args: unknown[]) => boolean;
// Assert that a nullable type is not null or undefined
export declare const assertNotNil: <T>(x: T, ...args: unknown[]) => NonNullable<T>;
// Assert that a value is not undefined
export declare const assertDefined: <T>(x: T, ...args: unknown[]) => NonNullable<T>;
// Function that does nothing and returns 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
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[];
// Prepends element to array
@@ -417,7 +417,7 @@ export declare const yieldThread: () => any;
// Poll options for condition checking
type PollOptions = {
signal: AbortSignal;
condition: () => boolean;
condition: () => unknown;
interval?: number;
};
@@ -468,6 +468,7 @@ export declare const setJson: (k: string, v: any) => void;
// Options for fetch requests
type FetchOpts = {
method?: string;
signal?: AbortSignal;
headers?: Record<string, string | boolean>;
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
export declare const displayList: <T>(xs: T[], conj?: string, n?: number) => string;
// Generates a hash string from input string
export declare const hash: (s: string) => string;
// Generates a hash number from input string
export declare const hash: (s: string) => number;
```
## 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
export declare const bech32ToHex: (b32: string) => string;
// Converts an array buffer to hex format
export declare const bytesToHex: (buffer: ArrayBuffer) => string;
// Converts an array buffer or Uint8Array to hex format
export declare const bytesToHex: (buffer: ArrayBuffer | Uint8Array) => string;
// Converts a hex string to a Uint8Array buffer
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'
// Override event validation
netContext.isEventValid: (event, url) => {
netContext.isEventValid = (event, url) => {
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
**Enums:**
- `RelayMessageType.Notice` - Human-readable notice from relay
- `RelayMessageType.Auth` - Authentication challenge
- `RelayMessageType.Closed` - Subscription closed
- `RelayMessageType.Eose` - End of stored events
@@ -15,11 +16,11 @@ Type definitions and utilities for Nostr protocol messages.
**Type Definitions:**
- `RelayMessage` - Base relay message type
- `RelayAuth`, `RelayClosed`, `RelayEose`, `RelayEvent`, `RelayNegErr`, `RelayNegMsg`, `RelayOk` - Specific message types
- `RelayAuthPayload`, `RelayClosedPayload`, etc. - Payload types for each message
- `RelayNotice`, `RelayAuth`, `RelayClosed`, `RelayEose`, `RelayEvent`, `RelayNegErr`, `RelayNegMsg`, `RelayOk` - Specific message types
- `RelayNoticePayload`, `RelayAuthPayload`, `RelayClosedPayload`, etc. - Payload types for each message
**Type Guards:**
- `isRelayAuth()`, `isRelayClosed()`, `isRelayEose()`, `isRelayEvent()`, `isRelayNegErr()`, `isRelayNegMsg()`, `isRelayOk()`
- `isRelayNotice()`, `isRelayAuth()`, `isRelayClosed()`, `isRelayEose()`, `isRelayEvent()`, `isRelayNegErr()`, `isRelayNegMsg()`, `isRelayOk()`
## 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
### `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`
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
- `has(url)` - Checks if a socket exists 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
- `remove(url)` - Removes and cleans up a socket
- `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
```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
- `autoClose?` - Auto-close subscription after EOSE
- 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)
@@ -38,9 +40,9 @@ Creates a batched loader function that delays and combines requests for efficien
- `threshold?` - Relay completion threshold
- 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
+5 -5
View File
@@ -31,7 +31,7 @@ const router = Router.get()
// Get relays for reading events from specific pubkeys
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()
// 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:**
- `FromRelays(relays)` - Use specific relays
- `ForUser()` / `FromUser()` / `UserMessaging()` - User's read/write/messaging relays
- `ForPubkey(pubkey)` / `FromPubkey(pubkey)` / `PubkeyMessaging(pubkey)` - Pubkey's relays
- `ForUser()` / `FromUser()` / `MessagesForUser()` - User's read/write/messaging relays
- `ForPubkey(pubkey)` / `FromPubkey(pubkey)` / `MessagesForPubkey(pubkey)` - Pubkey's relays
- `ForPubkeys(pubkeys)` / `FromPubkeys(pubkeys)` - Multiple pubkeys' relays
- `Event(event)` - Relays for an event's author
- `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:
- Search filters → search relays
- Wrap events → user's messaging
- Profile/relay kinds → indexer relays
- Profile/relay/follow/messaging-relay kinds → indexer 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.
+1
View File
@@ -15,6 +15,7 @@ interface ISigner {
// Core signing functionality
sign: SignWithOptions
getPubkey: () => Promise<string>
cleanup?: () => Promise<void>
// Encryption capabilities
nip04: {
+1 -2
View File
@@ -29,8 +29,7 @@ The NIP-01 implementation extends the base interface with two static utility met
### Usage Example
```typescript
import { ISigner } from './interfaces'
import { Nip01Signer } from './signers/nip01'
import { ISigner, Nip01Signer } from '@welshman/signer'
// Using the standard interface
const signer: ISigner = new Nip01Signer(mySecret)
+2 -2
View File
@@ -30,7 +30,7 @@ const signer = new Nip07Signer()
```typescript
import { Nip07Signer, getNip07 } from '@welshman/signer'
import { createEvent, NOTE } from '@welshman/util'
import { makeEvent, NOTE } from '@welshman/util'
async function example() {
// Check for NIP-07 provider
@@ -47,7 +47,7 @@ async function example() {
console.log('Public key:', pubkey)
// Create and sign an event (will prompt user)
const event = createEvent(NOTE, {
const event = makeEvent(NOTE, {
content: "Hello via browser extension!",
tags: [["t", "test"]]
})
+11 -10
View File
@@ -16,13 +16,13 @@ import {
Nip46Broker,
Nip46Signer
} from '@welshman/signer'
import { createEvent, NOTE } from '@welshman/util'
import { makeEvent, NOTE } from '@welshman/util'
async function connectToRemoteSigner() {
// Initial setup
const clientSecret = makeSecret()
const relays = ['wss://relay.example.com']
const broker = Nip46Broker.get({ relays, clientSecret })
const broker = new Nip46Broker({ relays, clientSecret })
const signer = new Nip46Signer(broker)
// Generate connection URL
@@ -36,9 +36,10 @@ async function connectToRemoteSigner() {
try {
// Wait for connection
const abortController = new AbortController()
const response = await broker.waitForNostrconnect(
ncUrl,
new AbortController()
abortController.signal
)
// Store signer info for later
@@ -46,7 +47,7 @@ async function connectToRemoteSigner() {
localStorage.setItem('bunkerUrl', bunkerUrl)
// Use the signer
const event = createEvent(NOTE, {
const event = makeEvent(NOTE, {
content: "Signed with remote signer!",
tags: [["t", "test"]]
})
@@ -72,7 +73,7 @@ async function reconnect() {
relays
} = Nip46Broker.parseBunkerUrl(bunkerUrl)
const broker = Nip46Broker.get({
const broker = new Nip46Broker({
relays,
clientSecret: makeSecret(),
signerPubkey,
@@ -88,8 +89,8 @@ async function reconnect() {
### Constructor and Factory
```typescript
// Recommended: use the singleton factory
const broker = Nip46Broker.get({
// Direct instantiation
new Nip46Broker({
relays: string[],
clientSecret: string,
connectSecret?: string,
@@ -97,8 +98,8 @@ const broker = Nip46Broker.get({
algorithm?: "nip04" | "nip44"
})
// Direct instantiation (not recommended)
new Nip46Broker(params)
// Static factory: create a broker from a bunker:// URL
Nip46Broker.fromBunkerUrl(url: string): Nip46Broker
```
### Connection Methods
@@ -110,7 +111,7 @@ broker.makeNostrconnectUrl(metadata: Record<string, string>): Promise<string>
// Wait for connection approval
broker.waitForNostrconnect(
url: string,
abort?: AbortController
signal: AbortSignal
): Promise<Nip46ResponseWithResult>
// 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
import { Nip55Signer, getNip55 } from '@welshman/signer'
import { createEvent, NOTE } from '@welshman/util'
import { makeEvent, NOTE } from '@welshman/util'
async function example() {
try {
@@ -69,7 +69,7 @@ async function example() {
console.log('Public key:', pubkey)
// Sign an event
const event = createEvent(NOTE, {
const event = makeEvent(NOTE, {
content: "Hello from native app!",
tags: [["t", "test"]]
})
+15 -21
View File
@@ -14,23 +14,20 @@ The `Nip59` class provides utilities for implementing the Gift Wrap protocol (NI
```typescript
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
const nip59 = Nip59.fromSigner(mySigner)
// Wrap an event
const rumor = await nip59.wrap(
// Wrap an event — returns the gift-wrap SignedEvent to publish
const wrappedEvent = await nip59.wrap(
recipientPubkey,
createEvent(DIRECT_MESSAGE, {
makeEvent(DIRECT_MESSAGE, {
content: "Secret message",
tags: [["p", recipientPubkey]]
})
)
// The wrapped event to publish
const wrappedEvent = rumor.wrap
// Unwrap a received event
const unwrapped = await nip59.unwrap(receivedWrappedEvent)
```
@@ -51,11 +48,12 @@ export const wrap = async (
template: StampedEvent,
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 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 template The event to wrap
* @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(
pubkey: string,
template: StampedEvent,
tags?: string[][]
): Promise<UnwrappedEvent>
): Promise<SignedEvent>
/**
* Unwraps a received wrapped event
* @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
@@ -109,24 +107,20 @@ class Nip59 {
```typescript
import { Nip59, Nip01Signer } from '@welshman/signer'
import { createEvent, DIRECT_MESSAGE } from '@welshman/util'
import { makeEvent, DIRECT_MESSAGE } from '@welshman/util'
async function example() {
// Create NIP-59 instance
const signer = new Nip01Signer(mySecret)
const nip59 = Nip59.fromSigner(signer)
// Create and wrap an event
const event = createEvent(DIRECT_MESSAGE, {
// Create and wrap an event — returns the gift-wrap SignedEvent to publish
const event = makeEvent(DIRECT_MESSAGE, {
content: "Secret message",
tags: [["p", recipientPubkey]]
})
const rumor = await nip59.wrap(recipientPubkey, event)
// rumor contains:
// - The original event (rumor)
// - The wrapped version to publish (rumor.wrap)
const wrappedEvent = await nip59.wrap(recipientPubkey, event)
// Later, unwrap a received event
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
```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'
const repository = new Repository()
+8 -12
View File
@@ -13,16 +13,16 @@ deriveEventsById(options: {
}): Readable<Map<string, TrustedEvent>>
// 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
deriveEventsAsc(eventsStore: Readable<TrustedEvent[]>): Readable<TrustedEvent[]>
deriveEventsAsc(eventsByIdStore: Readable<Map<string, TrustedEvent>>): Readable<TrustedEvent[]>
// 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
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
deriveIsDeleted(repository: Repository, event: TrustedEvent): Readable<boolean>
@@ -52,14 +52,14 @@ makeDeriveItem<T>(
// Create cached loader with staleness checking and exponential backoff
makeLoadItem<T>(
loadItem: (key: string, ...args: any[]) => Promise<unknown>,
getItem: (key: string) => T | undefined,
getItem: (key: string, ...args: any[]) => T | undefined,
options?: {getFetched?, setFetched?, timeout?}
): (key: string, ...args: any[]) => Promise<T | undefined>
// Create loader that always fetches fresh data
makeForceLoadItem<T>(
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>
// Optimized getter that switches to subscription when hot
@@ -70,17 +70,13 @@ getter<T>(store: Readable<T>, options?: {threshold?: number}): () => T
```typescript
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"
const repository = new Repository()
// Reactive store of text notes
const noteEventsById = deriveEventsById({
repository,
filters: [{kinds: [1], limit: 100}]
})
const notes = deriveEvents(noteEventsById)
const notes = deriveEvents({ repository, filters: [{kinds: [1], limit: 100}] })
// Reactive store of profiles indexed by pubkey
const profilesByPubkey = deriveItemsByKey({
+2 -10
View File
@@ -38,15 +38,9 @@ export type SignedEvent = HashedEvent & {
sig: string;
};
// Wrapped event (NIP-59)
export type UnwrappedEvent = HashedEvent & {
wrap: SignedEvent;
};
// Event that can be either signed or wrapped
// Event that may or may not be signed
export type TrustedEvent = HashedEvent & {
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 isHashedEvent: (e: HashedEvent) => e is HashedEvent;
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
@@ -88,7 +80,7 @@ export declare const getIdOrAddress: (e: HashedEvent) => string;
export declare const getIdAndAddress: (e: HashedEvent) => string[];
// Event deduplication by id or address
export declare const deduplicateEvents: (e: TrustedEvent) => TrustedEvent[];
export declare const deduplicateEvents: (events: TrustedEvent[]) => TrustedEvent[];
// Event type checking
export declare const isEphemeral: (e: EventTemplate) => boolean;
+2 -1
View File
@@ -79,7 +79,8 @@ const handlers = readHandlers(event)
```typescript
// Get unique handler identifier
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
const name = displayHandler(handler, "Unknown Handler")
+6 -1
View File
@@ -32,13 +32,18 @@ export type ManagementRequest = {
method: ManagementMethod
params: string[]
}
export type ManagementResponse = {
result?: any
error?: string
}
```
## Functions
```typescript
// 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
+1 -1
View File
@@ -6,7 +6,7 @@ Implementation of NIP-98 HTTP Authentication for authenticating HTTP requests wi
```typescript
// 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
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 {
Read = "read",
Write = "write",
Search = "search",
Blocked = "blocked",
Messaging = "messaging"
}
@@ -26,7 +28,7 @@ export type RelayProfile = {
version?: string;
negentropy?: number;
description?: string;
supported_nips?: number[];
supported_nips?: string[];
limitation?: {
min_pow_difficulty?: number;
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;
// 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