Add documentation

This commit is contained in:
Jon Staab
2024-12-13 14:16:38 -08:00
parent 627eb1e36d
commit f7baa54724
940 changed files with 4549 additions and 55 deletions
+29
View File
@@ -0,0 +1,29 @@
# @welshman/content [![version](https://badgen.net/npm/v/@welshman/content)](https://npmjs.com/package/@welshman/content)
Utilities for parsing and rendering note content. Customizable via RenderOptions.
```typescript
import {parse, render} from '@welshman/content'
const content = "Hello<br>from https://coracle.tools! <script>alert('evil')</script>"
const parsed = parse({content, tags: []})
// [
// { type: 'text', value: 'Hello<br>from ', raw: 'Hello<br>from ' },
// {
// type: 'link',
// value: { url: URL, isMedia: false },
// raw: 'https://coracle.tools'
// },
// {
// type: 'text',
// value: "! <script>alert('evil')</script>",
// raw: "! <script>alert('evil')</script>"
// }
// ]
const result = renderAsText(parsed)
// => Hello&lt;br&gt;from https://coracle.tools/! &lt;script&gt;alert('evil')&lt;/script&gt;
const result = renderAsHtml(parsed)
// => Hello&lt;br&gt;from <a href="https://coracle.tools/" target="_blank">coracle.tools/</a>! &lt;script&gt;alert('evil')&lt;/script&gt;
```
+89
View File
@@ -0,0 +1,89 @@
# @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
const {bytesToHex} = require('@noble/hashes/utils')
const {generateSecretKey} = require('nostr-tools')
const {createEvent} = require('@welshman/util')
const {subscribe} = require('@welshman/net')
const {DVM} = require('@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.emitter.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()
```
+34
View File
@@ -0,0 +1,34 @@
# @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
@@ -0,0 +1,13 @@
# @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
- Worker 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.
+61
View File
@@ -0,0 +1,61 @@
# @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.emitter.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))),
])
)
},
}))
```
+93
View File
@@ -0,0 +1,93 @@
# @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
```
+17
View File
@@ -0,0 +1,17 @@
# @welshman/store [![version](https://badgen.net/npm/v/@welshman/store)](https://npmjs.com/package/@welshman/store)
Utilities for dealing with svelte stores when using welshman.
```typescript
import {Repository, NAMED_PEOPLE, NAMED_TOPICS, type TrustedEvent, readUserList, List} from '@welshman/util'
import {deriveEventsMapped} from '@welshman/store'
const repository = new Repository()
// Create a svelte store that performantly maps matching events in the repository to List objects
const lists = deriveEventsMapped<PublishedUserList>(repository, {
filters: [{kinds: [NAMED_PEOPLE, NAMED_TOPICS]}],
eventToItem: (event: TrustedEvent) => (event.tags.length > 1 ? readUserList(event) : null),
itemToEvent: (list: List) => list.event,
})
```
+15
View File
@@ -0,0 +1,15 @@
# @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
+73
View File
@@ -0,0 +1,73 @@
# @welshman/store [![version](https://badgen.net/npm/v/@welshman/store)](https://npmjs.com/package/@welshman/store)
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}),
})
```