Finish pass on docs

This commit is contained in:
Jon Staab
2025-06-10 15:48:00 -07:00
parent 1a81768e8e
commit ee6fa8b956
19 changed files with 545 additions and 435 deletions
-60
View File
@@ -1,60 +0,0 @@
# Basic Utilities
## synced
Creates a writable store that automatically synchronizes its value with localStorage.
```typescript
const myStore = synced('storage-key', 'default value');
```
## getter
Creates a function that returns the current value of a store without subscribing to it.
```typescript
const myStore = writable('value');
const getValue = getter(myStore);
```
## withGetter
Enhances a store by adding a getter method to access its current value.
```typescript
const myStore = withGetter(writable('value'));
console.log(myStore.get()); // 'value'
```
## throttled
Creates a store that limits how often subscribers receive updates.
```typescript
const throttledStore = throttled(1000, myStore); // Updates at most once per second
```
## custom
Creates a custom store with optional throttling and custom set behavior.
```typescript
const customStore = custom(
set => {
// Setup logic
return () => {
// Cleanup logic
};
},
{ throttle: 1000 }
);
```
## adapter
Creates a derived store that can transform values between two types while maintaining two-way binding.
```typescript
const adaptedStore = adapter({
store: originalStore,
forward: (source) => /* transform to target */,
backward: (target) => /* transform back to source */
});
```
This is particularly useful when you need to transform data structures while maintaining the ability to update the original store.
+71
View File
@@ -0,0 +1,71 @@
# Collection
Utilities for creating reactive collections with automatic loading, caching, and staleness management using Svelte stores.
## Functions
### collection(options)
Creates a reactive collection that automatically loads missing items and manages freshness.
**Options:**
- `name` - Collection name for freshness tracking
- `store` - Readable store containing array of items
- `getKey` - Function to extract unique key from items
- `load` - Async function to load missing items
**Returns:**
- `indexStore` - Derived store with items indexed by key
- `deriveItem(key, relays)` - Creates a derived store for a specific item
- `loadItem(key, relays)` - Manually loads an item
- `onItem(callback)` - Subscribe to individual item updates
### makeCachedLoader(options)
Creates a cached loader function with staleness checking and exponential backoff.
**Options:**
- `name` - Loader name for freshness tracking
- `indexStore` - Store containing indexed items
- `load` - Async function to load items
- `subscribers` - Array of item update subscribers
### Freshness Management
- `getFreshness(ns, key)` - Get last update timestamp for an item
- `setFreshnessImmediate(update)` - Immediately update freshness
- `setFreshnessThrottled(update)` - Throttled freshness updates
## Example
```typescript
import {writable} from 'svelte/store'
import {derived, readable} from "svelte/store"
import {readProfile, PROFILE, PublishedProfile} from "@welshman/util"
import {Repository} from "@welshman/relay"
import {deriveEventsMapped, collection, withGetter} from "@welshman/store"
const repository = new Repository()
export const profiles = writable([])
export const {
indexStore: profilesByPubkey,
deriveItem: deriveProfile,
loadItem: loadProfile,
} = collection({
name: "profiles",
store: profiles,
getKey: profile => profile.event.pubkey,
load: (pubkey: string) => // Load the user's profile
})
// Get a reactive store for a specific profile
const hints = [/* optional relay hints to load from */]
const userProfile = deriveProfile("user-pubkey", hints)
// Subscribe to profile updates
userProfile.subscribe(profile => {
console.log("Profile updated:", profile)
})
```
+56
View File
@@ -0,0 +1,56 @@
# Custom Store
Utility for creating custom Svelte stores with start/stop lifecycle and optional throttling.
## Functions
### custom(start, options)
Creates a custom store that starts when first subscribed and stops when last subscriber unsubscribes.
**Parameters:**
- `start` - Function called when first subscriber is added. Receives a `set` function and should return an unsubscriber function
- `options` - Optional configuration object
**Options:**
- `throttle` - Throttle subscriber notifications (milliseconds)
- `onUpdate` - Callback function called when store value is set
**Returns:** WritableWithGetter store with `get()`, `set()`, `update()`, and `subscribe()` methods
## Example
```typescript
import {custom} from "@welshman/store"
// Create a store that tracks window width
const windowWidth = custom(
set => {
const updateWidth = () => set(window.innerWidth)
// Set initial value
updateWidth()
// Listen for resize events
window.addEventListener('resize', updateWidth)
// Return cleanup function
return () => window.removeEventListener('resize', updateWidth)
},
{
throttle: 100, // Throttle updates to every 100ms
onUpdate: (width) => console.log(`Window width: ${width}px`)
}
)
// Subscribe to changes
const unsubscribe = windowWidth.subscribe(width => {
console.log("Width changed:", width)
})
// Get current value
console.log("Current width:", windowWidth.get())
// Clean up
unsubscribe()
```
-122
View File
@@ -1,122 +0,0 @@
# Event-Based Stores
## deriveEventsMapped
Creates a store that maintains a mapped collection of events from a repository.
Useful when you want to transform events into a different data structure while maintaining reactivity.
```typescript
import {Repository, NAMED_PEOPLE, type TrustedEvent} from '@welshman/util'
import {deriveEventsMapped} from '@welshman/store'
interface UserProfile {
name: string;
about: string;
pubkey: string;
}
const repository = new Repository()
const profiles = deriveEventsMapped<UserProfile>(repository, {
filters: [{kinds: [PROFILE]}],
eventToItem: (event: TrustedEvent) => ({
name: event.content.name,
about: event.content.about,
pubkey: event.pubkey,
}),
itemToEvent: (profile: UserProfile) => ({
// Convert profile back to event format
kind: PROFILE,
pubkey: profile.pubkey,
content: {
name: profile.name,
about: profile.about,
}
}),
throttle: 1000, // Optional: throttle updates
includeDeleted: false // Optional: exclude deleted events
})
```
## deriveEvents
Creates a store that maintains a collection of raw events from a repository.
Useful when you want to work directly with events without transformation.
```typescript
import {Repository} from '@welshman/util'
import {deriveEvents} from '@welshman/store'
const repository = new Repository()
const textNotes = deriveEvents(repository, {
filters: [{kinds: [NOTE], // kind 1 = text note
authors: ['pubkey1', 'pubkey2']}],
throttle: 500,
includeDeleted: false
})
// Subscribe to changes
textNotes.subscribe(events => {
console.log('New text notes:', events)
})
```
## deriveEvent
Creates a store that tracks a single event by its ID or address.
Returns a derived store containing the event or undefined.
```typescript
import {Repository} from '@welshman/util'
import {deriveEvent} from '@welshman/store'
const repository = new Repository()
const specificEvent = deriveEvent(repository, 'event_id_or_address')
// Subscribe to changes of the specific event
specificEvent.subscribe(event => {
if (event) {
console.log('Event updated:', event)
} else {
console.log('Event not found')
}
})
```
## deriveIsDeleted
Creates a store that tracks whether an event has been deleted. Returns a boolean store.
```typescript
import {Repository} from '@welshman/util'
import {deriveIsDeleted} from '@welshman/store'
const repository = new Repository()
const event = /* your event */
const isDeleted = deriveIsDeleted(repository, event)
// Subscribe to deletion status changes
isDeleted.subscribe(deleted => {
console.log('Event deleted status:', deleted)
})
```
## deriveIsDeletedByAddress
Creates a store that tracks whether an event has been deleted by address.
Similar to deriveIsDeleted but checks deletion by address instead of event ID.
```typescript
import {Repository} from '@welshman/util'
import {deriveIsDeletedByAddress} from '@welshman/store'
const repository = new Repository()
const event = /* your event */
const isDeletedByAddress = deriveIsDeletedByAddress(repository, event)
// Subscribe to address-based deletion status changes
isDeletedByAddress.subscribe(deleted => {
if (deleted) {
console.log('Event has been deleted by address')
}
})
```
+55
View File
@@ -0,0 +1,55 @@
# Getter
Utilities for adding synchronous `get()` methods to Svelte stores, allowing immediate value access without subscribing. Note that this has performance implications, since it will activate a subscription that will never get unsubscribed. Do not use this on stores that require complex calculations, or which are created and destroyed.
## Functions
### getter(store)
Creates a getter function that returns the current value of a store.
**Parameters:**
- `store` - Any readable Svelte store
**Returns:** Function that returns the current store value
### withGetter(store)
Enhances a store by adding a synchronous `get()` method.
**Parameters:**
- `store` - Readable or writable Svelte store
**Returns:** Store with added `get()` method
## Types
- `ReadableWithGetter<T>` - Readable store with `get()` method
- `WritableWithGetter<T>` - Writable store with `get()` method
## Example
```typescript
import {writable, derived} from "svelte/store"
import {withGetter, getter} from "@welshman/store"
// Create enhanced stores with getter methods
const count = withGetter(writable(0))
const doubled = withGetter(derived(count, $count => $count * 2))
// Access values synchronously without subscribing
console.log(count.get()) // 0
console.log(doubled.get()) // 0
// Update the store
count.set(5)
// Get updated values immediately
console.log(count.get()) // 5
console.log(doubled.get()) // 10
// Alternative: create getter function separately
const regularStore = writable(42)
const getValue = getter(regularStore)
console.log(getValue()) // 42
```
+1
View File
@@ -1,6 +1,7 @@
# @welshman/store
[![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
+74
View File
@@ -0,0 +1,74 @@
# Repository
Reactive Svelte stores for querying and mapping events from a Repository with automatic updates.
## Functions
### deriveEventsMapped(repository, options)
Creates a reactive store that maps events to custom items and keeps them synchronized with repository updates.
**Options:**
- `filters` - Array of Nostr filters to query events
- `eventToItem` - Function to transform events to items (can return Promise)
- `itemToEvent` - Function to extract the event from an item
- `throttle?` - Throttle updates (milliseconds, default: 0)
- `includeDeleted?` - Include deleted events (default: false)
### deriveEvents(repository, options)
Creates a reactive store of events without transformation.
**Options:**
- `filters` - Array of Nostr filters
- `throttle?` - Throttle updates
- `includeDeleted?` - Include deleted events
### deriveEvent(repository, idOrAddress)
Creates a reactive store for a single event by ID or address.
### deriveIsDeleted(repository, event)
Creates a reactive store that tracks whether an event is deleted.
### deriveIsDeletedByAddress(repository, event)
Creates a reactive store that tracks whether an event is deleted by address.
## Example
```typescript
import {Repository} from "@welshman/relay"
import {deriveEventsMapped, deriveEvents} from "@welshman/store"
import {readProfile, PROFILE} from "@welshman/util"
const repository = new Repository()
// Reactive store of text notes
const textNotes = deriveEvents(repository, {
filters: [{kinds: [1], limit: 100}],
throttle: 100
})
// Reactive store of profiles mapped to custom objects
const profiles = deriveEventsMapped(repository, {
filters: [{kinds: [PROFILE]}],
eventToItem: event => readProfile(event),
itemToEvent: profile => profile.event,
includeDeleted: false
})
// Subscribe to updates
textNotes.subscribe(notes => {
console.log(`Found ${notes.length} text notes`)
})
profiles.subscribe(profiles => {
console.log(`Found ${profiles.length} profiles`)
})
// Add some events to the repository
repository.publish(someTextNoteEvent)
repository.publish(someProfileEvent)
```
+44
View File
@@ -0,0 +1,44 @@
# Synced Store
Utility for creating Svelte stores that automatically persist to and restore from localStorage.
## Functions
### synced(key, defaultValue)
Creates a writable store that synchronizes with localStorage using JSON serialization.
**Parameters:**
- `key` - localStorage key to store the value under
- `defaultValue` - Default value if nothing exists in localStorage
**Returns:** Writable Svelte store that persists changes to localStorage
The store automatically:
- Loads initial value from localStorage on creation
- Saves any changes back to localStorage
- Falls back to defaultValue if localStorage is empty or invalid
## Example
```typescript
import {synced} from "@welshman/store"
// Create a store that persists user preferences
const userPreferences = synced("user-prefs", {
theme: "dark",
notifications: true,
language: "en"
})
// Use like any writable store
userPreferences.subscribe(prefs => {
console.log("Preferences:", prefs)
})
// Update the store - automatically saves to localStorage
userPreferences.update(prefs => ({
...prefs,
theme: "light"
}))
```
+48
View File
@@ -0,0 +1,48 @@
# Throttled Store
Utility for wrapping Svelte stores to throttle subscriber notifications, reducing update frequency for performance.
## Functions
### throttled(delay, store)
Creates a throttled version of a store that limits how often subscribers are notified.
**Parameters:**
- `delay` - Throttle delay in milliseconds (0 disables throttling)
- `store` - Any readable Svelte store
**Returns:** Store with throttled subscription behavior
When `delay` is 0, returns the original store unchanged. Otherwise, wraps the store so that subscribers receive updates at most once per delay period.
## Example
```typescript
import {writable} from "svelte/store"
import {throttled} from "@welshman/store"
// Create a regular store that updates frequently
const fastStore = writable(0)
// Create a throttled version that only notifies every 100ms
const slowStore = throttled(100, fastStore)
// Subscribe to both stores
fastStore.subscribe(value => console.log("Fast:", value))
slowStore.subscribe(value => console.log("Slow:", value))
// Rapidly update the store
let count = 0
const interval = setInterval(() => {
fastStore.set(++count)
if (count >= 10) {
clearInterval(interval)
}
}, 10) // Update every 10ms
// Output:
// Fast: 1, Fast: 2, Fast: 3, ... (every update)
// Slow: 1, Slow: 5, Slow: 10 (throttled to ~100ms intervals)
```