Add vitepress docs

This commit is contained in:
Ticruz
2025-02-04 14:43:40 +01:00
committed by Jon Staab
parent 43255bcb74
commit 94375a56ec
84 changed files with 10821 additions and 139 deletions
+78
View File
@@ -0,0 +1,78 @@
# Connection
The `Connection` class is the core building block for relay communication in `@welshman/net`. It manages the complete lifecycle of a relay connection, including socket handling, message queuing, authentication, and statistics tracking.
## Overview
A Connection handles:
- WebSocket lifecycle
- Message queuing and throttling
- Connection state tracking
- Relay authentication
- Connection statistics
## Basic Usage
```typescript
import {Connection} from '@welshman/net'
// Create connection
const connection = new Connection("wss://relay.example.com")
// Listen for events
connection.on('event', (conn, subId, event) => {
console.log(`Got event from ${conn.url}`)
})
// Send a subscription
connection.send(["REQ", "my-sub", {kinds: [1], limit: 10}])
// Clean up when done
connection.cleanup()
```
## Handling Authentication
The `connection.open()` promise resolves when the WebSocket connection is fully established and ready for communication.
However, it's important to understand the authentication flow:
```typescript
import {Connection} from '@welshman/net'
const connection = new Connection("wss://relay.example.com")
// Basic open
await connection.open()
// Promise resolves when WebSocket is connected
// BUT might not be auth-ready yet!
// Complete open with auth handling
const openRelay = async (url: string) => {
const connection = new Connection(url)
// Open socket
await connection.open()
// Check if relay requires auth
if (connection.auth.status === 'requested') {
try {
// Handle auth challenge
await connection.auth.attempt(3000) // 3s timeout
} catch (e) {
console.error('Auth failed:', e)
return null
}
}
// NOW connection is fully ready
return connection
}
```
The key states after `open()` resolves:
- Socket is connected
- Messages can be queued
- BUT relay might request authentication
- AND authentication might fail
Always check `connection.auth.status` if you need to ensure the connection is fully authenticated before use.
+63
View File
@@ -0,0 +1,63 @@
# Context
The Context system is the backbone of `@welshman/net`, providing global configuration and shared services that are essential for the package's operation. It defines how events are handled, validated, and routed throughout the application.
## Overview
- Global connection pool (`ctx.net.pool`)
- Event validation (`ctx.net.isValid`)
- Event handling (`ctx.net.onEvent`)
- Deletion tracking (`ctx.net.isDeleted`)
- Event signing (`ctx.net.signEvent`)
- Subscription optimization (`ctx.net.optimizeSubscriptions`)
## Basic Usage
```typescript
import {ctx, setContext} from '@welshman/lib'
import {
getDefaultNetContext,
Pool,
hasValidSignature
} from '@welshman/net'
// Setup networking context
setContext({
net: getDefaultNetContext({
// Use shared pool
pool: new Pool(),
// Track events
onEvent: (url, event) => {
tracker.track(event.id, url)
repository.publish(event)
},
// Validate based on source
isValid: (url, event) => {
// Trust local relay
if (url === LOCAL_RELAY_URL) return true
// Validate signature for remote events
return hasValidSignature(event)
},
// Check deletion status
isDeleted: (url, event) =>
repository.isDeleted(event),
// Sign with current user
signEvent: async (event) =>
signer.get().sign(event)
})
})
// Now all package features will use these settings
subscribe(/*...*/) // Uses pool, validates events
publish(/*...*/) // Uses signEvent
```
The Context is used internally by most features in the package.
Without proper context configuration, core features like subscription, publishing, and event validation won't work correctly.
Think of it as the central configuration that defines how your nostr networking behaves.
+56
View File
@@ -0,0 +1,56 @@
# Executor
The Executor class orchestrates event delivery and subscription management across one or more [targets](/net/targets.md). It abstracts the complexity of handling multiple connections into a single interface.
## Overview
The Executor:
- Manages subscriptions
- Handles event publishing
- Supports NIP-77 (negentropy)
- Routes messages to appropriate targets
## Basic Usage
```typescript
import {Executor, Relays} from '@welshman/net'
// Create executor with relay target
const executor = new Executor(
new Relays([
connection1,
connection2
])
)
// Subscribe to events
const sub = executor.subscribe(
[{kinds: [1], limit: 10}],
{
onEvent: (url, event) => {
console.log(`Got event from ${url}`, event)
},
onEose: (url) => {
console.log(`EOSE from ${url}`)
}
}
)
// Publish event
const pub = executor.publish(
signedEvent,
{
onOk: (url, id, success, message) => {
console.log(`Published to ${url}: ${success ? 'OK' : message}`)
}
}
)
// Clean up
sub.unsubscribe()
executor.target.cleanup()
```
The Executor is used internally by higher-level APIs but can be used directly when you need fine-grained control over event routing and subscription management.
It's particularly useful when implementing custom targets or handling special relay configurations (like local relays or relay groups).
+19
View File
@@ -0,0 +1,19 @@
# @welshman/net
Core networking layer for nostr applications, handling relay connections, message management, and event delivery.
## What's Included
- **Connection Management** - WebSocket lifecycle and relay connections
- **Subscription System** - Event filtering and subscription handling
- **Publishing Tools** - Event broadcasting with status tracking
- **Sync Utilities** - NIP-77 (negentropy) event synchronization
- **Connection Pool** - Shared relay connection management
- **Targets** - Flexible message routing strategies
- **Event Tracking** - Monitor which relays have seen events
## Installation
```bash
npm install @welshman/net
```
+37
View File
@@ -0,0 +1,37 @@
# Pool
The Pool class manages a collection of relay connections, providing a centralized way to track and reuse connections across your application.
## Overview
- Creates and caches connections
- Ensures single connection per relay
- Handles cleanup of unused connections
- Provides connection lookup
## Usage
```typescript
import {Pool} from '@welshman/net'
// Create pool
const pool = new Pool()
// Get or create connection
const connection = pool.get("wss://relay.example.com")
// Check if relay is in pool
if (pool.has("wss://relay.example.com")) {
// Use existing connection
}
// Remove connection
pool.remove("wss://relay.example.com")
// Clear all connections
pool.clear()
```
The Pool is typically used internally by the router and executor, but can be used directly for custom connection management.
It ensures efficient connection reuse across your application.
+85
View File
@@ -0,0 +1,85 @@
# Publish
The `Publish` class handles event publishing to relays, managing publish status, relay responses, and error handling.
## Overview
- Sends events to relays
- Tracks publish status per relay
- Handles OK/Error responses
- Manages timeouts
## Basic Usage
```typescript
import {`Publish`, `Publish`Status} from '@welshman/net'
const {Pending, Success, Failure, Timeout, Aborted} = `Publish`Status
// Basic `Publish`
const pub = `Publish`({
event: signedEvent,
relays: ["wss://relay.example.com"],
timeout: 3000 // 3s timeout
})
// Track status
pub.emitter.on('*', (status: `Publish`Status, url: string, message?: string) => {
switch (status) {
case Success:
console.log(``Publish`ed to ${url}`)
break
case Failure:
console.log(`Failed on ${url}: ${message}`)
break
case Timeout:
console.log(`Timeout on ${url}`)
break
}
})
```
## Real World Example
```typescript
const publishWithStatus = async (event: SignedEvent) => {
const pub = `Publish`({
event,
relays: ctx.app.router
.FromUser()
.getUrls(),
timeout: 5000
})
// Track per-relay status
const status = new Map<string, string>()
pub.emitter.on('*', (state: `Publish`Status, url: string) => {
status.set(url, state)
// Log progress
const counts = {
pending: 0,
success: 0,
failed: 0
}
for (const s of status.values()) {
counts[s] = (counts[s] || 0) + 1
}
console.log(
`Progress: ${counts.success}/${status.size}`,
`(${counts.failed} failed)`
)
})
// Wait for completion
return pub.result
}
```
Like [Subscribe](/net/subscribe.md), `Publish` uses [Pool](/net/pool.md) for connections and creates appropriate [Targets](/net/targets.md) via an [Executor](/net/executor.md), but focuses on event publishing rather than subscription management.
Note: The base `@welshman/net` Publish class just handles network publishing.
For optimistic updates and repository integration, use Publish from `@welshman/app`.
+69
View File
@@ -0,0 +1,69 @@
# Socket
The Socket class is exclusively used by the `Connection` class as its low-level WebSocket manager. It's not meant to be used directly by other classes.
Its sole purpose is to provide a reliable, manageable WebSocket connection with nostr-specific handling.
## Core Responsibilities
```typescript
export class Socket {
// Track connection state
status: SocketStatus = "new" | "open" | "opening" | "closing" | "closed" | "error"
// Handle nostr message queue
worker: Worker<Message>
// Core operations
open = async () => {/* Initialize WebSocket */}
close = async () => {/* Clean shutdown */}
send = async (message: Message) => {/* Send with JSON serialization */}
}
```
Key features:
- State tracking
- Message queuing
- JSON serialization
- Error recovery
- Connection lifecycle
Think of it as a thin wrapper that turns raw WebSocket connections into something more suitable for nostr:
```typescript
// Raw WebSocket
ws.send(JSON.stringify(["REQ", "sub1", {kinds: [1]}]))
// With Socket
socket.send(["REQ", "sub1", {kinds: [1]}]) // Handles serialization
```
## Usage Chain
```typescript
// Hierarchy
Socket // WebSocket management
Connection // Uses Socket
Relay Target // Uses Connection
Executor // Uses Target
Subscribe // Uses Executor
Publish // Uses Executor
// In Connection.ts
export class Connection extends Emitter {
socket: Socket
constructor(url: string) {
this.socket = new Socket(this)
}
}
```
It's an internal implementation detail that you shouldn't need to use directly - always interact with the `Connection` class instead, which provides a higher-level interface.
```typescript
// DON'T use Socket directly
const socket = new Socket(/*...*/) // ❌
// DO use Connection
const connection = new Connection(url) // ✅
```
This encapsulation ensures consistent connection management across the library.
+115
View File
@@ -0,0 +1,115 @@
# Subscribe
The Subscribe class manages nostr subscriptions, handling subscription lifecycle, event filtering, and relay responses. It provides a unified interface for subscribing to events across multiple relays.
## Overview
The Subscription:
- Manages REQ/CLOSE lifecycle
- Handles EOSE responses
- Emits filtered events
- Tracks completion state
```typescript
import {subscribe, SubscriptionEvent} from '@welshman/net'
// Create subscription
const sub = subscribe({
filters: [{kinds: [1], limit: 10}],
relays: ["wss://relay.example.com"],
// Optional configurations
closeOnEose: true, // Close after all relays send EOSE
timeout: 3000, // Max time to wait
authTimeout: 300, // Time for auth negotiation
delay: 50 // Delay between batched requests
})
// Handle events
sub.on(SubscriptionEvent.Event, (url, event) => {
console.log(`Got event from ${url}:`, event)
})
sub.on(SubscriptionEvent.Eose, (url) => {
console.log(`Got EOSE from ${url}`)
})
sub.on(SubscriptionEvent.Complete, () => {
console.log('Subscription complete')
})
// Close when done
sub.close()
```
## Architecture
```typescript
import {subscribe, Pool, Executor, Relays} from '@welshman/net'
// Under the hood, subscribe:
// 1. Gets connections from global pool
// 2. Creates a target (usually Relays)
// 3. Uses Executor to manage subscription
// This is roughly equivalent to:
const manualSubscribe = (urls: string[]) => {
// Get connections from pool
const connections = urls.map(url =>
ctx.net.pool.get(url)
)
// Create target
const target = new Relays(connections)
// Create executor
const executor = new Executor(target)
// Subscribe via executor
return executor.subscribe(
[{kinds: [1], limit: 10}],
{
onEvent: (url, event) => {
console.log(`Got event from ${url}`)
}
}
)
}
```
## Real World Example
```typescript
// Combine local and remote relays
const loadProfile = async (pubkey: string) => {
// Get optimal relays
const relays = ctx.app.router
.ForPubkey(pubkey)
.getUrls()
const sub = subscribe({
filters: [{
kinds: [0],
authors: [pubkey],
limit: 1
}],
relays,
// This creates internally:
// 1. Connections via Pool
// 2. Multi target with Local + Relays
// 3. Executor to manage subscription
})
return new Promise(resolve => {
sub.on('event', (url, event) => {
resolve(event)
sub.close()
})
})
}
```
The Subscribe class abstracts away:
- Connection management (via Pool)
- Target creation and setup
- Executor orchestration
+70
View File
@@ -0,0 +1,70 @@
# Sync
The Sync utilities in `@welshman/net` provide methods for synchronizing events between relays and repositories, primarily using NIP-77 (Negentropy) when available, with fallback to traditional sync methods.
## Overview
```typescript
import {sync, pull, push} from '@welshman/net'
// Three main operations:
// 1. pull: Get events from relays
// 2. push: Send events to relays
// 3. sync: Bidirectional sync
```
These utilities are primarily used by:
- `Repository` for syncing with relays
- `FeedController` for initial feed loading
## Basic Usage
```typescript
import {sync, pull, getFilterSelections} from '@welshman/net'
// Sync user profile data
const syncProfiles = async (pubkeys: string[]) => {
await sync({
// What to sync
filters: [{
kinds: [0],
authors: pubkeys
}],
// Which relays
relays: ctx.app.router
.ForPubkeys(pubkeys)
.getUrls(),
// Local events to consider
events: repository.query([{
kinds: [0],
authors: pubkeys
}])
})
}
// Initial feed load with negentropy
const loadFeed = async () => {
await pull({
filters: [{
kinds: [1],
limit: 100
}],
relays: ctx.app.router
.ForUser()
.getUrls(),
events: [], // No local events yet
onEvent: (event) => {
// Handle new events
}
})
}
```
Sync operations:
- Use NIP-77 when supported by relay
- Fall back to traditional sync
- Handle bidirectional sync
- Support filtered sync
- Track sync progress
+137
View File
@@ -0,0 +1,137 @@
# Targets
The targets system provides different strategies for message routing.
Each target type implements a common interface for handling nostr messages but with different routing behaviors.
## Overview
Targets are used by the [Executor](/net/executor.md) class to:
- Route messages to connections
- Handle responses
- Manage connection lifecycles
- Combine multiple routing strategies
## Available Targets
### Echo Target
Simple target that echoes messages back. Useful for testing.
```typescript
import {Echo} from '@welshman/net'
const echo = new Echo()
echo.on('EVENT', (url, event) => {
console.log('Echo received:', event)
})
```
### Local Target
Connects to an in-memory relay implementation.
```typescript
import {Local} from '@welshman/net'
import {Repository, Relay} from '@welshman/util'
// Create local relay
const repository = new Repository()
const relay = new Relay(repository)
const local = new Local(relay)
// Use like any other target
local.send(['REQ', 'sub1', {kinds: [1]}])
```
### Relay Target
Single relay connection target.
```typescript
import {Relay} from '@welshman/net'
const target = new Relay(connection)
target.on('EVENT', (url, event) => {
console.log(`Event from ${url}:`, event)
})
```
### Relays Target
Manages multiple relay connections.
```typescript
import {Relays} from '@welshman/net'
const target = new Relays([
connection1,
connection2,
connection3
])
```
### Multi Target
Combines multiple targets into one.
```typescript
import {Multi, Local, Relays} from '@welshman/net'
// Create multi-target with local and remote relays
const target = new Multi([
new Local(localRelay),
new Relays(remoteConnections)
])
```
## Real World Example
Here's how Coracle might set up its relay infrastructure:
```typescript
import {
Executor,
Multi,
Local,
Relays
} from '@welshman/net'
import {Repository, Relay} from '@welshman/util'
// Setup
const setupRelayInfrastructure = () => {
// Create local repository & relay
const repository = new Repository()
const localRelay = new Relay(repository)
// Get remote connections from pool
const remoteConnections = [
pool.get("wss://relay1.example.com"),
pool.get("wss://relay2.example.com")
]
// Create multi-target executor
const executor = new Executor(
new Multi([
// Local relay for immediate responses
new Local(localRelay),
// Remote relays for network queries
new Relays(remoteConnections)
])
)
// Subscribe using combined target
const sub = executor.subscribe(
[{kinds: [1], limit: 10}],
{
onEvent: (url, event) => {
if (url === LOCAL_RELAY_URL) {
console.log('Got from cache:', event)
} else {
console.log('Got from network:', url, event)
}
}
}
)
return {executor, sub}
}
```
The target system allows for flexible relay configurations while maintaining a consistent interface for the rest of the application. This is particularly useful for:
- Caching with local relays
- Load balancing across relays
- Fallback strategies
- Testing and simulation
Each target type serves a specific purpose but can be combined using `Multi` for complex routing scenarios.
+72
View File
@@ -0,0 +1,72 @@
# Tracker
The Tracker is a simple but crucial class that keeps track of which relays an event was seen on or published to. It's essential for relay selection and event source tracking.
## Overview
```typescript
import {Tracker} from '@welshman/net'
const tracker = new Tracker()
// Track event source
tracker.track(eventId, relayUrl)
// Get relays for event
const relays = tracker.getRelays(eventId) // Set<string>
// Get events from relay
const events = tracker.getIds(relayUrl) // Set<string>
// Check specific relay
const seen = tracker.hasRelay(eventId, relayUrl)
```
## Used By
1. **Repository & Sync**
```typescript
// In sync operations
pull({
events,
relays,
onEvent: (event) => {
tracker.track(event.id, relay)
}
})
```
2. **Subscribe**
```typescript
// In @welshman/app subscribe
sub.on('event', (url, event) => {
// Track where we got the event
tracker.track(event.id, url)
})
```
3. **Publish**
```typescript
// In publish operations
pub.emitter.on('success', (url) => {
// Track where we published
tracker.track(event.id, url)
})
```
4. **Router**
```typescript
// Used for relay selection
const relays = tracker
.getRelays(event.id)
.filter(url =>
isHealthyRelay(url)
)
```
The Tracker:
- Maps events to their source relays
- Maps relays to their known events
- Helps optimize relay selection
Think of it as a memory of where events came from, helping make better decisions about where to find or publish events.