diff --git a/package.json b/package.json index 6a33fb4..e62ec28 100644 --- a/package.json +++ b/package.json @@ -35,6 +35,7 @@ "jsonwebtoken": "^8.5.1", "mime": "^3.0.0", "mkdirp": "^1.0.4", + "mongodb": "^4.10.0", "morgan": "^1.10.0", "multer": "^1.4.2", "nedb": "^1.8.0", diff --git a/src/backend/utils/appConfig.ts b/src/backend/utils/appConfig.ts index 2ab6b43..73e241b 100644 --- a/src/backend/utils/appConfig.ts +++ b/src/backend/utils/appConfig.ts @@ -19,6 +19,13 @@ const LocalDatabaseConfig = z.object({ }) }) +const MongoDatabaseConfig = z.object({ + driver: z.literal('mongodb'), + mongodb: z.object({ + uri: z.string() + }) +}) + const AuthConfig = z.object({ secret: z.string() // Secret for JWT }) @@ -48,7 +55,7 @@ const AppConfig = z.object({ password: z.string(), // Password for admin panel frontend: FrontendConfig, // Frontend configuration auth: AuthConfig, // Auth configuration - database: LocalDatabaseConfig, // Database configuration + database: z.union([LocalDatabaseConfig, MongoDatabaseConfig]), // Database configuration }) export type AppConfig = z.infer; diff --git a/src/backend/utils/database/index.ts b/src/backend/utils/database/index.ts index caf23d8..6c0529b 100644 --- a/src/backend/utils/database/index.ts +++ b/src/backend/utils/database/index.ts @@ -1,175 +1,16 @@ -import Datastore from 'nedb'; import { AliasData } from '../../models/alias.js'; import { FileData } from '../../models/file.js'; import { PageData } from '../../models/page.js'; import { PageOrderData } from '../../models/pageOrder.js'; -import initDb from './initDb.js'; +import appConfig from "../appConfig.js"; +import LocalDatabaseDriver from "./local.js"; +import MongoDatabaseDriver from "./mongodb.js"; -/** - * @typedef Options - optional params - * @param {boolean} multi - (false) allows to take action to several documents - * @param {boolean} upsert - (false) if true, upsert document with update fields. - * Method will return inserted doc or number of affected docs if doc hasn't been inserted - * @param {boolean} returnUpdatedDocs - (false) if true, returns affected docs - */ -interface Options { - multi?: boolean; - upsert?: boolean; - returnUpdatedDocs?: boolean; -} - -interface ResolveFunction { - (value: any): void; -} - -interface RejectFunction { - (reason?: unknown): void; -} - -/** - * @class Database - * @classdesc Simple decorator class to work with nedb datastore - * - * @property {Datastore} db - nedb Datastore object - */ -export class Database { - private db: Datastore; - /** - * @class - * - * @param {Object} nedbInstance - nedb Datastore object - */ - constructor(nedbInstance: Datastore) { - this.db = nedbInstance; - } - - /** - * Insert new document into the database - * - * @see https://github.com/louischatriot/nedb#inserting-documents - * - * @param {Object} doc - object to insert - * @returns {Promise} - inserted doc or Error object - */ - public async insert(doc: DocType): Promise { - return new Promise((resolve, reject) => this.db.insert(doc, (err, newDoc) => { - if (err) { - reject(err); - } - - resolve(newDoc); - })); - } - - /** - * Find documents that match passed query - * - * @see https://github.com/louischatriot/nedb#finding-documents - * - * @param {Object} query - query object - * @param {Object} projection - projection object - * @returns {Promise|Error>} - found docs or Error object - */ - public async find(query: Record, projection?: DocType): Promise> { - const cbk = (resolve: ResolveFunction, reject: RejectFunction) => (err: Error | null, docs: DocType[]) => { - if (err) { - reject(err); - } - - resolve(docs); - }; - - return new Promise((resolve, reject) => { - if (projection) { - this.db.find(query, projection, cbk(resolve, reject)); - } else { - this.db.find(query, cbk(resolve, reject)); - } - }); - } - - /** - * Find one document matches passed query - * - * @see https://github.com/louischatriot/nedb#finding-documents - * - * @param {Object} query - query object - * @param {Object} projection - projection object - * @returns {Promise} - found doc or Error object - */ - public async findOne(query: Record, projection?: DocType): Promise { - const cbk = (resolve: ResolveFunction, reject: RejectFunction) => (err: Error | null, doc: DocType) => { - if (err) { - reject(err); - } - - resolve(doc); - }; - - return new Promise((resolve, reject) => { - if (projection) { - this.db.findOne(query, projection, cbk(resolve, reject)); - } else { - this.db.findOne(query, cbk(resolve, reject)); - } - }); - } - - /** - * Update document matches query - * - * @see https://github.com/louischatriot/nedb#updating-documents - * - * @param {Object} query - query object - * @param {Object} update - fields to update - * @param {Options} options - optional params - * @returns {Promise} - number of updated rows or affected docs or Error object - */ - public async update(query: Record, update: DocType, options: Options = {}): Promise> { - return new Promise((resolve, reject) => this.db.update(query, update, options, (err, result, affectedDocs) => { - if (err) { - reject(err); - } - - switch (true) { - case options.returnUpdatedDocs: - resolve(affectedDocs); - break; - case options.upsert: - if (affectedDocs) { - resolve(affectedDocs); - } - resolve(result); - break; - default: - resolve(result); - } - })); - } - - /** - * Remove document matches passed query - * - * @see https://github.com/louischatriot/nedb#removing-documents - * - * @param {Object} query - query object - * @param {Options} options - optional params - * @returns {Promise} - number of removed rows or Error object - */ - public async remove(query: Record, options: Options = {}): Promise { - return new Promise((resolve, reject) => this.db.remove(query, options, (err, result) => { - if (err) { - reject(err); - } - - resolve(result); - })); - } -} +const Database = appConfig.database.driver === 'mongodb' ? MongoDatabaseDriver : LocalDatabaseDriver; export default { - pages: new Database(initDb('pages')), - aliases: new Database(initDb('aliases')), - pagesOrder: new Database(initDb('pagesOrder')), - files: new Database(initDb('files')), + pages: new Database('pages'), + aliases: new Database('aliases'), + pagesOrder: new Database('pagesOrder'), + files: new Database('files'), }; diff --git a/src/backend/utils/database/initDb.ts b/src/backend/utils/database/initDb.ts deleted file mode 100644 index ae24d41..0000000 --- a/src/backend/utils/database/initDb.ts +++ /dev/null @@ -1,16 +0,0 @@ -import Datastore from 'nedb'; -import path from 'path'; -import appConfig from "../appConfig.js"; - -/** - * Init function for nedb instance - * - * @param {string} name - name of the data file - * @returns {Datastore} db - nedb instance - */ -export default function initDb(name: string): Datastore { - return new Datastore({ - filename: path.resolve(`${appConfig.database.local.path}/${name}.db`), - autoload: true, - }); -} diff --git a/src/backend/utils/database/local.ts b/src/backend/utils/database/local.ts new file mode 100644 index 0000000..c7162db --- /dev/null +++ b/src/backend/utils/database/local.ts @@ -0,0 +1,162 @@ +import Datastore from "nedb"; +import {DatabaseDriver, Options, RejectFunction, ResolveFunction} from "./types.js"; +import path from "path"; +import appConfig from "../appConfig.js"; + +/** + * Init function for nedb instance + * + * @param {string} name - name of the data file + * @returns {Datastore} db - nedb instance + */ +function initDb(name: string): Datastore { + const dbConfig = appConfig.database.driver === 'local' ? appConfig.database.local : null; + if (!dbConfig) { + throw new Error('Database config is not initialized'); + } + return new Datastore({ + filename: path.resolve(`${dbConfig.path}/${name}.db`), + autoload: true, + }); +} + + +/** + * Simple decorator class to work with nedb datastore + */ +export default class LocalDatabaseDriver implements DatabaseDriver { + /** + * nedb Datastore object + */ + private db: Datastore; + + /** + * @param collectionName - collection name for storing data + */ + constructor(collectionName: string) { + this.db = initDb(collectionName); + } + + /** + * Insert new document into the database + * + * @see https://github.com/louischatriot/nedb#inserting-documents + * + * @param {Object} doc - object to insert + * @returns {Promise} - inserted doc or Error object + */ + public async insert(doc: DocType): Promise { + return new Promise((resolve, reject) => this.db.insert(doc, (err, newDoc) => { + if (err) { + reject(err); + } + + resolve(newDoc); + })); + } + + /** + * Find documents that match passed query + * + * @see https://github.com/louischatriot/nedb#finding-documents + * + * @param {Object} query - query object + * @param {Object} projection - projection object + * @returns {Promise|Error>} - found docs or Error object + */ + public async find(query: Record, projection?: DocType): Promise> { + const cbk = (resolve: ResolveFunction, reject: RejectFunction) => (err: Error | null, docs: DocType[]) => { + if (err) { + reject(err); + } + + resolve(docs); + }; + + return new Promise((resolve, reject) => { + if (projection) { + this.db.find(query, projection, cbk(resolve, reject)); + } else { + this.db.find(query, cbk(resolve, reject)); + } + }); + } + + /** + * Find one document matches passed query + * + * @see https://github.com/louischatriot/nedb#finding-documents + * + * @param {Object} query - query object + * @param {Object} projection - projection object + * @returns {Promise} - found doc or Error object + */ + public async findOne(query: Record, projection?: DocType): Promise { + const cbk = (resolve: ResolveFunction, reject: RejectFunction) => (err: Error | null, doc: DocType) => { + if (err) { + reject(err); + } + + resolve(doc); + }; + + return new Promise((resolve, reject) => { + if (projection) { + this.db.findOne(query, projection, cbk(resolve, reject)); + } else { + this.db.findOne(query, cbk(resolve, reject)); + } + }); + } + + /** + * Update document matches query + * + * @see https://github.com/louischatriot/nedb#updating-documents + * + * @param {Object} query - query object + * @param {Object} update - fields to update + * @param {Options} options - optional params + * @returns {Promise} - number of updated rows or affected docs or Error object + */ + public async update(query: Record, update: DocType, options: Options = {}): Promise> { + return new Promise((resolve, reject) => this.db.update(query, update, options, (err, result, affectedDocs) => { + if (err) { + reject(err); + } + + switch (true) { + case options.returnUpdatedDocs: + resolve(affectedDocs); + break; + case options.upsert: + if (affectedDocs) { + resolve(affectedDocs); + } + resolve(result); + break; + default: + resolve(result); + } + })); + } + + /** + * Remove document matches passed query + * + * @see https://github.com/louischatriot/nedb#removing-documents + * + * @param {Object} query - query object + * @param {Options} options - optional params + * @returns {Promise} - number of removed rows or Error object + */ + public async remove(query: Record, options: Options = {}): Promise { + return new Promise((resolve, reject) => this.db.remove(query, options, (err, result) => { + if (err) { + reject(err); + } + + resolve(result); + })); + } +} diff --git a/src/backend/utils/database/mongodb.ts b/src/backend/utils/database/mongodb.ts new file mode 100644 index 0000000..5794cbd --- /dev/null +++ b/src/backend/utils/database/mongodb.ts @@ -0,0 +1,112 @@ +import {Collection, Filter, MongoClient, OptionalUnlessRequiredId, UpdateFilter} from 'mongodb'; +import {DatabaseDriver, Options} from "./types.js"; +import appConfig from "../appConfig.js"; + +const mongodbUri = appConfig.database.driver === 'mongodb' ? appConfig.database.mongodb.uri : null; +const mongodbClient = mongodbUri ? await MongoClient.connect(mongodbUri): null; + +/** + * Simple decorator class to work with nedb datastore + */ +export default class MongoDatabaseDriver implements DatabaseDriver { + /** + * Mongo client instance + */ + private db: MongoClient; + private collection: Collection; + + constructor(collectionName: string) { + if (!mongodbClient) { + throw new Error('MongoDB client is not initialized'); + } + this.db = mongodbClient; + this.collection = mongodbClient.db().collection(collectionName); + } + + /** + * Insert new document into the database + * + * @param {Object} doc - object to insert + * @returns {Promise} - inserted doc or Error object + */ + public async insert(doc: DocType): Promise { + const result = await this.collection.insertOne(doc as OptionalUnlessRequiredId); + return { + ...doc, + _id: result.insertedId, + } + } + + /** + * Find documents that match passed query + * + * @param {Object} query - query object + * @param {Object} projection - projection object + * @returns {Promise|Error>} - found docs or Error object + */ + public async find(query: Record, projection?: DocType): Promise> { + const cursor = this.collection.find(query as Filter) + + if (projection) { + cursor.project(projection); + } + + const docs = await cursor.toArray(); + return docs as unknown as Array; + } + + /** + * Find one document matches passed query + * + * @param {Object} query - query object + * @param {Object} projection - projection object + * @returns {Promise} - found doc or Error object + */ + public async findOne(query: Record, projection?: DocType): Promise { + const doc = await this.collection.findOne(query as Filter, {projection}); + if (!doc) { + throw new Error('Document not found'); + } + + return doc as unknown as DocType; + } + + /** + * Update document matches query + * + * @param {Object} query - query object + * @param {Object} update - fields to update + * @param {Options} options - optional params + * @returns {Promise} - number of updated rows or affected docs or Error object + */ + public async update(query: Record, update: DocType, options: Options = {}): Promise> { + const updateDocument = { + $set: update + } as UpdateFilter; + const result = await this.collection.updateMany(query as Filter, updateDocument, options); + + switch (true) { + case options.returnUpdatedDocs: + return result.modifiedCount + case options.upsert: + if (result.modifiedCount) { + return result.modifiedCount; + } + return result as DocType[] + default: + return result as DocType[] + } + } + + /** + * Remove document matches passed query + * + * @param {Object} query - query object + * @param {Options} options - optional params + * @returns {Promise} - number of removed rows or Error object + */ + public async remove(query: Record, options: Options = {}): Promise { + const result = await this.collection.deleteMany(query as Filter); + return result.deletedCount; + } +} diff --git a/src/backend/utils/database/types.ts b/src/backend/utils/database/types.ts new file mode 100644 index 0000000..8b32741 --- /dev/null +++ b/src/backend/utils/database/types.ts @@ -0,0 +1,28 @@ +export interface DatabaseDriver { + insert(doc: DocType): Promise; + find(query: Record, projection?: DocType): Promise>; + findOne(query: Record, projection?: DocType): Promise; + update(query: Record, update: DocType, options: Options): Promise> + remove(query: Record, options: Options): Promise +} + +/** + * @typedef Options - optional params + * @param {boolean} multi - (false) allows to take action to several documents + * @param {boolean} upsert - (false) if true, upsert document with update fields. + * Method will return inserted doc or number of affected docs if doc hasn't been inserted + * @param {boolean} returnUpdatedDocs - (false) if true, returns affected docs + */ +export interface Options { + multi?: boolean; + upsert?: boolean; + returnUpdatedDocs?: boolean; +} + +export interface ResolveFunction { + (value: any): void; +} + +export interface RejectFunction { + (reason?: unknown): void; +} diff --git a/yarn.lock b/yarn.lock index e48e266..1762e7f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1341,6 +1341,19 @@ version "1.12.9" resolved "https://registry.yarnpkg.com/@types/twig/-/twig-1.12.9.tgz#76502345e4c85c303541dd6d700c3b86c4fed837" +"@types/webidl-conversions@*": + version "7.0.0" + resolved "https://registry.yarnpkg.com/@types/webidl-conversions/-/webidl-conversions-7.0.0.tgz#2b8e60e33906459219aa587e9d1a612ae994cfe7" + integrity sha512-xTE1E+YF4aWPJJeUzaZI5DRntlkY3+BCVJi0axFptnjGmAoWxkyREIh/XMrfxVLejwQxMCfDXdICo0VLxThrog== + +"@types/whatwg-url@^8.2.1": + version "8.2.2" + resolved "https://registry.yarnpkg.com/@types/whatwg-url/-/whatwg-url-8.2.2.tgz#749d5b3873e845897ada99be4448041d4cc39e63" + integrity sha512-FtQu10RWgn3D9U4aazdwIE2yzphmTJREDqNdODHrbrZmmMqI0vMheC/6NE/J1Yveaj8H+ela+YwWTjq5PGmuhA== + dependencies: + "@types/node" "*" + "@types/webidl-conversions" "*" + "@typescript-eslint/eslint-plugin@^4.6.1": version "4.33.0" resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.33.0.tgz#c24dc7c8069c7706bc40d99f6fa87edcb2005276" @@ -1882,6 +1895,13 @@ bson@*: dependencies: buffer "^5.6.0" +bson@^4.7.0: + version "4.7.0" + resolved "https://registry.yarnpkg.com/bson/-/bson-4.7.0.tgz#7874a60091ffc7a45c5dd2973b5cad7cded9718a" + integrity sha512-VrlEE4vuiO1WTpfof4VmaVolCVYkYTgB9iWgYNOrVlnifpME/06fhFRmONgBhClD5pFC1t9ZWqFUQEQAzY43bA== + dependencies: + buffer "^5.6.0" + buffer-equal-constant-time@1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz#f8e71132f7ffe6e01a5c9697a4c6f3e48d5cc819" @@ -2512,6 +2532,11 @@ delayed-stream@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" +denque@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/denque/-/denque-2.1.0.tgz#e93e1a6569fb5e66f16a3c2a2964617d349d6ab1" + integrity sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw== + depd@2.0.0, depd@~2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/depd/-/depd-2.0.0.tgz#b696163cc757560d09cf22cc8fad1571b79e76df" @@ -3564,6 +3589,11 @@ ip-regex@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/ip-regex/-/ip-regex-2.1.0.tgz#fa78bf5d2e6913c911ce9f819ee5146bb6d844e9" +ip@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/ip/-/ip-2.0.0.tgz#4cf4ab182fee2314c75ede1276f8c80b479936da" + integrity sha512-WKa+XuLG1A1R0UWhl2+1XQSi+fZWMsYKffMZTTYsiZaUD8k2yDAj5atimTUD2TZkyCkNEeYE5NhFZmupOGtjYQ== + ipaddr.js@1.9.1: version "1.9.1" resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.9.1.tgz#bff38543eeb8984825079ff3a2a8e6cbd46781b3" @@ -4116,6 +4146,11 @@ mem@^4.0.0: mimic-fn "^2.0.0" p-is-promise "^2.0.0" +memory-pager@^1.0.2: + version "1.5.0" + resolved "https://registry.yarnpkg.com/memory-pager/-/memory-pager-1.5.0.tgz#d8751655d22d384682741c972f2c3d6dfa3e66b5" + integrity sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg== + merge-descriptors@1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61" @@ -4248,6 +4283,26 @@ module-dispatcher@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/module-dispatcher/-/module-dispatcher-2.0.0.tgz#67701ff90cca9b51d500be4782abb1014ccb2b10" +mongodb-connection-string-url@^2.5.3: + version "2.5.3" + resolved "https://registry.yarnpkg.com/mongodb-connection-string-url/-/mongodb-connection-string-url-2.5.3.tgz#c0c572b71570e58be2bd52b33dffd1330cfb6990" + integrity sha512-f+/WsED+xF4B74l3k9V/XkTVj5/fxFH2o5ToKXd8Iyi5UhM+sO9u0Ape17Mvl/GkZaFtM0HQnzAG5OTmhKw+tQ== + dependencies: + "@types/whatwg-url" "^8.2.1" + whatwg-url "^11.0.0" + +mongodb@^4.10.0: + version "4.10.0" + resolved "https://registry.yarnpkg.com/mongodb/-/mongodb-4.10.0.tgz#49fc509c928ff126577d628ab09aaf1e5855cd22" + integrity sha512-My2QxLTw0Cc1O9gih0mz4mqo145Jq4rLAQx0Glk/Ha9iYBzYpt4I2QFNRIh35uNFNfe8KFQcdwY1/HKxXBkinw== + dependencies: + bson "^4.7.0" + denque "^2.1.0" + mongodb-connection-string-url "^2.5.3" + socks "^2.7.0" + optionalDependencies: + saslprep "^1.0.3" + morgan@^1.10.0: version "1.10.0" resolved "https://registry.yarnpkg.com/morgan/-/morgan-1.10.0.tgz#091778abc1fc47cd3509824653dae1faab6b17d7" @@ -5116,7 +5171,7 @@ pump@^3.0.0: end-of-stream "^1.1.0" once "^1.3.1" -punycode@^2.1.0: +punycode@^2.1.0, punycode@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" @@ -5414,6 +5469,13 @@ safe-buffer@5.2.1, safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@~5.2.0: version "2.1.2" resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" +saslprep@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/saslprep/-/saslprep-1.0.3.tgz#4c02f946b56cf54297e347ba1093e7acac4cf226" + integrity sha512-/MY/PEMbk2SuY5sScONwhUDsV2p77Znkb/q3nSVstq/yQzYJOH/Azh29p9oJLsl3LnQwSvZDKagDGBsBwSooag== + dependencies: + sparse-bitfield "^3.0.3" + schema-utils@^2.6.5: version "2.7.1" resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-2.7.1.tgz#1ca4f32d1b24c590c203b8e7a50bf0ea4cd394d7" @@ -5567,6 +5629,19 @@ slice-ansi@^4.0.0: astral-regex "^2.0.0" is-fullwidth-code-point "^3.0.0" +smart-buffer@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/smart-buffer/-/smart-buffer-4.2.0.tgz#6e1d71fa4f18c05f7d0ff216dd16a481d0e8d9ae" + integrity sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg== + +socks@^2.7.0: + version "2.7.0" + resolved "https://registry.yarnpkg.com/socks/-/socks-2.7.0.tgz#f9225acdb841e874dca25f870e9130990f3913d0" + integrity sha512-scnOe9y4VuiNUULJN72GrM26BNOjVsfPXI+j+98PkyEfsIXroa5ofyjT+FzGvn/xHs73U2JtoBYAVx9Hl4quSA== + dependencies: + ip "^2.0.0" + smart-buffer "^4.2.0" + source-map-js@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.0.2.tgz#adbc361d9c62df380125e7f161f71c826f1e490c" @@ -5582,6 +5657,13 @@ source-map@^0.6.0, source-map@^0.6.1: version "0.6.1" resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" +sparse-bitfield@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz#ff4ae6e68656056ba4b3e792ab3334d38273ca11" + integrity sha512-kvzhi7vqKTfkh0PZU+2D2PIllw2ymqJKujUcyPMd9Y75Nv4nPbGJZXNhxsgdQab2BmlDct1YnfQCguEvHr7VsQ== + dependencies: + memory-pager "^1.0.2" + spawn-command@^0.0.2-1: version "0.0.2-1" resolved "https://registry.yarnpkg.com/spawn-command/-/spawn-command-0.0.2-1.tgz#62f5e9466981c1b796dc5929937e11c9c6921bd0" @@ -5899,6 +5981,13 @@ token-types@^5.0.1: "@tokenizer/token" "^0.3.0" ieee754 "^1.2.1" +tr46@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/tr46/-/tr46-3.0.0.tgz#555c4e297a950617e8eeddef633c87d4d9d6cbf9" + integrity sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA== + dependencies: + punycode "^2.1.1" + tree-kill@^1.2.2: version "1.2.2" resolved "https://registry.yarnpkg.com/tree-kill/-/tree-kill-1.2.2.tgz#4ca09a9092c88b73a7cdc5e8a01b507b0790a0cc" @@ -6126,6 +6215,11 @@ web-streams-polyfill@^3.0.3: version "3.2.1" resolved "https://registry.yarnpkg.com/web-streams-polyfill/-/web-streams-polyfill-3.2.1.tgz#71c2718c52b45fd49dbeee88634b3a60ceab42a6" +webidl-conversions@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-7.0.0.tgz#256b4e1882be7debbf01d05f0aa2039778ea080a" + integrity sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g== + webpack-cli@^4.9.2: version "4.10.0" resolved "https://registry.yarnpkg.com/webpack-cli/-/webpack-cli-4.10.0.tgz#37c1d69c8d85214c5a65e589378f53aec64dab31" @@ -6183,6 +6277,14 @@ webpack@^5.70.0: watchpack "^2.4.0" webpack-sources "^3.2.3" +whatwg-url@^11.0.0: + version "11.0.0" + resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-11.0.0.tgz#0a849eebb5faf2119b901bb76fd795c2848d4018" + integrity sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ== + dependencies: + tr46 "^3.0.0" + webidl-conversions "^7.0.0" + which-boxed-primitive@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz#13757bc89b209b049fe5d86430e21cf40a89a8e6"