Update docs
This commit is contained in:
@@ -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
@@ -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({
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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>
|
||||
```
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
|
||||
```
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
|
||||
|
||||
|
||||
@@ -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
@@ -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
@@ -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
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -15,6 +15,7 @@ interface ISigner {
|
||||
// Core signing functionality
|
||||
sign: SignWithOptions
|
||||
getPubkey: () => Promise<string>
|
||||
cleanup?: () => Promise<void>
|
||||
|
||||
// Encryption capabilities
|
||||
nip04: {
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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
@@ -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
@@ -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()
|
||||
|
||||
@@ -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
@@ -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;
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user