From 6d854763d275b48b24565177277cf4face697c39 Mon Sep 17 00:00:00 2001 From: Paolo Insogna Date: Thu, 4 Aug 2022 16:56:16 +0200 Subject: [PATCH] feat: Remove fastq. (#83) --- packages/benchmarks/benchmark-table/index.mjs | 144 +++++----- packages/lyra/package.json | 3 - packages/lyra/src/insertion-checker.ts | 31 +++ packages/lyra/src/levenshtein.ts | 11 +- packages/lyra/src/lyra.ts | 250 ++++++++---------- packages/lyra/src/prefix-tree/node.ts | 2 + packages/lyra/src/tokenizer/index.ts | 5 +- packages/lyra/tests/lyra.dataset.test.ts | 45 +++- pnpm-lock.yaml | 51 ++-- 9 files changed, 300 insertions(+), 242 deletions(-) create mode 100644 packages/lyra/src/insertion-checker.ts diff --git a/packages/benchmarks/benchmark-table/index.mjs b/packages/benchmarks/benchmark-table/index.mjs index 31abcc942..60c051de9 100644 --- a/packages/benchmarks/benchmark-table/index.mjs +++ b/packages/benchmarks/benchmark-table/index.mjs @@ -1,81 +1,102 @@ -/* global console */ +/* global console , setImmediate*/ import { readFile } from "fs/promises"; import { create, insert, search, formatNanoseconds } from "@nearform/lyra"; import { URL } from "node:url"; -const lines = JSON.parse(await readFile(new URL("../dataset/divinaCommedia.json", import.meta.url).pathname)); +async function populateDB(db, lines) { + let i = 0; + return new Promise(resolve => { + function insertBatch() { + const batch = lines.slice(i * 1000, (i + 1) * 1000); + i++; -const db = create({ - schema: { - id: "string", - txt: "string", - }, -}); + if (!batch.length) { + return resolve(); + } -for (const line of lines) { - insert(db, line); + for (const line of batch) { + insert(db, line); + } + + setImmediate(insertBatch); + } + + setImmediate(insertBatch); + }); } -const d1 = search(db, { - term: "stelle", - properties: ["txt"], - exact: true, -}); - -const d2 = search(db, { - term: "stelle", - exact: true, -}); - -const d3 = search(db, { - term: "stele", - properties: "*", - tolerance: 1, -}); - -const d4 = search(db, { - term: "onde si muovono a diversi porti", - properties: "*", - exact: true, -}); - -const d5 = search(db, { - term: "ode si mossero a divisi porte", - properties: "*", - tolerance: 5, -}); - -const d6 = search(db, { - term: "ode si mossero a divisi porte", - properties: ["txt"], - tolerance: 5, -}); - -const table = ` +async function main() { + const db = create({ + schema: { + id: "string", + txt: "string", + }, + }); + + const lines = JSON.parse(await readFile(new URL("../dataset/divinaCommedia.json", import.meta.url).pathname)); + + await populateDB(db, lines); + + const d1 = search(db, { + term: "stelle", + properties: ["txt"], + exact: true, + }); + + const d2 = search(db, { + term: "stelle", + exact: true, + }); + + const d3 = search(db, { + term: "stele", + properties: "*", + tolerance: 1, + }); + + const d4 = search(db, { + term: "onde si muovono a diversi porti", + properties: "*", + exact: true, + }); + + const d5 = search(db, { + term: "ode si mossero a divisi porte", + properties: "*", + tolerance: 5, + }); + + const d6 = search(db, { + term: "ode si mossero a divisi porte", + properties: ["txt"], + tolerance: 5, + }); + + const table = ` | Search | Term | Properties | Typo tolerance | Time Elapsed | Results | |--------------------|---------------------------------------|------------|----------------|---------------|-------------| | **Exact search** | \`"stelle"\` | \`["txt"]\`| \`N/A\` | ${formatNanoseconds( - d1.elapsed, -)} | ${d1.count} | + d1.elapsed, + )} | ${d1.count} | | **Exact search** | \`"stelle"\` | \`"*"\` | \`N/A\` | ${formatNanoseconds( - d2.elapsed, -)} | ${d2.count} | + d2.elapsed, + )} | ${d2.count} | | **Typo tolerance** | \`"stele"\` | \`"*"\` | \`1\` | ${formatNanoseconds( - d3.elapsed, -)} | ${d3.count} | + d3.elapsed, + )} | ${d3.count} | | **Exact search** | \`"onde si muovono a diversi porti"\` | \`"*"\` | \`N/A\` | ${formatNanoseconds( - d4.elapsed, -)} | ${d4.count} | + d4.elapsed, + )} | ${d4.count} | | **Typo tolerance** | \`"ode si mossero a divisi porte"\` | \`"*"\` | \`5\` | ${formatNanoseconds( - d5.elapsed, -)} | ${d5.count} | + d5.elapsed, + )} | ${d5.count} | | **Typo tolerance** | \`"ode si mossero a divisi porte"\` | \`["txt"]\`| \`5\` | ${formatNanoseconds( - d6.elapsed, -)} | ${d6.count} | + d6.elapsed, + )} | ${d6.count} | `; -const markdownContent = ` + const markdownContent = ` # Benchmarks The following is an automated benchmark performed on the [Divina Commedia](https://en.wikipedia.org/wiki/Divina_Commedia) dataset.
@@ -86,4 +107,7 @@ You can find the full dataset [here](https://github.com/nearform/lyra/blob/main/ ${table} `; -console.log(markdownContent); + console.log(markdownContent); +} + +main(); diff --git a/packages/lyra/package.json b/packages/lyra/package.json index c7acef454..7068e7b07 100644 --- a/packages/lyra/package.json +++ b/packages/lyra/package.json @@ -22,9 +22,6 @@ "main": "./dist/cjs/lyra.js", "module": "./dist/esm/lyra.js", "types": "./dist/esm/lyra.d.ts", - "dependencies": { - "fastq": "^1.13.0" - }, "devDependencies": { "@types/node": "^18.6.2", "@types/tap": "^15.0.7", diff --git a/packages/lyra/src/insertion-checker.ts b/packages/lyra/src/insertion-checker.ts new file mode 100644 index 000000000..5359bc260 --- /dev/null +++ b/packages/lyra/src/insertion-checker.ts @@ -0,0 +1,31 @@ +const kInsertions = Symbol("lyra.insertions"); + +const warn = + "process" in globalThis + ? process.emitWarning + : function emitWarning(message: string, options: { code: string }) { + console.warn(`[WARNING] [${options.code}] ${message}`); + }; + +export function trackInsertion(_lyra: unknown) { + const lyra = _lyra as object & { [kInsertions]?: number }; + + if (typeof lyra[kInsertions] !== "number") { + queueMicrotask(() => { + lyra[kInsertions] = undefined; + }); + + lyra[kInsertions] = 0; + } + + if (lyra[kInsertions]! > 1000) { + warn( + "Lyra's insert operation is synchronous. Please avoid inserting a large number of document in a single operation in order not to block the main thread.", + { code: "LYRA0001" }, + ); + + lyra[kInsertions] = -1; + } else if (lyra[kInsertions] >= 0) { + lyra[kInsertions]++; + } +} diff --git a/packages/lyra/src/levenshtein.ts b/packages/lyra/src/levenshtein.ts index 4dab1d4cc..df91110c1 100644 --- a/packages/lyra/src/levenshtein.ts +++ b/packages/lyra/src/levenshtein.ts @@ -1,6 +1,13 @@ export function levenshtein(a: string, b: string): number { - if (!a.length) return b.length; - if (!b.length) return a.length; + /* c8 ignore next 3 */ + if (!a.length) { + return b.length; + } + + /* c8 ignore next 3 */ + if (!b.length) { + return a.length; + } let tmp; diff --git a/packages/lyra/src/lyra.ts b/packages/lyra/src/lyra.ts index 3eb8588a5..7fec13ffd 100644 --- a/packages/lyra/src/lyra.ts +++ b/packages/lyra/src/lyra.ts @@ -1,4 +1,3 @@ -import fastq from "fastq"; import * as ERRORS from "./errors"; import { tokenize } from "./tokenizer"; import { getNanosecondsTime, uniqueId } from "./utils"; @@ -6,6 +5,9 @@ import { Language, SUPPORTED_LANGUAGES } from "./tokenizer/languages"; import type { ResolveSchema, SearchProperties } from "./types"; import { create as createNode, Node } from "./prefix-tree/node"; import { find as trieFind, insert as trieInsert, removeDocumentByWord, Nodes } from "./prefix-tree/trie"; +import { trackInsertion } from "./insertion-checker"; + +type Index = Record; export { formatNanoseconds } from "./utils"; @@ -15,62 +17,118 @@ export type PropertiesSchema = { [key: string]: PropertyType | PropertiesSchema; }; -export type LyraProperties = { - schema: T; +export type Configuration = { + schema: S; defaultLanguage?: Language; edge?: boolean; }; -export type LyraDocs = Record | undefined>; +export type Data = { + docs: Record | undefined>; + index: Index; + nodes: Nodes; +}; + +export interface Lyra extends Data { + defaultLanguage: Language; + schema: S; + edge: boolean; +} -export type SearchParams = { +export type InsertConfig = { + language: Language; +}; + +export type SearchParams = { term: string; - properties?: "*" | SearchProperties[]; + properties?: "*" | SearchProperties[]; limit?: number; offset?: number; exact?: boolean; tolerance?: number; }; -export type InsertConfig = { - language: Language; +export type SearchResult = { + count: number; + hits: RetrievedDoc[]; + elapsed: bigint; }; -export type LyraData = { - docs: LyraDocs; - index: LyraIndex; - nodes: Nodes; +export type RetrievedDoc = ResolveSchema & { + id: string; }; -type LyraIndex = Record; +function buildIndex(lyra: Lyra, schema: S, prefix = "") { + for (const prop of Object.keys(schema)) { + const propType = typeof prop; + const isNested = typeof schema[prop] === "object"; -type QueueDocParams = { - id: string; - doc: ResolveSchema; - config: InsertConfig; -}; + if (propType !== "string") throw new Error(ERRORS.INVALID_SCHEMA_TYPE(propType)); -export type SearchResult = { - count: number; - hits: RetrievedDoc[]; - elapsed: bigint; -}; + const propName = `${prefix}${prop}`; -type RetrievedDoc = ResolveSchema & { - id: string; -}; + if (isNested) { + buildIndex(lyra, schema[prop] as S, `${propName}.`); + } else { + lyra.index[propName] = createNode(); + } + } +} -export interface Lyra { - defaultLanguage: Language; - schema: T; - docs: LyraDocs; - nodes: Nodes; - index: LyraIndex; - edge: boolean; - queue?: fastq.queue, void>; +function recursiveCheckDocSchema( + newDoc: ResolveSchema, + schema: PropertiesSchema, +): boolean { + for (const key in newDoc) { + if (!(key in schema)) { + return false; + } + + const propType = typeof newDoc[key]; + + if (propType === "object") { + recursiveCheckDocSchema(newDoc[key] as ResolveSchema, schema); + } else { + if (typeof newDoc[key] !== schema[key]) { + return false; + } + } + } + + return true; } -function getIndices(lyra: Lyra, indices: SearchParams["properties"]): string[] { +function recursiveTrieInsertion( + index: Index, + nodes: Nodes, + doc: ResolveSchema, + id: string, + config: InsertConfig, + prefix = "", +) { + for (const key of Object.keys(doc)) { + const isNested = typeof doc[key] === "object"; + const propName = `${prefix}${key}`; + if (isNested) { + recursiveTrieInsertion(index, nodes, doc[key] as ResolveSchema, id, config, propName + "."); + + return; + } + + if (typeof doc[key] === "string") { + // Use propName here because if doc is a nested object + // We will get the wrong index + const requestedTrie = index[propName]; + const tokens = tokenize(doc[key] as string, config.language); + + for (const token of tokens) { + trieInsert(nodes, requestedTrie, token, id); + } + } + } +} + +function getIndices(lyra: Lyra, indices: SearchParams["properties"]): string[] { const knownIndices = Object.keys(lyra.index); if (!indices) { @@ -94,9 +152,9 @@ function getIndices(lyra: Lyra, indices: SearchPa return indices as string[]; } -function getDocumentIDsFromSearch( - lyra: Lyra, - params: SearchParams & { index: string }, +function getDocumentIDsFromSearch( + lyra: Lyra, + params: SearchParams & { index: string }, ): string[] { const idx = lyra.index[params.index]; @@ -116,92 +174,14 @@ function getDocumentIDsFromSearch( return Array.from(ids); } -function buildIndex(lyra: Lyra, schema: T, prefix = "") { - for (const prop of Object.keys(schema)) { - const propType = typeof prop; - const isNested = typeof schema[prop] === "object"; - - if (propType !== "string") throw new Error(ERRORS.INVALID_SCHEMA_TYPE(propType)); - - const propName = `${prefix}${prop}`; - - if (isNested) { - buildIndex(lyra, schema[prop] as T, `${propName}.`); - } else { - lyra.index[propName] = createNode(); - } - } -} - -function _insert( - this: Lyra, - { doc, id, config }: QueueDocParams, - cb: (error: Error | null) => void, -): void { - const index = this.index; - const nodes = this.nodes; - this.docs[id] = doc; - - function recursiveTrieInsertion(doc: ResolveSchema, prefix = "") { - for (const key of Object.keys(doc)) { - const isNested = typeof doc[key] === "object"; - const propName = `${prefix}${key}`; - if (isNested) { - recursiveTrieInsertion(doc[key] as ResolveSchema, `${propName}.`); - - return; - } - - if (typeof doc[key] === "string") { - // Use propName here because if doc is a nested object - // We will get the wrong index - const requestedTrie = index[propName]; - const tokens = tokenize(doc[key] as string, config.language); - - for (const token of tokens) { - trieInsert(nodes, requestedTrie, token, id); - } - } - - cb(null); - } - } - - recursiveTrieInsertion(doc); -} - -function checkInsertDocSchema(lyra: Lyra, doc: QueueDocParams["doc"]): boolean { - function recursiveCheck(newDoc: QueueDocParams["doc"], schema: PropertiesSchema): boolean { - for (const key in newDoc) { - if (!(key in schema)) { - return false; - } - - const propType = typeof newDoc[key]; - - if (propType === "object") { - recursiveCheck(newDoc[key] as QueueDocParams["doc"], schema); - } else { - if (typeof newDoc[key] !== schema[key]) { - return false; - } - } - } - - return true; - } - - return recursiveCheck(doc, lyra.schema); -} - -export function create(properties: LyraProperties): Lyra { +export function create(properties: Configuration): Lyra { const defaultLanguage = (properties?.defaultLanguage?.toLowerCase() as Language) ?? "english"; if (!SUPPORTED_LANGUAGES.includes(defaultLanguage)) { throw new Error(ERRORS.LANGUAGE_NOT_SUPPORTED(defaultLanguage)); } - const instance: Lyra = { + const instance: Lyra = { defaultLanguage, schema: properties.schema, docs: {}, @@ -210,15 +190,13 @@ export function create(properties: LyraProperties edge: properties.edge ?? false, }; - instance.queue = fastq(instance, _insert.bind(instance), 1000) as fastq.queue>; - buildIndex(instance, properties.schema); return instance; } -export function insert( - lyra: Lyra, - doc: ResolveSchema, +export function insert( + lyra: Lyra, + doc: ResolveSchema, config?: InsertConfig, ): { id: string } { config = { language: lyra.defaultLanguage, ...config }; @@ -228,20 +206,18 @@ export function insert( throw new Error(ERRORS.LANGUAGE_NOT_SUPPORTED(config.language)); } - if (!checkInsertDocSchema(lyra, doc)) { + if (!recursiveCheckDocSchema(doc, lyra.schema)) { throw new Error(ERRORS.INVALID_DOC_SCHEMA(lyra.schema, doc)); } - lyra.queue!.push({ - id, - doc, - config, - }); + lyra.docs[id] = doc; + recursiveTrieInsertion(lyra.index, lyra.nodes, doc, id, config); + trackInsertion(lyra); return { id }; } -export function remove(lyra: Lyra, docID: string): boolean { +export function remove(lyra: Lyra, docID: string): boolean { if (!(docID in lyra.docs)) { throw new Error(ERRORS.DOC_ID_DOES_NOT_EXISTS(docID)); } @@ -268,11 +244,11 @@ export function remove(lyra: Lyra, docID: string) return true; } -export function search( - lyra: Lyra, - params: SearchParams, +export function search( + lyra: Lyra, + params: SearchParams, language?: Language, -): SearchResult { +): SearchResult { if (!language) { language = lyra.defaultLanguage; } @@ -281,7 +257,7 @@ export function search( const indices = getIndices(lyra, params.properties); const uniqueDocIds = new Set(); const { limit = 10, offset = 0, exact = false } = params; - const results: RetrievedDoc[] = Array.from({ + const results: RetrievedDoc[] = Array.from({ length: limit, }); @@ -330,11 +306,11 @@ export function search( }; } -export function save(lyra: Lyra): LyraData { +export function save(lyra: Lyra): Data { return { index: lyra.index, docs: lyra.docs, nodes: lyra.nodes }; } -export function load(lyra: Lyra, { index, docs, nodes }: LyraData) { +export function load(lyra: Lyra, { index, docs, nodes }: Data) { if (!lyra.edge) { throw new Error(ERRORS.GETTER_SETTER_WORKS_ON_EDGE_ONLY("load")); } diff --git a/packages/lyra/src/prefix-tree/node.ts b/packages/lyra/src/prefix-tree/node.ts index 52fd42bdd..47555ed84 100644 --- a/packages/lyra/src/prefix-tree/node.ts +++ b/packages/lyra/src/prefix-tree/node.ts @@ -34,6 +34,7 @@ export function updateParent(node: Node, parent: Node): void { export function removeDocument(node: Node, docID: string): boolean { const index = node.docs.indexOf(docID); + /* c8 ignore next 3 */ if (index === -1) { return false; } @@ -43,6 +44,7 @@ export function removeDocument(node: Node, docID: string): boolean { return true; } +/* c8 ignore next 5 */ function serialize(this: Node): object { const { key, word, children, docs, end } = this; diff --git a/packages/lyra/src/tokenizer/index.ts b/packages/lyra/src/tokenizer/index.ts index a8cc2e595..2180989ea 100644 --- a/packages/lyra/src/tokenizer/index.ts +++ b/packages/lyra/src/tokenizer/index.ts @@ -14,7 +14,10 @@ const splitRegex: Record = { }; export function tokenize(input: string, language: Language = "english") { - if (typeof input !== "string") return [input]; + /* c8 ignore next 3 */ + if (typeof input !== "string") { + return [input]; + } const splitRule = splitRegex[language]; const tokens = input.toLowerCase().split(splitRule).map(replaceDiacritics); diff --git a/packages/lyra/tests/lyra.dataset.test.ts b/packages/lyra/tests/lyra.dataset.test.ts index 357e98025..4e60f4fed 100644 --- a/packages/lyra/tests/lyra.dataset.test.ts +++ b/packages/lyra/tests/lyra.dataset.test.ts @@ -27,21 +27,42 @@ const db = create({ }, }); -for (const event of (dataset as any).result.events) { - insert(db, { - date: event.date, - description: event.description, - granularity: event.granularity, - categories: { - first: event.category1 ?? "", - second: event.category2 ?? "", - }, - }); -} - t.test("lyra.dataset", async t => { t.plan(3); + t.before(() => { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const events = (dataset as any).result.events; + + let i = 0; + return new Promise(resolve => { + function insertBatch() { + const batch = events.slice(i * 1000, (i + 1) * 1000); + i++; + + if (!batch.length) { + return resolve(); + } + + for (const event of batch) { + insert(db, { + date: event.date, + description: event.description, + granularity: event.granularity, + categories: { + first: event.category1 ?? "", + second: event.category2 ?? "", + }, + }); + } + + setImmediate(insertBatch); + } + + setImmediate(insertBatch); + }); + }); + t.test("should correctly populate the database with a large dataset", t => { t.plan(4); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 7a5047ee4..0aedd0998 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -150,13 +150,10 @@ importers: '@types/node': ^18.6.2 '@types/tap': ^15.0.7 c8: ^7.12.0 - fastq: ^1.13.0 rimraf: ^3.0.2 tap: ^16.3.0 ts-node: ^10.9.1 typescript: ^4.7.4 - dependencies: - fastq: 1.13.0 devDependencies: '@types/node': 18.6.3 '@types/tap': 15.0.7 @@ -2318,7 +2315,7 @@ packages: '@docusaurus/utils': 2.0.0-rc.1_fh5bds7ccduypwmpulad3xjppm '@docusaurus/utils-validation': 2.0.0-rc.1_fh5bds7ccduypwmpulad3xjppm algoliasearch: 4.14.2 - algoliasearch-helper: 3.10.0_algoliasearch@4.14.2 + algoliasearch-helper: 3.11.0_algoliasearch@4.14.2 clsx: 1.2.1 eta: 1.12.3 fs-extra: 10.1.0 @@ -2673,7 +2670,7 @@ packages: resolution: {integrity: sha512-/l/VWsdt/aBXgjshLWOFyFt3IVdYypu5y2Wn2rOO1un6nkqIn8SLXzgIMYXFyYsRWDyF5EthmKJMIdJvk08grg==} engines: {node: ^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0} dependencies: - '@sinclair/typebox': 0.24.26 + '@sinclair/typebox': 0.24.27 dev: true /@jest/source-map/28.1.2: @@ -2867,8 +2864,8 @@ packages: /@sideway/pinpoint/2.0.0: resolution: {integrity: sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ==} - /@sinclair/typebox/0.24.26: - resolution: {integrity: sha512-1ZVIyyS1NXDRVT8GjWD5jULjhDyM3IsIHef2VGUMdnWOlX2tkPjyEX/7K0TGSH2S8EaPhp1ylFdjSjUGQ+gecg==} + /@sinclair/typebox/0.24.27: + resolution: {integrity: sha512-K7C7IlQ3zLePEZleUN21ceBA2aLcMnLHTLph8QWk1JK37L90obdpY+QGY8bXMKxf1ht1Z0MNewvXxWv0oGDYFg==} dev: true /@sindresorhus/is/0.14.0: @@ -3918,8 +3915,8 @@ packages: require-from-string: 2.0.2 uri-js: 4.4.1 - /algoliasearch-helper/3.10.0_algoliasearch@4.14.2: - resolution: {integrity: sha512-4E4od8qWWDMVvQ3jaRX6Oks/k35ywD011wAA4LbYMMjOtaZV6VWaTjRr4iN2bdaXP2o1BP7SLFMBf3wvnHmd8Q==} + /algoliasearch-helper/3.11.0_algoliasearch@4.14.2: + resolution: {integrity: sha512-TLl/MSjtQ98mgkd8hngWkzSjE+dAWldZ1NpJtv2mT+ZoFJ2P2zDE85oF9WafJOXWN9FbVRmyxpO5H+qXcNaFng==} peerDependencies: algoliasearch: '>= 3.1 < 6' dependencies: @@ -4084,7 +4081,7 @@ packages: postcss: ^8.1.0 dependencies: browserslist: 4.21.3 - caniuse-lite: 1.0.30001373 + caniuse-lite: 1.0.30001374 fraction.js: 4.2.0 normalize-range: 0.1.2 picocolors: 1.0.0 @@ -4360,8 +4357,8 @@ packages: engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} hasBin: true dependencies: - caniuse-lite: 1.0.30001373 - electron-to-chromium: 1.4.210 + caniuse-lite: 1.0.30001374 + electron-to-chromium: 1.4.211 node-releases: 2.0.6 update-browserslist-db: 1.0.5_browserslist@4.21.3 @@ -4486,13 +4483,13 @@ packages: resolution: {integrity: sha512-bsTwuIg/BZZK/vreVTYYbSWoe2F+71P7K5QGEX+pT250DZbfU1MQ5prOKpPR+LL6uWKK3KMwMCAS74QB3Um1uw==} dependencies: browserslist: 4.21.3 - caniuse-lite: 1.0.30001373 + caniuse-lite: 1.0.30001374 lodash.memoize: 4.1.2 lodash.uniq: 4.5.0 dev: false - /caniuse-lite/1.0.30001373: - resolution: {integrity: sha512-pJYArGHrPp3TUqQzFYRmP/lwJlj8RCbVe3Gd3eJQkAV8SAC6b19XS9BjMvRdvaS8RMkaTN8ZhoHP6S1y8zzwEQ==} + /caniuse-lite/1.0.30001374: + resolution: {integrity: sha512-mWvzatRx3w+j5wx/mpFN5v5twlPrabG8NqX2c6e45LCpymdoGqNvRkRutFUqpRTXKFQFNQJasvK0YT7suW6/Hw==} /ccount/1.1.0: resolution: {integrity: sha512-vlNK021QdI7PNeiUh/lKkC/mNHHfV0m/Ad5JoI0TYtlBnJAslM/JIkm/tGC88bkLIwO6OQ5uV6ztS6kVAtCDlg==} @@ -5491,8 +5488,8 @@ packages: resolution: {integrity: sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=} dev: false - /electron-to-chromium/1.4.210: - resolution: {integrity: sha512-kSiX4tuyZijV7Cz0MWVmGT8K2siqaOA4Z66K5dCttPPRh0HicOcOAEj1KlC8O8J1aOS/1M8rGofOzksLKaHWcQ==} + /electron-to-chromium/1.4.211: + resolution: {integrity: sha512-BZSbMpyFQU0KBJ1JG26XGeFI3i4op+qOYGxftmZXFZoHkhLgsSv4DHDJfl8ogII3hIuzGt51PaZ195OVu0yJ9A==} /emittery/0.10.2: resolution: {integrity: sha512-aITqOwnLanpHLNXZJENbOgjUBeHocD+xsSJmNrjovKBW5HbSpW3d1pEls7GFQPUWXiwG9+0P4GtHfEqC/4M0Iw==} @@ -7041,8 +7038,8 @@ packages: ci-info: 2.0.0 dev: false - /is-core-module/2.9.0: - resolution: {integrity: sha512-+5FPy5PnwmO3lvfMb0AsoPaBG+5KHUI0wYFXOtYPnVVVspTFUuMZNfNaNVRt3FZadstu2c8x23vykRW/NBoU6A==} + /is-core-module/2.10.0: + resolution: {integrity: sha512-Erxj2n/LDAZ7H8WNJXd9tw38GYM3dv8rk8Zcs+jJuxYTW7sozH+SS8NtrSjVL1/vpLvWi1hxy96IzjJ3EHTJJg==} dependencies: has: 1.0.3 @@ -8504,7 +8501,7 @@ packages: destr: 1.1.1 node-fetch-native: 0.1.4 ufo: 0.8.5 - undici: 5.8.0 + undici: 5.8.1 dev: true /on-finished/2.4.1: @@ -9890,7 +9887,7 @@ packages: resolution: {integrity: sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==} hasBin: true dependencies: - is-core-module: 2.9.0 + is-core-module: 2.10.0 path-parse: 1.0.7 supports-preserve-symlinks-flag: 1.0.0 @@ -10810,7 +10807,7 @@ packages: make-error: 1.3.6 semver: 7.3.7 typescript: 4.7.4 - yargs-parser: 21.0.1 + yargs-parser: 21.1.0 dev: true /ts-node/10.9.1_e2tlcjkk7tlngjdlhzx5hjlnv4: @@ -10940,8 +10937,8 @@ packages: jiti: 1.14.0 dev: true - /undici/5.8.0: - resolution: {integrity: sha512-1F7Vtcez5w/LwH2G2tGnFIihuWUlc58YidwLiCv+jR2Z50x0tNXpRRw7eOIJ+GvqCqIkg9SB7NWAJ/T9TLfv8Q==} + /undici/5.8.1: + resolution: {integrity: sha512-iDRmWX4Zar/4A/t+1LrKQRm102zw2l9Wgat3LtTlTn8ykvMZmAmpq9tjyHEigx18FsY7IfATvyN3xSw9BDz0eA==} engines: {node: '>=12.18'} dev: true @@ -11670,8 +11667,8 @@ packages: engines: {node: '>=10'} dev: true - /yargs-parser/21.0.1: - resolution: {integrity: sha512-9BK1jFpLzJROCI5TzwZL/TU4gqjK5xiHV/RfWLOahrjAko/e4DJkRDZQXfvqAsiZzzYhgAzbgz6lg48jcm4GLg==} + /yargs-parser/21.1.0: + resolution: {integrity: sha512-xzm2t63xTV/f7+bGMSRzLhUNk1ajv/tDoaD5OeGyC3cFo2fl7My9Z4hS3q2VdQ7JaLvTxErO8Jp5pRIFGMD/zg==} engines: {node: '>=12'} dev: true @@ -11715,7 +11712,7 @@ packages: require-directory: 2.1.1 string-width: 4.2.3 y18n: 5.0.8 - yargs-parser: 21.0.1 + yargs-parser: 21.1.0 dev: true /yn/3.1.1: