From 2c3b88887cf47cb29fb3de3efd102c48be13d966 Mon Sep 17 00:00:00 2001 From: Mike Date: Wed, 24 Jul 2024 13:01:57 +0800 Subject: [PATCH] perf: csv import (#746) * feat: using worker parse csv * fix: import multiple column error * feat: update webpack config for import worker * fix: vitest worker file path error * fix: excel import missing key * feat: using `convertCellValue2DBValue` transfer cellvalue * feat: add workerId escape conflict * fix: sqlite e2e error * feat: compact filter input --- apps/nestjs-backend/package.json | 3 +- .../open-api/import-open-api.service.ts | 161 ++++++----- .../features/import/open-api/import.class.ts | 36 ++- .../src/features/record/record.service.ts | 9 +- apps/nestjs-backend/src/worker/parse.ts | 41 +++ .../test/table-import.e2e-spec.ts | 11 +- apps/nestjs-backend/vitest-e2e.setup.ts | 17 +- apps/nestjs-backend/webpack.config.js | 18 ++ apps/nestjs-backend/webpack.dev.js | 18 +- .../filter/condition/BaseOperatorSelect.tsx | 4 +- .../filter/condition/FieldSelect.tsx | 4 +- pnpm-lock.yaml | 258 +++++++++++++++++- 12 files changed, 482 insertions(+), 98 deletions(-) create mode 100644 apps/nestjs-backend/src/worker/parse.ts diff --git a/apps/nestjs-backend/package.json b/apps/nestjs-backend/package.json index ed8c92470..6addad7c7 100644 --- a/apps/nestjs-backend/package.json +++ b/apps/nestjs-backend/package.json @@ -74,12 +74,12 @@ "@types/oauth2orize": "1.11.5", "@types/papaparse": "5.3.14", "@types/passport": "1.0.16", - "@types/passport-openidconnect": "0.1.3", "@types/passport-github2": "1.2.9", "@types/passport-google-oauth20": "2.0.14", "@types/passport-jwt": "4.0.1", "@types/passport-local": "1.0.38", "@types/passport-oauth2-client-password": "0.1.5", + "@types/passport-openidconnect": "0.1.3", "@types/pause": "0.1.3", "@types/sharedb": "3.3.10", "@types/ws": "8.5.10", @@ -152,6 +152,7 @@ "cookie-parser": "1.4.6", "cors": "2.8.5", "dayjs": "1.11.10", + "esbuild": "0.23.0", "express": "4.19.1", "express-session": "1.18.0", "fs-extra": "11.2.0", diff --git a/apps/nestjs-backend/src/features/import/open-api/import-open-api.service.ts b/apps/nestjs-backend/src/features/import/open-api/import-open-api.service.ts index 2ac37bc0e..1f86343e4 100644 --- a/apps/nestjs-backend/src/features/import/open-api/import-open-api.service.ts +++ b/apps/nestjs-backend/src/features/import/open-api/import-open-api.service.ts @@ -1,6 +1,8 @@ +import { join } from 'path'; +import { Worker } from 'worker_threads'; import { Injectable, Logger, BadRequestException } from '@nestjs/common'; import type { IFieldRo } from '@teable/core'; -import { FieldType, FieldKeyType } from '@teable/core'; +import { FieldType, FieldKeyType, getRandomString } from '@teable/core'; import { PrismaService } from '@teable/db-main-prisma'; import type { IAnalyzeRo, @@ -172,77 +174,106 @@ export class ImportOpenApiService { sourceColumnMap?: Record; } ) { - const { skipFirstNLines, sheetKey, notification } = options; + const { sheetKey, notification } = options; const { columnInfo, fields, sourceColumnMap } = recordsCal; - importer.parse( - { - skipFirstNLines, - key: sheetKey, + const workerId = `worker_${getRandomString(8)}`; + const worker = new Worker(join(process.cwd(), 'dist', 'worker', 'parse.js'), { + workerData: { + config: importer.getConfig(), + options: { + key: options.sheetKey, + notification: options.notification, + skipFirstNLines: options.skipFirstNLines, + }, + id: workerId, }, - async (result) => { - const currentResult = result[sheetKey]; - // fill data - const records = currentResult.map((row) => { - const res: { fields: Record } = { - fields: {}, - }; - // import new table - if (columnInfo) { - columnInfo.forEach((col, index) => { - const { sourceColumnIndex } = col; - // empty row will be return void row value - const value = Array.isArray(row) ? row[sourceColumnIndex] : null; - res.fields[fields[index].id] = value?.toString(); - }); - } - // inplace records - if (sourceColumnMap) { - for (const [key, value] of Object.entries(sourceColumnMap)) { - if (value !== null) { - const { type } = fields.find((f) => f.id === key) || {}; - // link value should be string - res.fields[key] = type === FieldType.Link ? toString(row[value]) : row[value]; + }); + + worker.on('message', async (result) => { + const { type, data, chunkId, id } = result; + switch (type) { + case 'chunk': { + const currentResult = (data as Record)[sheetKey]; + // fill data + const records = currentResult.map((row) => { + const res: { fields: Record } = { + fields: {}, + }; + // import new table + if (columnInfo) { + columnInfo.forEach((col, index) => { + const { sourceColumnIndex } = col; + // empty row will be return void row value + const value = Array.isArray(row) ? row[sourceColumnIndex] : null; + res.fields[fields[index].id] = value?.toString(); + }); + } + // inplace records + if (sourceColumnMap) { + for (const [key, value] of Object.entries(sourceColumnMap)) { + if (value !== null) { + const { type } = fields.find((f) => f.id === key) || {}; + // link value should be string + res.fields[key] = type === FieldType.Link ? toString(row[value]) : row[value]; + } } } - } - return res; - }); - if (records.length === 0) { - return; - } - try { - const createFn = columnInfo - ? this.recordOpenApiService.createRecordsOnlySql.bind(this.recordOpenApiService) - : this.recordOpenApiService.multipleCreateRecords.bind(this.recordOpenApiService); - await createFn(table.id, { - fieldKeyType: FieldKeyType.Id, - typecast: true, - records, + return res; }); - } catch (e) { - this.logger.error((e as Error)?.message, (e as Error)?.stack); - throw e; + if (records.length === 0) { + return; + } + try { + const createFn = columnInfo + ? this.recordOpenApiService.createRecordsOnlySql.bind(this.recordOpenApiService) + : this.recordOpenApiService.multipleCreateRecords.bind(this.recordOpenApiService); + workerId === id && + (await createFn(table.id, { + fieldKeyType: FieldKeyType.Id, + typecast: true, + records, + })); + worker.postMessage({ type: 'done', chunkId }); + } catch (e) { + this.logger.error((e as Error)?.message, (e as Error)?.stack); + throw e; + } + break; } - }, - () => { - notification && - this.notificationService.sendImportResultNotify({ - baseId, - tableId: table.id, - toUserId: userId, - message: `🎉 ${table.name} ${sourceColumnMap ? 'inplace' : ''} imported successfully`, - }); - }, - (error) => { - notification && - this.notificationService.sendImportResultNotify({ - baseId, - tableId: table.id, - toUserId: userId, - message: `❌ ${table.name} import failed: ${error}`, - }); + case 'finished': + workerId === id && + notification && + this.notificationService.sendImportResultNotify({ + baseId, + tableId: table.id, + toUserId: userId, + message: `🎉 ${table.name} ${sourceColumnMap ? 'inplace' : ''} imported successfully`, + }); + break; + case 'error': + workerId === id && + notification && + this.notificationService.sendImportResultNotify({ + baseId, + tableId: table.id, + toUserId: userId, + message: `❌ ${table.name} import failed: ${data}`, + }); + break; } - ); + }); + worker.on('error', (e) => { + notification && + this.notificationService.sendImportResultNotify({ + baseId, + tableId: table.id, + toUserId: userId, + message: `❌ ${table.name} import failed: ${e.message}`, + }); + }); + worker.on('exit', (code) => { + this.logger.log(`Worker stopped with exit code ${code}`); + }); } } diff --git a/apps/nestjs-backend/src/features/import/open-api/import.class.ts b/apps/nestjs-backend/src/features/import/open-api/import.class.ts index 36ea27090..b2bb9b0a1 100644 --- a/apps/nestjs-backend/src/features/import/open-api/import.class.ts +++ b/apps/nestjs-backend/src/features/import/open-api/import.class.ts @@ -33,10 +33,11 @@ const validateZodSchemaMap: Record = { [FieldType.SingleLineText]: z.string(), }; -interface IImportConstructorParams { +export interface IImportConstructorParams { url: string; type: SUPPORTEDTYPE; maxRowCount?: number; + fileName?: string; } interface IParseResult { @@ -74,6 +75,14 @@ export abstract class Importer { ] ): Promise; + private setFileNameFromHeader(fileName: string) { + this.config.fileName = fileName; + } + + getConfig() { + return this.config; + } + async getFile() { const { url, type } = this.config; const { body: stream, headers } = await fetch(url); @@ -97,12 +106,29 @@ export abstract class Importer { ); } - return stream; + const contentDisposition = headers.get('content-disposition'); + let fileName = 'Import Table.csv'; + + if (contentDisposition) { + const fileNameMatch = + contentDisposition.match(/filename\*=UTF-8''([^;]+)/) || + contentDisposition.match(/filename="?([^"]+)"?/); + if (fileNameMatch) { + fileName = fileNameMatch[1]; + } + } + + const finalFileName = fileName.split('.').shift() as string; + + this.setFileNameFromHeader(decodeURIComponent(finalFileName)); + + return { stream, fileName: finalFileName }; } async genColumns() { const supportTypes = Importer.SUPPORTEDTYPE; const parseResult = await this.parse(); + const { fileName, type } = this.config; const result: IAnalyzeVo['worksheets'] = {}; for (const [sheetName, cols] of Object.entries(parseResult)) { @@ -154,7 +180,7 @@ export abstract class Importer { }); result[sheetName] = { - name: sheetName, + name: type === SUPPORTEDTYPE.EXCEL ? sheetName : fileName ? fileName : sheetName, columns: calculatedColumnHeaders, }; } @@ -185,7 +211,7 @@ export class CsvImporter extends Importer { ] ): Promise { const [options, chunkCb, onFinished, onError] = args; - const stream = await this.getFile(); + const { stream } = await this.getFile(); // chunk parse if (options && chunkCb) { @@ -299,7 +325,7 @@ export class ExcelImporter extends Importer { onFinished?: () => void, onError?: (errorMsg: string) => void ): Promise { - const fileSteam = await this.getFile(); + const { stream: fileSteam } = await this.getFile(); const asyncRs = async (stream: NodeJS.ReadableStream): Promise => new Promise((res, rej) => { diff --git a/apps/nestjs-backend/src/features/record/record.service.ts b/apps/nestjs-backend/src/features/record/record.service.ts index b727619e5..980a37fd9 100644 --- a/apps/nestjs-backend/src/features/record/record.service.ts +++ b/apps/nestjs-backend/src/features/record/record.service.ts @@ -676,18 +676,19 @@ export class RecordService { await this.creditCheck(tableId); const dbTableName = await this.getDbTableName(tableId); const fields = await this.getFieldsByProjection(tableId); - const fieldsMap = fields.reduce( + const fieldInstanceMap = fields.reduce( (map, curField) => { - map[curField.id] = curField.dbFieldName; + map[curField.id] = curField; return map; }, - {} as Record + {} as Record ); const newRecords = records.map((record) => { const fieldsValues: Record = {}; Object.entries(record.fields).forEach(([fieldId, value]) => { - fieldsValues[fieldsMap[fieldId]] = value; + const fieldInstance = fieldInstanceMap[fieldId]; + fieldsValues[fieldInstance.dbFieldName] = fieldInstance.convertCellValue2DBValue(value); }); return { __id: generateRecordId(), diff --git a/apps/nestjs-backend/src/worker/parse.ts b/apps/nestjs-backend/src/worker/parse.ts new file mode 100644 index 000000000..1fb8f738e --- /dev/null +++ b/apps/nestjs-backend/src/worker/parse.ts @@ -0,0 +1,41 @@ +import { parentPort, workerData } from 'worker_threads'; +import { getRandomString } from '@teable/core'; +import type { IImportConstructorParams } from '../features/import/open-api/import.class'; +import { importerFactory } from '../features/import/open-api/import.class'; + +const parse = () => { + const { config, options, id } = { ...workerData } as { + config: IImportConstructorParams; + options: { + skipFirstNLines: number; + key: string; + }; + id: string; + }; + const importer = importerFactory(config.type, config); + importer.parse( + { ...options }, + async (chunk) => { + return await new Promise((resolve) => { + const chunkId = `chunk_${getRandomString(8)}`; + parentPort?.postMessage({ type: 'chunk', data: chunk, chunkId, id }); + parentPort?.on('message', (result) => { + const { type, chunkId: tunnelChunkId } = result; + if (type === 'done' && tunnelChunkId === chunkId) { + resolve(); + } + }); + }); + }, + () => { + parentPort?.postMessage({ type: 'finished', id }); + parentPort?.close(); + }, + (error) => { + parentPort?.postMessage({ type: 'error', data: error, id }); + parentPort?.close(); + } + ); +}; + +parse(); diff --git a/apps/nestjs-backend/test/table-import.e2e-spec.ts b/apps/nestjs-backend/test/table-import.e2e-spec.ts index a7612c162..605fa2488 100644 --- a/apps/nestjs-backend/test/table-import.e2e-spec.ts +++ b/apps/nestjs-backend/test/table-import.e2e-spec.ts @@ -124,7 +124,7 @@ const genTestFiles = async () => { const { data: { presignedUrl }, - } = await apiNotify(token); + } = await apiNotify(token, undefined, 'Import Table.csv'); result[format] = { path: tmpPath, @@ -222,6 +222,8 @@ describe('OpenAPI ImportController (e2e)', () => { }); describe('/import/{baseId} OpenAPI ImportController (e2e) (Post)', () => { + const delay = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms)); + it.each(testFileFormats.filter((format) => format !== TestFileFormat.TXT))( 'should create a new Table from %s file', async (format) => { @@ -266,10 +268,15 @@ describe('OpenAPI ImportController (e2e)', () => { name: field.name, })); - await apiGetTableById(baseId, table.data[0].id); + await delay(1000); + + const { records } = await apiGetTableById(baseId, table.data[0].id, { + includeContent: true, + }); bases.push([baseId, id]); + expect(records?.length).toBe(2); expect(createdFields).toEqual(assertHeaders); } ); diff --git a/apps/nestjs-backend/vitest-e2e.setup.ts b/apps/nestjs-backend/vitest-e2e.setup.ts index 9e541a6c6..d50d93c46 100644 --- a/apps/nestjs-backend/vitest-e2e.setup.ts +++ b/apps/nestjs-backend/vitest-e2e.setup.ts @@ -3,6 +3,7 @@ import path from 'path'; import type { INestApplication } from '@nestjs/common'; import { DriverClient, getRandomString, parseDsn } from '@teable/core'; import dotenv from 'dotenv-flow'; +import { buildSync } from 'esbuild'; interface ITestConfig { driver: string; @@ -61,8 +62,20 @@ function prepareSqliteEnv() { fs.copyFileSync(path.join(dbPath, baseName), path.join(testDbPath, newFileName)); } +function compileWorkerFile() { + const entryFile = path.join(__dirname, 'src/worker/**.ts'); + const outFile = path.join(__dirname, 'dist/worker'); + + buildSync({ + entryPoints: [entryFile], + outdir: outFile, + bundle: true, + platform: 'node', + target: 'node14', + }); +} + async function setup() { - console.log('node-env', process.env.NODE_ENV); dotenv.config({ path: '../nextjs-app' }); // eslint-disable-next-line @typescript-eslint/no-non-null-assertion @@ -74,6 +87,8 @@ async function setup() { globalThis.testConfig.driver = driver; prepareSqliteEnv(); + + compileWorkerFile(); } export default setup(); diff --git a/apps/nestjs-backend/webpack.config.js b/apps/nestjs-backend/webpack.config.js index a170f44aa..5e8d13c48 100644 --- a/apps/nestjs-backend/webpack.config.js +++ b/apps/nestjs-backend/webpack.config.js @@ -1,8 +1,26 @@ +const path = require('path'); const CopyPlugin = require('copy-webpack-plugin'); +const glob = require('glob'); module.exports = function (options) { + const workerFiles = glob.sync(path.join(__dirname, 'src/worker/**.ts')); + const workerEntries = workerFiles.reduce((acc, file) => { + const relativePath = path.relative(path.join(__dirname, 'src/worker'), file); + const entryName = `worker/${path.dirname(relativePath)}/${path.basename(relativePath, '.ts')}`; + acc[entryName] = file; + return acc; + }, {}); + return { ...options, + entry: { + index: options.entry, + ...workerEntries, + }, + output: { + path: path.join(__dirname, 'dist'), + filename: '[name].js', + }, plugins: [ new CopyPlugin({ patterns: [{ from: 'src/features/mail-sender/templates', to: 'templates' }], diff --git a/apps/nestjs-backend/webpack.dev.js b/apps/nestjs-backend/webpack.dev.js index 2caf5f19c..4510cfd51 100644 --- a/apps/nestjs-backend/webpack.dev.js +++ b/apps/nestjs-backend/webpack.dev.js @@ -1,11 +1,27 @@ +const path = require('path'); const CopyPlugin = require('copy-webpack-plugin'); const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin'); +const glob = require('glob'); const nodeExternals = require('webpack-node-externals'); module.exports = function (options, webpack) { + const workerFiles = glob.sync(path.join(__dirname, 'src/worker/**.ts')); + const workerEntries = workerFiles.reduce((acc, file) => { + const relativePath = path.relative(path.join(__dirname, 'src/worker'), file); + const entryName = `worker/${path.dirname(relativePath)}/${path.basename(relativePath, '.ts')}`; + acc[entryName] = file; + return acc; + }, {}); return { ...options, - entry: ['webpack/hot/poll?100', options.entry], + entry: { + index: ['webpack/hot/poll?100', options.entry], + ...workerEntries, + }, + output: { + path: path.join(__dirname, 'dist'), + filename: '[name].js', + }, mode: 'development', devtool: 'source-map', externals: [ diff --git a/packages/sdk/src/components/filter/condition/BaseOperatorSelect.tsx b/packages/sdk/src/components/filter/condition/BaseOperatorSelect.tsx index 7d92a9c6e..938f4b603 100644 --- a/packages/sdk/src/components/filter/condition/BaseOperatorSelect.tsx +++ b/packages/sdk/src/components/filter/condition/BaseOperatorSelect.tsx @@ -40,8 +40,8 @@ export function BaseOperatorSelect(props: IBaseOperatorSelectProps) { options={operatorOption} popoverClassName="w-48" className={cn('m-1 shrink-0 justify-between', { - 'max-w-40': compact, - 'w-40': !compact, + 'max-w-32': compact, + 'w-32': !compact, })} onSelect={onSelect} disabled={shouldDisabled} diff --git a/packages/sdk/src/components/filter/condition/FieldSelect.tsx b/packages/sdk/src/components/filter/condition/FieldSelect.tsx index 91a15a890..759693dab 100644 --- a/packages/sdk/src/components/filter/condition/FieldSelect.tsx +++ b/packages/sdk/src/components/filter/condition/FieldSelect.tsx @@ -43,8 +43,8 @@ function FieldSelect(props: IFieldSelectProps) { onSelect={onSelect} value={value} className={cn('shrink-0', { - 'max-w-40': compact, - 'w-40': !compact, + 'max-w-32': compact, + 'w-32': !compact, })} popoverClassName="w-fit" optionRender={optionRender} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 320d71ecd..7dbaa742d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -186,6 +186,9 @@ importers: dayjs: specifier: 1.11.10 version: 1.11.10 + esbuild: + specifier: 0.23.0 + version: 0.23.0 express: specifier: 4.19.1 version: 4.19.1 @@ -330,7 +333,7 @@ importers: version: 8.4.1 '@nestjs/cli': specifier: 10.3.2 - version: 10.3.2(@swc/core@1.4.8) + version: 10.3.2(@swc/core@1.4.8)(esbuild@0.23.0) '@nestjs/testing': specifier: 10.3.5 version: 10.3.5(@nestjs/common@10.3.5)(@nestjs/core@10.3.5)(@nestjs/platform-express@10.3.5) @@ -486,7 +489,7 @@ importers: version: 1.3.1(typescript@5.4.3)(vitest@1.4.0) webpack: specifier: 5.91.0 - version: 5.91.0(@swc/core@1.4.8) + version: 5.91.0(@swc/core@1.4.8)(esbuild@0.23.0) apps/nextjs-app: dependencies: @@ -5015,6 +5018,14 @@ packages: dev: true optional: true + /@esbuild/aix-ppc64@0.23.0: + resolution: {integrity: sha512-3sG8Zwa5fMcA9bgqB8AfWPQ+HFke6uD3h1s3RIwUNK8EG7a4buxvuFTs3j1IMs2NXAk9F30C/FF4vxRgQCcmoQ==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + requiresBuild: true + optional: true + /@esbuild/android-arm64@0.19.12: resolution: {integrity: sha512-P0UVNGIienjZv3f5zq0DP3Nt2IE/3plFzuaS96vihvD0Hd6H/q4WXUGpCxD/E8YrSXfNyRPbpTq+T8ZQioSuPA==} engines: {node: '>=12'} @@ -5033,6 +5044,14 @@ packages: dev: true optional: true + /@esbuild/android-arm64@0.23.0: + resolution: {integrity: sha512-EuHFUYkAVfU4qBdyivULuu03FhJO4IJN9PGuABGrFy4vUuzk91P2d+npxHcFdpUnfYKy0PuV+n6bKIpHOB3prQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + requiresBuild: true + optional: true + /@esbuild/android-arm@0.19.12: resolution: {integrity: sha512-qg/Lj1mu3CdQlDEEiWrlC4eaPZ1KztwGJ9B6J+/6G+/4ewxJg7gqj8eVYWvao1bXrqGiW2rsBZFSX3q2lcW05w==} engines: {node: '>=12'} @@ -5051,6 +5070,14 @@ packages: dev: true optional: true + /@esbuild/android-arm@0.23.0: + resolution: {integrity: sha512-+KuOHTKKyIKgEEqKbGTK8W7mPp+hKinbMBeEnNzjJGyFcWsfrXjSTNluJHCY1RqhxFurdD8uNXQDei7qDlR6+g==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + requiresBuild: true + optional: true + /@esbuild/android-x64@0.19.12: resolution: {integrity: sha512-3k7ZoUW6Q6YqhdhIaq/WZ7HwBpnFBlW905Fa4s4qWJyiNOgT1dOqDiVAQFwBH7gBRZr17gLrlFCRzF6jFh7Kew==} engines: {node: '>=12'} @@ -5069,6 +5096,14 @@ packages: dev: true optional: true + /@esbuild/android-x64@0.23.0: + resolution: {integrity: sha512-WRrmKidLoKDl56LsbBMhzTTBxrsVwTKdNbKDalbEZr0tcsBgCLbEtoNthOW6PX942YiYq8HzEnb4yWQMLQuipQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + requiresBuild: true + optional: true + /@esbuild/darwin-arm64@0.19.12: resolution: {integrity: sha512-B6IeSgZgtEzGC42jsI+YYu9Z3HKRxp8ZT3cqhvliEHovq8HSX2YX8lNocDn79gCKJXOSaEot9MVYky7AKjCs8g==} engines: {node: '>=12'} @@ -5087,6 +5122,14 @@ packages: dev: true optional: true + /@esbuild/darwin-arm64@0.23.0: + resolution: {integrity: sha512-YLntie/IdS31H54Ogdn+v50NuoWF5BDkEUFpiOChVa9UnKpftgwzZRrI4J132ETIi+D8n6xh9IviFV3eXdxfow==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + requiresBuild: true + optional: true + /@esbuild/darwin-x64@0.19.12: resolution: {integrity: sha512-hKoVkKzFiToTgn+41qGhsUJXFlIjxI/jSYeZf3ugemDYZldIXIxhvwN6erJGlX4t5h417iFuheZ7l+YVn05N3A==} engines: {node: '>=12'} @@ -5105,6 +5148,14 @@ packages: dev: true optional: true + /@esbuild/darwin-x64@0.23.0: + resolution: {integrity: sha512-IMQ6eme4AfznElesHUPDZ+teuGwoRmVuuixu7sv92ZkdQcPbsNHzutd+rAfaBKo8YK3IrBEi9SLLKWJdEvJniQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + requiresBuild: true + optional: true + /@esbuild/freebsd-arm64@0.19.12: resolution: {integrity: sha512-4aRvFIXmwAcDBw9AueDQ2YnGmz5L6obe5kmPT8Vd+/+x/JMVKCgdcRwH6APrbpNXsPz+K653Qg8HB/oXvXVukA==} engines: {node: '>=12'} @@ -5123,6 +5174,14 @@ packages: dev: true optional: true + /@esbuild/freebsd-arm64@0.23.0: + resolution: {integrity: sha512-0muYWCng5vqaxobq6LB3YNtevDFSAZGlgtLoAc81PjUfiFz36n4KMpwhtAd4he8ToSI3TGyuhyx5xmiWNYZFyw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + requiresBuild: true + optional: true + /@esbuild/freebsd-x64@0.19.12: resolution: {integrity: sha512-EYoXZ4d8xtBoVN7CEwWY2IN4ho76xjYXqSXMNccFSx2lgqOG/1TBPW0yPx1bJZk94qu3tX0fycJeeQsKovA8gg==} engines: {node: '>=12'} @@ -5141,6 +5200,14 @@ packages: dev: true optional: true + /@esbuild/freebsd-x64@0.23.0: + resolution: {integrity: sha512-XKDVu8IsD0/q3foBzsXGt/KjD/yTKBCIwOHE1XwiXmrRwrX6Hbnd5Eqn/WvDekddK21tfszBSrE/WMaZh+1buQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + requiresBuild: true + optional: true + /@esbuild/linux-arm64@0.19.12: resolution: {integrity: sha512-EoTjyYyLuVPfdPLsGVVVC8a0p1BFFvtpQDB/YLEhaXyf/5bczaGeN15QkR+O4S5LeJ92Tqotve7i1jn35qwvdA==} engines: {node: '>=12'} @@ -5159,6 +5226,14 @@ packages: dev: true optional: true + /@esbuild/linux-arm64@0.23.0: + resolution: {integrity: sha512-j1t5iG8jE7BhonbsEg5d9qOYcVZv/Rv6tghaXM/Ug9xahM0nX/H2gfu6X6z11QRTMT6+aywOMA8TDkhPo8aCGw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + requiresBuild: true + optional: true + /@esbuild/linux-arm@0.19.12: resolution: {integrity: sha512-J5jPms//KhSNv+LO1S1TX1UWp1ucM6N6XuL6ITdKWElCu8wXP72l9MM0zDTzzeikVyqFE6U8YAV9/tFyj0ti+w==} engines: {node: '>=12'} @@ -5177,6 +5252,14 @@ packages: dev: true optional: true + /@esbuild/linux-arm@0.23.0: + resolution: {integrity: sha512-SEELSTEtOFu5LPykzA395Mc+54RMg1EUgXP+iw2SJ72+ooMwVsgfuwXo5Fn0wXNgWZsTVHwY2cg4Vi/bOD88qw==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + requiresBuild: true + optional: true + /@esbuild/linux-ia32@0.19.12: resolution: {integrity: sha512-Thsa42rrP1+UIGaWz47uydHSBOgTUnwBwNq59khgIwktK6x60Hivfbux9iNR0eHCHzOLjLMLfUMLCypBkZXMHA==} engines: {node: '>=12'} @@ -5195,6 +5278,14 @@ packages: dev: true optional: true + /@esbuild/linux-ia32@0.23.0: + resolution: {integrity: sha512-P7O5Tkh2NbgIm2R6x1zGJJsnacDzTFcRWZyTTMgFdVit6E98LTxO+v8LCCLWRvPrjdzXHx9FEOA8oAZPyApWUA==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + requiresBuild: true + optional: true + /@esbuild/linux-loong64@0.19.12: resolution: {integrity: sha512-LiXdXA0s3IqRRjm6rV6XaWATScKAXjI4R4LoDlvO7+yQqFdlr1Bax62sRwkVvRIrwXxvtYEHHI4dm50jAXkuAA==} engines: {node: '>=12'} @@ -5213,6 +5304,14 @@ packages: dev: true optional: true + /@esbuild/linux-loong64@0.23.0: + resolution: {integrity: sha512-InQwepswq6urikQiIC/kkx412fqUZudBO4SYKu0N+tGhXRWUqAx+Q+341tFV6QdBifpjYgUndV1hhMq3WeJi7A==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + requiresBuild: true + optional: true + /@esbuild/linux-mips64el@0.19.12: resolution: {integrity: sha512-fEnAuj5VGTanfJ07ff0gOA6IPsvrVHLVb6Lyd1g2/ed67oU1eFzL0r9WL7ZzscD+/N6i3dWumGE1Un4f7Amf+w==} engines: {node: '>=12'} @@ -5231,6 +5330,14 @@ packages: dev: true optional: true + /@esbuild/linux-mips64el@0.23.0: + resolution: {integrity: sha512-J9rflLtqdYrxHv2FqXE2i1ELgNjT+JFURt/uDMoPQLcjWQA5wDKgQA4t/dTqGa88ZVECKaD0TctwsUfHbVoi4w==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + requiresBuild: true + optional: true + /@esbuild/linux-ppc64@0.19.12: resolution: {integrity: sha512-nYJA2/QPimDQOh1rKWedNOe3Gfc8PabU7HT3iXWtNUbRzXS9+vgB0Fjaqr//XNbd82mCxHzik2qotuI89cfixg==} engines: {node: '>=12'} @@ -5249,6 +5356,14 @@ packages: dev: true optional: true + /@esbuild/linux-ppc64@0.23.0: + resolution: {integrity: sha512-cShCXtEOVc5GxU0fM+dsFD10qZ5UpcQ8AM22bYj0u/yaAykWnqXJDpd77ublcX6vdDsWLuweeuSNZk4yUxZwtw==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + requiresBuild: true + optional: true + /@esbuild/linux-riscv64@0.19.12: resolution: {integrity: sha512-2MueBrlPQCw5dVJJpQdUYgeqIzDQgw3QtiAHUC4RBz9FXPrskyyU3VI1hw7C0BSKB9OduwSJ79FTCqtGMWqJHg==} engines: {node: '>=12'} @@ -5267,6 +5382,14 @@ packages: dev: true optional: true + /@esbuild/linux-riscv64@0.23.0: + resolution: {integrity: sha512-HEtaN7Y5UB4tZPeQmgz/UhzoEyYftbMXrBCUjINGjh3uil+rB/QzzpMshz3cNUxqXN7Vr93zzVtpIDL99t9aRw==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + requiresBuild: true + optional: true + /@esbuild/linux-s390x@0.19.12: resolution: {integrity: sha512-+Pil1Nv3Umes4m3AZKqA2anfhJiVmNCYkPchwFJNEJN5QxmTs1uzyy4TvmDrCRNT2ApwSari7ZIgrPeUx4UZDg==} engines: {node: '>=12'} @@ -5285,6 +5408,14 @@ packages: dev: true optional: true + /@esbuild/linux-s390x@0.23.0: + resolution: {integrity: sha512-WDi3+NVAuyjg/Wxi+o5KPqRbZY0QhI9TjrEEm+8dmpY9Xir8+HE/HNx2JoLckhKbFopW0RdO2D72w8trZOV+Wg==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + requiresBuild: true + optional: true + /@esbuild/linux-x64@0.19.12: resolution: {integrity: sha512-B71g1QpxfwBvNrfyJdVDexenDIt1CiDN1TIXLbhOw0KhJzE78KIFGX6OJ9MrtC0oOqMWf+0xop4qEU8JrJTwCg==} engines: {node: '>=12'} @@ -5303,6 +5434,14 @@ packages: dev: true optional: true + /@esbuild/linux-x64@0.23.0: + resolution: {integrity: sha512-a3pMQhUEJkITgAw6e0bWA+F+vFtCciMjW/LPtoj99MhVt+Mfb6bbL9hu2wmTZgNd994qTAEw+U/r6k3qHWWaOQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + requiresBuild: true + optional: true + /@esbuild/netbsd-x64@0.19.12: resolution: {integrity: sha512-3ltjQ7n1owJgFbuC61Oj++XhtzmymoCihNFgT84UAmJnxJfm4sYCiSLTXZtE00VWYpPMYc+ZQmB6xbSdVh0JWA==} engines: {node: '>=12'} @@ -5321,6 +5460,22 @@ packages: dev: true optional: true + /@esbuild/netbsd-x64@0.23.0: + resolution: {integrity: sha512-cRK+YDem7lFTs2Q5nEv/HHc4LnrfBCbH5+JHu6wm2eP+d8OZNoSMYgPZJq78vqQ9g+9+nMuIsAO7skzphRXHyw==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + requiresBuild: true + optional: true + + /@esbuild/openbsd-arm64@0.23.0: + resolution: {integrity: sha512-suXjq53gERueVWu0OKxzWqk7NxiUWSUlrxoZK7usiF50C6ipColGR5qie2496iKGYNLhDZkPxBI3erbnYkU0rQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + requiresBuild: true + optional: true + /@esbuild/openbsd-x64@0.19.12: resolution: {integrity: sha512-RbrfTB9SWsr0kWmb9srfF+L933uMDdu9BIzdA7os2t0TXhCRjrQyCeOt6wVxr79CKD4c+p+YhCj31HBkYcXebw==} engines: {node: '>=12'} @@ -5339,6 +5494,14 @@ packages: dev: true optional: true + /@esbuild/openbsd-x64@0.23.0: + resolution: {integrity: sha512-6p3nHpby0DM/v15IFKMjAaayFhqnXV52aEmv1whZHX56pdkK+MEaLoQWj+H42ssFarP1PcomVhbsR4pkz09qBg==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + requiresBuild: true + optional: true + /@esbuild/sunos-x64@0.19.12: resolution: {integrity: sha512-HKjJwRrW8uWtCQnQOz9qcU3mUZhTUQvi56Q8DPTLLB+DawoiQdjsYq+j+D3s9I8VFtDr+F9CjgXKKC4ss89IeA==} engines: {node: '>=12'} @@ -5357,6 +5520,14 @@ packages: dev: true optional: true + /@esbuild/sunos-x64@0.23.0: + resolution: {integrity: sha512-BFelBGfrBwk6LVrmFzCq1u1dZbG4zy/Kp93w2+y83Q5UGYF1d8sCzeLI9NXjKyujjBBniQa8R8PzLFAUrSM9OA==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + requiresBuild: true + optional: true + /@esbuild/win32-arm64@0.19.12: resolution: {integrity: sha512-URgtR1dJnmGvX864pn1B2YUYNzjmXkuJOIqG2HdU62MVS4EHpU2946OZoTMnRUHklGtJdJZ33QfzdjGACXhn1A==} engines: {node: '>=12'} @@ -5375,6 +5546,14 @@ packages: dev: true optional: true + /@esbuild/win32-arm64@0.23.0: + resolution: {integrity: sha512-lY6AC8p4Cnb7xYHuIxQ6iYPe6MfO2CC43XXKo9nBXDb35krYt7KGhQnOkRGar5psxYkircpCqfbNDB4uJbS2jQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + requiresBuild: true + optional: true + /@esbuild/win32-ia32@0.19.12: resolution: {integrity: sha512-+ZOE6pUkMOJfmxmBZElNOx72NKpIa/HFOMGzu8fqzQJ5kgf6aTGrcJaFsNiVMH4JKpMipyK+7k0n2UXN7a8YKQ==} engines: {node: '>=12'} @@ -5393,6 +5572,14 @@ packages: dev: true optional: true + /@esbuild/win32-ia32@0.23.0: + resolution: {integrity: sha512-7L1bHlOTcO4ByvI7OXVI5pNN6HSu6pUQq9yodga8izeuB1KcT2UkHaH6118QJwopExPn0rMHIseCTx1CRo/uNA==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + requiresBuild: true + optional: true + /@esbuild/win32-x64@0.19.12: resolution: {integrity: sha512-T1QyPSDCyMXaO3pzBkF96E8xMkiRYbUEZADd29SyPGabqxMViNoii+NcK7eWJAEoU6RZyEm5lVSIjTmcdoB9HA==} engines: {node: '>=12'} @@ -5411,6 +5598,14 @@ packages: dev: true optional: true + /@esbuild/win32-x64@0.23.0: + resolution: {integrity: sha512-Arm+WgUFLUATuoxCJcahGuk6Yj9Pzxd6l11Zb/2aAuv5kWWvvfhLFo2fni4uSK5vzlUdCGZ/BdV5tH8klj8p8g==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + requiresBuild: true + optional: true + /@eslint-community/eslint-utils@4.4.0(eslint@8.57.0): resolution: {integrity: sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -6005,7 +6200,7 @@ packages: rxjs: 7.8.1 dev: false - /@nestjs/cli@10.3.2(@swc/core@1.4.8): + /@nestjs/cli@10.3.2(@swc/core@1.4.8)(esbuild@0.23.0): resolution: {integrity: sha512-aWmD1GLluWrbuC4a1Iz/XBk5p74Uj6nIVZj6Ov03JbTfgtWqGFLtXuMetvzMiHxfrHehx/myt2iKAPRhKdZvTg==} engines: {node: '>= 16.14'} hasBin: true @@ -6039,7 +6234,7 @@ packages: tsconfig-paths: 4.2.0 tsconfig-paths-webpack-plugin: 4.1.0 typescript: 5.3.3 - webpack: 5.90.1(@swc/core@1.4.8) + webpack: 5.90.1(@swc/core@1.4.8)(esbuild@0.23.0) webpack-node-externals: 3.0.0 transitivePeerDependencies: - esbuild @@ -13836,7 +14031,7 @@ packages: normalize-path: 3.0.0 schema-utils: 4.2.0 serialize-javascript: 6.0.2 - webpack: 5.91.0(@swc/core@1.4.8) + webpack: 5.91.0(@swc/core@1.4.8)(esbuild@0.23.0) dev: true /core-js-compat@3.36.1: @@ -15303,6 +15498,37 @@ packages: '@esbuild/win32-x64': 0.20.2 dev: true + /esbuild@0.23.0: + resolution: {integrity: sha512-1lvV17H2bMYda/WaFb2jLPeHU3zml2k4/yagNMG8Q/YtfMjCwEUZa2eXXMgZTVSL5q1n4H7sQ0X6CdJDqqeCFA==} + engines: {node: '>=18'} + hasBin: true + requiresBuild: true + optionalDependencies: + '@esbuild/aix-ppc64': 0.23.0 + '@esbuild/android-arm': 0.23.0 + '@esbuild/android-arm64': 0.23.0 + '@esbuild/android-x64': 0.23.0 + '@esbuild/darwin-arm64': 0.23.0 + '@esbuild/darwin-x64': 0.23.0 + '@esbuild/freebsd-arm64': 0.23.0 + '@esbuild/freebsd-x64': 0.23.0 + '@esbuild/linux-arm': 0.23.0 + '@esbuild/linux-arm64': 0.23.0 + '@esbuild/linux-ia32': 0.23.0 + '@esbuild/linux-loong64': 0.23.0 + '@esbuild/linux-mips64el': 0.23.0 + '@esbuild/linux-ppc64': 0.23.0 + '@esbuild/linux-riscv64': 0.23.0 + '@esbuild/linux-s390x': 0.23.0 + '@esbuild/linux-x64': 0.23.0 + '@esbuild/netbsd-x64': 0.23.0 + '@esbuild/openbsd-arm64': 0.23.0 + '@esbuild/openbsd-x64': 0.23.0 + '@esbuild/sunos-x64': 0.23.0 + '@esbuild/win32-arm64': 0.23.0 + '@esbuild/win32-ia32': 0.23.0 + '@esbuild/win32-x64': 0.23.0 + /escalade@3.1.2: resolution: {integrity: sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==} engines: {node: '>=6'} @@ -16639,7 +16865,7 @@ packages: semver: 7.6.0 tapable: 2.2.1 typescript: 5.3.3 - webpack: 5.90.1(@swc/core@1.4.8) + webpack: 5.90.1(@swc/core@1.4.8)(esbuild@0.23.0) dev: true /form-data@4.0.0: @@ -26165,7 +26391,7 @@ packages: worker-farm: 1.7.0 dev: true - /terser-webpack-plugin@5.3.10(@swc/core@1.4.8)(webpack@5.90.1): + /terser-webpack-plugin@5.3.10(@swc/core@1.4.8)(esbuild@0.23.0)(webpack@5.90.1): resolution: {integrity: sha512-BKFPWlPDndPs+NGGCr1U59t0XScL5317Y0UReNrHaw9/FwhPENlq6bfgs+4yPfyP51vqC1bQ4rp1EfXW5ZSH9w==} engines: {node: '>= 10.13.0'} peerDependencies: @@ -26183,14 +26409,15 @@ packages: dependencies: '@jridgewell/trace-mapping': 0.3.25 '@swc/core': 1.4.8 + esbuild: 0.23.0 jest-worker: 27.5.1 schema-utils: 3.3.0 serialize-javascript: 6.0.2 terser: 5.29.2 - webpack: 5.90.1(@swc/core@1.4.8) + webpack: 5.90.1(@swc/core@1.4.8)(esbuild@0.23.0) dev: true - /terser-webpack-plugin@5.3.10(@swc/core@1.4.8)(webpack@5.91.0): + /terser-webpack-plugin@5.3.10(@swc/core@1.4.8)(esbuild@0.23.0)(webpack@5.91.0): resolution: {integrity: sha512-BKFPWlPDndPs+NGGCr1U59t0XScL5317Y0UReNrHaw9/FwhPENlq6bfgs+4yPfyP51vqC1bQ4rp1EfXW5ZSH9w==} engines: {node: '>= 10.13.0'} peerDependencies: @@ -26208,11 +26435,12 @@ packages: dependencies: '@jridgewell/trace-mapping': 0.3.25 '@swc/core': 1.4.8 + esbuild: 0.23.0 jest-worker: 27.5.1 schema-utils: 3.3.0 serialize-javascript: 6.0.2 terser: 5.29.2 - webpack: 5.91.0(@swc/core@1.4.8) + webpack: 5.91.0(@swc/core@1.4.8)(esbuild@0.23.0) dev: true /terser-webpack-plugin@5.3.10(esbuild@0.20.2)(webpack@5.91.0): @@ -26533,7 +26761,7 @@ packages: semver: 7.6.0 source-map: 0.7.4 typescript: 5.4.3 - webpack: 5.91.0(@swc/core@1.4.8) + webpack: 5.91.0(@swc/core@1.4.8)(esbuild@0.23.0) dev: true /ts-mixer@6.0.4: @@ -27707,7 +27935,7 @@ packages: - supports-color dev: true - /webpack@5.90.1(@swc/core@1.4.8): + /webpack@5.90.1(@swc/core@1.4.8)(esbuild@0.23.0): resolution: {integrity: sha512-SstPdlAC5IvgFnhiRok8hqJo/+ArAbNv7rhU4fnWGHNVfN59HSQFaxZDSAL3IFG2YmqxuRs+IU33milSxbPlog==} engines: {node: '>=10.13.0'} hasBin: true @@ -27738,7 +27966,7 @@ packages: neo-async: 2.6.2 schema-utils: 3.3.0 tapable: 2.2.1 - terser-webpack-plugin: 5.3.10(@swc/core@1.4.8)(webpack@5.90.1) + terser-webpack-plugin: 5.3.10(@swc/core@1.4.8)(esbuild@0.23.0)(webpack@5.90.1) watchpack: 2.4.1 webpack-sources: 3.2.3 transitivePeerDependencies: @@ -27747,7 +27975,7 @@ packages: - uglify-js dev: true - /webpack@5.91.0(@swc/core@1.4.8): + /webpack@5.91.0(@swc/core@1.4.8)(esbuild@0.23.0): resolution: {integrity: sha512-rzVwlLeBWHJbmgTC/8TvAcu5vpJNII+MelQpylD4jNERPwpBJOE2lEcko1zJX3QJeLjTTAnQxn/OJ8bjDzVQaw==} engines: {node: '>=10.13.0'} hasBin: true @@ -27778,7 +28006,7 @@ packages: neo-async: 2.6.2 schema-utils: 3.3.0 tapable: 2.2.1 - terser-webpack-plugin: 5.3.10(@swc/core@1.4.8)(webpack@5.91.0) + terser-webpack-plugin: 5.3.10(@swc/core@1.4.8)(esbuild@0.23.0)(webpack@5.91.0) watchpack: 2.4.1 webpack-sources: 3.2.3 transitivePeerDependencies: