diff --git a/.changeset/lazy-emus-warn.md b/.changeset/lazy-emus-warn.md new file mode 100644 index 00000000..35f224cb --- /dev/null +++ b/.changeset/lazy-emus-warn.md @@ -0,0 +1,15 @@ +--- +'@nostrwatch/nostrings': minor +'@nostrwatch/trawler': minor +'@nostrwatch/nocapd': minor +'@nostrwatch/nocap-every-adapter-default': patch +'@nostrwatch/kit-adapter-idb': patch +'@nostrwatch/demo-kit-with-idb': patch +'@nostrwatch/sanitize': patch +'@nostrwatch/nip11': patch +'@nostrwatch/nocap': patch +'@nostrwatch/seed': patch +'@nostrwatch/idb': patch +--- + +nostrings + nocapd improvements diff --git a/apps/nocapd/src/classes/Persist.js b/apps/nocapd/src/classes/Persist.js deleted file mode 100644 index 44aa6269..00000000 --- a/apps/nocapd/src/classes/Persist.js +++ /dev/null @@ -1,45 +0,0 @@ -import { PersistQueue, BullMQ } from '@nostrwatch/controlflow' -import { delay, RedisConnectionDetails } from "@nostrwatch/utils" - -const JOB_OPTS = { - removeOnComplete: true, - removeOnFail: true, - attempts: 3, - backoff: { - type: 'exponential', - delay: 60*1000, - } -} - -export class Persist { - - $ = null - key - worker = () => {} - workerFn = () => { console.log('workerFn has not been set') } - - constructor(slug){ - this.key = `persist6/${slug}` - this.$ = PersistQueue(this.key) - this.worker = new BullMQ.Worker(this.key, this.work.bind(this), { concurrency: 1, connection: RedisConnectionDetails() } ) - this.$.$Queue.resume() - } - - async addJob(result){ - // console.log(`persist: ADD JOB ${result.url}`) - return this.$.$Queue.add( 'persist', result, JOB_OPTS ) - } - - setWorker(fn){ - if(fn instanceof Function) - this.workerFn = fn - } - - async work(jd) { - // console.log(`persist: BEGIN`) - await this.workerFn(jd).catch(e => { this.log.error(e.message) }) - await delay(1) - // console.log(`persist: SUCCESS`) - } - -} \ No newline at end of file diff --git a/apps/nocapd/src/classes/ShortBus.js b/apps/nocapd/src/classes/ShortBus.js new file mode 100644 index 00000000..c9c3748d --- /dev/null +++ b/apps/nocapd/src/classes/ShortBus.js @@ -0,0 +1,56 @@ +import Logger from '@nostrwatch/logger' +import { PersistQueue, BullMQ } from '@nostrwatch/controlflow' +import { delay, RedisConnectionDetails } from "@nostrwatch/utils" + +const JOB_OPTS = { + removeOnComplete: true, + removeOnFail: true, + attempts: 3, + backoff: { + type: 'exponential', + delay: 60*1000, + } +} + +export class ShortBus { + + $ = null + key + worker = () => {} + workerFns = new Map() + + constructor(slug){ + this.key = `persist6/${slug}` + this.$ = PersistQueue(this.key) + this.worker = new BullMQ.Worker(this.key, this.work.bind(this), { concurrency: 1, connection: RedisConnectionDetails() } ) + this.$.$Queue.resume() + this.log = new Logger('@nostrwatch/shortbus') + } + + async addJob(payload){ + return this.$.$Queue.add( 'persist', payload, JOB_OPTS ) + } + + setWorker(key, fn){ + this.log.debug(`ShortBus::setWorker`, key) + if(fn instanceof Function) + this.workerFns.set(key, fn) + this.log.debug(`ShortBus: ${this.workerFns.size} workers registered`) + } + + async work(job) { + await delay(1) + const { type } = job.data + for(let [key, fn] of this.workerFns) { + if( type !== key ) continue; + this.log.debug(`ShortBus: BEGIN ${key}`) + try { + await fn(job)?.catch(e => this.log.error(`Worker error ${e.message}`)) + } + catch(e){ + this.log.error(`Worker error ${e.message}`) + } + } + } + +} \ No newline at end of file diff --git a/apps/nocapd/src/classes/Worker.js b/apps/nocapd/src/classes/Worker.js index b252a551..d74ffe91 100644 --- a/apps/nocapd/src/classes/Worker.js +++ b/apps/nocapd/src/classes/Worker.js @@ -11,27 +11,25 @@ import Publish from '@nostrwatch/publisher' import { Nocap } from "@nostrwatch/nocap" import nocapAdapters from "@nostrwatch/nocap-every-adapter-default" -import { Persist } from './Persist.js' - import util from 'util' let errors = 0 export class NWWorker { + key = 'relay-check' $ rcache pubkey - persist + bus - constructor(pubkey, $q, rcache, config){ + constructor(pubkey, $q, rcache, bus, config){ this.pubkey = pubkey this.$ = $q this.rcache = rcache this.config = config - this.persist = new Persist(config?.monitor?.slug) - this.persist.setWorker(this.persist_result.bind(this)) this.setup() this.log.info(`${this.id()} initialized`) + this.bus = bus } setup(){ @@ -151,15 +149,16 @@ export class NWWorker { this.log.debug(`after_completed(): ${result.url}`) const concurrency = this.config?.nocapd?.bullmq?.worker?.concurrency if(!concurrency || concurrency <= 1) { - await this.persist_result({ data: result }) + await this.bus_result({ data: { result, type: this.key }}) } else { - await this.persist.addJob(result) + await this.bus.addJob({ result, type: this.key }) } } async persist_result(job){ - const {data:result} = job + const {result} = job.data + this.log.debug(`inside persist_result()`) let err = false const fail = result?.open?.data? false: true const cacheError = (from, e) => err = { from, e} @@ -464,7 +463,7 @@ export class NWWorker { } if(errors > 0) - console.log('DATA INTEGRITY ERRORS', errors) + this.log.debug(`DATA INTEGRITY ERRORS #: ${errors}`) expiredRelays = expiredRelays.sort((a, b) => a.retries - b.retries).map(r => r.url); diff --git a/apps/nocapd/src/daemon.js b/apps/nocapd/src/daemon.js index bee29214..5918a5d5 100644 --- a/apps/nocapd/src/daemon.js +++ b/apps/nocapd/src/daemon.js @@ -14,6 +14,7 @@ import { bootstrap } from '@nostrwatch/seed' import { parseRelayNetwork, delay, loadConfig, RedisConnectionDetails } from "@nostrwatch/utils" import { NWWorker } from './classes/Worker.js' +import { ShortBus } from './classes/ShortBus.js' import { NocapdQueues } from './classes/NocapdQueues.js' import util from 'util' @@ -26,6 +27,7 @@ const log = new Logger('@nostrwatch/nocapd') let rcache, config, $q, + bus, jobs const populateJobQueue = async () => { @@ -48,17 +50,22 @@ const setSchedules = async () => { return { relayPopulator, jobPopulator } } -const initWorker = async () => { +const initBus = () => { + bus = new ShortBus(config?.monitor?.slug) +} +const initQueue = async () => { + const connection = RedisConnectionDetails() - log.info(`initWorker(): connecting to redis at`, connection) + log.info(`initQueue(): connecting to redis at`, connection) const concurrency = config?.nocapd?.bullmq?.worker?.concurrency? config.nocapd.bullmq.worker.concurrency: 1 const ncdq = NocapdQueue(`nocapd/${config?.monitor?.slug}` || null) + $q = new NocapdQueues({ pubkey: PUBKEY, logger: new Logger('@nostrwatch/nocapd:queue-control'), redis: connection }) await $q .set( 'queue' , ncdq.$Queue ) .set( 'events' , ncdq.$QueueEvents ) - .set( 'checker', new NWWorker(PUBKEY, $q, rcache, {...config, logger: new Logger('@nostrwatch/nocapd:worker'), pubkey: PUBKEY }) ) + .set( 'checker', new NWWorker(PUBKEY, $q, rcache, bus, {...config, logger: new Logger('@nostrwatch/nocapd:worker'), pubkey: PUBKEY }) ) .set( 'worker' , new BullMQ.Worker($q.queue.name, $q.route_work.bind($q), { concurrency, connection, ...queueOpts() } ) ) await $q.checker.drainSmart() @@ -73,7 +80,7 @@ const initWorker = async () => { $q.resume() log.info(`initialized: ${$q.queue.name}`) - return $q + } const stop = async(signal) => { @@ -162,11 +169,20 @@ const scheduleJobPopulator = () =>{ return scheduleSeconds(name, seconds, job) } +const relayPopulatorOnTheShortBus = () => { + bus.setWorker('relay-import', persistRelays) +} + +const relayCheckerOnTheShortBus = () => { + bus.setWorker($q.checker.key, $q.checker.persist_result.bind($q.checker)) +} + const scheduleRelayPopulator = () =>{ const name = "scheduleRelayPopulator()" const seedOpts = config?.nocapd?.seed if(!seedOpts || !config?.nocapd?.seed?.sources?.length) return const seconds = timestring(seedOpts.interval, "s") + const job = async () => { log.debug(`Scheduled: populateRelays()`) await populateRelays() @@ -187,8 +203,8 @@ const pause = async (caller = "unknown") => { log.info(`${caller} pausing: all queues/workers`) await $q.queue.pause() await $q.worker.pause() - await $q.checker.persist.$.$Queue.pause() - await $q.checker.persist.worker.pause() + await bus.$.$Queue.pause() + await bus.worker.pause() await delay(1000) log.info(`${caller} paused: all queues/workers`) } @@ -197,15 +213,13 @@ const resume = async (caller = "unknown") => { log.info(`${caller} resuming: all queues/workers`) await $q.queue.resume() await $q.worker.resume() - await $q.checker.persist.$.$Queue.resume() - await $q.checker.persist.worker.resume() + await bus.$.$Queue.resume() + await bus.worker.resume() await delay(1000) log.info(`${caller} resumed: all queues/workers`) } const populateRelays = async () => { - if($q?.queue) await pause('populateRelays()') - log.debug(`populateRelays(): begin`) const syncData = await bootstrap('nocapd') await delay(1) @@ -213,15 +227,19 @@ const populateRelays = async () => { log.debug(`populateRelays(): found ${syncData[0].length} *maybe new* relays`) const relays = syncData[0].map(r => { return { url: normalizeUrl(r), online: null, network: parseRelayNetwork(r) } }) - log.debug(`populateRelays(): Persisting ${relays.length} relays`, relays) - const persisted = await rcache.relay.batch.insertIfNotExists(relays).catch(console.error) - - if($q?.queue) await resume('populateRelays()') + bus.addJob({ relays, type: 'relay-import' }) +} - if(persisted.length === 0) return 0 +const persistRelays = async (job) => { + log.debug('persistRelays(): begin') - log.info(chalk.yellow.bold(`Persisted ${persisted.length} new relays`)) - return persisted + const { relays } = job.data + log.debug(`populateRelays(): Persisting ${relays.length} relays`, relays) + const persisted = await rcache.relay.batch.insertIfNotExists(relays).catch(console.error) + + if(persisted.length === 0) return 0 + + log.info(chalk.yellow.bold(`Persisted ${persisted.length} new relays`)) } const queueOpts = () => { @@ -282,11 +300,12 @@ export const Nocapd = async () => { await maybeAnnounce() if(await maybeBootstrap()) log.info('Bootstrapped') - // else - // await populateRelays() - $q = await initWorker() - // $q.worker.on('drained', populateJobQueue) + initBus() + await initQueue() + + relayPopulatorOnTheShortBus() + relayCheckerOnTheShortBus() globalHandlers() return { diff --git a/apps/trawler/old/src/check-cache.js b/apps/trawler/old/src/check-cache.js deleted file mode 100644 index 090c875d..00000000 --- a/apps/trawler/old/src/check-cache.js +++ /dev/null @@ -1,117 +0,0 @@ -import { Nocap } from '@nostrwatch/nocap' -import { lastCheckedId } from '@nostrwatch/utils' -import Logger from '@nostrwatch/logger' - -import rcache from './relaydb.js' -import config from './config.js' - -import { retryId } from './utils.js' - -const logger = new Logger('check-cache') - -export default async () => { - const relays = rcache.relay.get.all(['url', 'online']) - const uncheckedRelays = getUncheckedRelays(relays) - const expiredRelays = await getExpiredRelays(relays) - - const relaysToCheck = [...new Set([ ...uncheckedRelays, ...expiredRelays ])] - let onlineRelays = relays.filter( relay => relay.online ) - const totalRelays = relays.length - - // logger.info(onlineRelays) - - logger.info(`total relays: ${totalRelays}`) - logger.info(`online relays: ${onlineRelays.length}`) - logger.info(`expired relays: ${expiredRelays.length}`) - logger.info(`unchecked relays: ${uncheckedRelays.length}`) - logger.info(`relays to check: ${relaysToCheck.length}`) - - await initRetryCount(relays) - - if(relaysToCheck.length === 0) return - - const doTruncate = config?.trawler?.check?.max - - if(config?.trawler?.check?.max && relaysToCheck.length > config.trawler.check.max && typeof config.trawler.check.max === 'number') - relaysToCheck.length = parseInt(config.trawler.check.max) - - logger.info(`checkCache(): Quickly filtering through ${uncheckedRelays.length} unchecked, - ${expiredRelays.length} expired and a total of ${totalRelays.length} relays before trawling. - ${doTruncate? "Since Max value is set, so only filtering "+relaysToCheck.length+" Relays.": ""} - There are currently ${onlineRelays.length} relays online according to the cache. - `) - for await ( const relay of relaysToCheck ) { - const { url } = relay - let online = false - const nocap = new Nocap(url, { timeout: { open: config?.trawler?.check?.timeout || 500 }}) - try { - await nocap.check('open').catch() - online = nocap.results.get('open').data? true: false - } - catch(e) { } - await setLastChecked(url) - await setRetries(url, online) - rcache.relay.patch({ url, online }) - } - onlineRelays = rcache.relay.get.all(['url', 'online']).filter( relay => relay.online ) - logger.info(`checkCache(): Completed, ${onlineRelays.length} cached relays are online`) -} - -const expiry = (retries) => { - if(typeof retries === 'undefined') return 0 - let map - if(config?.trawler?.check?.expiry && config.trawler.check.expiry instanceof Array ) - map = config.trawler.check.expiry.map( entry => { return { max: entry.max, delay: parseInt(eval(entry.delay)) } } ) - else - map = [ - { max: 3, delay: 1000 * 60 * 60 }, - { max: 6, delay: 1000 * 60 * 60 * 24 }, - { max: 13, delay: 1000 * 60 * 60 * 24 * 7 }, - { max: 17, delay: 1000 * 60 * 60 * 24 * 28 }, - { max: 29, delay: 1000 * 60 * 60 * 24 * 90 } - ]; - const found = map.find(entry => retries <= entry.max); - return found ? found.delay : map[map.length - 1].delay; -}; - -const getUncheckedRelays = (relays=[]) => { - let unchecked = relays.filter( relay => relay.online == null ) - return unchecked?.length? unchecked: [] -} - -const setLastChecked = async (url) => { - await rcache.cachetime.set( lastCheckedId('online',url), Date.now() ) -} - -const initRetryCount = async (relays) => { - relays.forEach(async (relay) => { - const url = relay.url - // logger.info(retryId(url)) - const retries = rcache.retry.get( retryId(url) ) - if(typeof retries === 'undefined' || typeof retries === null) - await rcache.retry.set(retryId(url), 0) - }) -} - -const getExpiredRelays = async (relays=[]) => { - const relayStatuses = await Promise.all(relays.map(async relay => { - const url = relay.url; - const lastChecked = await rcache.cachetime.get.one( lastCheckedId('online',url) ); - if (!lastChecked) return { relay, isExpired: true }; - const retries = await rcache.retry.get(retryId(url)); - const isExpired = lastChecked < Date.now() - expiry(retries); - return { relay, isExpired }; - })); - return relayStatuses.filter(r => r.isExpired).map(r => r.relay); -} - -const setRetries = async ( url, online ) => { - let id - if(online) { - logger.info(`${url} is online`) - id = await rcache.retry.set(retryId(url), 0) - } else { - id = await rcache.retry.increment(retryId(url)) - } - return id -} \ No newline at end of file diff --git a/apps/trawler/old/src/daemon.js b/apps/trawler/old/src/daemon.js deleted file mode 100644 index 470fa2a2..00000000 --- a/apps/trawler/old/src/daemon.js +++ /dev/null @@ -1,71 +0,0 @@ -import schedule from 'node-schedule' - -import rdb from './relaydb.js' -import config from "./config.js" -import checkCache from './check-cache.js' -import publish from './publish.js' -import Logger from '@nostrwatch/logger' -import chalk from 'chalk' - -import { configureQueues } from './queue.js' -import { bootstrap } from '@nostrwatch/seed' -import { chunkArray, msToCronTime } from '@nostrwatch/utils' - -const {trawlQueue, trawlWorker} = await configureQueues() -const logger = new Logger('daemon') -let busy = false - -const header = () => { - console.log(chalk.bold(` - -@nostrwatch/ - dMMMMMMP dMMMMb .aMMMb dMP dMP dMP dMP dMMMMMP dMMMMb - dMP dMP.dMP dMP"dMP dMP dMP dMP dMP dMP dMP.dMP - dMP dMMMMK" dMMMMMP dMP dMP dMP dMP dMMMP dMMMMK" - dMP dMP"AMF dMP dMP dMP.dMP.dMP dMP dMP dMP"AMF -dMP dMP dMP dMP dMP VMMMPVMMP" dMMMMMP dMMMMMP dMP dMP - -`)); -} - -const populateTrawler = async (relays) => { - await trawlWorker.pause() - const relaysPerChunk = config?.trawler?.trawl_concurrent_relays || 50; - const batches = chunkArray(relays, relaysPerChunk) - batches.forEach( (batch, index) => { - logger.debug(`adding batch ${index} to trawlQueue`) - trawlQueue.add(`trawlBatch${index}`, { relays: batch }) - }) - trawlWorker.resume() -} - -const maybeCheckRelays = async () => { - const seeded = rdb.relay.count.all() > 0 - const useCache = config?.trawler?.check?.enabled || false - if(!seeded || !useCache || busy === true) return - logger.info('maybeCheckRelays(): checking relays, pausing TrawlWorker') - busy = true - await checkCache() - busy = false - logger.info('maybeCheckRelays(): checked relays, resuming TrawlWorker') -} - -const schedules = () => { - const checkEveryMs = config?.trawler?.check?.interval? parseInt(eval(config.trawler.check.interval)): 12*60*60*1000 - schedule.scheduleJob( msToCronTime(checkEveryMs), maybeCheckRelays ) -} - - -export default async () => { - return new Promise( async (resolve) => { - header() - schedules() - await maybeCheckRelays() - const seed = await bootstrap('trawler') - const relays = seed[0] - const updatedAt = seed[1] - await populateTrawler( relays ) - trawlWorker.on('drained', () => populateTrawler( relays ) ) - resolve({ queues: { trawlQueue }, watcher: null }) - }) -} \ No newline at end of file diff --git a/apps/trawler/old/src/index.js b/apps/trawler/old/src/index.js deleted file mode 100644 index 8a2c2c25..00000000 --- a/apps/trawler/old/src/index.js +++ /dev/null @@ -1,11 +0,0 @@ -#!/usr/bin/env node - -import daemon from './daemon.js'; -import Logger from '@nostrwatch/logger' - -const logger = new Logger('root') - -logger.debug('Current Directory', process.cwd()) - -const $ = daemon() -$.catch(logger.err); \ No newline at end of file diff --git a/apps/trawler/old/src/parsers.js b/apps/trawler/old/src/parsers.js deleted file mode 100644 index 600d61fa..00000000 --- a/apps/trawler/old/src/parsers.js +++ /dev/null @@ -1,37 +0,0 @@ -import { normalizeRelays } from './sanitizers.js' - -export const parseRelayList = (note) => { - let parsed - if(note.kind === 2) - parsed = parseRelayFromKind2(note) - if(note.kind === 3) - parsed = parseRelayListFromKind3(note) - if(note.kind === 10002) - parsed = parseRelayListFromKind10002(note) - - return normalizeRelays(parsed) -} - -export const parseRelayFromKind2 = (note) => { - return [note.content] -} - -export const parseRelayListFromKind3 = (note) => { - let dirtyRelayList - try { - dirtyRelayList = Object.keys(JSON.parse(note.content)) - } catch(e) { - return - } - return dirtyRelayList.length ? dirtyRelayList : [] -} - -export const parseRelayListFromKind10002 = (note) => { - let dirtyRelayList - try { - dirtyRelayList = note?.tags - .filter( t => t[0] === 'r' ) - .map( t => t[1] ) - } catch(e) {""} - return dirtyRelayList ? dirtyRelayList : [] -} \ No newline at end of file diff --git a/apps/trawler/old/src/publish.js b/apps/trawler/old/src/publish.js deleted file mode 100644 index eb1b6eb1..00000000 --- a/apps/trawler/old/src/publish.js +++ /dev/null @@ -1,52 +0,0 @@ -import Publish from '@nostrwatch/publisher' -import rcache from "./relaydb.js" -import config from "./config.js" -import { lastPublishedId } from "./utils.js" - -const p30166 = new Publish.Kind30166() - -const filterRelayProperties = (relay) => { - const relay_ = {} - const relayProps = config?.trawler?.sync?.relays?.out?.events?.properties - if(!(relayProps instanceof Array)) return relay - Object.entries( relay ).forEach( entry => { - if( relayProps.includes(entry[0]) ) - relay_[entry[0]] = entry[1] - }) - return relay_ -} - -const filterRelaysProperties = (relays) => { - return relays.map( filterRelayProperties ) -} - -const updatePublishTimes = async (relays=[]) => { - for await ( const relay of relays ) { - await rcache.cachetime.set( lastPublishedId(relay.url), Date.now() ) - } -} - -export const publishOne = async (relay) => { - relay = filterRelayProperties(relay) - if(!relay) throw new Error('publishOne(): relay must be defined') - await p30166.one(relay) -} - -export const publishMany = async (relays = []) => { - relays = filterRelaysProperties(relays) - // const filteredRelays = relays.filter(relayIsExpired); - if (!relays.length) return; - await p30166.many(relays); - // await updatePublishTimes(relays) -} - -export const publishAll = async () => { - const relays = rcache.relay.get.all() - await publishMany(relays) -} - -export default { - many: publishMany, - one: publishOne, - all: publishAll -} \ No newline at end of file diff --git a/apps/trawler/old/src/queue.js b/apps/trawler/old/src/queue.js deleted file mode 100644 index e92cbf33..00000000 --- a/apps/trawler/old/src/queue.js +++ /dev/null @@ -1,63 +0,0 @@ -import { trawl } from './trawler.js'; -import Logger from '@nostrwatch/logger' - -import { TrawlQueue, BullMQ } from '@nostrwatch/controlflow' - -const { Worker } = BullMQ - -import { RedisConnectionDetails } from '@nostrwatch/utils' - -export const configureQueues = async function(){ - - const connection = RedisConnectionDetails() - - /********** - * Trawler - */ - - const trawlLogger = new Logger('trawler queue') - - //queue - - const trawler = TrawlQueue({ removeOnComplete: { age: 30*60*1000 }, removeOnFail: { age: 30*60*1000 }, timeout: 1000*60*10 }) - - const trawlJobProgress = async ($job, progress) => { - if(!(progress instanceof Object)) return trawlLogger.warn(`Progress data is not an object, it's a ${typeof progress}`) - const { type, source } = progress - if(type === 'found'){ - const { source, listCount, result, relaysPersisted, total } = progress - trawlLogger.info(`${source}: ${listCount} lists found, +${result?.length} relays persisted, ${relaysPersisted.size} total found in this chunk. ${total} total relays`) - } - if(type === 'resuming') { - const { since } = progress - trawlLogger.info(`${source} resuming from ${since}`) - } - } - - const trawlQueueDrained = () => { - - } - - trawler.$Queue.drain() - - // trawler.$Queue.on('drained', trawlQueueDrained) - // trawler.$QueueEvents.on('progress', trawlJobProgress) - - const trawlJobCompleted = async ($job, foundRelays) => { - trawlLogger.info(`trawlJob#${$job.id} found ${foundRelays.length} relays}`) - } - - const trawlJobFailed = async ($job, err) => { - trawlLogger.warn(`trawlJob ${$job.id} failed: ${err}`) - } - - const trawlWorker = new Worker(trawler.$Queue.name, trawl, { concurrency: 1, maxStalledCount: 1, connection }) - trawlWorker.on('completed', trawlJobCompleted) - trawlWorker.on('failed', trawlJobFailed) - trawlWorker.on('progress', trawlJobProgress) - - return { - trawlQueue: trawler.$Queue, - trawlWorker - } -} diff --git a/apps/trawler/old/src/relaydb.js b/apps/trawler/old/src/relaydb.js deleted file mode 100644 index a92aa142..00000000 --- a/apps/trawler/old/src/relaydb.js +++ /dev/null @@ -1,14 +0,0 @@ -import rcache from '@nostrwatch/nwcache' -import config from "./config.js" - -let $rcache - - -if(!process.env.NWCACHE_PATH) - throw new Error("NWCACHE_PATH, the path to the nostr watch LMDB cache, was not specified in the environment.") - -if(!$rcache) { - $rcache = rcache(process.env.NWCACHE_PATH) -} - -export default $rcache \ No newline at end of file diff --git a/apps/trawler/old/src/sanitizers.js b/apps/trawler/old/src/sanitizers.js deleted file mode 100644 index e7f248cb..00000000 --- a/apps/trawler/old/src/sanitizers.js +++ /dev/null @@ -1,155 +0,0 @@ -/** - * sanitizeRelayList - * @param {array} relays - * @returns Filtered and deduped list of relays - */ - -import Logger from '@nostrwatch/logger' -import isLocal from "url-local" - -const logger = new Logger('sanitizers') - -const BLOCK_HOSTNAMES = []; - -export const normalizeRelays = (relays) => { - return relays - .map( sanitizeRelayUrl ) - .filter( qualifyRelayUrl ) - .reduce ( normalizeRelayUrlAcc, [] ) -} - -export const sanitizeRelayList = (relays) => { - - const listFilters = [ - 'relaysFilterInvalid', - 'relaysFilterBlocked', - 'relaysFilterRobotsTxtDisallowed' - ] - - listFilters.forEach(listFilter => { - relays = listFilter(relays) - }); - - return relays -} - -export const qualifyRelayUrl = (relay) => { - if(isLocal(relay)) - return false - - if( /^(wss:\/\/)(.*)(:\/\/)(.*)$/.test(relay) ) //multiple protocols - return false - - if (!relay.startsWith('wss://') && !relay.startsWith('ws://')) - return false; - - if( relay.match(/localhost|\.local|[\n\r]|\[object object\]/) ) - return false - - if ( relay.includes('http://') || relay.includes('https://')) - return false; - - if ( relay.match(/(127\.)\d{0,3}(\.)\d{0,3}(\.)\d{0,3}|(192\.168|10\.)\d{1,3}(\.)\d{1,3}/)) - return false; - - if (/(npub)[A-z0-9]{0,60}/.test(relay)) - return false; - - return true; -} - -const normalizeRelayUrlAcc = (acc, relay) => { - const normalized = normalizeRelayUrl(relay); - if (normalized) { - acc.push(normalized); - } - return acc; -} - -const normalizeRelayUrls = (relays) => { - return relays.map( relay => normalizeRelayUrl(relay)) -} - -const normalizeRelayUrl = (relay) => { - let url = "" - try { - url = new URL(relay) - url.hash = '' - url.search = '' - url = url.toString() - } - catch(e) { - logger.warn(`Failed to normalize relay ${relay}`) - } - return url -} - -export const sanitizeRelayUrl = (relay) => { - try { - return decodeURI(relay) - .toLowerCase() - .trim() - .replace(/[\s\t]+/, '') // Consolidate whitespace and tab removal - .replace(/\/+$/, '') // Remove trailing slashes - .split(',')[0]; // Get the first part before any comma - } - catch(e) { - logger.warn(`Failed to sanitize relay ${relay}`) - return "" - } -} - -export const relaysFilterInvalid = (relays) => { - let invalids = 0; - const re = /^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\-]*[A-Za-z0-9])$/; - const validRelays = relays.filter(relay => { - try { - const test = new URL(relay); - if ( - (test.protocol === 'wss:' || test.protocol === 'ws:') && - re.test(test.hostname) && - !test.hostname.includes('https:') && - !test.hostname.includes('http:') && - test.hostname.includes('.') - ) { - return true; - } - invalids++; - return false; - } catch(e) { - invalids++; - return false; - } - }); - return validRelays; -} - -export const relaysFilterDuplicates = (relays) => { - const hostnameMap = new Map(); - return relays.filter(url => { - try { - const hostname = new URL(url).hostname; - if (!hostnameMap.has(hostname)) { - hostnameMap.set(hostname, true); - return true; - } - return false; - } catch(e) { - return false; - } - }); -} - -export const relaysFilterPortDuplicates = (relays) => { - const relaysMap = new Map(relays.map(relay => [new URL(relay).hostname, relay])); - return Array.from(relaysMap.values()); -} - -export const relaysFilterRobotsTxtDisallowed = (relays) => { - const disallowed = cache.get('disallowed') || []; - return relays.filter(relay => !disallowed.includes(relay)); -} - -export const relaysFilterBlocked = (relays) => { - return relays.filter(relay => !BLOCK_HOSTNAMES.some(hostname => relay.includes(hostname))); -} \ No newline at end of file diff --git a/apps/trawler/old/src/sync.js b/apps/trawler/old/src/sync.js deleted file mode 100644 index 9233f633..00000000 --- a/apps/trawler/old/src/sync.js +++ /dev/null @@ -1,64 +0,0 @@ - -import hash from 'object-hash' - -// import { SyncQueue } from "@nostrwatch/controlflow" - -import config from "./config.js" -import publish from "./publish.js" - -// const { $Queue:$SyncQueue } = SyncQueue() - -const syncConfig = config?.trawler?.sync - -export const syncRelayOut = async (data) => { - if(data.payload instanceof Array) throw new Error("syncRelayOut(): data.payload must be an object, not an array, otherwise use syncRelaysOut() if trying to sync multiple relays") - if(syncConfig?.relays?.out?.queue){ - // await $SyncQueue.add('relay-create', data, { priority: 1 /*, jobId: `SyncOut@${process.env.DAEMON_PUBKEY}:${data.url}` */ }) - } - if(syncConfig?.relays?.out?.events){ - await publish.one( data.payload ) - } -} - -export const syncRelaysOut = async (data) => { - if(!(data.payload instanceof Array)) throw new Error("syncRelaysOut(): data.payload must be an array of objects, not an object, otherwise use syncRelayOut() if trying to sync a single relay") - if(syncConfig?.relays?.out?.queue){ - // await $SyncQueue.add('relays-create', data, { priority: 1 /*, jobId: `SyncOut@${process.env.DAEMON_PUBKEY}:${hash(data.payload)}` */ }) - } - if(syncConfig?.relays?.out?.events){ - await publish.many( data.payload ) - } -} - -export const syncRelayIn = async (data) => { - if(syncConfig?.relays?.in?.queue){ - if(data.payload instanceof Array) throw new Error("syncRelayIn(): data.payload must be an object, not an array, otherwise use syncRelaysIn() if trying to sync multiple relays") - // await $SyncQueue.add('relay-get', data, { priority: 1, jobId: `SyncIn@${process.env.DAEMON_PUBKEY}:${data.url}` }) - //watch for completed on jobid, populate cache - } - if(syncConfig?.relays?.in?.events){ - //subscribe to events matching kind/pubkey/tag[d] filter, populate cache - } -} - -export const syncRelaysIn = async (data) => { - if(syncConfig?.relays?.in?.queue){ - if(!(data.payload instanceof Array)) throw new Error("syncRelaysIn(): data.payload must be an array, not an object, otherwise use syncRelayIn() if trying to sync a single relay") - // await $SyncQueue.add('relays-get', data, { priority: 1, jobId: `SyncIn@${process.env.DAEMON_PUBKEY}:${hash(data.payload)}` }) - //watch for completed on jobid, populate cache - } - if(syncConfig?.relays?.in?.events){ - //subscribe to events matching kind/pubkey filter, populate cache - } -} - -export default { - relay: { - out: syncRelayOut, - in: syncRelayIn - }, - relays: { - out: syncRelaysOut, - in: syncRelaysIn - } -} \ No newline at end of file diff --git a/apps/trawler/old/src/trawler.js b/apps/trawler/old/src/trawler.js deleted file mode 100644 index 063524cc..00000000 --- a/apps/trawler/old/src/trawler.js +++ /dev/null @@ -1,196 +0,0 @@ -import "websocket-polyfill"; -// import { Blob } from 'buffer'; - -import { NostrFetcher } from 'nostr-fetch'; -import { SimplePool } from 'nostr-tools'; -import { simplePoolAdapter } from '@nostr-fetch/adapter-nostr-tools' - -import config from "./config.js" -import rcache from "./relaydb.js" -import Logger from "@nostrwatch/logger"; -import sync from "./sync.js" - -import { parseRelayList } from "./parsers.js"; -import { lastTrawledId } from "./utils.js"; -import { parseRelayNetwork, relayId } from "@nostrwatch/utils" -import { TrawlQueue } from "@nostrwatch/controlflow" - -const logger = new Logger('trawler') - -const ignore = ["wss://nostr.searx.is/"] - -const { $Queue:$TrawlQueue, $QueueEvents:$TrawlEvents } = TrawlQueue() - -let relaysPersisted, - listCount - -let promises, - deferPersist, - $currentJob - -const addRelaysToCache = async (relayList) => { - const ids = [] - for (const relayObj of relayList) { - ids.push(await rcache.relay.insertIfNotExists(relayObj)) - } - return ids.filter(id => id !== undefined) -} - -const noteInCache = async (ev, relay, lastEvent) => { - const exists = await rcache.note.exists(ev) - if( exists ) - await rcache.cachetime.set( lastTrawledId(relay), lastEvent ) - return exists -} - -const setLastEvent = (ev, since, lastEvent) => { - const timestamp = parseInt(ev.created_at) - return timestamp>lastEvent? (timestamp>since? timestamp: since): lastEvent -} - -const determineSince = async (relay) => { - const cacheSince = await rcache.cachetime.get.one( lastTrawledId(relay) ) - return cacheSince || 0 -} - -export const relaysFromRelayList = async ( ev ) => { - let relayList = parseRelayList(ev) - - if(!(relayList instanceof Array)) - return false - - relayList = relayList.map( relay => { - return { - url: relay, - network: parseRelayNetwork(relay), - online: null - } - }) - return relayList -} - -const jobData = (relayList, roundtrip) => { - return { - type: 'relay', - action: 'create', - condition: 'ifNotExists', - batch: true, - payload: relayList, - roundtrip - } -} - -export const trawl = async function($job){ - promises = new Array() - deferPersist = new Object() - relaysPersisted = new Set() - listCount = 0 - $currentJob = $job - - const relays = $job.data.relays.filter( relay => !ignore.includes(relay.url) ) - const pool = new SimplePool(); - const fetcher = NostrFetcher.withCustomPool(simplePoolAdapter(pool)); - const kinds = [ 3, 10002 ] - - relays.forEach( async (relay) => { - - promises.push(new Promise( async (resolve) => { - let lastEvent = 0 - let since = await determineSince(relay) - $job.updateProgress({ type: 'resuming', source: relay, since }) - try { - - const it = await fetcher.allEventsIterator( - [ relay ], - { kinds }, - { since }, - { sort: true } - ) - for await (const ev of it) { - if(!kinds.includes(ev.kind)) { - continue - } - lastEvent = setLastEvent(ev, since, lastEvent) - if( await noteInCache(ev, relay, lastEvent) ) { - continue - } - const relayList = await relaysFromRelayList(ev) - - if(relayList === false) continue - listCount++ - - const cacheIds = await addRelaysToCache(relayList) - - for( const id of cacheIds ) { - relaysPersisted.add(rcache.relay.get.one(id)?.url) - } - - deferPersist[ev.id] = async () => await rcache.note.set.one(ev) - - if(cacheIds.length === 0) { - await deferPersist[ev.id]() - delete deferPersist[ev.id] - continue - } - - const foundRelays = (await rcache.relay.get.many(cacheIds)).map( relay => relay.url ) - console.log(`found ${cacheIds.length} new relays`, foundRelays) - - const roundtrip = { - requestedBy: process.env.DAEMON_PUBKEY, - source: relay, - trawlJobId: $job.id, - eventId: ev.id - } - if(config?.trawler?.sync?.out?.events) - roundtrip.syncEventsCallback = syncEventsCallback - - const data = jobData(relayList, 9) - await sync.relays.out(data) - } - } - catch(err) { - logger.err(`${relay}: ${err}`) - } - if(lastEvent > 0) - await rcache.cachetime.set( lastTrawledId(relay), lastEvent ) - resolve() - })) - }) - await Promise.allSettled(Object.values(promises)) - if(relaysPersisted.size > 0 ){ - console.log(`${relaysPersisted.size} relays persisted: ${[...relaysPersisted]}`) - } - return [...Array.from(relaysPersisted)] -} - -const watchQueue = () => { - $SyncEvents.on( 'completed', async ({returnvalue}) => { - const { result, roundtrip } = returnvalue - const { requestedBy } = roundtrip - if(requestedBy != process.env.DAEMON_PUBKEY) return - return finish(result, roundtrip) - }) -} - -if(config?.trawler?.sync?.out?.queue) - watchQueue() - -const syncEventsCallback = ( { result, roundtrip} ) => { - finish( result, roundtrip ) -} - -const finish = async (result, roundtrip) => { - if(result === false || result.length == 0) return - const { source, trawlJobId, eventId } = roundtrip - const $trawlJob = await $TrawlQueue.getJob(trawlJobId) - result.forEach(relay => relaysPersisted.add(relay)) - if(result?.length && result.length > 0) { - if(deferPersist?.[eventId]) - await deferPersist[eventId]() - if(relaysPersisted?.size && typeof $trawlJob?.updateProgress === 'function') - await $trawlJob.updateProgress({ type: 'found', source, listCount, result, relaysPersisted, total: rcache.relay.count.all() }) - } - if(deferPersist?.[eventId]) - delete deferPersist[eventId] -} \ No newline at end of file diff --git a/apps/trawler/old/src/utils.js b/apps/trawler/old/src/utils.js deleted file mode 100644 index b7c08d51..00000000 --- a/apps/trawler/old/src/utils.js +++ /dev/null @@ -1,87 +0,0 @@ -import _timestring from "timestring"; -import WebSocket from 'ws'; - -// import { env } from '@nostrwatch/utils' - -export const lastTrawledId = (relay) => `LastTrawled:${relay}` -export const retryId = (relay) => `Trawler:${relay}` -export const lastPublishedId = (relay) => `LastPublished:${relay}` -export const excludeKnownRelays = (known, discovered) => { - return discovered.filter( relay => !known.includes(relay) ) -} - -// Function to check if a single queue is empty -export const isQueueEmpty = async function(queue) { - const counts = await queue.getJobCounts("active"); - return counts.active === 0; - // return counts.active === 0 && counts.delayed === 0 && counts.completed === 0 && counts.failed === 0; -}; - -export const areAllQueuesEmpty = async function(queues) { - const checks = await Promise.allSettled(Object.keys(queues).map((key) => isQueueEmpty(queues[key]))); - return checks.every(check => check); -} - -export const whenAllQueuesEmpty = function(queues, callback=()=>{}) { - const checkQueues = async () => { - const allEmpty = await areAllQueuesEmpty(queues); - if (allEmpty) { - callback(); - } - setTimeout(checkQueues, 100); - }; - checkQueues(); -}; - -export const isQueueActive = async function(queue) { - const counts = await queue.getJobCounts("active"); - return counts.active > 0; -}; - -export const isAnyQueueActive = async function(queues) { - const checks = await Promise.allSettled(Object.keys(queues).map((key) => isQueueActive(queues[key]))); - return checks.some(check => check); -}; - -export const whenAnyQueueIsActive = function(input, callback=()=>{}) { - const check = async () => { - let anyActive; - if (Array.isArray(input)) - anyActive = await isAnyQueueActive(input) - else - anyActive = await isQueueActive(input); - if (anyActive) - callback(); - else - setTimeout(check, 1000); - }; - check(); -}; - -export const countItemsInObjectOfArrays = function(objectOfArrays) { - const counts = {}; - for (const key in objectOfArrays) { - counts[key] = objectOfArrays[key].length; - } - return counts; -} - -export const timestringSeconds = (str) => Math.round(_timestring(str)/1000) - -export const checkOnline = async function(relay, timeout = 10000) { - return new Promise(resolve => { - const ws = new WebSocket(relay) - const to = setTimeout(() => ws.close(), timeout) - ws.on('open', () => { - clearTimeout(to) - ws.close() - resolve(true) - }) - ws.on('error', () => { - ws.close() - resolve(false) - }) - }) -} - -export const delay = async (ms) => new Promise(resolve => setTimeout(resolve, ms)) \ No newline at end of file diff --git a/apps/trawler/package.json b/apps/trawler/package.json index c7ef4e3f..00e2d8ff 100644 --- a/apps/trawler/package.json +++ b/apps/trawler/package.json @@ -13,9 +13,10 @@ "@nostrwatch/controlflow": "0.5.1", "@nostrwatch/logger": "0.0.9", "@nostrwatch/nocap": "0.7.0", - "@nostrwatch/nostrawl": "0.0.3", + "@nostrwatch/nostrawl": "*", "@nostrwatch/nwcache": "0.2.0", "@nostrwatch/publisher": "0.10.0", + "@nostrwatch/nostrings": "0.0.1", "@nostrwatch/seed": "0.1.0", "@nostrwatch/utils": "0.1.7", "@types/ioredis": "5.0.0", diff --git a/apps/trawler/src/parse.js b/apps/trawler/src/parse.js index 79179779..8539e59d 100644 --- a/apps/trawler/src/parse.js +++ b/apps/trawler/src/parse.js @@ -1,4 +1,4 @@ -import sanitize from './sanitize.js' +import nostrings from '@nostrwatch/nostrings' export const parseRelayList = (note) => { let parsed @@ -12,7 +12,7 @@ export const parseRelayList = (note) => { else if(note.kind === 2) parsed = parseRelayFromKind2(note) - return parsed?.length? sanitize(parsed): [] + return parsed?.length? nostrings(parsed): [] } export const parseRelayFromKind2 = (note) => { diff --git a/internal/nostrings-conf/index.ts b/internal/nostrings-conf/index.ts new file mode 100644 index 00000000..31b5bcbc --- /dev/null +++ b/internal/nostrings-conf/index.ts @@ -0,0 +1,15 @@ +import type { Mutator, Discriminator, Modifier } from "@nostrwatch/nostrings" + +import { groupUrlsByHostname } from "./utils" + +const hostnameModifier: Discriminator = (relays: string[]): boolean => { + const urlMap = groupUrlsByHostname(relays) + + for (const [hostname, urls] of urlMap) { + if (urls.length > 1) { + if(hostname.includes('localhost')) { + return true + } + } + } +} \ No newline at end of file diff --git a/internal/nostrings-conf/path-ok.yaml b/internal/nostrings-conf/path-ok.yaml new file mode 100644 index 00000000..dfd67eb7 --- /dev/null +++ b/internal/nostrings-conf/path-ok.yaml @@ -0,0 +1,2 @@ +hostnames: + - 'nostr.band' \ No newline at end of file diff --git a/internal/nostrings-conf/utils.ts b/internal/nostrings-conf/utils.ts new file mode 100644 index 00000000..12c7dd85 --- /dev/null +++ b/internal/nostrings-conf/utils.ts @@ -0,0 +1,49 @@ +/** + * Groups an array of URLs by their hostname. + * @param {string[]} urls - The array of URLs to group by hostname. + * @returns {Map} A map where the keys are hostnames and the values are arrays of URLs associated with each hostname. + */ +function groupUrlsByHostname(urls: string[]): Map { + const urlMap = new Map(); + + // Group URLs by hostname + urls.forEach((url) => { + try { + const hostname = new URL(url).hostname; + + if (!urlMap.has(hostname)) { + urlMap.set(hostname, []); + } + + urlMap.get(hostname)!.push(url); // Non-null assertion since the array is initialized above + } catch (error) { + console.error(`Invalid URL: ${url}`); + } + }); + + return urlMap; +} + +/** + * Finds the URL with the path closest to the root for each hostname. + * @param {Map} urlMap - The map where keys are hostnames and values are arrays of URLs. + * @returns {Map} A map where the keys are hostnames and the values are the URL with the path closest to the root for each hostname. + */ +function findClosestToRoot(urlMap: Map): Map { + const closestToRootMap = new Map(); + + urlMap.forEach((urls, hostname) => { + if (urls.length > 1) { + // Sort URLs by path length (shortest first) + urls.sort((a, b) => { + const pathA = new URL(a).pathname; + const pathB = new URL(b).pathname; + return pathA.length - pathB.length; + }); + } + // Select the URL with the shortest path (first one after sorting) + closestToRootMap.set(hostname, urls[0]); + }); + + return closestToRootMap; +} \ No newline at end of file diff --git a/internal/seed/src/index.js b/internal/seed/src/index.js index fdb6ea29..4ff19ea2 100644 --- a/internal/seed/src/index.js +++ b/internal/seed/src/index.js @@ -111,8 +111,6 @@ export const relaysFromCache = async (opts) => { cache.$.close() cache = null - - console.log(cacheOpts.path, result.length) return [ result, Date.now() ] } diff --git a/libraries/nostrings/.gitignore b/libraries/nostrings/.gitignore new file mode 100644 index 00000000..e69de29b diff --git a/libraries/nostrings/package.json b/libraries/nostrings/package.json index 654bbd09..86092bd0 100644 --- a/libraries/nostrings/package.json +++ b/libraries/nostrings/package.json @@ -7,25 +7,26 @@ "repository": "https://github.com/sandwichfarm/nostr-watch", "author": "sandwich.farm", "license": "MIT", - "scripts": { - "build": "tsc", - "test": "vitest", - "test:watch": "vitest --watch", - "test:coverage": "vitest run --coverage", - "lint": "eslint 'src/**/*.ts'", + "scripts": { + "build": "tsc", + "prepublish": "yarn build", + "test": "vitest", + "test:watch": "vitest --watch", + "test:coverage": "vitest run --coverage", + "lint": "eslint 'src/**/*.ts'", "lint:fix": "eslint 'src/**/*.ts' --fix", - "clean": "rm -rf dist", - "build:clean": "npm run clean && npm run build" + "clean": "rm -rf dist", + "build:clean": "yarn clean && yarn build" }, "dependencies": { - "@nostrwatch/logger": "*" + "@nostrwatch/logger": "0.0.9" }, "devDependencies": { - "typescript": "^5.0.0", + "typescript": "^5.0.0", "vitest": "^0.31.0", - "eslint": "^8.0.0", - "@typescript-eslint/parser": "^5.0.0", + "eslint": "^8.0.0", + "@typescript-eslint/parser": "^5.0.0", "@typescript-eslint/eslint-plugin": "^5.0.0", - "@types/node": "^18.0.0" + "@types/node": "^18.0.0" } -} +} \ No newline at end of file diff --git a/libraries/nostrings/src/index.js b/libraries/nostrings/src/index.js deleted file mode 100644 index cff3368a..00000000 --- a/libraries/nostrings/src/index.js +++ /dev/null @@ -1,100 +0,0 @@ -import Logger from '@nostrwatch/logger' - -const logger = new Logger('@nostrwatch/nostrings') - -const BLOCK_HOSTNAMES = []; - -export const sanitize = (relays) => { - if(!relays?.length) - return console.log('!relays?.length', relays) - relays = maybeSplitRelayList(relays) - return relays - .map( sanitizeRelayUrl ) - .filter( qualifyRelayUrl ) - .reduce ( normalizeRelayUrlAcc, [] ) -} - -/******** - * maybeSplitRelayList - * - * @description som - */ -export const maybeSplitRelayList = (relays) => { - let _relays = [] - relays.forEach(relay => { - if(relay.includes(',')) { - const maybeRelays = relay.split(',') - _relays = [..._relays, ...maybeRelays] - } - else - _relays.push(relay) - }) - return _relays -} - -export const sanitizeRelayUrl = (relay) => { - try { - return decodeURI(relay) - .toLowerCase() - .trim() - .replace(/[\s\t]+/, '') // Consolidate whitespace and tab removal - .replace(/\/+$/, '') // Remove trailing slashes - .replace(/\.+$/, '') // Remove trailing dots - .replace('(blob_hash)', '') - .split(',')[0]; // Get the first part before any comma - } - catch(e) { - logger.warn(`Failed to sanitize relay ${relay}: ${e.message}`) - return "" - } -} - -export const qualifyRelayUrl = (relay) => { - - if( /^(wss:\/\/)(.*)(:\/\/)(.*)$/.test(relay) ) //multiple protocols - return false - - if (!relay.startsWith('wss://') && !relay.startsWith('ws://')) - return false; - - if( relay.match(/localhost|\.local|[\n\r]|\[object object\]/) ) - return false - - if ( relay.includes('http://') || relay.includes('https://')) - return false; - - if ( relay.match(/(127\.)\d{0,3}(\.)\d{0,3}(\.)\d{0,3}|(192\.168|10\.)\d{1,3}(\.)\d{1,3}/)) - return false; - - if (/(npub)[A-z0-9]{0,60}/.test(relay)) - return false; - - return true; -} - -export const normalizeRelayUrlAcc = (acc, relay) => { - const normalized = normalizeRelayUrl(relay); - if (normalized) { - acc.push(normalized); - } - return acc; -} - -export const normalizeRelayUrls = (relays) => { - return relays.map( relay => normalizeRelayUrl(relay)) -} - -export const normalizeRelayUrl = (relay) => { - try { - const url = new URL(relay) - url.hash = '' - url.search = '' - return url.toString() - } - catch(e) { - logger.warn(`Failed to normalize relay ${relay}: ${e.message}`) - } - return "" -} - -export default sanitize \ No newline at end of file diff --git a/libraries/nostrings/src/index.ts b/libraries/nostrings/src/index.ts index 38a12ee5..e3f17e9e 100644 --- a/libraries/nostrings/src/index.ts +++ b/libraries/nostrings/src/index.ts @@ -1,5 +1,5 @@ -export * from "./relay-urls"; -import relayUrls from "./relay-urls"; +export * from "./relay-urls.js"; +import relayUrls from "./relay-urls.js"; export default { sanitize: { relayUrls } diff --git a/libraries/nostrings/src/relay-urls.test.ts b/libraries/nostrings/src/relay-urls.test.ts index a683f4b3..d32b2e01 100644 --- a/libraries/nostrings/src/relay-urls.test.ts +++ b/libraries/nostrings/src/relay-urls.test.ts @@ -7,7 +7,7 @@ import { normalizeRelayUrlAcc, normalizeRelayUrls, normalizeRelayUrl -} from './relay-urls'; // Adjust this import to your actual file path +} from './relay-urls.js'; // Adjust this import to your actual file path describe('sanitize', () => { it('should correct a wire array of mishaps', () => { diff --git a/libraries/nostrings/src/relay-urls.ts b/libraries/nostrings/src/relay-urls.ts index 409a7319..1a6a2954 100644 --- a/libraries/nostrings/src/relay-urls.ts +++ b/libraries/nostrings/src/relay-urls.ts @@ -1,13 +1,37 @@ import Logger from '@nostrwatch/logger'; const logger = new Logger('@nostrwatch/nostrings'); +interface Discriminator { + (input: string): boolean; +} + +interface Discriminators { + [key: string]: Discriminator; +} + +interface Mutator { + (input: string): string; +} + +interface Mutators { + [key: string]: Mutator; +} + +type DiscriminatorsArgument = Discriminators | null; +type MutatorsArgument = Mutators | null; + +type RuleSet = { + discriminators?: DiscriminatorsArgument; + mutators?: MutatorsArgument; +}; + /** * Sanitizes a list of relay URLs. * * @param {string[]} relays - An array of relay URLs. * @returns {string[] | void} - An array of sanitized relay URLs, or void if input is empty. */ -export const sanitize = (relays: string[]): string[] | void => { +export const sanitize = (relays: string[], rules: RuleSet = {}): string[] | void => { if (!relays?.length) { logger.debug(`!relays?.length ${relays}`); return; @@ -16,6 +40,8 @@ export const sanitize = (relays: string[]): string[] | void => { relays = relays .map(sanitizeRelayUrl) .filter(qualifyRelayUrl) + .filter(relay => applyDiscriminators(relay, rules?.discriminators)) + .map(relay => applyMutators(relay, rules?.mutators)) .reduce(normalizeRelayUrlAcc, []); relays = dedup(relays) return relays @@ -25,6 +51,29 @@ export const dedup = (relays: string[]): string[] => { return [...new Set(relays)] } +const applyDiscriminators = (relay: string, rules: DiscriminatorsArgument = null): boolean => { + if(!rules || !Object.keys(rules).length) + return true + for (const [ruleName, rule] of Object.entries(rules)) { + if (!rule(relay)) { + logger.debug(`applyDiscriminators(): string ${relay} failed discriminator ${ruleName}`); + return false; + } + } + return true; +} + +const applyMutators = (_relay: string, mutators: MutatorsArgument = null): string => { + if(!mutators || !Object.keys(mutators).length) + return _relay + let relay = _relay + for (const [mutatorName, mutator] of Object.entries(mutators)) { + relay = mutator(relay) + logger.debug(`applyMutators(): ${_relay} mutated to ${relay} by ${mutatorName} `); + } + return relay +} + /** * Splits relay URLs by commas if they are present. * @@ -68,40 +117,67 @@ export const sanitizeRelayUrl = (relay: string): string => { }; /** - * Qualifies a relay URL to determine if it's valid. + * qualifyRelayUrl * - * @param {string} relay - A relay URL string. - * @returns {boolean} - Returns true if the relay URL is valid, false otherwise. + * qualify relay URLs with pessimistic invalidation pattern as opposed to validation pattern + * + * @param {string} relay - Relay URL + * @returns {boolean} Whether the relay URL qualifies based on various criteria */ -export const qualifyRelayUrl = (relay: string): boolean => { - if (/^(wss:\/\/)(.*)(:\/\/)(.*)$/.test(relay)) { - // multiple protocols - return false; - } +export const qualifyRelayUrl = (maybeRelay: string): boolean => { + if( typeof maybeRelay !== "string" ) return false; - if (!relay.startsWith('wss://') && !relay.startsWith('ws://')) { - return false; - } + if( maybeRelay.length === 0 ) return false; - if (relay.match(/localhost|\.local|[\n\r]|\[object object\]/)) { - return false; - } + if ( isLocalNet(maybeRelay) ) return false; - if (relay.includes('http://') || relay.includes('https://')) { - return false; - } + if ( isLocal(maybeRelay) ) return false; - if (relay.match(/(127\.)\d{0,3}(\.)\d{0,3}(\.)\d{0,3}|(192\.168|10\.)\d{1,3}(\.)\d{1,3}/)) { - return false; - } + if ( /^(wss:\/\/)(.*)(:\/\/)(.*)$/.test(maybeRelay) ) return false; // multiple protocols - if (/(npub)[A-z0-9]{0,60}/.test(relay)) { - return false; - } + if ( !maybeRelay.startsWith('wss://') && !maybeRelay.startsWith('ws://') ) return false; + + if ( maybeRelay.match(/localhost|\.local|[\n\r]|\[object object\]/)) return false; + + if ( maybeRelay.includes('http://') || maybeRelay.includes('https://') ) return false; + + if ( maybeRelay.match(/(127\.)\d{0,3}(\.)\d{0,3}(\.)\d{0,3}|(192\.168|10\.)\d{1,3}(\.)\d{1,3}/) ) return false; + + if ( /(npub)[A-z0-9]{0,60}/.test(maybeRelay) ) return false; return true; }; + +// export const qualifyRelayUrl = (relay: string): boolean => { +// if (/^(wss:\/\/)(.*)(:\/\/)(.*)$/.test(relay)) { +// // multiple protocols +// return false; +// } + +// if (!relay.startsWith('wss://') && !relay.startsWith('ws://')) { +// return false; +// } + +// if (relay.match(/localhost|\.local|[\n\r]|\[object object\]/)) { +// return false; +// } + +// if (relay.includes('http://') || relay.includes('https://')) { +// return false; +// } + +// if (relay.match(/(127\.)\d{0,3}(\.)\d{0,3}(\.)\d{0,3}|(192\.168|10\.)\d{1,3}(\.)\d{1,3}/)) { +// return false; +// } + +// if (/(npub)[A-z0-9]{0,60}/.test(relay)) { +// return false; +// } + +// return true; +// }; + /** * Accumulates normalized relay URLs into an array. * @@ -145,4 +221,57 @@ export const normalizeRelayUrl = (relay: string): string => { } }; +/** + * isLocal + * @description Checks if a given URL is a local file URL or a local network path. + * + * @param {string} _url - The URL string to be checked. + * @returns {boolean | null} - Returns true if the URL is a local file or network path, false otherwise. + * Returns null if the input is not a string. + */ +export const isLocal = (_url: string): boolean => { + try { + const url: URL = new URL(_url); + // Check if the URL protocol is file + return url.protocol === "file:"; + } catch (err) { + // If URL parsing fails, check if it's a local network path + if (/^[a-zA-Z]:\\/.test(_url) || /^\\\\/.test(_url)) { + return true; + } + } + return false +}; + + +/** + * isLocalNet + * @param {string} urlString - The URL string to be checked. + * @returns {boolean} - Returns true if the URL is in an IP range reserved for local networks, otherwise false. + */ +export const isLocalNet = (urlString: string): boolean => { + // Regular expressions for matching local network IP ranges + const localIpRanges = [ + /^127\.\d{1,3}\.\d{1,3}\.\d{1,3}$/, // 127.0.0.0/8 - Loopback addresses + /^10\.\d{1,3}\.\d{1,3}\.\d{1,3}$/, // 10.0.0.0/8 - Private network + /^192\.168\.\d{1,3}\.\d{1,3}$/, // 192.168.0.0/16 - Private network + /^172\.(1[6-9]|2\d|3[0-1])\.\d{1,3}\.\d{1,3}$/ // 172.16.0.0/12 - Private network + ]; + + try { + const url = new URL(urlString); + + // Extract the hostname from the URL + const hostname = url.hostname; + + // Check if the hostname matches any of the local IP ranges + return localIpRanges.some(range => range.test(hostname)); + } catch (e) { + // If URL parsing fails, log the error and return false + console.error(`Invalid URL: ${urlString}`, e); + return false; + } +}; + + export default sanitize; diff --git a/libraries/nostrings/tsconfig.json b/libraries/nostrings/tsconfig.json index 98ab62ba..ea822362 100644 --- a/libraries/nostrings/tsconfig.json +++ b/libraries/nostrings/tsconfig.json @@ -1,8 +1,8 @@ { "compilerOptions": { "target": "ESNext", - "module": "ESNext", - "moduleResolution": "node", + "module": "node16", + "moduleResolution": "node16", "esModuleInterop": true, "strict": true, "forceConsistentCasingInFileNames": true, @@ -10,9 +10,9 @@ "allowJs": true, "resolveJsonModule": true, "isolatedModules": true, - "noEmit": true, - "jsx": "preserve" + "outDir": "./dist", + "rootDir": "./src" }, - "include": ["src/**/*.ts", "tests/**/*.ts"], + "include": ["src/**/*.ts"], "exclude": ["node_modules"] } \ No newline at end of file diff --git a/libraries/sanitize/src/index.ts b/libraries/sanitize/src/index.ts index 02013089..f6c19764 100644 --- a/libraries/sanitize/src/index.ts +++ b/libraries/sanitize/src/index.ts @@ -175,7 +175,6 @@ export const isLocal = (_url: string): boolean | null => { * @returns {boolean} - Returns true if the URL is in an IP range reserved for local networks, otherwise false. */ export const isLocalNet = (urlString: string): boolean => { - // Regular expressions for matching local network IP ranges const localIpRanges = [ /^127\.\d{1,3}\.\d{1,3}\.\d{1,3}$/, // 127.0.0.0/8 - Loopback addresses /^10\.\d{1,3}\.\d{1,3}\.\d{1,3}$/, // 10.0.0.0/8 - Private network diff --git a/yarn.lock b/yarn.lock index 0e323a14..dbb61830 100644 --- a/yarn.lock +++ b/yarn.lock @@ -573,6 +573,11 @@ resolved "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz#c7184a326533fcdf1b8ee0733e21c713b975575f" integrity sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ== +"@esbuild/android-arm64@0.18.20": + version "0.18.20" + resolved "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.18.20.tgz#984b4f9c8d0377443cc2dfcef266d02244593622" + integrity sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ== + "@esbuild/android-arm64@0.21.5": version "0.21.5" resolved "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz#09d9b4357780da9ea3a7dfb833a1f1ff439b4052" @@ -583,46 +588,91 @@ resolved "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.15.18.tgz#266d40b8fdcf87962df8af05b76219bc786b4f80" integrity sha512-5GT+kcs2WVGjVs7+boataCkO5Fg0y4kCjzkB5bAip7H4jfnOS3dA6KPiww9W1OEKTKeAcUVhdZGvgI65OXmUnw== +"@esbuild/android-arm@0.18.20": + version "0.18.20" + resolved "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.18.20.tgz#fedb265bc3a589c84cc11f810804f234947c3682" + integrity sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw== + "@esbuild/android-arm@0.21.5": version "0.21.5" resolved "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz#9b04384fb771926dfa6d7ad04324ecb2ab9b2e28" integrity sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg== +"@esbuild/android-x64@0.18.20": + version "0.18.20" + resolved "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.18.20.tgz#35cf419c4cfc8babe8893d296cd990e9e9f756f2" + integrity sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg== + "@esbuild/android-x64@0.21.5": version "0.21.5" resolved "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz#29918ec2db754cedcb6c1b04de8cd6547af6461e" integrity sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA== +"@esbuild/darwin-arm64@0.18.20": + version "0.18.20" + resolved "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.18.20.tgz#08172cbeccf95fbc383399a7f39cfbddaeb0d7c1" + integrity sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA== + "@esbuild/darwin-arm64@0.21.5": version "0.21.5" resolved "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz#e495b539660e51690f3928af50a76fb0a6ccff2a" integrity sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ== +"@esbuild/darwin-x64@0.18.20": + version "0.18.20" + resolved "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.18.20.tgz#d70d5790d8bf475556b67d0f8b7c5bdff053d85d" + integrity sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ== + "@esbuild/darwin-x64@0.21.5": version "0.21.5" resolved "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz#c13838fa57372839abdddc91d71542ceea2e1e22" integrity sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw== +"@esbuild/freebsd-arm64@0.18.20": + version "0.18.20" + resolved "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.18.20.tgz#98755cd12707f93f210e2494d6a4b51b96977f54" + integrity sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw== + "@esbuild/freebsd-arm64@0.21.5": version "0.21.5" resolved "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz#646b989aa20bf89fd071dd5dbfad69a3542e550e" integrity sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g== +"@esbuild/freebsd-x64@0.18.20": + version "0.18.20" + resolved "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.18.20.tgz#c1eb2bff03915f87c29cece4c1a7fa1f423b066e" + integrity sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ== + "@esbuild/freebsd-x64@0.21.5": version "0.21.5" resolved "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz#aa615cfc80af954d3458906e38ca22c18cf5c261" integrity sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ== +"@esbuild/linux-arm64@0.18.20": + version "0.18.20" + resolved "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.18.20.tgz#bad4238bd8f4fc25b5a021280c770ab5fc3a02a0" + integrity sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA== + "@esbuild/linux-arm64@0.21.5": version "0.21.5" resolved "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz#70ac6fa14f5cb7e1f7f887bcffb680ad09922b5b" integrity sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q== +"@esbuild/linux-arm@0.18.20": + version "0.18.20" + resolved "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.18.20.tgz#3e617c61f33508a27150ee417543c8ab5acc73b0" + integrity sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg== + "@esbuild/linux-arm@0.21.5": version "0.21.5" resolved "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz#fc6fd11a8aca56c1f6f3894f2bea0479f8f626b9" integrity sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA== +"@esbuild/linux-ia32@0.18.20": + version "0.18.20" + resolved "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.18.20.tgz#699391cccba9aee6019b7f9892eb99219f1570a7" + integrity sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA== + "@esbuild/linux-ia32@0.21.5": version "0.21.5" resolved "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz#3271f53b3f93e3d093d518d1649d6d68d346ede2" @@ -638,61 +688,121 @@ resolved "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.15.18.tgz#128b76ecb9be48b60cf5cfc1c63a4f00691a3239" integrity sha512-L4jVKS82XVhw2nvzLg/19ClLWg0y27ulRwuP7lcyL6AbUWB5aPglXY3M21mauDQMDfRLs8cQmeT03r/+X3cZYQ== +"@esbuild/linux-loong64@0.18.20": + version "0.18.20" + resolved "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.18.20.tgz#e6fccb7aac178dd2ffb9860465ac89d7f23b977d" + integrity sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg== + "@esbuild/linux-loong64@0.21.5": version "0.21.5" resolved "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz#ed62e04238c57026aea831c5a130b73c0f9f26df" integrity sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg== +"@esbuild/linux-mips64el@0.18.20": + version "0.18.20" + resolved "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.18.20.tgz#eeff3a937de9c2310de30622a957ad1bd9183231" + integrity sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ== + "@esbuild/linux-mips64el@0.21.5": version "0.21.5" resolved "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz#e79b8eb48bf3b106fadec1ac8240fb97b4e64cbe" integrity sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg== +"@esbuild/linux-ppc64@0.18.20": + version "0.18.20" + resolved "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.18.20.tgz#2f7156bde20b01527993e6881435ad79ba9599fb" + integrity sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA== + "@esbuild/linux-ppc64@0.21.5": version "0.21.5" resolved "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz#5f2203860a143b9919d383ef7573521fb154c3e4" integrity sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w== +"@esbuild/linux-riscv64@0.18.20": + version "0.18.20" + resolved "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.18.20.tgz#6628389f210123d8b4743045af8caa7d4ddfc7a6" + integrity sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A== + "@esbuild/linux-riscv64@0.21.5": version "0.21.5" resolved "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz#07bcafd99322d5af62f618cb9e6a9b7f4bb825dc" integrity sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA== +"@esbuild/linux-s390x@0.18.20": + version "0.18.20" + resolved "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.18.20.tgz#255e81fb289b101026131858ab99fba63dcf0071" + integrity sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ== + "@esbuild/linux-s390x@0.21.5": version "0.21.5" resolved "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz#b7ccf686751d6a3e44b8627ababc8be3ef62d8de" integrity sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A== +"@esbuild/linux-x64@0.18.20": + version "0.18.20" + resolved "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.18.20.tgz#c7690b3417af318a9b6f96df3031a8865176d338" + integrity sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w== + "@esbuild/linux-x64@0.21.5": version "0.21.5" resolved "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz#6d8f0c768e070e64309af8004bb94e68ab2bb3b0" integrity sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ== +"@esbuild/netbsd-x64@0.18.20": + version "0.18.20" + resolved "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.18.20.tgz#30e8cd8a3dded63975e2df2438ca109601ebe0d1" + integrity sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A== + "@esbuild/netbsd-x64@0.21.5": version "0.21.5" resolved "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz#bbe430f60d378ecb88decb219c602667387a6047" integrity sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg== +"@esbuild/openbsd-x64@0.18.20": + version "0.18.20" + resolved "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.18.20.tgz#7812af31b205055874c8082ea9cf9ab0da6217ae" + integrity sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg== + "@esbuild/openbsd-x64@0.21.5": version "0.21.5" resolved "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz#99d1cf2937279560d2104821f5ccce220cb2af70" integrity sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow== +"@esbuild/sunos-x64@0.18.20": + version "0.18.20" + resolved "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.18.20.tgz#d5c275c3b4e73c9b0ecd38d1ca62c020f887ab9d" + integrity sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ== + "@esbuild/sunos-x64@0.21.5": version "0.21.5" resolved "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz#08741512c10d529566baba837b4fe052c8f3487b" integrity sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg== +"@esbuild/win32-arm64@0.18.20": + version "0.18.20" + resolved "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.18.20.tgz#73bc7f5a9f8a77805f357fab97f290d0e4820ac9" + integrity sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg== + "@esbuild/win32-arm64@0.21.5": version "0.21.5" resolved "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz#675b7385398411240735016144ab2e99a60fc75d" integrity sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A== +"@esbuild/win32-ia32@0.18.20": + version "0.18.20" + resolved "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.18.20.tgz#ec93cbf0ef1085cc12e71e0d661d20569ff42102" + integrity sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g== + "@esbuild/win32-ia32@0.21.5": version "0.21.5" resolved "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz#1bfc3ce98aa6ca9a0969e4d2af72144c59c1193b" integrity sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA== +"@esbuild/win32-x64@0.18.20": + version "0.18.20" + resolved "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.18.20.tgz#786c5f41f043b07afb1af37683d7c33668858f6d" + integrity sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ== + "@esbuild/win32-x64@0.21.5": version "0.21.5" resolved "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz#acad351d582d157bb145535db2a6ff53dd514b5c" @@ -705,7 +815,7 @@ dependencies: eslint-visitor-keys "^3.3.0" -"@eslint-community/regexpp@^4.10.0", "@eslint-community/regexpp@^4.6.1": +"@eslint-community/regexpp@^4.10.0", "@eslint-community/regexpp@^4.4.0", "@eslint-community/regexpp@^4.6.1": version "4.11.0" resolved "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.11.0.tgz#b0ffd0312b4a3fd2d6f77237e7248a5ad3a680ae" integrity sha512-G/M/tIiMrTAxEWRfLfQJMmGNX28IxBg4PBz8XqQhqUHLFI6TL2htpIB1iQCj144V5ee/JaKyT9/WZ0MGZWfA7A== @@ -1075,7 +1185,7 @@ "@jridgewell/gen-mapping" "^0.3.5" "@jridgewell/trace-mapping" "^0.3.25" -"@jridgewell/sourcemap-codec@^1.4.10", "@jridgewell/sourcemap-codec@^1.4.14", "@jridgewell/sourcemap-codec@^1.4.15": +"@jridgewell/sourcemap-codec@^1.4.10", "@jridgewell/sourcemap-codec@^1.4.14", "@jridgewell/sourcemap-codec@^1.4.15", "@jridgewell/sourcemap-codec@^1.5.0": version "1.5.0" resolved "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz#3188bcb273a414b0d215fd22a58540b989b9409a" integrity sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ== @@ -2149,7 +2259,7 @@ "@noble/curves" "^1.1.0" "@noble/hashes" "^1.2.0" -"@nostrwatch/nostrawl@0.0.3": +"@nostrwatch/nostrawl@*": version "0.0.3" resolved "https://registry.npmjs.org/@nostrwatch/nostrawl/-/nostrawl-0.0.3.tgz#5659ef827cce63b438fa77d992775a1e43b633c5" integrity sha512-+1kuRVnxyL2CRjrxj8NWIpotjdP4i2KG7qs9auknoNNOdYLN69R0V0sXuQVA9sUEVKXncG0RhbKS3nIo1gMjSQ== @@ -3136,6 +3246,13 @@ resolved "https://registry.npmjs.org/@types/node/-/node-16.18.104.tgz#33d5f4886c54133af0ff02445e57c5254025ee53" integrity sha512-OF3keVCbfPlkzxnnDBUZJn1RiCJzKeadjiW0xTEb0G1SUJ5gDVb3qnzZr2T4uIFvsbKJbXy1v2DN7e2zaEY7jQ== +"@types/node@^18.0.0": + version "18.19.50" + resolved "https://registry.npmjs.org/@types/node/-/node-18.19.50.tgz#8652b34ee7c0e7e2004b3f08192281808d41bf5a" + integrity sha512-xonK+NRrMBRtkL1hVCc3G+uXtjh1Al4opBLjqVmipe5ZAaBYWW6cNAiBVZ1BvmkBhep698rP3UM3aRAdSALuhg== + dependencies: + undici-types "~5.26.4" + "@types/object-mapper@6.2.2": version "6.2.2" resolved "https://registry.npmjs.org/@types/object-mapper/-/object-mapper-6.2.2.tgz#481290dea739ad6d0e7722c8a7d83e1285e6c56a" @@ -3151,7 +3268,7 @@ resolved "https://registry.npmjs.org/@types/pug/-/pug-2.0.10.tgz#52f8dbd6113517aef901db20b4f3fca543b88c1f" integrity sha512-Sk/uYFOBAB7mb74XcpizmH0KOR2Pv3D2Hmrh1Dmy5BmK3MpdSa5kqZcg6EKBdklU0bFXX9gCfzvpnyUehrPIuA== -"@types/semver@^7.5.0": +"@types/semver@^7.3.12", "@types/semver@^7.5.0": version "7.5.8" resolved "https://registry.npmjs.org/@types/semver/-/semver-7.5.8.tgz#8268a8c57a3e4abd25c165ecd36237db7948a55e" integrity sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ== @@ -3190,6 +3307,22 @@ dependencies: "@types/yargs-parser" "*" +"@typescript-eslint/eslint-plugin@^5.0.0": + version "5.62.0" + resolved "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.62.0.tgz#aeef0328d172b9e37d9bab6dbc13b87ed88977db" + integrity sha512-TiZzBSJja/LbhNPvk6yc0JrX9XqhQ0hdh6M2svYfsHGejaKFIAGd9MQ+ERIMzLGlN/kZoYIgdxFV0PuljTKXag== + dependencies: + "@eslint-community/regexpp" "^4.4.0" + "@typescript-eslint/scope-manager" "5.62.0" + "@typescript-eslint/type-utils" "5.62.0" + "@typescript-eslint/utils" "5.62.0" + debug "^4.3.4" + graphemer "^1.4.0" + ignore "^5.2.0" + natural-compare-lite "^1.4.0" + semver "^7.3.7" + tsutils "^3.21.0" + "@typescript-eslint/eslint-plugin@^7.1.1": version "7.17.0" resolved "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.17.0.tgz#c8ed1af1ad2928ede5cdd207f7e3090499e1f77b" @@ -3205,6 +3338,16 @@ natural-compare "^1.4.0" ts-api-utils "^1.3.0" +"@typescript-eslint/parser@^5.0.0": + version "5.62.0" + resolved "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.62.0.tgz#1b63d082d849a2fcae8a569248fbe2ee1b8a56c7" + integrity sha512-VlJEV0fOQ7BExOsHYAGrgbEiZoi8D+Bl2+f6V2RrXerRSylnp+ZBHmPvaIa8cz0Ajx7WO7Z5RqfgYg7ED1nRhA== + dependencies: + "@typescript-eslint/scope-manager" "5.62.0" + "@typescript-eslint/types" "5.62.0" + "@typescript-eslint/typescript-estree" "5.62.0" + debug "^4.3.4" + "@typescript-eslint/parser@^7.1.1": version "7.17.0" resolved "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.17.0.tgz#be8e32c159190cd40a305a2121220eadea5a88e7" @@ -3216,6 +3359,14 @@ "@typescript-eslint/visitor-keys" "7.17.0" debug "^4.3.4" +"@typescript-eslint/scope-manager@5.62.0": + version "5.62.0" + resolved "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.62.0.tgz#d9457ccc6a0b8d6b37d0eb252a23022478c5460c" + integrity sha512-VXuvVvZeQCQb5Zgf4HAxc04q5j+WrNAtNh9OwCsCgpKqESMTu3tF/jhZ3xG6T4NZwWl65Bg8KuS2uEvhSfLl0w== + dependencies: + "@typescript-eslint/types" "5.62.0" + "@typescript-eslint/visitor-keys" "5.62.0" + "@typescript-eslint/scope-manager@7.17.0": version "7.17.0" resolved "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.17.0.tgz#e072d0f914662a7bfd6c058165e3c2b35ea26b9d" @@ -3224,6 +3375,16 @@ "@typescript-eslint/types" "7.17.0" "@typescript-eslint/visitor-keys" "7.17.0" +"@typescript-eslint/type-utils@5.62.0": + version "5.62.0" + resolved "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.62.0.tgz#286f0389c41681376cdad96b309cedd17d70346a" + integrity sha512-xsSQreu+VnfbqQpW5vnCJdq1Z3Q0U31qiWmRhr98ONQmcp/yhiPJFPq8MXiJVLiksmOKSjIldZzkebzHuCGzew== + dependencies: + "@typescript-eslint/typescript-estree" "5.62.0" + "@typescript-eslint/utils" "5.62.0" + debug "^4.3.4" + tsutils "^3.21.0" + "@typescript-eslint/type-utils@7.17.0": version "7.17.0" resolved "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.17.0.tgz#c5da78feb134c9c9978cbe89e2b1a589ed22091a" @@ -3234,11 +3395,29 @@ debug "^4.3.4" ts-api-utils "^1.3.0" +"@typescript-eslint/types@5.62.0": + version "5.62.0" + resolved "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.62.0.tgz#258607e60effa309f067608931c3df6fed41fd2f" + integrity sha512-87NVngcbVXUahrRTqIK27gD2t5Cu1yuCXxbLcFtCzZGlfyVWWh8mLHkoxzjsB6DDNnvdL+fW8MiwPEJyGJQDgQ== + "@typescript-eslint/types@7.17.0": version "7.17.0" resolved "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.17.0.tgz#7ce8185bdf06bc3494e73d143dbf3293111b9cff" integrity sha512-a29Ir0EbyKTKHnZWbNsrc/gqfIBqYPwj3F2M+jWE/9bqfEHg0AMtXzkbUkOG6QgEScxh2+Pz9OXe11jHDnHR7A== +"@typescript-eslint/typescript-estree@5.62.0": + version "5.62.0" + resolved "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.62.0.tgz#7d17794b77fabcac615d6a48fb143330d962eb9b" + integrity sha512-CmcQ6uY7b9y694lKdRB8FEel7JbU/40iSAPomu++SjLMntB+2Leay2LO6i8VnJk58MtE9/nQSFIH6jpyRWyYzA== + dependencies: + "@typescript-eslint/types" "5.62.0" + "@typescript-eslint/visitor-keys" "5.62.0" + debug "^4.3.4" + globby "^11.1.0" + is-glob "^4.0.3" + semver "^7.3.7" + tsutils "^3.21.0" + "@typescript-eslint/typescript-estree@7.17.0": version "7.17.0" resolved "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.17.0.tgz#dcab3fea4c07482329dd6107d3c6480e228e4130" @@ -3253,6 +3432,20 @@ semver "^7.6.0" ts-api-utils "^1.3.0" +"@typescript-eslint/utils@5.62.0": + version "5.62.0" + resolved "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.62.0.tgz#141e809c71636e4a75daa39faed2fb5f4b10df86" + integrity sha512-n8oxjeb5aIbPFEtmQxQYOLI0i9n5ySBEY/ZEHHZqKQSFnxio1rv6dthascc9dLuwrL0RC5mPCxB7vnAVGAYWAQ== + dependencies: + "@eslint-community/eslint-utils" "^4.2.0" + "@types/json-schema" "^7.0.9" + "@types/semver" "^7.3.12" + "@typescript-eslint/scope-manager" "5.62.0" + "@typescript-eslint/types" "5.62.0" + "@typescript-eslint/typescript-estree" "5.62.0" + eslint-scope "^5.1.1" + semver "^7.3.7" + "@typescript-eslint/utils@7.17.0": version "7.17.0" resolved "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.17.0.tgz#815cd85b9001845d41b699b0ce4f92d6dfb84902" @@ -3263,6 +3456,14 @@ "@typescript-eslint/types" "7.17.0" "@typescript-eslint/typescript-estree" "7.17.0" +"@typescript-eslint/visitor-keys@5.62.0": + version "5.62.0" + resolved "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.62.0.tgz#2174011917ce582875954ffe2f6912d5931e353e" + integrity sha512-07ny+LHRzQXepkGg6w0mFY41fVUNBrL2Roj/++7V1txKugfjm/Ci/qSND03r2RhlJhJYMcTn9AhhSSqQp0Ysyw== + dependencies: + "@typescript-eslint/types" "5.62.0" + eslint-visitor-keys "^3.3.0" + "@typescript-eslint/visitor-keys@7.17.0": version "7.17.0" resolved "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.17.0.tgz#680465c734be30969e564b4647f38d6cdf49bfb0" @@ -3295,6 +3496,15 @@ strip-literal "^2.0.0" test-exclude "^6.0.0" +"@vitest/expect@0.31.4": + version "0.31.4" + resolved "https://registry.npmjs.org/@vitest/expect/-/expect-0.31.4.tgz#115c517404488bf3cb6ce4ac411c40d8e86891b8" + integrity sha512-tibyx8o7GUyGHZGyPgzwiaPaLDQ9MMuCOrc03BYT0nryUuhLbL7NV2r/q98iv5STlwMgaKuFJkgBW/8iPKwlSg== + dependencies: + "@vitest/spy" "0.31.4" + "@vitest/utils" "0.31.4" + chai "^4.3.7" + "@vitest/expect@0.34.6": version "0.34.6" resolved "https://registry.npmjs.org/@vitest/expect/-/expect-0.34.6.tgz#608a7b7a9aa3de0919db99b4cc087340a03ea77e" @@ -3323,16 +3533,6 @@ chai "^5.1.1" tinyrainbow "^1.2.0" -"@vitest/expect@2.0.5": - version "2.0.5" - resolved "https://registry.npmjs.org/@vitest/expect/-/expect-2.0.5.tgz#f3745a6a2c18acbea4d39f5935e913f40d26fa86" - integrity sha512-yHZtwuP7JZivj65Gxoi8upUN2OzHTi3zVfjwdpu2WrvCZPLwsJ2Ey5ILIPccoW23dd/zQBlJ4/dhi7DWNyXCpA== - dependencies: - "@vitest/spy" "2.0.5" - "@vitest/utils" "2.0.5" - chai "^5.1.1" - tinyrainbow "^1.2.0" - "@vitest/pretty-format@2.0.4", "@vitest/pretty-format@^2.0.4": version "2.0.4" resolved "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-2.0.4.tgz#9a3934932e7f8ddd836b38c34ddaeec91bd0f82e" @@ -3340,12 +3540,15 @@ dependencies: tinyrainbow "^1.2.0" -"@vitest/pretty-format@2.0.5", "@vitest/pretty-format@^2.0.5": - version "2.0.5" - resolved "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-2.0.5.tgz#91d2e6d3a7235c742e1a6cc50e7786e2f2979b1e" - integrity sha512-h8k+1oWHfwTkyTkb9egzwNMfJAEx4veaPSnMeKbVSjp4euqGSbQlm5+6VHwTr7u4FJslVVsUG5nopCaAYdOmSQ== +"@vitest/runner@0.31.4": + version "0.31.4" + resolved "https://registry.npmjs.org/@vitest/runner/-/runner-0.31.4.tgz#e99abee89132a500d9726a53b58dfc9160db1078" + integrity sha512-Wgm6UER+gwq6zkyrm5/wbpXGF+g+UBB78asJlFkIOwyse0pz8lZoiC6SW5i4gPnls/zUcPLWS7Zog0LVepXnpg== dependencies: - tinyrainbow "^1.2.0" + "@vitest/utils" "0.31.4" + concordance "^5.0.4" + p-limit "^4.0.0" + pathe "^1.1.0" "@vitest/runner@0.34.6": version "0.34.6" @@ -3373,13 +3576,14 @@ "@vitest/utils" "2.0.4" pathe "^1.1.2" -"@vitest/runner@2.0.5": - version "2.0.5" - resolved "https://registry.npmjs.org/@vitest/runner/-/runner-2.0.5.tgz#89197e712bb93513537d6876995a4843392b2a84" - integrity sha512-TfRfZa6Bkk9ky4tW0z20WKXFEwwvWhRY+84CnSEtq4+3ZvDlJyY32oNTJtM7AW9ihW90tX/1Q78cb6FjoAs+ig== +"@vitest/snapshot@0.31.4": + version "0.31.4" + resolved "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-0.31.4.tgz#59a42046fec4950a1ac70cf0ec64aada3b995559" + integrity sha512-LemvNumL3NdWSmfVAMpXILGyaXPkZbG5tyl6+RQSdcHnTj6hvA49UAI8jzez9oQyE/FWLKRSNqTGzsHuk89LRA== dependencies: - "@vitest/utils" "2.0.5" - pathe "^1.1.2" + magic-string "^0.30.0" + pathe "^1.1.0" + pretty-format "^27.5.1" "@vitest/snapshot@0.34.6": version "0.34.6" @@ -3408,14 +3612,12 @@ magic-string "^0.30.10" pathe "^1.1.2" -"@vitest/snapshot@2.0.5": - version "2.0.5" - resolved "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-2.0.5.tgz#a2346bc5013b73c44670c277c430e0334690a162" - integrity sha512-SgCPUeDFLaM0mIUHfaArq8fD2WbaXG/zVXjRupthYfYGzc8ztbFbu6dUNOblBG7XLMR1kEhS/DNnfCZ2IhdDew== +"@vitest/spy@0.31.4": + version "0.31.4" + resolved "https://registry.npmjs.org/@vitest/spy/-/spy-0.31.4.tgz#fce8e348cea32deff79996d116c67893b19cc47d" + integrity sha512-3ei5ZH1s3aqbEyftPAzSuunGICRuhE+IXOmpURFdkm5ybUADk+viyQfejNk6q8M5QGX8/EVKw+QWMEP3DTJDag== dependencies: - "@vitest/pretty-format" "2.0.5" - magic-string "^0.30.10" - pathe "^1.1.2" + tinyspy "^2.1.0" "@vitest/spy@0.34.6": version "0.34.6" @@ -3438,13 +3640,6 @@ dependencies: tinyspy "^3.0.0" -"@vitest/spy@2.0.5": - version "2.0.5" - resolved "https://registry.npmjs.org/@vitest/spy/-/spy-2.0.5.tgz#590fc07df84a78b8e9dd976ec2090920084a2b9f" - integrity sha512-c/jdthAhvJdpfVuaexSrnawxZz6pywlTPe84LUB2m/4t3rl2fTo9NFGBG4oWgaD+FTgDDV8hJ/nibT7IfH3JfA== - dependencies: - tinyspy "^3.0.0" - "@vitest/ui@0.34.6": version "0.34.6" resolved "https://registry.npmjs.org/@vitest/ui/-/ui-0.34.6.tgz#86a9d58d1514aaea6a4b27ddd3c430646afca488" @@ -3471,6 +3666,15 @@ picocolors "^1.0.0" sirv "^2.0.4" +"@vitest/utils@0.31.4": + version "0.31.4" + resolved "https://registry.npmjs.org/@vitest/utils/-/utils-0.31.4.tgz#5cfdcecfd604a7dbe3972cfe0f2b1e0af1246ad2" + integrity sha512-DobZbHacWznoGUfYU8XDPY78UubJxXfMNY1+SUdOp1NsI34eopSA6aZMeaGu10waSOeYwE8lxrd/pLfT0RMxjQ== + dependencies: + concordance "^5.0.4" + loupe "^2.3.6" + pretty-format "^27.5.1" + "@vitest/utils@0.34.6": version "0.34.6" resolved "https://registry.npmjs.org/@vitest/utils/-/utils-0.34.6.tgz#38a0a7eedddb8e7291af09a2409cb8a189516968" @@ -3500,16 +3704,6 @@ loupe "^3.1.1" tinyrainbow "^1.2.0" -"@vitest/utils@2.0.5": - version "2.0.5" - resolved "https://registry.npmjs.org/@vitest/utils/-/utils-2.0.5.tgz#6f8307a4b6bc6ceb9270007f73c67c915944e926" - integrity sha512-d8HKbqIcya+GR67mkZbrzhS5kKhtp8dQLcmRZLGTscGVg7yImT82cIrhtn2L8+VujWcy6KZweApgNmPsTAO/UQ== - dependencies: - "@vitest/pretty-format" "2.0.5" - estree-walker "^3.0.3" - loupe "^3.1.1" - tinyrainbow "^1.2.0" - "@webassemblyjs/ast@1.12.1", "@webassemblyjs/ast@^1.12.1": version "1.12.1" resolved "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.12.1.tgz#bb16a0e8b1914f979f45864c23819cc3e3f0d4bb" @@ -4095,6 +4289,11 @@ bluebird@3.7.2: resolved "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz#9f229c15be272454ffa973ace0dbee79a1b0c36f" integrity sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg== +blueimp-md5@^2.10.0: + version "2.19.0" + resolved "https://registry.npmjs.org/blueimp-md5/-/blueimp-md5-2.19.0.tgz#b53feea5498dcb53dc6ec4b823adb84b729c4af0" + integrity sha512-DRQrD6gJyy8FbiE4s+bDoXS9hiW3Vbx5uCdwvcCf3zLHL+Iv7LtGHLpr+GZV8rHG8tK766FGYBwRbu8pELTt+w== + bn.js@^4.0.0, bn.js@^4.1.0, bn.js@^4.11.9: version "4.12.0" resolved "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz#775b3f278efbb9718eec7361f483fb36fbbfea88" @@ -4367,7 +4566,7 @@ catharsis@0.5.6: resolved "https://registry.npmjs.org/catharsis/-/catharsis-0.5.6.tgz#210a0cfa23c5d09fa994d6fafe1a76383c170cbb" integrity sha512-FIKGuortcMexWC4B1gK+iJYr2xcGiWjJDdQYeqvWM5fDxS9l7EXjNixY+fjsquWcCXFOCZk84CYfmmSSk4Cb3g== -chai@^4.3.10, chai@^4.3.6: +chai@^4.3.10, chai@^4.3.6, chai@^4.3.7: version "4.5.0" resolved "https://registry.npmjs.org/chai/-/chai-4.5.0.tgz#707e49923afdd9b13a8b0b47d33d732d13812fd8" integrity sha512-RITGBfijLkBddZvnn8jdqoTypxvqbOLYQkGGxXzeFjVHvudaPw0HNFD9x928/eUwYWd2dPCugVqspGALTZZQKw== @@ -4641,6 +4840,20 @@ concat-map@0.0.1: resolved "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== +concordance@^5.0.4: + version "5.0.4" + resolved "https://registry.npmjs.org/concordance/-/concordance-5.0.4.tgz#9896073261adced72f88d60e4d56f8efc4bbbbd2" + integrity sha512-OAcsnTEYu1ARJqWVGwf4zh4JDfHZEaSNlNccFmt8YjB2l/n19/PF2viLINHc57vO4FKIAFl2FWASIGZZWZ2Kxw== + dependencies: + date-time "^3.1.0" + esutils "^2.0.3" + fast-diff "^1.2.0" + js-string-escape "^1.0.1" + lodash "^4.17.15" + md5-hex "^3.0.1" + semver "^7.3.2" + well-known-symbols "^2.0.0" + confbox@^0.1.7: version "0.1.7" resolved "https://registry.npmjs.org/confbox/-/confbox-0.1.7.tgz#ccfc0a2bcae36a84838e83a3b7f770fb17d6c579" @@ -4816,6 +5029,13 @@ data-uri-to-buffer@^4.0.0: resolved "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz#d8feb2b2881e6a4f58c2e08acfd0e2834e26222e" integrity sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A== +date-time@^3.1.0: + version "3.1.0" + resolved "https://registry.npmjs.org/date-time/-/date-time-3.1.0.tgz#0d1e934d170579f481ed8df1e2b8ff70ee845e1e" + integrity sha512-uqCUKXE5q1PNBXjPqvwhwJf9SwMoAHBgWJ6DcrnS5o+W2JOiIILl0JEdVD8SGujrNS02GGxgwAg2PN2zONgtjg== + dependencies: + time-zone "^1.0.0" + dayjs@^1.11.10: version "1.11.12" resolved "https://registry.npmjs.org/dayjs/-/dayjs-1.11.12.tgz#5245226cc7f40a15bf52e0b99fd2a04669ccac1d" @@ -5448,6 +5668,34 @@ esbuild@^0.15.9: esbuild-windows-64 "0.15.18" esbuild-windows-arm64 "0.15.18" +esbuild@^0.18.10: + version "0.18.20" + resolved "https://registry.npmjs.org/esbuild/-/esbuild-0.18.20.tgz#4709f5a34801b43b799ab7d6d82f7284a9b7a7a6" + integrity sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA== + optionalDependencies: + "@esbuild/android-arm" "0.18.20" + "@esbuild/android-arm64" "0.18.20" + "@esbuild/android-x64" "0.18.20" + "@esbuild/darwin-arm64" "0.18.20" + "@esbuild/darwin-x64" "0.18.20" + "@esbuild/freebsd-arm64" "0.18.20" + "@esbuild/freebsd-x64" "0.18.20" + "@esbuild/linux-arm" "0.18.20" + "@esbuild/linux-arm64" "0.18.20" + "@esbuild/linux-ia32" "0.18.20" + "@esbuild/linux-loong64" "0.18.20" + "@esbuild/linux-mips64el" "0.18.20" + "@esbuild/linux-ppc64" "0.18.20" + "@esbuild/linux-riscv64" "0.18.20" + "@esbuild/linux-s390x" "0.18.20" + "@esbuild/linux-x64" "0.18.20" + "@esbuild/netbsd-x64" "0.18.20" + "@esbuild/openbsd-x64" "0.18.20" + "@esbuild/sunos-x64" "0.18.20" + "@esbuild/win32-arm64" "0.18.20" + "@esbuild/win32-ia32" "0.18.20" + "@esbuild/win32-x64" "0.18.20" + esbuild@^0.21.3, esbuild@~0.21.5: version "0.21.5" resolved "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz#9ca301b120922959b766360d8ac830da0d02997d" @@ -5502,7 +5750,7 @@ escape-string-regexp@^4.0.0: resolved "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34" integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== -eslint-scope@5.1.1: +eslint-scope@5.1.1, eslint-scope@^5.1.1: version "5.1.1" resolved "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz#e786e59a66cb92b3f6c1fb0d508aab174848f48c" integrity sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw== @@ -5523,7 +5771,7 @@ eslint-visitor-keys@^3.3.0, eslint-visitor-keys@^3.4.1, eslint-visitor-keys@^3.4 resolved "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz#0cd72fe8550e3c2eae156a96a4dddcd1c8ac5800" integrity sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag== -eslint@^8.57.0: +eslint@^8.0.0, eslint@^8.57.0: version "8.57.0" resolved "https://registry.npmjs.org/eslint/-/eslint-8.57.0.tgz#c786a6fd0e0b68941aaf624596fb987089195668" integrity sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ== @@ -5632,7 +5880,7 @@ estree-walker@^3.0.0, estree-walker@^3.0.3: dependencies: "@types/estree" "^1.0.0" -esutils@^2.0.2: +esutils@^2.0.2, esutils@^2.0.3: version "2.0.3" resolved "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== @@ -5755,6 +6003,11 @@ fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3: resolved "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== +fast-diff@^1.2.0: + version "1.3.0" + resolved "https://registry.npmjs.org/fast-diff/-/fast-diff-1.3.0.tgz#ece407fa550a64d638536cd727e129c61616e0f0" + integrity sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw== + fast-glob@^3.2.9, fast-glob@^3.3.0, fast-glob@^3.3.2: version "3.3.2" resolved "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz#a904501e57cfdd2ffcded45e99a54fef55e46129" @@ -7214,6 +7467,11 @@ js-base64@^3.7.7: resolved "https://registry.npmjs.org/js-base64/-/js-base64-3.7.7.tgz#e51b84bf78fbf5702b9541e2cb7bfcb893b43e79" integrity sha512-7rCnleh0z2CkXhH67J8K1Ytz0b2Y+yxTPL+/KOJoa20hfnVQ/3/T6W/KflYI4bRHRagNeXeU2bkNGI3v1oS/lw== +js-string-escape@^1.0.1: + version "1.0.1" + resolved "https://registry.npmjs.org/js-string-escape/-/js-string-escape-1.0.1.tgz#e2625badbc0d67c7533e9edc1068c587ae4137ef" + integrity sha512-Smw4xcfIQ5LVjAOuJCvN/zIodzA/BBSsluuoSykP+lUvScIi4U6RJLfwHet5cxFnCswUjISV8oAXaqaJDY3chg== + js-tokens@^4.0.0: version "4.0.0" resolved "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" @@ -7638,7 +7896,7 @@ lodash.startcase@^4.4.0: resolved "https://registry.npmjs.org/lodash.startcase/-/lodash.startcase-4.4.0.tgz#9436e34ed26093ed7ffae1936144350915d9add8" integrity sha512-+WKqsK294HMSc2jEbNgpHpd0JfIBhp7rEV4aqXWqFr6AlXov+SlcgB1Fv01y2kGe3Gc8nMW7VA0SrGuSkRfIEg== -lodash@^4.17.11, lodash@^4.17.14, lodash@^4.17.21: +lodash@^4.17.11, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.21: version "4.17.21" resolved "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== @@ -7733,6 +7991,13 @@ magic-string@^0.25.7: dependencies: sourcemap-codec "^1.4.8" +magic-string@^0.30.0: + version "0.30.11" + resolved "https://registry.npmjs.org/magic-string/-/magic-string-0.30.11.tgz#301a6f93b3e8c2cb13ac1a7a673492c0dfd12954" + integrity sha512-+Wri9p0QHMy+545hKww7YAu5NyzF8iomPL/RQazugQ9+Ez4Ic3mERMd8ZTX5rfK944j+560ZJi8iAwgak1Ac7A== + dependencies: + "@jridgewell/sourcemap-codec" "^1.5.0" + magic-string@^0.30.1, magic-string@^0.30.10, magic-string@^0.30.3, magic-string@^0.30.4, magic-string@^0.30.5: version "0.30.10" resolved "https://registry.npmjs.org/magic-string/-/magic-string-0.30.10.tgz#123d9c41a0cb5640c892b041d4cfb3bd0aa4b39e" @@ -7796,6 +8061,13 @@ marked@^4.0.16: resolved "https://registry.npmjs.org/marked/-/marked-4.3.0.tgz#796362821b019f734054582038b116481b456cf3" integrity sha512-PRsaiG84bK+AMvxziE/lCFss8juXjNaWzVbN5tXAm4XjeaS9NAHhop+PjQxz2A9h8Q4M/xGmzP8vqNwy6JeK0A== +md5-hex@^3.0.1: + version "3.0.1" + resolved "https://registry.npmjs.org/md5-hex/-/md5-hex-3.0.1.tgz#be3741b510591434b2784d79e556eefc2c9a8e5c" + integrity sha512-BUiRtTtV39LIJwinWBjqVsU9xhdnz7/i889V859IBFpuqGAj6LuOvHv5XLbgZ2R7ptJoJaEcxkv88/h25T7Ciw== + dependencies: + blueimp-md5 "^2.10.0" + md5.js@^1.3.4: version "1.3.5" resolved "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz#b5d07b8e3216e3e27cd728d72f70d1e6a342005f" @@ -7960,7 +8232,7 @@ mkdirp@^0.5.1, mkdirp@^0.5.6: dependencies: minimist "^1.2.6" -mlly@^1.4.0, mlly@^1.4.2, mlly@^1.7.1: +mlly@^1.2.0, mlly@^1.4.0, mlly@^1.4.2, mlly@^1.7.1: version "1.7.1" resolved "https://registry.npmjs.org/mlly/-/mlly-1.7.1.tgz#e0336429bb0731b6a8e887b438cbdae522c8f32f" integrity sha512-rrVRZRELyQzrIUAVMHxP97kv+G786pHmOKzuFII8zDYahFBS7qnHh2AlYSl1GAHhaMPCz6/oHjVMcfFYgFYHgA== @@ -8045,6 +8317,11 @@ nanoid@^3.3.7: resolved "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz#d0c301a691bc8d54efa0a2226ccf3fe2fd656bd8" integrity sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g== +natural-compare-lite@^1.4.0: + version "1.4.0" + resolved "https://registry.npmjs.org/natural-compare-lite/-/natural-compare-lite-1.4.0.tgz#17b09581988979fddafe0201e931ba933c96cbb4" + integrity sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g== + natural-compare@^1.4.0: version "1.4.0" resolved "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" @@ -8715,7 +8992,7 @@ path-type@^4.0.0: resolved "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b" integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw== -pathe@^1.1.1, pathe@^1.1.2: +pathe@^1.1.0, pathe@^1.1.1, pathe@^1.1.2: version "1.1.2" resolved "https://registry.npmjs.org/pathe/-/pathe-1.1.2.tgz#6c4cb47a945692e48a1ddd6e4094d170516437ec" integrity sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ== @@ -8921,6 +9198,15 @@ postcss@^8.4.18, postcss@^8.4.23, postcss@^8.4.32, postcss@^8.4.38, postcss@^8.4 picocolors "^1.0.1" source-map-js "^1.2.0" +postcss@^8.4.27: + version "8.4.45" + resolved "https://registry.npmjs.org/postcss/-/postcss-8.4.45.tgz#538d13d89a16ef71edbf75d895284ae06b79e603" + integrity sha512-7KTLTdzdZZYscUc65XmjFiB73vBhBfbPztCYdUNvlaso9PrzjzcmjqBPR0lNGkcVlcO4BjiO5rK/qNz+XAen1Q== + dependencies: + nanoid "^3.3.7" + picocolors "^1.0.1" + source-map-js "^1.2.0" + prelude-ls@^1.2.1: version "1.2.1" resolved "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" @@ -8931,6 +9217,15 @@ prettier@^2.7.1: resolved "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz#e8c5d7e98a4305ffe3de2e1fc4aca1a71c28b1da" integrity sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q== +pretty-format@^27.5.1: + version "27.5.1" + resolved "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz#2181879fdea51a7a5851fb39d920faa63f01d88e" + integrity sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ== + dependencies: + ansi-regex "^5.0.1" + ansi-styles "^5.0.0" + react-is "^17.0.1" + pretty-format@^29.5.0, pretty-format@^29.7.0: version "29.7.0" resolved "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz#ca42c758310f365bfa71a0bda0a807160b776812" @@ -9149,6 +9444,11 @@ randomfill@^1.0.3: randombytes "^2.0.5" safe-buffer "^5.1.0" +react-is@^17.0.1: + version "17.0.2" + resolved "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz#e691d4a8e9c789365655539ab372762b0efb54f0" + integrity sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w== + react-is@^18.0.0: version "18.3.1" resolved "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz#e83557dc12eae63a99e003a46388b1dcbb44db7e" @@ -9370,6 +9670,13 @@ rollup@^2.74.1, rollup@^2.79.1: optionalDependencies: fsevents "~2.3.2" +rollup@^3.27.1: + version "3.29.4" + resolved "https://registry.npmjs.org/rollup/-/rollup-3.29.4.tgz#4d70c0f9834146df8705bfb69a9a19c9e1109981" + integrity sha512-oWzmBZwvYrU0iJHtDmhsm662rC15FRXmcjCk1xD771dFDx5jJ02ufAQQTn0etB2emNk4J9EZg/yWKpsn9BWGRw== + optionalDependencies: + fsevents "~2.3.2" + rollup@^4.13.0: version "4.19.1" resolved "https://registry.npmjs.org/rollup/-/rollup-4.19.1.tgz#21d865cd60d4a325172ce8b082e60caccd97b309" @@ -9470,7 +9777,7 @@ semver@^6.3.0, semver@^6.3.1: resolved "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4" integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== -semver@^7.3.4, semver@^7.5.3, semver@^7.5.4, semver@^7.6.0: +semver@^7.3.2, semver@^7.3.4, semver@^7.3.7, semver@^7.5.3, semver@^7.5.4, semver@^7.6.0: version "7.6.3" resolved "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz#980f7b5550bc175fb4dc09403085627f9eb33143" integrity sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A== @@ -9753,7 +10060,7 @@ statuses@2.0.1: resolved "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz#55cb000ccf1d48728bd23c685a063998cf1a1b63" integrity sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ== -std-env@^3.3.3, std-env@^3.5.0, std-env@^3.7.0: +std-env@^3.3.2, std-env@^3.3.3, std-env@^3.5.0, std-env@^3.7.0: version "3.7.0" resolved "https://registry.npmjs.org/std-env/-/std-env-3.7.0.tgz#c9f7386ced6ecf13360b6c6c55b8aaa4ef7481d2" integrity sha512-JPbdCEQLj1w5GilpiHAx3qJvFndqybBysA3qUOnznweH4QbNYUsW/ea8QzSrnh0vNsezMMw5bcVool8lM0gwzg== @@ -10207,6 +10514,11 @@ through@^2.3.4: resolved "https://registry.npmjs.org/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" integrity sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg== +time-zone@^1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/time-zone/-/time-zone-1.0.0.tgz#99c5bf55958966af6d06d83bdf3800dc82faec5d" + integrity sha512-TIsDdtKo6+XrPtiTm1ssmMngN1sAhyKnTO2kunQWqNPWIVvCm15Wmw4SWInwTVgJ5u/Tr04+8Ei9TNcw4x4ONA== + timers-browserify@^2.0.4: version "2.0.12" resolved "https://registry.npmjs.org/timers-browserify/-/timers-browserify-2.0.12.tgz#44a45c11fbf407f34f97bccd1577c652361b00ee" @@ -10242,6 +10554,11 @@ tinypool@^0.2.4: resolved "https://registry.npmjs.org/tinypool/-/tinypool-0.2.4.tgz#4d2598c4689d1a2ce267ddf3360a9c6b3925a20c" integrity sha512-Vs3rhkUH6Qq1t5bqtb816oT+HeJTXfwt2cbPH17sWHIYKTotQIFPk3tf2fgqRrVyMDVOc1EnPgzIxfIulXVzwQ== +tinypool@^0.5.0: + version "0.5.0" + resolved "https://registry.npmjs.org/tinypool/-/tinypool-0.5.0.tgz#3861c3069bf71e4f1f5aa2d2e6b3aaacc278961e" + integrity sha512-paHQtnrlS1QZYKF/GnLoOM/DN9fqaGOFbCbxzAhwniySnzl9Ebk8w73/dd34DAhe/obUbPAOldTyYXQZxnPBPQ== + tinypool@^0.7.0: version "0.7.0" resolved "https://registry.npmjs.org/tinypool/-/tinypool-0.7.0.tgz#88053cc99b4a594382af23190c609d93fddf8021" @@ -10267,7 +10584,7 @@ tinyspy@^1.0.0: resolved "https://registry.npmjs.org/tinyspy/-/tinyspy-1.1.1.tgz#0cb91d5157892af38cb2d217f5c7e8507a5bf092" integrity sha512-UVq5AXt/gQlti7oxoIg5oi/9r0WpF7DGEVwXgqWSMmyN16+e3tl5lIvTaOpJ3TAtu5xFzWccFRM4R5NaWHF+4g== -tinyspy@^2.1.1, tinyspy@^2.2.0: +tinyspy@^2.1.0, tinyspy@^2.1.1, tinyspy@^2.2.0: version "2.2.1" resolved "https://registry.npmjs.org/tinyspy/-/tinyspy-2.2.1.tgz#117b2342f1f38a0dbdcc73a50a454883adf861d1" integrity sha512-KYad6Vy5VDWV4GH3fjpseMQ/XU2BhIYP7Vzd0LG44qRWm/Yt2WCOTicFdvmgo6gWaqooMQCawTtILVQJupKu7A== @@ -10425,6 +10742,11 @@ tseep@1.2.2, tseep@^1.1.1: resolved "https://registry.npmjs.org/tseep/-/tseep-1.2.2.tgz#0cdd76097f4c427df93ce15a805f08fc772889db" integrity sha512-GgPFuNx+08UaYBYmJQmuI86ykYa2PUUtfXAYb4MLRHGunSCp8k9N+dbsR4PK1yk4/zV9q4e4PrNg8ymXqGYaYA== +tslib@^1.8.1: + version "1.14.1" + resolved "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" + integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== + tslib@^2.0.0, tslib@^2.0.3, tslib@^2.1.0, tslib@^2.4.1: version "2.6.3" resolved "https://registry.npmjs.org/tslib/-/tslib-2.6.3.tgz#0438f810ad7a9edcde7a241c3d80db693c8cbfe0" @@ -10460,6 +10782,13 @@ tsup@^5.1.0: sucrase "^3.20.3" tree-kill "^1.2.2" +tsutils@^3.21.0: + version "3.21.0" + resolved "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz#b48717d394cea6c1e096983eed58e9d61715b623" + integrity sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA== + dependencies: + tslib "^1.8.1" + tsx@4.16.2: version "4.16.2" resolved "https://registry.npmjs.org/tsx/-/tsx-4.16.2.tgz#8722be119ae226ef0b4c6210d5ee90f3ba823f19" @@ -10570,11 +10899,6 @@ typescript@5.5.4, typescript@^5.0.0, typescript@^5.0.3, typescript@^5.3.3: resolved "https://registry.npmjs.org/typescript/-/typescript-5.5.4.tgz#d9852d6c82bad2d2eda4fd74a5762a8f5909e9ba" integrity sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q== -typescript@5.6.2: - version "5.6.2" - resolved "https://registry.npmjs.org/typescript/-/typescript-5.6.2.tgz#d1de67b6bef77c41823f822df8f0b3bcff60a5a0" - integrity sha512-NW8ByodCSNCwZeghjN3o+JX5OFH0Ojg6sadjEKY4huZ52TqbJTJnDo5+Tw98lSy63NZvi4n+ez5m2u5d4PkZyw== - typical@^4.0.0: version "4.0.0" resolved "https://registry.npmjs.org/typical/-/typical-4.0.0.tgz#cbeaff3b9d7ae1e2bbfaf5a4e6f11eccfde94fc4" @@ -10745,6 +11069,18 @@ v8-to-istanbul@^9.0.1: "@types/istanbul-lib-coverage" "^2.0.1" convert-source-map "^2.0.0" +vite-node@0.31.4: + version "0.31.4" + resolved "https://registry.npmjs.org/vite-node/-/vite-node-0.31.4.tgz#0437f76c35fa83f0a868d3fb5896ca9e164291f5" + integrity sha512-uzL377GjJtTbuc5KQxVbDu2xfU/x0wVjUtXQR2ihS21q/NK6ROr4oG0rsSkBBddZUVCwzfx22in76/0ZZHXgkQ== + dependencies: + cac "^6.7.14" + debug "^4.3.4" + mlly "^1.2.0" + pathe "^1.1.0" + picocolors "^1.0.0" + vite "^3.0.0 || ^4.0.0" + vite-node@0.34.6: version "0.34.6" resolved "https://registry.npmjs.org/vite-node/-/vite-node-0.34.6.tgz#34d19795de1498562bf21541a58edcd106328a17" @@ -10779,17 +11115,6 @@ vite-node@2.0.4: tinyrainbow "^1.2.0" vite "^5.0.0" -vite-node@2.0.5: - version "2.0.5" - resolved "https://registry.npmjs.org/vite-node/-/vite-node-2.0.5.tgz#36d909188fc6e3aba3da5fc095b3637d0d18e27b" - integrity sha512-LdsW4pxj0Ot69FAoXZ1yTnA9bjGohr2yNBU7QKRxpz8ITSkhuDl6h3zS/tvgz4qrNjeRnvrWeXQ8ZF7Um4W00Q== - dependencies: - cac "^6.7.14" - debug "^4.3.5" - pathe "^1.1.2" - tinyrainbow "^1.2.0" - vite "^5.0.0" - vite-plugin-bundle@^1.0.0: version "1.1.1" resolved "https://registry.npmjs.org/vite-plugin-bundle/-/vite-plugin-bundle-1.1.1.tgz#b30547409389d8619e84e4541f3f970fe9426454" @@ -10850,6 +11175,17 @@ vite@5.3.1: optionalDependencies: fsevents "~2.3.2" +"vite@^3.0.0 || ^4.0.0": + version "4.5.3" + resolved "https://registry.npmjs.org/vite/-/vite-4.5.3.tgz#d88a4529ea58bae97294c7e2e6f0eab39a50fb1a" + integrity sha512-kQL23kMeX92v3ph7IauVkXkikdDRsYMGTVl5KY2E9OY4ONLvkHf04MDTbnfo6NKxZiDLWzVpP5oTa8hQD8U3dg== + dependencies: + esbuild "^0.18.10" + postcss "^8.4.27" + rollup "^3.27.1" + optionalDependencies: + fsevents "~2.3.2" + "vite@^3.0.0 || ^4.0.0 || ^5.0.0-0", "vite@^3.1.0 || ^4.0.0 || ^5.0.0-0", vite@^5.0.0, vite@^5.0.3: version "5.3.5" resolved "https://registry.npmjs.org/vite/-/vite-5.3.5.tgz#b847f846fb2b6cb6f6f4ed50a830186138cb83d8" @@ -10960,31 +11296,6 @@ vitest@2.0.4: vite-node "2.0.4" why-is-node-running "^2.3.0" -vitest@2.0.5: - version "2.0.5" - resolved "https://registry.npmjs.org/vitest/-/vitest-2.0.5.tgz#2f15a532704a7181528e399cc5b754c7f335fd62" - integrity sha512-8GUxONfauuIdeSl5f9GTgVEpg5BTOlplET4WEDaeY2QBiN8wSm68vxN/tb5z405OwppfoCavnwXafiaYBC/xOA== - dependencies: - "@ampproject/remapping" "^2.3.0" - "@vitest/expect" "2.0.5" - "@vitest/pretty-format" "^2.0.5" - "@vitest/runner" "2.0.5" - "@vitest/snapshot" "2.0.5" - "@vitest/spy" "2.0.5" - "@vitest/utils" "2.0.5" - chai "^5.1.1" - debug "^4.3.5" - execa "^8.0.1" - magic-string "^0.30.10" - pathe "^1.1.2" - std-env "^3.7.0" - tinybench "^2.8.0" - tinypool "^1.0.0" - tinyrainbow "^1.2.0" - vite "^5.0.0" - vite-node "2.0.5" - why-is-node-running "^2.3.0" - vitest@^0.20.2: version "0.20.3" resolved "https://registry.npmjs.org/vitest/-/vitest-0.20.3.tgz#24e0744fd1671a9a5d9cf8876281eae65e8d3be6" @@ -11000,6 +11311,37 @@ vitest@^0.20.2: tinyspy "^1.0.0" vite "^2.9.12 || ^3.0.0-0" +vitest@^0.31.0: + version "0.31.4" + resolved "https://registry.npmjs.org/vitest/-/vitest-0.31.4.tgz#5abe02562675262949c10e40811f348a80f6b2a6" + integrity sha512-GoV0VQPmWrUFOZSg3RpQAPN+LPmHg2/gxlMNJlyxJihkz6qReHDV6b0pPDcqFLNEPya4tWJ1pgwUNP9MLmUfvQ== + dependencies: + "@types/chai" "^4.3.5" + "@types/chai-subset" "^1.3.3" + "@types/node" "*" + "@vitest/expect" "0.31.4" + "@vitest/runner" "0.31.4" + "@vitest/snapshot" "0.31.4" + "@vitest/spy" "0.31.4" + "@vitest/utils" "0.31.4" + acorn "^8.8.2" + acorn-walk "^8.2.0" + cac "^6.7.14" + chai "^4.3.7" + concordance "^5.0.4" + debug "^4.3.4" + local-pkg "^0.4.3" + magic-string "^0.30.0" + pathe "^1.1.0" + picocolors "^1.0.0" + std-env "^3.3.2" + strip-literal "^1.0.1" + tinybench "^2.5.0" + tinypool "^0.5.0" + vite "^3.0.0 || ^4.0.0" + vite-node "0.31.4" + why-is-node-running "^2.2.2" + vm-browserify@^1.0.1: version "1.1.2" resolved "https://registry.npmjs.org/vm-browserify/-/vm-browserify-1.1.2.tgz#78641c488b8e6ca91a75f511e7a3b32a86e5dda0" @@ -11159,6 +11501,11 @@ websocket@^1.0.28: utf-8-validate "^5.0.2" yaeti "^0.0.6" +well-known-symbols@^2.0.0: + version "2.0.0" + resolved "https://registry.npmjs.org/well-known-symbols/-/well-known-symbols-2.0.0.tgz#e9c7c07dbd132b7b84212c8174391ec1f9871ba5" + integrity sha512-ZMjC3ho+KXo0BfJb7JgtQ5IBuvnShdlACNkKkdsqBmYw3bPAaJfPeYUo6tLUaT5tG/Gkh7xkpBhKRQ9e7pyg9Q== + whatwg-encoding@^2.0.0: version "2.0.0" resolved "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-2.0.0.tgz#e7635f597fd87020858626805a2729fa7698ac53"