diff --git a/.github/workflows/relayer-build.yml b/.github/workflows/relayer-build.yml index 143484ed0..6a5c76490 100644 --- a/.github/workflows/relayer-build.yml +++ b/.github/workflows/relayer-build.yml @@ -6,7 +6,13 @@ on: pull_request: env: - RPC_URL: "http://localhost:8545" + RELAYER_RPC_URL: "http://localhost:8545" + TTL: ${{ vars.RELAYER_TTL }} + LIMIT: ${{ vars.RELAYER_LIMIT }} + MONGO_DB_URI: ${{ secrets.RELAYER_MONGO_DB_URI }} + MONGODB_USER: ${{ secrets.MONGODB_USER }} + MONGODB_PASSWORD: ${{ secrets.MONGODB_PASSWORD }} + MONGODB_DATABASE: ${{ secrets.MONGODB_DATABASE }} concurrency: group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} diff --git a/apps/relayer/.env.example b/apps/relayer/.env.example index a6389442a..b0628a7ce 100644 --- a/apps/relayer/.env.example +++ b/apps/relayer/.env.example @@ -5,6 +5,12 @@ LIMIT=10 # Coordinator RPC url RELAYER_RPC_URL=http://localhost:8545 +# MongoDB configuration +MONGO_DB_URI=mongodb://localhost +MONGODB_USER=maci +MONGODB_PASSWORD= +MONGODB_DATABASE=maci-relayer + # Allowed origin host, use comma to separate each of them ALLOWED_ORIGINS= diff --git a/apps/relayer/package.json b/apps/relayer/package.json index b5aa86a57..e22ff6d7d 100644 --- a/apps/relayer/package.json +++ b/apps/relayer/package.json @@ -26,6 +26,7 @@ "dependencies": { "@nestjs/common": "^10.4.7", "@nestjs/core": "^10.4.7", + "@nestjs/mongoose": "^10.1.0", "@nestjs/platform-express": "^10.4.7", "@nestjs/platform-socket.io": "^10.3.10", "@nestjs/swagger": "^8.0.3", @@ -42,6 +43,7 @@ "helmet": "^8.0.0", "maci-contracts": "workspace:^2.5.0", "maci-domainobjs": "workspace:^2.5.0", + "mongoose": "^8.9.3", "mustache": "^4.2.0", "reflect-metadata": "^0.2.0", "rxjs": "^7.8.1", @@ -57,6 +59,7 @@ "@types/supertest": "^6.0.2", "fast-check": "^3.23.1", "jest": "^29.5.0", + "mongodb-memory-server": "^10.1.3", "supertest": "^7.0.0", "ts-jest": "^29.2.5", "typescript": "^5.7.2" diff --git a/apps/relayer/tests/app.test.ts b/apps/relayer/tests/app.test.ts index 26c6a977d..75194e545 100644 --- a/apps/relayer/tests/app.test.ts +++ b/apps/relayer/tests/app.test.ts @@ -6,7 +6,7 @@ import type { App } from "supertest/types"; import { AppModule } from "../ts/app.module"; -describe("e2e", () => { +describe("Integration", () => { let app: INestApplication; beforeAll(async () => { diff --git a/apps/relayer/tests/messages.test.ts b/apps/relayer/tests/messages.test.ts index 72d59ac63..401d3971a 100644 --- a/apps/relayer/tests/messages.test.ts +++ b/apps/relayer/tests/messages.test.ts @@ -8,7 +8,7 @@ import type { App } from "supertest/types"; import { AppModule } from "../ts/app.module"; -describe("e2e messages", () => { +describe("Integration messages", () => { let app: INestApplication; beforeAll(async () => { diff --git a/apps/relayer/ts/app.module.ts b/apps/relayer/ts/app.module.ts index fdc8e1fa6..752e77a5f 100644 --- a/apps/relayer/ts/app.module.ts +++ b/apps/relayer/ts/app.module.ts @@ -1,7 +1,9 @@ import { Module } from "@nestjs/common"; +import { MongooseModule } from "@nestjs/mongoose"; import { ThrottlerModule } from "@nestjs/throttler"; import { MessageModule } from "./message/message.module"; +import { MessageBatchModule } from "./messageBatch/messageBatch.module"; @Module({ imports: [ @@ -11,7 +13,26 @@ import { MessageModule } from "./message/message.module"; limit: Number(process.env.LIMIT), }, ]), + MongooseModule.forRootAsync({ + useFactory: async () => { + if (process.env.NODE_ENV === "test") { + const { getTestMongooseModuleOptions } = await import("./jest/mongo"); + + return getTestMongooseModuleOptions(); + } + + return { + uri: process.env.MONGO_DB_URI, + auth: { + username: process.env.MONGODB_USER, + password: process.env.MONGODB_PASSWORD, + }, + dbName: process.env.MONGODB_DATABASE, + }; + }, + }), MessageModule, + MessageBatchModule, ], }) export class AppModule {} diff --git a/apps/relayer/ts/jest/mongo.ts b/apps/relayer/ts/jest/mongo.ts new file mode 100644 index 000000000..074162357 --- /dev/null +++ b/apps/relayer/ts/jest/mongo.ts @@ -0,0 +1,20 @@ +import type { MongooseModuleOptions } from "@nestjs/mongoose"; + +/** + * Get test mongoose module options + * + * @param options mongoose module options + * @returns mongoose module options for testing + */ +export const getTestMongooseModuleOptions = async ( + options: MongooseModuleOptions = {}, +): Promise<MongooseModuleOptions> => { + // eslint-disable-next-line import/no-extraneous-dependencies + const { MongoMemoryServer } = await import("mongodb-memory-server"); + const mongod = await MongoMemoryServer.create(); + + return { + uri: mongod.getUri(), + ...options, + }; +}; diff --git a/apps/relayer/ts/message/__tests__/message.repository.test.ts b/apps/relayer/ts/message/__tests__/message.repository.test.ts new file mode 100644 index 000000000..d43c8a28b --- /dev/null +++ b/apps/relayer/ts/message/__tests__/message.repository.test.ts @@ -0,0 +1,77 @@ +import { ZeroAddress } from "ethers"; +import { Keypair } from "maci-domainobjs"; +import { Model } from "mongoose"; + +import { MessageRepository } from "../message.repository"; +import { Message } from "../message.schema"; + +import { defaultSaveMessagesArgs } from "./utils"; + +describe("MessageRepository", () => { + const defaultMessages: Message[] = [ + { + publicKey: new Keypair().pubKey.serialize(), + data: ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"], + maciContractAddress: ZeroAddress, + poll: 0, + }, + ]; + + const mockMessageModel = { + find: jest + .fn() + .mockReturnValue({ limit: jest.fn().mockReturnValue({ exec: jest.fn().mockResolvedValue(defaultMessages) }) }), + insertMany: jest.fn().mockResolvedValue(defaultMessages), + } as unknown as Model<Message>; + + beforeEach(() => { + mockMessageModel.find = jest + .fn() + .mockReturnValue({ limit: jest.fn().mockReturnValue({ exec: jest.fn().mockResolvedValue(defaultMessages) }) }); + mockMessageModel.insertMany = jest.fn().mockResolvedValue(defaultMessages); + }); + + afterEach(() => { + jest.clearAllMocks(); + }); + + test("should create messages properly", async () => { + const repository = new MessageRepository(mockMessageModel); + + const result = await repository.create(defaultSaveMessagesArgs); + + expect(result).toStrictEqual(defaultMessages); + }); + + test("should throw an error if creation is failed", async () => { + const error = new Error("error"); + + (mockMessageModel.insertMany as jest.Mock).mockRejectedValue(error); + + const repository = new MessageRepository(mockMessageModel); + + await expect(repository.create(defaultSaveMessagesArgs)).rejects.toThrow(error); + }); + + test("should find messages properly", async () => { + const repository = new MessageRepository(mockMessageModel); + + const result = await repository.find({}); + + expect(result).toStrictEqual(defaultMessages); + }); + + test("should throw an error if find is failed", async () => { + const error = new Error("error"); + + (mockMessageModel.find as jest.Mock).mockReturnValue({ + limit: jest.fn().mockReturnValue({ + exec: jest.fn().mockRejectedValue(error), + }), + }); + + const repository = new MessageRepository(mockMessageModel); + + await expect(repository.find({})).rejects.toThrow(error); + }); +}); diff --git a/apps/relayer/ts/message/__tests__/message.service.test.ts b/apps/relayer/ts/message/__tests__/message.service.test.ts index 114b8d326..6a4ecd74e 100644 --- a/apps/relayer/ts/message/__tests__/message.service.test.ts +++ b/apps/relayer/ts/message/__tests__/message.service.test.ts @@ -1,21 +1,64 @@ +import type { MessageBatchService } from "../../messageBatch/messageBatch.service"; +import type { MessageRepository } from "../message.repository"; + import { MessageService } from "../message.service"; -import { defaultSaveMessagesArgs } from "./utils"; +import { defaultMessages, defaultSaveMessagesArgs } from "./utils"; describe("MessageService", () => { + const mockMessageBatchService = { + saveMessageBatches: jest.fn().mockImplementation((args) => Promise.resolve(args)), + } as unknown as MessageBatchService; + + const mockRepository = { + create: jest.fn().mockResolvedValue(defaultMessages), + find: jest.fn().mockResolvedValue(defaultMessages), + } as unknown as MessageRepository; + + beforeEach(() => { + mockMessageBatchService.saveMessageBatches = jest.fn().mockImplementation((args) => Promise.resolve(args)); + + mockRepository.create = jest.fn().mockResolvedValue(defaultMessages); + mockRepository.find = jest.fn().mockResolvedValue(defaultMessages); + }); + + afterEach(() => { + jest.clearAllMocks(); + }); + test("should save messages properly", async () => { - const service = new MessageService(); + const service = new MessageService(mockMessageBatchService, mockRepository); const result = await service.saveMessages(defaultSaveMessagesArgs); - expect(result).toBe(true); + expect(result).toStrictEqual(defaultMessages); + }); + + test("should throw an error if can't save messages", async () => { + const error = new Error("error"); + + (mockRepository.create as jest.Mock).mockRejectedValue(error); + + const service = new MessageService(mockMessageBatchService, mockRepository); + + await expect(service.saveMessages(defaultSaveMessagesArgs)).rejects.toThrow(error); }); test("should publish messages properly", async () => { - const service = new MessageService(); + const service = new MessageService(mockMessageBatchService, mockRepository); - const result = await service.publishMessages(defaultSaveMessagesArgs); + const result = await service.publishMessages(); expect(result).toStrictEqual({ hash: "", ipfsHash: "" }); }); + + test("should throw an error if can't save message batch", async () => { + const error = new Error("error"); + + (mockMessageBatchService.saveMessageBatches as jest.Mock).mockRejectedValue(error); + + const service = new MessageService(mockMessageBatchService, mockRepository); + + await expect(service.publishMessages()).rejects.toThrow(error); + }); }); diff --git a/apps/relayer/ts/message/__tests__/utils.ts b/apps/relayer/ts/message/__tests__/utils.ts index 3852210ea..2aeac0278 100644 --- a/apps/relayer/ts/message/__tests__/utils.ts +++ b/apps/relayer/ts/message/__tests__/utils.ts @@ -1,15 +1,19 @@ import { ZeroAddress } from "ethers"; import { Keypair } from "maci-domainobjs"; +import { defaultMessageBatches } from "../../messageBatch/__tests__/utils"; +import { PublishMessagesDto } from "../dto"; + const keypair = new Keypair(); -export const defaultSaveMessagesArgs = { - maciContractAddress: ZeroAddress, - poll: 0, - messages: [ - { - data: ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"], - publicKey: keypair.pubKey.serialize(), - }, - ], -}; +export const defaultMessages = defaultMessageBatches[0].messages; + +export const defaultSaveMessagesArgs = new PublishMessagesDto(); +defaultSaveMessagesArgs.maciContractAddress = ZeroAddress; +defaultSaveMessagesArgs.poll = 0; +defaultSaveMessagesArgs.messages = [ + { + data: ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"], + publicKey: keypair.pubKey.serialize(), + }, +]; diff --git a/apps/relayer/ts/message/message.controller.ts b/apps/relayer/ts/message/message.controller.ts index 1f2da30dc..5bf1a8a51 100644 --- a/apps/relayer/ts/message/message.controller.ts +++ b/apps/relayer/ts/message/message.controller.ts @@ -3,6 +3,7 @@ import { Body, Controller, HttpException, HttpStatus, Logger, Post } from "@nest import { ApiBearerAuth, ApiBody, ApiResponse, ApiTags } from "@nestjs/swagger"; import { PublishMessagesDto } from "./dto"; +import { Message } from "./message.schema"; import { MessageService } from "./message.service"; @ApiTags("v1/messages") @@ -17,6 +18,7 @@ export class MessageController { /** * Initialize MessageController * + * @param messageService message service */ constructor(private readonly messageService: MessageService) {} @@ -24,7 +26,7 @@ export class MessageController { * Publish user messages api method. * Saves messages batch and then send them onchain by calling `publishMessages` method via cron job. * - * @param args - publish messages dto + * @param args publish messages dto * @returns success or not */ @ApiBody({ type: PublishMessagesDto }) @@ -32,7 +34,7 @@ export class MessageController { @ApiResponse({ status: HttpStatus.FORBIDDEN, description: "Forbidden" }) @ApiResponse({ status: HttpStatus.BAD_REQUEST, description: "BadRequest" }) @Post("publish") - async publish(@Body() args: PublishMessagesDto): Promise<boolean> { + async publish(@Body() args: PublishMessagesDto): Promise<Message[]> { return this.messageService.saveMessages(args).catch((error: Error) => { this.logger.error(`Error:`, error); throw new HttpException(error.message, HttpStatus.BAD_REQUEST); diff --git a/apps/relayer/ts/message/message.module.ts b/apps/relayer/ts/message/message.module.ts index d24abda23..a8cbbf8f4 100644 --- a/apps/relayer/ts/message/message.module.ts +++ b/apps/relayer/ts/message/message.module.ts @@ -1,10 +1,16 @@ import { Module } from "@nestjs/common"; +import { MongooseModule } from "@nestjs/mongoose"; + +import { MessageBatchModule } from "../messageBatch/messageBatch.module"; import { MessageController } from "./message.controller"; +import { MessageRepository } from "./message.repository"; +import { Message, MessageSchema } from "./message.schema"; import { MessageService } from "./message.service"; @Module({ + imports: [MongooseModule.forFeature([{ name: Message.name, schema: MessageSchema }]), MessageBatchModule], controllers: [MessageController], - providers: [MessageService], + providers: [MessageService, MessageRepository], }) export class MessageModule {} diff --git a/apps/relayer/ts/message/message.repository.ts b/apps/relayer/ts/message/message.repository.ts new file mode 100644 index 000000000..5b65ac7e2 --- /dev/null +++ b/apps/relayer/ts/message/message.repository.ts @@ -0,0 +1,60 @@ +import { Injectable, Logger } from "@nestjs/common"; +import { InjectModel } from "@nestjs/mongoose"; +import { Model, RootFilterQuery } from "mongoose"; + +import { PublishMessagesDto } from "./dto"; +import { Message, MESSAGES_LIMIT } from "./message.schema"; + +/** + * Message repository is used to interact with the message collection + */ +@Injectable() +export class MessageRepository { + /** + * Logger + */ + private readonly logger: Logger = new Logger(MessageRepository.name); + + /** + * Initializes the message repository + * + * @param MessageModel message model + */ + constructor(@InjectModel(Message.name) private MessageModel: Model<Message>) {} + + /** + * Create messages from dto + * + * @param dto publish messages dto + * @returns inserted messages + */ + async create(dto: PublishMessagesDto): Promise<Message[]> { + const messages = dto.messages.map(({ data, publicKey }) => ({ + data, + publicKey, + maciContractAddress: dto.maciContractAddress, + poll: dto.poll, + })); + + return this.MessageModel.insertMany(messages).catch((error) => { + this.logger.error(`Create messages error:`, error); + throw error; + }); + } + + /** + * Find messages with filter query + * + * @param filter filter query + * @returns messages + */ + async find(filter: RootFilterQuery<Message>, limit = MESSAGES_LIMIT): Promise<Message[]> { + return this.MessageModel.find(filter) + .limit(limit) + .exec() + .catch((error) => { + this.logger.error(`Find messages error:`, error); + throw error; + }); + } +} diff --git a/apps/relayer/ts/message/message.schema.ts b/apps/relayer/ts/message/message.schema.ts new file mode 100644 index 000000000..958163c5a --- /dev/null +++ b/apps/relayer/ts/message/message.schema.ts @@ -0,0 +1,55 @@ +import { Prop, Schema, SchemaFactory } from "@nestjs/mongoose"; +import mongoose, { HydratedDocument } from "mongoose"; + +import type { MessageBatch } from "../messageBatch/messageBatch.schema"; + +/** + * Message document type + */ +export type MessageDocument = HydratedDocument<Message>; + +/** + * Messages limit + */ +export const MESSAGES_LIMIT = 100; + +/** + * Message model + */ +@Schema() +export class Message { + /** + * Public key + */ + @Prop({ required: true }) + publicKey!: string; + + /** + * Message data + */ + @Prop({ required: true }) + data!: string[]; + + /** + * MACI contract address + */ + @Prop({ required: true }) + maciContractAddress!: string; + + /** + * Poll ID + */ + @Prop({ required: true }) + poll!: number; + + /** + * Message batch + */ + @Prop({ type: mongoose.Schema.Types.ObjectId, required: false }) + messageBatch?: MessageBatch; +} + +/** + * Message schema + */ +export const MessageSchema = SchemaFactory.createForClass(Message); diff --git a/apps/relayer/ts/message/message.service.ts b/apps/relayer/ts/message/message.service.ts index c554f6a62..a786a5cd7 100644 --- a/apps/relayer/ts/message/message.service.ts +++ b/apps/relayer/ts/message/message.service.ts @@ -3,8 +3,13 @@ import { Injectable, Logger } from "@nestjs/common"; import type { PublishMessagesDto } from "./dto"; import type { IPublishMessagesReturn } from "./types"; +import { MessageBatchService } from "../messageBatch/messageBatch.service"; + +import { MessageRepository } from "./message.repository"; +import { Message } from "./message.schema"; + /** - * MessageService is responsible for saving message batches and send them onchain + * MessageService is responsible for saving messages and send them onchain */ @Injectable() export class MessageService { @@ -14,24 +19,43 @@ export class MessageService { private readonly logger: Logger = new Logger(MessageService.name); /** - * Save messages batch + * Initialize MessageService + * + * @param messageBatchService message batch service + * @param messageRepository message repository + */ + constructor( + private readonly messageBatchService: MessageBatchService, + private readonly messageRepository: MessageRepository, + ) {} + + /** + * Save messages * - * @param args - publish messages dto + * @param args publish messages dto * @returns success or not */ - async saveMessages(args: PublishMessagesDto): Promise<boolean> { - this.logger.log("Save messages", args); - return Promise.resolve(true); + async saveMessages(args: PublishMessagesDto): Promise<Message[]> { + return this.messageRepository.create(args).catch((error) => { + this.logger.error(`Save messages error:`, error); + throw error; + }); } /** * Publish messages onchain * - * @param args - publish messages dto + * @param args publish messages dto * @returns transaction and ipfs hashes */ - async publishMessages(args: PublishMessagesDto): Promise<IPublishMessagesReturn> { - this.logger.log("Publish messages", args); + async publishMessages(): Promise<IPublishMessagesReturn> { + const messages = await this.messageRepository.find({ messageBatch: { $exists: false } }); + + await this.messageBatchService.saveMessageBatches([{ messages, ipfsHash: "" }]).catch((error) => { + this.logger.error(`Save message batch error:`, error); + throw error; + }); + return Promise.resolve({ hash: "", ipfsHash: "" }); } } diff --git a/apps/relayer/ts/message/validation.ts b/apps/relayer/ts/message/validation.ts index 598910ddf..d14ee8b8f 100644 --- a/apps/relayer/ts/message/validation.ts +++ b/apps/relayer/ts/message/validation.ts @@ -9,7 +9,7 @@ export class PublicKeyValidator implements ValidatorConstraintInterface { /** * Try to deserialize public key from text and return status of validation * - * @param text - text to validate + * @param text text to validate * @returns status of validation */ validate(text: string): boolean { diff --git a/apps/relayer/ts/messageBatch/__tests__/messageBatch.repository.test.ts b/apps/relayer/ts/messageBatch/__tests__/messageBatch.repository.test.ts new file mode 100644 index 000000000..36be1f3ef --- /dev/null +++ b/apps/relayer/ts/messageBatch/__tests__/messageBatch.repository.test.ts @@ -0,0 +1,66 @@ +import { Model } from "mongoose"; + +import { MessageBatchRepository } from "../messageBatch.repository"; +import { MessageBatch } from "../messageBatch.schema"; + +import { defaultMessageBatches } from "./utils"; + +describe("MessageBatchRepository", () => { + const mockMessageBatchModel = { + find: jest.fn().mockReturnValue({ + limit: jest.fn().mockReturnValue({ exec: jest.fn().mockResolvedValue([defaultMessageBatches]) }), + }), + insertMany: jest.fn().mockResolvedValue(defaultMessageBatches), + } as unknown as Model<MessageBatch>; + + beforeEach(() => { + mockMessageBatchModel.find = jest.fn().mockReturnValue({ + limit: jest.fn().mockReturnValue({ exec: jest.fn().mockResolvedValue([defaultMessageBatches]) }), + }); + mockMessageBatchModel.insertMany = jest.fn().mockResolvedValue(defaultMessageBatches); + }); + + afterEach(() => { + jest.clearAllMocks(); + }); + + test("should create message batch properly", async () => { + const repository = new MessageBatchRepository(mockMessageBatchModel); + + const result = await repository.create(defaultMessageBatches); + + expect(result).toStrictEqual(defaultMessageBatches); + }); + + test("should throw an error if creation is failed", async () => { + const error = new Error("error"); + + (mockMessageBatchModel.insertMany as jest.Mock).mockRejectedValue(error); + + const repository = new MessageBatchRepository(mockMessageBatchModel); + + await expect(repository.create(defaultMessageBatches)).rejects.toThrow(error); + }); + + test("should find message batches properly", async () => { + const repository = new MessageBatchRepository(mockMessageBatchModel); + + const result = await repository.find({}); + + expect(result).toStrictEqual([defaultMessageBatches]); + }); + + test("should throw an error if find is failed", async () => { + const error = new Error("error"); + + (mockMessageBatchModel.find as jest.Mock).mockReturnValue({ + limit: jest.fn().mockReturnValue({ + exec: jest.fn().mockRejectedValue(error), + }), + }); + + const repository = new MessageBatchRepository(mockMessageBatchModel); + + await expect(repository.find({})).rejects.toThrow(error); + }); +}); diff --git a/apps/relayer/ts/messageBatch/__tests__/messageBatch.service.test.ts b/apps/relayer/ts/messageBatch/__tests__/messageBatch.service.test.ts new file mode 100644 index 000000000..9743e592b --- /dev/null +++ b/apps/relayer/ts/messageBatch/__tests__/messageBatch.service.test.ts @@ -0,0 +1,52 @@ +import { MessageBatchDto } from "../dto"; +import { MessageBatchRepository } from "../messageBatch.repository"; +import { MessageBatchService } from "../messageBatch.service"; + +import { defaultMessageBatches } from "./utils"; + +describe("MessageBatchService", () => { + const mockRepository = { + create: jest.fn().mockResolvedValue(defaultMessageBatches), + } as unknown as MessageBatchRepository; + + beforeEach(() => { + mockRepository.create = jest.fn().mockResolvedValue(defaultMessageBatches); + }); + + afterEach(() => { + jest.clearAllMocks(); + }); + + test("should save message batches properly", async () => { + const service = new MessageBatchService(mockRepository); + + const result = await service.saveMessageBatches(defaultMessageBatches); + + expect(result).toStrictEqual(defaultMessageBatches); + }); + + test("should throw an error if can't save message batches", async () => { + const error = new Error("error"); + + (mockRepository.create as jest.Mock).mockRejectedValue(error); + + const service = new MessageBatchService(mockRepository); + + await expect(service.saveMessageBatches(defaultMessageBatches)).rejects.toThrow(error); + }); + + test("should throw an error if validation is failed", async () => { + const service = new MessageBatchService(mockRepository); + + const invalidEmptyMessagesArgs = new MessageBatchDto(); + invalidEmptyMessagesArgs.messages = []; + invalidEmptyMessagesArgs.ipfsHash = "invalid"; + + const invalidMessageArgs = new MessageBatchDto(); + invalidMessageArgs.messages = []; + invalidMessageArgs.ipfsHash = "invalid"; + + await expect(service.saveMessageBatches([invalidEmptyMessagesArgs])).rejects.toThrow("Validation error"); + await expect(service.saveMessageBatches([invalidMessageArgs])).rejects.toThrow("Validation error"); + }); +}); diff --git a/apps/relayer/ts/messageBatch/__tests__/utils.ts b/apps/relayer/ts/messageBatch/__tests__/utils.ts new file mode 100644 index 000000000..c717c8b12 --- /dev/null +++ b/apps/relayer/ts/messageBatch/__tests__/utils.ts @@ -0,0 +1,21 @@ +import { ZeroAddress } from "ethers"; +import { Keypair } from "maci-domainobjs"; + +import { MessageBatchDto } from "../dto"; + +const keypair = new Keypair(); + +export const defaultIpfsHash = "QmXj8v1qbwTqVp9RxkQR29Xjc6g5C1KL2m2gZ9b8t8THHj"; + +const defaultMessageBatch = new MessageBatchDto(); +defaultMessageBatch.messages = [ + { + publicKey: keypair.pubKey.serialize(), + data: ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"], + maciContractAddress: ZeroAddress, + poll: 0, + }, +]; +defaultMessageBatch.ipfsHash = defaultIpfsHash; + +export const defaultMessageBatches: MessageBatchDto[] = [defaultMessageBatch]; diff --git a/apps/relayer/ts/messageBatch/__tests__/validation.test.ts b/apps/relayer/ts/messageBatch/__tests__/validation.test.ts new file mode 100644 index 000000000..15d306012 --- /dev/null +++ b/apps/relayer/ts/messageBatch/__tests__/validation.test.ts @@ -0,0 +1,29 @@ +import { IpfsHashValidator } from "../validation"; + +import { defaultIpfsHash } from "./utils"; + +describe("IpfsHashValidator", () => { + test("should validate valid ipfs hash", () => { + const validator = new IpfsHashValidator(); + + const result = validator.validate(defaultIpfsHash); + + expect(result).toBe(true); + }); + + test("should validate invalid ipfs hash", () => { + const validator = new IpfsHashValidator(); + + const result = validator.validate("invalid"); + + expect(result).toBe(false); + }); + + test("should return default message properly", () => { + const validator = new IpfsHashValidator(); + + const result = validator.defaultMessage(); + + expect(result).toBe("IPFS hash ($value) is invalid"); + }); +}); diff --git a/apps/relayer/ts/messageBatch/dto.ts b/apps/relayer/ts/messageBatch/dto.ts new file mode 100644 index 000000000..4ad3fdcd7 --- /dev/null +++ b/apps/relayer/ts/messageBatch/dto.ts @@ -0,0 +1,39 @@ +import { ApiProperty } from "@nestjs/swagger"; +import { IsArray, ArrayMinSize, ArrayMaxSize, ValidateNested, Validate } from "class-validator"; + +import { Message } from "../message/message.schema"; + +import { IpfsHashValidator } from "./validation"; + +/** + * Max messages per batch + */ +const MAX_MESSAGES = 100; + +/** + * Data transfer object for message batch + */ +export class MessageBatchDto { + /** + * Messages + */ + @ApiProperty({ + description: "Messages", + type: [String], + }) + @IsArray() + @ArrayMinSize(1) + @ArrayMaxSize(MAX_MESSAGES) + @ValidateNested({ each: true }) + messages!: Message[]; + + /** + * IPFS hash + */ + @ApiProperty({ + description: "IPFS hash", + type: String, + }) + @Validate(IpfsHashValidator) + ipfsHash!: string; +} diff --git a/apps/relayer/ts/messageBatch/messageBatch.module.ts b/apps/relayer/ts/messageBatch/messageBatch.module.ts new file mode 100644 index 000000000..ec029fc1a --- /dev/null +++ b/apps/relayer/ts/messageBatch/messageBatch.module.ts @@ -0,0 +1,13 @@ +import { Module } from "@nestjs/common"; +import { MongooseModule } from "@nestjs/mongoose"; + +import { MessageBatchRepository } from "./messageBatch.repository"; +import { MessageBatch, MessageBatchSchema } from "./messageBatch.schema"; +import { MessageBatchService } from "./messageBatch.service"; + +@Module({ + imports: [MongooseModule.forFeature([{ name: MessageBatch.name, schema: MessageBatchSchema }])], + exports: [MessageBatchService], + providers: [MessageBatchService, MessageBatchRepository], +}) +export class MessageBatchModule {} diff --git a/apps/relayer/ts/messageBatch/messageBatch.repository.ts b/apps/relayer/ts/messageBatch/messageBatch.repository.ts new file mode 100644 index 000000000..bac2e6efc --- /dev/null +++ b/apps/relayer/ts/messageBatch/messageBatch.repository.ts @@ -0,0 +1,54 @@ +import { Injectable, Logger } from "@nestjs/common"; +import { InjectModel } from "@nestjs/mongoose"; +import { Model, RootFilterQuery } from "mongoose"; + +import { MessageBatchDto } from "./dto"; +import { MESSAGE_BATCHES_LIMIT, MessageBatch } from "./messageBatch.schema"; + +/** + * Message batch repository is used to interact with the message batch collection + */ +@Injectable() +export class MessageBatchRepository { + /** + * Logger + */ + private readonly logger: Logger = new Logger(MessageBatchRepository.name); + + /** + * Initializes the message batch repository + * + * @param MessageBatchModel message batch model + */ + constructor(@InjectModel(MessageBatch.name) private MessageBatchModel: Model<MessageBatch>) {} + + /** + * Create message batch from messages and ipfs hash + * + * @param messages messages + * @param ipfsHash ipfs hash + * @returns inserted message batch + */ + async create(dto: MessageBatchDto[]): Promise<MessageBatch[]> { + return this.MessageBatchModel.insertMany(dto).catch((error) => { + this.logger.error(`Create message batch error:`, error); + throw error; + }); + } + + /** + * Find message batches with filter query + * + * @param filter filter query + * @returns message batches + */ + async find(filter: RootFilterQuery<MessageBatch>, limit = MESSAGE_BATCHES_LIMIT): Promise<MessageBatch[]> { + return this.MessageBatchModel.find(filter) + .limit(limit) + .exec() + .catch((error) => { + this.logger.error(`Find message batches error:`, error); + throw error; + }); + } +} diff --git a/apps/relayer/ts/messageBatch/messageBatch.schema.ts b/apps/relayer/ts/messageBatch/messageBatch.schema.ts new file mode 100644 index 000000000..986a696d3 --- /dev/null +++ b/apps/relayer/ts/messageBatch/messageBatch.schema.ts @@ -0,0 +1,37 @@ +import { Prop, Schema, SchemaFactory } from "@nestjs/mongoose"; +import mongoose, { HydratedDocument } from "mongoose"; + +import type { Message } from "../message/message.schema"; + +/** + * Message batch document type + */ +export type MessageBatchDocument = HydratedDocument<MessageBatch>; + +/** + * Message batches limit + */ +export const MESSAGE_BATCHES_LIMIT = 1; + +/** + * Message batch model + */ +@Schema() +export class MessageBatch { + /** + * Messages + */ + @Prop({ type: [{ type: mongoose.Schema.Types.ObjectId, ref: "Message" }], required: true }) + messages!: Message[]; + + /** + * IPFS hash + */ + @Prop({ required: true }) + ipfsHash!: string; +} + +/** + * Message batch schema + */ +export const MessageBatchSchema = SchemaFactory.createForClass(MessageBatch); diff --git a/apps/relayer/ts/messageBatch/messageBatch.service.ts b/apps/relayer/ts/messageBatch/messageBatch.service.ts new file mode 100644 index 000000000..11c5e9204 --- /dev/null +++ b/apps/relayer/ts/messageBatch/messageBatch.service.ts @@ -0,0 +1,51 @@ +import { Injectable, Logger } from "@nestjs/common"; +import { validate } from "class-validator"; + +import type { MessageBatchDto } from "./dto"; + +import { MessageBatchRepository } from "./messageBatch.repository"; +import { MessageBatch } from "./messageBatch.schema"; + +/** + * MessageBatchService is responsible for saving message batches and send them to ipfs + */ +@Injectable() +export class MessageBatchService { + /** + * Logger + */ + private readonly logger: Logger = new Logger(MessageBatchService.name); + + /** + * Initialize MessageBatchService + * + * @param messageBatchRepository message batch repository + */ + constructor(private readonly messageBatchRepository: MessageBatchRepository) {} + + /** + * Save messages batch + * + * @param args publish messages dto + * @returns success or not + */ + async saveMessageBatches(args: MessageBatchDto[]): Promise<MessageBatch[]> { + const validationErrors = await Promise.all(args.map((values) => validate(values))).then((result) => + result.reduce((acc, errors) => { + acc.push(...errors); + return acc; + }, []), + ); + + if (validationErrors.length > 0) { + this.logger.error(`Validation error:`, validationErrors); + + throw new Error("Validation error"); + } + + return this.messageBatchRepository.create(args).catch((error) => { + this.logger.error(`Save message batch error:`, error); + throw error; + }); + } +} diff --git a/apps/relayer/ts/messageBatch/validation.ts b/apps/relayer/ts/messageBatch/validation.ts new file mode 100644 index 000000000..08391abbb --- /dev/null +++ b/apps/relayer/ts/messageBatch/validation.ts @@ -0,0 +1,31 @@ +import { ValidatorConstraint, ValidatorConstraintInterface } from "class-validator"; + +/** + * Check if the string is a valid base58 encoded CIDv1 hash + */ +const IPFS_REGEX = /^Qm[a-zA-Z0-9]{44}$/; + +/** + * Validate public key + */ +@ValidatorConstraint({ name: "ipfsHash", async: false }) +export class IpfsHashValidator implements ValidatorConstraintInterface { + /** + * Validate ipfs hash + * + * @param text text to validate + * @returns status of validation + */ + validate(text: string): boolean { + return IPFS_REGEX.test(text); + } + + /** + * Return default validation message + * + * @returns default validation message + */ + defaultMessage(): string { + return "IPFS hash ($value) is invalid"; + } +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index fe81febef..d4b9d6405 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -104,6 +104,9 @@ importers: '@nestjs/core': specifier: ^10.4.7 version: 10.4.15(@nestjs/common@10.4.15(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.15)(@nestjs/websockets@10.4.15)(encoding@0.1.13)(reflect-metadata@0.2.2)(rxjs@7.8.1) + '@nestjs/mongoose': + specifier: ^10.1.0 + version: 10.1.0(@nestjs/common@10.4.15(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.15(@nestjs/common@10.4.15(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.15)(@nestjs/websockets@10.4.15)(encoding@0.1.13)(reflect-metadata@0.2.2)(rxjs@7.8.1))(mongoose@8.9.3(socks@2.8.3))(rxjs@7.8.1) '@nestjs/platform-express': specifier: ^10.4.7 version: 10.4.15(@nestjs/common@10.4.15(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.15) @@ -152,6 +155,9 @@ importers: maci-domainobjs: specifier: workspace:^2.5.0 version: link:../../packages/domainobjs + mongoose: + specifier: ^8.9.3 + version: 8.9.3(socks@2.8.3) mustache: specifier: ^4.2.0 version: 4.2.0 @@ -192,6 +198,9 @@ importers: jest: specifier: ^29.5.0 version: 29.7.0(@types/node@22.10.5)(ts-node@10.9.2(@types/node@22.10.5)(typescript@5.7.2)) + mongodb-memory-server: + specifier: ^10.1.3 + version: 10.1.3(socks@2.8.3) supertest: specifier: ^7.0.0 version: 7.0.0 @@ -2106,6 +2115,9 @@ packages: '@microsoft/tsdoc@0.15.1': resolution: {integrity: sha512-4aErSrCR/On/e5G2hDP0wjooqDdauzEbIq8hIkIe5pXV0rtWJZvdCEKL0ykZxex+IxIwBp0eGeV48hQN07dXtw==} + '@mongodb-js/saslprep@1.1.9': + resolution: {integrity: sha512-tVkljjeEaAhCqTzajSdgbQ6gE6f3oneVwa3iXR6csiEwXXOFsiC6Uh9iAjAhXPtqa/XMDHWjjeNH/77m/Yq2dw==} + '@napi-rs/wasm-runtime@0.2.4': resolution: {integrity: sha512-9zESzOO5aDByvhIAsOy9TbpZ0Ur2AJbUI7UT73kcUTS2mxAMHOBaa1st/jAymNoCtvrit99kkzT1FZuXVcgfIQ==} @@ -2165,6 +2177,14 @@ packages: class-validator: optional: true + '@nestjs/mongoose@10.1.0': + resolution: {integrity: sha512-1ExAnZUfh2QffEaGjqYGgVPy/sYBQCVLCLqVgkcClKx/BCd0QNgND8MB70lwyobp3nm/+nbGQqBpu9F3/hgOCw==} + peerDependencies: + '@nestjs/common': ^8.0.0 || ^9.0.0 || ^10.0.0 + '@nestjs/core': ^8.0.0 || ^9.0.0 || ^10.0.0 + mongoose: ^6.0.2 || ^7.0.0 || ^8.0.0 + rxjs: ^7.0.0 + '@nestjs/platform-express@10.4.15': resolution: {integrity: sha512-63ZZPkXHjoDyO7ahGOVcybZCRa7/Scp6mObQKjcX/fTEq1YJeU75ELvMsuQgc8U2opMGOBD7GVuc4DV0oeDHoA==} peerDependencies: @@ -3375,6 +3395,12 @@ packages: '@types/validator@13.12.2': resolution: {integrity: sha512-6SlHBzUW8Jhf3liqrGGXyTJSIFe4nqlJ5A5KaMZ2l/vbM3Wh3KSybots/wfWVzNLK4D1NZluDlSQIbIEPx6oyA==} + '@types/webidl-conversions@7.0.3': + resolution: {integrity: sha512-CiJJvcRtIgzadHCYXw7dqEnMNRjhGZlYK05Mj9OyktqV8uVT8fD2BFOB7S1uwBE3Kj2Z+4UyPmFw/Ixgw/LAlA==} + + '@types/whatwg-url@11.0.5': + resolution: {integrity: sha512-coYR071JRaHa+xoEvvYqvnIHaVqaYrLPbsufM9BF63HkwI5Lgmy2QR8Q5K/lYDYo5AK82wOvSOS0UsLTpTG7uQ==} + '@types/ws@7.4.7': resolution: {integrity: sha512-JQbbmxZTZehdc2iszGKs5oC3NFnjeay7mtAWrdt7qNtAVK0g19muApzAy4bm9byz79xa2ZnO/BOBC2R8RC5Lww==} @@ -3657,6 +3683,10 @@ packages: resolution: {integrity: sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==} engines: {node: '>= 14'} + agent-base@7.1.3: + resolution: {integrity: sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw==} + engines: {node: '>= 14'} + aggregate-error@3.1.0: resolution: {integrity: sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==} engines: {node: '>=8'} @@ -3912,6 +3942,9 @@ packages: resolution: {integrity: sha512-ISvCdHdlTDlH5IpxQJIex7BWBywFWgjJSVdwst+/iQCoEYnyOaQ95+X1JGshuBjGp6nxKUy1jMgE3zPqN7fQdg==} hasBin: true + async-mutex@0.5.0: + resolution: {integrity: sha512-1A94B18jkJ3DYq284ohPxoXbfTA5HsQ7/Mf4DEhcyLx3Bz27Rh59iScbB6EPiP+B+joue6YCxcMXSbFC1tZKwA==} + async@1.5.2: resolution: {integrity: sha512-nSVgobk4rv61R9PUSDtYt7mPVB2olxNR5RWJcAsH676/ef11bUZwvu7+RGYrYauVdDPcO519v68wRhXQtxsV9w==} @@ -4015,6 +4048,9 @@ packages: balanced-match@1.0.2: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + bare-events@2.5.3: + resolution: {integrity: sha512-pCO3aoRJ0MBiRMu8B7vUga0qL3L7gO1+SW7ku6qlSsMLwuhaawnuvZDyzJY/kyC63Un0XAB0OPUcfF1eTO/V+Q==} + base-x@3.0.9: resolution: {integrity: sha512-H7JU6iBHTal1gp56aKoaa//YUxEaAOUiydvrV/pILqIHXTtqxSkATOnDA2u+jZ/61sD+L/412+7kzXRtWukhpQ==} @@ -4173,12 +4209,19 @@ packages: bser@2.1.1: resolution: {integrity: sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==} + bson@6.10.1: + resolution: {integrity: sha512-P92xmHDQjSKPLHqFxefqMxASNq/aWJMEZugpCjf+AF/pgcUpMMQCg7t7+ewko0/u8AapvF3luf/FoehddEK+sA==} + engines: {node: '>=16.20.1'} + buffer-alloc-unsafe@1.1.0: resolution: {integrity: sha512-TEM2iMIEQdJ2yjPJoSIsldnleVaAk1oW3DBVUykyOLsEsFmEc9kn+SFFPz+gl54KQNxlDnAwCXosOS9Okx2xAg==} buffer-alloc@1.2.0: resolution: {integrity: sha512-CFsHQgjtW1UChdXgbyJGtnm+O/uLQeZdtbDo8mfUgYXCHSM1wgrVxXm6bSyrUuErEb+4sYVGCzASBRot7zyrow==} + buffer-crc32@0.2.13: + resolution: {integrity: sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==} + buffer-fill@1.0.0: resolution: {integrity: sha512-T7zexNBwiiaCOGDg9xNX9PBmjrubblRkENuptryuI64URkXDFum9il/JGL8Lm8wYfAXpredVXXZz7eMHilimiQ==} @@ -6036,6 +6079,15 @@ packages: debug: optional: true + follow-redirects@1.15.9: + resolution: {integrity: sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==} + engines: {node: '>=4.0'} + peerDependencies: + debug: '*' + peerDependenciesMeta: + debug: + optional: true + for-each@0.3.3: resolution: {integrity: sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==} @@ -6685,6 +6737,10 @@ packages: resolution: {integrity: sha512-wlwpilI7YdjSkWaQ/7omYBMTliDcmCN8OLihO6I9B86g06lMyAoqgoDpV0XqoaPOKj+0DIdAvnsWfyAAhmimcg==} engines: {node: '>= 14'} + https-proxy-agent@7.0.6: + resolution: {integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==} + engines: {node: '>= 14'} + human-signals@2.1.0: resolution: {integrity: sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==} engines: {node: '>=10.17.0'} @@ -7522,6 +7578,10 @@ packages: just-diff@6.0.2: resolution: {integrity: sha512-S59eriX5u3/QhMNq3v/gm8Kd0w8OS6Tz2FS1NG4blv+z0MuQcBRJyFWjdovM0Rad4/P4aUPFtnkNjMjyMlMSYA==} + kareem@2.6.3: + resolution: {integrity: sha512-C3iHfuGUXK2u8/ipq9LfjFfXFxAZMQJJq7vLS45r3D9Y2xQ/m4S8zaR4zMLFWh9AsNPXmcFfUDhTEO8UIC/V6Q==} + engines: {node: '>=12.0.0'} + katex@0.16.10: resolution: {integrity: sha512-ZiqaC04tp2O5utMsl2TEZTXxa6WSC4yo0fv5ML++D3QZv/vx2Mct0mTlRx3O+uUkjfuAgOkzsCmq5MiUEsDDdA==} hasBin: true @@ -7952,6 +8012,9 @@ packages: resolution: {integrity: sha512-UERzLsxzllchadvbPs5aolHh65ISpKpM+ccLbOJ8/vvpBKmAWf+la7dXFy7Mr0ySHbdHrFv5kGFCUHHe6GFEmw==} engines: {node: '>= 4.0.0'} + memory-pager@1.5.0: + resolution: {integrity: sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==} + memorystream@0.3.1: resolution: {integrity: sha512-S3UwM3yj5mtUSEfP41UZmt/0SCoVYUcU1rkXv+BQ5Ig8ndL4sPoJNBUJERafdPb5jjHJGuMgytgKvKIf58XNBw==} engines: {node: '>= 0.10.0'} @@ -8293,6 +8356,56 @@ packages: resolution: {integrity: sha512-xV2bxeN6F7oYjZWTe/YPAy6MN2M+sL4u/Rlm2AHCIVGfo2p1yGmBHQ6vHehl4bRTZBdHu3TSkWdYgkwpYzAGSw==} engines: {node: '>=0.10.0'} + mongodb-connection-string-url@3.0.1: + resolution: {integrity: sha512-XqMGwRX0Lgn05TDB4PyG2h2kKO/FfWJyCzYQbIhXUxz7ETt0I/FqHjUeqj37irJ+Dl1ZtU82uYyj14u2XsZKfg==} + + mongodb-memory-server-core@10.1.3: + resolution: {integrity: sha512-ayBQHeV74wRHhgcAKpxHYI4th9Ufidy/m3XhJnLFRufKsOyDsyHYU3Zxv5Fm4hxsWE6wVd0GAVcQ7t7XNkivOg==} + engines: {node: '>=16.20.1'} + + mongodb-memory-server@10.1.3: + resolution: {integrity: sha512-QCUjsIIXSYv/EgkpDAjfhlqRKo6N+qR6DD43q4lyrCVn24xQmvlArdWHW/Um5RS4LkC9YWC3XveSncJqht2Hbg==} + engines: {node: '>=16.20.1'} + + mongodb@6.12.0: + resolution: {integrity: sha512-RM7AHlvYfS7jv7+BXund/kR64DryVI+cHbVAy9P61fnb1RcWZqOW1/Wj2YhqMCx+MuYhqTRGv7AwHBzmsCKBfA==} + engines: {node: '>=16.20.1'} + peerDependencies: + '@aws-sdk/credential-providers': ^3.188.0 + '@mongodb-js/zstd': ^1.1.0 || ^2.0.0 + gcp-metadata: ^5.2.0 + kerberos: ^2.0.1 + mongodb-client-encryption: '>=6.0.0 <7' + snappy: ^7.2.2 + socks: ^2.7.1 + peerDependenciesMeta: + '@aws-sdk/credential-providers': + optional: true + '@mongodb-js/zstd': + optional: true + gcp-metadata: + optional: true + kerberos: + optional: true + mongodb-client-encryption: + optional: true + snappy: + optional: true + socks: + optional: true + + mongoose@8.9.3: + resolution: {integrity: sha512-G50GNPdMqhoiRAJ/24GYAzg13yxXDD3FOOFeYiFwtHmHpAJem3hxbYIxAhLJGWbYEiUZL0qFMu2LXYkgGAmo+Q==} + engines: {node: '>=16.20.1'} + + mpath@0.9.0: + resolution: {integrity: sha512-ikJRQTk8hw5DEoFVxHG1Gn9T/xcjtdnOKIU1JTmGjZZlg9LST2mBLmcX3/ICIbgJydT2GOc15RnNy5mHmzfSew==} + engines: {node: '>=4.0.0'} + + mquery@5.0.0: + resolution: {integrity: sha512-iQMncpmEK8R8ncT8HJGsGc9Dsp8xcgYMVSbs5jgnm1lFHTZqMJTUWTDx1LBO8+mK3tPNZWFLBghQEIOULSTHZg==} + engines: {node: '>=14.0.0'} + mrmime@2.0.0: resolution: {integrity: sha512-eu38+hdgojoyq63s+yTpN4XMBdt5l8HhMhc4VKLO9KM5caLIBvUm4thi7fFaxyTmCKeNnXZ5pAlBwCUnhA09uw==} engines: {node: '>=10'} @@ -8376,6 +8489,10 @@ packages: neo-async@2.6.2: resolution: {integrity: sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==} + new-find-package-json@2.0.0: + resolution: {integrity: sha512-lDcBsjBSMlj3LXH2v/FW3txlh2pYTjmbOXPYJD93HI5EwuLzI11tdHSIpUMmfq/IOsldj4Ps8M8flhm+pCK4Ew==} + engines: {node: '>=12.22.0'} + no-case@3.0.4: resolution: {integrity: sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==} @@ -8891,6 +9008,9 @@ packages: resolution: {integrity: sha512-iuh7L6jA7JEGu2WxDwtQP1ddOpaJNC4KlDEFfdQajSGgGPNi4OyDc2R7QnbY2bR9QjBVGwgvTdNJZoE7RaxUMA==} engines: {node: '>=0.12'} + pend@1.2.0: + resolution: {integrity: sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==} + periscopic@3.1.0: resolution: {integrity: sha512-vKiQ8RRtkl9P+r/+oefh25C3fhybptkHKCZSPlcXiJux2tJF55GnEj3BVn4A5gKfq9NWWXXrxkHBwVPUfH0opw==} @@ -9363,6 +9483,9 @@ packages: queue-microtask@1.2.3: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} + queue-tick@1.0.1: + resolution: {integrity: sha512-kJt5qhMxoszgU/62PLP1CJytzd2NKetjSRnyuj31fDd3Rlcz3fzlFdFLD1SItunPwyqEOkca6GbV612BWfaBag==} + queue@6.0.2: resolution: {integrity: sha512-iHZWu+q3IdFZFX36ro/lKBkSvfkztY5Y7HMiPlOUjhupPcG2JMfst2KKEpu5XndviX/3UhFbRngUPNKtgvtZiA==} @@ -9974,6 +10097,9 @@ packages: resolution: {integrity: sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==} engines: {node: '>= 0.4'} + sift@17.1.3: + resolution: {integrity: sha512-Rtlj66/b0ICeFzYTuNvX/EF1igRbbnGSvEyT79McoZa/DeGhMyC5pWKOEsZKnpkqtSeovd5FL/bjHWC3CIIvCQ==} + signal-exit@3.0.7: resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} @@ -10114,6 +10240,9 @@ packages: space-separated-tokens@2.0.2: resolution: {integrity: sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==} + sparse-bitfield@3.0.3: + resolution: {integrity: sha512-kvzhi7vqKTfkh0PZU+2D2PIllw2ymqJKujUcyPMd9Y75Nv4nPbGJZXNhxsgdQab2BmlDct1YnfQCguEvHr7VsQ==} + spawn-wrap@2.0.0: resolution: {integrity: sha512-EeajNjfN9zMnULLwhZZQU3GWBoFNkbngTUPfaawT4RkMiviTxcX0qfhVbGey39mfctfDHkWtuecgQ8NJcyQWHg==} engines: {node: '>=8'} @@ -10199,6 +10328,9 @@ packages: resolution: {integrity: sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==} engines: {node: '>=10.0.0'} + streamx@2.21.1: + resolution: {integrity: sha512-PhP9wUnFLa+91CPy3N6tiQsK+gnYyUNuk15S3YG/zjYE7RuPeCjJngqnzpC31ow0lzBHQ+QGO4cNJnd0djYUsw==} + string-argv@0.3.2: resolution: {integrity: sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==} engines: {node: '>=0.6.19'} @@ -10418,6 +10550,9 @@ packages: resolution: {integrity: sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==} engines: {node: '>=6'} + tar-stream@3.1.7: + resolution: {integrity: sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ==} + tar@6.2.1: resolution: {integrity: sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==} engines: {node: '>=10'} @@ -10455,6 +10590,9 @@ packages: resolution: {integrity: sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==} engines: {node: '>=8'} + text-decoder@1.2.3: + resolution: {integrity: sha512-3/o9z3X0X0fTupwsYvR03pJ/DjWuqqrfwBgTQzdWDiQSm9KitAyz/9WqsT2JQW7KV2m+bC2ol/zqpW37NHxLaA==} + text-extensions@1.9.0: resolution: {integrity: sha512-wiBrwC1EhBelW12Zy26JeOUkQ5mRu+5o8rpsJk5+2t+Y5vE7e842qtZDQ2g1NpX/29HdyFeJ4nSIhI47ENSxlQ==} engines: {node: '>=0.10'} @@ -10530,6 +10668,10 @@ packages: tr46@0.0.3: resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} + tr46@4.1.1: + resolution: {integrity: sha512-2lv/66T7e5yNyhAAC4NaKe5nVavzuGJQVVtRYLyQ2OI8tsJ61PMLlelehb0wi2Hx6+hT/OJUWZcw8MjlSRnxvw==} + engines: {node: '>=14'} + tree-kill@1.2.2: resolution: {integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==} hasBin: true @@ -11071,6 +11213,10 @@ packages: webidl-conversions@3.0.1: resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} + webidl-conversions@7.0.0: + resolution: {integrity: sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==} + engines: {node: '>=12'} + webpack-bundle-analyzer@4.10.2: resolution: {integrity: sha512-vJptkMm9pk5si4Bv922ZbKLV8UTT4zib4FPgXMhgzUny0bfDDkLXAVQs3ly3fS4/TN9ROFtb0NFrm04UXFE/Vw==} engines: {node: '>= 10.13.0'} @@ -11149,6 +11295,10 @@ packages: resolution: {integrity: sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==} engines: {node: '>=18'} + whatwg-url@13.0.0: + resolution: {integrity: sha512-9WWbymnqj57+XEuqADHrCJ2eSXzn8WXIW/YSGaZtb2WKAInQ6CHfaUUcTyyver0p8BDg5StLQq8h1vtZuwmOig==} + engines: {node: '>=16'} + whatwg-url@5.0.0: resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==} @@ -11359,6 +11509,10 @@ packages: resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==} engines: {node: '>=12'} + yauzl@3.2.0: + resolution: {integrity: sha512-Ow9nuGZE+qp1u4JIPvg+uCiUr7xGQWdff7JQSk5VGYTAZMDe2q8lxJ10ygv10qmSj031Ty/6FNJpLO4o1Sgc+w==} + engines: {node: '>=12'} + yn@2.0.0: resolution: {integrity: sha512-uTv8J/wiWTgUTg+9vLTi//leUl5vDQS6uii/emeTb2ssY7vl6QWf2fFbIIGjnhjvbdKlU0ed7QPgY1htTC86jQ==} engines: {node: '>=4'} @@ -12776,12 +12930,12 @@ snapshots: cssnano-preset-advanced: 6.1.2(postcss@8.4.38) postcss: 8.4.38 postcss-sort-media-queries: 5.2.0(postcss@8.4.38) - tslib: 2.7.0 + tslib: 2.8.1 '@docusaurus/logger@3.5.2': dependencies: chalk: 4.1.2 - tslib: 2.7.0 + tslib: 2.8.1 '@docusaurus/mdx-loader@3.5.2(@docusaurus/types@3.5.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.6.3)': dependencies: @@ -12805,7 +12959,7 @@ snapshots: remark-frontmatter: 5.0.0 remark-gfm: 4.0.0 stringify-object: 3.3.0 - tslib: 2.7.0 + tslib: 2.8.1 unified: 11.0.4 unist-util-visit: 5.0.0 url-loader: 4.1.1(file-loader@6.2.0(webpack@5.92.1))(webpack@5.92.1) @@ -12857,7 +13011,7 @@ snapshots: react-dom: 18.3.1(react@18.3.1) reading-time: 1.5.0 srcset: 4.0.0 - tslib: 2.7.0 + tslib: 2.8.1 unist-util-visit: 5.0.0 utility-types: 3.11.0 webpack: 5.92.1 @@ -12898,7 +13052,7 @@ snapshots: lodash: 4.17.21 react: 18.3.1 react-dom: 18.3.1(react@18.3.1) - tslib: 2.7.0 + tslib: 2.8.1 utility-types: 3.11.0 webpack: 5.92.1 transitivePeerDependencies: @@ -12938,7 +13092,7 @@ snapshots: lodash: 4.17.21 react: 18.3.1 react-dom: 18.3.1(react@18.3.1) - tslib: 2.7.0 + tslib: 2.8.1 utility-types: 3.11.0 webpack: 5.92.1 transitivePeerDependencies: @@ -12970,7 +13124,7 @@ snapshots: fs-extra: 11.2.0 react: 18.3.1 react-dom: 18.3.1(react@18.3.1) - tslib: 2.7.0 + tslib: 2.8.1 webpack: 5.92.1 transitivePeerDependencies: - '@mdx-js/react' @@ -13000,7 +13154,7 @@ snapshots: react: 18.3.1 react-dom: 18.3.1(react@18.3.1) react-json-view-lite: 1.4.0(react@18.3.1) - tslib: 2.7.0 + tslib: 2.8.1 transitivePeerDependencies: - '@mdx-js/react' - '@parcel/css' @@ -13027,7 +13181,7 @@ snapshots: '@docusaurus/utils-validation': 3.5.2(@docusaurus/types@3.5.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(typescript@5.6.3) react: 18.3.1 react-dom: 18.3.1(react@18.3.1) - tslib: 2.7.0 + tslib: 2.8.1 transitivePeerDependencies: - '@mdx-js/react' - '@parcel/css' @@ -13055,7 +13209,7 @@ snapshots: '@types/gtag.js': 0.0.12 react: 18.3.1 react-dom: 18.3.1(react@18.3.1) - tslib: 2.7.0 + tslib: 2.8.1 transitivePeerDependencies: - '@mdx-js/react' - '@parcel/css' @@ -13082,7 +13236,7 @@ snapshots: '@docusaurus/utils-validation': 3.5.2(@docusaurus/types@3.5.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(typescript@5.6.3) react: 18.3.1 react-dom: 18.3.1(react@18.3.1) - tslib: 2.7.0 + tslib: 2.8.1 transitivePeerDependencies: - '@mdx-js/react' - '@parcel/css' @@ -13114,7 +13268,7 @@ snapshots: react: 18.3.1 react-dom: 18.3.1(react@18.3.1) sitemap: 7.1.2 - tslib: 2.7.0 + tslib: 2.8.1 transitivePeerDependencies: - '@mdx-js/react' - '@parcel/css' @@ -13296,7 +13450,7 @@ snapshots: lodash: 4.17.21 react: 18.3.1 react-dom: 18.3.1(react@18.3.1) - tslib: 2.7.0 + tslib: 2.8.1 utility-types: 3.11.0 transitivePeerDependencies: - '@algolia/client-search' @@ -13324,7 +13478,7 @@ snapshots: '@docusaurus/theme-translations@3.5.2': dependencies: fs-extra: 11.2.0 - tslib: 2.7.0 + tslib: 2.8.1 '@docusaurus/tsconfig@3.5.2': {} @@ -13350,7 +13504,7 @@ snapshots: '@docusaurus/utils-common@3.5.2(@docusaurus/types@3.5.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1))': dependencies: - tslib: 2.7.0 + tslib: 2.8.1 optionalDependencies: '@docusaurus/types': 3.5.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) @@ -13363,7 +13517,7 @@ snapshots: joi: 17.13.1 js-yaml: 4.1.0 lodash: 4.17.21 - tslib: 2.7.0 + tslib: 2.8.1 transitivePeerDependencies: - '@docusaurus/types' - '@swc/core' @@ -13391,7 +13545,7 @@ snapshots: prompts: 2.4.2 resolve-pathname: 3.0.0 shelljs: 0.8.5 - tslib: 2.7.0 + tslib: 2.8.1 url-loader: 4.1.1(file-loader@6.2.0(webpack@5.92.1))(webpack@5.92.1) utility-types: 3.11.0 webpack: 5.92.1 @@ -13453,17 +13607,17 @@ snapshots: '@emnapi/core@1.2.0': dependencies: '@emnapi/wasi-threads': 1.0.1 - tslib: 2.7.0 + tslib: 2.8.1 optional: true '@emnapi/runtime@1.2.0': dependencies: - tslib: 2.7.0 + tslib: 2.8.1 optional: true '@emnapi/wasi-threads@1.0.1': dependencies: - tslib: 2.7.0 + tslib: 2.8.1 optional: true '@eslint-community/eslint-utils@4.4.0(eslint@8.57.0)': @@ -14222,6 +14376,10 @@ snapshots: '@microsoft/tsdoc@0.15.1': {} + '@mongodb-js/saslprep@1.1.9': + dependencies: + sparse-bitfield: 3.0.3 + '@napi-rs/wasm-runtime@0.2.4': dependencies: '@emnapi/core': 1.2.0 @@ -14291,6 +14449,13 @@ snapshots: class-transformer: 0.5.1 class-validator: 0.14.1 + '@nestjs/mongoose@10.1.0(@nestjs/common@10.4.15(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.15(@nestjs/common@10.4.15(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.15)(@nestjs/websockets@10.4.15)(encoding@0.1.13)(reflect-metadata@0.2.2)(rxjs@7.8.1))(mongoose@8.9.3(socks@2.8.3))(rxjs@7.8.1)': + dependencies: + '@nestjs/common': 10.4.15(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1) + '@nestjs/core': 10.4.15(@nestjs/common@10.4.15(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.15)(@nestjs/websockets@10.4.15)(encoding@0.1.13)(reflect-metadata@0.2.2)(rxjs@7.8.1) + mongoose: 8.9.3(socks@2.8.3) + rxjs: 7.8.1 + '@nestjs/platform-express@10.4.15(@nestjs/common@10.4.15(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.15)': dependencies: '@nestjs/common': 10.4.15(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1) @@ -14871,7 +15036,7 @@ snapshots: '@nrwl/tao@19.2.0': dependencies: nx: 19.2.0 - tslib: 2.7.0 + tslib: 2.8.1 transitivePeerDependencies: - '@swc-node/register' - '@swc/core' @@ -14895,7 +15060,7 @@ snapshots: nx: 19.2.0 semver: 7.6.3 tmp: 0.2.3 - tslib: 2.7.0 + tslib: 2.8.1 yargs-parser: 21.1.1 '@nx/nx-darwin-arm64@19.2.0': @@ -14954,7 +15119,7 @@ snapshots: supports-color: 8.1.1 supports-hyperlinks: 2.3.0 ts-node: 10.9.2(@types/node@22.10.5)(typescript@5.7.2) - tslib: 2.7.0 + tslib: 2.8.1 widest-line: 3.1.0 wordwrap: 1.0.0 wrap-ansi: 7.0.0 @@ -14991,7 +15156,7 @@ snapshots: supports-color: 8.1.1 supports-hyperlinks: 2.3.0 ts-node: 10.9.2(@types/node@22.10.5)(typescript@5.7.2) - tslib: 2.6.3 + tslib: 2.8.1 widest-line: 3.1.0 wordwrap: 1.0.0 wrap-ansi: 7.0.0 @@ -15160,18 +15325,18 @@ snapshots: dependencies: asn1js: 3.0.5 pvtsutils: 1.3.5 - tslib: 2.7.0 + tslib: 2.8.1 '@peculiar/json-schema@1.1.12': dependencies: - tslib: 2.7.0 + tslib: 2.8.1 '@peculiar/webcrypto@1.5.0': dependencies: '@peculiar/asn1-schema': 2.3.8 '@peculiar/json-schema': 1.1.12 pvtsutils: 1.3.5 - tslib: 2.7.0 + tslib: 2.8.1 webcrypto-core: 1.8.0 '@pkgjs/parseargs@0.11.0': @@ -15486,7 +15651,7 @@ snapshots: '@tybys/wasm-util@0.9.0': dependencies: - tslib: 2.7.0 + tslib: 2.8.1 optional: true '@typechain/ethers-v6@0.5.1(ethers@6.13.4)(typechain@8.3.2(typescript@5.6.3))(typescript@5.6.3)': @@ -15856,6 +16021,12 @@ snapshots: '@types/validator@13.12.2': {} + '@types/webidl-conversions@7.0.3': {} + + '@types/whatwg-url@11.0.5': + dependencies: + '@types/webidl-conversions': 7.0.3 + '@types/ws@7.4.7': dependencies: '@types/node': 22.9.0 @@ -16121,7 +16292,7 @@ snapshots: busboy: 1.6.0 fast-querystring: 1.1.2 fast-url-parser: 1.1.3 - tslib: 2.7.0 + tslib: 2.8.1 '@xtuc/ieee754@1.2.0': {} @@ -16132,7 +16303,7 @@ snapshots: '@yarnpkg/parsers@3.0.0-rc.46': dependencies: js-yaml: 3.14.1 - tslib: 2.7.0 + tslib: 2.8.1 '@zk-kit/baby-jubjub@1.0.3': dependencies: @@ -16230,6 +16401,8 @@ snapshots: transitivePeerDependencies: - supports-color + agent-base@7.1.3: {} + aggregate-error@3.1.0: dependencies: clean-stack: 2.2.0 @@ -16476,7 +16649,7 @@ snapshots: dependencies: pvtsutils: 1.3.5 pvutils: 1.1.3 - tslib: 2.7.0 + tslib: 2.8.1 assemblyscript@0.19.10: dependencies: @@ -16499,6 +16672,10 @@ snapshots: astring@1.8.6: {} + async-mutex@0.5.0: + dependencies: + tslib: 2.8.1 + async@1.5.2: {} async@2.6.4: @@ -16641,6 +16818,9 @@ snapshots: balanced-match@1.0.2: {} + bare-events@2.5.3: + optional: true + base-x@3.0.9: dependencies: safe-buffer: 5.2.1 @@ -16878,6 +17058,8 @@ snapshots: dependencies: node-int64: 0.4.0 + bson@6.10.1: {} + buffer-alloc-unsafe@1.1.0: {} buffer-alloc@1.2.0: @@ -16885,6 +17067,8 @@ snapshots: buffer-alloc-unsafe: 1.1.0 buffer-fill: 1.0.0 + buffer-crc32@0.2.13: {} + buffer-fill@1.0.0: {} buffer-from@1.1.2: {} @@ -16960,7 +17144,7 @@ snapshots: camel-case@4.1.2: dependencies: pascal-case: 3.1.2 - tslib: 2.7.0 + tslib: 2.8.1 camelcase-keys@6.2.2: dependencies: @@ -18096,7 +18280,7 @@ snapshots: dot-case@3.0.4: dependencies: no-case: 3.0.4 - tslib: 2.7.0 + tslib: 2.8.1 dot-prop@5.3.0: dependencies: @@ -19173,6 +19357,10 @@ snapshots: optionalDependencies: debug: 4.3.6(supports-color@8.1.1) + follow-redirects@1.15.9(debug@4.3.7): + optionalDependencies: + debug: 4.3.7(supports-color@8.1.1) + for-each@0.3.3: dependencies: is-callable: 1.2.7 @@ -20207,6 +20395,13 @@ snapshots: transitivePeerDependencies: - supports-color + https-proxy-agent@7.0.6: + dependencies: + agent-base: 7.1.3 + debug: 4.3.7(supports-color@8.1.1) + transitivePeerDependencies: + - supports-color + human-signals@2.1.0: {} human-signals@5.0.0: {} @@ -21258,6 +21453,8 @@ snapshots: just-diff@6.0.2: {} + kareem@2.6.3: {} + katex@0.16.10: dependencies: commander: 8.3.0 @@ -21615,7 +21812,7 @@ snapshots: lower-case@2.0.2: dependencies: - tslib: 2.7.0 + tslib: 2.8.1 lowercase-keys@3.0.0: {} @@ -21921,6 +22118,8 @@ snapshots: dependencies: fs-monkey: 1.0.6 + memory-pager@1.5.0: {} + memorystream@0.3.1: {} meow@12.1.1: {} @@ -22429,6 +22628,84 @@ snapshots: modify-values@1.0.1: {} + mongodb-connection-string-url@3.0.1: + dependencies: + '@types/whatwg-url': 11.0.5 + whatwg-url: 13.0.0 + + mongodb-memory-server-core@10.1.3(socks@2.8.3): + dependencies: + async-mutex: 0.5.0 + camelcase: 6.3.0 + debug: 4.3.7(supports-color@8.1.1) + find-cache-dir: 3.3.2 + follow-redirects: 1.15.9(debug@4.3.7) + https-proxy-agent: 7.0.6 + mongodb: 6.12.0(socks@2.8.3) + new-find-package-json: 2.0.0 + semver: 7.6.3 + tar-stream: 3.1.7 + tslib: 2.8.1 + yauzl: 3.2.0 + transitivePeerDependencies: + - '@aws-sdk/credential-providers' + - '@mongodb-js/zstd' + - gcp-metadata + - kerberos + - mongodb-client-encryption + - snappy + - socks + - supports-color + + mongodb-memory-server@10.1.3(socks@2.8.3): + dependencies: + mongodb-memory-server-core: 10.1.3(socks@2.8.3) + tslib: 2.8.1 + transitivePeerDependencies: + - '@aws-sdk/credential-providers' + - '@mongodb-js/zstd' + - gcp-metadata + - kerberos + - mongodb-client-encryption + - snappy + - socks + - supports-color + + mongodb@6.12.0(socks@2.8.3): + dependencies: + '@mongodb-js/saslprep': 1.1.9 + bson: 6.10.1 + mongodb-connection-string-url: 3.0.1 + optionalDependencies: + socks: 2.8.3 + + mongoose@8.9.3(socks@2.8.3): + dependencies: + bson: 6.10.1 + kareem: 2.6.3 + mongodb: 6.12.0(socks@2.8.3) + mpath: 0.9.0 + mquery: 5.0.0 + ms: 2.1.3 + sift: 17.1.3 + transitivePeerDependencies: + - '@aws-sdk/credential-providers' + - '@mongodb-js/zstd' + - gcp-metadata + - kerberos + - mongodb-client-encryption + - snappy + - socks + - supports-color + + mpath@0.9.0: {} + + mquery@5.0.0: + dependencies: + debug: 4.3.7(supports-color@8.1.1) + transitivePeerDependencies: + - supports-color + mrmime@2.0.0: {} ms@2.0.0: {} @@ -22515,10 +22792,16 @@ snapshots: neo-async@2.6.2: {} + new-find-package-json@2.0.0: + dependencies: + debug: 4.3.7(supports-color@8.1.1) + transitivePeerDependencies: + - supports-color + no-case@3.0.4: dependencies: lower-case: 2.0.2 - tslib: 2.7.0 + tslib: 2.8.1 node-abort-controller@3.1.1: {} @@ -22710,7 +22993,7 @@ snapshots: tar-stream: 2.2.0 tmp: 0.2.3 tsconfig-paths: 4.2.0 - tslib: 2.7.0 + tslib: 2.8.1 yargs: 17.7.2 yargs-parser: 21.1.1 optionalDependencies: @@ -23015,7 +23298,7 @@ snapshots: param-case@3.0.4: dependencies: dot-case: 3.0.4 - tslib: 2.7.0 + tslib: 2.8.1 parent-module@1.0.1: dependencies: @@ -23084,7 +23367,7 @@ snapshots: pascal-case@3.1.2: dependencies: no-case: 3.0.4 - tslib: 2.7.0 + tslib: 2.8.1 password-prompt@1.1.3: dependencies: @@ -23151,6 +23434,8 @@ snapshots: safe-buffer: 5.2.1 sha.js: 2.4.11 + pend@1.2.0: {} + periscopic@3.1.0: dependencies: '@types/estree': 1.0.5 @@ -23554,7 +23839,7 @@ snapshots: pvtsutils@1.3.5: dependencies: - tslib: 2.7.0 + tslib: 2.8.1 pvutils@1.1.3: {} @@ -23568,6 +23853,8 @@ snapshots: queue-microtask@1.2.3: {} + queue-tick@1.0.1: {} + queue@6.0.2: dependencies: inherits: 2.0.4 @@ -24366,6 +24653,8 @@ snapshots: get-intrinsic: 1.2.4 object-inspect: 1.13.1 + sift@17.1.3: {} + signal-exit@3.0.7: {} signal-exit@4.1.0: {} @@ -24425,7 +24714,7 @@ snapshots: snake-case@3.0.4: dependencies: dot-case: 3.0.4 - tslib: 2.7.0 + tslib: 2.8.1 snarkjs@0.5.0: dependencies: @@ -24624,6 +24913,10 @@ snapshots: space-separated-tokens@2.0.2: {} + sparse-bitfield@3.0.3: + dependencies: + memory-pager: 1.5.0 + spawn-wrap@2.0.0: dependencies: foreground-child: 2.0.0 @@ -24720,6 +25013,14 @@ snapshots: streamsearch@1.1.0: {} + streamx@2.21.1: + dependencies: + fast-fifo: 1.3.2 + queue-tick: 1.0.1 + text-decoder: 1.2.3 + optionalDependencies: + bare-events: 2.5.3 + string-argv@0.3.2: {} string-format@2.0.0: {} @@ -24951,7 +25252,7 @@ snapshots: synckit@0.9.1: dependencies: '@pkgr/core': 0.1.1 - tslib: 2.7.0 + tslib: 2.8.1 table-layout@1.0.2: dependencies: @@ -24997,6 +25298,12 @@ snapshots: inherits: 2.0.4 readable-stream: 3.6.2 + tar-stream@3.1.7: + dependencies: + b4a: 1.6.6 + fast-fifo: 1.3.2 + streamx: 2.21.1 + tar@6.2.1: dependencies: chownr: 2.0.0 @@ -25048,6 +25355,10 @@ snapshots: glob: 7.2.3 minimatch: 3.1.2 + text-decoder@1.2.3: + dependencies: + b4a: 1.6.6 + text-extensions@1.9.0: {} text-extensions@2.4.0: {} @@ -25119,6 +25430,10 @@ snapshots: tr46@0.0.3: {} + tr46@4.1.1: + dependencies: + punycode: 2.3.1 + tree-kill@1.2.2: {} treeverse@3.0.0: {} @@ -25730,10 +26045,12 @@ snapshots: '@peculiar/json-schema': 1.1.12 asn1js: 3.0.5 pvtsutils: 1.3.5 - tslib: 2.7.0 + tslib: 2.8.1 webidl-conversions@3.0.1: {} + webidl-conversions@7.0.0: {} + webpack-bundle-analyzer@4.10.2: dependencies: '@discoveryjs/json-ext': 0.5.7 @@ -25934,6 +26251,11 @@ snapshots: whatwg-mimetype@4.0.0: {} + whatwg-url@13.0.0: + dependencies: + tr46: 4.1.1 + webidl-conversions: 7.0.0 + whatwg-url@5.0.0: dependencies: tr46: 0.0.3 @@ -26167,6 +26489,11 @@ snapshots: y18n: 5.0.8 yargs-parser: 21.1.1 + yauzl@3.2.0: + dependencies: + buffer-crc32: 0.2.13 + pend: 1.2.0 + yn@2.0.0: {} yn@3.1.1: {}