├── .env.example ├── .gitignore ├── .prettierrc ├── README.md ├── package.json ├── src ├── api │ ├── cast.ts │ ├── event.ts │ ├── fid.ts │ ├── hub.ts │ ├── link.ts │ ├── reaction.ts │ ├── signer.ts │ ├── storage.ts │ ├── user-data.ts │ └── verification.ts ├── db │ ├── db.types.ts │ ├── kysely.ts │ ├── migrations │ │ ├── 001_initial_migrations.ts │ │ └── 002_add_convenience_views.ts │ ├── migrator.ts │ └── search-migrations.sql ├── index.ts └── lib │ ├── backfill.ts │ ├── bullmq.ts │ ├── event.ts │ ├── express.ts │ ├── hub-client.ts │ ├── logger.ts │ ├── paginate.ts │ ├── redis.ts │ ├── subscriber.ts │ └── utils.ts ├── tsconfig.json └── yarn.lock /.env.example: -------------------------------------------------------------------------------- 1 | # Farcaster Hub 2 | HUB_RPC="nemes.farcaster.xyz:2283" 3 | HUB_SSL="true" # set to false if using a local hub 4 | 5 | # Database 6 | DATABASE_URL="" # (Postgres connection string) 7 | REDIS_URL="redis://localhost:6379" 8 | 9 | # Indexer Config 10 | WORKER_CONCURRENCY="5" 11 | BACKFILL_MAX_FID="100" 12 | 13 | # Logging 14 | # LOG_LEVEL="trace" # 'fatal' | 'error' | 'warn' | 'info' | 'debug' | 'trace' 15 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .env 2 | .DS_Store 3 | /dist 4 | /ignore 5 | /node_modules 6 | # Supabase 7 | **/supabase/.branches 8 | **/supabase/.temp 9 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "semi": false, 3 | "tabWidth": 2, 4 | "printWidth": 80, 5 | "useTabs": false, 6 | "singleQuote": true, 7 | "trailingComma": "es5", 8 | "plugins": ["@trivago/prettier-plugin-sort-imports"], 9 | "importOrder": ["", "^@/", "^[./]"], 10 | "importOrderSeparation": true, 11 | "importOrderSortSpecifiers": true 12 | } 13 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Farcaster Indexer 2 | 3 | This is an indexer that listens for messages from a [Farcaster Hub](https://docs.farcaster.xyz/learn/architecture/hubs) and inserts relevant data into a postgres database. 4 | 5 | The most performant way to run this is to co-locate everything (hub, node app, postgres, redis) on the same machine. I recommend [Latitude](https://www.latitude.sh/r/673C7DB2) (referral code for $200 of free credits). 6 | 7 | ## How to run 8 | 9 | Clone this repo 10 | 11 | ```bash 12 | git clone -b hubs https://github.com/gskril/farcaster-indexer.git 13 | ``` 14 | 15 | Install dependencies 16 | 17 | ```bash 18 | yarn install 19 | ``` 20 | 21 | Create a `.env` file with your hub, database, and redis connection details 22 | 23 | ```bash 24 | cp .env.example .env 25 | ``` 26 | 27 | Run the latest database migrations 28 | 29 | ```bash 30 | yarn kysely:migrate 31 | ``` 32 | 33 | Run the indexer 34 | 35 | ```bash 36 | # Recommended to get the full state. You only need to run this once. 37 | # Streaming will start after the backfill is complete. 38 | yarn run backfill 39 | 40 | # Ignores backfill and start streaming from the latest recorded event. 41 | # You should run this after one initial backfill. 42 | yarn start 43 | ``` 44 | 45 | ## How it works 46 | 47 | - Backfill and streaming are separate processes. 48 | - Every operation is run through [BullMQ](https://bullmq.io/) for better concurrency and error handling. 49 | - For backfill, the indexer adds all FIDs (in batches of 100) to a queue and processes them in parallel. The `WORKER_CONCURRENCY` environment variable controls how many workers are spawned. 50 | - Once backfill is complete, the indexer subscribes to a hub's event stream and processes messages as they arrive. BullMQ is used as middleware to ensure that hub events are getting handled fast enough, otherwise the stream will disconnect. 51 | 52 | ## Extras 53 | 54 | If you want to add search functionality, you can manually apply the SQL migration at [src/db/search-migrations.sql](./src/db/search-migrations.sql) 55 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "farcaster-indexer", 3 | "author": "Greg Skriloff", 4 | "license": "ISC", 5 | "type": "module", 6 | "scripts": { 7 | "build": "tsc", 8 | "clean": "rm -rf ./dist", 9 | "start": "tsc && node ./dist/index.js", 10 | "dev": "tsc && node ./dist/index.js", 11 | "backfill": "tsc && node ./dist/index.js --backfill", 12 | "kysely:migrate": "tsc && node ./dist/db/migrator.js" 13 | }, 14 | "dependencies": { 15 | "@bull-board/express": "^5.16.0", 16 | "@farcaster/hub-nodejs": "^0.11.0", 17 | "bullmq": "^5.7.6", 18 | "cli-progress": "^3.12.0", 19 | "dotenv": "^16.4.4", 20 | "express": "^4.19.2", 21 | "ioredis": "^5.4.1", 22 | "kysely": "^0.27.2", 23 | "pg": "^8.11.3", 24 | "pg-pool": "^3.6.1", 25 | "pino": "^8.19.0", 26 | "pino-pretty": "^10.3.1" 27 | }, 28 | "devDependencies": { 29 | "@trivago/prettier-plugin-sort-imports": "^4.3.0", 30 | "@types/cli-progress": "^3.11.5", 31 | "@types/express": "^4.17.21", 32 | "@types/node": "^20.11.19", 33 | "@types/pg-pool": "^2.0.6", 34 | "prettier": "^3.2.5", 35 | "typescript": "^5.3.3" 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/api/cast.ts: -------------------------------------------------------------------------------- 1 | import { Message } from '@farcaster/hub-nodejs' 2 | 3 | import { db } from '../db/kysely.js' 4 | import { log } from '../lib/logger.js' 5 | import { 6 | breakIntoChunks, 7 | farcasterTimeToDate, 8 | formatCasts, 9 | } from '../lib/utils.js' 10 | 11 | /** 12 | * Insert casts in the database 13 | * @param msgs Raw hub messages 14 | */ 15 | export async function insertCasts(msgs: Message[]) { 16 | const casts = formatCasts(msgs) 17 | if (casts.length === 0) return 18 | const chunks = breakIntoChunks(casts, 1000) 19 | 20 | for (const chunk of chunks) { 21 | try { 22 | await db 23 | .insertInto('casts') 24 | .values(chunk) 25 | .onConflict((oc) => oc.column('hash').doNothing()) 26 | .execute() 27 | 28 | log.debug(`CASTS INSERTED`) 29 | } catch (error) { 30 | log.error(error, 'ERROR INSERTING CAST') 31 | throw error 32 | } 33 | } 34 | } 35 | /** 36 | * Soft delete casts in the database 37 | * @param msgs Raw hub messages 38 | */ 39 | export async function deleteCasts(msgs: Message[]) { 40 | try { 41 | await db.transaction().execute(async (trx) => { 42 | for (const msg of msgs) { 43 | const data = msg.data! 44 | 45 | await trx 46 | .updateTable('casts') 47 | .set({ 48 | deletedAt: farcasterTimeToDate(data.timestamp), 49 | }) 50 | .where('hash', '=', data.castRemoveBody?.targetHash!) 51 | .execute() 52 | } 53 | }) 54 | 55 | log.debug(`CASTS DELETED`) 56 | } catch (error) { 57 | log.error(error, 'ERROR DELETING CAST') 58 | throw error 59 | } 60 | } 61 | 62 | /** 63 | * Soft prune casts in the database 64 | * @param msgs Raw hub messages 65 | */ 66 | export async function pruneCasts(msgs: Message[]) { 67 | try { 68 | await db.transaction().execute(async (trx) => { 69 | for (const msg of msgs) { 70 | const data = msg.data! 71 | 72 | await trx 73 | .updateTable('casts') 74 | .set({ 75 | prunedAt: farcasterTimeToDate(data.timestamp), 76 | }) 77 | .where('fid', '=', data.fid) 78 | .where('text', '=', data.castAddBody!.text) 79 | .execute() 80 | } 81 | }) 82 | 83 | log.debug(`CASTS PRUNED`) 84 | } catch (error) { 85 | log.error(error, 'ERROR PRUNING CAST') 86 | throw error 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /src/api/event.ts: -------------------------------------------------------------------------------- 1 | import { redis } from '../lib/redis.js' 2 | 3 | const redisKey = 'hub:latest-event-id' 4 | 5 | /** 6 | * Insert an event ID in the database 7 | * @param eventId Hub event ID 8 | */ 9 | export async function saveLatestEventId(eventId: number) { 10 | await redis.set(redisKey, eventId) 11 | } 12 | 13 | /** 14 | * Get the latest event ID from the database 15 | * @returns Latest event ID 16 | */ 17 | export async function getLatestEvent(): Promise { 18 | const res = await redis.get(redisKey) 19 | return res ? parseInt(res) : undefined 20 | } 21 | -------------------------------------------------------------------------------- /src/api/fid.ts: -------------------------------------------------------------------------------- 1 | import { 2 | IdRegisterEventType, 3 | OnChainEvent, 4 | OnChainEventType, 5 | isIdRegisterOnChainEvent, 6 | } from '@farcaster/hub-nodejs' 7 | 8 | import { db } from '../db/kysely.js' 9 | import { hubClient } from '../lib/hub-client.js' 10 | import { getOnChainEventsByFidInBatchesOf } from '../lib/paginate.js' 11 | import { MAX_PAGE_SIZE, NULL_ETH_ADDRESS } from '../lib/utils.js' 12 | 13 | export async function getAllRegistrationsByFid(fid: number) { 14 | let registrationEvents: OnChainEvent[] = [] 15 | 16 | for await (const events of getOnChainEventsByFidInBatchesOf(hubClient, { 17 | fid, 18 | pageSize: MAX_PAGE_SIZE, 19 | eventTypes: [OnChainEventType.EVENT_TYPE_ID_REGISTER], 20 | })) { 21 | registrationEvents = registrationEvents.concat(...events) 22 | } 23 | 24 | // Since there could be many events, ensure we process them in sorted order 25 | const sortedEventsForFid = registrationEvents.sort((a, b) => 26 | a.blockNumber === b.blockNumber 27 | ? a.logIndex - b.logIndex 28 | : a.blockNumber - b.blockNumber 29 | ) 30 | 31 | return sortedEventsForFid 32 | } 33 | 34 | export async function insertRegistrations(registrationEvents: OnChainEvent[]) { 35 | for (const registration of registrationEvents) { 36 | if (!isIdRegisterOnChainEvent(registration)) 37 | throw new Error(`Invalid Registration Event: ${registration}`) 38 | 39 | const body = registration.idRegisterEventBody 40 | const custodyAddress = body.to.length ? body.to : NULL_ETH_ADDRESS 41 | const recoveryAddress = body.recoveryAddress.length 42 | ? body.recoveryAddress 43 | : NULL_ETH_ADDRESS 44 | 45 | switch (body.eventType) { 46 | case IdRegisterEventType.REGISTER: { 47 | await db 48 | .insertInto('fids') 49 | .values({ 50 | fid: registration.fid, 51 | registeredAt: new Date(registration.blockTimestamp * 1000), 52 | custodyAddress, 53 | recoveryAddress, 54 | }) 55 | .onConflict((oc) => 56 | oc.column('fid').doUpdateSet(({ ref }) => ({ 57 | registeredAt: ref('excluded.registeredAt'), 58 | custodyAddress: ref('excluded.custodyAddress'), 59 | recoveryAddress: ref('excluded.recoveryAddress'), 60 | updatedAt: new Date(), 61 | })) 62 | ) 63 | .execute() 64 | break 65 | } 66 | case IdRegisterEventType.TRANSFER: { 67 | await db 68 | .updateTable('fids') 69 | .where('fid', '=', registration.fid) 70 | .set({ 71 | custodyAddress, 72 | updatedAt: new Date(), 73 | }) 74 | .execute() 75 | break 76 | } 77 | case IdRegisterEventType.CHANGE_RECOVERY: { 78 | await db 79 | .updateTable('fids') 80 | .where('fid', '=', registration.fid) 81 | .set({ 82 | recoveryAddress, 83 | updatedAt: new Date(), 84 | }) 85 | .execute() 86 | break 87 | } 88 | case IdRegisterEventType.NONE: 89 | throw new Error(`Invalid IdRegisterEventType: ${body.eventType}`) 90 | default: 91 | // If we're getting a type error on the line below, it means we've missed a case above. 92 | // Did we add a new event type? 93 | throw new Error(`Invalid IdRegisterEventType: ${body.eventType}`) 94 | } 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /src/api/hub.ts: -------------------------------------------------------------------------------- 1 | import { ContactInfoContentBody } from '@farcaster/hub-nodejs' 2 | 3 | import { db } from '../db/kysely.js' 4 | import { log } from '../lib/logger.js' 5 | import { breakIntoChunks, formatHubs } from '../lib/utils.js' 6 | 7 | /** 8 | * Insert hubs in the database 9 | * @param msg List of connected peers 10 | */ 11 | export async function insertHubs(contacts: ContactInfoContentBody[]) { 12 | const hubs = formatHubs(contacts) 13 | if (hubs.length === 0) return 14 | const chunks = breakIntoChunks(hubs, 1000) 15 | 16 | for (const chunk of chunks) { 17 | try { 18 | await db 19 | .insertInto('hubs') 20 | .values(chunk) 21 | .onConflict((oc) => oc.column('id').doNothing()) 22 | .execute() 23 | 24 | log.debug(`HUBS INSERTED`) 25 | } catch (error) { 26 | log.error(error, 'ERROR INSERTING HUBS') 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/api/link.ts: -------------------------------------------------------------------------------- 1 | import { Message } from '@farcaster/hub-nodejs' 2 | 3 | import { db } from '../db/kysely.js' 4 | import { log } from '../lib/logger.js' 5 | import { 6 | breakIntoChunks, 7 | farcasterTimeToDate, 8 | formatLinks, 9 | } from '../lib/utils.js' 10 | 11 | export async function insertLinks(msgs: Message[]) { 12 | const links = formatLinks(msgs) 13 | if (links.length === 0) return 14 | const chunks = breakIntoChunks(links, 1000) 15 | 16 | for (const chunk of chunks) { 17 | try { 18 | await db 19 | .insertInto('links') 20 | .values(chunk) 21 | .onConflict((oc) => oc.column('hash').doNothing()) 22 | .execute() 23 | 24 | log.debug(`LINKS INSERTED`) 25 | } catch (error) { 26 | log.error(error, 'ERROR INSERTING LINKS') 27 | throw error 28 | } 29 | } 30 | } 31 | 32 | export async function deleteLinks(msgs: Message[]) { 33 | try { 34 | await db.transaction().execute(async (trx) => { 35 | for (const msg of msgs) { 36 | const data = msg.data! 37 | 38 | await trx 39 | .updateTable('links') 40 | .set({ 41 | deletedAt: farcasterTimeToDate(data.timestamp), 42 | }) 43 | .where('fid', '=', data.fid) 44 | .where('targetFid', '=', data.linkBody!.targetFid!) 45 | .execute() 46 | } 47 | }) 48 | 49 | log.debug(`LINKS DELETED`) 50 | } catch (error) { 51 | log.error(error, 'ERROR DELETING LINKS') 52 | throw error 53 | } 54 | } 55 | 56 | export async function pruneLinks(msgs: Message[]) { 57 | try { 58 | await db.transaction().execute(async (trx) => { 59 | for (const msg of msgs) { 60 | const data = msg.data! 61 | 62 | await trx 63 | .updateTable('links') 64 | .set({ 65 | prunedAt: farcasterTimeToDate(data.timestamp), 66 | }) 67 | .where('fid', '=', data.fid) 68 | .where('targetFid', '=', data.linkBody!.targetFid!) 69 | .execute() 70 | } 71 | }) 72 | 73 | log.debug(`LINKS PRUNED`) 74 | } catch (error) { 75 | log.error(error, 'ERROR PRUNING LINKS') 76 | throw error 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/api/reaction.ts: -------------------------------------------------------------------------------- 1 | import { Message } from '@farcaster/hub-nodejs' 2 | 3 | import { db } from '../db/kysely.js' 4 | import { log } from '../lib/logger.js' 5 | import { 6 | breakIntoChunks, 7 | farcasterTimeToDate, 8 | formatReactions, 9 | } from '../lib/utils.js' 10 | 11 | /** 12 | * Insert a reaction in the database 13 | * @param msg Hub event in JSON format 14 | */ 15 | export async function insertReactions(msgs: Message[]) { 16 | const reactions = formatReactions(msgs) 17 | if (reactions.length === 0) return 18 | const chunks = breakIntoChunks(reactions, 1000) 19 | 20 | for (const chunk of chunks) { 21 | try { 22 | await db 23 | .insertInto('reactions') 24 | .values(chunk) 25 | .onConflict((oc) => oc.column('hash').doNothing()) 26 | .execute() 27 | 28 | log.debug(`REACTIONS INSERTED`) 29 | } catch (error) { 30 | log.error(error, 'ERROR INSERTING REACTIONS') 31 | throw error 32 | } 33 | } 34 | } 35 | 36 | export async function deleteReactions(msgs: Message[]) { 37 | try { 38 | await db.transaction().execute(async (trx) => { 39 | for (const msg of msgs) { 40 | const data = msg.data! 41 | const reaction = data.reactionBody! 42 | 43 | if (reaction.targetCastId) { 44 | await trx 45 | .updateTable('reactions') 46 | .set({ deletedAt: farcasterTimeToDate(data.timestamp) }) 47 | .where('fid', '=', data.fid) 48 | .where('type', '=', reaction.type) 49 | .where('targetCastHash', '=', reaction.targetCastId.hash) 50 | .execute() 51 | } else if (reaction.targetUrl) { 52 | await trx 53 | .updateTable('reactions') 54 | .set({ deletedAt: farcasterTimeToDate(data.timestamp) }) 55 | .where('fid', '=', data.fid) 56 | .where('type', '=', reaction.type) 57 | .where('targetUrl', '=', reaction.targetUrl) 58 | .execute() 59 | } 60 | } 61 | }) 62 | 63 | log.debug(`REACTIONS DELETED`) 64 | } catch (error) { 65 | log.error(error, 'ERROR DELETING REACTIONS') 66 | throw error 67 | } 68 | } 69 | 70 | export async function pruneReactions(msgs: Message[]) { 71 | try { 72 | await db.transaction().execute(async (trx) => { 73 | for (const msg of msgs) { 74 | const data = msg.data! 75 | const reaction = data.reactionBody! 76 | 77 | if (reaction.targetCastId) { 78 | await trx 79 | .updateTable('reactions') 80 | .set({ prunedAt: farcasterTimeToDate(data.timestamp) }) 81 | .where('fid', '=', data.fid) 82 | .where('type', '=', reaction.type) 83 | .where('targetCastHash', '=', reaction.targetCastId.hash) 84 | .execute() 85 | } else if (reaction.targetUrl) { 86 | await trx 87 | .updateTable('reactions') 88 | .set({ prunedAt: farcasterTimeToDate(data.timestamp) }) 89 | .where('fid', '=', data.fid) 90 | .where('type', '=', reaction.type) 91 | .where('targetUrl', '=', reaction.targetUrl) 92 | .execute() 93 | } 94 | } 95 | }) 96 | 97 | log.debug(`REACTIONS PRUNED`) 98 | } catch (error) { 99 | log.error(error, 'ERROR PRUNING REACTIONS') 100 | throw error 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /src/api/signer.ts: -------------------------------------------------------------------------------- 1 | import { 2 | OnChainEvent, 3 | OnChainEventType, 4 | SignerEventType, 5 | isSignerOnChainEvent, 6 | } from '@farcaster/hub-nodejs' 7 | import { bytesToHex, decodeAbiParameters } from 'viem' 8 | 9 | import { db } from '../db/kysely.js' 10 | import { hubClient } from '../lib/hub-client.js' 11 | import { getOnChainEventsByFidInBatchesOf } from '../lib/paginate.js' 12 | import { MAX_PAGE_SIZE } from '../lib/utils.js' 13 | 14 | const signedKeyRequestAbi = [ 15 | { 16 | components: [ 17 | { 18 | name: 'requestFid', 19 | type: 'uint256', 20 | }, 21 | { 22 | name: 'requestSigner', 23 | type: 'address', 24 | }, 25 | { 26 | name: 'signature', 27 | type: 'bytes', 28 | }, 29 | { 30 | name: 'deadline', 31 | type: 'uint256', 32 | }, 33 | ], 34 | name: 'SignedKeyRequest', 35 | type: 'tuple', 36 | }, 37 | ] as const 38 | 39 | export function decodeSignedKeyRequestMetadata(metadata: Uint8Array) { 40 | return decodeAbiParameters(signedKeyRequestAbi, bytesToHex(metadata))[0] 41 | } 42 | 43 | export async function getAllSignersByFid(fid: number) { 44 | let signerEvents: OnChainEvent[] = [] 45 | 46 | for await (const events of getOnChainEventsByFidInBatchesOf(hubClient, { 47 | fid, 48 | pageSize: MAX_PAGE_SIZE, 49 | eventTypes: [OnChainEventType.EVENT_TYPE_SIGNER], 50 | })) { 51 | signerEvents = signerEvents.concat(...events) 52 | } 53 | 54 | // Since there could be many events, ensure we process them in sorted order 55 | const sortedEventsForFid = signerEvents.sort((a, b) => 56 | a.blockNumber === b.blockNumber 57 | ? a.logIndex - b.logIndex 58 | : a.blockNumber - b.blockNumber 59 | ) 60 | 61 | return sortedEventsForFid 62 | } 63 | 64 | export async function insertSigners(signers: OnChainEvent[]) { 65 | for (const signer of signers) { 66 | if (!isSignerOnChainEvent(signer)) 67 | throw new Error(`Invalid SignerOnChainEvent: ${signer}`) 68 | 69 | const body = signer.signerEventBody 70 | const timestamp = new Date(signer.blockTimestamp * 1000) 71 | 72 | switch (body.eventType) { 73 | case SignerEventType.ADD: { 74 | const signedKeyRequestMetadata = decodeSignedKeyRequestMetadata( 75 | body.metadata 76 | ) 77 | const metadataJson = { 78 | requestFid: Number(signedKeyRequestMetadata.requestFid), 79 | requestSigner: signedKeyRequestMetadata.requestSigner, 80 | signature: signedKeyRequestMetadata.signature, 81 | deadline: Number(signedKeyRequestMetadata.deadline), 82 | } 83 | 84 | await db 85 | .insertInto('signers') 86 | .values({ 87 | addedAt: timestamp, 88 | fid: signer.fid, 89 | requesterFid: metadataJson.requestFid, 90 | key: body.key, 91 | keyType: body.keyType, 92 | metadata: JSON.stringify(metadataJson), 93 | metadataType: body.metadataType, 94 | }) 95 | .onConflict((oc) => 96 | oc.columns(['fid', 'key']).doUpdateSet(({ ref }) => ({ 97 | // Update all other fields in case this was a replayed transaction from a block re-org 98 | addedAt: ref('excluded.addedAt'), 99 | requesterFid: ref('excluded.requesterFid'), 100 | keyType: ref('excluded.keyType'), 101 | metadata: JSON.stringify(metadataJson), 102 | metadataType: ref('excluded.metadataType'), 103 | updatedAt: new Date(), 104 | })) 105 | ) 106 | .execute() 107 | 108 | break 109 | } 110 | case SignerEventType.REMOVE: { 111 | // Smart contract ensures there will always be an add before a remove, so we know we can update in-place 112 | db.updateTable('signers') 113 | .set({ 114 | removedAt: timestamp, 115 | updatedAt: new Date(), 116 | }) 117 | .where('fid', '=', signer.fid) 118 | .where('key', '=', body.key) 119 | .execute() 120 | 121 | break 122 | } 123 | } 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /src/api/storage.ts: -------------------------------------------------------------------------------- 1 | import { 2 | OnChainEvent, 3 | OnChainEventType, 4 | isStorageRentOnChainEvent, 5 | } from '@farcaster/hub-nodejs' 6 | 7 | import { db } from '../db/kysely.js' 8 | import { hubClient } from '../lib/hub-client.js' 9 | import { getOnChainEventsByFidInBatchesOf } from '../lib/paginate.js' 10 | import { MAX_PAGE_SIZE, farcasterTimeToDate } from '../lib/utils.js' 11 | 12 | export async function getAllStorageByFid(fid: number) { 13 | let storageEvents: OnChainEvent[] = [] 14 | 15 | for await (const events of getOnChainEventsByFidInBatchesOf(hubClient, { 16 | fid, 17 | pageSize: MAX_PAGE_SIZE, 18 | eventTypes: [OnChainEventType.EVENT_TYPE_STORAGE_RENT], 19 | })) { 20 | storageEvents = storageEvents.concat(...events) 21 | } 22 | 23 | // Since there could be many events, ensure we process them in sorted order 24 | const sortedEventsForFid = storageEvents.sort((a, b) => 25 | a.blockNumber === b.blockNumber 26 | ? a.logIndex - b.logIndex 27 | : a.blockNumber - b.blockNumber 28 | ) 29 | 30 | return sortedEventsForFid 31 | } 32 | 33 | export async function insertStorage(storageEvents: OnChainEvent[]) { 34 | for (const storage of storageEvents) { 35 | if (!isStorageRentOnChainEvent(storage)) 36 | throw new Error(`Invalid SignerOnChainEvent: ${storage}`) 37 | 38 | const body = storage.storageRentEventBody 39 | const timestamp = new Date(storage.blockTimestamp * 1000) 40 | 41 | await db 42 | .insertInto('storage') 43 | .values({ 44 | fid: storage.fid, 45 | units: body.units, 46 | payer: body.payer, 47 | rentedAt: timestamp, 48 | expiresAt: farcasterTimeToDate(body.expiry), 49 | }) 50 | .onConflict((oc) => 51 | oc.columns(['fid', 'expiresAt']).doUpdateSet(({ ref }) => ({ 52 | units: ref('excluded.units'), 53 | payer: ref('excluded.payer'), 54 | expiresAt: ref('excluded.expiresAt'), 55 | rentedAt: ref('excluded.rentedAt'), 56 | updatedAt: new Date(), 57 | })) 58 | ) 59 | .execute() 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/api/user-data.ts: -------------------------------------------------------------------------------- 1 | import { Message } from '@farcaster/hub-nodejs' 2 | 3 | import { db } from '../db/kysely.js' 4 | import { log } from '../lib/logger.js' 5 | import { formatUserDatas } from '../lib/utils.js' 6 | 7 | export async function insertUserDatas(msgs: Message[]) { 8 | const userDatas = formatUserDatas(msgs) 9 | if (userDatas.length === 0) return 10 | 11 | try { 12 | await db 13 | .insertInto('userData') 14 | .values(userDatas) 15 | .onConflict((oc) => 16 | oc.columns(['fid', 'type']).doUpdateSet((eb) => ({ 17 | hash: eb.ref('excluded.hash'), 18 | value: eb.ref('excluded.value'), 19 | })) 20 | ) 21 | .execute() 22 | 23 | log.debug(`USER DATA INSERTED`) 24 | } catch (error) { 25 | log.error(error, 'ERROR INSERTING USER DATA') 26 | throw error 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/api/verification.ts: -------------------------------------------------------------------------------- 1 | import { Message } from '@farcaster/hub-nodejs' 2 | 3 | import { db } from '../db/kysely.js' 4 | import { log } from '../lib/logger.js' 5 | import { farcasterTimeToDate, formatVerifications } from '../lib/utils.js' 6 | 7 | /** 8 | * Insert a new verification in the database 9 | * @param msg Hub event in JSON format 10 | */ 11 | export async function insertVerifications(msgs: Message[]) { 12 | const verifications = formatVerifications(msgs) 13 | if (verifications.length === 0) return 14 | 15 | try { 16 | await db 17 | .insertInto('verifications') 18 | .values(verifications) 19 | .onConflict((oc) => oc.columns(['fid', 'signerAddress']).doNothing()) 20 | .execute() 21 | 22 | log.debug(`VERIFICATIONS INSERTED`) 23 | } catch (error) { 24 | log.error(error, 'ERROR INSERTING VERIFICATIONS') 25 | throw error 26 | } 27 | } 28 | 29 | /** 30 | * Delete a verification from the database 31 | * @param msg Hub event in JSON format 32 | */ 33 | export async function deleteVerifications(msgs: Message[]) { 34 | try { 35 | await db.transaction().execute(async (trx) => { 36 | for (const msg of msgs) { 37 | const data = msg.data! 38 | const address = data.verificationRemoveBody!.address 39 | 40 | await trx 41 | .updateTable('verifications') 42 | .set({ deletedAt: farcasterTimeToDate(data.timestamp) }) 43 | .where('signerAddress', '=', address) 44 | .where('fid', '=', data.fid) 45 | .execute() 46 | } 47 | }) 48 | 49 | log.debug('VERIFICATIONS DELETED') 50 | } catch (error) { 51 | log.error(error, 'ERROR DELETING VERIFICATIONS') 52 | throw error 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/db/db.types.ts: -------------------------------------------------------------------------------- 1 | import { ReactionType, UserDataType } from '@farcaster/hub-nodejs' 2 | import { ColumnType, Generated, GeneratedAlways } from 'kysely' 3 | 4 | type Fid = number 5 | type Hex = `0x${string}` 6 | 7 | type CastIdJson = { 8 | fid: Fid 9 | hash: Hex 10 | } 11 | 12 | // CASTS ------------------------------------------------------------------------------------------- 13 | declare const $castDbId: unique symbol 14 | type CastDbId = string & { [$castDbId]: true } 15 | 16 | type CastEmbedJson = { url: string } | { castId: CastIdJson } 17 | 18 | type CastRow = { 19 | id: GeneratedAlways 20 | createdAt: Generated 21 | updatedAt: Generated 22 | timestamp: Date 23 | deletedAt: Date | null 24 | prunedAt: Date | null 25 | fid: Fid 26 | parentFid: Fid | null 27 | hash: Uint8Array 28 | rootParentHash: Uint8Array | null 29 | parentHash: Uint8Array | null 30 | rootParentUrl: string | null 31 | parentUrl: string | null 32 | text: string 33 | embeds: ColumnType 34 | mentions: ColumnType 35 | mentionsPositions: ColumnType 36 | } 37 | 38 | // REACTIONS --------------------------------------------------------------------------------------- 39 | declare const $reactionDbId: unique symbol 40 | type ReactionDbId = string & { [$reactionDbId]: true } 41 | 42 | type ReactionRow = { 43 | id: GeneratedAlways 44 | createdAt: Generated 45 | updatedAt: Generated 46 | timestamp: Date 47 | deletedAt: Date | null 48 | prunedAt: Date | null 49 | fid: Fid 50 | targetCastFid: Fid | null 51 | type: ReactionType 52 | hash: Uint8Array 53 | targetCastHash: Uint8Array | null 54 | targetUrl: string | null 55 | } 56 | 57 | // LINKS ------------------------------------------------------------------------------------------- 58 | declare const $linkDbId: unique symbol 59 | type LinkDbId = string & { [$linkDbId]: true } 60 | 61 | type LinkRow = { 62 | id: GeneratedAlways 63 | createdAt: Generated 64 | updatedAt: Generated 65 | timestamp: Date 66 | deletedAt: Date | null 67 | prunedAt: Date | null 68 | fid: Fid 69 | targetFid: Fid | null 70 | displayTimestamp: Date | null 71 | type: string 72 | hash: Uint8Array 73 | } 74 | 75 | // VERIFICATIONS ----------------------------------------------------------------------------------- 76 | declare const $verificationDbId: unique symbol 77 | type VerificationDbId = string & { [$verificationDbId]: true } 78 | 79 | type VerificationRow = { 80 | id: GeneratedAlways 81 | createdAt: Generated 82 | updatedAt: Generated 83 | timestamp: Date 84 | deletedAt: Date | null 85 | fid: Fid 86 | hash: Uint8Array 87 | signerAddress: Uint8Array 88 | blockHash: Uint8Array 89 | signature: Uint8Array 90 | } 91 | 92 | // USER DATA --------------------------------------------------------------------------------------- 93 | declare const $userDataDbId: unique symbol 94 | type UserDataDbId = string & { [$userDataDbId]: true } 95 | 96 | type UserDataRow = { 97 | id: GeneratedAlways 98 | createdAt: Generated 99 | updatedAt: Generated 100 | timestamp: Date 101 | deletedAt: Date | null 102 | fid: Fid 103 | type: UserDataType 104 | hash: Uint8Array 105 | value: string 106 | } 107 | 108 | // FIDS ------------------------------------------------------------------------------------------- 109 | type FidRow = { 110 | fid: Fid 111 | createdAt: Generated 112 | updatedAt: Generated 113 | registeredAt: Date 114 | custodyAddress: Uint8Array 115 | recoveryAddress: Uint8Array 116 | } 117 | 118 | // SIGNERS ----------------------------------------------------------------------------------------- 119 | declare const $signerDbId: unique symbol 120 | type SignerDbId = string & { [$signerDbId]: true } 121 | 122 | type SignerAddMetadataJson = { 123 | requestFid: number 124 | requestSigner: Hex 125 | signature: Hex 126 | deadline: number 127 | } 128 | 129 | type SignerRow = { 130 | id: GeneratedAlways 131 | createdAt: Generated 132 | updatedAt: Generated 133 | addedAt: Date 134 | removedAt: Date | null 135 | fid: Fid 136 | requesterFid: Fid 137 | key: Uint8Array 138 | keyType: number 139 | metadata: ColumnType 140 | metadataType: number 141 | } 142 | 143 | // STORAGE ALLOCATIONS ----------------------------------------------------------------------------- 144 | declare const $storageDbId: unique symbol 145 | type StorageDbId = string & { [$storageDbId]: true } 146 | 147 | type StorageRow = { 148 | id: GeneratedAlways 149 | createdAt: Generated 150 | updatedAt: Generated 151 | rentedAt: Date 152 | expiresAt: Date 153 | fid: Fid 154 | units: number 155 | payer: Uint8Array 156 | } 157 | 158 | // Hubs ------------------------------------------------------------------------------------------ 159 | declare const $hubsDbId: unique symbol 160 | type HubsDbId = string & { [$hubsDbId]: true } 161 | 162 | // TODO: store `gossipAddress` and `rpcAddress` as jsonb 163 | type HubRow = { 164 | id: GeneratedAlways 165 | createdAt: Generated 166 | updatedAt: Generated 167 | gossipAddress: string 168 | rpcAddress: string 169 | excludedHashes: string[] 170 | count: number 171 | hubVersion: string 172 | network: string 173 | appVersion: string 174 | timestamp: number 175 | } 176 | 177 | // ALL TABLES -------------------------------------------------------------------------------------- 178 | export interface Tables { 179 | casts: CastRow 180 | reactions: ReactionRow 181 | links: LinkRow 182 | verifications: VerificationRow 183 | userData: UserDataRow 184 | fids: FidRow 185 | signers: SignerRow 186 | storage: StorageRow 187 | hubs: HubRow 188 | } 189 | -------------------------------------------------------------------------------- /src/db/kysely.ts: -------------------------------------------------------------------------------- 1 | import { CamelCasePlugin, Kysely, PostgresDialect } from 'kysely' 2 | import Pool from 'pg-pool' 3 | 4 | import { Tables } from './db.types' 5 | 6 | if (!process.env.DATABASE_URL) { 7 | throw new Error('DATABASE_URL is not set') 8 | } 9 | 10 | export const db = new Kysely({ 11 | dialect: new PostgresDialect({ 12 | pool: new Pool({ 13 | max: 10, 14 | connectionString: process.env.DATABASE_URL, 15 | }), 16 | }), 17 | plugins: [new CamelCasePlugin()], 18 | }) 19 | -------------------------------------------------------------------------------- /src/db/migrations/001_initial_migrations.ts: -------------------------------------------------------------------------------- 1 | import { Kysely, sql } from 'kysely' 2 | 3 | /************************************************************************************************** 4 | Notes about the patterns in this file: 5 | 6 | * Uses ULIDs as the surrogate key for most tables so that we don't rely on sequences, allowing 7 | tables to be partitioned in the future if needed. ULIDs still have temporal ordering unlike 8 | most UUIDs. 9 | 10 | * Uses created_at/updated_at columns to refer to database row create/update time, NOT 11 | the creation time of the entity on the Farcaster network itself. 12 | Separate columns (e.g. "timestamp") represent when the content was created on Farcaster. 13 | 14 | * Declares columns in a particular order to minimize storage on disk. If the declaration order 15 | looks odd, remember it's to reduce disk space. 16 | See https://www.2ndquadrant.com/en/blog/on-rocks-and-sand/ for more info. 17 | 18 | * Uses bytea columns to store raw bytes instead of text columns with `0x` prefixed strings, since 19 | raw bytes reduce storage space, reduce index size, are faster to query (especially with joins), 20 | and avoid case sensitivity issues when dealing with string comparison. 21 | 22 | * Uses B-tree indexes (the default) for most columns representing a hash digest, since you can 23 | perform lookups on those hashes matching by prefix, whereas you can't do this with hash indexes. 24 | 25 | * Declares some indexes that we think might be useful for data analysis and general querying, 26 | but which aren't actually required by the replicator itself. 27 | 28 | * Declares partial indexes (via a WHERE predicate) to reduce the size of the index and ensure 29 | only relevant rows are returned (e.g. ignoring soft-deleted rows, etc.) 30 | 31 | * Uses JSON columns instead of native Postgres array columns to significantly reduce on-disk 32 | storage (JSON is treated like TEXT) at the cost of slightly slower querying time. JSON columns 33 | can also be more easily modified over time without requiring a schema migration. 34 | **************************************************************************************************/ 35 | 36 | export const up = async (db: Kysely) => { 37 | // Used for generating random bytes in ULID creation 38 | await sql`CREATE EXTENSION IF NOT EXISTS pgcrypto`.execute(db) 39 | 40 | // ULID generation function for creating unique IDs without centralized coordination. 41 | // Avoids limitations of a monotonic (auto-incrementing) ID. 42 | await sql`CREATE OR REPLACE FUNCTION generate_ulid() RETURNS uuid 43 | LANGUAGE sql STRICT PARALLEL SAFE 44 | RETURN ((lpad(to_hex((floor((EXTRACT(epoch FROM clock_timestamp()) * (1000)::numeric)))::bigint), 12, '0'::text) || encode(public.gen_random_bytes(10), 'hex'::text)))::uuid; 45 | `.execute(db) 46 | 47 | // CASTS 48 | await db.schema 49 | .createTable('casts') 50 | .ifNotExists() 51 | .addColumn('id', 'uuid', (col) => 52 | col.defaultTo(sql`generate_ulid()`).primaryKey() 53 | ) 54 | .addColumn('createdAt', 'timestamptz', (col) => 55 | col.notNull().defaultTo(sql`current_timestamp`) 56 | ) 57 | .addColumn('updatedAt', 'timestamptz', (col) => 58 | col.notNull().defaultTo(sql`current_timestamp`) 59 | ) 60 | .addColumn('timestamp', 'timestamptz', (col) => col.notNull()) 61 | .addColumn('deletedAt', 'timestamptz') 62 | .addColumn('prunedAt', 'timestamptz') 63 | .addColumn('fid', 'bigint', (col) => col.notNull()) 64 | .addColumn('parentFid', 'bigint') 65 | .addColumn('hash', 'bytea', (col) => col.notNull().unique()) 66 | .addColumn('rootParentHash', 'bytea') 67 | .addColumn('parentHash', 'bytea') 68 | .addColumn('rootParentUrl', 'text') 69 | .addColumn('parentUrl', 'text') 70 | .addColumn('text', 'text', (col) => col.notNull()) 71 | .addColumn('embeds', 'json', (col) => col.notNull().defaultTo(sql`'[]'`)) 72 | .addColumn('mentions', 'json', (col) => col.notNull().defaultTo(sql`'[]'`)) 73 | .addColumn('mentionsPositions', 'json', (col) => 74 | col.notNull().defaultTo(sql`'[]'`) 75 | ) 76 | .execute() 77 | 78 | await db.schema 79 | .createIndex('casts_active_fid_timestamp_index') 80 | .ifNotExists() 81 | .on('casts') 82 | .columns(['fid', 'timestamp']) 83 | .where(sql.ref('deleted_at'), 'is', null) // Only index active (non-deleted) casts 84 | .execute() 85 | 86 | await db.schema 87 | .createIndex('casts_timestamp_index') 88 | .ifNotExists() 89 | .on('casts') 90 | .columns(['timestamp']) 91 | .execute() 92 | 93 | await db.schema 94 | .createIndex('casts_parent_hash_index') 95 | .ifNotExists() 96 | .on('casts') 97 | .column('parentHash') 98 | .where('parentHash', 'is not', null) 99 | .execute() 100 | 101 | await db.schema 102 | .createIndex('casts_root_parent_hash_index') 103 | .ifNotExists() 104 | .on('casts') 105 | .columns(['rootParentHash']) 106 | .where('rootParentHash', 'is not', null) 107 | .execute() 108 | 109 | await db.schema 110 | .createIndex('casts_parent_url_index') 111 | .ifNotExists() 112 | .on('casts') 113 | .columns(['parentUrl']) 114 | .where('parentUrl', 'is not', null) 115 | .execute() 116 | 117 | await db.schema 118 | .createIndex('casts_root_parent_url_index') 119 | .ifNotExists() 120 | .on('casts') 121 | .columns(['rootParentUrl']) 122 | .where('rootParentUrl', 'is not', null) 123 | .execute() 124 | 125 | // REACTIONS 126 | await db.schema 127 | .createTable('reactions') 128 | .ifNotExists() 129 | .addColumn('id', 'uuid', (col) => 130 | col.defaultTo(sql`generate_ulid()`).primaryKey() 131 | ) 132 | .addColumn('createdAt', 'timestamptz', (col) => 133 | col.notNull().defaultTo(sql`current_timestamp`) 134 | ) 135 | .addColumn('updatedAt', 'timestamptz', (col) => 136 | col.notNull().defaultTo(sql`current_timestamp`) 137 | ) 138 | .addColumn('timestamp', 'timestamptz', (col) => col.notNull()) 139 | .addColumn('deletedAt', 'timestamptz') 140 | .addColumn('prunedAt', 'timestamptz') 141 | .addColumn('fid', 'bigint', (col) => col.notNull()) 142 | .addColumn('targetCastFid', 'bigint') 143 | .addColumn('type', 'int2', (col) => col.notNull()) 144 | .addColumn('hash', 'bytea', (col) => col.notNull().unique()) 145 | .addColumn('targetCastHash', 'bytea') 146 | .addColumn('targetUrl', 'text') 147 | .execute() 148 | 149 | await db.schema 150 | .createIndex('reactions_active_fid_timestamp_index') 151 | .ifNotExists() 152 | .on('reactions') 153 | .columns(['fid', 'timestamp']) 154 | .where(sql.ref('deleted_at'), 'is', null) // Only index active (non-deleted) reactions 155 | .execute() 156 | 157 | await db.schema 158 | .createIndex('reactions_target_cast_hash_index') 159 | .ifNotExists() 160 | .on('reactions') 161 | .column('targetCastHash') 162 | .where('targetCastHash', 'is not', null) 163 | .execute() 164 | 165 | await db.schema 166 | .createIndex('reactions_target_url_index') 167 | .ifNotExists() 168 | .on('reactions') 169 | .columns(['targetUrl']) 170 | .where('targetUrl', 'is not', null) 171 | .execute() 172 | 173 | // LINKS 174 | await db.schema 175 | .createTable('links') 176 | .ifNotExists() 177 | .addColumn('id', 'uuid', (col) => 178 | col.defaultTo(sql`generate_ulid()`).primaryKey() 179 | ) 180 | .addColumn('createdAt', 'timestamptz', (col) => 181 | col.notNull().defaultTo(sql`current_timestamp`) 182 | ) 183 | .addColumn('updatedAt', 'timestamptz', (col) => 184 | col.notNull().defaultTo(sql`current_timestamp`) 185 | ) 186 | .addColumn('timestamp', 'timestamptz', (col) => col.notNull()) 187 | .addColumn('deletedAt', 'timestamptz') 188 | .addColumn('prunedAt', 'timestamptz') 189 | .addColumn('fid', 'bigint', (col) => col.notNull()) 190 | .addColumn('targetFid', 'bigint', (col) => col.notNull()) 191 | .addColumn('displayTimestamp', 'timestamptz') 192 | .addColumn('type', 'text', (col) => col.notNull()) 193 | .addColumn('hash', 'bytea', (col) => col.notNull().unique()) 194 | .execute() 195 | 196 | // VERIFICATIONS 197 | await db.schema 198 | .createTable('verifications') 199 | .ifNotExists() 200 | .addColumn('id', 'uuid', (col) => 201 | col.defaultTo(sql`generate_ulid()`).primaryKey() 202 | ) 203 | .addColumn('createdAt', 'timestamptz', (col) => 204 | col.notNull().defaultTo(sql`current_timestamp`) 205 | ) 206 | .addColumn('updatedAt', 'timestamptz', (col) => 207 | col.notNull().defaultTo(sql`current_timestamp`) 208 | ) 209 | .addColumn('timestamp', 'timestamptz', (col) => col.notNull()) 210 | .addColumn('deletedAt', 'timestamptz') 211 | .addColumn('fid', 'bigint', (col) => col.notNull()) 212 | .addColumn('hash', 'bytea', (col) => col.notNull()) 213 | .addColumn('signerAddress', 'bytea', (col) => col.notNull()) 214 | .addColumn('blockHash', 'bytea', (col) => col.notNull()) 215 | .addColumn('signature', 'bytea', (col) => col.notNull()) 216 | .addUniqueConstraint('verifications_signer_address_fid_unique', [ 217 | 'signerAddress', 218 | 'fid', 219 | ]) 220 | .execute() 221 | 222 | await db.schema 223 | .createIndex('verifications_fid_timestamp_index') 224 | .ifNotExists() 225 | .on('verifications') 226 | .columns(['fid', 'timestamp']) 227 | .execute() 228 | 229 | // USER DATA 230 | await db.schema 231 | .createTable('userData') 232 | .ifNotExists() 233 | .addColumn('id', 'uuid', (col) => 234 | col.defaultTo(sql`generate_ulid()`).primaryKey() 235 | ) 236 | .addColumn('createdAt', 'timestamptz', (col) => 237 | col.notNull().defaultTo(sql`current_timestamp`) 238 | ) 239 | .addColumn('updatedAt', 'timestamptz', (col) => 240 | col.notNull().defaultTo(sql`current_timestamp`) 241 | ) 242 | .addColumn('timestamp', 'timestamptz', (col) => col.notNull()) 243 | .addColumn('deletedAt', 'timestamptz') 244 | .addColumn('fid', 'bigint', (col) => col.notNull()) 245 | .addColumn('type', 'int2', (col) => col.notNull()) 246 | .addColumn('hash', 'bytea', (col) => col.notNull().unique()) 247 | .addColumn('value', 'text', (col) => col.notNull()) 248 | .addUniqueConstraint('user_data_fid_type_unique', ['fid', 'type']) 249 | .execute() 250 | 251 | // FIDS ----------------------------------------------------------------------------------------- 252 | await db.schema 253 | .createTable('fids') 254 | .addColumn('fid', 'bigint', (col) => col.notNull()) 255 | .addPrimaryKeyConstraint('fids_pkey', ['fid']) 256 | .addColumn('createdAt', 'timestamptz', (col) => 257 | col.notNull().defaultTo(sql`current_timestamp`) 258 | ) 259 | .addColumn('updatedAt', 'timestamptz', (col) => 260 | col.notNull().defaultTo(sql`current_timestamp`) 261 | ) 262 | .addColumn('registeredAt', 'timestamptz', (col) => col.notNull()) 263 | .addColumn('custodyAddress', 'bytea', (col) => col.notNull()) 264 | .addColumn('recoveryAddress', 'bytea', (col) => col.notNull()) 265 | .execute() 266 | 267 | // SIGNERS -------------------------------------------------------------------------------------- 268 | await db.schema 269 | .createTable('signers') 270 | .addColumn('id', 'uuid', (col) => col.defaultTo(sql`generate_ulid()`)) 271 | .addColumn('createdAt', 'timestamptz', (col) => 272 | col.notNull().defaultTo(sql`current_timestamp`) 273 | ) 274 | .addColumn('updatedAt', 'timestamptz', (col) => 275 | col.notNull().defaultTo(sql`current_timestamp`) 276 | ) 277 | .addColumn('addedAt', 'timestamptz', (col) => col.notNull()) 278 | .addColumn('removedAt', 'timestamptz') 279 | .addColumn('fid', 'bigint', (col) => col.notNull()) 280 | .addColumn('requesterFid', 'bigint', (col) => col.notNull()) 281 | .addColumn('keyType', sql`smallint`, (col) => col.notNull()) 282 | .addColumn('metadataType', sql`smallint`, (col) => col.notNull()) 283 | .addColumn('key', 'bytea', (col) => col.notNull()) 284 | .addColumn('metadata', 'json', (col) => col.notNull()) 285 | .addUniqueConstraint('signers_fid_key_unique', ['fid', 'key']) 286 | .execute() 287 | 288 | await db.schema 289 | .createIndex('signers_fid_index') 290 | .on('signers') 291 | .column('fid') 292 | .execute() 293 | 294 | await db.schema 295 | .createIndex('signers_requester_fid_index') 296 | .on('signers') 297 | .column('requesterFid') 298 | .execute() 299 | 300 | // STORAGE ALLOCATIONS --------------------------------------------------------------------------- 301 | await db.schema 302 | .createTable('storage') 303 | .addColumn('id', 'uuid', (col) => col.defaultTo(sql`generate_ulid()`)) 304 | .addColumn('createdAt', 'timestamptz', (col) => 305 | col.notNull().defaultTo(sql`current_timestamp`) 306 | ) 307 | .addColumn('updatedAt', 'timestamptz', (col) => 308 | col.notNull().defaultTo(sql`current_timestamp`) 309 | ) 310 | .addColumn('rentedAt', 'timestamptz', (col) => col.notNull()) 311 | .addColumn('expiresAt', 'timestamptz', (col) => col.notNull()) 312 | .addColumn('fid', 'bigint', (col) => col.notNull()) 313 | .addColumn('units', sql`smallint`, (col) => col.notNull()) 314 | .addColumn('payer', 'bytea', (col) => col.notNull()) 315 | .addUniqueConstraint('storage_fid_expires_at_unique', ['fid', 'expiresAt']) 316 | .execute() 317 | 318 | await db.schema 319 | .createIndex('storage_fid_expires_at_index') 320 | .on('storage') 321 | .columns(['fid', 'expiresAt']) 322 | .execute() 323 | 324 | // HUBS 325 | await db.schema 326 | .createTable('hubs') 327 | .ifNotExists() 328 | .addColumn('id', 'uuid', (col) => 329 | col.defaultTo(sql`gen_random_uuid()`).primaryKey() 330 | ) 331 | .addColumn('gossipAddress', 'text', (col) => col.notNull()) 332 | .addColumn('rpcAddress', 'text', (col) => col.notNull()) 333 | .addColumn('excludedHashes', sql`TEXT[]`, (col) => col.notNull()) 334 | .addColumn('count', 'int4', (col) => col.notNull().defaultTo(0)) 335 | .addColumn('hubVersion', 'text', (col) => col.notNull()) 336 | .addColumn('network', 'text', (col) => col.notNull()) 337 | .addColumn('appVersion', 'text', (col) => col.notNull()) 338 | .addColumn('timestamp', 'int8', (col) => col.notNull()) 339 | .addColumn('createdAt', 'timestamptz', (col) => 340 | col.notNull().defaultTo(sql`current_timestamp`) 341 | ) 342 | .addColumn('updatedAt', 'timestamptz', (col) => 343 | col.notNull().defaultTo(sql`current_timestamp`) 344 | ) 345 | .execute() 346 | } 347 | 348 | export const down = async (db: Kysely) => { 349 | // Delete in reverse order of above so that foreign keys are not violated. 350 | await db.schema.dropTable('hubs').ifExists().execute() 351 | await db.schema.dropTable('userData').ifExists().execute() 352 | await db.schema.dropTable('verifications').ifExists().execute() 353 | await db.schema.dropTable('links').ifExists().execute() 354 | await db.schema.dropTable('reactions').ifExists().execute() 355 | await db.schema.dropTable('casts').ifExists().execute() 356 | } 357 | -------------------------------------------------------------------------------- /src/db/migrations/002_add_convenience_views.ts: -------------------------------------------------------------------------------- 1 | import { Kysely, sql } from 'kysely' 2 | 3 | export const up = async (db: Kysely) => { 4 | // Users view 5 | await db.schema 6 | .createView('users') 7 | .orReplace() 8 | .as( 9 | sql` 10 | SELECT 11 | s.fid, 12 | MAX(CASE WHEN ud.type = 1 THEN ud.value END) AS pfp, 13 | MAX(CASE WHEN ud.type = 2 THEN ud.value END) AS display, 14 | MAX(CASE WHEN ud.type = 3 THEN ud.value END) AS bio, 15 | MAX(CASE WHEN ud.type = 5 THEN ud.value END) AS url, 16 | MAX(CASE WHEN ud.type = 6 THEN ud.value END) AS username 17 | FROM storage s 18 | JOIN signers sg ON s.fid = sg.fid 19 | LEFT JOIN user_data ud ON s.fid = ud.fid 20 | WHERE s.units > 0 21 | AND sg.requester_fid IS NOT NULL 22 | AND ud.deleted_at IS NULL 23 | GROUP BY s.fid 24 | ORDER BY s.fid ASC; 25 | ` 26 | ) 27 | .execute() 28 | 29 | // Casts with metadata view 30 | await db.schema 31 | .createView('casts_enhanced') 32 | .orReplace() 33 | .as( 34 | sql` 35 | WITH p AS ( 36 | SELECT 37 | fid, 38 | MAX(CASE WHEN type = 1 THEN value END) AS pfp, 39 | MAX(CASE WHEN type = 2 THEN value END) AS display, 40 | MAX(CASE WHEN type = 6 THEN value END) AS username 41 | FROM user_data 42 | GROUP BY fid 43 | ) 44 | SELECT 45 | c.fid, 46 | c.hash, 47 | c.parent_fid, 48 | c.parent_url, 49 | c.parent_hash, 50 | c.root_parent_url, 51 | c.root_parent_hash, 52 | c.timestamp, 53 | c.text, 54 | c.embeds, 55 | c.mentions, 56 | c.mentions_positions, 57 | p.pfp AS author_pfp, 58 | p.display AS author_display, 59 | p.username AS author_username 60 | FROM 61 | casts c 62 | JOIN p ON c.fid = p.fid 63 | WHERE c.deleted_at IS NULL 64 | ` 65 | ) 66 | .execute() 67 | } 68 | 69 | export const down = async (db: Kysely) => { 70 | await db.schema.dropView('users').ifExists().execute() 71 | await db.schema.dropView('casts_enhanced').ifExists().execute() 72 | } 73 | -------------------------------------------------------------------------------- /src/db/migrator.ts: -------------------------------------------------------------------------------- 1 | import 'dotenv/config' 2 | import { promises as fs } from 'fs' 3 | import { FileMigrationProvider, Migrator } from 'kysely' 4 | import * as path from 'path' 5 | import { fileURLToPath } from 'url' 6 | 7 | import { log } from '../lib/logger.js' 8 | import { db } from './kysely.js' 9 | 10 | async function migrateToLatest() { 11 | const migrator = new Migrator({ 12 | db, 13 | provider: new FileMigrationProvider({ 14 | fs, 15 | path, 16 | migrationFolder: path.join( 17 | path.dirname(fileURLToPath(import.meta.url)), 18 | 'migrations' 19 | ), 20 | }), 21 | }) 22 | 23 | const { error, results } = await migrator.migrateToLatest() 24 | 25 | results?.forEach((it) => { 26 | if (it.status === 'Success') { 27 | log.info(`Migration "${it.migrationName}" was executed successfully`) 28 | } else if (it.status === 'Error') { 29 | log.error(`Failed to execute migration "${it.migrationName}"`) 30 | } 31 | }) 32 | 33 | if (error) { 34 | log.error(error, 'Failed to migrate') 35 | process.exit(1) 36 | } 37 | 38 | await db.destroy() 39 | } 40 | 41 | migrateToLatest() 42 | -------------------------------------------------------------------------------- /src/db/search-migrations.sql: -------------------------------------------------------------------------------- 1 | -- Opinionated additions to the schema for Searchcaster 2 | 3 | -- Generate a tsvector column for the casts table 4 | ALTER TABLE casts 5 | ADD COLUMN fts tsvector GENERATED always AS ( 6 | to_tsvector('english', text)) stored; 7 | 8 | -- Create an index on the fts column for faster searching 9 | CREATE INDEX casts_fts ON casts 10 | USING GIN (fts); 11 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import 'dotenv/config' 2 | 3 | import { getLatestEvent } from './api/event.js' 4 | import { backfill, backfillQueue, backfillWorker } from './lib/backfill.js' 5 | import { initExpressApp } from './lib/express.js' 6 | import { log } from './lib/logger.js' 7 | import { subscribe } from './lib/subscriber.js' 8 | 9 | initExpressApp() 10 | 11 | if (process.argv[2] === '--backfill') { 12 | await backfill({ 13 | maxFid: Number(process.env.BACKFILL_MAX_FID) || undefined, 14 | }) 15 | 16 | // Once backfill completes, start subscribing to new events 17 | let subscriberStarted = false 18 | backfillWorker.on('completed', async () => { 19 | if (subscriberStarted) return 20 | const queueSize = await backfillQueue.getActiveCount() 21 | 22 | if (queueSize === 0) { 23 | subscriberStarted = true 24 | log.info('Finished backfill') 25 | subscribe(await getLatestEvent()) 26 | } 27 | }) 28 | } else { 29 | subscribe(await getLatestEvent()) 30 | } 31 | -------------------------------------------------------------------------------- /src/lib/backfill.ts: -------------------------------------------------------------------------------- 1 | import { Job } from 'bullmq' 2 | 3 | import { insertCasts } from '../api/cast.js' 4 | import { saveLatestEventId } from '../api/event.js' 5 | import { insertRegistrations } from '../api/fid.js' 6 | import { insertHubs } from '../api/hub.js' 7 | import { insertLinks } from '../api/link.js' 8 | import { insertReactions } from '../api/reaction.js' 9 | import { insertSigners } from '../api/signer.js' 10 | import { insertStorage } from '../api/storage.js' 11 | import { insertUserDatas } from '../api/user-data.js' 12 | import { insertVerifications } from '../api/verification.js' 13 | import { createQueue, createWorker } from '../lib/bullmq.js' 14 | import { hubClient } from '../lib/hub-client.js' 15 | import { log } from '../lib/logger.js' 16 | import { getFullProfileFromHub } from '../lib/utils.js' 17 | import { makeLatestEventId } from './event.js' 18 | 19 | type BackfillJob = { 20 | fids: number[] 21 | } 22 | 23 | export const backfillQueue = createQueue('backfill') 24 | export const backfillWorker = createWorker('backfill', handleJob) 25 | 26 | async function addFidsToBackfillQueue(maxFid?: number) { 27 | const fids = (await getAllFids()).slice(0, maxFid) 28 | const batchSize = 10 29 | 30 | for (let i = 0; i < fids.length; i += batchSize) { 31 | const batch = fids.slice(i, i + batchSize) 32 | await backfillQueue.add('backfill', { fids: batch }) 33 | } 34 | 35 | log.info('Added fids to queue') 36 | } 37 | 38 | /** 39 | * Backfill the database with data from a hub. This may take a while. 40 | */ 41 | export async function backfill({ maxFid }: { maxFid?: number | undefined }) { 42 | // Only add fids to the queue if it's empty, otherwise it creates duplicate jobs 43 | if ((await backfillQueue.getWaitingCount()) > 0) { 44 | log.info('Backfill queue already has jobs waiting.') 45 | return 46 | } 47 | 48 | log.info('Starting backfill') 49 | 50 | // Save the latest event ID so we can subscribe from there after backfill completes 51 | const latestEventId = makeLatestEventId() 52 | await saveLatestEventId(latestEventId) 53 | await addFidsToBackfillQueue(maxFid) 54 | await getHubs() 55 | // await getDbInfo() 56 | } 57 | 58 | /** 59 | * Get all fids 60 | * @returns array of fids 61 | */ 62 | async function getAllFids() { 63 | const maxFidResult = await hubClient.getFids({ 64 | pageSize: 1, 65 | reverse: true, 66 | }) 67 | 68 | if (maxFidResult.isErr()) { 69 | throw new Error('Unable to backfill', { cause: maxFidResult.error }) 70 | } 71 | 72 | const maxFid = maxFidResult.value.fids[0] 73 | return Array.from({ length: Number(maxFid) }, (_, i) => i + 1) 74 | } 75 | 76 | /** 77 | * Get all hubs 78 | */ 79 | async function getHubs() { 80 | const peers = await hubClient.getCurrentPeers({}) 81 | 82 | if (peers.isErr()) { 83 | throw new Error('Unable to backfill Hubs', { cause: peers.error }) 84 | } 85 | 86 | insertHubs(peers.value.contacts) 87 | } 88 | 89 | // async function getDbInfo() { 90 | // const dbInfo = await hubClient.getInfo({ 91 | // dbStats: true, 92 | // }) 93 | 94 | // if (dbInfo.isErr()) { 95 | // throw new Error('Unable to get DB info', { cause: dbInfo.error }) 96 | // } 97 | 98 | // log.info(dbInfo.value) 99 | // } 100 | 101 | async function handleJob(job: Job) { 102 | const { fids } = job.data 103 | 104 | for (let i = 0; i < fids.length; i++) { 105 | const fid = fids[i] 106 | 107 | const p = await getFullProfileFromHub(fid).catch((err) => { 108 | log.error(err, `Error getting profile for FID ${fid}`) 109 | return null 110 | }) 111 | 112 | if (!p) continue 113 | 114 | await insertCasts(p.casts) 115 | await insertLinks(p.links) 116 | await insertReactions(p.reactions) 117 | await insertUserDatas(p.userData) 118 | await insertVerifications(p.verifications) 119 | 120 | await insertRegistrations(await p.registrations) 121 | await insertSigners(await p.signers) 122 | await insertStorage(await p.storage) 123 | 124 | await job.updateProgress(((i + 1) / fids.length) * 100) 125 | } 126 | 127 | await job.updateProgress(100) 128 | } 129 | -------------------------------------------------------------------------------- /src/lib/bullmq.ts: -------------------------------------------------------------------------------- 1 | import { Job, Queue, QueueOptions, Worker } from 'bullmq' 2 | 3 | import { redis } from './redis.js' 4 | 5 | const bullMqOptions: QueueOptions = { 6 | connection: redis, 7 | prefix: 'hub', 8 | } 9 | 10 | export function createQueue(name: string) { 11 | return new Queue(name, bullMqOptions) 12 | } 13 | 14 | export function createWorker( 15 | name: string, 16 | jobHandler: (job: Job) => Promise, 17 | opts?: { 18 | concurrency?: number 19 | } 20 | ) { 21 | const concurrency = 22 | opts?.concurrency || Number(process.env.WORKER_CONCURRENCY || 5) 23 | 24 | return new Worker(name, jobHandler, { 25 | ...bullMqOptions, 26 | useWorkerThreads: concurrency > 1, 27 | removeOnComplete: { count: 100 }, 28 | concurrency, 29 | }) 30 | } 31 | -------------------------------------------------------------------------------- /src/lib/event.ts: -------------------------------------------------------------------------------- 1 | import { 2 | FARCASTER_EPOCH, 3 | HubEvent, 4 | HubEventType, 5 | MessageType, 6 | OnChainEventType, 7 | } from '@farcaster/hub-nodejs' 8 | import { Job } from 'bullmq' 9 | 10 | import { deleteCasts, insertCasts, pruneCasts } from '../api/cast.js' 11 | import { insertRegistrations } from '../api/fid.js' 12 | import { deleteLinks, insertLinks, pruneLinks } from '../api/link.js' 13 | import { 14 | deleteReactions, 15 | insertReactions, 16 | pruneReactions, 17 | } from '../api/reaction.js' 18 | import { insertSigners } from '../api/signer.js' 19 | import { insertStorage } from '../api/storage.js' 20 | import { insertUserDatas } from '../api/user-data.js' 21 | import { 22 | deleteVerifications, 23 | insertVerifications, 24 | } from '../api/verification.js' 25 | import { log } from './logger.js' 26 | 27 | /** 28 | * Update the database based on the event type 29 | * @param job Job to add to the `stream` queue 30 | */ 31 | export async function handleEvent(job: Job) { 32 | const encodedEvent = job.data 33 | const event = HubEvent.decode(Buffer.from(encodedEvent)) 34 | 35 | switch (event.type) { 36 | case HubEventType.MERGE_MESSAGE: { 37 | const msg = event.mergeMessageBody!.message! 38 | const msgType = msg.data!.type 39 | 40 | switch (msgType) { 41 | case MessageType.CAST_ADD: { 42 | await insertCasts([msg]) 43 | break 44 | } 45 | case MessageType.CAST_REMOVE: { 46 | await deleteCasts([msg]) 47 | break 48 | } 49 | case MessageType.VERIFICATION_ADD_ETH_ADDRESS: { 50 | await insertVerifications([msg]) 51 | break 52 | } 53 | case MessageType.VERIFICATION_REMOVE: { 54 | await deleteVerifications([msg]) 55 | break 56 | } 57 | case MessageType.USER_DATA_ADD: { 58 | await insertUserDatas([msg]) 59 | break 60 | } 61 | case MessageType.REACTION_ADD: { 62 | await insertReactions([msg]) 63 | break 64 | } 65 | case MessageType.REACTION_REMOVE: { 66 | await deleteReactions([msg]) 67 | break 68 | } 69 | case MessageType.LINK_ADD: { 70 | await insertLinks([msg]) 71 | break 72 | } 73 | case MessageType.LINK_REMOVE: { 74 | await deleteLinks([msg]) 75 | break 76 | } 77 | default: { 78 | log.debug('UNHANDLED MERGE_MESSAGE EVENT', event.id) 79 | } 80 | } 81 | 82 | break 83 | } 84 | case HubEventType.PRUNE_MESSAGE: { 85 | const msg = event.pruneMessageBody!.message! 86 | const msgType = msg.data!.type 87 | 88 | switch (msgType) { 89 | case MessageType.CAST_ADD: { 90 | await pruneCasts([msg]) 91 | break 92 | } 93 | case MessageType.REACTION_ADD: { 94 | await pruneReactions([msg]) 95 | break 96 | } 97 | case MessageType.LINK_ADD: { 98 | await pruneLinks([msg]) 99 | break 100 | } 101 | default: { 102 | log.debug(msg.data, 'UNHANDLED PRUNE_MESSAGE EVENT') 103 | } 104 | } 105 | 106 | break 107 | } 108 | case HubEventType.REVOKE_MESSAGE: { 109 | // Events are emitted when a signer that was used to create a message is removed 110 | // TODO: handle revoking messages 111 | break 112 | } 113 | case HubEventType.MERGE_ON_CHAIN_EVENT: { 114 | const onChainEvent = event.mergeOnChainEventBody!.onChainEvent! 115 | 116 | switch (onChainEvent.type) { 117 | case OnChainEventType.EVENT_TYPE_ID_REGISTER: { 118 | await insertRegistrations([onChainEvent]) 119 | break 120 | } 121 | case OnChainEventType.EVENT_TYPE_SIGNER: { 122 | await insertSigners([onChainEvent]) 123 | break 124 | } 125 | case OnChainEventType.EVENT_TYPE_STORAGE_RENT: { 126 | await insertStorage([onChainEvent]) 127 | break 128 | } 129 | } 130 | 131 | break 132 | } 133 | default: { 134 | log.debug('UNHANDLED HUB EVENT', event.id) 135 | break 136 | } 137 | } 138 | 139 | await job.updateProgress(100) 140 | } 141 | 142 | export function makeLatestEventId() { 143 | const seq = 0 144 | const now = Date.now() 145 | const timestamp = now - FARCASTER_EPOCH 146 | const SEQUENCE_BITS = 12 147 | 148 | const binaryTimestamp = timestamp.toString(2) 149 | let binarySeq = seq.toString(2) 150 | if (binarySeq.length) { 151 | while (binarySeq.length < SEQUENCE_BITS) { 152 | binarySeq = `0${binarySeq}` 153 | } 154 | } 155 | 156 | return parseInt(binaryTimestamp + binarySeq, 2) 157 | } 158 | -------------------------------------------------------------------------------- /src/lib/express.ts: -------------------------------------------------------------------------------- 1 | import { createBullBoard } from '@bull-board/api' 2 | import { BullMQAdapter } from '@bull-board/api/bullMQAdapter.js' 3 | import { ExpressAdapter } from '@bull-board/express' 4 | import { extractEventTimestamp } from '@farcaster/hub-nodejs' 5 | import express from 'express' 6 | 7 | import { getLatestEvent } from '../api/event.js' 8 | import { backfillQueue } from './backfill.js' 9 | import { log } from './logger.js' 10 | import { streamQueue } from './subscriber.js' 11 | 12 | export function initExpressApp() { 13 | const app = express() 14 | const serverAdapter = new ExpressAdapter() 15 | 16 | app.listen(3001, () => { 17 | log.info('Server started on http://localhost:3001') 18 | }) 19 | 20 | serverAdapter.setBasePath('/') 21 | app.use('/', serverAdapter.getRouter()) 22 | 23 | app.get('/stats', async (req, res) => { 24 | let latestEventTimestamp 25 | const latestEventId = await getLatestEvent() 26 | const isBackfillActive = (await backfillQueue.getActiveCount()) > 0 27 | 28 | if (latestEventId) { 29 | latestEventTimestamp = extractEventTimestamp(latestEventId) 30 | } 31 | 32 | return res 33 | .status(200) 34 | .json({ latestEventId, latestEventTimestamp, isBackfillActive }) 35 | }) 36 | 37 | createBullBoard({ 38 | queues: [new BullMQAdapter(backfillQueue), new BullMQAdapter(streamQueue)], 39 | serverAdapter, 40 | }) 41 | } 42 | -------------------------------------------------------------------------------- /src/lib/hub-client.ts: -------------------------------------------------------------------------------- 1 | import { 2 | getInsecureHubRpcClient, 3 | getSSLHubRpcClient, 4 | } from '@farcaster/hub-nodejs' 5 | 6 | const HUB_RPC = process.env.HUB_RPC 7 | const HUB_SSL = process.env.HUB_SSL || 'true' 8 | 9 | if (!HUB_RPC) { 10 | throw new Error('HUB_RPC env variable is not set') 11 | } 12 | 13 | export const hubClient = 14 | HUB_SSL === 'true' 15 | ? getSSLHubRpcClient(HUB_RPC) 16 | : getInsecureHubRpcClient(HUB_RPC) 17 | -------------------------------------------------------------------------------- /src/lib/logger.ts: -------------------------------------------------------------------------------- 1 | import { pino } from 'pino' 2 | 3 | export const log = pino({ 4 | // 'fatal' | 'error' | 'warn' | 'info' | 'debug' | 'trace' 5 | level: process.env.LOG_LEVEL || 'info', 6 | transport: { 7 | target: 'pino-pretty', 8 | options: { 9 | colorize: true, 10 | // singleLine: true, 11 | }, 12 | }, 13 | }) 14 | -------------------------------------------------------------------------------- /src/lib/paginate.ts: -------------------------------------------------------------------------------- 1 | // TODO: Clean up the functions in this file, it's very repetitive 2 | import { 3 | FidRequest, 4 | HubResult, 5 | HubRpcClient, 6 | Message, 7 | OnChainEvent, 8 | OnChainEventType, 9 | } from '@farcaster/hub-nodejs' 10 | 11 | import { hubClient } from './hub-client.js' 12 | import { MAX_PAGE_SIZE, checkMessages } from './utils.js' 13 | 14 | export async function getAllCastsByFid(fid: FidRequest) { 15 | const casts: Message[] = new Array() 16 | let nextPageToken: Uint8Array | undefined 17 | 18 | while (true) { 19 | const res = await hubClient.getCastsByFid({ 20 | ...fid, 21 | pageSize: MAX_PAGE_SIZE, 22 | pageToken: nextPageToken, 23 | }) 24 | 25 | const messages = checkMessages(res, fid.fid) 26 | casts.push(...messages) 27 | 28 | if (messages.length < MAX_PAGE_SIZE) { 29 | break 30 | } 31 | 32 | nextPageToken = res._unsafeUnwrap().nextPageToken 33 | } 34 | 35 | return casts 36 | } 37 | 38 | export async function getAllReactionsByFid(fid: FidRequest) { 39 | const reactions: Message[] = new Array() 40 | let nextPageToken: Uint8Array | undefined 41 | 42 | while (true) { 43 | const res = await hubClient.getReactionsByFid({ 44 | ...fid, 45 | pageSize: MAX_PAGE_SIZE, 46 | pageToken: nextPageToken, 47 | }) 48 | 49 | const messages = checkMessages(res, fid.fid) 50 | reactions.push(...messages) 51 | 52 | if (messages.length < MAX_PAGE_SIZE) { 53 | break 54 | } 55 | 56 | nextPageToken = res._unsafeUnwrap().nextPageToken 57 | } 58 | 59 | return reactions 60 | } 61 | 62 | export async function getAllLinksByFid(fid: FidRequest) { 63 | const links: Message[] = new Array() 64 | let nextPageToken: Uint8Array | undefined 65 | 66 | while (true) { 67 | const res = await hubClient.getLinksByFid({ 68 | ...fid, 69 | pageSize: MAX_PAGE_SIZE, 70 | pageToken: nextPageToken, 71 | }) 72 | 73 | const messages = checkMessages(res, fid.fid) 74 | links.push(...messages) 75 | 76 | if (messages.length < MAX_PAGE_SIZE) { 77 | break 78 | } 79 | 80 | nextPageToken = res._unsafeUnwrap().nextPageToken 81 | } 82 | 83 | return links 84 | } 85 | 86 | // TODO: refactor this to be more consistent with the other functions 87 | // Currently its a rip from the old replicator 88 | export async function* getOnChainEventsByFidInBatchesOf( 89 | hub: HubRpcClient, 90 | { 91 | fid, 92 | pageSize, 93 | eventTypes, 94 | }: { 95 | fid: number 96 | pageSize: number 97 | eventTypes: OnChainEventType[] 98 | } 99 | ) { 100 | for (const eventType of eventTypes) { 101 | let result = await retryHubCallWithExponentialBackoff(() => 102 | hub.getOnChainEvents({ pageSize, fid, eventType }) 103 | ) 104 | for (;;) { 105 | if (result.isErr()) { 106 | throw new Error( 107 | `Unable to backfill events for FID ${fid} of type ${eventType}`, 108 | { cause: result.error } 109 | ) 110 | } 111 | 112 | const { events, nextPageToken: pageToken } = result.value 113 | yield events as OnChainEvent[] 114 | 115 | if (!pageToken?.length) break 116 | result = await retryHubCallWithExponentialBackoff(() => 117 | hub.getOnChainEvents({ pageSize, pageToken, fid, eventType }) 118 | ) 119 | } 120 | } 121 | } 122 | 123 | async function retryHubCallWithExponentialBackoff( 124 | fn: () => Promise>, 125 | attempt = 1, 126 | maxAttempts = 10, 127 | baseDelayMs = 100 128 | ): Promise> { 129 | let currentAttempt = attempt 130 | try { 131 | const result = await fn() 132 | if (result.isErr()) { 133 | throw new Error(`maybe retryable error : ${JSON.stringify(result.error)}`) 134 | } 135 | return result 136 | } catch (error) { 137 | if (currentAttempt >= maxAttempts) { 138 | throw error 139 | } 140 | 141 | const delayMs = baseDelayMs * 2 ** currentAttempt 142 | 143 | await new Promise((resolve) => setTimeout(resolve, delayMs)) 144 | 145 | currentAttempt++ 146 | return retryHubCallWithExponentialBackoff( 147 | fn, 148 | currentAttempt, 149 | maxAttempts, 150 | delayMs 151 | ) 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /src/lib/redis.ts: -------------------------------------------------------------------------------- 1 | import Redis from 'ioredis' 2 | 3 | const REDIS_URL = process.env.REDIS_URL || 'redis://localhost:6379' 4 | 5 | export const redis = new Redis(REDIS_URL, { 6 | connectTimeout: 5_000, 7 | maxRetriesPerRequest: null, // BullMQ wants this set 8 | }) 9 | -------------------------------------------------------------------------------- /src/lib/subscriber.ts: -------------------------------------------------------------------------------- 1 | import { HubEvent, HubEventType } from '@farcaster/hub-nodejs' 2 | 3 | import { saveLatestEventId } from '../api/event.js' 4 | import { createQueue, createWorker } from './bullmq.js' 5 | import { handleEvent } from './event.js' 6 | import { hubClient } from './hub-client.js' 7 | import { log } from './logger.js' 8 | 9 | export const streamQueue = createQueue('stream') 10 | createWorker('stream', handleEvent, { concurrency: 1 }) 11 | 12 | /** 13 | * Listen for new events from a Hub 14 | */ 15 | export async function subscribe(fromEventId: number | undefined) { 16 | const result = await hubClient.subscribe({ 17 | eventTypes: [ 18 | HubEventType.MERGE_MESSAGE, 19 | HubEventType.PRUNE_MESSAGE, 20 | HubEventType.REVOKE_MESSAGE, 21 | HubEventType.MERGE_ON_CHAIN_EVENT, 22 | ], 23 | fromId: fromEventId, 24 | }) 25 | 26 | if (result.isErr()) { 27 | log.error(result.error, 'Error starting stream') 28 | return 29 | } 30 | 31 | result.match( 32 | (stream) => { 33 | log.info( 34 | `Subscribed to stream from ${fromEventId ? `event ${fromEventId}` : 'head'}` 35 | ) 36 | 37 | stream.on('data', async (e: HubEvent) => { 38 | const encodedEvent = Buffer.from(HubEvent.encode(e).finish()) 39 | await streamQueue.add('stream', encodedEvent) 40 | // TODO: we can probably remove the `hub:latest-event-id` key and just use the last event ID in the queue 41 | await saveLatestEventId(e.id) 42 | }) 43 | 44 | stream.on('close', async () => { 45 | log.warn(`Hub stream closed`) 46 | }) 47 | 48 | stream.on('end', async () => { 49 | log.warn(`Hub stream ended`) 50 | }) 51 | }, 52 | (e) => { 53 | log.error(e, 'Error streaming data.') 54 | } 55 | ) 56 | } 57 | -------------------------------------------------------------------------------- /src/lib/utils.ts: -------------------------------------------------------------------------------- 1 | import { 2 | ContactInfoContentBody, 3 | FidRequest, 4 | HubResult, 5 | Message, 6 | MessagesResponse, 7 | OnChainEventResponse, 8 | fromFarcasterTime, 9 | } from '@farcaster/hub-nodejs' 10 | import { Insertable } from 'kysely' 11 | 12 | import { getAllRegistrationsByFid } from '../api/fid.js' 13 | import { getAllSignersByFid } from '../api/signer.js' 14 | import { getAllStorageByFid } from '../api/storage.js' 15 | import { Tables } from '../db/db.types.js' 16 | import { hubClient } from './hub-client.js' 17 | import { log } from './logger.js' 18 | import { 19 | getAllCastsByFid, 20 | getAllLinksByFid, 21 | getAllReactionsByFid, 22 | } from './paginate.js' 23 | 24 | export const MAX_PAGE_SIZE = 10_000 25 | 26 | export const NULL_ETH_ADDRESS = Uint8Array.from( 27 | Buffer.from('0000000000000000000000000000000000000000', 'hex') 28 | ) 29 | 30 | export function farcasterTimeToDate(time: number): Date { 31 | const result = fromFarcasterTime(time) 32 | if (result.isErr()) throw result.error 33 | return new Date(result.value) 34 | } 35 | 36 | export function formatCasts(msgs: Message[]) { 37 | return msgs.map((msg) => { 38 | const data = msg.data! 39 | const castAddBody = data.castAddBody! 40 | 41 | return { 42 | timestamp: farcasterTimeToDate(data.timestamp), 43 | fid: data.fid, 44 | parentFid: castAddBody.parentCastId?.fid, 45 | hash: msg.hash, 46 | parentHash: castAddBody.parentCastId?.hash, 47 | parentUrl: castAddBody.parentUrl, 48 | text: castAddBody.text, 49 | embeds: JSON.stringify(castAddBody.embeds), 50 | mentions: JSON.stringify(castAddBody.mentions), 51 | mentionsPositions: JSON.stringify(castAddBody.mentionsPositions), 52 | } satisfies Insertable 53 | }) 54 | } 55 | 56 | export function formatReactions(msgs: Message[]) { 57 | return msgs.map((msg) => { 58 | const data = msg.data! 59 | const reaction = data.reactionBody! 60 | 61 | return { 62 | timestamp: farcasterTimeToDate(data.timestamp), 63 | fid: data.fid, 64 | targetCastFid: reaction.targetCastId?.fid, 65 | type: reaction.type, 66 | hash: msg.hash, 67 | targetCastHash: reaction.targetCastId?.hash, 68 | targetUrl: reaction.targetUrl, 69 | } satisfies Insertable 70 | }) 71 | } 72 | 73 | export function formatUserDatas(msgs: Message[]) { 74 | // Users can submit multiple messages with the same `userDataAddBody.type` within the batch period 75 | // We reconcile this by using the value of the last message with the same type from that fid 76 | const userDataMap = new Map() 77 | 78 | for (const msg of msgs) { 79 | const data = msg.data! 80 | const userDataAddBody = data.userDataBody! 81 | userDataMap.set(`fid:${data.fid}-type:${userDataAddBody.type}`, msg) 82 | } 83 | 84 | return Array.from(userDataMap.values()).map((msg) => { 85 | const data = msg.data! 86 | const userDataAddBody = data.userDataBody! 87 | 88 | return { 89 | timestamp: farcasterTimeToDate(data.timestamp), 90 | fid: data.fid, 91 | type: userDataAddBody.type, 92 | hash: msg.hash, 93 | value: userDataAddBody.value, 94 | } satisfies Insertable 95 | }) 96 | } 97 | 98 | export function formatVerifications(msgs: Message[]) { 99 | return msgs.map((msg) => { 100 | const data = msg.data! 101 | const addAddressBody = data.verificationAddAddressBody! 102 | 103 | return { 104 | timestamp: farcasterTimeToDate(data.timestamp), 105 | fid: data.fid, 106 | hash: msg.hash, 107 | signerAddress: addAddressBody.address, 108 | blockHash: addAddressBody.blockHash, 109 | signature: addAddressBody.claimSignature, 110 | } satisfies Insertable 111 | }) 112 | } 113 | 114 | export function formatLinks(msgs: Message[]) { 115 | return msgs.map((msg) => { 116 | const data = msg.data! 117 | const link = data.linkBody! 118 | 119 | return { 120 | timestamp: farcasterTimeToDate(data.timestamp), 121 | fid: data.fid, 122 | targetFid: link.targetFid, 123 | displayTimestamp: link.displayTimestamp 124 | ? farcasterTimeToDate(link.displayTimestamp) 125 | : null, 126 | type: link.type, 127 | hash: msg.hash, 128 | } satisfies Insertable 129 | }) 130 | } 131 | 132 | export function formatHubs(contacts: ContactInfoContentBody[]) { 133 | return contacts.map( 134 | (c) => 135 | ({ 136 | gossipAddress: JSON.stringify(c.gossipAddress), 137 | rpcAddress: JSON.stringify(c.rpcAddress), 138 | excludedHashes: c.excludedHashes, 139 | count: c.count, 140 | hubVersion: c.hubVersion, 141 | network: c.network.toString(), 142 | appVersion: c.appVersion, 143 | timestamp: c.timestamp, 144 | }) satisfies Insertable 145 | ) 146 | } 147 | 148 | export function breakIntoChunks(array: T[], size: number) { 149 | const chunks = [] 150 | for (let i = 0; i < array.length; i += size) { 151 | chunks.push(array.slice(i, i + size)) 152 | } 153 | return chunks 154 | } 155 | 156 | export function checkMessages( 157 | messages: HubResult, 158 | fid: number 159 | ) { 160 | if (messages.isErr()) { 161 | // This happens consistently for the same fids for an unknown reason, but still saves their relevant data 162 | log.debug(messages.error, `Error fetching messages for FID ${fid}`) 163 | } 164 | 165 | return messages.isOk() ? messages.value.messages : [] 166 | } 167 | 168 | export function checkOnchainEvent( 169 | event: HubResult, 170 | fid: number 171 | ) { 172 | if (event.isErr()) { 173 | log.warn(event.error, `Error fetching onchain event for FID ${fid}`) 174 | } 175 | 176 | return event.isOk() ? event.value : null 177 | } 178 | 179 | /** 180 | * Index all messages from a profile 181 | * @param fid Farcaster ID 182 | */ 183 | export async function getFullProfileFromHub(_fid: number) { 184 | const fid = FidRequest.create({ fid: _fid }) 185 | 186 | const userData = await hubClient.getUserDataByFid(fid) 187 | const verifications = await hubClient.getVerificationsByFid(fid) 188 | 189 | return { 190 | casts: await getAllCastsByFid(fid), 191 | reactions: await getAllReactionsByFid(fid), 192 | links: await getAllLinksByFid(fid), 193 | userData: checkMessages(userData, _fid), 194 | verifications: checkMessages(verifications, _fid), 195 | 196 | // Onchain events 197 | registrations: getAllRegistrationsByFid(_fid), 198 | signers: getAllSignersByFid(_fid), 199 | storage: getAllStorageByFid(_fid), 200 | } 201 | } 202 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2022", 4 | "module": "es2022", 5 | "rootDir": "./src", 6 | "outDir": "./dist", 7 | "moduleResolution": "node", 8 | "declaration": true, 9 | "skipLibCheck": true, 10 | "esModuleInterop": true, 11 | "strict": true 12 | }, 13 | "include": ["./src/**/*.ts"] 14 | } 15 | -------------------------------------------------------------------------------- /yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | "@adraffy/ens-normalize@1.10.0": 6 | version "1.10.0" 7 | resolved "https://registry.yarnpkg.com/@adraffy/ens-normalize/-/ens-normalize-1.10.0.tgz#d2a39395c587e092d77cbbc80acf956a54f38bf7" 8 | integrity sha512-nA9XHtlAkYfJxY7bce8DcN7eKxWWCWkU+1GR9d+U6MbNpfwQp8TI7vqOsBsMcHoT4mBu2kypKoSKnghEzOOq5Q== 9 | 10 | "@babel/code-frame@^7.22.13", "@babel/code-frame@^7.23.5": 11 | version "7.23.5" 12 | resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.23.5.tgz#9009b69a8c602293476ad598ff53e4562e15c244" 13 | integrity sha512-CgH3s1a96LipHCmSUmYFPwY7MNx8C3avkq7i4Wl3cfa662ldtUe4VM1TPXX70pfmrlWTb6jLqTYrZyT2ZTJBgA== 14 | dependencies: 15 | "@babel/highlight" "^7.23.4" 16 | chalk "^2.4.2" 17 | 18 | "@babel/generator@7.17.7": 19 | version "7.17.7" 20 | resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.17.7.tgz#8da2599beb4a86194a3b24df6c085931d9ee45ad" 21 | integrity sha512-oLcVCTeIFadUoArDTwpluncplrYBmTCCZZgXCbgNGvOBBiSDDK3eWO4b/+eOTli5tKv1lg+a5/NAXg+nTcei1w== 22 | dependencies: 23 | "@babel/types" "^7.17.0" 24 | jsesc "^2.5.1" 25 | source-map "^0.5.0" 26 | 27 | "@babel/generator@^7.23.0": 28 | version "7.23.6" 29 | resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.23.6.tgz#9e1fca4811c77a10580d17d26b57b036133f3c2e" 30 | integrity sha512-qrSfCYxYQB5owCmGLbl8XRpX1ytXlpueOb0N0UmQwA073KZxejgQTzAmJezxvpwQD9uGtK2shHdi55QT+MbjIw== 31 | dependencies: 32 | "@babel/types" "^7.23.6" 33 | "@jridgewell/gen-mapping" "^0.3.2" 34 | "@jridgewell/trace-mapping" "^0.3.17" 35 | jsesc "^2.5.1" 36 | 37 | "@babel/helper-environment-visitor@^7.22.20": 38 | version "7.22.20" 39 | resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz#96159db61d34a29dba454c959f5ae4a649ba9167" 40 | integrity sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA== 41 | 42 | "@babel/helper-function-name@^7.23.0": 43 | version "7.23.0" 44 | resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz#1f9a3cdbd5b2698a670c30d2735f9af95ed52759" 45 | integrity sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw== 46 | dependencies: 47 | "@babel/template" "^7.22.15" 48 | "@babel/types" "^7.23.0" 49 | 50 | "@babel/helper-hoist-variables@^7.22.5": 51 | version "7.22.5" 52 | resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz#c01a007dac05c085914e8fb652b339db50d823bb" 53 | integrity sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw== 54 | dependencies: 55 | "@babel/types" "^7.22.5" 56 | 57 | "@babel/helper-split-export-declaration@^7.22.6": 58 | version "7.22.6" 59 | resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz#322c61b7310c0997fe4c323955667f18fcefb91c" 60 | integrity sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g== 61 | dependencies: 62 | "@babel/types" "^7.22.5" 63 | 64 | "@babel/helper-string-parser@^7.23.4": 65 | version "7.23.4" 66 | resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.23.4.tgz#9478c707febcbbe1ddb38a3d91a2e054ae622d83" 67 | integrity sha512-803gmbQdqwdf4olxrX4AJyFBV/RTr3rSmOj0rKwesmzlfhYNDEs+/iOcznzpNWlJlIlTJC2QfPFcHB6DlzdVLQ== 68 | 69 | "@babel/helper-validator-identifier@^7.16.7", "@babel/helper-validator-identifier@^7.22.20": 70 | version "7.22.20" 71 | resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz#c4ae002c61d2879e724581d96665583dbc1dc0e0" 72 | integrity sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A== 73 | 74 | "@babel/highlight@^7.23.4": 75 | version "7.23.4" 76 | resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.23.4.tgz#edaadf4d8232e1a961432db785091207ead0621b" 77 | integrity sha512-acGdbYSfp2WheJoJm/EBBBLh/ID8KDc64ISZ9DYtBmC8/Q204PZJLHyzeB5qMzJ5trcOkybd78M4x2KWsUq++A== 78 | dependencies: 79 | "@babel/helper-validator-identifier" "^7.22.20" 80 | chalk "^2.4.2" 81 | js-tokens "^4.0.0" 82 | 83 | "@babel/parser@^7.20.5", "@babel/parser@^7.23.0", "@babel/parser@^7.24.0": 84 | version "7.24.0" 85 | resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.24.0.tgz#26a3d1ff49031c53a97d03b604375f028746a9ac" 86 | integrity sha512-QuP/FxEAzMSjXygs8v4N9dvdXzEHN4W1oF3PxuWAtPo08UdM17u89RDMgjLn/mlc56iM0HlLmVkO/wgR+rDgHg== 87 | 88 | "@babel/template@^7.22.15": 89 | version "7.24.0" 90 | resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.24.0.tgz#c6a524aa93a4a05d66aaf31654258fae69d87d50" 91 | integrity sha512-Bkf2q8lMB0AFpX0NFEqSbx1OkTHf0f+0j82mkw+ZpzBnkk7e9Ql0891vlfgi+kHwOk8tQjiQHpqh4LaSa0fKEA== 92 | dependencies: 93 | "@babel/code-frame" "^7.23.5" 94 | "@babel/parser" "^7.24.0" 95 | "@babel/types" "^7.24.0" 96 | 97 | "@babel/traverse@7.23.2": 98 | version "7.23.2" 99 | resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.23.2.tgz#329c7a06735e144a506bdb2cad0268b7f46f4ad8" 100 | integrity sha512-azpe59SQ48qG6nu2CzcMLbxUudtN+dOM9kDbUqGq3HXUJRlo7i8fvPoxQUzYgLZ4cMVmuZgm8vvBpNeRhd6XSw== 101 | dependencies: 102 | "@babel/code-frame" "^7.22.13" 103 | "@babel/generator" "^7.23.0" 104 | "@babel/helper-environment-visitor" "^7.22.20" 105 | "@babel/helper-function-name" "^7.23.0" 106 | "@babel/helper-hoist-variables" "^7.22.5" 107 | "@babel/helper-split-export-declaration" "^7.22.6" 108 | "@babel/parser" "^7.23.0" 109 | "@babel/types" "^7.23.0" 110 | debug "^4.1.0" 111 | globals "^11.1.0" 112 | 113 | "@babel/types@7.17.0": 114 | version "7.17.0" 115 | resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.17.0.tgz#a826e368bccb6b3d84acd76acad5c0d87342390b" 116 | integrity sha512-TmKSNO4D5rzhL5bjWFcVHHLETzfQ/AmbKpKPOSjlP0WoHZ6L911fgoOKY4Alp/emzG4cHJdyN49zpgkbXFEHHw== 117 | dependencies: 118 | "@babel/helper-validator-identifier" "^7.16.7" 119 | to-fast-properties "^2.0.0" 120 | 121 | "@babel/types@^7.17.0", "@babel/types@^7.22.5", "@babel/types@^7.23.0", "@babel/types@^7.23.6", "@babel/types@^7.24.0": 122 | version "7.24.0" 123 | resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.24.0.tgz#3b951f435a92e7333eba05b7566fd297960ea1bf" 124 | integrity sha512-+j7a5c253RfKh8iABBhywc8NSfP5LURe7Uh4qpsh6jc+aLJguvmIUBdjSdEMQv2bENrCR5MfRdjGo7vzS/ob7w== 125 | dependencies: 126 | "@babel/helper-string-parser" "^7.23.4" 127 | "@babel/helper-validator-identifier" "^7.22.20" 128 | to-fast-properties "^2.0.0" 129 | 130 | "@bull-board/api@5.16.0": 131 | version "5.16.0" 132 | resolved "https://registry.yarnpkg.com/@bull-board/api/-/api-5.16.0.tgz#3960e0c113df9794b9b8e0a6c5fdbd7e35543cad" 133 | integrity sha512-JnPi2M11At/4lxDP267k20AqUBZdVv+82nNMcO80dPJAEKpzbvhLqAl4H3nDSEowmzgzVVlD+/eFOMjg07h9Zg== 134 | dependencies: 135 | redis-info "^3.0.8" 136 | 137 | "@bull-board/express@^5.16.0": 138 | version "5.16.0" 139 | resolved "https://registry.yarnpkg.com/@bull-board/express/-/express-5.16.0.tgz#96741dcfc19422fe7b865071e12e38d1dea1bacc" 140 | integrity sha512-UNuJcLvkAI+KdHHlx6U4+0JWr1PrVrlJBFaAPckd0vjoLZT1KRjNNyTYEyOUi84Zwa0RYkHS4mOKDkZRYT8aww== 141 | dependencies: 142 | "@bull-board/api" "5.16.0" 143 | "@bull-board/ui" "5.16.0" 144 | ejs "^3.1.7" 145 | express "^4.17.3" 146 | 147 | "@bull-board/ui@5.16.0": 148 | version "5.16.0" 149 | resolved "https://registry.yarnpkg.com/@bull-board/ui/-/ui-5.16.0.tgz#d9c2684485e7c86d6de8c363d4926e6032babeb4" 150 | integrity sha512-XJoHVKUDWCjOb0kMTKs1wTqn5p9mvFUFurTVNcbhPuqkIPoPJLuKhE8a7FvpTIK+HA5NhFRllaRK3K9/Y8JzIA== 151 | dependencies: 152 | "@bull-board/api" "5.16.0" 153 | 154 | "@farcaster/core@0.14.4": 155 | version "0.14.4" 156 | resolved "https://registry.yarnpkg.com/@farcaster/core/-/core-0.14.4.tgz#ab0dc5acfa548a94f7054b573c00505fd1ae3dea" 157 | integrity sha512-SIh2yHYpPQsUaYbKhHk9zM63fYitqQhMOa3FDH7L5tmmfzzjnUgOWcihNkwINpjAYk8tre8Bgus54QfiJlQT0g== 158 | dependencies: 159 | "@noble/curves" "^1.0.0" 160 | "@noble/hashes" "^1.3.0" 161 | bs58 "^5.0.0" 162 | neverthrow "^6.0.0" 163 | viem "^1.12.2" 164 | 165 | "@farcaster/hub-nodejs@^0.11.0": 166 | version "0.11.4" 167 | resolved "https://registry.yarnpkg.com/@farcaster/hub-nodejs/-/hub-nodejs-0.11.4.tgz#d2385aa2ae3d568ae863af333819fc3d68d84bb9" 168 | integrity sha512-lcJ7/jTP/MawOWq8/2+MWB3Ze+wNb+8Skmja+ktaMjxOw9PulwoVUHMVO1rotc+gvrvhBEctxHzzIF3YhCfrQQ== 169 | dependencies: 170 | "@farcaster/core" "0.14.4" 171 | "@grpc/grpc-js" "~1.8.21" 172 | "@noble/hashes" "^1.3.0" 173 | neverthrow "^6.0.0" 174 | 175 | "@grpc/grpc-js@~1.8.21": 176 | version "1.8.21" 177 | resolved "https://registry.yarnpkg.com/@grpc/grpc-js/-/grpc-js-1.8.21.tgz#d282b122c71227859bf6c5866f4c40f4a2696513" 178 | integrity sha512-KeyQeZpxeEBSqFVTi3q2K7PiPXmgBfECc4updA1ejCLjYmoAlvvM3ZMp5ztTDUCUQmoY3CpDxvchjO1+rFkoHg== 179 | dependencies: 180 | "@grpc/proto-loader" "^0.7.0" 181 | "@types/node" ">=12.12.47" 182 | 183 | "@grpc/proto-loader@^0.7.0": 184 | version "0.7.10" 185 | resolved "https://registry.yarnpkg.com/@grpc/proto-loader/-/proto-loader-0.7.10.tgz#6bf26742b1b54d0a473067743da5d3189d06d720" 186 | integrity sha512-CAqDfoaQ8ykFd9zqBDn4k6iWT9loLAlc2ETmDFS9JCD70gDcnA4L3AFEo2iV7KyAtAAHFW9ftq1Fz+Vsgq80RQ== 187 | dependencies: 188 | lodash.camelcase "^4.3.0" 189 | long "^5.0.0" 190 | protobufjs "^7.2.4" 191 | yargs "^17.7.2" 192 | 193 | "@ioredis/commands@^1.1.1": 194 | version "1.2.0" 195 | resolved "https://registry.yarnpkg.com/@ioredis/commands/-/commands-1.2.0.tgz#6d61b3097470af1fdbbe622795b8921d42018e11" 196 | integrity sha512-Sx1pU8EM64o2BrqNpEO1CNLtKQwyhuXuqyfH7oGKCk+1a33d2r5saW8zNwm3j6BTExtjrv2BxTgzzkMwts6vGg== 197 | 198 | "@jridgewell/gen-mapping@^0.3.2": 199 | version "0.3.5" 200 | resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz#dcce6aff74bdf6dad1a95802b69b04a2fcb1fb36" 201 | integrity sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg== 202 | dependencies: 203 | "@jridgewell/set-array" "^1.2.1" 204 | "@jridgewell/sourcemap-codec" "^1.4.10" 205 | "@jridgewell/trace-mapping" "^0.3.24" 206 | 207 | "@jridgewell/resolve-uri@^3.1.0": 208 | version "3.1.2" 209 | resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz#7a0ee601f60f99a20c7c7c5ff0c80388c1189bd6" 210 | integrity sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw== 211 | 212 | "@jridgewell/set-array@^1.2.1": 213 | version "1.2.1" 214 | resolved "https://registry.yarnpkg.com/@jridgewell/set-array/-/set-array-1.2.1.tgz#558fb6472ed16a4c850b889530e6b36438c49280" 215 | integrity sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A== 216 | 217 | "@jridgewell/sourcemap-codec@^1.4.10", "@jridgewell/sourcemap-codec@^1.4.14": 218 | version "1.4.15" 219 | resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz#d7c6e6755c78567a951e04ab52ef0fd26de59f32" 220 | integrity sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg== 221 | 222 | "@jridgewell/trace-mapping@^0.3.17", "@jridgewell/trace-mapping@^0.3.24": 223 | version "0.3.25" 224 | resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz#15f190e98895f3fc23276ee14bc76b675c2e50f0" 225 | integrity sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ== 226 | dependencies: 227 | "@jridgewell/resolve-uri" "^3.1.0" 228 | "@jridgewell/sourcemap-codec" "^1.4.14" 229 | 230 | "@msgpackr-extract/msgpackr-extract-darwin-arm64@3.0.2": 231 | version "3.0.2" 232 | resolved "https://registry.yarnpkg.com/@msgpackr-extract/msgpackr-extract-darwin-arm64/-/msgpackr-extract-darwin-arm64-3.0.2.tgz#44d752c1a2dc113f15f781b7cc4f53a307e3fa38" 233 | integrity sha512-9bfjwDxIDWmmOKusUcqdS4Rw+SETlp9Dy39Xui9BEGEk19dDwH0jhipwFzEff/pFg95NKymc6TOTbRKcWeRqyQ== 234 | 235 | "@msgpackr-extract/msgpackr-extract-darwin-x64@3.0.2": 236 | version "3.0.2" 237 | resolved "https://registry.yarnpkg.com/@msgpackr-extract/msgpackr-extract-darwin-x64/-/msgpackr-extract-darwin-x64-3.0.2.tgz#f954f34355712212a8e06c465bc06c40852c6bb3" 238 | integrity sha512-lwriRAHm1Yg4iDf23Oxm9n/t5Zpw1lVnxYU3HnJPTi2lJRkKTrps1KVgvL6m7WvmhYVt/FIsssWay+k45QHeuw== 239 | 240 | "@msgpackr-extract/msgpackr-extract-linux-arm64@3.0.2": 241 | version "3.0.2" 242 | resolved "https://registry.yarnpkg.com/@msgpackr-extract/msgpackr-extract-linux-arm64/-/msgpackr-extract-linux-arm64-3.0.2.tgz#45c63037f045c2b15c44f80f0393fa24f9655367" 243 | integrity sha512-FU20Bo66/f7He9Fp9sP2zaJ1Q8L9uLPZQDub/WlUip78JlPeMbVL8546HbZfcW9LNciEXc8d+tThSJjSC+tmsg== 244 | 245 | "@msgpackr-extract/msgpackr-extract-linux-arm@3.0.2": 246 | version "3.0.2" 247 | resolved "https://registry.yarnpkg.com/@msgpackr-extract/msgpackr-extract-linux-arm/-/msgpackr-extract-linux-arm-3.0.2.tgz#35707efeafe6d22b3f373caf9e8775e8920d1399" 248 | integrity sha512-MOI9Dlfrpi2Cuc7i5dXdxPbFIgbDBGgKR5F2yWEa6FVEtSWncfVNKW5AKjImAQ6CZlBK9tympdsZJ2xThBiWWA== 249 | 250 | "@msgpackr-extract/msgpackr-extract-linux-x64@3.0.2": 251 | version "3.0.2" 252 | resolved "https://registry.yarnpkg.com/@msgpackr-extract/msgpackr-extract-linux-x64/-/msgpackr-extract-linux-x64-3.0.2.tgz#091b1218b66c341f532611477ef89e83f25fae4f" 253 | integrity sha512-gsWNDCklNy7Ajk0vBBf9jEx04RUxuDQfBse918Ww+Qb9HCPoGzS+XJTLe96iN3BVK7grnLiYghP/M4L8VsaHeA== 254 | 255 | "@msgpackr-extract/msgpackr-extract-win32-x64@3.0.2": 256 | version "3.0.2" 257 | resolved "https://registry.yarnpkg.com/@msgpackr-extract/msgpackr-extract-win32-x64/-/msgpackr-extract-win32-x64-3.0.2.tgz#0f164b726869f71da3c594171df5ebc1c4b0a407" 258 | integrity sha512-O+6Gs8UeDbyFpbSh2CPEz/UOrrdWPTBYNblZK5CxxLisYt4kGX3Sc+czffFonyjiGSq3jWLwJS/CCJc7tBr4sQ== 259 | 260 | "@noble/curves@1.2.0", "@noble/curves@~1.2.0": 261 | version "1.2.0" 262 | resolved "https://registry.yarnpkg.com/@noble/curves/-/curves-1.2.0.tgz#92d7e12e4e49b23105a2555c6984d41733d65c35" 263 | integrity sha512-oYclrNgRaM9SsBUBVbb8M6DTV7ZHRTKugureoYEncY5c65HOmRzvSiTE3y5CYaPYJA/GVkrhXEoF0M3Ya9PMnw== 264 | dependencies: 265 | "@noble/hashes" "1.3.2" 266 | 267 | "@noble/curves@^1.0.0": 268 | version "1.3.0" 269 | resolved "https://registry.yarnpkg.com/@noble/curves/-/curves-1.3.0.tgz#01be46da4fd195822dab821e72f71bf4aeec635e" 270 | integrity sha512-t01iSXPuN+Eqzb4eBX0S5oubSqXbK/xXa1Ne18Hj8f9pStxztHCE2gfboSp/dZRLSqfuLpRK2nDXDK+W9puocA== 271 | dependencies: 272 | "@noble/hashes" "1.3.3" 273 | 274 | "@noble/hashes@1.3.2": 275 | version "1.3.2" 276 | resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.3.2.tgz#6f26dbc8fbc7205873ce3cee2f690eba0d421b39" 277 | integrity sha512-MVC8EAQp7MvEcm30KWENFjgR+Mkmf+D189XJTkFIlwohU5hcBbn1ZkKq7KVTi2Hme3PMGF390DaL52beVrIihQ== 278 | 279 | "@noble/hashes@1.3.3", "@noble/hashes@^1.3.0", "@noble/hashes@~1.3.0", "@noble/hashes@~1.3.2": 280 | version "1.3.3" 281 | resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.3.3.tgz#39908da56a4adc270147bb07968bf3b16cfe1699" 282 | integrity sha512-V7/fPHgl+jsVPXqqeOzT8egNj2iBIVt+ECeMMG8TdcnTikP3oaBtUVqpT/gYCR68aEBJSF+XbYUxStjbFMqIIA== 283 | 284 | "@protobufjs/aspromise@^1.1.1", "@protobufjs/aspromise@^1.1.2": 285 | version "1.1.2" 286 | resolved "https://registry.yarnpkg.com/@protobufjs/aspromise/-/aspromise-1.1.2.tgz#9b8b0cc663d669a7d8f6f5d0893a14d348f30fbf" 287 | integrity sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ== 288 | 289 | "@protobufjs/base64@^1.1.2": 290 | version "1.1.2" 291 | resolved "https://registry.yarnpkg.com/@protobufjs/base64/-/base64-1.1.2.tgz#4c85730e59b9a1f1f349047dbf24296034bb2735" 292 | integrity sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg== 293 | 294 | "@protobufjs/codegen@^2.0.4": 295 | version "2.0.4" 296 | resolved "https://registry.yarnpkg.com/@protobufjs/codegen/-/codegen-2.0.4.tgz#7ef37f0d010fb028ad1ad59722e506d9262815cb" 297 | integrity sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg== 298 | 299 | "@protobufjs/eventemitter@^1.1.0": 300 | version "1.1.0" 301 | resolved "https://registry.yarnpkg.com/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz#355cbc98bafad5978f9ed095f397621f1d066b70" 302 | integrity sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q== 303 | 304 | "@protobufjs/fetch@^1.1.0": 305 | version "1.1.0" 306 | resolved "https://registry.yarnpkg.com/@protobufjs/fetch/-/fetch-1.1.0.tgz#ba99fb598614af65700c1619ff06d454b0d84c45" 307 | integrity sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ== 308 | dependencies: 309 | "@protobufjs/aspromise" "^1.1.1" 310 | "@protobufjs/inquire" "^1.1.0" 311 | 312 | "@protobufjs/float@^1.0.2": 313 | version "1.0.2" 314 | resolved "https://registry.yarnpkg.com/@protobufjs/float/-/float-1.0.2.tgz#5e9e1abdcb73fc0a7cb8b291df78c8cbd97b87d1" 315 | integrity sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ== 316 | 317 | "@protobufjs/inquire@^1.1.0": 318 | version "1.1.0" 319 | resolved "https://registry.yarnpkg.com/@protobufjs/inquire/-/inquire-1.1.0.tgz#ff200e3e7cf2429e2dcafc1140828e8cc638f089" 320 | integrity sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q== 321 | 322 | "@protobufjs/path@^1.1.2": 323 | version "1.1.2" 324 | resolved "https://registry.yarnpkg.com/@protobufjs/path/-/path-1.1.2.tgz#6cc2b20c5c9ad6ad0dccfd21ca7673d8d7fbf68d" 325 | integrity sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA== 326 | 327 | "@protobufjs/pool@^1.1.0": 328 | version "1.1.0" 329 | resolved "https://registry.yarnpkg.com/@protobufjs/pool/-/pool-1.1.0.tgz#09fd15f2d6d3abfa9b65bc366506d6ad7846ff54" 330 | integrity sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw== 331 | 332 | "@protobufjs/utf8@^1.1.0": 333 | version "1.1.0" 334 | resolved "https://registry.yarnpkg.com/@protobufjs/utf8/-/utf8-1.1.0.tgz#a777360b5b39a1a2e5106f8e858f2fd2d060c570" 335 | integrity sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw== 336 | 337 | "@scure/base@~1.1.0", "@scure/base@~1.1.2": 338 | version "1.1.5" 339 | resolved "https://registry.yarnpkg.com/@scure/base/-/base-1.1.5.tgz#1d85d17269fe97694b9c592552dd9e5e33552157" 340 | integrity sha512-Brj9FiG2W1MRQSTB212YVPRrcbjkv48FoZi/u4l/zds/ieRrqsh7aUf6CLwkAq61oKXr/ZlTzlY66gLIj3TFTQ== 341 | 342 | "@scure/bip32@1.3.2": 343 | version "1.3.2" 344 | resolved "https://registry.yarnpkg.com/@scure/bip32/-/bip32-1.3.2.tgz#90e78c027d5e30f0b22c1f8d50ff12f3fb7559f8" 345 | integrity sha512-N1ZhksgwD3OBlwTv3R6KFEcPojl/W4ElJOeCZdi+vuI5QmTFwLq3OFf2zd2ROpKvxFdgZ6hUpb0dx9bVNEwYCA== 346 | dependencies: 347 | "@noble/curves" "~1.2.0" 348 | "@noble/hashes" "~1.3.2" 349 | "@scure/base" "~1.1.2" 350 | 351 | "@scure/bip39@1.2.1": 352 | version "1.2.1" 353 | resolved "https://registry.yarnpkg.com/@scure/bip39/-/bip39-1.2.1.tgz#5cee8978656b272a917b7871c981e0541ad6ac2a" 354 | integrity sha512-Z3/Fsz1yr904dduJD0NpiyRHhRYHdcnyh73FZWiV+/qhWi83wNJ3NWolYqCEN+ZWsUz2TWwajJggcRE9r1zUYg== 355 | dependencies: 356 | "@noble/hashes" "~1.3.0" 357 | "@scure/base" "~1.1.0" 358 | 359 | "@trivago/prettier-plugin-sort-imports@^4.3.0": 360 | version "4.3.0" 361 | resolved "https://registry.yarnpkg.com/@trivago/prettier-plugin-sort-imports/-/prettier-plugin-sort-imports-4.3.0.tgz#725f411646b3942193a37041c84e0b2116339789" 362 | integrity sha512-r3n0onD3BTOVUNPhR4lhVK4/pABGpbA7bW3eumZnYdKaHkf1qEC+Mag6DPbGNuuh0eG8AaYj+YqmVHSiGslaTQ== 363 | dependencies: 364 | "@babel/generator" "7.17.7" 365 | "@babel/parser" "^7.20.5" 366 | "@babel/traverse" "7.23.2" 367 | "@babel/types" "7.17.0" 368 | javascript-natural-sort "0.7.1" 369 | lodash "^4.17.21" 370 | 371 | "@types/body-parser@*": 372 | version "1.19.5" 373 | resolved "https://registry.yarnpkg.com/@types/body-parser/-/body-parser-1.19.5.tgz#04ce9a3b677dc8bd681a17da1ab9835dc9d3ede4" 374 | integrity sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg== 375 | dependencies: 376 | "@types/connect" "*" 377 | "@types/node" "*" 378 | 379 | "@types/cli-progress@^3.11.5": 380 | version "3.11.5" 381 | resolved "https://registry.yarnpkg.com/@types/cli-progress/-/cli-progress-3.11.5.tgz#9518c745e78557efda057e3f96a5990c717268c3" 382 | integrity sha512-D4PbNRbviKyppS5ivBGyFO29POlySLmA2HyUFE4p5QGazAMM3CwkKWcvTl8gvElSuxRh6FPKL8XmidX873ou4g== 383 | dependencies: 384 | "@types/node" "*" 385 | 386 | "@types/connect@*": 387 | version "3.4.38" 388 | resolved "https://registry.yarnpkg.com/@types/connect/-/connect-3.4.38.tgz#5ba7f3bc4fbbdeaff8dded952e5ff2cc53f8d858" 389 | integrity sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug== 390 | dependencies: 391 | "@types/node" "*" 392 | 393 | "@types/express-serve-static-core@^4.17.33": 394 | version "4.19.0" 395 | resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-4.19.0.tgz#3ae8ab3767d98d0b682cda063c3339e1e86ccfaa" 396 | integrity sha512-bGyep3JqPCRry1wq+O5n7oiBgGWmeIJXPjXXCo8EK0u8duZGSYar7cGqd3ML2JUsLGeB7fmc06KYo9fLGWqPvQ== 397 | dependencies: 398 | "@types/node" "*" 399 | "@types/qs" "*" 400 | "@types/range-parser" "*" 401 | "@types/send" "*" 402 | 403 | "@types/express@^4.17.21": 404 | version "4.17.21" 405 | resolved "https://registry.yarnpkg.com/@types/express/-/express-4.17.21.tgz#c26d4a151e60efe0084b23dc3369ebc631ed192d" 406 | integrity sha512-ejlPM315qwLpaQlQDTjPdsUFSc6ZsP4AN6AlWnogPjQ7CVi7PYF3YVz+CY3jE2pwYf7E/7HlDAN0rV2GxTG0HQ== 407 | dependencies: 408 | "@types/body-parser" "*" 409 | "@types/express-serve-static-core" "^4.17.33" 410 | "@types/qs" "*" 411 | "@types/serve-static" "*" 412 | 413 | "@types/http-errors@*": 414 | version "2.0.4" 415 | resolved "https://registry.yarnpkg.com/@types/http-errors/-/http-errors-2.0.4.tgz#7eb47726c391b7345a6ec35ad7f4de469cf5ba4f" 416 | integrity sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA== 417 | 418 | "@types/mime@^1": 419 | version "1.3.5" 420 | resolved "https://registry.yarnpkg.com/@types/mime/-/mime-1.3.5.tgz#1ef302e01cf7d2b5a0fa526790c9123bf1d06690" 421 | integrity sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w== 422 | 423 | "@types/node@*", "@types/node@>=12.12.47", "@types/node@>=13.7.0", "@types/node@^20.11.19": 424 | version "20.11.24" 425 | resolved "https://registry.yarnpkg.com/@types/node/-/node-20.11.24.tgz#cc207511104694e84e9fb17f9a0c4c42d4517792" 426 | integrity sha512-Kza43ewS3xoLgCEpQrsT+xRo/EJej1y0kVYGiLFE1NEODXGzTfwiC6tXTLMQskn1X4/Rjlh0MQUvx9W+L9long== 427 | dependencies: 428 | undici-types "~5.26.4" 429 | 430 | "@types/pg-pool@^2.0.6": 431 | version "2.0.6" 432 | resolved "https://registry.yarnpkg.com/@types/pg-pool/-/pg-pool-2.0.6.tgz#1376d9dc5aec4bb2ec67ce28d7e9858227403c77" 433 | integrity sha512-TaAUE5rq2VQYxab5Ts7WZhKNmuN78Q6PiFonTDdpbx8a1H0M1vhy3rhiMjl+e2iHmogyMw7jZF4FrE6eJUy5HQ== 434 | dependencies: 435 | "@types/pg" "*" 436 | 437 | "@types/pg@*": 438 | version "8.11.2" 439 | resolved "https://registry.yarnpkg.com/@types/pg/-/pg-8.11.2.tgz#e5c306601d2e0cc54c0801cc61a41761c8a95c92" 440 | integrity sha512-G2Mjygf2jFMU/9hCaTYxJrwdObdcnuQde1gndooZSOHsNSaCehAuwc7EIuSA34Do8Jx2yZ19KtvW8P0j4EuUXw== 441 | dependencies: 442 | "@types/node" "*" 443 | pg-protocol "*" 444 | pg-types "^4.0.1" 445 | 446 | "@types/qs@*": 447 | version "6.9.15" 448 | resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.9.15.tgz#adde8a060ec9c305a82de1babc1056e73bd64dce" 449 | integrity sha512-uXHQKES6DQKKCLh441Xv/dwxOq1TVS3JPUMlEqoEglvlhR6Mxnlew/Xq/LRVHpLyk7iK3zODe1qYHIMltO7XGg== 450 | 451 | "@types/range-parser@*": 452 | version "1.2.7" 453 | resolved "https://registry.yarnpkg.com/@types/range-parser/-/range-parser-1.2.7.tgz#50ae4353eaaddc04044279812f52c8c65857dbcb" 454 | integrity sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ== 455 | 456 | "@types/send@*": 457 | version "0.17.4" 458 | resolved "https://registry.yarnpkg.com/@types/send/-/send-0.17.4.tgz#6619cd24e7270793702e4e6a4b958a9010cfc57a" 459 | integrity sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA== 460 | dependencies: 461 | "@types/mime" "^1" 462 | "@types/node" "*" 463 | 464 | "@types/serve-static@*": 465 | version "1.15.7" 466 | resolved "https://registry.yarnpkg.com/@types/serve-static/-/serve-static-1.15.7.tgz#22174bbd74fb97fe303109738e9b5c2f3064f714" 467 | integrity sha512-W8Ym+h8nhuRwaKPaDw34QUkwsGi6Rc4yYqvKFo5rm2FUEhCFbzVWrxXUxuKK8TASjWsysJY0nsmNCGhCOIsrOw== 468 | dependencies: 469 | "@types/http-errors" "*" 470 | "@types/node" "*" 471 | "@types/send" "*" 472 | 473 | abitype@0.9.8: 474 | version "0.9.8" 475 | resolved "https://registry.yarnpkg.com/abitype/-/abitype-0.9.8.tgz#1f120b6b717459deafd213dfbf3a3dd1bf10ae8c" 476 | integrity sha512-puLifILdm+8sjyss4S+fsUN09obiT1g2YW6CtcQF+QDzxR0euzgEB29MZujC6zMk2a6SVmtttq1fc6+YFA7WYQ== 477 | 478 | abort-controller@^3.0.0: 479 | version "3.0.0" 480 | resolved "https://registry.yarnpkg.com/abort-controller/-/abort-controller-3.0.0.tgz#eaf54d53b62bae4138e809ca225c8439a6efb392" 481 | integrity sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg== 482 | dependencies: 483 | event-target-shim "^5.0.0" 484 | 485 | accepts@~1.3.8: 486 | version "1.3.8" 487 | resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.8.tgz#0bf0be125b67014adcb0b0921e62db7bffe16b2e" 488 | integrity sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw== 489 | dependencies: 490 | mime-types "~2.1.34" 491 | negotiator "0.6.3" 492 | 493 | ansi-regex@^5.0.1: 494 | version "5.0.1" 495 | resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" 496 | integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== 497 | 498 | ansi-styles@^3.2.1: 499 | version "3.2.1" 500 | resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" 501 | integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA== 502 | dependencies: 503 | color-convert "^1.9.0" 504 | 505 | ansi-styles@^4.0.0, ansi-styles@^4.1.0: 506 | version "4.3.0" 507 | resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937" 508 | integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== 509 | dependencies: 510 | color-convert "^2.0.1" 511 | 512 | array-flatten@1.1.1: 513 | version "1.1.1" 514 | resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2" 515 | integrity sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg== 516 | 517 | async@^3.2.3: 518 | version "3.2.5" 519 | resolved "https://registry.yarnpkg.com/async/-/async-3.2.5.tgz#ebd52a8fdaf7a2289a24df399f8d8485c8a46b66" 520 | integrity sha512-baNZyqaaLhyLVKm/DlvdW051MSgO6b8eVfIezl9E5PqWxFgzLm/wQntEW4zOytVburDEr0JlALEpdOFwvErLsg== 521 | 522 | atomic-sleep@^1.0.0: 523 | version "1.0.0" 524 | resolved "https://registry.yarnpkg.com/atomic-sleep/-/atomic-sleep-1.0.0.tgz#eb85b77a601fc932cfe432c5acd364a9e2c9075b" 525 | integrity sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ== 526 | 527 | balanced-match@^1.0.0: 528 | version "1.0.2" 529 | resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" 530 | integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== 531 | 532 | base-x@^4.0.0: 533 | version "4.0.0" 534 | resolved "https://registry.yarnpkg.com/base-x/-/base-x-4.0.0.tgz#d0e3b7753450c73f8ad2389b5c018a4af7b2224a" 535 | integrity sha512-FuwxlW4H5kh37X/oW59pwTzzTKRzfrrQwhmyspRM7swOEZcHtDZSCt45U6oKgtuFE+WYPblePMVIPR4RZrh/hw== 536 | 537 | base64-js@^1.3.1: 538 | version "1.5.1" 539 | resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" 540 | integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== 541 | 542 | body-parser@1.20.2: 543 | version "1.20.2" 544 | resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.20.2.tgz#6feb0e21c4724d06de7ff38da36dad4f57a747fd" 545 | integrity sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA== 546 | dependencies: 547 | bytes "3.1.2" 548 | content-type "~1.0.5" 549 | debug "2.6.9" 550 | depd "2.0.0" 551 | destroy "1.2.0" 552 | http-errors "2.0.0" 553 | iconv-lite "0.4.24" 554 | on-finished "2.4.1" 555 | qs "6.11.0" 556 | raw-body "2.5.2" 557 | type-is "~1.6.18" 558 | unpipe "1.0.0" 559 | 560 | brace-expansion@^1.1.7: 561 | version "1.1.11" 562 | resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" 563 | integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== 564 | dependencies: 565 | balanced-match "^1.0.0" 566 | concat-map "0.0.1" 567 | 568 | brace-expansion@^2.0.1: 569 | version "2.0.1" 570 | resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-2.0.1.tgz#1edc459e0f0c548486ecf9fc99f2221364b9a0ae" 571 | integrity sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA== 572 | dependencies: 573 | balanced-match "^1.0.0" 574 | 575 | bs58@^5.0.0: 576 | version "5.0.0" 577 | resolved "https://registry.yarnpkg.com/bs58/-/bs58-5.0.0.tgz#865575b4d13c09ea2a84622df6c8cbeb54ffc279" 578 | integrity sha512-r+ihvQJvahgYT50JD05dyJNKlmmSlMoOGwn1lCcEzanPglg7TxYjioQUYehQ9mAR/+hOSd2jRc/Z2y5UxBymvQ== 579 | dependencies: 580 | base-x "^4.0.0" 581 | 582 | buffer-writer@2.0.0: 583 | version "2.0.0" 584 | resolved "https://registry.yarnpkg.com/buffer-writer/-/buffer-writer-2.0.0.tgz#ce7eb81a38f7829db09c873f2fbb792c0c98ec04" 585 | integrity sha512-a7ZpuTZU1TRtnwyCNW3I5dc0wWNC3VR9S++Ewyk2HHZdrO3CQJqSpd+95Us590V6AL7JqUAH2IwZ/398PmNFgw== 586 | 587 | buffer@^6.0.3: 588 | version "6.0.3" 589 | resolved "https://registry.yarnpkg.com/buffer/-/buffer-6.0.3.tgz#2ace578459cc8fbe2a70aaa8f52ee63b6a74c6c6" 590 | integrity sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA== 591 | dependencies: 592 | base64-js "^1.3.1" 593 | ieee754 "^1.2.1" 594 | 595 | bullmq@^5.7.6: 596 | version "5.7.6" 597 | resolved "https://registry.yarnpkg.com/bullmq/-/bullmq-5.7.6.tgz#bb5b86d2ecf99c41747e4fc0010466cac09d8adb" 598 | integrity sha512-bDTNqqNDrmUGHcA5lw6tcestchS4vV7/VkTyywybvOiXGtkGZCyjuziBzCNwDYMKibPpWmKySe5yTJsEb5bIlw== 599 | dependencies: 600 | cron-parser "^4.6.0" 601 | ioredis "^5.3.2" 602 | msgpackr "^1.10.1" 603 | node-abort-controller "^3.1.1" 604 | semver "^7.5.4" 605 | tslib "^2.0.0" 606 | uuid "^9.0.0" 607 | 608 | bytes@3.1.2: 609 | version "3.1.2" 610 | resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.2.tgz#8b0beeb98605adf1b128fa4386403c009e0221a5" 611 | integrity sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg== 612 | 613 | call-bind@^1.0.7: 614 | version "1.0.7" 615 | resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.7.tgz#06016599c40c56498c18769d2730be242b6fa3b9" 616 | integrity sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w== 617 | dependencies: 618 | es-define-property "^1.0.0" 619 | es-errors "^1.3.0" 620 | function-bind "^1.1.2" 621 | get-intrinsic "^1.2.4" 622 | set-function-length "^1.2.1" 623 | 624 | chalk@^2.4.2: 625 | version "2.4.2" 626 | resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" 627 | integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== 628 | dependencies: 629 | ansi-styles "^3.2.1" 630 | escape-string-regexp "^1.0.5" 631 | supports-color "^5.3.0" 632 | 633 | chalk@^4.0.2: 634 | version "4.1.2" 635 | resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" 636 | integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== 637 | dependencies: 638 | ansi-styles "^4.1.0" 639 | supports-color "^7.1.0" 640 | 641 | cli-progress@^3.12.0: 642 | version "3.12.0" 643 | resolved "https://registry.yarnpkg.com/cli-progress/-/cli-progress-3.12.0.tgz#807ee14b66bcc086258e444ad0f19e7d42577942" 644 | integrity sha512-tRkV3HJ1ASwm19THiiLIXLO7Im7wlTuKnvkYaTkyoAPefqjNg7W7DHKUlGRxy9vxDvbyCYQkQozvptuMkGCg8A== 645 | dependencies: 646 | string-width "^4.2.3" 647 | 648 | cliui@^8.0.1: 649 | version "8.0.1" 650 | resolved "https://registry.yarnpkg.com/cliui/-/cliui-8.0.1.tgz#0c04b075db02cbfe60dc8e6cf2f5486b1a3608aa" 651 | integrity sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ== 652 | dependencies: 653 | string-width "^4.2.0" 654 | strip-ansi "^6.0.1" 655 | wrap-ansi "^7.0.0" 656 | 657 | cluster-key-slot@^1.1.0: 658 | version "1.1.2" 659 | resolved "https://registry.yarnpkg.com/cluster-key-slot/-/cluster-key-slot-1.1.2.tgz#88ddaa46906e303b5de30d3153b7d9fe0a0c19ac" 660 | integrity sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA== 661 | 662 | color-convert@^1.9.0: 663 | version "1.9.3" 664 | resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" 665 | integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg== 666 | dependencies: 667 | color-name "1.1.3" 668 | 669 | color-convert@^2.0.1: 670 | version "2.0.1" 671 | resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" 672 | integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== 673 | dependencies: 674 | color-name "~1.1.4" 675 | 676 | color-name@1.1.3: 677 | version "1.1.3" 678 | resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" 679 | integrity sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw== 680 | 681 | color-name@~1.1.4: 682 | version "1.1.4" 683 | resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" 684 | integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== 685 | 686 | colorette@^2.0.7: 687 | version "2.0.20" 688 | resolved "https://registry.yarnpkg.com/colorette/-/colorette-2.0.20.tgz#9eb793e6833067f7235902fcd3b09917a000a95a" 689 | integrity sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w== 690 | 691 | concat-map@0.0.1: 692 | version "0.0.1" 693 | resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" 694 | integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== 695 | 696 | content-disposition@0.5.4: 697 | version "0.5.4" 698 | resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.4.tgz#8b82b4efac82512a02bb0b1dcec9d2c5e8eb5bfe" 699 | integrity sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ== 700 | dependencies: 701 | safe-buffer "5.2.1" 702 | 703 | content-type@~1.0.4, content-type@~1.0.5: 704 | version "1.0.5" 705 | resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.5.tgz#8b773162656d1d1086784c8f23a54ce6d73d7918" 706 | integrity sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA== 707 | 708 | cookie-signature@1.0.6: 709 | version "1.0.6" 710 | resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c" 711 | integrity sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ== 712 | 713 | cookie@0.6.0: 714 | version "0.6.0" 715 | resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.6.0.tgz#2798b04b071b0ecbff0dbb62a505a8efa4e19051" 716 | integrity sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw== 717 | 718 | cron-parser@^4.6.0: 719 | version "4.9.0" 720 | resolved "https://registry.yarnpkg.com/cron-parser/-/cron-parser-4.9.0.tgz#0340694af3e46a0894978c6f52a6dbb5c0f11ad5" 721 | integrity sha512-p0SaNjrHOnQeR8/VnfGbmg9te2kfyYSQ7Sc/j/6DtPL3JQvKxmjO9TSjNFpujqV3vEYYBvNNvXSxzyksBWAx1Q== 722 | dependencies: 723 | luxon "^3.2.1" 724 | 725 | dateformat@^4.6.3: 726 | version "4.6.3" 727 | resolved "https://registry.yarnpkg.com/dateformat/-/dateformat-4.6.3.tgz#556fa6497e5217fedb78821424f8a1c22fa3f4b5" 728 | integrity sha512-2P0p0pFGzHS5EMnhdxQi7aJN+iMheud0UhG4dlE1DLAlvL8JHjJJTX/CSm4JXwV0Ka5nGk3zC5mcb5bUQUxxMA== 729 | 730 | debug@2.6.9: 731 | version "2.6.9" 732 | resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" 733 | integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== 734 | dependencies: 735 | ms "2.0.0" 736 | 737 | debug@^4.1.0, debug@^4.3.4: 738 | version "4.3.4" 739 | resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" 740 | integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== 741 | dependencies: 742 | ms "2.1.2" 743 | 744 | define-data-property@^1.1.4: 745 | version "1.1.4" 746 | resolved "https://registry.yarnpkg.com/define-data-property/-/define-data-property-1.1.4.tgz#894dc141bb7d3060ae4366f6a0107e68fbe48c5e" 747 | integrity sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A== 748 | dependencies: 749 | es-define-property "^1.0.0" 750 | es-errors "^1.3.0" 751 | gopd "^1.0.1" 752 | 753 | denque@^2.1.0: 754 | version "2.1.0" 755 | resolved "https://registry.yarnpkg.com/denque/-/denque-2.1.0.tgz#e93e1a6569fb5e66f16a3c2a2964617d349d6ab1" 756 | integrity sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw== 757 | 758 | depd@2.0.0: 759 | version "2.0.0" 760 | resolved "https://registry.yarnpkg.com/depd/-/depd-2.0.0.tgz#b696163cc757560d09cf22cc8fad1571b79e76df" 761 | integrity sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw== 762 | 763 | destroy@1.2.0: 764 | version "1.2.0" 765 | resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.2.0.tgz#4803735509ad8be552934c67df614f94e66fa015" 766 | integrity sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg== 767 | 768 | dotenv@^16.4.4: 769 | version "16.4.5" 770 | resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.4.5.tgz#cdd3b3b604cb327e286b4762e13502f717cb099f" 771 | integrity sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg== 772 | 773 | ee-first@1.1.1: 774 | version "1.1.1" 775 | resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" 776 | integrity sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow== 777 | 778 | ejs@^3.1.7: 779 | version "3.1.10" 780 | resolved "https://registry.yarnpkg.com/ejs/-/ejs-3.1.10.tgz#69ab8358b14e896f80cc39e62087b88500c3ac3b" 781 | integrity sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA== 782 | dependencies: 783 | jake "^10.8.5" 784 | 785 | emoji-regex@^8.0.0: 786 | version "8.0.0" 787 | resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" 788 | integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== 789 | 790 | encodeurl@~1.0.2: 791 | version "1.0.2" 792 | resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59" 793 | integrity sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w== 794 | 795 | end-of-stream@^1.1.0: 796 | version "1.4.4" 797 | resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0" 798 | integrity sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q== 799 | dependencies: 800 | once "^1.4.0" 801 | 802 | es-define-property@^1.0.0: 803 | version "1.0.0" 804 | resolved "https://registry.yarnpkg.com/es-define-property/-/es-define-property-1.0.0.tgz#c7faefbdff8b2696cf5f46921edfb77cc4ba3845" 805 | integrity sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ== 806 | dependencies: 807 | get-intrinsic "^1.2.4" 808 | 809 | es-errors@^1.3.0: 810 | version "1.3.0" 811 | resolved "https://registry.yarnpkg.com/es-errors/-/es-errors-1.3.0.tgz#05f75a25dab98e4fb1dcd5e1472c0546d5057c8f" 812 | integrity sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw== 813 | 814 | escalade@^3.1.1: 815 | version "3.1.2" 816 | resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.2.tgz#54076e9ab29ea5bf3d8f1ed62acffbb88272df27" 817 | integrity sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA== 818 | 819 | escape-html@~1.0.3: 820 | version "1.0.3" 821 | resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" 822 | integrity sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow== 823 | 824 | escape-string-regexp@^1.0.5: 825 | version "1.0.5" 826 | resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" 827 | integrity sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg== 828 | 829 | etag@~1.8.1: 830 | version "1.8.1" 831 | resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887" 832 | integrity sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg== 833 | 834 | event-target-shim@^5.0.0: 835 | version "5.0.1" 836 | resolved "https://registry.yarnpkg.com/event-target-shim/-/event-target-shim-5.0.1.tgz#5d4d3ebdf9583d63a5333ce2deb7480ab2b05789" 837 | integrity sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ== 838 | 839 | events@^3.3.0: 840 | version "3.3.0" 841 | resolved "https://registry.yarnpkg.com/events/-/events-3.3.0.tgz#31a95ad0a924e2d2c419a813aeb2c4e878ea7400" 842 | integrity sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q== 843 | 844 | express@^4.17.3, express@^4.19.2: 845 | version "4.19.2" 846 | resolved "https://registry.yarnpkg.com/express/-/express-4.19.2.tgz#e25437827a3aa7f2a827bc8171bbbb664a356465" 847 | integrity sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q== 848 | dependencies: 849 | accepts "~1.3.8" 850 | array-flatten "1.1.1" 851 | body-parser "1.20.2" 852 | content-disposition "0.5.4" 853 | content-type "~1.0.4" 854 | cookie "0.6.0" 855 | cookie-signature "1.0.6" 856 | debug "2.6.9" 857 | depd "2.0.0" 858 | encodeurl "~1.0.2" 859 | escape-html "~1.0.3" 860 | etag "~1.8.1" 861 | finalhandler "1.2.0" 862 | fresh "0.5.2" 863 | http-errors "2.0.0" 864 | merge-descriptors "1.0.1" 865 | methods "~1.1.2" 866 | on-finished "2.4.1" 867 | parseurl "~1.3.3" 868 | path-to-regexp "0.1.7" 869 | proxy-addr "~2.0.7" 870 | qs "6.11.0" 871 | range-parser "~1.2.1" 872 | safe-buffer "5.2.1" 873 | send "0.18.0" 874 | serve-static "1.15.0" 875 | setprototypeof "1.2.0" 876 | statuses "2.0.1" 877 | type-is "~1.6.18" 878 | utils-merge "1.0.1" 879 | vary "~1.1.2" 880 | 881 | fast-copy@^3.0.0: 882 | version "3.0.1" 883 | resolved "https://registry.yarnpkg.com/fast-copy/-/fast-copy-3.0.1.tgz#9e89ef498b8c04c1cd76b33b8e14271658a732aa" 884 | integrity sha512-Knr7NOtK3HWRYGtHoJrjkaWepqT8thIVGAwt0p0aUs1zqkAzXZV4vo9fFNwyb5fcqK1GKYFYxldQdIDVKhUAfA== 885 | 886 | fast-redact@^3.1.1: 887 | version "3.3.0" 888 | resolved "https://registry.yarnpkg.com/fast-redact/-/fast-redact-3.3.0.tgz#7c83ce3a7be4898241a46560d51de10f653f7634" 889 | integrity sha512-6T5V1QK1u4oF+ATxs1lWUmlEk6P2T9HqJG3e2DnHOdVgZy2rFJBoEnrIedcTXlkAHU/zKC+7KETJ+KGGKwxgMQ== 890 | 891 | fast-safe-stringify@^2.1.1: 892 | version "2.1.1" 893 | resolved "https://registry.yarnpkg.com/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz#c406a83b6e70d9e35ce3b30a81141df30aeba884" 894 | integrity sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA== 895 | 896 | filelist@^1.0.4: 897 | version "1.0.4" 898 | resolved "https://registry.yarnpkg.com/filelist/-/filelist-1.0.4.tgz#f78978a1e944775ff9e62e744424f215e58352b5" 899 | integrity sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q== 900 | dependencies: 901 | minimatch "^5.0.1" 902 | 903 | finalhandler@1.2.0: 904 | version "1.2.0" 905 | resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.2.0.tgz#7d23fe5731b207b4640e4fcd00aec1f9207a7b32" 906 | integrity sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg== 907 | dependencies: 908 | debug "2.6.9" 909 | encodeurl "~1.0.2" 910 | escape-html "~1.0.3" 911 | on-finished "2.4.1" 912 | parseurl "~1.3.3" 913 | statuses "2.0.1" 914 | unpipe "~1.0.0" 915 | 916 | forwarded@0.2.0: 917 | version "0.2.0" 918 | resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.2.0.tgz#2269936428aad4c15c7ebe9779a84bf0b2a81811" 919 | integrity sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow== 920 | 921 | fresh@0.5.2: 922 | version "0.5.2" 923 | resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7" 924 | integrity sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q== 925 | 926 | function-bind@^1.1.2: 927 | version "1.1.2" 928 | resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.2.tgz#2c02d864d97f3ea6c8830c464cbd11ab6eab7a1c" 929 | integrity sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA== 930 | 931 | get-caller-file@^2.0.5: 932 | version "2.0.5" 933 | resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" 934 | integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== 935 | 936 | get-intrinsic@^1.1.3, get-intrinsic@^1.2.4: 937 | version "1.2.4" 938 | resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.2.4.tgz#e385f5a4b5227d449c3eabbad05494ef0abbeadd" 939 | integrity sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ== 940 | dependencies: 941 | es-errors "^1.3.0" 942 | function-bind "^1.1.2" 943 | has-proto "^1.0.1" 944 | has-symbols "^1.0.3" 945 | hasown "^2.0.0" 946 | 947 | globals@^11.1.0: 948 | version "11.12.0" 949 | resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e" 950 | integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA== 951 | 952 | gopd@^1.0.1: 953 | version "1.0.1" 954 | resolved "https://registry.yarnpkg.com/gopd/-/gopd-1.0.1.tgz#29ff76de69dac7489b7c0918a5788e56477c332c" 955 | integrity sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA== 956 | dependencies: 957 | get-intrinsic "^1.1.3" 958 | 959 | has-flag@^3.0.0: 960 | version "3.0.0" 961 | resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" 962 | integrity sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw== 963 | 964 | has-flag@^4.0.0: 965 | version "4.0.0" 966 | resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" 967 | integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== 968 | 969 | has-property-descriptors@^1.0.2: 970 | version "1.0.2" 971 | resolved "https://registry.yarnpkg.com/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz#963ed7d071dc7bf5f084c5bfbe0d1b6222586854" 972 | integrity sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg== 973 | dependencies: 974 | es-define-property "^1.0.0" 975 | 976 | has-proto@^1.0.1: 977 | version "1.0.3" 978 | resolved "https://registry.yarnpkg.com/has-proto/-/has-proto-1.0.3.tgz#b31ddfe9b0e6e9914536a6ab286426d0214f77fd" 979 | integrity sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q== 980 | 981 | has-symbols@^1.0.3: 982 | version "1.0.3" 983 | resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.3.tgz#bb7b2c4349251dce87b125f7bdf874aa7c8b39f8" 984 | integrity sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A== 985 | 986 | hasown@^2.0.0: 987 | version "2.0.2" 988 | resolved "https://registry.yarnpkg.com/hasown/-/hasown-2.0.2.tgz#003eaf91be7adc372e84ec59dc37252cedb80003" 989 | integrity sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ== 990 | dependencies: 991 | function-bind "^1.1.2" 992 | 993 | help-me@^5.0.0: 994 | version "5.0.0" 995 | resolved "https://registry.yarnpkg.com/help-me/-/help-me-5.0.0.tgz#b1ebe63b967b74060027c2ac61f9be12d354a6f6" 996 | integrity sha512-7xgomUX6ADmcYzFik0HzAxh/73YlKR9bmFzf51CZwR+b6YtzU2m0u49hQCqV6SvlqIqsaxovfwdvbnsw3b/zpg== 997 | 998 | http-errors@2.0.0: 999 | version "2.0.0" 1000 | resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-2.0.0.tgz#b7774a1486ef73cf7667ac9ae0858c012c57b9d3" 1001 | integrity sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ== 1002 | dependencies: 1003 | depd "2.0.0" 1004 | inherits "2.0.4" 1005 | setprototypeof "1.2.0" 1006 | statuses "2.0.1" 1007 | toidentifier "1.0.1" 1008 | 1009 | iconv-lite@0.4.24: 1010 | version "0.4.24" 1011 | resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" 1012 | integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== 1013 | dependencies: 1014 | safer-buffer ">= 2.1.2 < 3" 1015 | 1016 | ieee754@^1.2.1: 1017 | version "1.2.1" 1018 | resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352" 1019 | integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA== 1020 | 1021 | inherits@2.0.4: 1022 | version "2.0.4" 1023 | resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" 1024 | integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== 1025 | 1026 | ioredis@^5.3.2, ioredis@^5.4.1: 1027 | version "5.4.1" 1028 | resolved "https://registry.yarnpkg.com/ioredis/-/ioredis-5.4.1.tgz#1c56b70b759f01465913887375ed809134296f40" 1029 | integrity sha512-2YZsvl7jopIa1gaePkeMtd9rAcSjOOjPtpcLlOeusyO+XH2SK5ZcT+UCrElPP+WVIInh2TzeI4XW9ENaSLVVHA== 1030 | dependencies: 1031 | "@ioredis/commands" "^1.1.1" 1032 | cluster-key-slot "^1.1.0" 1033 | debug "^4.3.4" 1034 | denque "^2.1.0" 1035 | lodash.defaults "^4.2.0" 1036 | lodash.isarguments "^3.1.0" 1037 | redis-errors "^1.2.0" 1038 | redis-parser "^3.0.0" 1039 | standard-as-callback "^2.1.0" 1040 | 1041 | ipaddr.js@1.9.1: 1042 | version "1.9.1" 1043 | resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.9.1.tgz#bff38543eeb8984825079ff3a2a8e6cbd46781b3" 1044 | integrity sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g== 1045 | 1046 | is-fullwidth-code-point@^3.0.0: 1047 | version "3.0.0" 1048 | resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" 1049 | integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== 1050 | 1051 | isows@1.0.3: 1052 | version "1.0.3" 1053 | resolved "https://registry.yarnpkg.com/isows/-/isows-1.0.3.tgz#93c1cf0575daf56e7120bab5c8c448b0809d0d74" 1054 | integrity sha512-2cKei4vlmg2cxEjm3wVSqn8pcoRF/LX/wpifuuNquFO4SQmPwarClT+SUCA2lt+l581tTeZIPIZuIDo2jWN1fg== 1055 | 1056 | jake@^10.8.5: 1057 | version "10.8.7" 1058 | resolved "https://registry.yarnpkg.com/jake/-/jake-10.8.7.tgz#63a32821177940c33f356e0ba44ff9d34e1c7d8f" 1059 | integrity sha512-ZDi3aP+fG/LchyBzUM804VjddnwfSfsdeYkwt8NcbKRvo4rFkjhs456iLFn3k2ZUWvNe4i48WACDbza8fhq2+w== 1060 | dependencies: 1061 | async "^3.2.3" 1062 | chalk "^4.0.2" 1063 | filelist "^1.0.4" 1064 | minimatch "^3.1.2" 1065 | 1066 | javascript-natural-sort@0.7.1: 1067 | version "0.7.1" 1068 | resolved "https://registry.yarnpkg.com/javascript-natural-sort/-/javascript-natural-sort-0.7.1.tgz#f9e2303d4507f6d74355a73664d1440fb5a0ef59" 1069 | integrity sha512-nO6jcEfZWQXDhOiBtG2KvKyEptz7RVbpGP4vTD2hLBdmNQSsCiicO2Ioinv6UI4y9ukqnBpy+XZ9H6uLNgJTlw== 1070 | 1071 | joycon@^3.1.1: 1072 | version "3.1.1" 1073 | resolved "https://registry.yarnpkg.com/joycon/-/joycon-3.1.1.tgz#bce8596d6ae808f8b68168f5fc69280996894f03" 1074 | integrity sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw== 1075 | 1076 | js-tokens@^4.0.0: 1077 | version "4.0.0" 1078 | resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" 1079 | integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== 1080 | 1081 | jsesc@^2.5.1: 1082 | version "2.5.2" 1083 | resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-2.5.2.tgz#80564d2e483dacf6e8ef209650a67df3f0c283a4" 1084 | integrity sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA== 1085 | 1086 | kysely@^0.27.2: 1087 | version "0.27.2" 1088 | resolved "https://registry.yarnpkg.com/kysely/-/kysely-0.27.2.tgz#b289ce5e561064ec613a17149b7155783d2b36de" 1089 | integrity sha512-DmRvEfiR/NLpgsTbSxma2ldekhsdcd65+MNiKXyd/qj7w7X5e3cLkXxcj+MypsRDjPhHQ/CD5u3Eq1sBYzX0bw== 1090 | 1091 | lodash.camelcase@^4.3.0: 1092 | version "4.3.0" 1093 | resolved "https://registry.yarnpkg.com/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz#b28aa6288a2b9fc651035c7711f65ab6190331a6" 1094 | integrity sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA== 1095 | 1096 | lodash.defaults@^4.2.0: 1097 | version "4.2.0" 1098 | resolved "https://registry.yarnpkg.com/lodash.defaults/-/lodash.defaults-4.2.0.tgz#d09178716ffea4dde9e5fb7b37f6f0802274580c" 1099 | integrity sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ== 1100 | 1101 | lodash.isarguments@^3.1.0: 1102 | version "3.1.0" 1103 | resolved "https://registry.yarnpkg.com/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz#2f573d85c6a24289ff00663b491c1d338ff3458a" 1104 | integrity sha512-chi4NHZlZqZD18a0imDHnZPrDeBbTtVN7GXMwuGdRH9qotxAjYs3aVLKc7zNOG9eddR5Ksd8rvFEBc9SsggPpg== 1105 | 1106 | lodash@^4.17.11, lodash@^4.17.21: 1107 | version "4.17.21" 1108 | resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" 1109 | integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== 1110 | 1111 | long@^5.0.0: 1112 | version "5.2.3" 1113 | resolved "https://registry.yarnpkg.com/long/-/long-5.2.3.tgz#a3ba97f3877cf1d778eccbcb048525ebb77499e1" 1114 | integrity sha512-lcHwpNoggQTObv5apGNCTdJrO69eHOZMi4BNC+rTLER8iHAqGrUVeLh/irVIM7zTw2bOXA8T6uNPeujwOLg/2Q== 1115 | 1116 | lru-cache@^6.0.0: 1117 | version "6.0.0" 1118 | resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94" 1119 | integrity sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA== 1120 | dependencies: 1121 | yallist "^4.0.0" 1122 | 1123 | luxon@^3.2.1: 1124 | version "3.4.4" 1125 | resolved "https://registry.yarnpkg.com/luxon/-/luxon-3.4.4.tgz#cf20dc27dc532ba41a169c43fdcc0063601577af" 1126 | integrity sha512-zobTr7akeGHnv7eBOXcRgMeCP6+uyYsczwmeRCauvpvaAltgNyTbLH/+VaEAPUeWBT+1GuNmz4wC/6jtQzbbVA== 1127 | 1128 | media-typer@0.3.0: 1129 | version "0.3.0" 1130 | resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" 1131 | integrity sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ== 1132 | 1133 | merge-descriptors@1.0.1: 1134 | version "1.0.1" 1135 | resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61" 1136 | integrity sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w== 1137 | 1138 | methods@~1.1.2: 1139 | version "1.1.2" 1140 | resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee" 1141 | integrity sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w== 1142 | 1143 | mime-db@1.52.0: 1144 | version "1.52.0" 1145 | resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70" 1146 | integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== 1147 | 1148 | mime-types@~2.1.24, mime-types@~2.1.34: 1149 | version "2.1.35" 1150 | resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a" 1151 | integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== 1152 | dependencies: 1153 | mime-db "1.52.0" 1154 | 1155 | mime@1.6.0: 1156 | version "1.6.0" 1157 | resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1" 1158 | integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg== 1159 | 1160 | minimatch@^3.1.2: 1161 | version "3.1.2" 1162 | resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" 1163 | integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== 1164 | dependencies: 1165 | brace-expansion "^1.1.7" 1166 | 1167 | minimatch@^5.0.1: 1168 | version "5.1.6" 1169 | resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-5.1.6.tgz#1cfcb8cf5522ea69952cd2af95ae09477f122a96" 1170 | integrity sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g== 1171 | dependencies: 1172 | brace-expansion "^2.0.1" 1173 | 1174 | minimist@^1.2.6: 1175 | version "1.2.8" 1176 | resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c" 1177 | integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA== 1178 | 1179 | ms@2.0.0: 1180 | version "2.0.0" 1181 | resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" 1182 | integrity sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A== 1183 | 1184 | ms@2.1.2: 1185 | version "2.1.2" 1186 | resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" 1187 | integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== 1188 | 1189 | ms@2.1.3: 1190 | version "2.1.3" 1191 | resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" 1192 | integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== 1193 | 1194 | msgpackr-extract@^3.0.2: 1195 | version "3.0.2" 1196 | resolved "https://registry.yarnpkg.com/msgpackr-extract/-/msgpackr-extract-3.0.2.tgz#e05ec1bb4453ddf020551bcd5daaf0092a2c279d" 1197 | integrity sha512-SdzXp4kD/Qf8agZ9+iTu6eql0m3kWm1A2y1hkpTeVNENutaB0BwHlSvAIaMxwntmRUAUjon2V4L8Z/njd0Ct8A== 1198 | dependencies: 1199 | node-gyp-build-optional-packages "5.0.7" 1200 | optionalDependencies: 1201 | "@msgpackr-extract/msgpackr-extract-darwin-arm64" "3.0.2" 1202 | "@msgpackr-extract/msgpackr-extract-darwin-x64" "3.0.2" 1203 | "@msgpackr-extract/msgpackr-extract-linux-arm" "3.0.2" 1204 | "@msgpackr-extract/msgpackr-extract-linux-arm64" "3.0.2" 1205 | "@msgpackr-extract/msgpackr-extract-linux-x64" "3.0.2" 1206 | "@msgpackr-extract/msgpackr-extract-win32-x64" "3.0.2" 1207 | 1208 | msgpackr@^1.10.1: 1209 | version "1.10.1" 1210 | resolved "https://registry.yarnpkg.com/msgpackr/-/msgpackr-1.10.1.tgz#51953bb4ce4f3494f0c4af3f484f01cfbb306555" 1211 | integrity sha512-r5VRLv9qouXuLiIBrLpl2d5ZvPt8svdQTl5/vMvE4nzDMyEX4sgW5yWhuBBj5UmgwOTWj8CIdSXn5sAfsHAWIQ== 1212 | optionalDependencies: 1213 | msgpackr-extract "^3.0.2" 1214 | 1215 | negotiator@0.6.3: 1216 | version "0.6.3" 1217 | resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.3.tgz#58e323a72fedc0d6f9cd4d31fe49f51479590ccd" 1218 | integrity sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg== 1219 | 1220 | neverthrow@^6.0.0: 1221 | version "6.1.0" 1222 | resolved "https://registry.yarnpkg.com/neverthrow/-/neverthrow-6.1.0.tgz#51a6e9ce2e06600045b3c1b37aecc536d267bf95" 1223 | integrity sha512-xNbNjp/6M5vUV+mststgneJN9eJeJCDSYSBTaf3vxgvcKooP+8L0ATFpM8DGfmH7UWKJeoa24Qi33tBP9Ya3zA== 1224 | 1225 | node-abort-controller@^3.1.1: 1226 | version "3.1.1" 1227 | resolved "https://registry.yarnpkg.com/node-abort-controller/-/node-abort-controller-3.1.1.tgz#a94377e964a9a37ac3976d848cb5c765833b8548" 1228 | integrity sha512-AGK2yQKIjRuqnc6VkX2Xj5d+QW8xZ87pa1UK6yA6ouUyuxfHuMP6umE5QK7UmTeOAymo+Zx1Fxiuw9rVx8taHQ== 1229 | 1230 | node-gyp-build-optional-packages@5.0.7: 1231 | version "5.0.7" 1232 | resolved "https://registry.yarnpkg.com/node-gyp-build-optional-packages/-/node-gyp-build-optional-packages-5.0.7.tgz#5d2632bbde0ab2f6e22f1bbac2199b07244ae0b3" 1233 | integrity sha512-YlCCc6Wffkx0kHkmam79GKvDQ6x+QZkMjFGrIMxgFNILFvGSbCp2fCBC55pGTT9gVaz8Na5CLmxt/urtzRv36w== 1234 | 1235 | object-inspect@^1.13.1: 1236 | version "1.13.1" 1237 | resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.13.1.tgz#b96c6109324ccfef6b12216a956ca4dc2ff94bc2" 1238 | integrity sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ== 1239 | 1240 | obuf@~1.1.2: 1241 | version "1.1.2" 1242 | resolved "https://registry.yarnpkg.com/obuf/-/obuf-1.1.2.tgz#09bea3343d41859ebd446292d11c9d4db619084e" 1243 | integrity sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg== 1244 | 1245 | on-exit-leak-free@^2.1.0: 1246 | version "2.1.2" 1247 | resolved "https://registry.yarnpkg.com/on-exit-leak-free/-/on-exit-leak-free-2.1.2.tgz#fed195c9ebddb7d9e4c3842f93f281ac8dadd3b8" 1248 | integrity sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA== 1249 | 1250 | on-finished@2.4.1: 1251 | version "2.4.1" 1252 | resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.4.1.tgz#58c8c44116e54845ad57f14ab10b03533184ac3f" 1253 | integrity sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg== 1254 | dependencies: 1255 | ee-first "1.1.1" 1256 | 1257 | once@^1.3.1, once@^1.4.0: 1258 | version "1.4.0" 1259 | resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" 1260 | integrity sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w== 1261 | dependencies: 1262 | wrappy "1" 1263 | 1264 | packet-reader@1.0.0: 1265 | version "1.0.0" 1266 | resolved "https://registry.yarnpkg.com/packet-reader/-/packet-reader-1.0.0.tgz#9238e5480dedabacfe1fe3f2771063f164157d74" 1267 | integrity sha512-HAKu/fG3HpHFO0AA8WE8q2g+gBJaZ9MG7fcKk+IJPLTGAD6Psw4443l+9DGRbOIh3/aXr7Phy0TjilYivJo5XQ== 1268 | 1269 | parseurl@~1.3.3: 1270 | version "1.3.3" 1271 | resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4" 1272 | integrity sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ== 1273 | 1274 | path-to-regexp@0.1.7: 1275 | version "0.1.7" 1276 | resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c" 1277 | integrity sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ== 1278 | 1279 | pg-cloudflare@^1.1.1: 1280 | version "1.1.1" 1281 | resolved "https://registry.yarnpkg.com/pg-cloudflare/-/pg-cloudflare-1.1.1.tgz#e6d5833015b170e23ae819e8c5d7eaedb472ca98" 1282 | integrity sha512-xWPagP/4B6BgFO+EKz3JONXv3YDgvkbVrGw2mTo3D6tVDQRh1e7cqVGvyR3BE+eQgAvx1XhW/iEASj4/jCWl3Q== 1283 | 1284 | pg-connection-string@^2.6.2: 1285 | version "2.6.2" 1286 | resolved "https://registry.yarnpkg.com/pg-connection-string/-/pg-connection-string-2.6.2.tgz#713d82053de4e2bd166fab70cd4f26ad36aab475" 1287 | integrity sha512-ch6OwaeaPYcova4kKZ15sbJ2hKb/VP48ZD2gE7i1J+L4MspCtBMAx8nMgz7bksc7IojCIIWuEhHibSMFH8m8oA== 1288 | 1289 | pg-int8@1.0.1: 1290 | version "1.0.1" 1291 | resolved "https://registry.yarnpkg.com/pg-int8/-/pg-int8-1.0.1.tgz#943bd463bf5b71b4170115f80f8efc9a0c0eb78c" 1292 | integrity sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw== 1293 | 1294 | pg-numeric@1.0.2: 1295 | version "1.0.2" 1296 | resolved "https://registry.yarnpkg.com/pg-numeric/-/pg-numeric-1.0.2.tgz#816d9a44026086ae8ae74839acd6a09b0636aa3a" 1297 | integrity sha512-BM/Thnrw5jm2kKLE5uJkXqqExRUY/toLHda65XgFTBTFYZyopbKjBe29Ii3RbkvlsMoFwD+tHeGaCjjv0gHlyw== 1298 | 1299 | pg-pool@^3.6.1: 1300 | version "3.6.1" 1301 | resolved "https://registry.yarnpkg.com/pg-pool/-/pg-pool-3.6.1.tgz#5a902eda79a8d7e3c928b77abf776b3cb7d351f7" 1302 | integrity sha512-jizsIzhkIitxCGfPRzJn1ZdcosIt3pz9Sh3V01fm1vZnbnCMgmGl5wvGGdNN2EL9Rmb0EcFoCkixH4Pu+sP9Og== 1303 | 1304 | pg-protocol@*, pg-protocol@^1.6.0: 1305 | version "1.6.0" 1306 | resolved "https://registry.yarnpkg.com/pg-protocol/-/pg-protocol-1.6.0.tgz#4c91613c0315349363af2084608db843502f8833" 1307 | integrity sha512-M+PDm637OY5WM307051+bsDia5Xej6d9IR4GwJse1qA1DIhiKlksvrneZOYQq42OM+spubpcNYEo2FcKQrDk+Q== 1308 | 1309 | pg-types@^2.1.0: 1310 | version "2.2.0" 1311 | resolved "https://registry.yarnpkg.com/pg-types/-/pg-types-2.2.0.tgz#2d0250d636454f7cfa3b6ae0382fdfa8063254a3" 1312 | integrity sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA== 1313 | dependencies: 1314 | pg-int8 "1.0.1" 1315 | postgres-array "~2.0.0" 1316 | postgres-bytea "~1.0.0" 1317 | postgres-date "~1.0.4" 1318 | postgres-interval "^1.1.0" 1319 | 1320 | pg-types@^4.0.1: 1321 | version "4.0.2" 1322 | resolved "https://registry.yarnpkg.com/pg-types/-/pg-types-4.0.2.tgz#399209a57c326f162461faa870145bb0f918b76d" 1323 | integrity sha512-cRL3JpS3lKMGsKaWndugWQoLOCoP+Cic8oseVcbr0qhPzYD5DWXK+RZ9LY9wxRf7RQia4SCwQlXk0q6FCPrVng== 1324 | dependencies: 1325 | pg-int8 "1.0.1" 1326 | pg-numeric "1.0.2" 1327 | postgres-array "~3.0.1" 1328 | postgres-bytea "~3.0.0" 1329 | postgres-date "~2.1.0" 1330 | postgres-interval "^3.0.0" 1331 | postgres-range "^1.1.1" 1332 | 1333 | pg@^8.11.3: 1334 | version "8.11.3" 1335 | resolved "https://registry.yarnpkg.com/pg/-/pg-8.11.3.tgz#d7db6e3fe268fcedd65b8e4599cda0b8b4bf76cb" 1336 | integrity sha512-+9iuvG8QfaaUrrph+kpF24cXkH1YOOUeArRNYIxq1viYHZagBxrTno7cecY1Fa44tJeZvaoG+Djpkc3JwehN5g== 1337 | dependencies: 1338 | buffer-writer "2.0.0" 1339 | packet-reader "1.0.0" 1340 | pg-connection-string "^2.6.2" 1341 | pg-pool "^3.6.1" 1342 | pg-protocol "^1.6.0" 1343 | pg-types "^2.1.0" 1344 | pgpass "1.x" 1345 | optionalDependencies: 1346 | pg-cloudflare "^1.1.1" 1347 | 1348 | pgpass@1.x: 1349 | version "1.0.5" 1350 | resolved "https://registry.yarnpkg.com/pgpass/-/pgpass-1.0.5.tgz#9b873e4a564bb10fa7a7dbd55312728d422a223d" 1351 | integrity sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug== 1352 | dependencies: 1353 | split2 "^4.1.0" 1354 | 1355 | pino-abstract-transport@^1.0.0, pino-abstract-transport@v1.1.0: 1356 | version "1.1.0" 1357 | resolved "https://registry.yarnpkg.com/pino-abstract-transport/-/pino-abstract-transport-1.1.0.tgz#083d98f966262164504afb989bccd05f665937a8" 1358 | integrity sha512-lsleG3/2a/JIWUtf9Q5gUNErBqwIu1tUKTT3dUzaf5DySw9ra1wcqKjJjLX1VTY64Wk1eEOYsVGSaGfCK85ekA== 1359 | dependencies: 1360 | readable-stream "^4.0.0" 1361 | split2 "^4.0.0" 1362 | 1363 | pino-pretty@^10.3.1: 1364 | version "10.3.1" 1365 | resolved "https://registry.yarnpkg.com/pino-pretty/-/pino-pretty-10.3.1.tgz#e3285a5265211ac6c7cd5988f9e65bf3371a0ca9" 1366 | integrity sha512-az8JbIYeN/1iLj2t0jR9DV48/LQ3RC6hZPpapKPkb84Q+yTidMCpgWxIT3N0flnBDilyBQ1luWNpOeJptjdp/g== 1367 | dependencies: 1368 | colorette "^2.0.7" 1369 | dateformat "^4.6.3" 1370 | fast-copy "^3.0.0" 1371 | fast-safe-stringify "^2.1.1" 1372 | help-me "^5.0.0" 1373 | joycon "^3.1.1" 1374 | minimist "^1.2.6" 1375 | on-exit-leak-free "^2.1.0" 1376 | pino-abstract-transport "^1.0.0" 1377 | pump "^3.0.0" 1378 | readable-stream "^4.0.0" 1379 | secure-json-parse "^2.4.0" 1380 | sonic-boom "^3.0.0" 1381 | strip-json-comments "^3.1.1" 1382 | 1383 | pino-std-serializers@^6.0.0: 1384 | version "6.2.2" 1385 | resolved "https://registry.yarnpkg.com/pino-std-serializers/-/pino-std-serializers-6.2.2.tgz#d9a9b5f2b9a402486a5fc4db0a737570a860aab3" 1386 | integrity sha512-cHjPPsE+vhj/tnhCy/wiMh3M3z3h/j15zHQX+S9GkTBgqJuTuJzYJ4gUyACLhDaJ7kk9ba9iRDmbH2tJU03OiA== 1387 | 1388 | pino@^8.19.0: 1389 | version "8.19.0" 1390 | resolved "https://registry.yarnpkg.com/pino/-/pino-8.19.0.tgz#ccc15ef736f103ec02cfbead0912bc436dc92ce4" 1391 | integrity sha512-oswmokxkav9bADfJ2ifrvfHUwad6MLp73Uat0IkQWY3iAw5xTRoznXbXksZs8oaOUMpmhVWD+PZogNzllWpJaA== 1392 | dependencies: 1393 | atomic-sleep "^1.0.0" 1394 | fast-redact "^3.1.1" 1395 | on-exit-leak-free "^2.1.0" 1396 | pino-abstract-transport v1.1.0 1397 | pino-std-serializers "^6.0.0" 1398 | process-warning "^3.0.0" 1399 | quick-format-unescaped "^4.0.3" 1400 | real-require "^0.2.0" 1401 | safe-stable-stringify "^2.3.1" 1402 | sonic-boom "^3.7.0" 1403 | thread-stream "^2.0.0" 1404 | 1405 | postgres-array@~2.0.0: 1406 | version "2.0.0" 1407 | resolved "https://registry.yarnpkg.com/postgres-array/-/postgres-array-2.0.0.tgz#48f8fce054fbc69671999329b8834b772652d82e" 1408 | integrity sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA== 1409 | 1410 | postgres-array@~3.0.1: 1411 | version "3.0.2" 1412 | resolved "https://registry.yarnpkg.com/postgres-array/-/postgres-array-3.0.2.tgz#68d6182cb0f7f152a7e60dc6a6889ed74b0a5f98" 1413 | integrity sha512-6faShkdFugNQCLwucjPcY5ARoW1SlbnrZjmGl0IrrqewpvxvhSLHimCVzqeuULCbG0fQv7Dtk1yDbG3xv7Veog== 1414 | 1415 | postgres-bytea@~1.0.0: 1416 | version "1.0.0" 1417 | resolved "https://registry.yarnpkg.com/postgres-bytea/-/postgres-bytea-1.0.0.tgz#027b533c0aa890e26d172d47cf9ccecc521acd35" 1418 | integrity sha512-xy3pmLuQqRBZBXDULy7KbaitYqLcmxigw14Q5sj8QBVLqEwXfeybIKVWiqAXTlcvdvb0+xkOtDbfQMOf4lST1w== 1419 | 1420 | postgres-bytea@~3.0.0: 1421 | version "3.0.0" 1422 | resolved "https://registry.yarnpkg.com/postgres-bytea/-/postgres-bytea-3.0.0.tgz#9048dc461ac7ba70a6a42d109221619ecd1cb089" 1423 | integrity sha512-CNd4jim9RFPkObHSjVHlVrxoVQXz7quwNFpz7RY1okNNme49+sVyiTvTRobiLV548Hx/hb1BG+iE7h9493WzFw== 1424 | dependencies: 1425 | obuf "~1.1.2" 1426 | 1427 | postgres-date@~1.0.4: 1428 | version "1.0.7" 1429 | resolved "https://registry.yarnpkg.com/postgres-date/-/postgres-date-1.0.7.tgz#51bc086006005e5061c591cee727f2531bf641a8" 1430 | integrity sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q== 1431 | 1432 | postgres-date@~2.1.0: 1433 | version "2.1.0" 1434 | resolved "https://registry.yarnpkg.com/postgres-date/-/postgres-date-2.1.0.tgz#b85d3c1fb6fb3c6c8db1e9942a13a3bf625189d0" 1435 | integrity sha512-K7Juri8gtgXVcDfZttFKVmhglp7epKb1K4pgrkLxehjqkrgPhfG6OO8LHLkfaqkbpjNRnra018XwAr1yQFWGcA== 1436 | 1437 | postgres-interval@^1.1.0: 1438 | version "1.2.0" 1439 | resolved "https://registry.yarnpkg.com/postgres-interval/-/postgres-interval-1.2.0.tgz#b460c82cb1587507788819a06aa0fffdb3544695" 1440 | integrity sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ== 1441 | dependencies: 1442 | xtend "^4.0.0" 1443 | 1444 | postgres-interval@^3.0.0: 1445 | version "3.0.0" 1446 | resolved "https://registry.yarnpkg.com/postgres-interval/-/postgres-interval-3.0.0.tgz#baf7a8b3ebab19b7f38f07566c7aab0962f0c86a" 1447 | integrity sha512-BSNDnbyZCXSxgA+1f5UU2GmwhoI0aU5yMxRGO8CdFEcY2BQF9xm/7MqKnYoM1nJDk8nONNWDk9WeSmePFhQdlw== 1448 | 1449 | postgres-range@^1.1.1: 1450 | version "1.1.4" 1451 | resolved "https://registry.yarnpkg.com/postgres-range/-/postgres-range-1.1.4.tgz#a59c5f9520909bcec5e63e8cf913a92e4c952863" 1452 | integrity sha512-i/hbxIE9803Alj/6ytL7UHQxRvZkI9O4Sy+J3HGc4F4oo/2eQAjTSNJ0bfxyse3bH0nuVesCk+3IRLaMtG3H6w== 1453 | 1454 | prettier@^3.2.5: 1455 | version "3.2.5" 1456 | resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.2.5.tgz#e52bc3090586e824964a8813b09aba6233b28368" 1457 | integrity sha512-3/GWa9aOC0YeD7LUfvOG2NiDyhOWRvt1k+rcKhOuYnMY24iiCphgneUfJDyFXd6rZCAnuLBv6UeAULtrhT/F4A== 1458 | 1459 | process-warning@^3.0.0: 1460 | version "3.0.0" 1461 | resolved "https://registry.yarnpkg.com/process-warning/-/process-warning-3.0.0.tgz#96e5b88884187a1dce6f5c3166d611132058710b" 1462 | integrity sha512-mqn0kFRl0EoqhnL0GQ0veqFHyIN1yig9RHh/InzORTUiZHFRAur+aMtRkELNwGs9aNwKS6tg/An4NYBPGwvtzQ== 1463 | 1464 | process@^0.11.10: 1465 | version "0.11.10" 1466 | resolved "https://registry.yarnpkg.com/process/-/process-0.11.10.tgz#7332300e840161bda3e69a1d1d91a7d4bc16f182" 1467 | integrity sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A== 1468 | 1469 | protobufjs@^7.2.4: 1470 | version "7.2.6" 1471 | resolved "https://registry.yarnpkg.com/protobufjs/-/protobufjs-7.2.6.tgz#4a0ccd79eb292717aacf07530a07e0ed20278215" 1472 | integrity sha512-dgJaEDDL6x8ASUZ1YqWciTRrdOuYNzoOf27oHNfdyvKqHr5i0FV7FSLU+aIeFjyFgVxrpTOtQUi0BLLBymZaBw== 1473 | dependencies: 1474 | "@protobufjs/aspromise" "^1.1.2" 1475 | "@protobufjs/base64" "^1.1.2" 1476 | "@protobufjs/codegen" "^2.0.4" 1477 | "@protobufjs/eventemitter" "^1.1.0" 1478 | "@protobufjs/fetch" "^1.1.0" 1479 | "@protobufjs/float" "^1.0.2" 1480 | "@protobufjs/inquire" "^1.1.0" 1481 | "@protobufjs/path" "^1.1.2" 1482 | "@protobufjs/pool" "^1.1.0" 1483 | "@protobufjs/utf8" "^1.1.0" 1484 | "@types/node" ">=13.7.0" 1485 | long "^5.0.0" 1486 | 1487 | proxy-addr@~2.0.7: 1488 | version "2.0.7" 1489 | resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.7.tgz#f19fe69ceab311eeb94b42e70e8c2070f9ba1025" 1490 | integrity sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg== 1491 | dependencies: 1492 | forwarded "0.2.0" 1493 | ipaddr.js "1.9.1" 1494 | 1495 | pump@^3.0.0: 1496 | version "3.0.0" 1497 | resolved "https://registry.yarnpkg.com/pump/-/pump-3.0.0.tgz#b4a2116815bde2f4e1ea602354e8c75565107a64" 1498 | integrity sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww== 1499 | dependencies: 1500 | end-of-stream "^1.1.0" 1501 | once "^1.3.1" 1502 | 1503 | qs@6.11.0: 1504 | version "6.11.0" 1505 | resolved "https://registry.yarnpkg.com/qs/-/qs-6.11.0.tgz#fd0d963446f7a65e1367e01abd85429453f0c37a" 1506 | integrity sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q== 1507 | dependencies: 1508 | side-channel "^1.0.4" 1509 | 1510 | quick-format-unescaped@^4.0.3: 1511 | version "4.0.4" 1512 | resolved "https://registry.yarnpkg.com/quick-format-unescaped/-/quick-format-unescaped-4.0.4.tgz#93ef6dd8d3453cbc7970dd614fad4c5954d6b5a7" 1513 | integrity sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg== 1514 | 1515 | range-parser@~1.2.1: 1516 | version "1.2.1" 1517 | resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031" 1518 | integrity sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg== 1519 | 1520 | raw-body@2.5.2: 1521 | version "2.5.2" 1522 | resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.5.2.tgz#99febd83b90e08975087e8f1f9419a149366b68a" 1523 | integrity sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA== 1524 | dependencies: 1525 | bytes "3.1.2" 1526 | http-errors "2.0.0" 1527 | iconv-lite "0.4.24" 1528 | unpipe "1.0.0" 1529 | 1530 | readable-stream@^4.0.0: 1531 | version "4.5.2" 1532 | resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-4.5.2.tgz#9e7fc4c45099baeed934bff6eb97ba6cf2729e09" 1533 | integrity sha512-yjavECdqeZ3GLXNgRXgeQEdz9fvDDkNKyHnbHRFtOr7/LcfgBcmct7t/ET+HaCTqfh06OzoAxrkN/IfjJBVe+g== 1534 | dependencies: 1535 | abort-controller "^3.0.0" 1536 | buffer "^6.0.3" 1537 | events "^3.3.0" 1538 | process "^0.11.10" 1539 | string_decoder "^1.3.0" 1540 | 1541 | real-require@^0.2.0: 1542 | version "0.2.0" 1543 | resolved "https://registry.yarnpkg.com/real-require/-/real-require-0.2.0.tgz#209632dea1810be2ae063a6ac084fee7e33fba78" 1544 | integrity sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg== 1545 | 1546 | redis-errors@^1.0.0, redis-errors@^1.2.0: 1547 | version "1.2.0" 1548 | resolved "https://registry.yarnpkg.com/redis-errors/-/redis-errors-1.2.0.tgz#eb62d2adb15e4eaf4610c04afe1529384250abad" 1549 | integrity sha512-1qny3OExCf0UvUV/5wpYKf2YwPcOqXzkwKKSmKHiE6ZMQs5heeE/c8eXK+PNllPvmjgAbfnsbpkGZWy8cBpn9w== 1550 | 1551 | redis-info@^3.0.8: 1552 | version "3.1.0" 1553 | resolved "https://registry.yarnpkg.com/redis-info/-/redis-info-3.1.0.tgz#5e349c8720e82d27ac84c73136dce0931e10469a" 1554 | integrity sha512-ER4L9Sh/vm63DkIE0bkSjxluQlioBiBgf5w1UuldaW/3vPcecdljVDisZhmnCMvsxHNiARTTDDHGg9cGwTfrKg== 1555 | dependencies: 1556 | lodash "^4.17.11" 1557 | 1558 | redis-parser@^3.0.0: 1559 | version "3.0.0" 1560 | resolved "https://registry.yarnpkg.com/redis-parser/-/redis-parser-3.0.0.tgz#b66d828cdcafe6b4b8a428a7def4c6bcac31c8b4" 1561 | integrity sha512-DJnGAeenTdpMEH6uAJRK/uiyEIH9WVsUmoLwzudwGJUwZPp80PDBWPHXSAGNPwNvIXAbe7MSUB1zQFugFml66A== 1562 | dependencies: 1563 | redis-errors "^1.0.0" 1564 | 1565 | require-directory@^2.1.1: 1566 | version "2.1.1" 1567 | resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" 1568 | integrity sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q== 1569 | 1570 | safe-buffer@5.2.1, safe-buffer@~5.2.0: 1571 | version "5.2.1" 1572 | resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" 1573 | integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== 1574 | 1575 | safe-stable-stringify@^2.3.1: 1576 | version "2.4.3" 1577 | resolved "https://registry.yarnpkg.com/safe-stable-stringify/-/safe-stable-stringify-2.4.3.tgz#138c84b6f6edb3db5f8ef3ef7115b8f55ccbf886" 1578 | integrity sha512-e2bDA2WJT0wxseVd4lsDP4+3ONX6HpMXQa1ZhFQ7SU+GjvORCmShbCMltrtIDfkYhVHrOcPtj+KhmDBdPdZD1g== 1579 | 1580 | "safer-buffer@>= 2.1.2 < 3": 1581 | version "2.1.2" 1582 | resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" 1583 | integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== 1584 | 1585 | secure-json-parse@^2.4.0: 1586 | version "2.7.0" 1587 | resolved "https://registry.yarnpkg.com/secure-json-parse/-/secure-json-parse-2.7.0.tgz#5a5f9cd6ae47df23dba3151edd06855d47e09862" 1588 | integrity sha512-6aU+Rwsezw7VR8/nyvKTx8QpWH9FrcYiXXlqC4z5d5XQBDRqtbfsRjnwGyqbi3gddNtWHuEk9OANUotL26qKUw== 1589 | 1590 | semver@^7.5.4: 1591 | version "7.6.0" 1592 | resolved "https://registry.yarnpkg.com/semver/-/semver-7.6.0.tgz#1a46a4db4bffcccd97b743b5005c8325f23d4e2d" 1593 | integrity sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg== 1594 | dependencies: 1595 | lru-cache "^6.0.0" 1596 | 1597 | send@0.18.0: 1598 | version "0.18.0" 1599 | resolved "https://registry.yarnpkg.com/send/-/send-0.18.0.tgz#670167cc654b05f5aa4a767f9113bb371bc706be" 1600 | integrity sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg== 1601 | dependencies: 1602 | debug "2.6.9" 1603 | depd "2.0.0" 1604 | destroy "1.2.0" 1605 | encodeurl "~1.0.2" 1606 | escape-html "~1.0.3" 1607 | etag "~1.8.1" 1608 | fresh "0.5.2" 1609 | http-errors "2.0.0" 1610 | mime "1.6.0" 1611 | ms "2.1.3" 1612 | on-finished "2.4.1" 1613 | range-parser "~1.2.1" 1614 | statuses "2.0.1" 1615 | 1616 | serve-static@1.15.0: 1617 | version "1.15.0" 1618 | resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.15.0.tgz#faaef08cffe0a1a62f60cad0c4e513cff0ac9540" 1619 | integrity sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g== 1620 | dependencies: 1621 | encodeurl "~1.0.2" 1622 | escape-html "~1.0.3" 1623 | parseurl "~1.3.3" 1624 | send "0.18.0" 1625 | 1626 | set-function-length@^1.2.1: 1627 | version "1.2.2" 1628 | resolved "https://registry.yarnpkg.com/set-function-length/-/set-function-length-1.2.2.tgz#aac72314198eaed975cf77b2c3b6b880695e5449" 1629 | integrity sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg== 1630 | dependencies: 1631 | define-data-property "^1.1.4" 1632 | es-errors "^1.3.0" 1633 | function-bind "^1.1.2" 1634 | get-intrinsic "^1.2.4" 1635 | gopd "^1.0.1" 1636 | has-property-descriptors "^1.0.2" 1637 | 1638 | setprototypeof@1.2.0: 1639 | version "1.2.0" 1640 | resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.2.0.tgz#66c9a24a73f9fc28cbe66b09fed3d33dcaf1b424" 1641 | integrity sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw== 1642 | 1643 | side-channel@^1.0.4: 1644 | version "1.0.6" 1645 | resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.6.tgz#abd25fb7cd24baf45466406b1096b7831c9215f2" 1646 | integrity sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA== 1647 | dependencies: 1648 | call-bind "^1.0.7" 1649 | es-errors "^1.3.0" 1650 | get-intrinsic "^1.2.4" 1651 | object-inspect "^1.13.1" 1652 | 1653 | sonic-boom@^3.0.0, sonic-boom@^3.7.0: 1654 | version "3.8.0" 1655 | resolved "https://registry.yarnpkg.com/sonic-boom/-/sonic-boom-3.8.0.tgz#e442c5c23165df897d77c3c14ef3ca40dec66a66" 1656 | integrity sha512-ybz6OYOUjoQQCQ/i4LU8kaToD8ACtYP+Cj5qd2AO36bwbdewxWJ3ArmJ2cr6AvxlL2o0PqnCcPGUgkILbfkaCA== 1657 | dependencies: 1658 | atomic-sleep "^1.0.0" 1659 | 1660 | source-map@^0.5.0: 1661 | version "0.5.7" 1662 | resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc" 1663 | integrity sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ== 1664 | 1665 | split2@^4.0.0, split2@^4.1.0: 1666 | version "4.2.0" 1667 | resolved "https://registry.yarnpkg.com/split2/-/split2-4.2.0.tgz#c9c5920904d148bab0b9f67145f245a86aadbfa4" 1668 | integrity sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg== 1669 | 1670 | standard-as-callback@^2.1.0: 1671 | version "2.1.0" 1672 | resolved "https://registry.yarnpkg.com/standard-as-callback/-/standard-as-callback-2.1.0.tgz#8953fc05359868a77b5b9739a665c5977bb7df45" 1673 | integrity sha512-qoRRSyROncaz1z0mvYqIE4lCd9p2R90i6GxW3uZv5ucSu8tU7B5HXUP1gG8pVZsYNVaXjk8ClXHPttLyxAL48A== 1674 | 1675 | statuses@2.0.1: 1676 | version "2.0.1" 1677 | resolved "https://registry.yarnpkg.com/statuses/-/statuses-2.0.1.tgz#55cb000ccf1d48728bd23c685a063998cf1a1b63" 1678 | integrity sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ== 1679 | 1680 | string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: 1681 | version "4.2.3" 1682 | resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" 1683 | integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== 1684 | dependencies: 1685 | emoji-regex "^8.0.0" 1686 | is-fullwidth-code-point "^3.0.0" 1687 | strip-ansi "^6.0.1" 1688 | 1689 | string_decoder@^1.3.0: 1690 | version "1.3.0" 1691 | resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e" 1692 | integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA== 1693 | dependencies: 1694 | safe-buffer "~5.2.0" 1695 | 1696 | strip-ansi@^6.0.0, strip-ansi@^6.0.1: 1697 | version "6.0.1" 1698 | resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" 1699 | integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== 1700 | dependencies: 1701 | ansi-regex "^5.0.1" 1702 | 1703 | strip-json-comments@^3.1.1: 1704 | version "3.1.1" 1705 | resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" 1706 | integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== 1707 | 1708 | supports-color@^5.3.0: 1709 | version "5.5.0" 1710 | resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" 1711 | integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow== 1712 | dependencies: 1713 | has-flag "^3.0.0" 1714 | 1715 | supports-color@^7.1.0: 1716 | version "7.2.0" 1717 | resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" 1718 | integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== 1719 | dependencies: 1720 | has-flag "^4.0.0" 1721 | 1722 | thread-stream@^2.0.0: 1723 | version "2.4.1" 1724 | resolved "https://registry.yarnpkg.com/thread-stream/-/thread-stream-2.4.1.tgz#6d588b14f0546e59d3f306614f044bc01ce43351" 1725 | integrity sha512-d/Ex2iWd1whipbT681JmTINKw0ZwOUBZm7+Gjs64DHuX34mmw8vJL2bFAaNacaW72zYiTJxSHi5abUuOi5nsfg== 1726 | dependencies: 1727 | real-require "^0.2.0" 1728 | 1729 | to-fast-properties@^2.0.0: 1730 | version "2.0.0" 1731 | resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-2.0.0.tgz#dc5e698cbd079265bc73e0377681a4e4e83f616e" 1732 | integrity sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog== 1733 | 1734 | toidentifier@1.0.1: 1735 | version "1.0.1" 1736 | resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.1.tgz#3be34321a88a820ed1bd80dfaa33e479fbb8dd35" 1737 | integrity sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA== 1738 | 1739 | tslib@^2.0.0: 1740 | version "2.6.2" 1741 | resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.6.2.tgz#703ac29425e7b37cd6fd456e92404d46d1f3e4ae" 1742 | integrity sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q== 1743 | 1744 | type-is@~1.6.18: 1745 | version "1.6.18" 1746 | resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.18.tgz#4e552cd05df09467dcbc4ef739de89f2cf37c131" 1747 | integrity sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g== 1748 | dependencies: 1749 | media-typer "0.3.0" 1750 | mime-types "~2.1.24" 1751 | 1752 | typescript@^5.3.3: 1753 | version "5.3.3" 1754 | resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.3.3.tgz#b3ce6ba258e72e6305ba66f5c9b452aaee3ffe37" 1755 | integrity sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw== 1756 | 1757 | undici-types@~5.26.4: 1758 | version "5.26.5" 1759 | resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-5.26.5.tgz#bcd539893d00b56e964fd2657a4866b221a65617" 1760 | integrity sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA== 1761 | 1762 | unpipe@1.0.0, unpipe@~1.0.0: 1763 | version "1.0.0" 1764 | resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" 1765 | integrity sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ== 1766 | 1767 | utils-merge@1.0.1: 1768 | version "1.0.1" 1769 | resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713" 1770 | integrity sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA== 1771 | 1772 | uuid@^9.0.0: 1773 | version "9.0.1" 1774 | resolved "https://registry.yarnpkg.com/uuid/-/uuid-9.0.1.tgz#e188d4c8853cc722220392c424cd637f32293f30" 1775 | integrity sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA== 1776 | 1777 | vary@~1.1.2: 1778 | version "1.1.2" 1779 | resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" 1780 | integrity sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg== 1781 | 1782 | viem@^1.12.2: 1783 | version "1.21.4" 1784 | resolved "https://registry.yarnpkg.com/viem/-/viem-1.21.4.tgz#883760e9222540a5a7e0339809202b45fe6a842d" 1785 | integrity sha512-BNVYdSaUjeS2zKQgPs+49e5JKocfo60Ib2yiXOWBT6LuVxY1I/6fFX3waEtpXvL1Xn4qu+BVitVtMh9lyThyhQ== 1786 | dependencies: 1787 | "@adraffy/ens-normalize" "1.10.0" 1788 | "@noble/curves" "1.2.0" 1789 | "@noble/hashes" "1.3.2" 1790 | "@scure/bip32" "1.3.2" 1791 | "@scure/bip39" "1.2.1" 1792 | abitype "0.9.8" 1793 | isows "1.0.3" 1794 | ws "8.13.0" 1795 | 1796 | wrap-ansi@^7.0.0: 1797 | version "7.0.0" 1798 | resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" 1799 | integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== 1800 | dependencies: 1801 | ansi-styles "^4.0.0" 1802 | string-width "^4.1.0" 1803 | strip-ansi "^6.0.0" 1804 | 1805 | wrappy@1: 1806 | version "1.0.2" 1807 | resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" 1808 | integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ== 1809 | 1810 | ws@8.13.0: 1811 | version "8.13.0" 1812 | resolved "https://registry.yarnpkg.com/ws/-/ws-8.13.0.tgz#9a9fb92f93cf41512a0735c8f4dd09b8a1211cd0" 1813 | integrity sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA== 1814 | 1815 | xtend@^4.0.0: 1816 | version "4.0.2" 1817 | resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54" 1818 | integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ== 1819 | 1820 | y18n@^5.0.5: 1821 | version "5.0.8" 1822 | resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55" 1823 | integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA== 1824 | 1825 | yallist@^4.0.0: 1826 | version "4.0.0" 1827 | resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" 1828 | integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== 1829 | 1830 | yargs-parser@^21.1.1: 1831 | version "21.1.1" 1832 | resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-21.1.1.tgz#9096bceebf990d21bb31fa9516e0ede294a77d35" 1833 | integrity sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw== 1834 | 1835 | yargs@^17.7.2: 1836 | version "17.7.2" 1837 | resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.7.2.tgz#991df39aca675a192b816e1e0363f9d75d2aa269" 1838 | integrity sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w== 1839 | dependencies: 1840 | cliui "^8.0.1" 1841 | escalade "^3.1.1" 1842 | get-caller-file "^2.0.5" 1843 | require-directory "^2.1.1" 1844 | string-width "^4.2.3" 1845 | y18n "^5.0.5" 1846 | yargs-parser "^21.1.1" 1847 | --------------------------------------------------------------------------------