Delete readmes, update docs

This commit is contained in:
Jon Staab
2025-04-09 11:17:12 -07:00
parent 3301616e7d
commit 8e585fc150
53 changed files with 1946 additions and 2468 deletions
+2 -1
View File
@@ -1,4 +1,5 @@
--ignore-dir=docs --ignore-dir=docs/reference
--ignore-dir=docs/.vitepress/cache
--ignore-dir=dist --ignore-dir=dist
--ignore-dir=build --ignore-dir=build
--ignore-dir=.svelte-kit --ignore-dir=.svelte-kit
+2
View File
@@ -1,2 +1,4 @@
node_modules node_modules
docs/reference
docs/.vitepress/cache
build build
+62 -60
View File
@@ -21,13 +21,17 @@ export default defineConfig({
], ],
}, },
{ {
text: "@welshman/lib", text: "@welshman/app",
link: "/lib/", link: "/app/",
items: [ items: [
{text: "Utilities", link: "/lib/tools"}, {text: "Session Management", link: "/app/session"},
{text: "LRU cache", link: "/lib/lru"}, {text: "Relay Selection", link: "/app/relay-selection"},
{text: "Worker", link: "/lib/worker"}, {text: "Making Requests", link: "/app/making-requests"},
{text: "Deferred", link: "/lib/deferred"}, {text: "Publishing Events", link: "/app/publishing-events"},
{text: "Tag utilities", link: "/app/tags"},
{text: "Web of Trust", link: "/app/wot"},
{text: "Storage", link: "/app/storage"},
{text: "Context", link: "/app/context"},
], ],
}, },
{ {
@@ -48,37 +52,6 @@ export default defineConfig({
{text: "Zaps", link: "/util/zaps"}, {text: "Zaps", link: "/util/zaps"},
], ],
}, },
{
text: "@welshman/content",
link: "/content/",
items: [
{text: "Parser", link: "/content/parser"},
{text: "Renderer", link: "/content/renderer"},
],
},
{
text: "@welshman/feeds",
link: "/feeds/",
items: [
{text: "Core", link: "/feeds/core"},
{text: "Utilities", link: "/feeds/utils"},
{text: "Compiler", link: "/feeds/compiler"},
{text: "Controller", link: "/feeds/controller"},
],
},
{
text: "@welshman/editor",
link: "/editor/",
items: [],
},
{
text: "@welshman/store",
link: "/store/",
items: [
{text: "Basic utilities", link: "/store/basic"},
{text: "Event stores", link: "/store/events"},
],
},
{ {
text: "@welshman/net", text: "@welshman/net",
link: "/net/", link: "/net/",
@@ -95,14 +68,6 @@ export default defineConfig({
{text: "Socket", link: "/net/socket"}, {text: "Socket", link: "/net/socket"},
], ],
}, },
{
text: "@welshman/dvm",
link: "/dvm/",
items: [
{text: "Handler", link: "/dvm/handler"},
{text: "Request", link: "/dvm/request"},
],
},
{ {
text: "@welshman/signer", text: "@welshman/signer",
link: "/signer/", link: "/signer/",
@@ -116,22 +81,59 @@ export default defineConfig({
], ],
}, },
{ {
text: "@welshman/app", text: "@welshman/relay",
link: "/app/", link: "/relay/",
items: [ items: [
{text: "Context", link: "/app/context"}, ],
{text: "Storage", link: "/app/storage"}, },
{text: "Router", link: "/app/router"}, {
{text: "Session", link: "/app/session"}, text: "@welshman/content",
{text: "Collection", link: "/app/collection"}, link: "/content/",
{text: "Commands", link: "/app/commands"}, items: [
{text: "Subscription", link: "/app/subscription"}, {text: "Parser", link: "/content/parser"},
{text: "Publish (Thunks)", link: "/app/thunks"}, {text: "Renderer", link: "/content/renderer"},
{text: "Feed", link: "/app/feed"}, ],
{text: "Tag utilities", link: "/app/tags"}, },
{text: "Topics", link: "/app/topics"}, {
{text: "Web of Trust", link: "/app/wot"}, text: "@welshman/editor",
{text: "Stores and Loaders", link: "/app/storesandloaders"}, link: "/editor/",
items: [],
},
{
text: "@welshman/feeds",
link: "/feeds/",
items: [
{text: "Core", link: "/feeds/core"},
{text: "Utilities", link: "/feeds/utils"},
{text: "Compiler", link: "/feeds/compiler"},
{text: "Controller", link: "/feeds/controller"},
],
},
{
text: "@welshman/dvm",
link: "/dvm/",
items: [
{text: "Handler", link: "/dvm/handler"},
{text: "Request", link: "/dvm/request"},
],
},
{
text: "@welshman/store",
link: "/store/",
items: [
{text: "Basic utilities", link: "/store/basic"},
{text: "Event stores", link: "/store/events"},
],
},
{
text: "@welshman/lib",
link: "/lib/",
items: [
{text: "Utilities", link: "/lib/tools"},
{text: "LRU cache", link: "/lib/lru"},
{text: "Task Queue", link: "/lib/task-queue"},
{text: "Normalize URL", link: "/lib/normalize-url"},
{text: "Deferred", link: "/lib/deferred"},
], ],
}, },
], ],
-103
View File
@@ -1,103 +0,0 @@
# Collection Stores
The `collection` utility creates stores that handle caching, loading, and indexing of Nostr data. It provides a consistent pattern for managing entities that need to be fetched from the network and cached locally.
```typescript
const {
indexStore, // Map of all items by key
deriveItem, // Get reactive item by key
loadItem // Trigger network load
} = collection({
name: "storeName", // For persistence
store: writable([]), // Base store
getKey: item => item.id // How to index items
load: async (key) => { // Network loader
// Load logic here
}
})
```
## Available Collections
```typescript
// Profiles
profiles profilesByPubkey deriveProfile loadProfile
// Lists
follows followsByPubkey deriveFollows loadFollows
mutes mutesByPubkey deriveMutes loadMutes
pins pinsByPubkey derivePins loadPins
// Relays
relays relaysByUrl deriveRelay loadRelay
relaySelections relaySelectionsByPubkey deriveRelaySelections loadRelaySelections
inboxRelaySelections inboxRelaySelectionsByPubkey deriveInboxRelaySelections loadInboxRelaySelections
// Identity
handles handlesByNip05 deriveHandle loadHandle
zappers zappersByLnurl deriveZapper loadZapper
```
## Real World Examples
### Loading and Displaying Profiles
```typescript
import {
deriveProfile,
loadProfile,
displayProfile
} from '@welshman/app'
// In a Svelte component
let profile
// Subscribe to profile changes
$: profile = $deriveProfile(pubkey)
// Load automatically triggers when needed
onMount(() => {
loadProfile(pubkey, {
// Optional request params
relays: ["wss://relay.example.com"]
})
})
// Display with fallback
$: name = displayProfile(profile, "unknown")
```
### Managing Relay Selections
```typescript
import {
deriveRelaySelections,
loadRelaySelections,
getReadRelayUrls,
getWriteRelayUrls
} from '@welshman/app'
// Get user's relay preferences
const selections = deriveRelaySelections(pubkey).get()
// Load from network if needed
await loadRelaySelections(pubkey)
// Get read/write URLs
const readRelays = getReadRelayUrls(selections)
const writeRelays = getWriteRelayUrls(selections)
// Use with router
const relays = ctx.app.router
.FromPubkey(pubkey)
.getUrls()
```
Each collection automatically:
- Caches to IndexedDB
- Deduplicates network requests
- Updates reactively
- Provides typed access
- Handles loading states
The pattern is consistent across all stores, making it predictable to work with different types of nostr data.
-88
View File
@@ -1,88 +0,0 @@
# Commands
High-level commands for common Nostr operations.
Each command handles signing, encryption, and relay selection automatically.
## Available Commands
```typescript
// List Management
follow(pubkey)
unfollow(pubkey)
mute(pubkey)
unmute(pubkey)
pin(tag)
unpin(tag)
```
Each command returns a [`Thunk`](app/thunk) which:
- Optimistically updates local state
- Signs and publishes the event
- Can be aborted within a delay window
- Reports publish progress
## Real World Examples
### Following/Unfollowing Users
```typescript
import {follow, unfollow, userFollows} from '@welshman/app'
// Follow with optimistic update
const followUser = async (pubkey: string) => {
// Creates and publishes event with an updated follow list
const thunk = await follow(pubkey)
// Track publish status per relay
thunk.status.subscribe(statuses => {
for (const [url, status] of Object.entries(statuses)) {
console.log(`${url}: ${status}`)
}
})
// Can abort within delay window
setTimeout(() => thunk.controller.abort(), 1000)
}
// Unfollow works the same way
const unfollowUser = async (pubkey: string) => {
const thunk = await unfollow(pubkey)
// Wait for completion
const results = await thunk.result
}
```
### Managing Pins
```typescript
import {pin, unpin, userPins} from '@welshman/app'
// Pin an event with context
const pinEvent = async (event: TrustedEvent) => {
const thunk = await pin([
'e', event.id,
ctx.app.router.Event(event).getUrl()
])
// Handle specific relay errors
thunk.status.subscribe(statuses => {
for (const [url, {status, message}] of Object.entries(statuses)) {
if (status === 'failure') {
console.error(`Failed on ${url}: ${message}`)
}
}
})
}
```
All commands:
- Handle encryption automatically
- Select appropriate relays
- Update local state immediately
- Allow soft-undo via abort
- Report per-relay status
- Return consistent Thunk interface
Commands provide a high-level way to modify the Nostr state without dealing with the complexities of event creation, encryption, and relay selection.
+6 -132
View File
@@ -1,139 +1,13 @@
# Application Context # Application Context
The `@welshman/app` package uses a global context system to configure core behaviors. The `@welshman/app` package uses a global context system to configure a few core behaviors.
Understanding the app context is essential as it powers [session/authentication](/app/session), [relay routing](/app/relay) and [request handling](/app/request).
## Basic Setup ## Dufflepud
[Dufflepud](https://github.com/coracle-social/dufflepud) is a utility server that can retrieve NIP 05 profiles, zappers, relay metadata, link previews, etc. It's not necessary for using welshman, but can improve things by bypassing CORS.
```typescript ```typescript
import {ctx, setContext} from '@welshman/lib' import {appContext} from '@welshman/app'
import {getDefaultNetContext, getDefaultAppContext} from '@welshman/app'
// Initialize app with default settings appContext.dufflepudUrl = 'https://my-dufflepud-instance.com'
setContext({
net: getDefaultNetContext(),
app: getDefaultAppContext()
})
// Access context anywhere
console.log(ctx.app.router)
console.log(ctx.net.pool)
```
## Default App Context
```typescript
export type AppContext = {
// Smart relay routing system
router: Router
// Time to wait between batched requests (ms)
requestDelay: number // default: 50
// Time to wait for NIP-42 relay auth (ms)
authTimeout: number // default: 300
// Time to wait for request completion (ms)
requestTimeout: number // default: 3000
// URL of metadata service (optional)
dufflepudUrl?: string
// Additional relays for indexed content
indexerRelays?: string[]
}
// Example with custom settings
setContext({
app: getDefaultAppContext({
requestDelay: 100,
authTimeout: 500,
requestTimeout: 5000,
dufflepudUrl: "https://api.example.com",
indexerRelays: [
"wss://relay.example.com",
"wss://indexed.example.com"
]
})
})
```
## Network Context
```typescript
export type NetContext = {
// Global connection pool
pool: Pool
// How to handle NIP-42 auth
authMode: AuthMode // default: 'implicit'
// Event validation and handling
onEvent: (url: string, event: TrustedEvent) => void
isDeleted: (url: string, event: TrustedEvent) => boolean
isValid: (url: string, event: TrustedEvent) => boolean
// Event signing (used by all packages)
signEvent: (event: StampedEvent) => Promise<SignedEvent>
// Subscription optimization
optimizeSubscriptions: (subs: Subscription[]) => RelaysAndFilters[]
}
// Example with custom validation
setContext({
net: getDefaultNetContext({
// Custom event validation
isValid: (url, event) => {
if (url === LOCAL_RELAY_URL) return true
return hasValidSignature(event)
},
// Track deleted events
isDeleted: (url, event) =>
repository.isDeleted(event),
// Custom event handling
onEvent: (url, event) => {
// Save to local repository
repository.publish(event)
// Track which relay it came from
tracker.track(event.id, url)
}
})
})
```
## Using Context Values
Once configured, context values are used throughout the app:
```typescript
import {ctx} from '@welshman/lib'
// Smart relay routing
const relays = ctx.app.router
.ForPubkey(pubkey)
.getUrls()
// Publish with timeout
const pub = publish({
event,
relays,
timeout: ctx.app.requestTimeout
})
// Subscribe with auth
const sub = subscribe({
filters,
relays,
authTimeout: ctx.app.authTimeout
})
// Check connection pool
const connected = ctx.net.pool
.get(relay)
.socket.status === 'open'
``` ```
-96
View File
@@ -1,96 +0,0 @@
# Feed
The feed system provides a powerful way to compose and load complex `Nostr` queries. It supports user scopes, web of trust filtering, DVM integration, and thread construction.
## Controller
The `controller.load()` function is the main interface for fetching events from a feed. It handles all the complexity of relay selection, subscription management, and event filtering.
```typescript
import {createFeedController} from '@welshman/app'
import {scopeFeed, wotFeed} from '@welshman/feeds'
const controller = createFeedController({
// Define what to load
feed: scopeFeed("follows"),
// Optional configurations
closeOnEose: true, // Close after getting all events
onEvent: event => {}, // Handle events as they arrive
onEose: url => {}, // Handle EOSE from each relay
onComplete: () => {}, // Called when all relays complete
})
// Load first 20 events
const events = await controller.load(20)
// Load next 20 events
const moreEvents = await controller.load(20)
```
The controller maintains its state between loads, so subsequent calls will:
- Continue from last position
- Use appropriate time windows
- Skip already seen events
- Maintain relay connections
## Paginated Feed
```typescript
import {intersectionFeed, scopeFeed, wotFeed} from '@welshman/feeds'
const HomeFeed = {
let events = []
let loading = false
let controller
onMount(() => {
// Create feed for home timeline
controller = createFeedController({
feed: intersectionFeed(
// Content from follows
scopeFeed("follows"),
// Filtered by web of trust
wotFeed({min: 0.1})
),
// Handle events as they arrive
onEvent: event => {
events = [...events, event]
},
// Track loading state
onComplete: () => {
loading = false
}
})
// Initial load
loadMore()
})
const loadMore = async () => {
if (loading) return
loading = true
// Load next batch
await controller.load(20)
}
}
```
Key points about `controller.load()`:
- Takes a limit parameter for batch size
- Returns a promise of loaded events
- Can be called repeatedly for pagination
- Handles subscription lifecycle
- Manages relay connections
- Deduplicates events
The controller is stateful and maintains:
- Current time window
- Seen events
- Active subscriptions
- Relay connections
This makes it ideal for implementing infinite scroll feeds, thread loading, and other paginated content scenarios.
+63 -1
View File
@@ -1,5 +1,7 @@
# @welshman/app # @welshman/app
[![version](https://badgen.net/npm/v/@welshman/app)](https://npmjs.com/package/@welshman/app)
A comprehensive framework for building nostr clients, powering production applications like [Coracle](https://coracle.social) and [Flotilla](https://flotilla.social). It provides a complete toolkit for managing events, subscriptions, user data, and relay connections. A comprehensive framework for building nostr clients, powering production applications like [Coracle](https://coracle.social) and [Flotilla](https://flotilla.social). It provides a complete toolkit for managing events, subscriptions, user data, and relay connections.
## What's Included ## What's Included
@@ -7,11 +9,71 @@ A comprehensive framework for building nostr clients, powering production applic
- **Repository System** - Event storage and query capabilities - **Repository System** - Event storage and query capabilities
- **Router** - Intelligent relay selection for optimal networking - **Router** - Intelligent relay selection for optimal networking
- **Feed Controller** - Manages feed creation and updates - **Feed Controller** - Manages feed creation and updates
- **Authentication** - User identity and key management - **Session Management** - User identity and key management
- **Event Actions** - High-level operations like reacting, replying, etc. - **Event Actions** - High-level operations like reacting, replying, etc.
- **Profile Management** - User profile handling and metadata - **Profile Management** - User profile handling and metadata
- **Relay Directories** - Discovery and management of relays - **Relay Directories** - Discovery and management of relays
- **Web of Trust** - Utilities for building webs of trust
## Quick Example
```typescript
import {getNip07} from '@welshman/signer'
import {load, request, RequestEvent, defaultSocketPolicies, makeSocketPolicyAuth, Socket} from '@welshman/net'
import {StampedEvent, TrustedEvent, makeEvent, NOTE} from '@welshman/util'
import {pubkey, signer, publishThunk} from '@welshman/app'
// Log in via NIP 07
addSession({method: 'nip07', pubkey: await getNip07().getPubkey()})
// Enable automatic authentication to relays
defaultSocketPolicies.push(
makeSocketPolicyAuth({
sign: (event: StampedEvent) => signer.get()?.sign(event),
shouldAuth: (socket: Socket) => true,
}),
)
// This will fetch the user's profile automatically, and return a store that updates
// automatically. Several different stores exist that are ready to go, including handles,
// zappers, relaySelections, relays, follows, mutes.
const profile = deriveProfile(pubkey.get())
// Publish is done using thunks, which optimistically publish to the local database, deferring
// signing and publishing for instant user feedback. Progress is reported as relays accept/reject the event
// Events are automatically signed using the current session
const thunk = publishThunk({
relays: Router.get().FromUser().getUrls(),
event: makeEvent(NOTE, {content: "hi"}),
delay: 3000,
})
// Thunks can be aborted until after `delay`, allowing for soft-undo
thunk.controller.abort()
// Some commands are included
const thunk = follow('97c70a44366a6535c145b333f973ea86dfdc2d7a99da618c40c64705ad98e322')
// Load events as a promise
const events = await load({
relays: Router.get().ForUser().getUrls(),
filters: [{kinds: [NOTE],
}])
// Or use `request` for more fine-grained subscription control
const req = request({
relays: Router.get().ForUser().getUrls(),
filters: [{kinds: [NOTE],
}])
// Listen for events
req.on(RequestEvent.Event, (event: TrustedEvent) => {
console.log(event)
})
// Close the req
req.close()
```
## Installation ## Installation
+143
View File
@@ -0,0 +1,143 @@
# Making Requests
Welshman extends Nostr's base subscription model with intelligent caching, repository integration, and configurable behaviors.
## Key Concepts
- **Local Repository**: Events are automatically cached and tracked
- **Cache Intelligence**: Smart decisions about when to use cached data
- **Relay Integration**: Works with the router for optimal relay selection
- **Configurable Behavior**: Control caching and timeouts
## Request and Load
The base functionality for subscription management is implemented in `@welshman/net`. Please refer to [the documentation](/net) for that module for details.
## Collections and Loaders
The `collection` utility creates stores that handle caching, loading, and indexing of Nostr data. It provides a consistent pattern for managing entities that need to be fetched from the network and cached locally.
```typescript
const {
indexStore, // Map of all items by key
deriveItem, // Get reactive item by key
loadItem // Trigger network load
} = collection({
name: "storeName", // For persistence
store: writable([]), // Base store
getKey: item => item.id // How to index items
load: async (key) => { // Network loader
// Load logic here
}
})
```
### Deriving Events
The best way to create collections is by deriving their contents from the app `repository` using `deriveEvents` from `@welshman/store`. For more control, use `deriveEventsMapped`.
```typescript
import {deriveEventsMapped} from "@welshman/store"
export const notes = deriveEvents<TrustedEvent>(repository, {filters: [{kinds: [NOTE]}]})
```
A collection could then be created by passing the `notes` store to `collection`.
### Available Collections
Several common collections are built-in and ready for use:
```typescript
// Profiles
profiles profilesByPubkey deriveProfile loadProfile
// Lists
follows followsByPubkey deriveFollows loadFollows
mutes mutesByPubkey deriveMutes loadMutes
pins pinsByPubkey derivePins loadPins
// Relays
relays relaysByUrl deriveRelay loadRelay
relaySelections relaySelectionsByPubkey deriveRelaySelections loadRelaySelections
inboxRelaySelections inboxRelaySelectionsByPubkey deriveInboxRelaySelections loadInboxRelaySelections
// Identity
handles handlesByNip05 deriveHandle loadHandle
zappers zappersByLnurl deriveZapper loadZapper
```
### Example - Loading and Displaying Profiles
```typescript
import {get} from 'svelte/store'
import {displayProfile} from '@welshman/util'
import {deriveProfile, deriveProfileDisplay} from '@welshman/app'
// Subscribe to profile changes - this will automatically load the profile in the background
const profile = deriveProfile(pubkey)
// Display with fallback
const name = displayProfile(get(profile), 'unknown')
// Better: use built-in deriveProfileDisplay utility
const name = deriveProfileDisplay(pubkey)
```
### User-Specific Collections
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'
userProfile.subscribe(profile => {
// Current user's profile data
})
userFollows.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
The repository serves as an intelligent cache layer, making subsequent queries for the same data faster.
## Feeds
A high-level feed loader utility is also provided, which combines application state with utilities from `@welshman/net` and `@welshman/feeds`.
```typescript
import {NOTE} from '@welshman/util'
import {makeKindFeed} from '@welshman/feeds'
import {createFeedController} from '@welshman/app'
const abortController = new AbortController()
let done = false
const ctrl = createFeedController({
feed: makeKindFeed(NOTE),
useWindowing: true,
signal: abortController.signal,
onEvent: e => {
console.log(e)
},
onExhausted: () => {
done = true
},
})
// Load some notes
ctrl.load(100)
// Cancel any pending requests
abortController.abort()
```
@@ -5,9 +5,10 @@ Thunks provide optimistic updates for event publishing. They immediately update
## Overview ## Overview
A thunk: A thunk:
- Updates local state immediately - Updates local state immediately
- Handles event signing in the background - Handles event signing in the background using the current session
- Manages publish status per relay - Tracks publish status per relay
- Supports soft-undo via abort - Supports soft-undo via abort
- Can be delayed/cancelled - Can be delayed/cancelled
- Tracks successful publishes - Tracks successful publishes
@@ -46,7 +47,17 @@ const publish = async (content: string) => {
}, 1000) }, 1000)
// Wait for completion // Wait for completion
const results = await thunk.result await thunk.result
return results
} }
``` ```
## Built in commands
Several thunk factories are provided for more complicated scenarios like updating lists:
- `follow(pubkey)`
- `unfollow(pubkey)`
- `mute(pubkey)`
- `unmute(pubkey)`
- `pin(tag)`
- `unpin(tag)`
+56
View File
@@ -0,0 +1,56 @@
# Router
The Welshman router can be used to enable the `outbox model` in your Nostr application. It handles relay selection for reading, writing, and discovering events while considering relay quality, user preferences, and network conditions.
## Overview
The router provides scenarios for common **Nostr** operations:
- Reading user profiles
- Publishing events
- Following threads
- Handling DMs
- Searching content
Each scenario considers:
- User's relay preferences (NIP-65)
- Event hints in tags
- Relay quality scores
- Fallback policies
- Connection status
## Basic Usage
```typescript
import {routerContext, addMaximalFallbacks, Router} from '@welshman/app'
// Set up global router options
routerContext.getDefaultRelays = () => ["wss://relay.damus.io/", "wss://nos.lol/"]
// Router can be used directly with options, or via a singleton with global options
const router = Router.get()
// Get relays for reading a profile
const readRelays = router.ForPubkey(pubkey).getUrls()
// Get relays for broadcasting events by the current user
const writeRelays = router.FromUser().getUrls()
// Get relays for a quote
const quoteRelays = Router.get()
.Quote(parentEvent, idOrAddress, relayHints)
.policy(addMaximalFallbacks)
.getUrls()
```
## Router Features
- Smart relay selection based on relay monitoring
- Quality scoring of relays
- Fallback strategies
- Handling of special relay types (.onion, local)
- NIP-65 support
The router is central to efficient nostr operations, ensuring events reach their intended audience while minimizing unnecessary network traffic.
-103
View File
@@ -1,103 +0,0 @@
# Router
The Router is the critical component to efficiently enable the `outbox model` in your Nostr application. It handles relay selection for reading, writing, and discovering events while considering relay quality, user preferences, and network conditions.
## Overview
The router provides scenarios for common **Nostr** operations:
- Reading user profiles
- Publishing events
- Following threads
- Handling DMs
- Searching content
Each scenario considers:
- User's relay preferences (NIP-65)
- Event hints in tags
- Relay quality scores
- Fallback policies
- Connection status
## Basic Usage
```typescript
import {ctx, setContext} from '@welshman/lib'
import {getDefaultAppContext} from '@welshman/app'
// Initialize router
setContext({
app: getDefaultAppContext()
})
// Use router scenarios
const router = ctx.app.router
// Get relays for reading a profile
const readRelays = router.ForPubkey(pubkey).getUrls()
// Get relays for publishing
const writeRelays = router.FromUser().getUrls()
// Get relays for a thread
const threadRelays = router.Replies(event).getUrls()
```
## Thread Navigation
```typescript
import {ctx} from '@welshman/lib'
import {createEvent, NOTE} from '@welshman/util'
import {publishThunk} from '@welshman/app'
const loadThread = async (event: TrustedEvent) => {
// Get relays for root event
const rootRelays = ctx.app.router
.EventRoots(event)
.getUrls()
// Get relays for replies
const replyRelays = ctx.app.router
.EventParents(event)
.getUrls()
// Get relays for mentions
const mentionRelays = ctx.app.router
.EventMentions(event)
.getUrls()
// Load from all relevant relays
await Promise.all([
subscribe({filters, relays: rootRelays}),
subscribe({filters, relays: replyRelays}),
subscribe({filters, relays: mentionRelays})
])
}
// Posting a reply
const reply = async (parent: TrustedEvent, content: string) => {
const event = createEvent(NOTE, {content})
// Get optimal relays for publishing
const relays = ctx.app.router
.PublishEvent(event)
// Skip .onion relays
.allowOnion(false)
// Allow up to 5 relays
.limit(5)
.getUrls()
return publishThunk({event, relays})
}
```
## Router Features
- Smart relay selection based on context
- Quality scoring of relays
- Fallback strategies
- Handling of special relay types (.onion, local)
- Automatic weight calculation
- Connection state awareness
- NIP-65 compliance
The router is central to efficient nostr operations, ensuring events reach their intended audience while minimizing unnecessary network traffic.
+5 -80
View File
@@ -1,16 +1,16 @@
# Session Management # Session Management
The session system provides a unified way to handle different authentication methods: The session system provides a unified way to handle different authentication methods:
- Secret Key NIP-01
- Nostr Extensions NIP-07
- Bunker URL NIP-46
- Amber or in-device NIP-55
while managing user state and encryption capabilities. - NIP-01 via Secret Key
- NIP-07 via Browser Extension
- NIP-46 via Bunker URL or Nostrconnect
- NIP-55 via Android Signer Application
## Overview ## Overview
Sessions are stored in local storage and can be: Sessions are stored in local storage and can be:
- Persisted across page reloads - Persisted across page reloads
- Used with multiple accounts - Used with multiple accounts
- Switched dynamically - Switched dynamically
@@ -18,39 +18,6 @@ Sessions are stored in local storage and can be:
## Basic Usage ## Basic Usage
```typescript
import {ctx, setContext} from '@welshman/lib'
import {
getDefaultNetContext,
getDefaultAppContext,
pubkey,
sessions,
session,
addSession,
getNip07
} from '@welshman/app'
// Set up app config
setContext({
net: getDefaultNetContext(),
app: getDefaultAppContext(),
})
// Log in via NIP-07 extension (browser wallet)
if (await getNip07()) {
addSession({
method: 'nip07',
pubkey: await getNip07().getPublicKey()
})
}
// Get current session
console.log(session.get()) // Current active session
console.log(pubkey.get()) // Current pubkey
```
## Multiple Sessions
```typescript ```typescript
import {sessions, pubkey, addSession, dropSession} from '@welshman/app' import {sessions, pubkey, addSession, dropSession} from '@welshman/app'
@@ -118,23 +85,6 @@ const encrypted = await signer.get().nip44.encrypt(
) )
``` ```
## Session Persistence
Sessions are automatically persisted to local storage. On page load:
```typescript
import {pubkey, sessions} from '@welshman/app'
// Sessions load automatically from local storage
console.log(sessions.get()) // All stored sessions
// the current active session
console.log(session.get())
// Last active pubkey is restored
console.log(pubkey.get())
```
## Session Types ## Session Types
```typescript ```typescript
@@ -159,28 +109,3 @@ type SessionNip01 = {
secret: string secret: string
} }
``` ```
## Error Handling
```typescript
import {tryCatch} from '@welshman/lib'
import {addSession, getNip07} from '@welshman/app'
const login = async () => {
const nip07 = await tryCatch(getNip07)
if (!nip07) {
throw new Error("No NIP-07 extension found")
}
const pubkey = await tryCatch(
() => nip07.getPublicKey()
)
if (!pubkey) {
throw new Error("Failed to get public key")
}
addSession({method: 'nip07', pubkey})
}
```
+16 -52
View File
@@ -1,67 +1,31 @@
# Storage # Storage
The storage system provides IndexedDB persistence for stores and repositories. The storage system provides IndexedDB persistence for stores and repositories.
It's critical to initialize this early in your application lifecycle to ensure data consistency.
Initialize this early in your application lifecycle to ensure data consistency.
```typescript ```typescript
import { import {initStorage, defaultStorageAdapters} from '@welshman/app'
initStorage,
storageAdapters,
throttled,
repository,
tracker,
relays,
handles,
freshness,
plaintext
} from '@welshman/app'
// Real world example from Coracle // Use default storage adapters, which track important metadata events,
const initializeStorage = async () => { // relays, handles, zappers, etc.
const ready = initStorage("coracle-db", 1, { await initStorage("my-db", 1, {
// Persist relay info ...defaultStorageAdapters,
relays: { custom: {
keyPath: "url", keyPath: "key",
store: throttled(3000, relays) init: async () => console.log(await getAll("custom")),
sync: () => {
// Set up a listener for changes, using bulkPut to save records.
// Return an unsubscribe function for cleanup
}, },
},
// Persist NIP-05 handles })
handles: {
keyPath: "nip05",
store: throttled(3000, handles)
},
// Track data freshness
freshness: storageAdapters.fromObjectStore(
freshness,
{throttle: 3000}
),
// Store decrypted content
plaintext: storageAdapters.fromObjectStore(
plaintext,
{throttle: 3000}
),
// Store events and their sources
events: storageAdapters.fromRepositoryAndTracker(
repository,
tracker,
{throttle: 3000}
)
})
// Wait for storage to be ready
await ready
// App can now start loading data
}
``` ```
The storage system: The storage system:
- Persists data across page reloads - Persists data across page reloads
- Throttles writes for performance - Throttles writes for performance
- Handles store migrations
- Syncs bidirectionally - Syncs bidirectionally
- Supports custom adapters - Supports custom adapters
-108
View File
@@ -1,108 +0,0 @@
# Stores and Loaders
The `@welshman/app` package provides a powerful system of collection-based reactive stores and loader utilities.
These utilities follow a consistent pattern for working with various types of Nostr data, making it easy to:
1. Query data from the repository
2. Transform it into application-specific structures
3. Access it reactively in your UI
4. Trigger network loading when needed
## Core Concept
Each collection-based module exports a similar set of utilities:
```typescript
// Common pattern across collection-based modules
export const {
// Main collection store (derived from repository)
store: follows,
// Indexed map for efficient lookup
indexStore: followsByPubkey,
// Function to get a reactive store for a specific item
deriveItem: deriveFollows,
// Function to trigger loading an item from the network
loadItem: loadFollows
} = collection({
name: "collection-name",
store: baseStore,
getKey: item => item.keyProperty,
load: async (key) => { /* Loading logic */ }
})
```
## Available Collections
| Collection | Key | Kind | Description |
|------------|-----|------|-------------|
| `follows` | pubkey | 3 | User follow lists |
| `mutes` | pubkey | 10000 | User mute lists |
| `pins` | pubkey | 10001 | User pinned items |
| `profiles` | pubkey | 0 | User profile metadata |
| `relaySelections` | pubkey | 10002 | User relay preferences |
| `inboxRelaySelections` | pubkey | 10005 | User inbox relay settings |
| `zappers` | lnurl | - | Lightning zapper metadata |
| `handles` | nip05 | - | NIP-05 identifier metadata |
## Usage Examples
### Loading and Accessing Data
```typescript
import { loadProfile, deriveProfile, profilesByPubkey } from '@welshman/app'
// Trigger loading a profile from the network
await loadProfile('pubkey123')
// Get a reactive store for a specific profile
const profile = deriveProfile('pubkey123')
// Access all profiles by pubkey
const allProfiles = profilesByPubkey.get()
const specificProfile = allProfiles.get('pubkey123')
```
### User-Specific Collections
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'
// These are derived stores that automatically:
// 1. Watch for changes to the current user's pubkey
// 2. Load the appropriate data when the user changes
// 3. Provide the data reactively
userProfile.subscribe(profile => {
// Current user's profile data
})
userFollows.subscribe(follows => {
// Current user's follow list
})
```
### Web of Trust Utilities
The `wot.ts` module provides additional utilities for analyzing the social graph:
```typescript
import { getFollows, getFollowers, getNetwork, getWotScore } from '@welshman/app'
// Get users followed by a pubkey
const followedUsers = getFollows('pubkey123')
// Get users following a pubkey
const followers = getFollowers('pubkey123')
// Get extended network (follows-of-follows)
const network = getNetwork('pubkey123')
// Calculate trust score between users
const score = getWotScore('userPubkey', 'targetPubkey')
```
-153
View File
@@ -1,153 +0,0 @@
# Subscription System
The subscription system extends Nostr's base subscription model with intelligent caching, repository integration, and configurable behaviors.
## Key Concepts
- **Local Repository**: Events are automatically cached and tracked
- **Cache Intelligence**: Smart decisions about when to use cached data
- **Relay Integration**: Works with the router for optimal relay selection
- **Configurable Behavior**: Control caching and timeouts
## Configuration Options
```typescript
type SubscribeRequest = {
// Required
filters: Filter[] // What to query
// Behavior Control
closeOnEose?: boolean // Auto-close and use cache
timeout?: number // Max time to wait
authTimeout?: number // Time for auth negotiation
requestDelay?: number // Delay between batched requests
// Optional
relays?: string[] // Specific relays to query
// Event Handlers
onEvent?: (event: TrustedEvent) => void
onEose?: (url: string) => void
onComplete?: () => void
}
```
## Cache Behavior Control
The `closeOnEose` parameter is crucial for controlling caching behavior:
```typescript
// WITH closeOnEose: true (default for load())
// - Checks cache first
// - Returns cached results if complete
// - Closes after EOSE
// - Good for: Known events, historical data
const loadKnownEvent = async (id: string) => {
const events = await load({
filters: [{ids: [id]}],
closeOnEose: true
})
return events[0]
}
// WITH closeOnEose: false
// - Always queries relays
// - Stays open for updates
// - Ignores cache completeness
// - Good for: Replaceable events, live data
const watchProfile = (pubkey: string) => {
return subscribe({
filters: [{
kinds: [PROFILE],
authors: [pubkey]
}],
closeOnEose: false // Force relay query
})
}
```
## Common Usage Patterns
### One-time Queries
```typescript
// Load specific event
const event = await load({
filters: [{ids: [eventId]}]
// closeOnEose: true by default
})
// Load latest profile
const profile = await load({
filters: [{
kinds: [PROFILE],
authors: [pubkey],
limit: 1
}],
closeOnEose: false // Get latest from network
})
```
### Live Subscriptions
```typescript
// Watch for updates
const sub = subscribe({
filters: [{
kinds: [NOTE],
since: now() // Only new events
}],
closeOnEose: false, // Stay open
})
sub.on('event', (url, event) => {
// Handle live events
})
```
### Smart Caching
```typescript
// Profile loader with refresh control
const loadProfile = async (pubkey: string, options = {}) => {
const {
forceRefresh = false, // Skip cache
timeout = 3000, // Max wait time
relays = [] // Optional relay override
} = options
// Get optimal relays if not specified
const targetRelays = relays.length > 0
? relays
: ctx.app.router.ForPubkey(pubkey).getUrls()
return new Promise((resolve) => {
const sub = subscribe({
filters: [{
kinds: [PROFILE],
authors: [pubkey],
limit: 1
}],
relays: targetRelays,
closeOnEose: !forceRefresh, // Control cache behavior
timeout,
onEvent: (url, event) => {
resolve(event)
sub.close()
},
onComplete: () => resolve(null)
})
})
}
```
## Repository Integration
All events from subscriptions are automatically:
- Saved to the repository
- Tracked to their source relay
- Checked against deletion status
The repository serves as an intelligent cache layer, making subsequent queries for the same data faster.
+5 -28
View File
@@ -1,12 +1,13 @@
# Tag Utilities # Tag Utilities
The tag utilities provide helper functions for creating properly formatted Nostr event tags with correct relay hints and metadata. The tag utilities provide helper functions for creating properly formatted Nostr event tags with correct relay hints and metadata.
These are especially useful when creating events that reference other events or users. These are especially useful when creating events that reference other events or users.
## Tag Creators ## Tag Creators
### Pubkey Tags
### User Tags
```typescript ```typescript
import {tagPubkey} from '@welshman/app' import {tagPubkey} from '@welshman/app'
@@ -23,7 +24,7 @@ import {
tagEvent, // Basic event reference tagEvent, // Basic event reference
tagEventForQuote, // For quoting events tagEventForQuote, // For quoting events
tagEventForReply, // For reply threads tagEventForReply, // For reply threads
tagEventForComment, // For NIP-23 comments tagEventForComment, // For NIP-22 comments
tagEventForReaction // For reactions tagEventForReaction // For reactions
} from '@welshman/app' } from '@welshman/app'
@@ -36,34 +37,10 @@ const createReply = async (parent: TrustedEvent, content: string) => {
// - Relay hints // - Relay hints
const tags = tagEventForReply(parent) const tags = tagEventForReply(parent)
const event = await signer.get().sign(
createEvent(NOTE, {
content,
tags,
created_at: now()
})
)
return publishThunk({ return publishThunk({
event,
// Use relay hints from tags // Use relay hints from tags
relays: ctx.app.router.PublishEvent(event).getUrls() relays: Router.get().PublishEvent(event).getUrls()
event: await signer.get().sign(createEvent(NOTE, {content, tags})),
}) })
} }
``` ```
All tag creators:
- Add appropriate relay hints using the router
- Handle replaceable/parameterized events
- Follow adequate NIP-10/NIP-22 conventions for threading
- Include metadata like usernames
- Deduplicate references
- Preserve tag order
The tagging system is crucial for:
- Thread construction
- Event reactions
- User mentions
- Zap splits
Tag utilities ensure consistent and correct tag creation across the application while integrating with the router for relay hints.
-39
View File
@@ -1,39 +0,0 @@
# Topics
The topics system provides a reactive way to track and count hashtags across all events in the repository. It automatically updates as new events arrive or are removed.
```typescript
import {topics} from '@welshman/app'
// In a Svelte component
<script>
// Reactive list of all topics with counts
$: topicList = $topics
.sort((a, b) => b.count - a.count)
.slice(0, 20)
</script>
<div class="topics">
{#each topicList as {name, count}}
<a href="/t/{name}">
#{name}
<span class="count">({count})</span>
</a>
{/each}
</div>
```
The store:
- Updates automatically with new events
- Maintains topic counts
- Is throttled to prevent excess updates
- Is case-insensitive
- Integrates with the repository
Think of it as a live tag cloud that stays in sync with your local event cache.
This is commonly used for:
- Tag clouds
- Topic discovery
- Content organization
- Trending topics
+2 -2
View File
@@ -1,6 +1,6 @@
# Web of Trust (WOT) Module # Web of Trust (WOT)
The `wot.ts` module provides utilities for implementing a Web of Trust system within Nostr applications. This system analyzes social connections (follows and mutes) to build a reputation graph that can be used for content filtering, user scoring, and discovery. Welshman provides utilities for implementing a Web of Trust system within Nostr applications. This system analyzes social connections (follows and mutes) to build a reputation graph that can be used for content filtering, user scoring, and discovery.
## Core Concepts ## Core Concepts
+2
View File
@@ -1,5 +1,7 @@
# @welshman/content # @welshman/content
[![version](https://badgen.net/npm/v/@welshman/content)](https://npmjs.com/package/@welshman/content)
`@welshman/content` is a comprehensive content processing library designed specifically for Nostr applications. `@welshman/content` is a comprehensive content processing library designed specifically for Nostr applications.
It provides a robust system for parsing, processing, and rendering Nostr content while handling various special formats and entities common in the Nostr ecosystem. It provides a robust system for parsing, processing, and rendering Nostr content while handling various special formats and entities common in the Nostr ecosystem.
+86
View File
@@ -1,5 +1,7 @@
# @welshman/dvm # @welshman/dvm
[![version](https://badgen.net/npm/v/@welshman/dvm)](https://npmjs.com/package/@welshman/dvm)
`@welshman/dvm` is a comprehensive package for building and interacting with Data Vending Machines (DVMs) in the Nostr ecosystem. It provides both server-side DVM implementation capabilities and client-side request handling. `@welshman/dvm` is a comprehensive package for building and interacting with Data Vending Machines (DVMs) in the Nostr ecosystem. It provides both server-side DVM implementation capabilities and client-side request handling.
## What is a DVM? ## What is a DVM?
@@ -10,6 +12,90 @@ A Data Vending Machine (DVM) is a Nostr service that:
- Responds with new events containing processed data - Responds with new events containing processed data
- Optionally provides progress updates during processing - Optionally provides progress updates during processing
# Request example
```typescript
import {makeDvmRequest, DVMEvent} from '@welshman/dvm'
const req = makeDvmRequest({
// Create and sign a dvm request event, including any desired tags
event: createAndSign({kind: 5300}),
// Publish and subscribe to these relays
relays: ['wss://relay.damus.io', 'wss://dvms.f7z.io'],
// Timeout defaults to 30 seconds
timeout: 30_000,
// Auto close on first result (defaults to true)
autoClose: true,
// Listen for and emit `progress` events
reportProgress: true,
})
// Listen for progress, result, etc
req.emitter.on(DVMEvent.Progress, (url, event) => console.log(event))
req.emitter.on(DVMEvent.Result, (url, event) => console.log(event))
```
# Handler example
```typescript
import {bytesToHex} from '@noble/hashes/utils'
import {generateSecretKey} from 'nostr-tools'
import {createEvent} from '@welshman/util'
import {subscribe} from '@welshman/net'
import {DVM} from '@welshman/dvm'
// Your DVM's private key. Store this somewhere safe
// const hexPrivateKey = bytesToHex(generateSecretKey())
const hexPrivateKey = '9cd387a3aa0c1abc2ef517c8402f29c069b4174e02a426491aec7566501bee67'
// Tags that we'll return as content discovery suggestions
const tags = []
// Populate the tags with music by Ainsley Costello
const sub = subscribe({
timeout: 30_000,
relays: ["wss://relay.wavlake.com"],
filters: [{
kinds: [31337],
'#p': ['8806372af51515bf4aef807291b96487ea1826c966a5596bca86697b5d8b23bc'],
}],
})
// Push event ids to our suggestions
sub.on('event', (url, e) => tags.push(["e", e.id, url]))
const dvm = new DVM({
// The private key used to sign events
sk: hexPrivateKey,
// Relays that the DVM will listen on
relays: ['wss://relay.damus.io', 'wss://dvms.f7z.io'],
// Only listen to requests tagging our dvm
requireMention: true,
// Expire results after 1 hour (the default)
expireAfter: 60 * 60,
// Handlers for various kinds
handlers: {
5300: dvm => ({
handleEvent: async function* (event) {
// DVM responses are stringified into the content
const content = JSON.stringify(tags)
// Yield our response. Kind 7000 can be used for partial results too
yield createEvent(event.kind + 1000, {content})
},
}),
}
})
// Enable logging
dvm.logEvents = true
// When you're ready
dvm.start()
// When you're done
dvm.stop()
```
## Installation ## Installation
+2
View File
@@ -1,5 +1,7 @@
# @welshman/editor # @welshman/editor
[![version](https://badgen.net/npm/v/@welshman/editor)](https://npmjs.com/package/@welshman/editor)
`@welshman/editor` provides a comprehensive Nostr-ready text editor, built on top of [nostr-editor](https://github.com/cesardeazevedo/nostr-editor). `@welshman/editor` provides a comprehensive Nostr-ready text editor, built on top of [nostr-editor](https://github.com/cesardeazevedo/nostr-editor).
This package powers the editors of [Coracle](https://coracle.social) and [Flotilla](https://flotilla.social). This package powers the editors of [Coracle](https://coracle.social) and [Flotilla](https://flotilla.social).
+35 -2
View File
@@ -1,7 +1,10 @@
# @welshman/feeds # @welshman/feeds
A powerful package for building and executing dynamic Nostr feeds. [![version](https://badgen.net/npm/v/@welshman/feeds)](https://npmjs.com/package/@welshman/feeds)
It provides a declarative way to define complex feed compositions using set operations (union, intersection, difference) and various filtering mechanisms.
A package for building and executing dynamic Nostr feeds which provides a declarative way to define complex feed compositions using set operations (union, intersection, difference) and various filtering mechanisms.
Read the spec on [the NIPs repository](https://github.com/nostr-protocol/nips/blob/af4329986cae9b0ef625a01c8cefd5e802ca6895/fe.md).
## What's Included ## What's Included
@@ -11,6 +14,36 @@ It provides a declarative way to define complex feed compositions using set oper
- **Feed Utils** - Helper functions for creating and manipulating feeds - **Feed Utils** - Helper functions for creating and manipulating feeds
- **Feed Types** - Supports authors, kinds, tags, DVMs, lists, WOT, and more - **Feed Types** - Supports authors, kinds, tags, DVMs, lists, WOT, and more
## Quick Example
```javascript
// Define a feed using set operations
const feed = intersectionFeed(
unionFeed(
dvmFeed({
kind: 5300,
pubkey: '19b78ccfa7c5e31e6bacbb3f2a1703f64b62017702e584440bf29a7e16263e8c',
}),
listFeed("10003:19ba654f26afd4930fd3d51baf4e26f1413b7aeec7190cd6c0cdf4d2f14cec6b:"),
)
wotFeed({min: 0.1}),
scopeFeed("global"),
)
// 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)
```
## Installation ## Installation
+8 -8
View File
@@ -13,23 +13,23 @@ npm i @welshman/relay
# Networking and relay management # Networking and relay management
npm i @welshman/net npm i @welshman/net
# Content parsing and rendering
npm i @welshman/content
# Event signing and encryption # Event signing and encryption
npm i @welshman/signer npm i @welshman/signer
# Dynamic feed compilation # Dynamic feed compilation
npm i @welshman/feeds npm i @welshman/feeds
# Content parsing and rendering
npm i @welshman/content
# Rich text editor component
npm i @welshman/editor
# Svelte stores and state management # Svelte stores and state management
npm i @welshman/store npm i @welshman/store
# Full application framework (requires Svelte) # Complete application framework
npm i @welshman/app npm i @welshman/app
# Rich text editor component (requires Svelte)
npm i @welshman/editor
``` ```
Choose packages based on your needs: Choose packages based on your needs:
@@ -49,4 +49,4 @@ Choose packages based on your needs:
npm i @welshman/content npm i @welshman/content
``` ```
Each package is independent but integrates seamlessly. All packages are framework-agnostic, but work best with Svelte. Each package is independent but integrates seamlessly. All packages are framework-agnostic, but work best with Svelte due to svelte stores being a common pattern for state management.
+1 -1
View File
@@ -1,6 +1,6 @@
# Deferred Promises # Deferred Promises
The Deferred module provides utilities for creating promises with exposed resolve/reject functions and typed error handling. This is particularly useful for managing asynchronous operations where you need external control over promise resolution. The `Deferred` module provides utilities for creating promises with exposed resolve/reject functions and typed error handling. This is particularly useful for managing asynchronous operations where you need external control over promise resolution.
## Types ## Types
+2
View File
@@ -1,5 +1,7 @@
# @welshman/lib # @welshman/lib
[![version](https://badgen.net/npm/v/@welshman/lib)](https://npmjs.com/package/@welshman/lib)
A lightweight TypeScript utility library with zero dependencies, providing essential tools for modern JavaScript development. A lightweight TypeScript utility library with zero dependencies, providing essential tools for modern JavaScript development.
## What's Included ## What's Included
+1 -1
View File
@@ -1,6 +1,6 @@
# LRU Cache # LRU Cache
The LRU (Least Recently Used) Cache implementation provides efficient caching with automatic eviction of least recently used items when the cache reaches its maximum size. A LRU (Least Recently Used) Cache implementation provides efficient caching with automatic eviction of least recently used items when the cache reaches its maximum size.
## Basic Usage ## Basic Usage
+69
View File
@@ -0,0 +1,69 @@
# URL Normalization
A `normalizeUrl` function borrowed from [sindresorhus/normalize-url](https://github.com/sindresorhus/normalize-url) is included for convenience.
## Basic Usage
```typescript
normalizeUrl('example')
//=> 'http://example'
normalizeUrl('sindresorhus.com/about.html#contact', {stripHash: true});
//=> 'http://sindresorhus.com/about.html'
```
## API Reference
### Configuration
```typescript
export type Options = {
// Default protocol to prepend
readonly defaultProtocol?: 'https' | 'http'
// Prepends `defaultProtocol` to the URL if it's protocol-relative.
readonly normalizeProtocol?: boolean
// Normalizes HTTPS URLs to HTTP.
readonly forceHttp?: boolean
// Normalizes HTTP URLs to HTTPS.
readonly forceHttps?: boolean
// Strip the [authentication](https://en.wikipedia.org/wiki/Basic_access_authentication) part of a URL.
readonly stripAuthentication?: boolean
// Removes hash from the URL.
readonly stripHash?: boolean
// Remove the protocol from the URL: `http://sindresorhus.com` → `sindresorhus.com`.
readonly stripProtocol?: boolean
// Strip the [text fragment](https://web.dev/text-fragments/) part of the URL
readonly stripTextFragment?: boolean
// Removes `www.` from the URL.
readonly stripWWW?: boolean
// Removes query parameters that matches any of the provided strings or regexes.
readonly removeQueryParameters?: ReadonlyArray<RegExp | string> | boolean
// Keeps only query parameters that matches any of the provided strings or regexes.
readonly keepQueryParameters?: ReadonlyArray<RegExp | string>
// Removes trailing slash.
readonly removeTrailingSlash?: boolean
// Remove a sole `/` pathname in the output. This option is independent of `removeTrailingSlash`.
readonly removeSingleSlash?: boolean
// Removes the default directory index file from path that matches any of the provided strings or regexes.
readonly removeDirectoryIndex?: boolean | ReadonlyArray<RegExp | string>
// Removes an explicit port number from the URL.
readonly removeExplicitPort?: boolean
// Sorts the query parameters alphabetically by key.
readonly sortQueryParameters?: boolean
}
```
+59
View File
@@ -0,0 +1,59 @@
# Task Queue
The `TaskQueue` class provides a simple queue processing system with batched operations and throttling. It's designed to handle asynchronous operations efficiently while maintaining control over processing rates and resource usage.
## Basic Usage
```typescript
// Create queue for processing messages
const queue = new TaskQueue<number>({
chunkSize: 10,
processItem: (n: number) => console.log(n)
})
// Add and remove items to/from queue
worker.push(10)
worker.push(21)
worker.remove(10)
worker.push(57)
```
## Control Methods
Control message processing:
```typescript
// Pause processing
worker.stop()
// Resume processing
worker.start()
// Clear queue
worker.clear()
```
## API Reference
### Constructor
```typescript
class TaskQueue<Item> {
constructor(readonly options: TaskQueueOptions<Item>) {}
}
```
The TaskQueue class accepts messages of type `Item` and processes them.
### Configuration
```typescript
type TaskQueueOptions<Item> = {
// How many items to process at a time
batchSize: number
// A function for processing items. Any promises returned will be awaited
processItem: (item: Item) => unknown
}
```
+367 -174
View File
@@ -1,256 +1,449 @@
# Utility Functions # Utility Functions
The `Tools.ts` module provides a comprehensive collection of utility functions for common programming tasks. It includes functions for array manipulation, object handling, type checking, math operations, and more. The `Tools` module provides a comprehensive collection of utility functions for common programming tasks. It includes functions for array manipulation, object handling, type checking, math operations, and more.
## Types ## Basic functional programming utilities
```typescript ```typescript
type Nil = null | undefined // Function that does nothing and returns undefined
type Maybe<T> = T | undefined export declare const noop: (...args: unknown[]) => undefined;
type Obj<T = any> = Record<string, T>
// Returns the input value unchanged
export declare const identity: <T>(x: T, ...args: unknown[]) => T;
// Creates a function that always returns the same value
export declare const always: <T>(x: T, ...args: unknown[]) => () => T;
// Returns the logical NOT of a value
export declare const not: (x: any, ...args: unknown[]) => boolean;
// Deep equality comparison
export declare const equals: (a: any, b: any) => boolean;
``` ```
## Categories ## Numbers
### Type Checking & Basic Operations
```typescript ```typescript
// Check if value is null or undefined // Converts string or number to number
isNil(x: any): boolean export declare const ensureNumber: (x: number | string) => number;
// Execute function if value exists // Converts a `number | undefined` to a number, defaulting to 0
ifLet<T>(x: T | undefined, f: (x: T) => void) export declare const num: (x: number | undefined) => number;
// Return value unchanged // Adds two numbers, handling undefined values
identity<T>(x: T): T export declare const add: (x: number | undefined, y: number | undefined) => number;
// Create function that always returns same value // Subtracts two numbers, handling undefined values
always<T>(x: T): () => T export declare const sub: (x: number | undefined, y: number | undefined) => number;
// Logical NOT // Multiplies two numbers, handling undefined values
not(x: any): boolean export declare const mul: (x: number | undefined, y: number | undefined) => number;
// Create complement of a predicate function // Divides two numbers, handling undefined values
complement<T extends unknown[]>(f: (...args: T) => any): (...args: T) => boolean export declare const div: (x: number | undefined, y: number) => number;
// Increments a number by 1, handling undefined values
export declare const inc: (x: number | undefined) => number;
// Decrements a number by 1, handling undefined values
export declare const dec: (x: number | undefined) => number;
// Less than comparison, handling undefined values
export declare const lt: (x: number | undefined, y: number | undefined) => boolean;
// Less than or equal comparison, handling undefined values
export declare const lte: (x: number | undefined, y: number | undefined) => boolean;
// Greater than comparison, handling undefined values
export declare const gt: (x: number | undefined, y: number | undefined) => boolean;
// Greater than or equal comparison, handling undefined values
export declare const gte: (x: number | undefined, y: number | undefined) => boolean;
// Returns maximum value in array, handling undefined values
export declare const max: (xs: (number | undefined)[]) => number;
// Returns minimum value in array, handling undefined values
export declare const min: (xs: (number | undefined)[]) => number;
// Returns sum of array values, handling undefined values
export declare const sum: (xs: (number | undefined)[]) => number;
// Returns average of array values, handling undefined values
export declare const avg: (xs: (number | undefined)[]) => number;
// Checks if a number is between two values (exclusive)
export declare const between: ([low, high]: [number, number], n: number) => boolean;
// Checks if a number is between two values (inclusive)
export declare const within: ([low, high]: [number, number], n: number) => boolean;
// Constrains number between min and max values
export declare const clamp: ([min, max]: [number, number], n: number) => number;
// Round a number to the nearest float precision
export declare const round: (precision: number, x: number) => number;
``` ```
### Array Operations ## Timestamps
```typescript ```typescript
// Get first element // One minute in seconds
first<T>(xs: T[]): T | undefined export declare const MINUTE = 60;
// Get first element of first array // One hour in seconds
ffirst<T>(xs: T[][]): T | undefined export declare const HOUR: number;
// Get last element // One day in seconds
last<T>(xs: T[]): T | undefined export declare const DAY: number;
// Drop first n elements // One week in seconds
drop<T>(n: number, xs: T[]): T[] export declare const WEEK: number;
// Take first n elements // One month in seconds (approximate)
take<T>(n: number, xs: T[]): T[] export declare const MONTH: number;
// Remove duplicates // One quarter in seconds (approximate)
uniq<T>(xs: T[]): T[] export declare const QUARTER: number;
// Remove duplicates by key function // One year in seconds (approximate)
uniqBy<T>(f: (x: T) => any, xs: T[]): T[] export declare const YEAR: number;
// Create array of n items using generator function // Multiplies time unit by count
initArray<T>(n: number, f: () => T): T[] export declare const int: (unit: number, count?: number) => number;
// Split array into chunks // Returns current Unix timestamp in seconds
chunk<T>(chunkLength: number, xs: T[]): T[][] export declare const now: () => number;
// Split array into n chunks // Returns Unix timestamp from specified time ago
chunks<T>(n: number, xs: T[]): T[][] export declare const ago: (unit: number, count?: number) => number;
// Converts seconds to milliseconds
export declare const ms: (seconds: number) => number;
``` ```
### Object Operations ## Sequences
```typescript ```typescript
// Create object excluding specified keys // Returns the first element of an array
omit<T extends Obj>(ks: string[], x: T): T export declare const first: <T>(xs: Iterable<T>, ...args: unknown[]) => T | undefined;
// Create object excluding entries with specified values // Returns the first element of the first array in a nested array
omitVals<T extends Obj>(xs: any[], x: T): T export declare const ffirst: <T>(xs: Iterable<Iterable<T>>, ...args: unknown[]) => T | undefined;
// Create object with only specified keys // Returns the last element of an array
pick<T extends Obj>(ks: string[], x: T): T export declare const last: <T>(xs: Iterable<T>, ...args: unknown[]) => T;
// Transform object keys // Returns array with first n elements removed
mapKeys<T extends Obj>(f: (v: string) => string, x: T): T export declare const drop: <T>(n: number, xs: Iterable<T>) => T[];
// Transform object values // Returns first n elements of array
mapVals<V, U>(f: (v: V) => U, x: Record<string, V>): Record<string, U> export declare const take: <T>(n: number, xs: Iterable<T>) => T[];
// Merge objects (left priority) // Concatenates multiple arrays, filtering out null/undefined
mergeLeft<T extends Obj>(a: T, b: T): T export declare const concat: <T>(...xs: T[][]) => T[];
// Merge objects (right priority) // Appends element to array
mergeRight<T extends Obj>(a: T, b: T): T export declare const append: <T>(x: T, xs: T[]) => T[];
// Deep merge objects // Creates union of two arrays
deepMergeLeft(a: Obj, b: Obj): Obj export declare const union: <T>(a: T[], b: T[]) => T[];
deepMergeRight(a: Obj, b: Obj): Obj
// Returns elements common to both arrays
export declare const intersection: <T>(a: T[], b: T[]) => T[];
// Returns elements in first array not present in second
export declare const difference: <T>(a: T[], b: T[]) => T[];
// Removes all instances of an element from array
export declare const remove: <T>(a: T, xs: T[]) => T[];
// Returns elements from second array not present in first
export declare const without: <T>(a: T[], b: T[]) => T[];
// Toggles presence of element in array
export declare const toggle: <T>(x: T, xs: T[]) => T[];
// Generates sequence of numbers from a to b
export declare function range(a: number, b: number, step?: number): Generator<number, void, unknown>;
// Yields indexed items
export declare function enumerate<T>(items: T[]): Generator<[number, T], void, unknown>;
// Returns a function that gets property value from object
export declare const pluck: <T>(k: string, xs: Record<string, unknown>[]) => T[];
// Creates object from array of key-value pairs
export declare const fromPairs: <T>(pairs: [k?: string, v?: T, ...args: unknown[]][]) => Record<string, T>;
// Flattens array of arrays into single array
export declare const flatten: <T>(xs: T[][]) => T[];
// Splits array into two arrays based on predicate
export declare const partition: <T>(f: (x: T) => boolean, xs: T[]) => T[][];
// Returns array with duplicate elements removed
export declare const uniq: <T>(xs: T[]) => T[];
// Returns array with elements unique by key function
export declare const uniqBy: <T>(f: (x: T) => any, xs: T[]) => T[];
// Returns sorted copy of array
export declare const sort: <T>(xs: T[]) => T[];
// Returns array sorted by key function
export declare const sortBy: <T>(f: (x: T) => any, xs: T[]) => T[];
// Groups array elements by key function
export declare const groupBy: <T, K>(f: (x: T) => K, xs: T[]) => Map<K, T[]>;
// Creates map from array using key function
export declare const indexBy: <T, K>(f: (x: T) => K, xs: T[]) => Map<K, T>;
// Creates array of specified length using generator function
export declare const initArray: <T>(n: number, f: () => T) => T[];
// Splits array into chunks of specified length
export declare const chunk: <T>(chunkLength: number, xs: T[]) => T[][];
// Splits array into specified number of chunks
export declare const chunks: <T>(n: number, xs: T[]) => T[][];
// Splits array into two parts at index
export declare const splitAt: <T>(n: number, xs: T[]) => T[][];
// Inserts element into array at index
export declare const insert: <T>(n: number, x: T, xs: T[]) => T[];
// Returns random element from array
export declare const choice: <T>(xs: T[]) => T;
// Returns shuffled copy of iterable
export declare const shuffle: <T>(xs: Iterable<T>) => T[];
// Returns n random elements from array
export declare const sample: <T>(n: number, xs: T[]) => T[];
// Checks if value is iterable
export declare const isIterable: (x: any) => boolean;
// Ensures value is iterable by wrapping in array if needed
export declare const toIterable: (x: any) => any;
// Ensures value is array by wrapping if needed
export declare const ensurePlural: <T>(x: T | T[]) => T[];
``` ```
### Number Operations ## Objects
```typescript ```typescript
// Convert Maybe<number> to number // Checks if value is a plain object
num(x: Maybe<number>): number export declare const isPojo: (obj: any) => boolean;
// Basic arithmetic with Maybe<number> // Creates new object with only specified keys
add(x: Maybe<number>, y: Maybe<number>): number export declare const pick: <T extends Obj>(ks: string[], x: T) => T;
sub(x: Maybe<number>, y: Maybe<number>): number
mul(x: Maybe<number>, y: Maybe<number>): number
div(x: Maybe<number>, y: number): number
// Increment/Decrement // Creates new object with specified keys removed
inc(x: Maybe<number>): number export declare const omit: <T extends Obj>(ks: string[], x: T) => T;
dec(x: Maybe<number>): number
// Comparisons // Creates new object excluding entries with specified values
lt(x: Maybe<number>, y: Maybe<number>): boolean export declare const omitVals: <T extends Obj>(xs: any[], x: T) => T;
lte(x: Maybe<number>, y: Maybe<number>): boolean
gt(x: Maybe<number>, y: Maybe<number>): boolean
gte(x: Maybe<number>, y: Maybe<number>): boolean
// Array number operations // Filters object values based on predicate
max(xs: Maybe<number>[]): number export declare const filterVals: <T extends Record<string, any>>(f: (v: any) => boolean, x: T) => T;
min(xs: Maybe<number>[]): number
sum(xs: Maybe<number>[]): number // Creates new object with transformed keys
avg(xs: Maybe<number>[]): number export declare const mapKeys: <T extends Obj>(f: (v: string) => string, x: T) => T;
// Creates new object with transformed values
export declare const mapVals: <V, U>(f: (v: V) => U, x: Record<string, V>) => Record<string, U>;
// Merges two objects, with left object taking precedence
export declare const mergeLeft: <T extends Obj>(a: T, b: T) => T;
// Merges two objects, with right object taking precedence
export declare const mergeRight: <T extends Obj>(a: T, b: T) => T;
// Deep merge two objects, prioritizing the first argument.
export declare const deepMergeLeft: (a: Obj, b: Obj) => Obj<any>;
// Deep merge two objects, prioritizing the second argument.
export declare const deepMergeRight: (a: Obj, b: Obj) => Obj<any>;
// Switches on key in object, with default fallback
export declare const switcher: <T>(k: string, m: Record<string, T>) => T;
``` ```
### String Operations ## Combinators
```typescript ```typescript
// Truncate string with ellipsis // Returns a function that returns the boolean negation of the given function
ellipsize(s: string, l: number, suffix = "..."): string export declare const complement: <T extends unknown[]>(f: (...args: T) => any) => (...args: T) => boolean;
// URL operations // Safely executes function and handles errors
stripProtocol(url: string): string export declare const tryCatch: <T>(f: () => T, onError?: (e: Error) => void) => T | undefined;
displayUrl(url: string): string
displayDomain(url: string): string
// Bech32 encoding/decoding // Creates function that only executes once
hexToBech32(prefix: string, hex: string): string export declare const once: (f: (...args: any) => void) => (...args: any) => void;
bech32ToHex(b32: string): string
// Calls a function
export declare const call: <T>(f: () => T, ...args: unknown[]) => T;
// Memoizes function results based on arguments
export declare const memoize: <T>(f: (...args: any[]) => T) => (...args: any[]) => T;
// Executes a function if the value is defined
export declare const ifLet: <T>(x: T | undefined, f: (x: T) => void) => void;
``` ```
### Collection Operations ## Randomness
```typescript ```typescript
// Create union of arrays // Generates random integer between min and max (inclusive)
union<T>(a: T[], b: T[]): T[] export declare const randomInt: (min?: number, max?: number) => number;
// Get intersection of arrays // Generates random string ID
intersection<T>(a: T[], b: T[]): T[] export declare const randomId: () => string;
// Get difference of arrays
difference<T>(a: T[], b: T[]): T[]
// Remove element from array
remove<T>(a: T, xs: T[]): T[]
// Filter array by another array
without<T>(a: T[], b: T[]): T[]
// Toggle element in array
toggle<T>(x: T, xs: T[]): T[]
// Group array by key function
groupBy<T, K>(f: (x: T) => K, xs: T[]): Map<K, T[]>
// Create map from array
indexBy<T, K>(f: (x: T) => K, xs: T[]): Map<K, T>
``` ```
### Time Constants ## Async
```typescript ```typescript
const MINUTE = 60 // Creates a promise that resolves after specified time
const HOUR = 60 * MINUTE export declare const sleep: (t: number) => Promise<unknown>;
const DAY = 24 * HOUR
const WEEK = 7 * DAY
const MONTH = 30 * DAY
const QUARTER = 90 * DAY
const YEAR = 365 * DAY
// Get current timestamp in seconds // Creates a microtask that yields to other tasks in the event loop
now(): number export declare const yieldThread: () => any;
// Get timestamp from ago in seconds // Creates throttled version of function
ago(unit: number, count = 1): number export declare const throttle: <F extends (...args: any[]) => any>(ms: number, f: F) => F | ((...thisArgs: Parameters<F>) => void);
// Convert seconds to milliseconds // Creates throttled function that returns cached value
ms(seconds: number): number export declare const throttleWithValue: <T>(ms: number, f: () => T) => () => T;
// Creates batching function that collects items
export declare const batch: <T>(t: number, f: (xs: T[]) => void) => (x: T) => void;
// Creates batching function that returns results
export declare const batcher: <T, U>(t: number, execute: (request: T[]) => U[] | Promise<U[]>) => (request: T) => Promise<U>;
``` ```
### Function Utilities ## URLs
```typescript ```typescript
// Create function that executes once // Removes protocol (http://, https://, etc) from URL
once(f: (...args: any) => void): (...args: any) => void export declare const stripProtocol: (url: string) => string;
// Memoize function results // Formats URL for display by removing protocol, www, and trailing slash
memoize<T>(f: (...args: any[]) => T): (...args: any[]) => T export declare const displayUrl: (url: string) => string;
// Create throttled function // Extracts and formats domain from URL
throttle<F extends (...args: any[]) => any>( export declare const displayDomain: (url: string) => string;
ms: number,
f: F
): F
// Create batching function
batch<T>(
t: number,
f: (xs: T[]) => void
): (x: T) => void
``` ```
### Network Utilities ## JSON, localStorage, fetch, event emitters, etc
```typescript ```typescript
// Fetch JSON with options // Safely parses JSON string
fetchJson(url: string, opts?: FetchOpts): Promise<any> export declare const parseJson: (json: string | undefined) => any;
// Post JSON data // Gets and parses JSON from localStorage
postJson<T>(url: string, data: T, opts?: FetchOpts): Promise<any> export declare const getJson: (k: string) => any;
// Upload file // Stringifies and stores value in localStorage
uploadFile(url: string, file: File): Promise<any> export declare const setJson: (k: string, v: any) => void;
// Options for fetch requests
type FetchOpts = {
method?: string;
headers?: Record<string, string | boolean>;
body?: string | FormData;
};
// Fetches JSON from URL with options
export declare const fetchJson: (url: string, opts?: FetchOpts) => Promise<any>;
// Posts JSON data to URL
export declare const postJson: <T>(url: string, data: T, opts?: FetchOpts) => Promise<any>;
// Uploads file to URL
export declare const uploadFile: (url: string, file: File) => Promise<any>;
// A generic type-safe event listener function that works with event emitters.
export declare const on: <EventMap extends Record<string | symbol, any[]>, E extends keyof EventMap>(target: {
on(event: E, listener: (...args: EventMap[E]) => any): any;
off(event: E, listener: (...args: EventMap[E]) => any): any;
}, eventName: E, callback: (...args: EventMap[E]) => void) => (() => void);
``` ```
## Usage Examples ## Strings
```typescript ```typescript
// Array operations // Truncates string to length, breaking at word boundaries
const nums = [1, 2, 2, 3, 3, 3] export declare const ellipsize: (s: string, l: number, suffix?: string) => string;
uniq(nums) // => [1, 2, 3]
// Object operations // Generates a hash string from input string
const obj = {a: 1, b: 2, c: 3} export declare const hash: (s: string) => string;
omit(['a', 'b'], obj) // => {c: 3}
// Number operations
add(5, undefined) // => 5
inc(undefined) // => 1
// Time operations
ago(DAY, 2) // => timestamp from 2 days ago
// URL operations
displayUrl('https://www.example.com/') // => 'example.com'
// Collection operations
const users = [{id: 1, name: 'Alice'}, {id: 2, name: 'Bob'}]
indexBy(u => u.id, users) // => Map(1 => {id: 1, name: 'Alice'}, ...)
// Function utilities
const throttledFn = throttle(1000, () => console.log('throttled'))
``` ```
## Curried utilities for working with collections
```typescript
// Returns a function that gets the nth element of an array
export declare const nth: (i: number) => <T>(xs: T[], ...args: unknown[]) => T;
// Returns a function that checks if nth element equals value
export declare const nthEq: (i: number, v: any) => (xs: any[], ...args: unknown[]) => boolean;
// Returns a function that checks if nth element does not equal value
export declare const nthNe: (i: number, v: any) => (xs: any[], ...args: unknown[]) => boolean;
// Returns a function that checks if key/value pairs of x match all pairs in spec
export declare const spec: (values: Obj | Array<any>) => (x: Obj | Array<any>, ...args: unknown[]) => boolean;
// Returns a function that checks equality with value
export declare const eq: <T>(v: T) => (x: T) => boolean;
// Returns a function that checks inequality with value
export declare const ne: <T>(v: T) => (x: T) => boolean;
// Returns a function that gets property value from object
export declare const prop: <T>(k: string) => (x: Record<string, unknown>) => T;
// Returns a function that adds/updates a property on object
export declare const assoc: <K extends string, T, U>(k: K, v: T) => (o: U) => U & Record<K, T>;
// Returns a function that removes a property on object
export declare const dissoc: <K extends string, T extends Obj>(k: K) => (o: T) => T;
```
## Sets
```typescript
// Adds value to Set at key in object
export declare const addToKey: <T>(m: Record<string, Set<T>>, k: string, v: T) => void;
// Pushes value to array at key in object
export declare const pushToKey: <T>(m: Record<string, T[]>, k: string, v: T) => void;
```
## Maps
```typescript
// Adds value to Set at key in Map
export declare const addToMapKey: <K, T>(m: Map<K, Set<T>>, k: K, v: T) => void;
// Pushes value to array at key in Map
export declare const pushToMapKey: <K, T>(m: Map<K, T[]>, k: K, v: T) => void;
```
## Bech32 <-> hex encoding
```typescript
// Converts hex string to bech32 format
export declare const hexToBech32: (prefix: string, hex: string) => `${Lowercase<string>}1${string}`;
// Converts bech32 string to hex format
export declare const bech32ToHex: (b32: string) => string;
```
-117
View File
@@ -1,117 +0,0 @@
# Worker
The Worker class provides a robust queue processing system with batched operations, throttling, and message routing capabilities. It's designed to handle asynchronous operations efficiently while maintaining control over processing rates and resource usage.
## Overview
```typescript
class Worker<T> {
constructor(readonly opts: WorkerOpts<T> = {})
}
```
The Worker class accepts messages of type `T` and processes them according to configured options and handlers.
## Configuration
```typescript
type WorkerOpts<T> = {
// Function to determine routing key for messages
getKey?: (x: T) => any
// Function to check if message should be deferred
shouldDefer?: (x: T) => boolean
// Maximum messages to process in one batch
chunkSize?: number // default: 50
// Milliseconds between processing batches
delay?: number // default: 50
}
```
## Basic Usage
```typescript
// Create worker for processing messages
const worker = new Worker<Message>({
chunkSize: 10,
delay: 100,
getKey: msg => msg.type
})
// Add message handlers
worker.addHandler('email', async (msg) => {
await sendEmail(msg)
})
worker.addHandler('notification', async (msg) => {
await sendNotification(msg)
})
// Add messages to queue
worker.push({
type: 'email',
content: 'Hello'
})
```
## Features
### Message Routing
Messages can be routed to specific handlers based on a key:
```typescript
const worker = new Worker<Task>({
getKey: task => task.priority
})
// Handle high priority tasks
worker.addHandler('high', async (task) => {
await processUrgent(task)
})
// Handle normal priority tasks
worker.addHandler('normal', async (task) => {
await processNormal(task)
})
```
### Global Handlers
Handle all messages regardless of routing key:
```typescript
worker.addGlobalHandler(async (message) => {
console.log('Processing:', message)
})
```
### Message Deferral
Defer processing of messages that aren't ready:
```typescript
const worker = new Worker<Task>({
shouldDefer: (task) => !task.isReady(),
delay: 1000
})
worker.push(task) // Will retry until task.isReady()
```
### Flow Control
Control message processing:
```typescript
// Pause processing
worker.pause()
// Resume processing
worker.resume()
// Clear queue
worker.clear()
```
+62
View File
@@ -1,5 +1,7 @@
# @welshman/net # @welshman/net
[![version](https://badgen.net/npm/v/@welshman/net)](https://npmjs.com/package/@welshman/net)
Core networking layer for nostr applications, handling relay connections, message management, and event delivery. Core networking layer for nostr applications, handling relay connections, message management, and event delivery.
## What's Included ## What's Included
@@ -12,6 +14,66 @@ Core networking layer for nostr applications, handling relay connections, messag
- **Targets** - Flexible message routing strategies - **Targets** - Flexible message routing strategies
- **Event Tracking** - Monitor which relays have seen events - **Event Tracking** - Monitor which relays have seen events
## Quick Example
```typescript
import {ctx, setContext} from '@welshman/lib'
import {type TrustedEvent, createEvent, NOTE} from '@welshman/util'
import {subscribe, publish, getDefaultNetContext} from '@welshman/net'
// Sets up customizable event valdation, handlers, etc
setContext(getDefaultNetContext())
// Send a subscription
const sub = subscribe({
relays: ['wss://relay.example.com/'],
filters: [{kinds: [1], limit: 1}],
closeOnEose: true,
timeout: 10000,
})
sub.on(SubscriptionEvent.Event, (url: string, event: TrustedEvent) => {
console.log(url, event)
sub.close()
})
// Publish an event
const pub = publish({
relays: ['wss://relay.example.com/'],
event: createEvent(NOTE, {content: 'hi'}),
})
pub.emitter.on('*', (status: PublishStatus, url: string) => {
console.log(status, url)
})
// The Tracker class can tell you which relays an event was read from or published to
console.log(ctx.net.tracker.getRelays(event.id))
```
The main reason this module exists is to support different backends via Executor and different `target` classes. For example, to add a local relay that automatically gets used:
```typescript
import {setContext} from '@welshman/lib'
import {LOCAL_RELAY_URL, Relay, Repository} from '@welshman/util'
import {getDefaultNetContext, Multi, Local, Relays, Executor} from '@welshman/net'
const repository = new Repository()
const relay = new Relay(repository)
setContext(getDefaultNetContext({
getExecutor: (relays: string[]) => {
return new Executor(
new Multi([
new Local(relay),
new Relays(remoteUrls.map(url => ctx.net.pool.get(url))),
])
)
},
}))
```
## Installation ## Installation
```bash ```bash
+21
View File
@@ -0,0 +1,21 @@
# @welshman/relay
[![version](https://badgen.net/npm/v/@welshman/relay)](https://npmjs.com/package/@welshman/relay)
Core networking layer for nostr applications, handling relay connections, message management, and event delivery.
## What's Included
- **Event Store** - A Repository class which stores events in memory
- **Relay Adapter** - A LocalRelay class which adapts nostr messages to the repository
## Quick Example
```typescript
```
## Installation
```bash
npm install @welshman/relay
```
+11 -3
View File
@@ -1,8 +1,8 @@
# @welshman/signer # @welshman/signer
A comprehensive Nostr signing implementation that supports multiple authentication methods and encryption standards. [![version](https://badgen.net/npm/v/@welshman/signer)](https://npmjs.com/package/@welshman/signer)
It provides a unified interface for working with different signing mechanisms while maintaining compatibility with various Nostr Implementation Possibilities (NIPs).
A Nostr signer implementation that supports multiple authentication methods and encryption standards.
## What's Included ## What's Included
@@ -13,11 +13,19 @@ It provides a unified interface for working with different signing mechanisms wh
- **NIP-55 Signer** - Native app integration via Capacitor - **NIP-55 Signer** - Native app integration via Capacitor
- **NIP-59 Utils** - Gift Wrap protocol for secure event encryption - **NIP-59 Utils** - Gift Wrap protocol for secure event encryption
## Quick Example
```typescript
import { makeEvent } from '@welshman/util'
import { ISigner, Nip01Signer, makeSecret } from '@welshman/signer'
const signer: ISigner = new Nip01Signer(makeSecret())
signer.sign(makeEvent(1)).then(signedEvent => console.log(signedEvent))
```
## Installation ## Installation
```bash ```bash
npm install @welshman/signer npm install @welshman/signer
``` ```
+17 -1
View File
@@ -1,6 +1,7 @@
# @welshman/store # @welshman/store
A utility package designed specifically for Svelte applications, providing enhanced store functionality and utilities for managing state. While it's primarily built for use with Svelte's store system, the concepts could be valuable for developers familiar with reactive programming patterns like RxJS. [![version](https://badgen.net/npm/v/@welshman/store)](https://npmjs.com/package/@welshman/store)
A utility package providing welshman-specific svelte store functionality and utilities for managing state. While it's primarily built for use with Svelte's store system, the concepts could be valuable for developers familiar with reactive programming patterns like RxJS.
## What's Included ## What's Included
@@ -10,6 +11,21 @@ A utility package designed specifically for Svelte applications, providing enhan
- **Persistence Layer** - Automatic localStorage synchronization - **Persistence Layer** - Automatic localStorage synchronization
- **Performance Optimizations** - Throttled updates and efficient subscription management - **Performance Optimizations** - Throttled updates and efficient subscription management
## Quick Example
```typescript
import {Repository, NAMED_PEOPLE, TrustedEvent, PublishedList, readList} from '@welshman/util'
import {deriveEventsMapped} from '@welshman/store'
const repository = new Repository()
// Create a store that performantly maps matching events in the repository to List objects
const lists = deriveEventsMapped<PublishedList>(repository, {
filters: [{kinds: [NAMED_PEOPLE]}],
eventToItem: (event: TrustedEvent) => (event.tags.length > 1 ? readList(event) : null),
itemToEvent: (list: PublishedList) => list.event,
})
```
## Installation ## Installation
-6
View File
@@ -12,9 +12,3 @@ Need just a content parser? Grab @welshman/content. Building a complex client? S
Each module is battle-tested in production, designed to work together but never dependent on each other. Each module is battle-tested in production, designed to work together but never dependent on each other.
Build your next Nostr client with the same tools that power today's leading Nostr applications. Build your next Nostr client with the same tools that power today's leading Nostr applications.
<!-- ## Installation
```sh
npm install @welshman
``` -->
-73
View File
@@ -1,73 +0,0 @@
# @welshman/app [![version](https://badgen.net/npm/v/@welshman/app)](https://npmjs.com/package/@welshman/app)
Utilities for dealing with svelte stores when using welshman.
```typescript
import {ctx, setContext} from '@welshman/lib'
import {getNip07} from '@welshman/signer'
import {throttled} from '@welshman/store'
import {createEvent, NOTE} from '@welshman/util'
import {
getDefaultNetContext,
getDefaultAppContext,
signer,
pubkey,
publishThunk,
load,
initStorage,
storageAdapters,
freshness,
plaintext,
repository,
tracker,
} from '@welshman/app'
// Set up app config
setContext({
net: getDefaultNetContext(),
app: getDefaultAppContext(),
})
// Log in via NIP 07
addSession({method: 'nip07', pubkey: await getNip07().getPubkey()})
// Signer is ready to go
const event = signer.get().encrypt(/* ... */)
// This will fetch the user's profile automatically, and return an observable that updates
// automatically. Several different stores exist that are ready to go, including handles,
// zappers, relaySelections, relays, follows, mutes.
const profile = deriveProfile(pubkey.get())
// A global router helps make intelligent relay selections
const router = ctx.app.router
// Publish is done using thunks, which optimistically publish to the local database, deferring
// signing and publishing for instant user feedback. Progress is reported as relays accept/reject the event
const thunk = publishThunk({
relays: router.FromUser().getUrls(),
event: createEvent(NOTE, {content: "hi"}),
delay: 3000,
})
// Thunks can be aborted until after `delay`, allowing for soft-undo
thunk.controller.abort()
// Subscriptions automatically infer relays using `router` if not provided. If the request can be cached,
// results from the local repository are returned immediately. `subscribe` and `load` are both available
const events = await load({filters: [{kinds: [NOTE]}])
// Some commands are included
const thunk = follow('97c70a44366a6535c145b333f973ea86dfdc2d7a99da618c40c64705ad98e322')
// Stores can be easily synchronized with indexeddb. Freshness keeps track of how stale the caches are,
// plaintext maps encrypted events to their decrypted content, repository and tracker hold events and
// event/relay mappings, respectively.
const ready = initStorage("my-db", 1, {
relays: {keyPath: "url", store: throttled(3000, relays)},
handles: {keyPath: "nip05", store: throttled(3000, handles)},
freshness: storageAdapters.fromObjectStore(freshness, {throttle: 3000}),
plaintext: storageAdapters.fromObjectStore(plaintext, {throttle: 3000}),
events: storageAdapters.fromRepositoryAndTracker(repository, tracker, {throttle: 3000}),
})
```
+103
View File
@@ -0,0 +1,103 @@
import {get, derived} from 'svelte/store'
import {batch, fromPairs} from '@welshman/lib'
import {PROFILE, FOLLOWS, MUTES, RELAYS, INBOX_RELAYS, getPubkeyTagValues, getListTags} from '@welshman/util'
import {throttled, withGetter} from '@welshman/store'
import {RepositoryUpdate} from '@welshman/relay'
import {getAll, bulkPut, bulkDelete} from './storage.js'
import {relays} from './relays.js'
import {handles, onHandle} from './handles.js'
import {zappers, onZapper} from './zappers.js'
import {plaintext} from './plaintext.js'
import {freshness} from './freshness.js'
import {repository} from './core.js'
import {sessions} from './session.js'
import {userFollows} from './user.js'
export const defaultStorageAdapters = {
relays: {
keyPath: "url",
init: async () => relays.set(await getAll("relays")),
sync: () => throttled(3000, relays).subscribe($relays => bulkPut("relays", $relays)),
},
handles: {
keyPath: "nip05",
init: async () => handles.set(await getAll("handles")),
sync: () => onHandle(batch(300, $handles => bulkPut("handles", $handles))),
},
zappers: {
keyPath: "lnurl",
init: async () => zappers.set(await getAll("zappers")),
sync: () => onZapper(batch(300, $zappers => bulkPut("zappers", $zappers))),
},
freshness: {
keyPath: "key",
init: async () => {
const items = await getAll("freshness")
freshness.set(fromPairs(items.map(item => [item.key, item.value])))
},
sync: () => {
const interval = setInterval(() => {
bulkPut(
"freshness",
Object.entries(freshness.get()).map(([key, value]) => ({key, value})),
)
}, 10_000)
return () => clearInterval(interval)
},
},
plaintext: {
keyPath: "key",
init: async () => {
const items = await getAll("plaintext")
plaintext.set(fromPairs(items.map(item => [item.key, item.value])))
},
sync: () => {
const interval = setInterval(() => {
bulkPut(
"plaintext",
Object.entries(plaintext.get()).map(([key, value]) => ({key, value})),
)
}, 10_000)
return () => clearInterval(interval)
},
},
events: {
keyPath: "id",
init: async () => repository.load(await getAll("events")),
sync: () => {
const userFollowPubkeys = withGetter(
derived(userFollows, l => new Set(getPubkeyTagValues(getListTags(l))))
)
const onUpdate = async ({added, removed}: RepositoryUpdate) => {
const sessionKeys = new Set(Object.keys(sessions.get()))
const metaKinds = [PROFILE, FOLLOWS, MUTES, RELAYS, INBOX_RELAYS]
if (removed.size > 0) {
await bulkDelete("events", Array.from(removed))
}
if (added.length > 0) {
await bulkPut(
"events",
added.filter(e => {
if (sessionKeys.has(e.pubkey)) return true
if (e.tags.some(t => sessionKeys.has(t[1]))) return true
if (metaKinds.includes(e.kind) && userFollowPubkeys.get()?.has(e.pubkey)) return true
return false
}),
)
}
}
repository.on("update", onUpdate)
return () => repository.off("update", onUpdate)
},
},
}
+3 -3
View File
@@ -1,5 +1,5 @@
import {readable, derived, type Readable, type Subscriber} from "svelte/store" import {readable, derived, type Readable, type Subscriber} from "svelte/store"
import {indexBy, remove, type Maybe, now} from "@welshman/lib" import {indexBy, remove, now} from "@welshman/lib"
import {withGetter} from "@welshman/store" import {withGetter} from "@welshman/store"
import {getFreshness, setFreshnessThrottled} from "./freshness.js" import {getFreshness, setFreshnessThrottled} from "./freshness.js"
@@ -15,7 +15,7 @@ export const collection = <T>({
load?: (key: string, relays: string[]) => Promise<any> load?: (key: string, relays: string[]) => Promise<any>
}) => { }) => {
const indexStore = withGetter(derived(store, $items => indexBy(getKey, $items))) const indexStore = withGetter(derived(store, $items => indexBy(getKey, $items)))
const pending = new Map<string, Promise<Maybe<T>>>() const pending = new Map<string, Promise<T | void>>()
const loadAttempts = new Map<string, number>() const loadAttempts = new Map<string, number>()
let subscribers: Subscriber<T>[] = [] let subscribers: Subscriber<T>[] = []
@@ -76,7 +76,7 @@ export const collection = <T>({
return fresh return fresh
} }
const deriveItem = (key: Maybe<string>, relays: string[] = []) => { const deriveItem = (key: string | undefined, relays: string[] = []) => {
if (!key) { if (!key) {
return readable(undefined) return readable(undefined)
} }
+1
View File
@@ -1,3 +1,4 @@
export * from "./adapters.js"
export * from "./context.js" export * from "./context.js"
export * from "./core.js" export * from "./core.js"
export * from "./collection.js" export * from "./collection.js"
-89
View File
@@ -1,89 +0,0 @@
# @welshman/dvm [![version](https://badgen.net/npm/v/@welshman/dvm)](https://npmjs.com/package/@welshman/dvm)
Utilities for building nostr DVMs.
# Request example
```javascript
import type {Publish, Subscription} from '@welshman/net'
import {makeDvmRequest, DVMEvent} from '@welshman/dvm'
const req = makeDvmRequest({
// Create and sign a dvm request event, including any desired tags
event: createAndSign({kind: 5300}),
// Publish and subscribe to these relays
relays: ['wss://relay.damus.io', 'wss://dvms.f7z.io'],
// Timeout defaults to 30 seconds
timeout: 30_000,
// Auto close on first result (defaults to true)
autoClose: true,
// Listen for and emit `progress` events
reportProgress: true,
})
// Listen for progress, result, etc
req.emitter.on(DVMEvent.Progress, (url, event) => console.log(event))
req.emitter.on(DVMEvent.Result, (url, event) => console.log(event))
```
# Handler example
```javascript
import {bytesToHex} from '@noble/hashes/utils'
import {generateSecretKey} from 'nostr-tools'
import {createEvent} from '@welshman/util'
import {subscribe} from '@welshman/net'
import {DVM} from '@welshman/dvm'
// Your DVM's private key. Store this somewhere safe
// const hexPrivateKey = bytesToHex(generateSecretKey())
const hexPrivateKey = '9cd387a3aa0c1abc2ef517c8402f29c069b4174e02a426491aec7566501bee67'
// Tags that we'll return as content discovery suggestions
const tags = []
// Populate the tags with music by Ainsley Costello
const sub = subscribe({
timeout: 30_000,
relays: ["wss://relay.wavlake.com"],
filters: [{
kinds: [31337],
'#p': ['8806372af51515bf4aef807291b96487ea1826c966a5596bca86697b5d8b23bc'],
}],
})
// Push event ids to our suggestions
sub.on('event', (url, e) => tags.push(["e", e.id, url]))
const dvm = new DVM({
// The private key used to sign events
sk: hexPrivateKey,
// Relays that the DVM will listen on
relays: ['wss://relay.damus.io', 'wss://dvms.f7z.io'],
// Only listen to requests tagging our dvm
requireMention: true,
// Expire results after 1 hour (the default)
expireAfter: 60 * 60,
// Handlers for various kinds
handlers: {
5300: dvm => ({
handleEvent: async function* (event) {
// DVM responses are stringified into the content
const content = JSON.stringify(tags)
// Yield our response. Kind 7000 can be used for partial results too
yield createEvent(event.kind + 1000, {content})
},
}),
}
})
// Enable logging
dvm.logEvents = true
// When you're ready
dvm.start()
// When you're done
dvm.stop()
```
-3
View File
@@ -1,3 +0,0 @@
# @welshman/editor [![version](https://badgen.net/npm/v/@welshman/editor)](https://npmjs.com/package/@welshman/editor)
A batteries-included nostr-editor.
-34
View File
@@ -1,34 +0,0 @@
# @welshman/feeds [![version](https://badgen.net/npm/v/@welshman/feeds)](https://npmjs.com/package/@welshman/feeds)
A custom feed compiler and loader for nostr. Read the spec on [wikifreedia](https://wikifreedia.xyz/cip-01/97c70a44366a6535c1).
# Example
```javascript
// Define a feed using set operations
const feed = intersectionFeed(
unionFeed(
dvmFeed({
kind: 5300,
pubkey: '19b78ccfa7c5e31e6bacbb3f2a1703f64b62017702e584440bf29a7e16263e8c',
}),
listFeed("10003:19ba654f26afd4930fd3d51baf4e26f1413b7aeec7190cd6c0cdf4d2f14cec6b:"),
)
wotFeed({min: 0.1}),
scopeFeed("global"),
)
// 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)
```
-13
View File
@@ -1,13 +0,0 @@
# @welshman/lib [![version](https://badgen.net/npm/v/@welshman/lib)](https://npmjs.com/package/@welshman/lib)
Some general-purpose utilities for use in @welshman apps.
Includes:
- LRU cache implementation
- TaskQueue for throttling work to avoid locking up the UI
- URL normalization (taken from normalize-url)
- A global `ctx` variable which can be used for global configuration
- CustomPromise, which provides an error type, and `defer` utility
- Ramda-like utilities, but without auto-currying
- Utils for throttling, working with nil, json, fetch, deep equals, etc.
-7
View File
@@ -11,13 +11,6 @@ describe("Tools", () => {
vi.useRealTimers() vi.useRealTimers()
}) })
describe("Basic Utils", () => { describe("Basic Utils", () => {
it("should check for nil values", () => {
expect(T.isNil(null)).toBe(true)
expect(T.isNil(undefined)).toBe(true)
expect(T.isNil(0)).toBe(false)
expect(T.isNil("")).toBe(false)
})
it("should handle ifLet", () => { it("should handle ifLet", () => {
const fn = vi.fn() const fn = vi.fn()
T.ifLet(undefined, fn) T.ifLet(undefined, fn)
+711 -651
View File
File diff suppressed because it is too large Load Diff
-61
View File
@@ -1,61 +0,0 @@
# @welshman/net [![version](https://badgen.net/npm/v/@welshman/net)](https://npmjs.com/package/@welshman/net)
Utilities having to do with connection management and nostr messages.
```typescript
import {ctx, setContext} from '@welshman/lib'
import {type TrustedEvent, createEvent, NOTE} from '@welshman/util'
import {subscribe, publish, getDefaultNetContext} from '@welshman/net'
// Sets up customizable event valdation, handlers, etc
setContext(getDefaultNetContext())
// Send a subscription
const sub = subscribe({
relays: ['wss://relay.example.com/'],
filters: [{kinds: [1], limit: 1}],
closeOnEose: true,
timeout: 10000,
})
sub.on(SubscriptionEvent.Event, (url: string, event: TrustedEvent) => {
console.log(url, event)
sub.close()
})
// Publish an event
const pub = publish({
relays: ['wss://relay.example.com/'],
event: createEvent(NOTE, {content: 'hi'}),
})
pub.emitter.on('*', (status: PublishStatus, url: string) => {
console.log(status, url)
})
// The Tracker class can tell you which relays an event was read from or published to
console.log(ctx.net.tracker.getRelays(event.id))
```
The main reason this module exists is to support different backends via Executor and different `target` classes. For example, to add a local relay that automatically gets used:
```typescript
import {setContext} from '@welshman/lib'
import {LOCAL_RELAY_URL, Relay, Repository} from '@welshman/util'
import {getDefaultNetContext, Multi, Local, Relays, Executor} from '@welshman/net'
const repository = new Repository()
const relay = new Relay(repository)
setContext(getDefaultNetContext({
getExecutor: (relays: string[]) => {
return new Executor(
new Multi([
new Local(relay),
new Relays(remoteUrls.map(url => ctx.net.pool.get(url))),
])
)
},
}))
```
+2
View File
@@ -166,3 +166,5 @@ export class MultiPublish extends EventEmitter {
this.removeAllListeners() this.removeAllListeners()
} }
} }
export const publish = (options: MultiPublishOptions) => new MultiPublish(options)
-61
View File
@@ -1,61 +0,0 @@
# @welshman/net [![version](https://badgen.net/npm/v/@welshman/net)](https://npmjs.com/package/@welshman/net)
Utilities having to do with connection management and nostr messages.
```typescript
import {ctx, setContext} from '@welshman/lib'
import {type TrustedEvent, createEvent, NOTE} from '@welshman/util'
import {subscribe, publish, getDefaultNetContext} from '@welshman/net'
// Sets up customizable event valdation, handlers, etc
setContext(getDefaultNetContext())
// Send a subscription
const sub = subscribe({
relays: ['wss://relay.example.com/'],
filters: [{kinds: [1], limit: 1}],
closeOnEose: true,
timeout: 10000,
})
sub.on(SubscriptionEvent.Event, (url: string, event: TrustedEvent) => {
console.log(url, event)
sub.close()
})
// Publish an event
const pub = publish({
relays: ['wss://relay.example.com/'],
event: createEvent(NOTE, {content: 'hi'}),
})
pub.emitter.on('*', (status: PublishStatus, url: string) => {
console.log(status, url)
})
// The Tracker class can tell you which relays an event was read from or published to
console.log(ctx.net.tracker.getRelays(event.id))
```
The main reason this module exists is to support different backends via Executor and different `target` classes. For example, to add a local relay that automatically gets used:
```typescript
import {setContext} from '@welshman/lib'
import {LOCAL_RELAY_URL, Relay, Repository} from '@welshman/util'
import {getDefaultNetContext, Multi, Local, Relays, Executor} from '@welshman/net'
const repository = new Repository()
const relay = new Relay(repository)
setContext(getDefaultNetContext({
getExecutor: (relays: string[]) => {
return new Executor(
new Multi([
new Local(relay),
new Relays(remoteUrls.map(url => ctx.net.pool.get(url))),
])
)
},
}))
```
+5
View File
@@ -29,6 +29,11 @@ const getDay = (ts: number) => Math.floor(ts / DAY)
export let repositorySingleton: Repository<TrustedEvent> export let repositorySingleton: Repository<TrustedEvent>
export type RepositoryUpdate = {
added: TrustedEvent[]
removed: Set<string>
}
export class Repository<E extends HashedEvent = TrustedEvent> extends Emitter { export class Repository<E extends HashedEvent = TrustedEvent> extends Emitter {
eventsById = new Map<string, E>() eventsById = new Map<string, E>()
eventsByWrap = new Map<string, E>() eventsByWrap = new Map<string, E>()
-93
View File
@@ -1,93 +0,0 @@
# @welshman/signer [![version](https://badgen.net/npm/v/@welshman/signer)](https://npmjs.com/package/@welshman/signer)
Implementations of signer utilities and classes.
## Nips supported
- NIP 01 (private key login)
- NIP 07
- NIP 46
- NIP 55
- NIP 59 (gift wrapping, works with any signer that supports encryption)
## Examples
### NIP 01
```typescript
import {makeSecret, Nip01Signer} from '@welshman/signer'
const signer = Nip01Signer.fromSecret(makeSecret())
```
### NIP 07
```typescript
import {getNip07, Nip07Signer} from '@welshman/signer'
if (getNip07()) {
const signer = new Nip07Signer()
}
```
### NIP 55
```typescript
import {getNip07, Nip07Signer} from '@welshman/signer'
if (getNip07()) {
const signer = new Nip07Signer()
}
```
### NIP 46
```typescript
import {createEvent, NOTE} from '@welshman/util'
import {makeSecret, Nip46Broker, Nip46Signer} from '@welshman/signer'
const clientSecret = makeSecret()
const relays = ['wss://relay.signer.example/']
const broker = Nip46Broker.get({relays, clientSecret})
const signer = new Nip46Signer(broker)
const ncUrl = broker.makeNostrconnectUrl({name: "My app"})
const abortController = new AbortController()
let response
try {
response = await broker.waitForNostrconnect(url, abortController)
} catch (e: any) {
if (e?.error) {
showWarning(`Received error from signer: ${e.error}`)
} else if (e) {
console.error(e)
}
}
if (response) {
// Now we know the bunker's pubkey and can do stuff with the signer
const signerPubkey = response.event.pubkey
// Next time we want to use our signer, we can instantiate it like so:
const newBroker = Nip46Broker.get({relays, clientSecret, signerPubkey})
const newSigner = new Nip46Signer(newBroker)
}
```
### Using signers
```typescript
import {createEvent, NOTE, DIRECT_MESSAGE} from '@welshman/util'
const signer = // Create your signer...
const nip59 = Nip59.fromSigner(signer)
// Sign an event
const event = await signer.sign(createEvent(NOTE, {content: "hi"}))
// Wrap a NIP 17 DM
const rumor = await nip59.wrap(recipientPubkey, createEvent(DIRECT_MESSAGE, {content: "hi"}))
// Note that it returns a rumor; be sure to publish the `wrap`
const wrap = rumor.wrap
```
+1 -2
View File
@@ -10,7 +10,6 @@ import {
partition, partition,
first, first,
} from "@welshman/lib" } from "@welshman/lib"
import {Maybe} from "@welshman/lib"
import {Repository} from "@welshman/relay" import {Repository} from "@welshman/relay"
import {matchFilters, getIdAndAddress, getIdFilters, Filter, TrustedEvent} from "@welshman/util" import {matchFilters, getIdAndAddress, getIdFilters, Filter, TrustedEvent} from "@welshman/util"
@@ -142,7 +141,7 @@ export const adapter = <Source, Target>({
export type DeriveEventsMappedOptions<T> = { export type DeriveEventsMappedOptions<T> = {
filters: Filter[] filters: Filter[]
eventToItem: (event: TrustedEvent) => Maybe<T | T[] | Promise<T | T[]>> eventToItem: (event: TrustedEvent) => T | T[] | Promise<T | T[]> | undefined
itemToEvent: (item: T) => TrustedEvent itemToEvent: (item: T) => TrustedEvent
throttle?: number throttle?: number
includeDeleted?: boolean includeDeleted?: boolean
-15
View File
@@ -1,15 +0,0 @@
# @welshman/util [![version](https://badgen.net/npm/v/@welshman/util)](https://npmjs.com/package/@welshman/util)
Some nostr-specific utilities. For the most part, these will not have side effects or manage state. Includes:
- Event kind constants
- A nostr address class
- Utilities for working with nostr filters and tags
- Helpers for working with zap events and lightning invoices
- A `Encryptable` for ensuring payloads get encrypted
- An implementation of an in-memory relay, backed by an events repository
- Utilities for building events, validating signatures, and checking event type (replaceable, etc.)
- Types and utilities for NIP 89 handlers
- Types and utilities for NIP 51 lists
- Types and utilities for NIP 01 profile metadata
- Types and utilities for NIP 11 relay profiles