diff --git a/package-lock.json b/package-lock.json index 51c6b3f..26b0ed6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2169,9 +2169,9 @@ } }, "node_modules/nostr-tools": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/nostr-tools/-/nostr-tools-2.7.0.tgz", - "integrity": "sha512-jJoL2J1CBiKDxaXZww27nY/Wsuxzx7AULxmGKFce4sskDu1tohNyfnzYQ8BvDyvkstU8kNZUAXPL32tre33uig==", + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/nostr-tools/-/nostr-tools-2.7.2.tgz", + "integrity": "sha512-Bq3Ug0SZFtgtL1+0wCnAe8AJtI7yx/00/a2nUug9SkhfOwlKS92Tef12iCK9FdwXw+oFZWMtRnSwcLayQso+xA==", "dependencies": { "@noble/ciphers": "^0.5.1", "@noble/curves": "1.2.0", @@ -3283,12 +3283,12 @@ }, "packages/dvm": { "name": "@welshman/dvm", - "version": "0.0.3", + "version": "0.0.4", "license": "MIT", "dependencies": { - "@welshman/lib": "0.0.13", - "@welshman/net": "0.0.16", - "@welshman/util": "0.0.24", + "@welshman/lib": "0.0.14", + "@welshman/net": "0.0.18", + "@welshman/util": "0.0.25", "nostr-tools": "^2.7.0" }, "devDependencies": { @@ -3297,29 +3297,11 @@ "typescript": "~5.1.6" } }, - "packages/dvm/node_modules/@scure/base": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/@scure/base/-/base-1.1.7.tgz", - "integrity": "sha512-PPNYBslrLNNUQ/Yad37MHYsNQtK67EhWb6WtSvNLLPo7SdVZgkUjD6Dg+5On7zNwmskf8OX7I7Nx5oN+MIWE0g==", - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "packages/dvm/node_modules/@welshman/net": { - "version": "0.0.16", - "resolved": "https://registry.npmjs.org/@welshman/net/-/net-0.0.16.tgz", - "integrity": "sha512-9NoNKs3BxMs7biyfhLtQFs0rJP5e8raMMjCXYL0+WaHTSPi1VaKBzXGlsikF713pJ0xm5kigAxa6wquonsWHtg==", - "dependencies": { - "@welshman/lib": "0.0.12", - "@welshman/util": "0.0.23", - "isomorphic-ws": "^5.0.0", - "ws": "^8.16.0" - } - }, "packages/dvm/node_modules/@welshman/net/node_modules/@welshman/lib": { "version": "0.0.12", "resolved": "https://registry.npmjs.org/@welshman/lib/-/lib-0.0.12.tgz", "integrity": "sha512-865dbPRbXdpZXNkAXz0KDgW0Yq6krC9Pz59cycfe6k75fDO1w4jA30UlW4E1mJuGQycmBtyi9sAkUPP9LG4Srw==", + "extraneous": true, "dependencies": { "@scure/base": "^1.1.6", "@types/events": "^3.0.3", @@ -3332,6 +3314,7 @@ "version": "0.0.23", "resolved": "https://registry.npmjs.org/@welshman/util/-/util-0.0.23.tgz", "integrity": "sha512-xV9RnPvO3XZ163TD/5ga/vCnLtejMpkIu9JlfiO/iyHJ1DdObq9WkyuHTgVDNxkrPBf74KuoF9gnub9rLMCj/w==", + "extraneous": true, "dependencies": { "@welshman/lib": "0.0.12", "nostr-tools": "^2.3.2" @@ -3339,10 +3322,10 @@ }, "packages/feeds": { "name": "@welshman/feeds", - "version": "0.0.13", + "version": "0.0.14", "license": "MIT", "dependencies": { - "@welshman/util": "0.0.24" + "@welshman/util": "0.0.25" }, "devDependencies": { "gts": "^5.0.1", @@ -3352,7 +3335,7 @@ }, "packages/lib": { "name": "@welshman/lib", - "version": "0.0.13", + "version": "0.0.14", "license": "MIT", "dependencies": { "@scure/base": "^1.1.6", @@ -3376,11 +3359,11 @@ }, "packages/net": { "name": "@welshman/net", - "version": "0.0.17", + "version": "0.0.18", "license": "MIT", "dependencies": { - "@welshman/lib": "0.0.13", - "@welshman/util": "0.0.24", + "@welshman/lib": "0.0.14", + "@welshman/util": "0.0.25", "isomorphic-ws": "^5.0.0", "ws": "^8.16.0" }, @@ -3392,12 +3375,13 @@ }, "packages/signer": { "name": "@welshman/signer", - "version": "0.0.1", + "version": "0.0.2", "license": "MIT", "dependencies": { - "@welshman/lib": "^0.0.13", - "@welshman/net": "^0.0.17", - "@welshman/util": "^0.0.24" + "@welshman/lib": "0.0.14", + "@welshman/net": "0.0.18", + "@welshman/util": "0.0.25", + "nostr-tools": "^2.7.2" }, "devDependencies": { "gts": "^5.0.1", @@ -3407,7 +3391,7 @@ }, "packages/store": { "name": "@welshman/store", - "version": "0.0.1", + "version": "0.0.2", "license": "MIT", "dependencies": { "svelte": "^4.2.18" @@ -3420,10 +3404,10 @@ }, "packages/util": { "name": "@welshman/util", - "version": "0.0.24", + "version": "0.0.25", "license": "MIT", "dependencies": { - "@welshman/lib": "0.0.13", + "@welshman/lib": "0.0.14", "nostr-tools": "^2.3.2" }, "devDependencies": { diff --git a/packages/content/package.json b/packages/content/package.json index a77729c..9907ea7 100644 --- a/packages/content/package.json +++ b/packages/content/package.json @@ -1,6 +1,6 @@ { "name": "@welshman/content", - "version": "0.0.6", + "version": "0.0.7", "author": "hodlbod", "license": "MIT", "description": "A collection of utilities for parsing nostr note content.", diff --git a/packages/dvm/package.json b/packages/dvm/package.json index c65ac66..ab90c86 100644 --- a/packages/dvm/package.json +++ b/packages/dvm/package.json @@ -1,6 +1,6 @@ { "name": "@welshman/dvm", - "version": "0.0.3", + "version": "0.0.4", "author": "hodlbod", "license": "MIT", "description": "A collection of utilities for building nostr DVMs.", @@ -31,9 +31,9 @@ "typescript": "~5.1.6" }, "dependencies": { - "@welshman/lib": "0.0.13", - "@welshman/net": "0.0.16", - "@welshman/util": "0.0.24", + "@welshman/lib": "0.0.14", + "@welshman/net": "0.0.18", + "@welshman/util": "0.0.25", "nostr-tools": "^2.7.0" } } diff --git a/packages/feeds/package.json b/packages/feeds/package.json index 03073a1..c4ab6cc 100644 --- a/packages/feeds/package.json +++ b/packages/feeds/package.json @@ -1,6 +1,6 @@ { "name": "@welshman/feeds", - "version": "0.0.13", + "version": "0.0.14", "author": "hodlbod", "license": "MIT", "description": "Utilities for building dynamic nostr feeds.", @@ -31,6 +31,6 @@ "typescript": "~5.1.6" }, "dependencies": { - "@welshman/util": "0.0.24" + "@welshman/util": "0.0.25" } } diff --git a/packages/lib/package.json b/packages/lib/package.json index aefdd31..e51d6c0 100644 --- a/packages/lib/package.json +++ b/packages/lib/package.json @@ -1,6 +1,6 @@ { "name": "@welshman/lib", - "version": "0.0.13", + "version": "0.0.14", "author": "hodlbod", "license": "MIT", "description": "A collection of utilities.", diff --git a/packages/lib/src/Tools.ts b/packages/lib/src/Tools.ts index 76f35d6..966a25b 100644 --- a/packages/lib/src/Tools.ts +++ b/packages/lib/src/Tools.ts @@ -355,6 +355,20 @@ export const once = (f: (...args: any) => void) => { } } +export const memoize = (f: (...args: any[]) => T) => { + let prevArgs: any[] + let result: T + + return (...args: any[]) => { + if (!equals(prevArgs, args)) { + prevArgs = args + result = f(...args) + } + + return result + } +} + export const batch = (t: number, f: (xs: T[]) => void) => { const xs: T[] = [] const cb = throttle(t, () => xs.length > 0 && f(xs.splice(0))) @@ -365,6 +379,30 @@ export const batch = (t: number, f: (xs: T[]) => void) => { } } +export const batcher = (t: number, execute: (request: T[]) => U[] | Promise) => { + const queue: {request: T, resolve: (x: U) => void}[] = [] + + const _execute = async () => { + const items = queue.splice(0) + const results = await execute(items.map(item => item.request)) + + if (results.length !== items.length) { + throw new Error("Execute must return a promise for each request") + } + + results.forEach(async (r, i) => items[i].resolve(await r)) + } + + return (request: T): Promise => + new Promise(resolve => { + if (queue.length === 0) { + setTimeout(_execute, t) + } + + queue.push({request, resolve}) + }) +} + export const addToKey = (m: Record>, k: string, v: T) => { const s = m[k] || new Set() diff --git a/packages/net/package.json b/packages/net/package.json index ec783f8..8b01893 100644 --- a/packages/net/package.json +++ b/packages/net/package.json @@ -1,6 +1,6 @@ { "name": "@welshman/net", - "version": "0.0.17", + "version": "0.0.18", "author": "hodlbod", "license": "MIT", "description": "Utilities for connecting with nostr relays.", @@ -31,8 +31,8 @@ "typescript": "~5.1.6" }, "dependencies": { - "@welshman/lib": "0.0.13", - "@welshman/util": "0.0.24", + "@welshman/lib": "0.0.14", + "@welshman/util": "0.0.25", "isomorphic-ws": "^5.0.0", "ws": "^8.16.0" } diff --git a/packages/net/src/target/Local.ts b/packages/net/src/target/Local.ts index 9dfb4fa..ed15ee3 100644 --- a/packages/net/src/target/Local.ts +++ b/packages/net/src/target/Local.ts @@ -1,9 +1,10 @@ import {Emitter} from '@welshman/lib' +import type {TrustedEvent} from '@welshman/util' import {Relay, LOCAL_RELAY_URL} from '@welshman/util' import type {Message} from '../Socket' -export class Local extends Emitter { - constructor(readonly relay: Relay) { +export class Local extends Emitter { + constructor(readonly relay: Relay) { super() relay.on('*', this.onMessage) diff --git a/packages/signer/package.json b/packages/signer/package.json index 6fa44b9..b0e9d36 100644 --- a/packages/signer/package.json +++ b/packages/signer/package.json @@ -1,6 +1,6 @@ { "name": "@welshman/signer", - "version": "0.0.1", + "version": "0.0.2", "author": "hodlbod", "license": "MIT", "description": "A nostr signer implemenation supporting several login methods.", @@ -31,8 +31,9 @@ "typescript": "~5.1.6" }, "dependencies": { - "@welshman/lib": "^0.0.13", - "@welshman/net": "^0.0.17", - "@welshman/util": "^0.0.24" + "@welshman/lib": "0.0.14", + "@welshman/net": "0.0.18", + "@welshman/util": "0.0.25", + "nostr-tools": "^2.7.2" } } diff --git a/packages/signer/src/signers/nip46.ts b/packages/signer/src/signers/nip46.ts index b39d05b..88ed2b2 100644 --- a/packages/signer/src/signers/nip46.ts +++ b/packages/signer/src/signers/nip46.ts @@ -55,7 +55,7 @@ export class Nip46Broker extends Emitter { this.#sub = this.subscribe() } - subscribe() { + subscribe = () => { const sub = subscribe({ relays: this.handler.relays, filters: [ @@ -91,7 +91,7 @@ export class Nip46Broker extends Emitter { return sub } - async request(method: string, params: string[], admin = false) { + request = async (method: string, params: string[], admin = false) => { // nsecbunker has a race condition await this.#ready @@ -105,7 +105,7 @@ export class Nip46Broker extends Emitter { publish({event, relays: this.handler.relays}) this.once(`auth-${id}`, res => { - window.open(res.result, "Coracle", "width=600,height=800,popup=yes") + window.open(res.error, "Coracle", "width=600,height=800,popup=yes") }) return new Promise((resolve, reject) => { @@ -119,7 +119,7 @@ export class Nip46Broker extends Emitter { }) } - createAccount(username: string) { + createAccount = (username: string) => { if (!this.handler.domain) { throw new Error("Unable to create an account without a handler domain") } @@ -127,7 +127,7 @@ export class Nip46Broker extends Emitter { return this.request("create_account", [username, this.handler.domain, "", Perms], true) } - async connect(token = "") { + connect = async (token = "") => { if (!this.#connectResult) { const params = [this.pubkey, token, Perms] @@ -137,27 +137,27 @@ export class Nip46Broker extends Emitter { return this.#connectResult === "ack" } - async signEvent(event: EventTemplate) { + signEvent = async (event: EventTemplate) => { return JSON.parse(await this.request("sign_event", [JSON.stringify(event)]) as string) } - nip04Encrypt(pk: string, message: string) { + nip04Encrypt = (pk: string, message: string) => { return this.request("nip04_encrypt", [pk, message]) } - nip04Decrypt(pk: string, message: string) { + nip04Decrypt = (pk: string, message: string) => { return this.request("nip04_decrypt", [pk, message]) } - nip44Encrypt(pk: string, message: string) { + nip44Encrypt = (pk: string, message: string) => { return this.request("nip44_encrypt", [pk, message]) } - nip44Decrypt(pk: string, message: string) { + nip44Decrypt = (pk: string, message: string) => { return this.request("nip44_decrypt", [pk, message]) } - teardown() { + teardown = () => { this.#closed = true this.#sub?.close() } diff --git a/packages/store/package.json b/packages/store/package.json index 8972802..2bcc160 100644 --- a/packages/store/package.json +++ b/packages/store/package.json @@ -1,6 +1,6 @@ { "name": "@welshman/store", - "version": "0.0.1", + "version": "0.0.2", "author": "hodlbod", "license": "MIT", "description": "A collection of utilities based on svelte/store for use with welshman", diff --git a/packages/store/src/index.ts b/packages/store/src/index.ts index 4550530..7401a0f 100644 --- a/packages/store/src/index.ts +++ b/packages/store/src/index.ts @@ -68,8 +68,8 @@ export function withGetter(store: Readable | Writable) { export const throttled = (delay: number, store: Readable) => custom(set => store.subscribe(throttle(delay, set))) -export const createEventStore = (repository: Repository) => { - let subs: Sub[] = [] +export const createEventStore = (repository: Repository) => { + let subs: Sub[] = [] const onUpdate = throttle(300, () => { const $events = repository.dump() @@ -81,8 +81,8 @@ export const createEventStore = (repository: Repository) => { return { get: () => repository.dump(), - set: (events: TrustedEvent[]) => repository.load(events), - subscribe: (f: Sub) => { + set: (events: E[]) => repository.load(events), + subscribe: (f: Sub) => { f(repository.dump()) subs.push(f) @@ -102,7 +102,7 @@ export const createEventStore = (repository: Repository) => { } } -export const deriveEventsMapped = ({ +export const deriveEventsMapped = ({ filters, repository, eventToItem, @@ -110,9 +110,9 @@ export const deriveEventsMapped = ({ includeDeleted = false, }: { filters: Filter[] - repository: Repository, - eventToItem: (event: TrustedEvent) => T - itemToEvent: (item: T) => TrustedEvent + repository: Repository, + eventToItem: (event: E) => T + itemToEvent: (item: T) => E includeDeleted?: boolean }) => custom(setter => { @@ -120,7 +120,7 @@ export const deriveEventsMapped = ({ setter(data) - const onUpdate = batch(300, (updates: {added: TrustedEvent[]; removed: Set}[]) => { + const onUpdate = batch(300, (updates: {added: E[]; removed: Set}[]) => { const removed = new Set() const added = new Map() @@ -150,7 +150,7 @@ export const deriveEventsMapped = ({ if (!includeDeleted && removed.size > 0) { const [deleted, ok] = partition( - (item: T) => getIdAndAddress(itemToEvent(item)).some(id => removed.has(id)), + (item: T) => getIdAndAddress(itemToEvent(item)).some((id: string) => removed.has(id)), data, ) @@ -170,15 +170,15 @@ export const deriveEventsMapped = ({ return () => repository.off("update", onUpdate) }) -export const deriveEvents = (repository: Repository, opts: {filters: Filter[]; includeDeleted?: boolean}) => - deriveEventsMapped({ +export const deriveEvents = (repository: Repository, opts: {filters: Filter[]; includeDeleted?: boolean}) => + deriveEventsMapped({ ...opts, repository, eventToItem: identity, itemToEvent: identity, }) -export const deriveEvent = (repository: Repository, idOrAddress: string) => +export const deriveEvent = (repository: Repository, idOrAddress: string) => derived( deriveEvents(repository, { filters: getIdFilters([idOrAddress]), @@ -187,7 +187,7 @@ export const deriveEvent = (repository: Repository, idOrAddress: string) => first ) -export const deriveIsDeletedByAddress = (repository: Repository, event: TrustedEvent) => +export const deriveIsDeletedByAddress = (repository: Repository, event: E) => custom(setter => { setter(repository.isDeletedByAddress(event)) diff --git a/packages/util/package.json b/packages/util/package.json index 869aac2..f9d70a6 100644 --- a/packages/util/package.json +++ b/packages/util/package.json @@ -1,6 +1,6 @@ { "name": "@welshman/util", - "version": "0.0.24", + "version": "0.0.25", "author": "hodlbod", "license": "MIT", "description": "A collection of nostr-related utilities.", @@ -31,7 +31,7 @@ "typescript": "~5.1.6" }, "dependencies": { - "@welshman/lib": "0.0.13", + "@welshman/lib": "0.0.14", "nostr-tools": "^2.3.2" } } diff --git a/packages/util/src/Relay.ts b/packages/util/src/Relay.ts index 8ea17fb..69ff02f 100644 --- a/packages/util/src/Relay.ts +++ b/packages/util/src/Relay.ts @@ -48,22 +48,22 @@ export const normalizeRelayUrl = (url: string, {allowInsecure = false}: Normaliz return prefix + url } -export class Relay extends Emitter { +export class Relay extends Emitter { subs = new Map() - constructor(readonly repository: Repository) { + constructor(readonly repository: Repository) { super() } send(type: string, ...message: any[]) { switch(type) { - case 'EVENT': return this.handleEVENT(message as [TrustedEvent]) + case 'EVENT': return this.handleEVENT(message as [T]) case 'CLOSE': return this.handleCLOSE(message as [string]) case 'REQ': return this.handleREQ(message as [string, ...Filter[]]) } } - handleEVENT([event]: [TrustedEvent]) { + handleEVENT([event]: [T]) { this.repository.publish(event) // Callers generally expect async relays diff --git a/packages/util/src/Repository.ts b/packages/util/src/Repository.ts index 8e7a789..a34f25f 100644 --- a/packages/util/src/Repository.ts +++ b/packages/util/src/Repository.ts @@ -10,13 +10,13 @@ export const DAY = 86400 const getDay = (ts: number) => Math.floor(ts / DAY) -export class Repository extends Emitter { - eventsById = new Map() - eventsByWrap = new Map() - eventsByAddress = new Map() - eventsByTag = new Map() - eventsByDay = new Map() - eventsByAuthor = new Map() +export class Repository extends Emitter { + eventsById = new Map() + eventsByWrap = new Map() + eventsByAddress = new Map() + eventsByTag = new Map() + eventsByDay = new Map() + eventsByAuthor = new Map() deletes = new Map() // Dump/load/clear @@ -25,7 +25,7 @@ export class Repository extends Emitter { return Array.from(this.eventsById.values()) } - load = async (events: TrustedEvent[], chunkSize = 1000) => { + load = async (events: T[], chunkSize = 1000) => { this.clear() const added = [] @@ -69,7 +69,7 @@ export class Repository extends Emitter { : this.eventsById.get(idOrAddress) } - hasEvent = (event: TrustedEvent) => { + hasEvent = (event: T) => { const duplicate = ( this.eventsById.get(event.id) || this.eventsByAddress.get(getAddress(event)) @@ -79,12 +79,12 @@ export class Repository extends Emitter { } query = (filters: Filter[], {includeDeleted = false} = {}) => { - const result: TrustedEvent[][] = [] + const result: T[][] = [] for (let filter of filters) { - let events: TrustedEvent[] = Array.from(this.eventsById.values()) + let events: T[] = Array.from(this.eventsById.values()) if (filter.ids) { - events = filter.ids!.map(id => this.eventsById.get(id)).filter(identity) as TrustedEvent[] + events = filter.ids!.map(id => this.eventsById.get(id)).filter(identity) as T[] filter = omit(['ids'], filter) } else if (filter.authors) { events = uniq(filter.authors!.flatMap(pubkey => this.eventsByAuthor.get(pubkey) || [])) @@ -112,8 +112,8 @@ export class Repository extends Emitter { } } - const chunk: TrustedEvent[] = [] - for (const event of sortBy((e: TrustedEvent) => -e.created_at, events)) { + const chunk: T[] = [] + for (const event of sortBy((e: T) => -e.created_at, events)) { if (filter.limit && chunk.length >= filter.limit) { break } @@ -133,7 +133,7 @@ export class Repository extends Emitter { return uniq(flatten(result)) } - publish = (event: TrustedEvent, {shouldNotify = true} = {}): boolean => { + publish = (event: T, {shouldNotify = true} = {}): boolean => { if (!isTrustedEvent(event)) { throw new Error("Invalid event published to Repository", event) } @@ -203,19 +203,19 @@ export class Repository extends Emitter { return true } - isDeletedByAddress = (event: TrustedEvent) => (this.deletes.get(getAddress(event)) || 0) > event.created_at + isDeletedByAddress = (event: T) => (this.deletes.get(getAddress(event)) || 0) > event.created_at - isDeletedById = (event: TrustedEvent) => (this.deletes.get(event.id) || 0) > event.created_at + isDeletedById = (event: T) => (this.deletes.get(event.id) || 0) > event.created_at - isDeleted = (event: TrustedEvent) => this.isDeletedByAddress(event) || this.isDeletedById(event) + isDeleted = (event: T) => this.isDeletedByAddress(event) || this.isDeletedById(event) // Utilities - _updateIndex(m: Map, k: K, e: TrustedEvent, duplicate?: TrustedEvent) { + _updateIndex(m: Map, k: K, e: T, duplicate?: T) { let a = m.get(k) || [] if (duplicate) { - a = a.filter((x: TrustedEvent) => x !== duplicate) + a = a.filter((x: T) => x !== duplicate) } a.push(e)