Update docs, tweak url based event derivation

This commit is contained in:
Jon Staab
2025-11-20 15:08:59 -08:00
parent 6d36f5a912
commit 2fec078a5b
12 changed files with 154 additions and 317 deletions
+26 -18
View File
@@ -13,37 +13,45 @@ Welshman extends Nostr's base subscription model with intelligent caching, repos
The base functionality for subscription management is implemented in `@welshman/net`. Please refer to [the documentation](/net) for that module for details. 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 ## Indexed 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. Create indexed stores with automatic loading using repository derivations and loader utilities:
```typescript ```typescript
const { import {deriveItemsByKey, deriveItems, makeDeriveItem, makeLoadItem, getter} from "@welshman/store"
indexStore, // Map of all items by key
deriveItem, // Get reactive item by key // Create indexed map from repository
loadItem // Trigger network load const itemsByKey = deriveItemsByKey({
} = collection({ repository,
name: "storeName", // For persistence filters: [{kinds: [SOME_KIND]}],
store: writable([]), // Base store eventToItem: event => transformEvent(event),
getKey: item => item.id // How to index items getKey: item => item.id
load: async (key) => { // Network loader
// Load logic here
}
}) })
// Create array view
const items = deriveItems(itemsByKey)
// Create getter for accessing map
const getItemsByKey = getter(itemsByKey)
// Create loader
const loadItem = makeLoadItem(fetchItem, key => getItemsByKey().get(key))
// Create deriver with automatic loading
const deriveItem = makeDeriveItem(itemsByKey, loadItem)
``` ```
### Deriving Events ### 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`. Query events from the repository using `deriveEventsById` and `deriveEvents`:
```typescript ```typescript
import {deriveEventsMapped} from "@welshman/store" import {deriveEventsById, deriveEvents} from "@welshman/store"
export const notes = deriveEvents<TrustedEvent>(repository, {filters: [{kinds: [NOTE]}]}) const noteEventsById = deriveEventsById({repository, filters: [{kinds: [NOTE]}]})
export const notes = deriveEvents(noteEventsById)
``` ```
A collection could then be created by passing the `notes` store to `collection`.
### Available Collections ### Available Collections
Several common collections are built-in and ready for use: Several common collections are built-in and ready for use:
-71
View File
@@ -1,71 +0,0 @@
# 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/net"
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
@@ -1,56 +0,0 @@
# 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()
```
+10 -49
View File
@@ -1,55 +1,16 @@
# Getter # 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. Utility for creating optimized getter functions that adapt based on access patterns.
## 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 ```typescript
import {writable, derived} from "svelte/store" // Create optimized getter that switches to subscription when hot
import {withGetter, getter} from "@welshman/store" getter<T>(store: Readable<T>, options?: {
threshold?: number // Calls per second before switching to subscription (default: 10)
}): () => T
// Create enhanced stores with getter methods // Add .get() method to a store
const count = withGetter(writable(0)) withGetter<T>(store: Readable<T>): ReadableWithGetter<T>
const doubled = withGetter(derived(count, $count => $count * 2)) withGetter<T>(store: Writable<T>): WritableWithGetter<T>
// 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
``` ```
The `getter` function automatically switches between `get()` and subscription based on call frequency, optimizing performance for hot code paths.
+5 -4
View File
@@ -16,15 +16,16 @@ A utility package providing welshman-specific svelte store functionality and uti
```typescript ```typescript
import {Repository, NAMED_PEOPLE, TrustedEvent, PublishedList, readList} from '@welshman/util' import {Repository, NAMED_PEOPLE, TrustedEvent, PublishedList, readList} from '@welshman/util'
import {deriveEventsMapped} from '@welshman/store' import {deriveItemsByKey} from '@welshman/store'
const repository = new Repository() const repository = new Repository()
// Create a store that performantly maps matching events in the repository to List objects // Create a reactive map of lists indexed by pubkey
const lists = deriveEventsMapped<PublishedList>(repository, { const listsByPubkey = deriveItemsByKey<PublishedList>({
repository,
filters: [{kinds: [NAMED_PEOPLE]}], filters: [{kinds: [NAMED_PEOPLE]}],
eventToItem: (event: TrustedEvent) => (event.tags.length > 1 ? readList(event) : null), eventToItem: (event: TrustedEvent) => (event.tags.length > 1 ? readList(event) : null),
itemToEvent: (list: PublishedList) => list.event, getKey: (list: PublishedList) => list.event.pubkey,
}) })
``` ```
+66 -38
View File
@@ -1,74 +1,102 @@
# Repository # Repository
Reactive Svelte stores for querying and mapping events from a Repository with automatic updates. Reactive Svelte stores for querying events from a Repository with automatic updates.
## Functions ## Event Stores
### deriveEventsMapped(repository, options) ```typescript
// Derive map of events by ID
deriveEventsById(options: {
repository: Repository
filters: Filter[]
includeDeleted?: boolean
}): Readable<Map<string, TrustedEvent>>
Creates a reactive store that maps events to custom items and keeps them synchronized with repository updates. // Convert events by ID to array
deriveEvents(eventsByIdStore: Readable<Map<string, TrustedEvent>>): Readable<TrustedEvent[]>
**Options:** // Sort events ascending by created_at
- `filters` - Array of Nostr filters to query events deriveEventsAsc(eventsStore: Readable<TrustedEvent[]>): Readable<TrustedEvent[]>
- `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) // Sort events descending by created_at
deriveEventsDesc(eventsStore: Readable<TrustedEvent[]>): Readable<TrustedEvent[]>
Creates a reactive store of events without transformation. // Derive single event by ID or address
deriveEvent(repository: Repository, idOrAddress: string): Readable<TrustedEvent | undefined>
**Options:** // Track if event is deleted
- `filters` - Array of Nostr filters deriveIsDeleted(repository: Repository, event: TrustedEvent): Readable<boolean>
- `throttle?` - Throttle updates ```
- `includeDeleted?` - Include deleted events
### deriveEvent(repository, idOrAddress) ## Indexed Collections
Creates a reactive store for a single event by ID or address. ```typescript
// Create indexed map of items from repository events
deriveItemsByKey<T>(options: {
repository: Repository
filters: Filter[]
eventToItem: (event: TrustedEvent) => MaybeAsync<Maybe<T>>
getKey: (item: T) => string
includeDeleted?: boolean
}): Readable<Map<string, T>>
### deriveIsDeleted(repository, event) // Convert itemsByKey map to array
deriveItems<T>(itemsByKeyStore: Readable<Map<string, T>>): Readable<T[]>
Creates a reactive store that tracks whether an event is deleted. // Create function to derive single item by key
makeDeriveItem<T>(
itemsByKeyStore: Readable<Map<string, T>>,
onDerive?: (key: string, ...args: any[]) => void
): (key: string, ...args: any[]) => Readable<T | undefined>
### deriveIsDeletedByAddress(repository, event) // Create cached loader with staleness checking and exponential backoff
makeLoadItem<T>(
loadItem: (key: string, ...args: any[]) => Promise<unknown>,
getItem: (key: string) => T | undefined,
options?: {getFetched?, setFetched?, timeout?}
): (key: string, ...args: any[]) => Promise<T | undefined>
Creates a reactive store that tracks whether an event is deleted by address. // Create loader that always fetches fresh data
makeForceLoadItem<T>(
loadItem: (key: string, ...args: any[]) => Promise<unknown>,
getItem: (key: string) => T | undefined
): (key: string, ...args: any[]) => Promise<T | undefined>
// Optimized getter that switches to subscription when hot
getter<T>(store: Readable<T>, options?: {threshold?: number}): () => T
```
## Example ## Example
```typescript ```typescript
import {Repository} from "@welshman/net" import {Repository} from "@welshman/net"
import {deriveEventsMapped, deriveEvents} from "@welshman/store" import {deriveEventsById, deriveEvents, deriveItemsByKey, deriveItems} from "@welshman/store"
import {readProfile, PROFILE} from "@welshman/util" import {readProfile, PROFILE} from "@welshman/util"
const repository = new Repository() const repository = new Repository()
// Reactive store of text notes // Reactive store of text notes
const textNotes = deriveEvents(repository, { const noteEventsById = deriveEventsById({
filters: [{kinds: [1], limit: 100}], repository,
throttle: 100 filters: [{kinds: [1], limit: 100}]
}) })
const notes = deriveEvents(noteEventsById)
// Reactive store of profiles mapped to custom objects // Reactive store of profiles indexed by pubkey
const profiles = deriveEventsMapped(repository, { const profilesByPubkey = deriveItemsByKey({
repository,
filters: [{kinds: [PROFILE]}], filters: [{kinds: [PROFILE]}],
eventToItem: event => readProfile(event), eventToItem: event => readProfile(event),
itemToEvent: profile => profile.event, getKey: profile => profile.event.pubkey
includeDeleted: false
}) })
const profiles = deriveItems(profilesByPubkey)
// Subscribe to updates // Subscribe to updates
textNotes.subscribe(notes => { notes.subscribe($notes => {
console.log(`Found ${notes.length} text notes`) console.log(`Found ${$notes.length} text notes`)
}) })
profiles.subscribe(profiles => { profiles.subscribe($profiles => {
console.log(`Found ${profiles.length} profiles`) console.log(`Found ${$profiles.length} profiles`)
}) })
// Add some events to the repository
repository.publish(someTextNoteEvent)
repository.publish(someProfileEvent)
``` ```
+2 -2
View File
@@ -85,12 +85,12 @@ const _relayGetter = (fn?: (relay: RelayProfile) => any) =>
}) })
export const getPubkeyRelays = (pubkey: string, mode?: RelayMode) => export const getPubkeyRelays = (pubkey: string, mode?: RelayMode) =>
mode === RelayMode.Messages mode === RelayMode.Messaging
? getRelaysFromList(getMessagingRelayList(pubkey)) ? getRelaysFromList(getMessagingRelayList(pubkey))
: getRelaysFromList(getRelayList(pubkey), mode) : getRelaysFromList(getRelayList(pubkey), mode)
export const derivePubkeyRelays = (pubkey: string, mode?: RelayMode) => export const derivePubkeyRelays = (pubkey: string, mode?: RelayMode) =>
mode === RelayMode.Messages mode === RelayMode.Messaging
? derived(deriveMessagingRelayList(pubkey), list => getRelaysFromList(list)) ? derived(deriveMessagingRelayList(pubkey), list => getRelaysFromList(list))
: derived(deriveRelayList(pubkey), list => getRelaysFromList(list, mode)) : derived(deriveRelayList(pubkey), list => getRelaysFromList(list, mode))
+2
View File
@@ -15,6 +15,8 @@ export const messagingRelayLists = deriveItems(messagingRelayListsByPubkey)
export const getMessagingRelayListsByPubkey = getter(messagingRelayListsByPubkey) export const getMessagingRelayListsByPubkey = getter(messagingRelayListsByPubkey)
export const getMessagingRelayLists = getter(messagingRelayLists)
export const getMessagingRelayList = (pubkey: string) => getMessagingRelayListsByPubkey().get(pubkey) export const getMessagingRelayList = (pubkey: string) => getMessagingRelayListsByPubkey().get(pubkey)
export const forceLoadMessagingRelayList = makeForceLoadItem(makeOutboxLoader(MESSAGING_RELAYS), getMessagingRelayList) export const forceLoadMessagingRelayList = makeForceLoadItem(makeOutboxLoader(MESSAGING_RELAYS), getMessagingRelayList)
+1 -1
View File
@@ -634,7 +634,7 @@ export const sort = <T>(xs: T[]) => [...xs].sort()
* @param xs - Array to sort * @param xs - Array to sort
* @returns Sorted array * @returns Sorted array
*/ */
export const sortBy = <T>(f: (x: T) => any, xs: T[]) => export const sortBy = <T>(f: (x: T) => any, xs: Iterable<T>) =>
[...xs].sort((a: T, b: T) => { [...xs].sort((a: T, b: T) => {
const x = f(a) const x = f(a)
const y = f(b) const y = f(b)
+3 -3
View File
@@ -174,14 +174,14 @@ export class Router {
FromUser = () => this.FromRelays(this.getRelaysForUser(RelayMode.Write)) FromUser = () => this.FromRelays(this.getRelaysForUser(RelayMode.Write))
UserMessages = () => this.FromRelays(this.getRelaysForUser(RelayMode.Messages)) MessagesForUser = () => this.FromRelays(this.getRelaysForUser(RelayMode.Messaging))
ForPubkey = (pubkey: string) => this.FromRelays(this.getRelaysForPubkey(pubkey, RelayMode.Read)) ForPubkey = (pubkey: string) => this.FromRelays(this.getRelaysForPubkey(pubkey, RelayMode.Read))
FromPubkey = (pubkey: string) => this.FromRelays(this.getRelaysForPubkey(pubkey, RelayMode.Write)) FromPubkey = (pubkey: string) => this.FromRelays(this.getRelaysForPubkey(pubkey, RelayMode.Write))
MessagesForPubkey = (pubkey: string) => MessagesForPubkey = (pubkey: string) =>
this.FromRelays(this.getRelaysForPubkey(pubkey, RelayMode.Messages)) this.FromRelays(this.getRelaysForPubkey(pubkey, RelayMode.Messaging))
ForPubkeys = (pubkeys: string[]) => this.merge(pubkeys.map(pubkey => this.ForPubkey(pubkey))) ForPubkeys = (pubkeys: string[]) => this.merge(pubkeys.map(pubkey => this.ForPubkey(pubkey)))
@@ -371,7 +371,7 @@ export const getFilterSelectionsForWraps = (filter: Filter) => {
return [ return [
{ {
filter: {...filter, kinds: [WRAP]}, filter: {...filter, kinds: [WRAP]},
scenario: Router.get().UserMessages(), scenario: Router.get().MessagesForUser(),
}, },
] ]
} }
+38 -74
View File
@@ -1,5 +1,5 @@
import {derived, readable, Readable} from "svelte/store" import {derived, readable, Readable} from "svelte/store"
import {on, now, indexBy, mapPop, Maybe, MaybeAsync, call, sortBy, first} from "@welshman/lib" import {on, assoc, now, indexBy, mapPop, Maybe, MaybeAsync, call, sortBy, first} from "@welshman/lib"
import {matchFilters, getIdFilters, Filter, TrustedEvent} from "@welshman/util" import {matchFilters, getIdFilters, Filter, TrustedEvent} from "@welshman/util"
import {Repository, RepositoryUpdate, Tracker} from "@welshman/net" import {Repository, RepositoryUpdate, Tracker} from "@welshman/net"
import {deriveDeduplicated} from "./misc.js" import {deriveDeduplicated} from "./misc.js"
@@ -48,117 +48,84 @@ export const deriveEventsById = ({
export const deriveEvents = (eventsByIdStore: Readable<EventsById>) => export const deriveEvents = (eventsByIdStore: Readable<EventsById>) =>
deriveDeduplicated(eventsByIdStore, eventsById => Array.from(eventsById.values())) deriveDeduplicated(eventsByIdStore, eventsById => Array.from(eventsById.values()))
export const deriveEventsAsc = (eventsStore: Readable<TrustedEvent[]>) => export const deriveEventsAsc = (eventsByIdStore: Readable<EventsById>) =>
deriveDeduplicated(eventsStore, events => sortBy(e => e.created_at, events)) deriveDeduplicated(eventsByIdStore, eventsById => sortBy(e => e.created_at, eventsById.values()))
export const deriveEventsDesc = (eventsStore: Readable<TrustedEvent[]>) => export const deriveEventsDesc = (eventsByIdStore: Readable<EventsById>) =>
deriveDeduplicated(eventsStore, events => sortBy(e => -e.created_at, events)) deriveDeduplicated(eventsByIdStore, eventsById => sortBy(e => -e.created_at, eventsById.values()))
// Events by id by url // Events by id by url
export type EventsByIdByUrl = Map<string, EventsById> export type DeriveEventsByIdForUrlOptions = DeriveEventsByIdOptions & {
url: string
export type DeriveEventsByIdByUrlOptions = DeriveEventsByIdOptions & {
tracker: Tracker tracker: Tracker
} }
export const deriveEventsByIdByUrl = ({ export const deriveEventsByIdForUrl = ({
url,
filters, filters,
tracker, tracker,
repository, repository,
includeDeleted, includeDeleted,
}: DeriveEventsByIdByUrlOptions) => { }: DeriveEventsByIdForUrlOptions) => {
const eventsByIdByUrl: EventsByIdByUrl = new Map() const eventsById: EventsById = new Map()
const addEvent = (url: string, event: TrustedEvent) => { const initialize = () => {
const eventsById = eventsByIdByUrl.get(url) const initialIds = Array.from(tracker.getIds(url))
const initialFilters = filters.map(assoc('ids', initialIds))
if (!eventsById?.has(event.id)) { for (const event of repository.query(initialFilters, {includeDeleted})) {
// Create a new map so we can detect which key changed eventsById.set(event.id, event)
const newEventsById = new Map(eventsById)
newEventsById.set(event.id, event)
eventsByIdByUrl.set(url, newEventsById)
return true
} }
return false return eventsById
} }
const removeEvent = (url: string, id: string) => { return readable(initialize(), set => {
const eventsById = eventsByIdByUrl.get(url)
if (eventsById?.has(id)) {
eventsById.delete(id)
if (eventsById.size === 0) {
eventsByIdByUrl.delete(url)
} else {
// Create a new map so we can detect which key changed
eventsByIdByUrl.set(url, new Map(eventsById))
}
return true
}
return false
}
for (const event of repository.query(filters, {includeDeleted})) {
for (const url of tracker.getRelays(event.id)) {
addEvent(url, event)
}
}
return readable(eventsByIdByUrl, set => {
const unsubscribers = [ const unsubscribers = [
on(repository, "update", ({added, removed}: RepositoryUpdate) => { on(repository, "update", ({added, removed}: RepositoryUpdate) => {
let dirty = false let dirty = false
for (const event of added) { for (const event of added) {
for (const url of tracker.getRelays(event.id)) { if (tracker.hasRelay(event.id, url) && !eventsById.has(event.id)) {
dirty = dirty || addEvent(url, event) eventsById.set(event.id, event)
dirty = true
} }
} }
for (const id of removed) { for (const id of removed) {
for (const url of tracker.getRelays(id)) { eventsById.delete(id)
dirty = dirty || removeEvent(url, id) dirty = true
}
} }
if (dirty) { if (dirty) {
set(eventsByIdByUrl) set(eventsById)
} }
}), }),
on(tracker, "add", (id: string, url: string) => { on(tracker, "add", (id: string, url: string) => {
const event = repository.getEvent(id) const event = repository.getEvent(id)
if (event && addEvent(url, event)) { if (event && tracker.hasRelay(id, url) && !eventsById.has(id)) {
set(eventsByIdByUrl) eventsById.set(id, event)
set(eventsById)
} }
}), }),
on(tracker, "remove", (id: string, url: string) => { on(tracker, "remove", (id: string, url: string) => {
if (removeEvent(url, id)) { if (eventsById.has(id)) {
set(eventsByIdByUrl) eventsById.delete(id)
set(eventsById)
} }
}), }),
on(tracker, "load", () => { on(tracker, "load", () => {
eventsByIdByUrl.clear() eventsById.clear()
initialize()
for (const event of repository.query(filters, {includeDeleted})) { set(eventsById)
for (const url of tracker.getRelays(event.id)) {
addEvent(url, event)
}
}
set(eventsByIdByUrl)
}), }),
on(tracker, "clear", () => { on(tracker, "clear", () => {
eventsByIdByUrl.clear() eventsById.clear()
set(eventsByIdByUrl) set(eventsById)
}), }),
] ]
@@ -166,11 +133,6 @@ export const deriveEventsByIdByUrl = ({
}) })
} }
export const deriveEventsByIdForUrl = (
url: string,
eventsByIdByUrlStore: Readable<EventsByIdByUrl>,
) => deriveDeduplicated(eventsByIdByUrlStore, eventsByIdByUrl => eventsByIdByUrl.get(url))
// Items by key // Items by key
export type ItemsByKey<T> = Map<string, T> export type ItemsByKey<T> = Map<string, T>
@@ -268,7 +230,9 @@ export const makeDeriveItem = <T>(
itemsByKeyStore: Readable<ItemsByKey<T>>, itemsByKeyStore: Readable<ItemsByKey<T>>,
onDerive?: (key: string, ...args: any[]) => void, onDerive?: (key: string, ...args: any[]) => void,
) => { ) => {
return (key: string, ...args: any[]) => { return (key?: string, ...args: any[]) => {
if (!key) return readable(undefined)
onDerive?.(key, ...args) onDerive?.(key, ...args)
return deriveDeduplicated(itemsByKeyStore, itemsByKey => itemsByKey.get(key)) return deriveDeduplicated(itemsByKeyStore, itemsByKey => itemsByKey.get(key))
+1 -1
View File
@@ -5,7 +5,7 @@ import {last, normalizeUrl, stripProtocol} from "@welshman/lib"
export enum RelayMode { export enum RelayMode {
Read = "read", Read = "read",
Write = "write", Write = "write",
Messages = "messages", Messaging = "messaging",
} }
export type RelayProfile = { export type RelayProfile = {