diff --git a/.github/workflows/broken_links_checker.yml b/.github/workflows/broken_links_checker.yml index f2079ec..82ec1cd 100644 --- a/.github/workflows/broken_links_checker.yml +++ b/.github/workflows/broken_links_checker.yml @@ -15,7 +15,7 @@ jobs: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Configure broken links checker run: | mkdir -p ./target @@ -27,6 +27,6 @@ jobs: ']}' > ./target/broken_links_checker.json - uses: gaurav-nelson/github-action-markdown-link-check@v1 with: - use-quiet-mode: 'yes' - use-verbose-mode: 'yes' + use-quiet-mode: "yes" + use-verbose-mode: "yes" config-file: ./target/broken_links_checker.json diff --git a/.github/workflows/ci-build.yml b/.github/workflows/ci-build.yml index b408314..a14203b 100644 --- a/.github/workflows/ci-build.yml +++ b/.github/workflows/ci-build.yml @@ -14,13 +14,13 @@ jobs: cancel-in-progress: true steps: - name: Checkout the repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: fetch-depth: 0 - name: Set up Node 18 uses: actions/setup-node@v3 with: - node-version: 16 + node-version: 18 cache: "npm" - name: Install dependencies run: npm ci diff --git a/.github/workflows/project-keeper-verify.yml b/.github/workflows/project-keeper-verify.yml index 6dc8f84..abaf33a 100644 --- a/.github/workflows/project-keeper-verify.yml +++ b/.github/workflows/project-keeper-verify.yml @@ -14,7 +14,7 @@ jobs: cancel-in-progress: true steps: - name: Checkout the repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: fetch-depth: 0 @@ -27,7 +27,11 @@ jobs: - name: Set up Go uses: actions/setup-go@v4 with: - go-version: 1.20 + go-version: "1.20" + cache-dependency-path: | + go.sum + .github/workflows/project-keeper-verify.yml + .github/workflows/project-keeper.sh - name: Set up NPM uses: actions/setup-node@v3 @@ -44,16 +48,5 @@ jobs: restore-keys: | ${{ runner.os }}-pk- - - name: Cache go-licenses - uses: actions/cache@v3 - with: - path: | - ~/.cache/go-build - ~/go/pkg/mod - key: ${{ runner.os }}-go-licenses-${{ hashFiles('.github/workflows/project-keeper-verify.yml') }} - restore-keys: | - ${{ runner.os }}-go-licenses-${{ hashFiles('.github/workflows/project-keeper-verify.yml') }} - ${{ runner.os }}-go-licenses- - - name: Project Keeper Verify run: ./.github/workflows/project-keeper.sh diff --git a/.github/workflows/project-keeper.sh b/.github/workflows/project-keeper.sh index af0f76f..af9eeec 100755 --- a/.github/workflows/project-keeper.sh +++ b/.github/workflows/project-keeper.sh @@ -5,7 +5,7 @@ set -o nounset set -o pipefail readonly pk_mode="${1-verify}"; -readonly version="2.9.10" +readonly version="2.9.15" readonly pk_jar="$HOME/.m2/repository/com/exasol/project-keeper-cli/$version/project-keeper-cli-$version.jar" diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index eb6bb40..d91dec4 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -8,7 +8,7 @@ jobs: name: Build and release runs-on: ubuntu-22.04 steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - uses: actions/setup-node@v3 with: node-version: 18 diff --git a/.project-keeper.yml b/.project-keeper.yml index 1e3be0b..6a8e692 100644 --- a/.project-keeper.yml +++ b/.project-keeper.yml @@ -1,4 +1,3 @@ sources: - type: npm path: package.json -version: 0.3.1 diff --git a/README.md b/README.md index f7b60c1..5ac00e2 100644 --- a/README.md +++ b/README.md @@ -11,5 +11,6 @@ the [Exasol extension-manager](https://github.com/exasol/extension-manager/). ## Additional Information * [Changelog](doc/changes/changelog.md) +* [Guide for developing an extension for Extension Manager](https://github.com/exasol/extension-manager/blob/main/doc/extension_developer_guide.md) * [Developer Guide](doc/developer_guide/developer_guide.md) * [Dependencies](dependencies.md) diff --git a/doc/changes/changelog.md b/doc/changes/changelog.md index 3bba240..1950025 100644 --- a/doc/changes/changelog.md +++ b/doc/changes/changelog.md @@ -1,5 +1,6 @@ # Changes +* [0.4.0](changes_0.4.0.md) * [0.3.1](changes_0.3.1.md) * [0.3.0](changes_0.3.0.md) * [0.2.0](changes_0.2.0.md) diff --git a/doc/changes/changes_0.4.0.md b/doc/changes/changes_0.4.0.md new file mode 100644 index 0000000..552666c --- /dev/null +++ b/doc/changes/changes_0.4.0.md @@ -0,0 +1,22 @@ +# Extension Manager Interface 0.4.0, released 2023-11-03 + +Code name: Simplify Virtual Schema Extensions + +## Summary + +This release simplifies creating extension definitions for Java-based virtual schemas. To use it, simply create your `ExasolExtension` by calling `convertVirtualSchemaBaseExtension(baseExtension: JavaVirtualSchemaBaseExtension)`. This works similar to `convertBaseExtension(baseExtension: JavaBaseExtension)` for Java-based script extensions. No need to implement all extension methods yourself, just configure required `SCRIPT`s, `CONNECTION` parameters and `VIRTUAL SCHEMA` properties. See the the S3 Virtual Schema [extension](https://github.com/exasol/s3-document-files-virtual-schema/blob/main/extension/src/extension.ts) updated in [#139](https://github.com/exasol/s3-document-files-virtual-schema/pull/139) as an example. + +## Features + +* #49: Extracted common code for virtual schema extensions + +## Dependency Updates + +### Development Dependency Updates + +* Updated `eslint:^8.47.0` to `^8.52.0` +* Updated `@jest/globals:^29.6.3` to `^29.7.0` +* Updated `@typescript-eslint/parser:^6.4.1` to `^6.9.1` +* Updated `typescript:5.1.6` to `5.2.2` +* Updated `@typescript-eslint/eslint-plugin:^6.4.1` to `^6.9.1` +* Updated `jest:^29.6.3` to `^29.7.0` diff --git a/package-lock.json b/package-lock.json index 14dfaa2..6c433f2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,22 +1,22 @@ { "name": "@exasol/extension-manager-interface", - "version": "0.3.1", + "version": "0.4.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@exasol/extension-manager-interface", - "version": "0.3.1", + "version": "0.4.0", "license": "MIT", "devDependencies": { - "@jest/globals": "^29.6.3", - "@typescript-eslint/eslint-plugin": "^6.4.1", - "@typescript-eslint/parser": "^6.4.1", - "eslint": "^8.47.0", - "jest": "^29.6.3", + "@jest/globals": "^29.7.0", + "@typescript-eslint/eslint-plugin": "^6.9.1", + "@typescript-eslint/parser": "^6.9.1", + "eslint": "^8.52.0", + "jest": "^29.7.0", "ts-jest": "^29.1.1", "ts-node": "^10.9.1", - "typescript": "5.1.6" + "typescript": "5.2.2" } }, "node_modules/@aashutoshrathi/word-wrap": { @@ -42,12 +42,12 @@ } }, "node_modules/@babel/code-frame": { - "version": "7.22.10", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.10.tgz", - "integrity": "sha512-/KKIMG4UEL35WmI9OlvMhurwtytjvXoFcGNrOvyG9zIzA8YmPjVtIZUf7b05+TPO7G7/GEmLHDaoCgACHl9hhA==", + "version": "7.22.13", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.13.tgz", + "integrity": "sha512-XktuhWlJ5g+3TJXc5upd9Ks1HutSArik6jf2eAjYFyIOf4ej3RN+184cZbzDvbPnuTJIUhPKKJE3cIsYTiAT3w==", "dev": true, "dependencies": { - "@babel/highlight": "^7.22.10", + "@babel/highlight": "^7.22.13", "chalk": "^2.4.2" }, "engines": { @@ -180,12 +180,12 @@ } }, "node_modules/@babel/generator": { - "version": "7.22.10", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.22.10.tgz", - "integrity": "sha512-79KIf7YiWjjdZ81JnLujDRApWtl7BxTqWD88+FFdQEIOG8LJ0etDOM7CXuIgGJa55sGOwZVwuEsaLEm0PJ5/+A==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.0.tgz", + "integrity": "sha512-lN85QRR+5IbYrMWM6Y4pE/noaQtg4pNiqeNGX60eqOfo6gtEj6uw/JagelB8vVztSd7R6M5n1+PQkDbHbBRU4g==", "dev": true, "dependencies": { - "@babel/types": "^7.22.10", + "@babel/types": "^7.23.0", "@jridgewell/gen-mapping": "^0.3.2", "@jridgewell/trace-mapping": "^0.3.17", "jsesc": "^2.5.1" @@ -220,22 +220,22 @@ } }, "node_modules/@babel/helper-environment-visitor": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.5.tgz", - "integrity": "sha512-XGmhECfVA/5sAt+H+xpSg0mfrHq6FzNr9Oxh7PSEBBRUb/mL7Kz3NICXb194rCqAEdxkhPT1a88teizAFyvk8Q==", + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz", + "integrity": "sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==", "dev": true, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-function-name": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.22.5.tgz", - "integrity": "sha512-wtHSq6jMRE3uF2otvfuD3DIvVhOsSNshQl0Qrd7qC9oQJzHvOL4qQXlQn2916+CXGywIjpGuIkoyZRRxHPiNQQ==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz", + "integrity": "sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==", "dev": true, "dependencies": { - "@babel/template": "^7.22.5", - "@babel/types": "^7.22.5" + "@babel/template": "^7.22.15", + "@babel/types": "^7.23.0" }, "engines": { "node": ">=6.9.0" @@ -327,9 +327,9 @@ } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.5.tgz", - "integrity": "sha512-aJXu+6lErq8ltp+JhkJUfk1MTGyuA4v7f3pA+BJ5HLfNC6nAQ0Cpi9uOquUj8Hehg0aUiHzWQbOVJGao6ztBAQ==", + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz", + "integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==", "dev": true, "engines": { "node": ">=6.9.0" @@ -359,12 +359,12 @@ } }, "node_modules/@babel/highlight": { - "version": "7.22.10", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.10.tgz", - "integrity": "sha512-78aUtVcT7MUscr0K5mIEnkwxPE0MaxkR5RxRwuHaQ+JuU5AmTPhY+do2mdzVTnIJJpyBglql2pehuBIWHug+WQ==", + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.20.tgz", + "integrity": "sha512-dkdMCN3py0+ksCgYmGG8jKeGA/8Tk+gJwSYYlFGxG5lmhfKNoAy004YpLxpS1W2J8m/EK2Ew+yOs9pVRwO89mg==", "dev": true, "dependencies": { - "@babel/helper-validator-identifier": "^7.22.5", + "@babel/helper-validator-identifier": "^7.22.20", "chalk": "^2.4.2", "js-tokens": "^4.0.0" }, @@ -444,9 +444,9 @@ } }, "node_modules/@babel/parser": { - "version": "7.22.10", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.22.10.tgz", - "integrity": "sha512-lNbdGsQb9ekfsnjFGhEiF4hfFqGgfOP3H3d27re3n+CGhNuTSUEQdfWk556sTLNTloczcdM5TYF2LhzmDQKyvQ==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.0.tgz", + "integrity": "sha512-vvPKKdMemU85V9WE/l5wZEmImpCtLqbnTvqDS2U1fJ96KrxoW7KrXhNsNCblQlg8Ck4b85yxdTyelsMUgFUXiw==", "dev": true, "bin": { "parser": "bin/babel-parser.js" @@ -633,33 +633,33 @@ } }, "node_modules/@babel/template": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.5.tgz", - "integrity": "sha512-X7yV7eiwAxdj9k94NEylvbVHLiVG1nvzCV2EAowhxLTwODV1jl9UzZ48leOC0sH7OnuHrIkllaBgneUykIcZaw==", + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.15.tgz", + "integrity": "sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w==", "dev": true, "dependencies": { - "@babel/code-frame": "^7.22.5", - "@babel/parser": "^7.22.5", - "@babel/types": "^7.22.5" + "@babel/code-frame": "^7.22.13", + "@babel/parser": "^7.22.15", + "@babel/types": "^7.22.15" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/traverse": { - "version": "7.22.10", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.22.10.tgz", - "integrity": "sha512-Q/urqV4pRByiNNpb/f5OSv28ZlGJiFiiTh+GAHktbIrkPhPbl90+uW6SmpoLyZqutrg9AEaEf3Q/ZBRHBXgxig==", + "version": "7.23.2", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.2.tgz", + "integrity": "sha512-azpe59SQ48qG6nu2CzcMLbxUudtN+dOM9kDbUqGq3HXUJRlo7i8fvPoxQUzYgLZ4cMVmuZgm8vvBpNeRhd6XSw==", "dev": true, "dependencies": { - "@babel/code-frame": "^7.22.10", - "@babel/generator": "^7.22.10", - "@babel/helper-environment-visitor": "^7.22.5", - "@babel/helper-function-name": "^7.22.5", + "@babel/code-frame": "^7.22.13", + "@babel/generator": "^7.23.0", + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-function-name": "^7.23.0", "@babel/helper-hoist-variables": "^7.22.5", "@babel/helper-split-export-declaration": "^7.22.6", - "@babel/parser": "^7.22.10", - "@babel/types": "^7.22.10", + "@babel/parser": "^7.23.0", + "@babel/types": "^7.23.0", "debug": "^4.1.0", "globals": "^11.1.0" }, @@ -677,13 +677,13 @@ } }, "node_modules/@babel/types": { - "version": "7.22.10", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.22.10.tgz", - "integrity": "sha512-obaoigiLrlDZ7TUQln/8m4mSqIW2QFeOrCQc9r+xsaHGNoplVNYlRVpsfE8Vj35GEm2ZH4ZhrNYogs/3fj85kg==", + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.0.tgz", + "integrity": "sha512-0oIyUfKoI3mSqMvsxBdclDwxXKXAUA8v/apZbc+iSyARYou1o8ZGDxbUYyLFoW2arqS2jDGqJuZvv1d/io1axg==", "dev": true, "dependencies": { "@babel/helper-string-parser": "^7.22.5", - "@babel/helper-validator-identifier": "^7.22.5", + "@babel/helper-validator-identifier": "^7.22.20", "to-fast-properties": "^2.0.0" }, "engines": { @@ -766,21 +766,21 @@ } }, "node_modules/@eslint/js": { - "version": "8.47.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.47.0.tgz", - "integrity": "sha512-P6omY1zv5MItm93kLM8s2vr1HICJH8v0dvddDhysbIuZ+vcjOHg5Zbkf1mTkcmi2JA9oBG2anOkRnW8WJTS8Og==", + "version": "8.52.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.52.0.tgz", + "integrity": "sha512-mjZVbpaeMZludF2fsWLD0Z9gCref1Tk4i9+wddjRvpUNqqcndPkBD09N/Mapey0b3jaXbLm2kICwFv2E64QinA==", "dev": true, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, "node_modules/@humanwhocodes/config-array": { - "version": "0.11.10", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.10.tgz", - "integrity": "sha512-KVVjQmNUepDVGXNuoRRdmmEjruj0KfiGSbS8LVc12LMsWDQzRXJ0qdhN8L8uUigKpfEHRhlaQFY0ib1tnUbNeQ==", + "version": "0.11.13", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.13.tgz", + "integrity": "sha512-JSBDMiDKSzQVngfRjOdFXgFfklaXI4K9nLF49Auh21lmBWRLIK3+xTErTWD4KU54pb6coM6ESE7Awz/FNU3zgQ==", "dev": true, "dependencies": { - "@humanwhocodes/object-schema": "^1.2.1", + "@humanwhocodes/object-schema": "^2.0.1", "debug": "^4.1.1", "minimatch": "^3.0.5" }, @@ -802,9 +802,9 @@ } }, "node_modules/@humanwhocodes/object-schema": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", - "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.1.tgz", + "integrity": "sha512-dvuCeX5fC9dXgJn9t+X5atfmgQAzUOWqS1254Gh0m6i8wKd10ebXkfNKiRK+1GWi/yTvvLDHpoxLr0xxxeslWw==", "dev": true }, "node_modules/@istanbuljs/load-nyc-config": { @@ -916,16 +916,16 @@ } }, "node_modules/@jest/console": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/console/-/console-29.6.3.tgz", - "integrity": "sha512-ukZbHAdDH4ktZIOKvWs1juAXhiVAdvCyM8zv4S/7Ii3vJSDvMW5k+wOVGMQmHLHUFw3Ko63ZQNy7NI6PSlsD5w==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/console/-/console-29.7.0.tgz", + "integrity": "sha512-5Ni4CU7XHQi32IJ398EEP4RrB8eV09sXP2ROqD4bksHrnTree52PsxvX8tpL8LvTZ3pFzXyPbNQReSN41CAhOg==", "dev": true, "dependencies": { "@jest/types": "^29.6.3", "@types/node": "*", "chalk": "^4.0.0", - "jest-message-util": "^29.6.3", - "jest-util": "^29.6.3", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", "slash": "^3.0.0" }, "engines": { @@ -933,15 +933,15 @@ } }, "node_modules/@jest/core": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/core/-/core-29.6.3.tgz", - "integrity": "sha512-skV1XrfNxfagmjRUrk2FyN5/2YwIzdWVVBa/orUfbLvQUANXxERq2pTvY0I+FinWHjDKB2HRmpveUiph4X0TJw==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/core/-/core-29.7.0.tgz", + "integrity": "sha512-n7aeXWKMnGtDA48y8TLWJPJmLmmZ642Ceo78cYWEpiD7FzDgmNDV/GCVRorPABdXLJZ/9wzzgZAlHjXjxDHGsg==", "dev": true, "dependencies": { - "@jest/console": "^29.6.3", - "@jest/reporters": "^29.6.3", - "@jest/test-result": "^29.6.3", - "@jest/transform": "^29.6.3", + "@jest/console": "^29.7.0", + "@jest/reporters": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", "@jest/types": "^29.6.3", "@types/node": "*", "ansi-escapes": "^4.2.1", @@ -949,21 +949,21 @@ "ci-info": "^3.2.0", "exit": "^0.1.2", "graceful-fs": "^4.2.9", - "jest-changed-files": "^29.6.3", - "jest-config": "^29.6.3", - "jest-haste-map": "^29.6.3", - "jest-message-util": "^29.6.3", + "jest-changed-files": "^29.7.0", + "jest-config": "^29.7.0", + "jest-haste-map": "^29.7.0", + "jest-message-util": "^29.7.0", "jest-regex-util": "^29.6.3", - "jest-resolve": "^29.6.3", - "jest-resolve-dependencies": "^29.6.3", - "jest-runner": "^29.6.3", - "jest-runtime": "^29.6.3", - "jest-snapshot": "^29.6.3", - "jest-util": "^29.6.3", - "jest-validate": "^29.6.3", - "jest-watcher": "^29.6.3", + "jest-resolve": "^29.7.0", + "jest-resolve-dependencies": "^29.7.0", + "jest-runner": "^29.7.0", + "jest-runtime": "^29.7.0", + "jest-snapshot": "^29.7.0", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", + "jest-watcher": "^29.7.0", "micromatch": "^4.0.4", - "pretty-format": "^29.6.3", + "pretty-format": "^29.7.0", "slash": "^3.0.0", "strip-ansi": "^6.0.0" }, @@ -980,37 +980,37 @@ } }, "node_modules/@jest/environment": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.6.3.tgz", - "integrity": "sha512-u/u3cCztYCfgBiGHsamqP5x+XvucftOGPbf5RJQxfpeC1y4AL8pCjKvPDA3oCmdhZYPgk5AE0VOD/flweR69WA==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.7.0.tgz", + "integrity": "sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw==", "dev": true, "dependencies": { - "@jest/fake-timers": "^29.6.3", + "@jest/fake-timers": "^29.7.0", "@jest/types": "^29.6.3", "@types/node": "*", - "jest-mock": "^29.6.3" + "jest-mock": "^29.7.0" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/@jest/expect": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-29.6.3.tgz", - "integrity": "sha512-Ic08XbI2jlg6rECy+CGwk/8NDa6VE7UmIG6++9OTPAMnQmNGY28hu69Nf629CWv6T7YMODLbONxDFKdmQeI9FA==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-29.7.0.tgz", + "integrity": "sha512-8uMeAMycttpva3P1lBHB8VciS9V0XAr3GymPpipdyQXbBcuhkLQOSe8E/p92RyAdToS6ZD1tFkX+CkhoECE0dQ==", "dev": true, "dependencies": { - "expect": "^29.6.3", - "jest-snapshot": "^29.6.3" + "expect": "^29.7.0", + "jest-snapshot": "^29.7.0" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/@jest/expect-utils": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.6.3.tgz", - "integrity": "sha512-nvOEW4YoqRKD9HBJ9OJ6przvIvP9qilp5nAn1462P5ZlL/MM9SgPEZFyjTGPfs7QkocdUsJa6KjHhyRn4ueItA==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.7.0.tgz", + "integrity": "sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA==", "dev": true, "dependencies": { "jest-get-type": "^29.6.3" @@ -1020,47 +1020,47 @@ } }, "node_modules/@jest/fake-timers": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-29.6.3.tgz", - "integrity": "sha512-pa1wmqvbj6eX0nMvOM2VDAWvJOI5A/Mk3l8O7n7EsAh71sMZblaKO9iT4GjIj0LwwK3CP/Jp1ypEV0x3m89RvA==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-29.7.0.tgz", + "integrity": "sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ==", "dev": true, "dependencies": { "@jest/types": "^29.6.3", "@sinonjs/fake-timers": "^10.0.2", "@types/node": "*", - "jest-message-util": "^29.6.3", - "jest-mock": "^29.6.3", - "jest-util": "^29.6.3" + "jest-message-util": "^29.7.0", + "jest-mock": "^29.7.0", + "jest-util": "^29.7.0" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/@jest/globals": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-29.6.3.tgz", - "integrity": "sha512-RB+uI+CZMHntzlnOPlll5x/jgRff3LEPl/td/jzMXiIgR0iIhKq9qm1HLU+EC52NuoVy/1swit/sDGjVn4bc6A==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-29.7.0.tgz", + "integrity": "sha512-mpiz3dutLbkW2MNFubUGUEVLkTGiqW6yLVTA+JbP6fI6J5iL9Y0Nlg8k95pcF8ctKwCS7WVxteBs29hhfAotzQ==", "dev": true, "dependencies": { - "@jest/environment": "^29.6.3", - "@jest/expect": "^29.6.3", + "@jest/environment": "^29.7.0", + "@jest/expect": "^29.7.0", "@jest/types": "^29.6.3", - "jest-mock": "^29.6.3" + "jest-mock": "^29.7.0" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/@jest/reporters": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-29.6.3.tgz", - "integrity": "sha512-kGz59zMi0GkVjD2CJeYWG9k6cvj7eBqt9aDAqo2rcCLRTYlvQ62Gu/n+tOmJMBHGjzeijjuCENjzTyYBgrtLUw==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-29.7.0.tgz", + "integrity": "sha512-DApq0KJbJOEzAFYjHADNNxAE3KbhxQB1y5Kplb5Waqw6zVbuWatSnMjE5gs8FUgEPmNsnZA3NCWl9NG0ia04Pg==", "dev": true, "dependencies": { "@bcoe/v8-coverage": "^0.2.3", - "@jest/console": "^29.6.3", - "@jest/test-result": "^29.6.3", - "@jest/transform": "^29.6.3", + "@jest/console": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", "@jest/types": "^29.6.3", "@jridgewell/trace-mapping": "^0.3.18", "@types/node": "*", @@ -1074,9 +1074,9 @@ "istanbul-lib-report": "^3.0.0", "istanbul-lib-source-maps": "^4.0.0", "istanbul-reports": "^3.1.3", - "jest-message-util": "^29.6.3", - "jest-util": "^29.6.3", - "jest-worker": "^29.6.3", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", + "jest-worker": "^29.7.0", "slash": "^3.0.0", "string-length": "^4.0.1", "strip-ansi": "^6.0.0", @@ -1121,12 +1121,12 @@ } }, "node_modules/@jest/test-result": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-29.6.3.tgz", - "integrity": "sha512-k7ZZaNvOSMBHPZYiy0kuiaFoyansR5QnTwDux1EjK3kD5iWpRVyJIJ0RAIV39SThafchuW59vra7F8mdy5Hfgw==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-29.7.0.tgz", + "integrity": "sha512-Fdx+tv6x1zlkJPcWXmMDAG2HBnaR9XPSd5aDWQVsfrZmLVT3lU1cwyxLgRmXR9yrq4NBoEm9BMsfgFzTQAbJYA==", "dev": true, "dependencies": { - "@jest/console": "^29.6.3", + "@jest/console": "^29.7.0", "@jest/types": "^29.6.3", "@types/istanbul-lib-coverage": "^2.0.0", "collect-v8-coverage": "^1.0.0" @@ -1136,14 +1136,14 @@ } }, "node_modules/@jest/test-sequencer": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-29.6.3.tgz", - "integrity": "sha512-/SmijaAU2TY9ComFGIYa6Z+fmKqQMnqs2Nmwb0P/Z/tROdZ7M0iruES1EaaU9PBf8o9uED5xzaJ3YPFEIcDgAg==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-29.7.0.tgz", + "integrity": "sha512-GQwJ5WZVrKnOJuiYiAF52UNUJXgTZx1NHjFSEB0qEMmSZKAkdMoIzw/Cj6x6NF4AvV23AUqDpFzQkN/eYCYTxw==", "dev": true, "dependencies": { - "@jest/test-result": "^29.6.3", + "@jest/test-result": "^29.7.0", "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.6.3", + "jest-haste-map": "^29.7.0", "slash": "^3.0.0" }, "engines": { @@ -1151,9 +1151,9 @@ } }, "node_modules/@jest/transform": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.6.3.tgz", - "integrity": "sha512-dPIc3DsvMZ/S8ut4L2ViCj265mKO0owB0wfzBv2oGzL9pQ+iRvJewHqLBmsGb7XFb5UotWIEtvY5A/lnylaIoQ==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.7.0.tgz", + "integrity": "sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw==", "dev": true, "dependencies": { "@babel/core": "^7.11.6", @@ -1164,9 +1164,9 @@ "convert-source-map": "^2.0.0", "fast-json-stable-stringify": "^2.1.0", "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.6.3", + "jest-haste-map": "^29.7.0", "jest-regex-util": "^29.6.3", - "jest-util": "^29.6.3", + "jest-util": "^29.7.0", "micromatch": "^4.0.4", "pirates": "^4.0.4", "slash": "^3.0.0", @@ -1366,9 +1366,9 @@ } }, "node_modules/@types/graceful-fs": { - "version": "4.1.6", - "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.6.tgz", - "integrity": "sha512-Sig0SNORX9fdW+bQuTEovKj3uHcUL6LQKbCrrqb1X7J6/ReAbhCXRAhc+SMejhLELFj2QcyuxmUooZ4bt5ReSw==", + "version": "4.1.8", + "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.8.tgz", + "integrity": "sha512-NhRH7YzWq8WiNKVavKPBmtLYZHxNY19Hh+az28O/phfp68CF45pMFud+ZzJ8ewnxnC5smIdF3dqFeiSUQ5I+pw==", "dev": true, "dependencies": { "@types/node": "*" @@ -1399,9 +1399,9 @@ } }, "node_modules/@types/json-schema": { - "version": "7.0.12", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.12.tgz", - "integrity": "sha512-Hr5Jfhc9eYOQNPYO5WLDq/n4jqijdHNlDXjuAQkkt+mWdQR+XJToOHrsD4cPaMXpn6KO7y2+wM8AZEs8VpBLVA==", + "version": "7.0.14", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.14.tgz", + "integrity": "sha512-U3PUjAudAdJBeC2pgN8uTIKgxrb4nlDF3SF0++EldXQvQBGkpFZMSnwQiIoDU77tv45VgNkl/L4ouD+rEomujw==", "dev": true }, "node_modules/@types/node": { @@ -1411,15 +1411,15 @@ "dev": true }, "node_modules/@types/semver": { - "version": "7.5.0", - "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.0.tgz", - "integrity": "sha512-G8hZ6XJiHnuhQKR7ZmysCeJWE08o8T0AXtk5darsCaTVsYZhhgUrq53jizaR2FvsoeCwJhlmwTjkXBY5Pn/ZHw==", + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-MMzuxN3GdFwskAnb6fz0orFvhfqi752yjaXylr0Rp4oDg5H0Zn1IuyRhDVvYOwAXoJirx2xuS16I3WjxnAIHiQ==", "dev": true }, "node_modules/@types/stack-utils": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.1.tgz", - "integrity": "sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.2.tgz", + "integrity": "sha512-g7CK9nHdwjK2n0ymT2CW698FuWJRIx+RP6embAzZ2Qi8/ilIrA1Imt2LVSeHUzKvpoi7BhmmQcXz95eS0f2JXw==", "dev": true }, "node_modules/@types/yargs": { @@ -1438,16 +1438,16 @@ "dev": true }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "6.4.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.4.1.tgz", - "integrity": "sha512-3F5PtBzUW0dYlq77Lcqo13fv+58KDwUib3BddilE8ajPJT+faGgxmI9Sw+I8ZS22BYwoir9ZhNXcLi+S+I2bkw==", + "version": "6.9.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.9.1.tgz", + "integrity": "sha512-w0tiiRc9I4S5XSXXrMHOWgHgxbrBn1Ro+PmiYhSg2ZVdxrAJtQgzU5o2m1BfP6UOn7Vxcc6152vFjQfmZR4xEg==", "dev": true, "dependencies": { "@eslint-community/regexpp": "^4.5.1", - "@typescript-eslint/scope-manager": "6.4.1", - "@typescript-eslint/type-utils": "6.4.1", - "@typescript-eslint/utils": "6.4.1", - "@typescript-eslint/visitor-keys": "6.4.1", + "@typescript-eslint/scope-manager": "6.9.1", + "@typescript-eslint/type-utils": "6.9.1", + "@typescript-eslint/utils": "6.9.1", + "@typescript-eslint/visitor-keys": "6.9.1", "debug": "^4.3.4", "graphemer": "^1.4.0", "ignore": "^5.2.4", @@ -1473,15 +1473,15 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "6.4.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.4.1.tgz", - "integrity": "sha512-610G6KHymg9V7EqOaNBMtD1GgpAmGROsmfHJPXNLCU9bfIuLrkdOygltK784F6Crboyd5tBFayPB7Sf0McrQwg==", + "version": "6.9.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.9.1.tgz", + "integrity": "sha512-C7AK2wn43GSaCUZ9do6Ksgi2g3mwFkMO3Cis96kzmgudoVaKyt62yNzJOktP0HDLb/iO2O0n2lBOzJgr6Q/cyg==", "dev": true, "dependencies": { - "@typescript-eslint/scope-manager": "6.4.1", - "@typescript-eslint/types": "6.4.1", - "@typescript-eslint/typescript-estree": "6.4.1", - "@typescript-eslint/visitor-keys": "6.4.1", + "@typescript-eslint/scope-manager": "6.9.1", + "@typescript-eslint/types": "6.9.1", + "@typescript-eslint/typescript-estree": "6.9.1", + "@typescript-eslint/visitor-keys": "6.9.1", "debug": "^4.3.4" }, "engines": { @@ -1501,13 +1501,13 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "6.4.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.4.1.tgz", - "integrity": "sha512-p/OavqOQfm4/Hdrr7kvacOSFjwQ2rrDVJRPxt/o0TOWdFnjJptnjnZ+sYDR7fi4OimvIuKp+2LCkc+rt9fIW+A==", + "version": "6.9.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.9.1.tgz", + "integrity": "sha512-38IxvKB6NAne3g/+MyXMs2Cda/Sz+CEpmm+KLGEM8hx/CvnSRuw51i8ukfwB/B/sESdeTGet1NH1Wj7I0YXswg==", "dev": true, "dependencies": { - "@typescript-eslint/types": "6.4.1", - "@typescript-eslint/visitor-keys": "6.4.1" + "@typescript-eslint/types": "6.9.1", + "@typescript-eslint/visitor-keys": "6.9.1" }, "engines": { "node": "^16.0.0 || >=18.0.0" @@ -1518,13 +1518,13 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "6.4.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.4.1.tgz", - "integrity": "sha512-7ON8M8NXh73SGZ5XvIqWHjgX2f+vvaOarNliGhjrJnv1vdjG0LVIz+ToYfPirOoBi56jxAKLfsLm40+RvxVVXA==", + "version": "6.9.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.9.1.tgz", + "integrity": "sha512-eh2oHaUKCK58qIeYp19F5V5TbpM52680sB4zNSz29VBQPTWIlE/hCj5P5B1AChxECe/fmZlspAWFuRniep1Skg==", "dev": true, "dependencies": { - "@typescript-eslint/typescript-estree": "6.4.1", - "@typescript-eslint/utils": "6.4.1", + "@typescript-eslint/typescript-estree": "6.9.1", + "@typescript-eslint/utils": "6.9.1", "debug": "^4.3.4", "ts-api-utils": "^1.0.1" }, @@ -1545,9 +1545,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "6.4.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.4.1.tgz", - "integrity": "sha512-zAAopbNuYu++ijY1GV2ylCsQsi3B8QvfPHVqhGdDcbx/NK5lkqMnCGU53amAjccSpk+LfeONxwzUhDzArSfZJg==", + "version": "6.9.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.9.1.tgz", + "integrity": "sha512-BUGslGOb14zUHOUmDB2FfT6SI1CcZEJYfF3qFwBeUrU6srJfzANonwRYHDpLBuzbq3HaoF2XL2hcr01c8f8OaQ==", "dev": true, "engines": { "node": "^16.0.0 || >=18.0.0" @@ -1558,13 +1558,13 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "6.4.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.4.1.tgz", - "integrity": "sha512-xF6Y7SatVE/OyV93h1xGgfOkHr2iXuo8ip0gbfzaKeGGuKiAnzS+HtVhSPx8Www243bwlW8IF7X0/B62SzFftg==", + "version": "6.9.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.9.1.tgz", + "integrity": "sha512-U+mUylTHfcqeO7mLWVQ5W/tMLXqVpRv61wm9ZtfE5egz7gtnmqVIw9ryh0mgIlkKk9rZLY3UHygsBSdB9/ftyw==", "dev": true, "dependencies": { - "@typescript-eslint/types": "6.4.1", - "@typescript-eslint/visitor-keys": "6.4.1", + "@typescript-eslint/types": "6.9.1", + "@typescript-eslint/visitor-keys": "6.9.1", "debug": "^4.3.4", "globby": "^11.1.0", "is-glob": "^4.0.3", @@ -1585,17 +1585,17 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "6.4.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.4.1.tgz", - "integrity": "sha512-F/6r2RieNeorU0zhqZNv89s9bDZSovv3bZQpUNOmmQK1L80/cV4KEu95YUJWi75u5PhboFoKUJBnZ4FQcoqhDw==", + "version": "6.9.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.9.1.tgz", + "integrity": "sha512-L1T0A5nFdQrMVunpZgzqPL6y2wVreSyHhKGZryS6jrEN7bD9NplVAyMryUhXsQ4TWLnZmxc2ekar/lSGIlprCA==", "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", "@types/json-schema": "^7.0.12", "@types/semver": "^7.5.0", - "@typescript-eslint/scope-manager": "6.4.1", - "@typescript-eslint/types": "6.4.1", - "@typescript-eslint/typescript-estree": "6.4.1", + "@typescript-eslint/scope-manager": "6.9.1", + "@typescript-eslint/types": "6.9.1", + "@typescript-eslint/typescript-estree": "6.9.1", "semver": "^7.5.4" }, "engines": { @@ -1610,12 +1610,12 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "6.4.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.4.1.tgz", - "integrity": "sha512-y/TyRJsbZPkJIZQXrHfdnxVnxyKegnpEvnRGNam7s3TRR2ykGefEWOhaef00/UUN3IZxizS7BTO3svd3lCOJRQ==", + "version": "6.9.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.9.1.tgz", + "integrity": "sha512-MUaPUe/QRLEffARsmNfmpghuQkW436DvESW+h+M52w0coICHRfD6Np9/K6PdACwnrq1HmuLl+cSPZaJmeVPkSw==", "dev": true, "dependencies": { - "@typescript-eslint/types": "6.4.1", + "@typescript-eslint/types": "6.9.1", "eslint-visitor-keys": "^3.4.1" }, "engines": { @@ -1626,6 +1626,12 @@ "url": "https://opencollective.com/typescript-eslint" } }, + "node_modules/@ungap/structured-clone": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz", + "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==", + "dev": true + }, "node_modules/acorn": { "version": "8.10.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.10.0.tgz", @@ -1758,12 +1764,12 @@ } }, "node_modules/babel-jest": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.6.3.tgz", - "integrity": "sha512-1Ne93zZZEy5XmTa4Q+W5+zxBrDpExX8E3iy+xJJ+24ewlfo/T3qHfQJCzi/MMVFmBQDNxtRR/Gfd2dwb/0yrQw==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.7.0.tgz", + "integrity": "sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg==", "dev": true, "dependencies": { - "@jest/transform": "^29.6.3", + "@jest/transform": "^29.7.0", "@types/babel__core": "^7.1.14", "babel-plugin-istanbul": "^6.1.1", "babel-preset-jest": "^29.6.3", @@ -2104,6 +2110,27 @@ "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", "dev": true }, + "node_modules/create-jest": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/create-jest/-/create-jest-29.7.0.tgz", + "integrity": "sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q==", + "dev": true, + "dependencies": { + "@jest/types": "^29.6.3", + "chalk": "^4.0.0", + "exit": "^0.1.2", + "graceful-fs": "^4.2.9", + "jest-config": "^29.7.0", + "jest-util": "^29.7.0", + "prompts": "^2.0.1" + }, + "bin": { + "create-jest": "bin/create-jest.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, "node_modules/create-require": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", @@ -2276,18 +2303,19 @@ } }, "node_modules/eslint": { - "version": "8.47.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.47.0.tgz", - "integrity": "sha512-spUQWrdPt+pRVP1TTJLmfRNJJHHZryFmptzcafwSvHsceV81djHOdnEeDmkdotZyLNjDhrOasNK8nikkoG1O8Q==", + "version": "8.52.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.52.0.tgz", + "integrity": "sha512-zh/JHnaixqHZsolRB/w9/02akBk9EPrOs9JwcTP2ek7yL5bVvXuRariiaAjjoJ5DvuwQ1WAE/HsMz+w17YgBCg==", "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", "@eslint/eslintrc": "^2.1.2", - "@eslint/js": "^8.47.0", - "@humanwhocodes/config-array": "^0.11.10", + "@eslint/js": "8.52.0", + "@humanwhocodes/config-array": "^0.11.13", "@humanwhocodes/module-importer": "^1.0.1", "@nodelib/fs.walk": "^1.2.8", + "@ungap/structured-clone": "^1.2.0", "ajv": "^6.12.4", "chalk": "^4.0.0", "cross-spawn": "^7.0.2", @@ -2462,16 +2490,16 @@ } }, "node_modules/expect": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/expect/-/expect-29.6.3.tgz", - "integrity": "sha512-x1vY4LlEMWUYVZQrFi4ZANXFwqYbJ/JNQspLVvzhW2BNY28aNcXMQH6imBbt+RBf5sVRTodYHXtSP/TLEU0Dxw==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/expect/-/expect-29.7.0.tgz", + "integrity": "sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw==", "dev": true, "dependencies": { - "@jest/expect-utils": "^29.6.3", + "@jest/expect-utils": "^29.7.0", "jest-get-type": "^29.6.3", - "jest-matcher-utils": "^29.6.3", - "jest-message-util": "^29.6.3", - "jest-util": "^29.6.3" + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" @@ -2620,12 +2648,6 @@ "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, - "node_modules/function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", - "dev": true - }, "node_modules/gensync": { "version": "1.0.0-beta.2", "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", @@ -2745,13 +2767,10 @@ "dev": true }, "node_modules/has": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", - "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.4.tgz", + "integrity": "sha512-qdSAmqLF6209RFj4VVItywPMbm3vWylknmB3nvNiUIs72xAimcM8nVYxYr7ncvZq5qzk9MKIZR8ijqD/1QuYjQ==", "dev": true, - "dependencies": { - "function-bind": "^1.1.1" - }, "engines": { "node": ">= 0.4.0" } @@ -2952,9 +2971,9 @@ } }, "node_modules/istanbul-lib-instrument": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.0.tgz", - "integrity": "sha512-x58orMzEVfzPUKqlbLd1hXCnySCxKdDKa6Rjg97CwuLLRI4g3FHTdnExu1OqffVFay6zeMW+T6/DowFLndWnIw==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-6.0.1.tgz", + "integrity": "sha512-EAMEJBsYuyyztxMxW3g7ugGPkrZsV57v0Hmv3mm1uQsmB+QnZuepg731CRaIgeUVSdmsTngOkSnauNF8p7FIhA==", "dev": true, "dependencies": { "@babel/core": "^7.12.3", @@ -3009,15 +3028,15 @@ } }, "node_modules/jest": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/jest/-/jest-29.6.3.tgz", - "integrity": "sha512-alueLuoPCDNHFcFGmgETR4KpQ+0ff3qVaiJwxQM4B5sC0CvXcgg4PEi7xrDkxuItDmdz/FVc7SSit4KEu8GRvw==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest/-/jest-29.7.0.tgz", + "integrity": "sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==", "dev": true, "dependencies": { - "@jest/core": "^29.6.3", + "@jest/core": "^29.7.0", "@jest/types": "^29.6.3", "import-local": "^3.0.2", - "jest-cli": "^29.6.3" + "jest-cli": "^29.7.0" }, "bin": { "jest": "bin/jest.js" @@ -3035,13 +3054,13 @@ } }, "node_modules/jest-changed-files": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-29.6.3.tgz", - "integrity": "sha512-G5wDnElqLa4/c66ma5PG9eRjE342lIbF6SUnTJi26C3J28Fv2TVY2rOyKB9YGbSA5ogwevgmxc4j4aVjrEK6Yg==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-29.7.0.tgz", + "integrity": "sha512-fEArFiwf1BpQ+4bXSprcDc3/x4HSzL4al2tozwVpDFpsxALjLYdyiIK4e5Vz66GQJIbXJ82+35PtysofptNX2w==", "dev": true, "dependencies": { "execa": "^5.0.0", - "jest-util": "^29.6.3", + "jest-util": "^29.7.0", "p-limit": "^3.1.0" }, "engines": { @@ -3049,28 +3068,28 @@ } }, "node_modules/jest-circus": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-29.6.3.tgz", - "integrity": "sha512-p0R5YqZEMnOpHqHLWRSjm2z/0p6RNsrNE/GRRT3eli8QGOAozj6Ys/3Tv+Ej+IfltJoSPwcQ6/hOCRkNlxLLCw==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-29.7.0.tgz", + "integrity": "sha512-3E1nCMgipcTkCocFwM90XXQab9bS+GMsjdpmPrlelaxwD93Ad8iVEjX/vvHPdLPnFf+L40u+5+iutRdA1N9myw==", "dev": true, "dependencies": { - "@jest/environment": "^29.6.3", - "@jest/expect": "^29.6.3", - "@jest/test-result": "^29.6.3", + "@jest/environment": "^29.7.0", + "@jest/expect": "^29.7.0", + "@jest/test-result": "^29.7.0", "@jest/types": "^29.6.3", "@types/node": "*", "chalk": "^4.0.0", "co": "^4.6.0", "dedent": "^1.0.0", "is-generator-fn": "^2.0.0", - "jest-each": "^29.6.3", - "jest-matcher-utils": "^29.6.3", - "jest-message-util": "^29.6.3", - "jest-runtime": "^29.6.3", - "jest-snapshot": "^29.6.3", - "jest-util": "^29.6.3", + "jest-each": "^29.7.0", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-runtime": "^29.7.0", + "jest-snapshot": "^29.7.0", + "jest-util": "^29.7.0", "p-limit": "^3.1.0", - "pretty-format": "^29.6.3", + "pretty-format": "^29.7.0", "pure-rand": "^6.0.0", "slash": "^3.0.0", "stack-utils": "^2.0.3" @@ -3080,22 +3099,21 @@ } }, "node_modules/jest-cli": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-29.6.3.tgz", - "integrity": "sha512-KuPdXUPXQIf0t6DvmG8MV4QyhcjR1a6ruKl3YL7aGn/AQ8JkROwFkWzEpDIpt11Qy188dHbRm8WjwMsV/4nmnQ==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-29.7.0.tgz", + "integrity": "sha512-OVVobw2IubN/GSYsxETi+gOe7Ka59EFMR/twOU3Jb2GnKKeMGJB5SGUUrEz3SFVmJASUdZUzy83sLNNQ2gZslg==", "dev": true, "dependencies": { - "@jest/core": "^29.6.3", - "@jest/test-result": "^29.6.3", + "@jest/core": "^29.7.0", + "@jest/test-result": "^29.7.0", "@jest/types": "^29.6.3", "chalk": "^4.0.0", + "create-jest": "^29.7.0", "exit": "^0.1.2", - "graceful-fs": "^4.2.9", "import-local": "^3.0.2", - "jest-config": "^29.6.3", - "jest-util": "^29.6.3", - "jest-validate": "^29.6.3", - "prompts": "^2.0.1", + "jest-config": "^29.7.0", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", "yargs": "^17.3.1" }, "bin": { @@ -3114,31 +3132,31 @@ } }, "node_modules/jest-config": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-29.6.3.tgz", - "integrity": "sha512-nb9bOq2aEqogbyL4F9mLkAeQGAgNt7Uz6U59YtQDIxFPiL7Ejgq0YIrp78oyEHD6H4CIV/k7mFrK7eFDzUJ69w==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-29.7.0.tgz", + "integrity": "sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ==", "dev": true, "dependencies": { "@babel/core": "^7.11.6", - "@jest/test-sequencer": "^29.6.3", + "@jest/test-sequencer": "^29.7.0", "@jest/types": "^29.6.3", - "babel-jest": "^29.6.3", + "babel-jest": "^29.7.0", "chalk": "^4.0.0", "ci-info": "^3.2.0", "deepmerge": "^4.2.2", "glob": "^7.1.3", "graceful-fs": "^4.2.9", - "jest-circus": "^29.6.3", - "jest-environment-node": "^29.6.3", + "jest-circus": "^29.7.0", + "jest-environment-node": "^29.7.0", "jest-get-type": "^29.6.3", "jest-regex-util": "^29.6.3", - "jest-resolve": "^29.6.3", - "jest-runner": "^29.6.3", - "jest-util": "^29.6.3", - "jest-validate": "^29.6.3", + "jest-resolve": "^29.7.0", + "jest-runner": "^29.7.0", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", "micromatch": "^4.0.4", "parse-json": "^5.2.0", - "pretty-format": "^29.6.3", + "pretty-format": "^29.7.0", "slash": "^3.0.0", "strip-json-comments": "^3.1.1" }, @@ -3159,24 +3177,24 @@ } }, "node_modules/jest-diff": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.6.3.tgz", - "integrity": "sha512-3sw+AdWnwH9sSNohMRKA7JiYUJSRr/WS6+sEFfBuhxU5V5GlEVKfvUn8JuMHE0wqKowemR1C2aHy8VtXbaV8dQ==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.7.0.tgz", + "integrity": "sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw==", "dev": true, "dependencies": { "chalk": "^4.0.0", "diff-sequences": "^29.6.3", "jest-get-type": "^29.6.3", - "pretty-format": "^29.6.3" + "pretty-format": "^29.7.0" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/jest-docblock": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-29.6.3.tgz", - "integrity": "sha512-2+H+GOTQBEm2+qFSQ7Ma+BvyV+waiIFxmZF5LdpBsAEjWX8QYjSCa4FrkIYtbfXUJJJnFCYrOtt6TZ+IAiTjBQ==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-29.7.0.tgz", + "integrity": "sha512-q617Auw3A612guyaFgsbFeYpNP5t2aoUNLwBUbc/0kD1R4t9ixDbyFTHd1nok4epoVFpr7PmeWHrhvuV3XaJ4g==", "dev": true, "dependencies": { "detect-newline": "^3.0.0" @@ -3186,33 +3204,33 @@ } }, "node_modules/jest-each": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-29.6.3.tgz", - "integrity": "sha512-KoXfJ42k8cqbkfshW7sSHcdfnv5agDdHCPA87ZBdmHP+zJstTJc0ttQaJ/x7zK6noAL76hOuTIJ6ZkQRS5dcyg==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-29.7.0.tgz", + "integrity": "sha512-gns+Er14+ZrEoC5fhOfYCY1LOHHr0TI+rQUHZS8Ttw2l7gl+80eHc/gFf2Ktkw0+SIACDTeWvpFcv3B04VembQ==", "dev": true, "dependencies": { "@jest/types": "^29.6.3", "chalk": "^4.0.0", "jest-get-type": "^29.6.3", - "jest-util": "^29.6.3", - "pretty-format": "^29.6.3" + "jest-util": "^29.7.0", + "pretty-format": "^29.7.0" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/jest-environment-node": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-29.6.3.tgz", - "integrity": "sha512-PKl7upfPJXMYbWpD+60o4HP86KvFO2c9dZ+Zr6wUzsG5xcPx/65o3ArNgHW5M0RFvLYdW4/aieR4JSooD0a2ew==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-29.7.0.tgz", + "integrity": "sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw==", "dev": true, "dependencies": { - "@jest/environment": "^29.6.3", - "@jest/fake-timers": "^29.6.3", + "@jest/environment": "^29.7.0", + "@jest/fake-timers": "^29.7.0", "@jest/types": "^29.6.3", "@types/node": "*", - "jest-mock": "^29.6.3", - "jest-util": "^29.6.3" + "jest-mock": "^29.7.0", + "jest-util": "^29.7.0" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" @@ -3228,9 +3246,9 @@ } }, "node_modules/jest-haste-map": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.6.3.tgz", - "integrity": "sha512-GecR5YavfjkhOytEFHAeI6aWWG3f/cOKNB1YJvj/B76xAmeVjy4zJUYobGF030cRmKaO1FBw3V8CZZ6KVh9ZSw==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.7.0.tgz", + "integrity": "sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA==", "dev": true, "dependencies": { "@jest/types": "^29.6.3", @@ -3240,8 +3258,8 @@ "fb-watchman": "^2.0.0", "graceful-fs": "^4.2.9", "jest-regex-util": "^29.6.3", - "jest-util": "^29.6.3", - "jest-worker": "^29.6.3", + "jest-util": "^29.7.0", + "jest-worker": "^29.7.0", "micromatch": "^4.0.4", "walker": "^1.0.8" }, @@ -3253,37 +3271,37 @@ } }, "node_modules/jest-leak-detector": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-29.6.3.tgz", - "integrity": "sha512-0kfbESIHXYdhAdpLsW7xdwmYhLf1BRu4AA118/OxFm0Ho1b2RcTmO4oF6aAMaxpxdxnJ3zve2rgwzNBD4Zbm7Q==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-29.7.0.tgz", + "integrity": "sha512-kYA8IJcSYtST2BY9I+SMC32nDpBT3J2NvWJx8+JCuCdl/CR1I4EKUJROiP8XtCcxqgTTBGJNdbB1A8XRKbTetw==", "dev": true, "dependencies": { "jest-get-type": "^29.6.3", - "pretty-format": "^29.6.3" + "pretty-format": "^29.7.0" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/jest-matcher-utils": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.6.3.tgz", - "integrity": "sha512-6ZrMYINZdwduSt5Xu18/n49O1IgXdjsfG7NEZaQws9k69eTKWKcVbJBw/MZsjOZe2sSyJFmuzh8042XWwl54Zg==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.7.0.tgz", + "integrity": "sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g==", "dev": true, "dependencies": { "chalk": "^4.0.0", - "jest-diff": "^29.6.3", + "jest-diff": "^29.7.0", "jest-get-type": "^29.6.3", - "pretty-format": "^29.6.3" + "pretty-format": "^29.7.0" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/jest-message-util": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.6.3.tgz", - "integrity": "sha512-FtzaEEHzjDpQp51HX4UMkPZjy46ati4T5pEMyM6Ik48ztu4T9LQplZ6OsimHx7EuM9dfEh5HJa6D3trEftu3dA==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.7.0.tgz", + "integrity": "sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w==", "dev": true, "dependencies": { "@babel/code-frame": "^7.12.13", @@ -3292,7 +3310,7 @@ "chalk": "^4.0.0", "graceful-fs": "^4.2.9", "micromatch": "^4.0.4", - "pretty-format": "^29.6.3", + "pretty-format": "^29.7.0", "slash": "^3.0.0", "stack-utils": "^2.0.3" }, @@ -3301,14 +3319,14 @@ } }, "node_modules/jest-mock": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-29.6.3.tgz", - "integrity": "sha512-Z7Gs/mOyTSR4yPsaZ72a/MtuK6RnC3JYqWONe48oLaoEcYwEDxqvbXz85G4SJrm2Z5Ar9zp6MiHF4AlFlRM4Pg==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-29.7.0.tgz", + "integrity": "sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw==", "dev": true, "dependencies": { "@jest/types": "^29.6.3", "@types/node": "*", - "jest-util": "^29.6.3" + "jest-util": "^29.7.0" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" @@ -3341,17 +3359,17 @@ } }, "node_modules/jest-resolve": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-29.6.3.tgz", - "integrity": "sha512-WMXwxhvzDeA/J+9jz1i8ZKGmbw/n+s988EiUvRI4egM+eTn31Hb5v10Re3slG3/qxntkBt2/6GkQVDGu6Bwyhw==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-29.7.0.tgz", + "integrity": "sha512-IOVhZSrg+UvVAshDSDtHyFCCBUl/Q3AAJv8iZ6ZjnZ74xzvwuzLXid9IIIPgTnY62SJjfuupMKZsZQRsCvxEgA==", "dev": true, "dependencies": { "chalk": "^4.0.0", "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.6.3", + "jest-haste-map": "^29.7.0", "jest-pnp-resolver": "^1.2.2", - "jest-util": "^29.6.3", - "jest-validate": "^29.6.3", + "jest-util": "^29.7.0", + "jest-validate": "^29.7.0", "resolve": "^1.20.0", "resolve.exports": "^2.0.0", "slash": "^3.0.0" @@ -3361,43 +3379,43 @@ } }, "node_modules/jest-resolve-dependencies": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-29.6.3.tgz", - "integrity": "sha512-iah5nhSPTwtUV7yzpTc9xGg8gP3Ch2VNsuFMsKoCkNCrQSbFtx5KRPemmPJ32AUhTSDqJXB6djPN6zAaUGV53g==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-29.7.0.tgz", + "integrity": "sha512-un0zD/6qxJ+S0et7WxeI3H5XSe9lTBBR7bOHCHXkKR6luG5mwDDlIzVQ0V5cZCuoTgEdcdwzTghYkTWfubi+nA==", "dev": true, "dependencies": { "jest-regex-util": "^29.6.3", - "jest-snapshot": "^29.6.3" + "jest-snapshot": "^29.7.0" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/jest-runner": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-29.6.3.tgz", - "integrity": "sha512-E4zsMhQnjhirFPhDTJgoLMWUrVCDij/KGzWlbslDHGuO8Hl2pVUfOiygMzVZtZq+BzmlqwEr7LYmW+WFLlmX8w==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-29.7.0.tgz", + "integrity": "sha512-fsc4N6cPCAahybGBfTRcq5wFR6fpLznMg47sY5aDpsoejOcVYFb07AHuSnR0liMcPTgBsA3ZJL6kFOjPdoNipQ==", "dev": true, "dependencies": { - "@jest/console": "^29.6.3", - "@jest/environment": "^29.6.3", - "@jest/test-result": "^29.6.3", - "@jest/transform": "^29.6.3", + "@jest/console": "^29.7.0", + "@jest/environment": "^29.7.0", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", "@jest/types": "^29.6.3", "@types/node": "*", "chalk": "^4.0.0", "emittery": "^0.13.1", "graceful-fs": "^4.2.9", - "jest-docblock": "^29.6.3", - "jest-environment-node": "^29.6.3", - "jest-haste-map": "^29.6.3", - "jest-leak-detector": "^29.6.3", - "jest-message-util": "^29.6.3", - "jest-resolve": "^29.6.3", - "jest-runtime": "^29.6.3", - "jest-util": "^29.6.3", - "jest-watcher": "^29.6.3", - "jest-worker": "^29.6.3", + "jest-docblock": "^29.7.0", + "jest-environment-node": "^29.7.0", + "jest-haste-map": "^29.7.0", + "jest-leak-detector": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-resolve": "^29.7.0", + "jest-runtime": "^29.7.0", + "jest-util": "^29.7.0", + "jest-watcher": "^29.7.0", + "jest-worker": "^29.7.0", "p-limit": "^3.1.0", "source-map-support": "0.5.13" }, @@ -3406,17 +3424,17 @@ } }, "node_modules/jest-runtime": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-29.6.3.tgz", - "integrity": "sha512-VM0Z3a9xaqizGpEKwCOIhImkrINYzxgwk8oQAvrmAiXX8LNrJrRjyva30RkuRY0ETAotHLlUcd2moviCA1hgsQ==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-29.7.0.tgz", + "integrity": "sha512-gUnLjgwdGqW7B4LvOIkbKs9WGbn+QLqRQQ9juC6HndeDiezIwhDP+mhMwHWCEcfQ5RUXa6OPnFF8BJh5xegwwQ==", "dev": true, "dependencies": { - "@jest/environment": "^29.6.3", - "@jest/fake-timers": "^29.6.3", - "@jest/globals": "^29.6.3", + "@jest/environment": "^29.7.0", + "@jest/fake-timers": "^29.7.0", + "@jest/globals": "^29.7.0", "@jest/source-map": "^29.6.3", - "@jest/test-result": "^29.6.3", - "@jest/transform": "^29.6.3", + "@jest/test-result": "^29.7.0", + "@jest/transform": "^29.7.0", "@jest/types": "^29.6.3", "@types/node": "*", "chalk": "^4.0.0", @@ -3424,13 +3442,13 @@ "collect-v8-coverage": "^1.0.0", "glob": "^7.1.3", "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.6.3", - "jest-message-util": "^29.6.3", - "jest-mock": "^29.6.3", + "jest-haste-map": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-mock": "^29.7.0", "jest-regex-util": "^29.6.3", - "jest-resolve": "^29.6.3", - "jest-snapshot": "^29.6.3", - "jest-util": "^29.6.3", + "jest-resolve": "^29.7.0", + "jest-snapshot": "^29.7.0", + "jest-util": "^29.7.0", "slash": "^3.0.0", "strip-bom": "^4.0.0" }, @@ -3439,9 +3457,9 @@ } }, "node_modules/jest-snapshot": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-29.6.3.tgz", - "integrity": "sha512-66Iu7H1ojiveQMGFnKecHIZPPPBjZwfQEnF6wxqpxGf57sV3YSUtAb5/sTKM5TPa3OndyxZp1wxHFbmgVhc53w==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-29.7.0.tgz", + "integrity": "sha512-Rm0BMWtxBcioHr1/OX5YCP8Uov4riHvKPknOGs804Zg9JGZgmIBkbtlxJC/7Z4msKYVbIJtfU+tKb8xlYNfdkw==", "dev": true, "dependencies": { "@babel/core": "^7.11.6", @@ -3449,20 +3467,20 @@ "@babel/plugin-syntax-jsx": "^7.7.2", "@babel/plugin-syntax-typescript": "^7.7.2", "@babel/types": "^7.3.3", - "@jest/expect-utils": "^29.6.3", - "@jest/transform": "^29.6.3", + "@jest/expect-utils": "^29.7.0", + "@jest/transform": "^29.7.0", "@jest/types": "^29.6.3", "babel-preset-current-node-syntax": "^1.0.0", "chalk": "^4.0.0", - "expect": "^29.6.3", + "expect": "^29.7.0", "graceful-fs": "^4.2.9", - "jest-diff": "^29.6.3", + "jest-diff": "^29.7.0", "jest-get-type": "^29.6.3", - "jest-matcher-utils": "^29.6.3", - "jest-message-util": "^29.6.3", - "jest-util": "^29.6.3", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0", "natural-compare": "^1.4.0", - "pretty-format": "^29.6.3", + "pretty-format": "^29.7.0", "semver": "^7.5.3" }, "engines": { @@ -3470,9 +3488,9 @@ } }, "node_modules/jest-util": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.6.3.tgz", - "integrity": "sha512-QUjna/xSy4B32fzcKTSz1w7YYzgiHrjjJjevdRf61HYk998R5vVMMNmrHESYZVDS5DSWs+1srPLPKxXPkeSDOA==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz", + "integrity": "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==", "dev": true, "dependencies": { "@jest/types": "^29.6.3", @@ -3487,9 +3505,9 @@ } }, "node_modules/jest-validate": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-29.6.3.tgz", - "integrity": "sha512-e7KWZcAIX+2W1o3cHfnqpGajdCs1jSM3DkXjGeLSNmCazv1EeI1ggTeK5wdZhF+7N+g44JI2Od3veojoaumlfg==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-29.7.0.tgz", + "integrity": "sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw==", "dev": true, "dependencies": { "@jest/types": "^29.6.3", @@ -3497,7 +3515,7 @@ "chalk": "^4.0.0", "jest-get-type": "^29.6.3", "leven": "^3.1.0", - "pretty-format": "^29.6.3" + "pretty-format": "^29.7.0" }, "engines": { "node": "^14.15.0 || ^16.10.0 || >=18.0.0" @@ -3516,18 +3534,18 @@ } }, "node_modules/jest-watcher": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-29.6.3.tgz", - "integrity": "sha512-NgpFjZ2U2MKusjidbi4Oiu7tfs+nrgdIxIEVROvH1cFmOei9Uj25lwkMsakqLnH/s0nEcvxO1ck77FiRlcnpZg==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-29.7.0.tgz", + "integrity": "sha512-49Fg7WXkU3Vl2h6LbLtMQ/HyB6rXSIX7SqvBLQmssRBGN9I0PNvPmAmCWSOY6SOvrjhI/F7/bGAv9RtnsPA03g==", "dev": true, "dependencies": { - "@jest/test-result": "^29.6.3", + "@jest/test-result": "^29.7.0", "@jest/types": "^29.6.3", "@types/node": "*", "ansi-escapes": "^4.2.1", "chalk": "^4.0.0", "emittery": "^0.13.1", - "jest-util": "^29.6.3", + "jest-util": "^29.7.0", "string-length": "^4.0.1" }, "engines": { @@ -3535,13 +3553,13 @@ } }, "node_modules/jest-worker": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.6.3.tgz", - "integrity": "sha512-wacANXecZ/GbQakpf2CClrqrlwsYYDSXFd4fIGdL+dXpM2GWoJ+6bhQ7vR3TKi3+gkSfBkjy1/khH/WrYS4Q6g==", + "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, "dependencies": { "@types/node": "*", - "jest-util": "^29.6.3", + "jest-util": "^29.7.0", "merge-stream": "^2.0.0", "supports-color": "^8.0.0" }, @@ -4074,9 +4092,9 @@ } }, "node_modules/pretty-format": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.6.3.tgz", - "integrity": "sha512-ZsBgjVhFAj5KeK+nHfF1305/By3lechHQSMWCTl8iHSbfOm2TN5nHEtFc/+W7fAyUeCs2n5iow72gld4gW0xDw==", + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", "dev": true, "dependencies": { "@jest/schemas": "^29.6.3", @@ -4122,9 +4140,9 @@ } }, "node_modules/pure-rand": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.0.2.tgz", - "integrity": "sha512-6Yg0ekpKICSjPswYOuC5sku/TSWaRYlA0qsXqJgM/d/4pLPHPuTxK7Nbf7jFKzAeedUhR8C7K9Uv63FBsSo8xQ==", + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.0.4.tgz", + "integrity": "sha512-LA0Y9kxMYv47GIPJy6MI84fqTd2HmYZI83W/kM/SkKfDlajnZYfmXFTxkbY+xSBPkLJxltMa9hIkmdc29eguMA==", "dev": true, "funding": [ { @@ -4173,9 +4191,9 @@ } }, "node_modules/resolve": { - "version": "1.22.4", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.4.tgz", - "integrity": "sha512-PXNdCiPqDqeUou+w1C2eTQbNfxKSuMxqTCuvlmmMsk1NWHL5fRrhY6Pl0qEYYc6+QqGClco1Qj8XnjPego4wfg==", + "version": "1.22.8", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", + "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", "dev": true, "dependencies": { "is-core-module": "^2.13.0", @@ -4669,9 +4687,9 @@ } }, "node_modules/typescript": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.1.6.tgz", - "integrity": "sha512-zaWCozRZ6DLEWAWFrVDz1H6FVXzUSfTy5FUMWsQlU8Ym5JP9eO4xkTIROFCQvhQf61z6O/G6ugw3SgAnvvm+HA==", + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.2.2.tgz", + "integrity": "sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==", "dev": true, "bin": { "tsc": "bin/tsc", @@ -4727,25 +4745,19 @@ "dev": true }, "node_modules/v8-to-istanbul": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.1.0.tgz", - "integrity": "sha512-6z3GW9x8G1gd+JIIgQQQxXuiJtCXeAjp6RaPEPLv62mH3iPHPxV6W3robxtCzNErRo6ZwTmzWhsbNvjyEBKzKA==", + "version": "9.1.3", + "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.1.3.tgz", + "integrity": "sha512-9lDD+EVI2fjFsMWXc6dy5JJzBsVTcQ2fVkfBvncZ6xJWG9wtBhOldG+mHkSL0+V1K/xgZz0JDO5UT5hFwHUghg==", "dev": true, "dependencies": { "@jridgewell/trace-mapping": "^0.3.12", "@types/istanbul-lib-coverage": "^2.0.1", - "convert-source-map": "^1.6.0" + "convert-source-map": "^2.0.0" }, "engines": { "node": ">=10.12.0" } }, - "node_modules/v8-to-istanbul/node_modules/convert-source-map": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", - "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", - "dev": true - }, "node_modules/walker": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz", diff --git a/package.json b/package.json index ecfbe52..608e63a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@exasol/extension-manager-interface", - "version": "0.3.1", + "version": "0.4.0", "main": "dist/api.js", "types": "dist/api.d.ts", "description": "Interface for extensions for the Exasol extension manager.", @@ -9,18 +9,18 @@ "build": "tsc --build", "clean": "tsc --build --clean", "test": "jest --silent", - "test-watch": "jest --watch", + "test-watch": "jest --watch --silent", "lint": "eslint --report-unused-disable-directives --exit-on-fatal-error ./src/" }, "devDependencies": { - "@jest/globals": "^29.6.3", - "@typescript-eslint/eslint-plugin": "^6.4.1", - "@typescript-eslint/parser": "^6.4.1", - "eslint": "^8.47.0", - "jest": "^29.6.3", + "@jest/globals": "^29.7.0", + "@typescript-eslint/eslint-plugin": "^6.9.1", + "@typescript-eslint/parser": "^6.9.1", + "eslint": "^8.52.0", + "jest": "^29.7.0", "ts-jest": "^29.1.1", "ts-node": "^10.9.1", - "typescript": "5.1.6" + "typescript": "5.2.2" }, "files": [ "LICENSE", @@ -31,4 +31,4 @@ "src/", "dist/" ] -} \ No newline at end of file +} diff --git a/src/api.ts b/src/api.ts index 701d804..327db10 100644 --- a/src/api.ts +++ b/src/api.ts @@ -5,7 +5,7 @@ import { BadRequestError, NotFoundError, PreconditionFailedError } from "./error import { ExaMetadata } from "./exasolSchema"; import { Parameter } from "./parameters"; -export const CURRENT_API_VERSION = "0.3.1"; +export const CURRENT_API_VERSION = "0.4.0"; /** * This class represents an extension that can be installed and managed with the extension-manager. @@ -211,4 +211,3 @@ export * from "./error"; export * from "./exasolSchema"; export * from "./parameters"; export * from "./sqlClient"; - diff --git a/src/base-vs/addInstance.test.ts b/src/base-vs/addInstance.test.ts new file mode 100644 index 0000000..6a4f3cd --- /dev/null +++ b/src/base-vs/addInstance.test.ts @@ -0,0 +1,161 @@ + +import { describe, expect, it } from '@jest/globals'; +import { ConnectionParameterDefinition, convertVirtualSchemaBaseExtension, createJsonConnectionDefinition, createUserPasswordConnectionDefinition, createVirtualSchemaBuilder } from '.'; +import { Instance, Parameter, ParameterValue } from '../api'; +import { ContextMock, createMockContext } from '../base/test-utils'; +import { emptyBaseVsExtension, param, vsNameParam } from './test-vs-utils'; + +let context: ContextMock = undefined + +function addInstance(paramValues: ParameterValue[], version: string, virtualSchemaParameterDefs: Parameter[], connectionParameterDefs: Parameter[]): Instance { + return addInstanceWithConnectionDef(paramValues, version, virtualSchemaParameterDefs, createJsonConnectionDefinition(connectionParameterDefs)) +} +function addInstanceWithUserPasswordConnection(paramValues: ParameterValue[], version: string, virtualSchemaParameterDefs: Parameter[], addressParam: Parameter = undefined, userParam: Parameter = undefined, passwordParam: Parameter = undefined): Instance { + return addInstanceWithConnectionDef(paramValues, version, virtualSchemaParameterDefs, createUserPasswordConnectionDefinition(addressParam, userParam, passwordParam)) +} +function addInstanceWithConnectionDef(paramValues: ParameterValue[], version: string, virtualSchemaParameterDefs: Parameter[], connectionDefinition: ConnectionParameterDefinition): Instance { + const baseExtension = emptyBaseVsExtension() + baseExtension.name = "testing-extension" + baseExtension.builder = createVirtualSchemaBuilder({ + connectionNameProperty: "CONNECTION_NAME", + virtualSchemaParameters: virtualSchemaParameterDefs, + connectionDefinition + }) + context = createMockContext(); + const installations = convertVirtualSchemaBaseExtension(baseExtension).addInstance(context, version, { values: paramValues }) + expect(installations).toBeDefined() + return installations +} + +function getStatement(index: number): string { + const sqlStatements = context.mocks.sqlExecute.mock.calls + expect(sqlStatements).toHaveLength(4) + expect(sqlStatements[index]).toHaveLength(1) + const stmt = sqlStatements[index][0]; + expect(stmt).toBeDefined() + return stmt +} + +function getCreateConnectionStatement(): string { + return getStatement(0) +} + +function getCreateVirtualSchemaStatement(): string { + return getStatement(1) +} + +describe("addInstance()", () => { + it("fails for wrong version", () => { + expect(() => addInstance([], "wrongVersion", [], [])).toThrow("Version 'wrongVersion' not supported, can only use 'v0'.") + }) + it("returns new instance", () => { + expect(addInstance([vsNameParam("vs1")], "v0", [], [])).toStrictEqual({ id: "vs1", name: "vs1" }) + }) + it("executes statements", () => { + addInstance([vsNameParam("vs1")], "v0", [], []) + expect(getStatement(0)).toBe(`CREATE OR REPLACE CONNECTION "vs1_CONNECTION" TO '' IDENTIFIED BY '{}'`) + expect(getStatement(1)).toBe(`CREATE VIRTUAL SCHEMA "vs1" USING "ext-schema"."vs-adapter-script-name" WITH CONNECTION_NAME = 'vs1_CONNECTION'`) + expect(getStatement(2)).toBe(`COMMENT ON CONNECTION "vs1_CONNECTION" IS 'Created by Extension Manager for testing-extension vv0 vs1'`) + expect(getStatement(3)).toBe(`COMMENT ON SCHEMA "vs1" IS 'Created by Extension Manager for testing-extension vv0 vs1'`) + }) + + describe("creates virtual schema", () => { + const tests: { name: string, vsParamDefs: Parameter[], params: ParameterValue[], expected: string }[] = [ + { name: "no param", vsParamDefs: [], params: [], expected: "" }, + { name: "param def without value", vsParamDefs: [{ id: "p1", name: "P1", type: "string" }], params: [], expected: "" }, + { name: "param def with value", vsParamDefs: [{ id: "p1", name: "P1", type: "string" }], params: [param("p1", "v1")], expected: " p1 = 'v1'" }, + { + name: "multiple values", vsParamDefs: [{ id: "p1", name: "P1", type: "string" }, { id: "p2", name: "P2", type: "string" }], + params: [param("p1", "v1"), param("p2", "v2")], expected: " p1 = 'v1' p2 = 'v2'" + }, + { name: "single quotes escaped", vsParamDefs: [{ id: "p1", name: "P1", type: "string" }], params: [param("p1", "va'lue")], expected: " p1 = 'va''lue'" }, + { name: "double quotes escaped", vsParamDefs: [{ id: "p1", name: "P1", type: "string" }], params: [param("p1", "va\"lue")], expected: " p1 = 'va\"lue'" }, + ] + tests.forEach(test => it(test.name, () => { + addInstance([vsNameParam("vs1"), ...test.params], "v0", test.vsParamDefs, []) + expect(getCreateVirtualSchemaStatement()).toBe(`CREATE VIRTUAL SCHEMA "vs1" USING "ext-schema"."vs-adapter-script-name" ` + + `WITH CONNECTION_NAME = 'vs1_CONNECTION'${test.expected}`) + })) + }) + + describe("creates connection", () => { + describe("with JSON payload", () => { + const tests: { name: string, connParamDefs: Parameter[], params: ParameterValue[], expected: string }[] = [ + { name: "no param", connParamDefs: [], params: [], expected: `{}` }, + { name: "ignores unknown params", connParamDefs: [], params: [param("unknown", "val")], expected: `{}` }, + { name: "missing param with default value ignored", connParamDefs: [{ id: "p1", name: "P1", type: "string", default: "def" }], params: [], expected: `{}` }, + { name: "optional string param", connParamDefs: [{ id: "p1", name: "P1", type: "string", required: false }], params: [param("p1", "v1")], expected: `{"p1":"v1"}` }, + { name: "required string param", connParamDefs: [{ id: "p1", name: "P1", type: "string", required: true }], params: [param("p1", "v1")], expected: `{"p1":"v1"}` }, + { name: "escapes single quotes", connParamDefs: [{ id: "p1", name: "P1", type: "string" }], params: [param("p1", "v'1")], expected: `{"p1":"v''1"}` }, + { name: "escapes double quotes", connParamDefs: [{ id: "p1", name: "P1", type: "string" }], params: [param("p1", "v\"1")], expected: `{"p1":"v\\"1"}` }, + { name: "outputs boolean as string", connParamDefs: [{ id: "p1", name: "P1", type: "boolean" }], params: [param("p1", "true")], expected: `{"p1":"true"}` }, + { name: "outputs select as string", connParamDefs: [{ id: "p1", name: "P1", type: "select", options: [] }], params: [param("p1", "v1")], expected: `{"p1":"v1"}` }, + { name: "multiple parameter definitions, value missing", connParamDefs: [{ id: "p1", name: "P1", type: "string" }, { id: "p2", name: "P2", type: "string" }], params: [param("p1", "v1")], expected: `{"p1":"v1"}` }, + { name: "multiple parameter definitions", connParamDefs: [{ id: "p1", name: "P1", type: "string" }, { id: "p2", name: "P2", type: "string" }], params: [param("p1", "v1"), param("p2", "v2")], expected: `{"p1":"v1","p2":"v2"}` }, + ] + tests.forEach(test => it(test.name, () => { + addInstance([vsNameParam("vs1"), ...test.params], "v0", [], test.connParamDefs) + expect(getCreateConnectionStatement()).toBe(`CREATE OR REPLACE CONNECTION "vs1_CONNECTION" TO '' IDENTIFIED BY '${test.expected}'`) + })) + }) + + describe("with user & password", () => { + interface Test { name: string, connAddr?: Parameter, connUser?: Parameter, connPassword?: Parameter, params: ParameterValue[], expected: string } + + function testValue(testName: string, type: "addr" | "user" | "password", paramValue: string, expected: string): Test { + const params = [] + if (paramValue) { + params.push(param("p1", paramValue)) + } + const paramTemplate: Parameter = { id: "p1", type: "string", name: "P1" } + let connectionParam = undefined + switch (type) { + case "addr": + connectionParam = { connAddr: paramTemplate }; + break + case "user": + connectionParam = { connUser: paramTemplate }; + break + case "password": + connectionParam = { connPassword: paramTemplate }; + break + } + return { name: testName, ...connectionParam, params, expected: expected } + } + + function addressValue(testName: string, paramValue: string, expectedAddress: string): Test { + return testValue(testName, "addr", paramValue, expectedAddress) + } + function userValue(testName: string, paramValue: string, expectedUser: string): Test { + return testValue(testName, "user", paramValue, `TO ''${expectedUser}`) + } + function passwordValue(testName: string, paramValue: string, expectedPassword: string): Test { + return testValue(testName, "password", paramValue, `TO ''${expectedPassword}`) + } + + const tests: Test[] = [ + { name: "no param", params: [], expected: `TO ''` }, + { + name: "all params", connAddr: { id: "p1", type: "string", name: "P1" }, connUser: { id: "p2", type: "string", name: "P2" }, connPassword: { id: "p3", type: "string", name: "P3" }, + params: [param("p1", "addr"), param("p2", "user"), param("p3", "pass")], expected: `TO 'addr' USER 'user' IDENTIFIED BY 'pass'` + }, + addressValue("address value not present", undefined, "TO ''"), + addressValue("address value present", "addr", "TO 'addr'"), + addressValue("address value with single quote escaped", "ad'dr", "TO 'ad''dr'"), + addressValue("address value with double quote escaped", "ad\"dr", "TO 'ad\"dr'"), + userValue("user value not present", undefined, ""), + userValue("user value present", "user", " USER 'user'"), + userValue("user value with single quote escaped", "us'er", " USER 'us''er'"), + userValue("user value with double quote escaped", "us\"er", " USER 'us\"er'"), + passwordValue("password value not present", undefined, ""), + passwordValue("password value present", "pass", " IDENTIFIED BY 'pass'"), + passwordValue("password value with single quote escaped", "pa'ss", " IDENTIFIED BY 'pa''ss'"), + passwordValue("password value with double quote escaped", "pa\"ss", " IDENTIFIED BY 'pa\"ss'"), + ] + tests.forEach(test => it(test.name, () => { + addInstanceWithUserPasswordConnection([vsNameParam("vs1"), ...test.params], "v0", [], test.connAddr, test.connUser, test.connPassword) + expect(getCreateConnectionStatement()).toBe(`CREATE OR REPLACE CONNECTION "vs1_CONNECTION" ${test.expected}`) + })) + }) + }) +}) diff --git a/src/base-vs/addInstance.ts b/src/base-vs/addInstance.ts new file mode 100644 index 0000000..bc243b2 --- /dev/null +++ b/src/base-vs/addInstance.ts @@ -0,0 +1,44 @@ +import { JavaVirtualSchemaBaseExtension } from "."; +import { Instance } from "../api"; +import { Context } from "../context"; +import { convertSchemaNameToInstanceId, escapeSingleQuotes, getConnectionName } from "./common"; +import { ParameterAccessor } from "./parameterAccessor"; +import { PARAM_VIRTUAL_SCHEMA_NAME } from "./parameters"; + +export function addInstance(context: Context, baseExtension: JavaVirtualSchemaBaseExtension, parameters: ParameterAccessor): Instance { + const virtualSchemaName = parameters.get(PARAM_VIRTUAL_SCHEMA_NAME) + const connectionName = getConnectionName(virtualSchemaName) + context.sqlClient.execute(buildConnectionStatement(baseExtension, parameters, connectionName)) + context.sqlClient.execute(buildVirtualSchemaStatement(baseExtension, parameters, connectionName, context, virtualSchemaName)) + + const comment = `Created by Extension Manager for ${baseExtension.name} v${baseExtension.version} ${escapeSingleQuotes(virtualSchemaName)}`; + context.sqlClient.execute(`COMMENT ON CONNECTION "${connectionName}" IS '${comment}'`); + context.sqlClient.execute(`COMMENT ON SCHEMA "${virtualSchemaName}" IS '${comment}'`); + return { id: convertSchemaNameToInstanceId(virtualSchemaName), name: virtualSchemaName } +} + +function buildVirtualSchemaStatement(baseExtension: JavaVirtualSchemaBaseExtension, parameters: ParameterAccessor, connectionName: string, context: Context, virtualSchemaName: string) { + const def = baseExtension.builder.buildVirtualSchema(parameters, connectionName); + const adapter = `"${context.extensionSchemaName}"."${baseExtension.virtualSchemaAdapterScript}"`; + let stmt = `CREATE VIRTUAL SCHEMA "${virtualSchemaName}" USING ${adapter}`; + if (def.properties.length > 0) { + stmt += " WITH" + for (const property of def.properties) { + stmt += ` ${property.property} = '${escapeSingleQuotes(property.value)}'`; + } + } + return stmt; +} + +function buildConnectionStatement(baseExtension: JavaVirtualSchemaBaseExtension, parameters: ParameterAccessor, connectionName: string) { + const connDef = baseExtension.builder.buildConnection(parameters); + const to = connDef.connectionTo ?? ''; + let stmt = `CREATE OR REPLACE CONNECTION "${connectionName}" TO '${escapeSingleQuotes(to)}'`; + if (connDef.user) { + stmt += ` USER '${escapeSingleQuotes(connDef.user)}'`; + } + if (connDef.identifiedBy) { + stmt += ` IDENTIFIED BY '${escapeSingleQuotes(connDef.identifiedBy)}'`; + } + return stmt; +} diff --git a/src/base-vs/builders.ts b/src/base-vs/builders.ts new file mode 100644 index 0000000..75f3605 --- /dev/null +++ b/src/base-vs/builders.ts @@ -0,0 +1,99 @@ +import { ConnectionDefinition, VirtualSchemaBuilder, VirtualSchemaProperty } from "."; +import { Parameter } from "../parameters"; +import { ParameterAccessor } from "./parameterAccessor"; + +/** + * Definition of `CONNECTION` parameters. + * @see {@link createUserPasswordConnectionDefinition} + * @see {@link createJsonConnectionDefinition} + */ +export interface ConnectionParameterDefinition { + /** Parameter definitions used in the `CONNECTION` */ + parameters: Parameter[] + /** Factory function for a `CONNECTION` definition */ + builder: (parameters: ParameterAccessor) => ConnectionDefinition +} + +/** Configuration for the function {@link createVirtualSchemaBuilder()} */ +export interface Config { + /** Parameter definitions used as properties for the `VIRTUAL SCHEMA` */ + virtualSchemaParameters: Parameter[] + /** Name of the Virtual Schema's connection property, e.g. `CONNECTION_NAME` */ + connectionNameProperty: string + /** + * Connection definition. Create a connection definition with one of the functions + * {@link createJsonConnectionDefinition} or {@link createUserPasswordConnectionDefinition} + */ + connectionDefinition: ConnectionParameterDefinition +} + +/** + * Create a virtual schema builder. + * @param param0 configuration + * @returns a new virtual schema builder + */ +export function createVirtualSchemaBuilder({ virtualSchemaParameters, connectionNameProperty, connectionDefinition }: Config): VirtualSchemaBuilder { + function convertVirtualSchemaParameters(connectionName: string, parameters: ParameterAccessor) { + const result: VirtualSchemaProperty[] = [] + result.push({ property: connectionNameProperty, value: connectionName }) + for (const param of virtualSchemaParameters) { + const value = parameters.getOptional(param) + if (value) { + result.push({ property: param.id, value }) + } + } + return result + } + + return { + getParameters() { + return [...virtualSchemaParameters, ...connectionDefinition.parameters] + }, + buildConnection: connectionDefinition.builder, + buildVirtualSchema(parameters: ParameterAccessor, connectionName: string) { + return { properties: convertVirtualSchemaParameters(connectionName, parameters) } + }, + } +} + +/** + * Creates the definition for a connection that contains all configuration formatted as JSON in the `IDENTIFIED BY` clause. + * This is usually used in document based virtual schemas. + * @param parameterDefinitions parameter definitions + * @returns connection definition + */ +export function createJsonConnectionDefinition(parameterDefinitions: Parameter[]): ConnectionParameterDefinition { + function builder(parameters: ParameterAccessor): ConnectionDefinition { + /* eslint-disable @typescript-eslint/no-explicit-any */ + const paramValues: any = {} + for (const param of parameterDefinitions) { + const value = parameters.getOptional(param) + if (value) { + /* eslint-disable @typescript-eslint/no-unsafe-member-access */ + paramValues[param.id] = value + } + } + + return { identifiedBy: JSON.stringify(paramValues) } + } + return { parameters: parameterDefinitions, builder } +} + +/** + * Creates the definition for a connection that contains `TO`, `USER` and `IDENTIFIED BY` clauses. + * This is usually used in JDBC based virtual schemas. + * @param addressParam parameter definition for the `TO` clause + * @param userParam parameter definition for the `USER` clause + * @param passwordParam parameter definition for the `IDENTIFIED BY` clause + * @returns connection definition + */ +export function createUserPasswordConnectionDefinition(addressParam: Parameter, userParam: Parameter, passwordParam: Parameter): ConnectionParameterDefinition { + function builder(parameters: ParameterAccessor): ConnectionDefinition { + return { + connectionTo: addressParam ? parameters.getOptional(addressParam) : undefined, + user: userParam ? parameters.getOptional(userParam) : undefined, + identifiedBy: passwordParam ? parameters.getOptional(passwordParam) : undefined + } + } + return { parameters: [addressParam, userParam, passwordParam], builder } +} diff --git a/src/base-vs/common.test.ts b/src/base-vs/common.test.ts new file mode 100644 index 0000000..369fa8f --- /dev/null +++ b/src/base-vs/common.test.ts @@ -0,0 +1,31 @@ +import { describe, expect, it } from '@jest/globals'; +import { convertInstanceIdToSchemaName, convertSchemaNameToInstanceId, escapeSingleQuotes, getConnectionName } from './common'; + +describe("common", () => { + describe("convertInstanceIdToSchemaName()", () => { + it("returns same value", () => { + expect(convertInstanceIdToSchemaName("My Value")).toBe("My Value") + }) + }) + describe("convertSchemaNameToInstanceId()", () => { + it("returns same value", () => { + expect(convertSchemaNameToInstanceId("My Value")).toBe("My Value") + }) + }) + describe("getConnectionName()", () => { + it("appends _CONNECTION", () => { + expect(getConnectionName("My Value")).toBe("My Value_CONNECTION") + }) + }) + describe("escapeSingleQuotes()", () => { + const tests: { name: string, input: string, expectedResult: string }[] = [ + { name: "no single quote", input: "My Value", expectedResult: "My Value" }, + { name: "single quote", input: "My'Value", expectedResult: "My''Value" }, + { name: "two single quote", input: "My''Value", expectedResult: "My''''Value" }, + { name: "quoted string", input: "My 'Value'", expectedResult: "My ''Value''" }, + ] + tests.forEach(test => it(test.name, () => { + expect(escapeSingleQuotes(test.input)).toBe(test.expectedResult) + })) + }) +}) diff --git a/src/base-vs/common.ts b/src/base-vs/common.ts new file mode 100644 index 0000000..8431fe8 --- /dev/null +++ b/src/base-vs/common.ts @@ -0,0 +1,14 @@ +function identity(arg: string): string { + return arg; +} + +export const convertInstanceIdToSchemaName = identity +export const convertSchemaNameToInstanceId = identity + +export function getConnectionName(virtualSchemaName: string): string { + return `${virtualSchemaName}_CONNECTION`; +} + +export function escapeSingleQuotes(value: string): string { + return value.replace(/'/g, "''") +} diff --git a/src/base-vs/deleteInstance.test.ts b/src/base-vs/deleteInstance.test.ts new file mode 100644 index 0000000..1f17a7b --- /dev/null +++ b/src/base-vs/deleteInstance.test.ts @@ -0,0 +1,39 @@ + +import { describe, expect, it } from '@jest/globals'; +import { convertVirtualSchemaBaseExtension, createJsonConnectionDefinition, createVirtualSchemaBuilder } from '.'; +import { ContextMock, createMockContext } from '../base/test-utils'; +import { emptyBaseVsExtension } from './test-vs-utils'; + +let context: ContextMock = undefined + +function deleteInstance(version: string, instanceId: string): void { + const baseExtension = emptyBaseVsExtension() + baseExtension.name = "testing-extension" + baseExtension.builder = createVirtualSchemaBuilder({ + connectionNameProperty: "CONNECTION_NAME", + virtualSchemaParameters: [], + connectionDefinition: createJsonConnectionDefinition([]) + }) + context = createMockContext(); + convertVirtualSchemaBaseExtension(baseExtension).deleteInstance(context, version, instanceId) +} + +function getStatement(index: number): string { + const sqlStatements = context.mocks.sqlExecute.mock.calls + expect(sqlStatements).toHaveLength(2) + expect(sqlStatements[index]).toHaveLength(1) + const stmt = sqlStatements[index][0]; + expect(stmt).toBeDefined() + return stmt +} + +describe("deleteInstance()", () => { + it("fails for wrong version", () => { + expect(() => deleteInstance("wrongVersion", "instId")).toThrow("Version 'wrongVersion' not supported, can only use 'v0'.") + }) + it("executes DROP statements", () => { + deleteInstance("v0", "instId") + expect(getStatement(0)).toBe(`DROP VIRTUAL SCHEMA IF EXISTS "instId" CASCADE`) + expect(getStatement(1)).toBe(`DROP CONNECTION IF EXISTS "instId_CONNECTION"`) + }) +}) diff --git a/src/base-vs/deleteInstance.ts b/src/base-vs/deleteInstance.ts new file mode 100644 index 0000000..08a153f --- /dev/null +++ b/src/base-vs/deleteInstance.ts @@ -0,0 +1,17 @@ +import { JavaVirtualSchemaBaseExtension } from "."; +import { Context } from "../api"; +import { convertInstanceIdToSchemaName, getConnectionName } from "./common"; + +export function deleteInstance(context: Context, extension: JavaVirtualSchemaBaseExtension, instanceId: string): void { + const schemaName = convertInstanceIdToSchemaName(instanceId); + context.sqlClient.execute(dropVirtualSchemaStatement(schemaName)); + context.sqlClient.execute(dropConnectionStatement(getConnectionName(schemaName))); +} + +function dropVirtualSchemaStatement(schemaName: string): string { + return `DROP VIRTUAL SCHEMA IF EXISTS "${schemaName}" CASCADE`; +} + +function dropConnectionStatement(connectionName: string): string { + return `DROP CONNECTION IF EXISTS "${connectionName}"`; +} diff --git a/src/base-vs/findInstances.test.ts b/src/base-vs/findInstances.test.ts new file mode 100644 index 0000000..a03b626 --- /dev/null +++ b/src/base-vs/findInstances.test.ts @@ -0,0 +1,38 @@ + +import { describe, expect, it } from '@jest/globals'; +import { convertVirtualSchemaBaseExtension } from '.'; +import { Instance, Row } from '../api'; +import { ContextMock, createMockContext } from '../base/test-utils'; +import { emptyBaseVsExtension } from './test-vs-utils'; + +let context: ContextMock = undefined + +function findInstances(version: string, rows: Row[]): Instance[] { + const baseExtension = emptyBaseVsExtension() + baseExtension.name = "testing-extension" + context = createMockContext(); + context.mocks.sqlQuery.mockReturnValueOnce({ columns: [], rows }) + const installations = convertVirtualSchemaBaseExtension(baseExtension).findInstances(context, version) + expect(installations).toBeDefined() + return installations +} + +describe("findInstances()", () => { + it("fails for wrong version", () => { + expect(() => findInstances("wrongVersion", [])).toThrow("Version 'wrongVersion' not supported, can only use 'v0'.") + }) + it("returns empty array when no virtual schema exists", () => { + expect(findInstances("v0", [])).toStrictEqual([]) + }) + it("returns single entry when virtual schema exists", () => { + expect(findInstances("v0", [["vs-name"]])).toStrictEqual([{ id: "vs-name", name: "vs-name" }]) + }) + it("returns multiple entries", () => { + expect(findInstances("v0", [["vs1"], ["vs2"]])).toStrictEqual([{ id: "vs1", name: "vs1" }, { id: "vs2", name: "vs2" }]) + }) + it("executes expected query", () => { + findInstances("v0", [["vs1"], ["vs2"]]) + const expectedQuery = "SELECT SCHEMA_NAME FROM SYS.EXA_ALL_VIRTUAL_SCHEMAS WHERE ADAPTER_SCRIPT_SCHEMA = ? AND ADAPTER_SCRIPT_NAME = ? ORDER BY SCHEMA_NAME"; + expect(context.mocks.sqlQuery.mock.calls).toStrictEqual([[expectedQuery, "ext-schema", "vs-adapter-script-name"]]) + }) +}) diff --git a/src/base-vs/findInstances.ts b/src/base-vs/findInstances.ts new file mode 100644 index 0000000..2b03180 --- /dev/null +++ b/src/base-vs/findInstances.ts @@ -0,0 +1,12 @@ +import { Instance } from "../api"; +import { Context } from "../context"; + +export function findInstances(context: Context, adapterScript: string): Instance[] { + const result = context.sqlClient.query("SELECT SCHEMA_NAME FROM SYS.EXA_ALL_VIRTUAL_SCHEMAS" + + " WHERE ADAPTER_SCRIPT_SCHEMA = ? AND ADAPTER_SCRIPT_NAME = ? " + + "ORDER BY SCHEMA_NAME", context.extensionSchemaName, adapterScript) + return result.rows.map(row => { + const schemaName = row[0]; + return { id: schemaName, name: schemaName } + }) +} diff --git a/src/base-vs/getInstanceParameters.test.ts b/src/base-vs/getInstanceParameters.test.ts new file mode 100644 index 0000000..b23bb1b --- /dev/null +++ b/src/base-vs/getInstanceParameters.test.ts @@ -0,0 +1,40 @@ + +import { describe, expect, it } from '@jest/globals'; +import { convertVirtualSchemaBaseExtension } from '.'; +import { Parameter } from '../api'; +import { createMockContext } from '../base/test-utils'; +import { PreconditionFailedError } from '../error'; +import { emptyBaseVsExtension } from './test-vs-utils'; + +function getInstanceParameters(version: string): Parameter[] { + const baseExtension = emptyBaseVsExtension() + baseExtension.name = "testing-extension" + const installations = convertVirtualSchemaBaseExtension(baseExtension).getInstanceParameters(createMockContext(), version) + expect(installations).toBeDefined() + return installations +} + +describe("getInstanceParameters", () => { + it("fails for unsupported version", () => { + expect(() => getInstanceParameters("wrong version")) + .toThrowError(new PreconditionFailedError("Version 'wrong version' not supported, can only use 'v0'.")) + }) + it("succeeds for supported version", () => { + expect(getInstanceParameters("v0")) + .toStrictEqual([ + { + description: "Name for the new virtual schema", + id: "base-vs.virtual-schema-name", + name: "Virtual Schema name", + placeholder: "MY_VIRTUAL_SCHEMA", + regex: "[a-zA-Z_]+", + required: true, + type: "string", + }, + { id: "vs-required", name: "n1", required: true, type: "string" }, + { id: "vs-optional", name: "n2", required: false, type: "string" }, + { id: "conn-required", name: "n1", required: true, type: "string" }, + { id: "conn-optional", name: "n2", required: false, type: "string" }, + ]) + }) +}) diff --git a/src/base-vs/getInstanceParameters.ts b/src/base-vs/getInstanceParameters.ts new file mode 100644 index 0000000..a62170e --- /dev/null +++ b/src/base-vs/getInstanceParameters.ts @@ -0,0 +1,7 @@ +import { JavaVirtualSchemaBaseExtension } from "."; +import { Parameter } from "../parameters"; +import { PARAM_VIRTUAL_SCHEMA_NAME } from "./parameters"; + +export function getInstanceParameters(baseExtension: JavaVirtualSchemaBaseExtension): Parameter[] { + return [PARAM_VIRTUAL_SCHEMA_NAME, ...baseExtension.builder.getParameters()] +} diff --git a/src/base-vs/index.ts b/src/base-vs/index.ts new file mode 100644 index 0000000..0702ebe --- /dev/null +++ b/src/base-vs/index.ts @@ -0,0 +1,105 @@ +import { Context, ExasolExtension, Instance, NotFoundError, Parameter, ParameterValues } from "../api"; +import { JavaBaseExtension, convertBaseExtension } from "../base"; +import { addInstance } from "./addInstance"; +import { deleteInstance } from "./deleteInstance"; +import { findInstances } from "./findInstances"; +import { getInstanceParameters } from "./getInstanceParameters"; +import { ParameterAccessor, createParameterAccessor } from "./parameterAccessor"; + +/** + * Simplified version of an {@link ExasolExtension} specifically for Java based `VIRTUAL SCHEMA`s. + */ +export interface JavaVirtualSchemaBaseExtension extends JavaBaseExtension { + /** Unqualified name of the virtual schema `ADAPTER SCRIPT`, e.g. `S3_FILES_ADAPTER` */ + virtualSchemaAdapterScript: string, + /** A builder for virtual schemas. Use function {@link createVirtualSchemaBuilder()} to create it. */ + builder: VirtualSchemaBuilder, +} + +/** Definition of an Exasol `CONNECTION` object */ +export interface ConnectionDefinition { + /** Connection address, i.e. the `CONNECT TO '...'` part */ + connectionTo?: string + /** Connection user, i.e. the `USER '...'` part */ + user?: string + /** Connection password, i.e. the `IDENTIFIED BY '...'` part */ + identifiedBy?: string +} + +/** + * Property in the `WITH` clause of a `VIRTUAL SCHEMA` definition. + * @see {@link VirtualSchemaDefinition} + */ +export interface VirtualSchemaProperty { + /** Property name, e.g. `CONNECTION_NAME` or `MAPPING` */ + property: string + /** Value of the property, e.g. `MY_S3_VS_CONNECTION` or `{...}` */ + value: string +} + +/** Definition of a `VIRTUAL SCHEMA` */ +export interface VirtualSchemaDefinition { + /** Property values for the `WITH` clause */ + properties: VirtualSchemaProperty[] +} + +/** A builder for creating `VIRTUAL SCHEMA`s including their `CONNECTION`. */ +export interface VirtualSchemaBuilder { + /** + * Get all parameters for the `VIRTUAL SCHEMA` and `CONNECTION`. + * @returns all parameters + */ + getParameters: () => Parameter[] + /** + * Create the `VIRTUAL SCHEMA` definition incl. user provided parameter values. + * @param parameters resolves {@link Parameter} objects to actual values supplied by the user + * @param connectionName name of the connection to use for the virtual schema + * @returns `VIRTUAL SCHEMA` definition + */ + buildVirtualSchema: (parameters: ParameterAccessor, connectionName: string) => VirtualSchemaDefinition + /** + * Create the `CONNECTION` definition incl. user provided parameter values. + * @param parameters resolves {@link Parameter} objects to actual values supplied by the user + * @returns `CONNECTION` definition + */ + buildConnection: (parameters: ParameterAccessor) => ConnectionDefinition +} + +/** + * Converts a {@link JavaVirtualSchemaBaseExtension} to an {@link ExasolExtension}. + * Use this in your virtual schema extension to avoid duplicating code. + * @param baseExtension the base extension to convert + * @returns an {@link ExasolExtension} + */ +export function convertVirtualSchemaBaseExtension(baseExtension: JavaVirtualSchemaBaseExtension): ExasolExtension { + const extension = convertBaseExtension(baseExtension) + + function verifyVersion(version: string) { + if (baseExtension.version !== version) { + throw new NotFoundError(`Version '${version}' not supported, can only use '${baseExtension.version}'.`) + } + } + + return { + ...extension, + getInstanceParameters(context: Context, extensionVersion: string): Parameter[] { + verifyVersion(extensionVersion) + return getInstanceParameters(baseExtension) + }, + findInstances(context: Context, version: string): Instance[] { + verifyVersion(version) + return findInstances(context, baseExtension.virtualSchemaAdapterScript); + }, + addInstance(context: Context, versionToInstall: string, paramValues: ParameterValues): Instance { + verifyVersion(versionToInstall) + const parameters = createParameterAccessor(paramValues) + return addInstance(context, baseExtension, parameters) + }, + deleteInstance(context: Context, extensionVersion: string, instanceId: string) { + verifyVersion(extensionVersion) + deleteInstance(context, baseExtension, instanceId) + }, + } +} + +export * from './builders'; diff --git a/src/base-vs/parameterAccessor.test.ts b/src/base-vs/parameterAccessor.test.ts new file mode 100644 index 0000000..8511509 --- /dev/null +++ b/src/base-vs/parameterAccessor.test.ts @@ -0,0 +1,97 @@ + +import { describe, expect, it } from '@jest/globals'; +import { Parameter, ParameterValue } from '../api'; +import { createParameterAccessor } from './parameterAccessor'; + +describe("ParameterAccessor", () => { + function param(name: string, value: string): ParameterValue { + return { name, value } + } + function testee(...values: ParameterValue[]) { + return createParameterAccessor({ values }) + } + + describe("createParameterAccessor()", () => { + it("succeeds for empty parameter list", () => { + expect(testee()).toBeDefined() + }) + it("succeeds for single parameter", () => { + expect(testee(param("p1", "v1"))).toBeDefined() + }) + it("succeeds for multiple parameters", () => { + expect(testee(param("p1", "v1"), param("p2", "v2"))).toBeDefined() + }) + it("fails for duplicate parameter names", () => { + expect(() => testee(param("p1", "v1"), param("p1", "v2"))).toThrow("Two values 'v2' and 'v1' found for parameter p1") + }) + }) + + describe("getOptional()", () => { + describe("supported parameter types", () => { + const tests: { name: string, param: Parameter }[] = [ + { name: "optional string", param: { id: "p1", name: "n1", type: "string", required: false } }, + { name: "optional select", param: { id: "p1", name: "n1", type: "select", options: [], required: false } }, + { name: "optional boolean", param: { id: "p1", name: "n1", type: "boolean", required: false } }, + { name: "required string", param: { id: "p1", name: "n1", type: "string", required: true } }, + { name: "required select", param: { id: "p1", name: "n1", type: "select", options: [], required: true } }, + { name: "required boolean", param: { id: "p1", name: "n1", type: "boolean", required: true } }, + ] + tests.forEach(test => describe(test.name, () => { + it("returns undefined for empty parameter values", () => { + expect(testee().getOptional(test.param)).toBeUndefined() + }) + it("returns undefined for missing parameter", () => { + expect(testee(param("wrong-name", "value")).getOptional(test.param)).toBeUndefined() + }) + it("returns value for available parameter", () => { + expect(testee(param(test.param.id, "value")).getOptional(test.param)).toEqual("value") + }) + it("returns undefined for undefined parameter", () => { + expect(testee(param(test.param.id, undefined)).getOptional(test.param)).toBeUndefined() + }) + it("returns value for multiple parameters", () => { + expect(testee(param("wrong-name", "value"), param(test.param.id, "value")).getOptional(test.param)).toEqual("value") + }) + it("returns undefined for missing parameter without default value", () => { + expect(testee().getOptional({ ...test.param, default: undefined })).toBeUndefined() + }) + it("returns undefined for missing parameter with default value", () => { + expect(testee().getOptional({ default: "def", ...test.param })).toBeUndefined() + }) + })) + }) + }) + + describe("get()", () => { + it("fails for optional parameter", () => { + expect(() => testee().get({ id: "p1", name: "n1", type: "string", required: false })).toThrow("Parameter p1 is optional. Use method 'resolveOptional()' to resolve it.") + }) + it("fails for optional parameter with default", () => { + expect(() => testee().get({ id: "p1", name: "n1", type: "string", required: false, default: "def" })).toThrow("Parameter p1 is optional. Use method 'resolveOptional()' to resolve it.") + }) + describe("supported parameter types", () => { + const tests: { name: string, param: Parameter }[] = [ + { name: "string", param: { id: "p1", name: "n1", type: "string", required: true } }, + { name: "select", param: { id: "p1", name: "n1", type: "select", options: [], required: true } }, + { name: "boolean", param: { id: "p1", name: "n1", type: "boolean", required: true } }, + ] + tests.forEach(test => describe(test.name, () => { + it("fails for empty parameter values", () => { + expect(() => testee().get(test.param)).toThrow("No value found for required parameter p1") + }) + it("fails for missing parameter", () => { + expect(() => testee(param("wrong-name", "value")).get(test.param)).toThrow("No value found for required parameter p1") + }) + it("returns value for available parameter", () => { + expect(testee(param(test.param.id, "value")).get(test.param)).toEqual("value") + }) + it("fails for undefined parameter", () => { + expect(() => testee(param(test.param.id, undefined)).get(test.param)).toThrow("No value found for required parameter p1") + }) + it("returns value for multiple parameters", () => { + expect(testee(param("wrong-name", "value"), param(test.param.id, "value")).get(test.param)).toEqual("value") + }) + })) + }) + }) +}) diff --git a/src/base-vs/parameterAccessor.ts b/src/base-vs/parameterAccessor.ts new file mode 100644 index 0000000..263cb88 --- /dev/null +++ b/src/base-vs/parameterAccessor.ts @@ -0,0 +1,48 @@ +import { Parameter, ParameterValues } from "../api"; + +/** Gives access to parameter values provided by the user. */ +export interface ParameterAccessor { + /** + * Get the value of a mandatory parameter. + * @param parameterDefinition parameter definition for which to get the parameter value + * @returns parameter value + * @throws an Error if the user did not provide a value for the parameter + */ + get: (parameterDefinition: Parameter) => string; + /** + * Get the value of an optional parameter. + * @param parameterDefinition parameter definition for which to get the parameter value + * @returns parameter value or `undefined` if no value is available + */ + getOptional: (parameterDefinition: Parameter) => string | undefined; +} + +function buildValuesMap(values: ParameterValues): Map { + const valuesMap = new Map() + for (const value of values.values) { + if (valuesMap.has(value.name)) { + throw new Error(`Two values '${value.value}' and '${valuesMap.get(value.name)}' found for parameter ${value.name}`) + } + valuesMap.set(value.name, value.value) + } + return valuesMap +} + +export function createParameterAccessor(paramValues: ParameterValues): ParameterAccessor { + const values = buildValuesMap(paramValues) + function getOptional(paramDef: Parameter): string | undefined { + return values.get(paramDef.id); + } + + function get(paramDef: Parameter): string { + if (!paramDef.required) { + throw new Error(`Parameter ${paramDef.id} is optional. Use method 'resolveOptional()' to resolve it.`) + } + const value = values.get(paramDef.id) + if (!value) { + throw new Error(`No value found for required parameter ${paramDef.id}`) + } + return value + } + return { getOptional, get } +} diff --git a/src/base-vs/parameters.ts b/src/base-vs/parameters.ts new file mode 100644 index 0000000..1e04c99 --- /dev/null +++ b/src/base-vs/parameters.ts @@ -0,0 +1,6 @@ +import { Parameter } from "../parameters"; + +export const PARAM_VIRTUAL_SCHEMA_NAME: Parameter = { + id: "base-vs.virtual-schema-name", name: "Virtual Schema name", description: "Name for the new virtual schema", + type: "string", required: true, placeholder: "MY_VIRTUAL_SCHEMA", regex: "[a-zA-Z_]+" +} diff --git a/src/base-vs/test-vs-utils.ts b/src/base-vs/test-vs-utils.ts new file mode 100644 index 0000000..42c83dc --- /dev/null +++ b/src/base-vs/test-vs-utils.ts @@ -0,0 +1,37 @@ +import { JavaVirtualSchemaBaseExtension, createJsonConnectionDefinition, createVirtualSchemaBuilder } from "."; +import { ParameterValue } from "../api"; +import { successResult } from "../base/common"; + +export function emptyBaseVsExtension(): JavaVirtualSchemaBaseExtension { + const adapterName = "vs-adapter-script-name" + return { + name: "testing-base-extension", + version: "v0", + category: "test-category", + description: "Testing base extension", + file: { + name: "test-ext.jar", + size: 12345 + }, + scripts: [{ name: adapterName, type: "SCALAR", parameters: "...", emitParameters: "...", scriptClass: "com.exasol.TestingAdapter" }], + scriptVersionExtractor: () => successResult("dummy version"), + virtualSchemaAdapterScript: adapterName, + builder: createVirtualSchemaBuilder({ + connectionNameProperty: "CONNECTION_NAME", + virtualSchemaParameters: [ + { id: "vs-required", name: "n1", type: "string", required: true }, + { id: "vs-optional", name: "n2", type: "string", required: false }], + connectionDefinition: createJsonConnectionDefinition([ + { id: "conn-required", name: "n1", type: "string", required: true }, + { id: "conn-optional", name: "n2", type: "string", required: false }]) + }) + } +} + +export function param(name: string, value: string): ParameterValue { + return { name, value } +} + +export function vsNameParam(vsName: string): ParameterValue { + return param("base-vs.virtual-schema-name", vsName) +} diff --git a/src/base/adapterScript.ts b/src/base/adapterScript.ts index 8b7651b..5be57e5 100644 --- a/src/base/adapterScript.ts +++ b/src/base/adapterScript.ts @@ -14,13 +14,4 @@ export class AdapterScript { get name() { return this.script.name } - get qualifiedName() { - return `${this.script.schema}.${this.script.name}` - } - get schema() { - return this.script.schema - } - get text() { - return this.script.text - } } diff --git a/src/base/findInstallations.test.ts b/src/base/findInstallations.test.ts index 09df269..24a4d43 100644 --- a/src/base/findInstallations.test.ts +++ b/src/base/findInstallations.test.ts @@ -1,6 +1,6 @@ import { describe, expect, it } from '@jest/globals'; -import { ScriptDefinition, VersionExtractor, convertBaseExtension } from '.'; +import { ScalarSetScriptDefinition, ScriptDefinition, VersionExtractor, convertBaseExtension } from '.'; import { Installation } from '../api'; import { PreconditionFailedError } from '../error'; import { ExaMetadata, ExaScriptsRow } from '../exasolSchema'; @@ -13,8 +13,8 @@ const failingVersionExtractor: (failureMessage: string) => VersionExtractor = (f function script({ schema = "schema", name = "name", inputType, resultType = "EMITS", type = "UDF", text = "", comment }: Partial): ExaScriptsRow { return { schema, name, inputType, resultType, type, text, comment } } -function def({ name = "name", type = "SET", args = "args", scriptClass = "script class" }: Partial): ScriptDefinition { - return { name, type, args, scriptClass }; +function def({ name = "name", type = "SET", parameters = "param", emitParameters = "emitParam", scriptClass = "script class" }: Partial): ScriptDefinition { + return { name, type, parameters, emitParameters, scriptClass }; } function findInstallations(allScripts: ExaScriptsRow[], scriptDefinitions: ScriptDefinition[], versionExtractor: VersionExtractor): Installation[] { @@ -48,4 +48,3 @@ describe("findInstallations", () => { .toStrictEqual([{ name: "testing-extension", version: "v1" }]) }) }) - diff --git a/src/base/getInstanceParameters.test.ts b/src/base/getInstanceParameters.test.ts new file mode 100644 index 0000000..1ebd8fe --- /dev/null +++ b/src/base/getInstanceParameters.test.ts @@ -0,0 +1,21 @@ + +import { describe, expect, it } from '@jest/globals'; +import { convertBaseExtension } from '.'; +import { Parameter } from '../api'; +import { PreconditionFailedError } from '../error'; +import { createMockContext, emptyBaseExtension } from './test-utils'; + +function getInstanceParameters(): Parameter[] { + const baseExtension = emptyBaseExtension() + baseExtension.name = "testing-extension" + const installations = convertBaseExtension(baseExtension).getInstanceParameters(createMockContext(), "version") + expect(installations).toBeDefined() + return installations +} + +describe("getInstanceParameters", () => { + it("not supported", () => { + expect(() => getInstanceParameters()) + .toThrowError(new PreconditionFailedError("Creating instances not supported")) + }) +}) diff --git a/src/base/index.test.ts b/src/base/index.test.ts new file mode 100644 index 0000000..d371ab7 --- /dev/null +++ b/src/base/index.test.ts @@ -0,0 +1,45 @@ + +import { describe, expect, it } from '@jest/globals'; +import { ExasolExtension, NotFoundError } from '../api'; +import { convertBaseExtension } from './index'; +import { emptyBaseExtension } from './test-utils'; + + +describe("index", () => { + function testee(): ExasolExtension { + const baseExtension = emptyBaseExtension() + baseExtension.name = "test-ext" + baseExtension.version = "v1" + return convertBaseExtension(baseExtension) + } + + describe("findInstances()", () => { + it("is not supported", () => { + expect(() => testee().findInstances(undefined, undefined)).toThrowError(new NotFoundError("Finding instances not supported")) + }) + }) + + describe("addInstance()", () => { + it("is not supported", () => { + expect(() => testee().addInstance(undefined, undefined, undefined)).toThrowError(new NotFoundError("Creating instances not supported")) + }) + }) + + describe("deleteInstance()", () => { + it("is not supported", () => { + expect(() => testee().deleteInstance(undefined, undefined, undefined)).toThrowError(new NotFoundError("Deleting instances not supported")) + }) + }) + + describe("getInstanceParameters()", () => { + it("is not supported", () => { + expect(() => testee().getInstanceParameters(undefined, undefined)).toThrowError(new NotFoundError("Creating instances not supported")) + }) + }) + + describe("readInstanceParameterValues()", () => { + it("is not supported", () => { + expect(() => testee().readInstanceParameterValues(undefined, undefined, undefined)).toThrowError(new NotFoundError("Reading instance parameter values not supported")) + }) + }) +}) diff --git a/src/base/index.ts b/src/base/index.ts index 21f90ea..35bdae8 100644 --- a/src/base/index.ts +++ b/src/base/index.ts @@ -6,23 +6,52 @@ import { uninstall } from "./uninstall"; import { upgrade } from "./upgrade"; /** Definition of a Java based Exasol `SCRIPT` with all information required for creating it in the database. */ -export interface ScriptDefinition { +export type ScriptDefinition = ScalarSetScriptDefinition | AdapterScriptDefinition + +/** Definition of a SCALAR or SET script */ +export interface ScalarSetScriptDefinition { + /** Script type */ + type: "SCALAR" | "SET" + /** Unqualified script name, e.g. "IMPORT_FROM_S3_DOCUMENT_FILES", KAFKA_CONSUMER */ + name: string + /** Script parameters, e.g. "..." or "params VARCHAR(2000), kafka_partition DECIMAL(18, 0), kafka_offset DECIMAL(36, 0)" */ + parameters: string + /** Emit parameters, e.g. "..." or "filename VARCHAR(2000), partition_index VARCHAR(100), start_index DECIMAL(36, 0), end_index DECIMAL(36, 0)" */ + emitParameters: string + /** Script Java class name, e.g. "com.exasol.adapter.document.UdfEntryPoint" */ + scriptClass: string +} + +/** Definition of an ADAPTER script */ +export interface AdapterScriptDefinition { + /** Unqualified script name, e.g. "S3_FILES_ADAPTER" */ name: string - type: "SET" | "SCALAR" - args: string + /** Script type */ + type: "ADAPTER" + /** Script Java class name, e.g. "com.exasol.adapter.document.UdfEntryPoint" */ scriptClass: string } +/** + * Simplified version of an {@link ExasolExtension} specifically for Java based extensions. + * Use function {@link convertBaseExtension} to convert this to a {@link ExasolExtension}. + */ export interface JavaBaseExtension { + /** Readable extension name, e.g. "S3 Virtual Schema" */ name: string + /** Extension description, e.g. "Virtual Schema for document files on AWS S3" */ description: string + /** Extension category, e.g. "document-virtual-schema", "jdbc-virtual-schema", ... */ category: string + /** Current extension version, e.g. 1.2.3 */ version: string file: { name: string size: number } + /** Adapter script definitions for this extension */ scripts: ScriptDefinition[] + /** Extracts the version number from the SCRIPT text. A possible implementation is {@link jarFileVersionExtractor}. */ scriptVersionExtractor: VersionExtractor } @@ -30,6 +59,12 @@ export type VersionExtractor = (adapterScriptText: string) => Result export { jarFileVersionExtractor } from './jarFileVersionExtractor'; export function convertBaseExtension(baseExtension: JavaBaseExtension): ExasolExtension { + function verifyVersion(version: string) { + if (baseExtension.version !== version) { + throw new NotFoundError(`Version '${version}' not supported, can only use '${baseExtension.version}'.`) + } + } + return { name: baseExtension.name, description: baseExtension.description, @@ -47,10 +82,12 @@ export function convertBaseExtension(baseExtension: JavaBaseExtension): ExasolEx return findInstallations(metadata.allScripts.rows, baseExtension) }, install(context: Context, version: string): void { - installExtension(context, baseExtension, version) + verifyVersion(version) + installExtension(context, baseExtension) }, uninstall(context: Context, version: string) { - uninstall(context, baseExtension, version) + verifyVersion(version) + uninstall(context, baseExtension) }, upgrade(context: Context) { return upgrade(context, baseExtension) @@ -72,4 +109,3 @@ export function convertBaseExtension(baseExtension: JavaBaseExtension): ExasolEx } } } - diff --git a/src/base/install.test.ts b/src/base/install.test.ts index eafa650..2e81e7b 100644 --- a/src/base/install.test.ts +++ b/src/base/install.test.ts @@ -4,9 +4,6 @@ import { PreconditionFailedError } from '../error'; import { ScriptDefinition, convertBaseExtension } from './index'; import { ContextMock, createMockContext, emptyBaseExtension } from './test-utils'; -function def({ name = "name", type = "SET", args = "args", scriptClass = "script class" }: Partial): ScriptDefinition { - return { name, type, args, scriptClass }; -} describe("install", () => { @@ -27,25 +24,52 @@ describe("install", () => { expect(executeCalls.length).toBe(0) }) - it("single script", () => { - install("v1", [def({ name: "SCRIPT_1", type: "SET", args: "SCRIPT_ARGS", scriptClass: "com.example.Script" })]) + it("creates comment", () => { + install("v1", [{ name: "SCRIPT_1", type: "SET", parameters: "param", emitParameters: "emitParam", scriptClass: "com.example.Script" }]) + const executeCalls = context.mocks.sqlExecute.mock.calls + expect(executeCalls.length).toBe(2) + expect(executeCalls[1][0]).toBe(`COMMENT ON SCRIPT "ext-schema"."SCRIPT_1" IS 'Created by Extension Manager for test-ext v1'`) + }) + + it("creates a set script", () => { + install("v1", [{ name: "SCRIPT_1", type: "SET", parameters: "param", emitParameters: "emitParam", scriptClass: "com.example.Script" }]) const executeCalls = context.mocks.sqlExecute.mock.calls expect(executeCalls.length).toBe(2) - expect(executeCalls[0][0]).toBe(`CREATE OR REPLACE JAVA SET SCRIPT "ext-schema"."SCRIPT_1"(...) EMITS (SCRIPT_ARGS) AS + expect(executeCalls[0][0]).toBe(`CREATE OR REPLACE JAVA SET SCRIPT "ext-schema"."SCRIPT_1"(param) EMITS (emitParam) AS + %scriptclass com.example.Script; + %jar /bucketfs/test-ext.jar;`) + }) + + it("creates a scalar script", () => { + install("v1", [{ name: "SCRIPT_1", type: "SCALAR", parameters: "...", emitParameters: "emitArgs", scriptClass: "com.example.Script" }]) + const executeCalls = context.mocks.sqlExecute.mock.calls + expect(executeCalls.length).toBe(2) + + expect(executeCalls[0][0]).toBe(`CREATE OR REPLACE JAVA SCALAR SCRIPT "ext-schema"."SCRIPT_1"(...) EMITS (emitArgs) AS + %scriptclass com.example.Script; + %jar /bucketfs/test-ext.jar;`) + }) + + + it("creates an adapter script", () => { + install("v1", [{ name: "SCRIPT_1", type: "ADAPTER", scriptClass: "com.example.Script" }]) + const executeCalls = context.mocks.sqlExecute.mock.calls + expect(executeCalls.length).toBe(2) + + expect(executeCalls[0][0]).toBe(`CREATE OR REPLACE JAVA ADAPTER SCRIPT "ext-schema"."SCRIPT_1" AS %scriptclass com.example.Script; %jar /bucketfs/test-ext.jar;`) - expect(executeCalls[1][0]).toBe(`COMMENT ON SCRIPT "ext-schema"."SCRIPT_1" IS 'Created by Extension Manager for test-ext v1'`) }) it("two scripts", () => { install("v1", [ - def({ name: "SCRIPT_1", type: "SET", args: "SCRIPT_ARGS", scriptClass: "com.example.Script1" }), - def({ name: "SCRIPT_2", type: "SCALAR", args: "...", scriptClass: "com.example.Script2" })]) + { name: "SCRIPT_1", type: "SET", parameters: "param", emitParameters: "emitParam", scriptClass: "com.example.Script1" }, + { name: "SCRIPT_2", type: "SCALAR", parameters: "...", emitParameters: "...", scriptClass: "com.example.Script2" }]) const executeCalls = context.mocks.sqlExecute.mock.calls expect(executeCalls.length).toBe(4) - expect(executeCalls[0][0]).toBe(`CREATE OR REPLACE JAVA SET SCRIPT "ext-schema"."SCRIPT_1"(...) EMITS (SCRIPT_ARGS) AS + expect(executeCalls[0][0]).toBe(`CREATE OR REPLACE JAVA SET SCRIPT "ext-schema"."SCRIPT_1"(param) EMITS (emitParam) AS %scriptclass com.example.Script1; %jar /bucketfs/test-ext.jar;`) expect(executeCalls[1][0]).toBe(`CREATE OR REPLACE JAVA SCALAR SCRIPT "ext-schema"."SCRIPT_2"(...) EMITS (...) AS @@ -57,6 +81,6 @@ describe("install", () => { it("fails for wrong version", () => { expect(() => { install("wrongVersion") }) - .toThrowError(new PreconditionFailedError(`Installing version 'wrongVersion' not supported, try 'v1'.`)) + .toThrowError(new PreconditionFailedError(`Version 'wrongVersion' not supported, can only use 'v1'.`)) }) }) diff --git a/src/base/install.ts b/src/base/install.ts index 845ea9f..84af339 100644 --- a/src/base/install.ts +++ b/src/base/install.ts @@ -1,27 +1,28 @@ import { JavaBaseExtension, ScriptDefinition } from "."; import { Context } from "../context"; -import { BadRequestError } from "../error"; - -export function installExtension(context: Context, extension: JavaBaseExtension, versionToInstall: string): void { - if (extension.version !== versionToInstall) { - throw new BadRequestError(`Installing version '${versionToInstall}' not supported, try '${extension.version}'.`); - } +export function installExtension(context: Context, extension: JavaBaseExtension): void { const jarPath = context.bucketFs.resolvePath(extension.file.name); - function qualifiedName(script: ScriptDefinition) { return `"${context.extensionSchemaName}"."${script.name}"` } function createScript(script: ScriptDefinition): string { - return `CREATE OR REPLACE JAVA ${script.type} SCRIPT ${qualifiedName(script)}(...) EMITS (${script.args}) AS - %scriptclass ${script.scriptClass}; - %jar ${jarPath};`; + let stmt = `CREATE OR REPLACE JAVA ${script.type} SCRIPT ${qualifiedName(script)}` + if (script.type == "SCALAR" || script.type == "SET") { + stmt += `(${script.parameters}) EMITS (${script.emitParameters})` + } + stmt += " AS\n" + stmt += ` %scriptclass ${script.scriptClass};\n` + stmt += ` %jar ${jarPath};` + return stmt } + function createComment(script: ScriptDefinition): string { return `COMMENT ON SCRIPT ${qualifiedName(script)} IS 'Created by Extension Manager for ${extension.name} ${extension.version}'`; } + console.log(`Installing extension '${extension.name}' in version ${extension.version} with ${extension.scripts.length} scripts...`) extension.scripts.forEach(script => context.sqlClient.execute(createScript(script))); extension.scripts.forEach(script => context.sqlClient.execute(createComment(script))); } diff --git a/src/base/readInstanceParameterValues.test.ts b/src/base/readInstanceParameterValues.test.ts new file mode 100644 index 0000000..aa74025 --- /dev/null +++ b/src/base/readInstanceParameterValues.test.ts @@ -0,0 +1,21 @@ + +import { describe, expect, it } from '@jest/globals'; +import { convertBaseExtension } from '.'; +import { ParameterValues } from '../api'; +import { PreconditionFailedError } from '../error'; +import { createMockContext, emptyBaseExtension } from './test-utils'; + +function readInstanceParameterValues(): ParameterValues { + const baseExtension = emptyBaseExtension() + baseExtension.name = "testing-extension" + const installations = convertBaseExtension(baseExtension).readInstanceParameterValues(createMockContext(), "version", "instanceId") + expect(installations).toBeDefined() + return installations +} + +describe("readInstanceParameterValues", () => { + it("not supported", () => { + expect(() => readInstanceParameterValues()) + .toThrowError(new PreconditionFailedError("Reading instance parameter values not supported")) + }) +}) diff --git a/src/base/test-utils.ts b/src/base/test-utils.ts index ddef372..2d57bff 100644 --- a/src/base/test-utils.ts +++ b/src/base/test-utils.ts @@ -33,7 +33,7 @@ export function createMockContext(): ContextMock { sqlClient, bucketFs: { resolvePath(fileName: string) { - return "/bucketfs/" + fileName; + return `/bucketfs/${fileName}` }, }, metadata: { @@ -64,4 +64,4 @@ export function emptyBaseExtension(): JavaBaseExtension { scripts: [], scriptVersionExtractor: () => successResult("dummy version"), } -} \ No newline at end of file +} diff --git a/src/base/uninstall.test.ts b/src/base/uninstall.test.ts index 480d4ce..72aa630 100644 --- a/src/base/uninstall.test.ts +++ b/src/base/uninstall.test.ts @@ -1,11 +1,15 @@ import { beforeEach, describe, expect, it } from '@jest/globals'; import { PreconditionFailedError } from '../error'; -import { ScriptDefinition, convertBaseExtension } from './index'; +import { AdapterScriptDefinition, ScalarSetScriptDefinition, ScriptDefinition, convertBaseExtension } from './index'; import { ContextMock, createMockContext, emptyBaseExtension } from './test-utils'; -function def({ name = "name", type = "SET", args = "args", scriptClass = "script class" }: Partial): ScriptDefinition { - return { name, type, args, scriptClass }; +function def({ name = "name", type = "SET", parameters = "param", emitParameters = "emitParam", scriptClass = "script class" }: Partial): ScriptDefinition { + return { name, type, parameters, emitParameters, scriptClass }; +} + +function adapterDef({ name = "name", scriptClass = "script class" }: Partial): ScriptDefinition { + return { name, type: 'ADAPTER', scriptClass }; } describe("uninstall", () => { @@ -53,12 +57,20 @@ describe("uninstall", () => { expect(executeCalls.length).toBe(0) }) - it("executes expected statements for single script", () => { - simulateSchemaExists() - uninstall("v1", [def({ name: "SCRIPT1" })]) - const calls = context.mocks.sqlExecute.mock.calls - expect(calls.length).toEqual(1) - expect(calls[0]).toEqual([`DROP SCRIPT "ext-schema"."SCRIPT1"`]) + describe("executes expected statements", () => { + const tests: { name: string, script: ScriptDefinition, expectedDropStatement: string }[] = [ + { name: "set script", script: def({ name: "SCRIPT1", type: "SET" }), expectedDropStatement: `DROP SCRIPT "ext-schema"."SCRIPT1"` }, + { name: "scalar script", script: def({ name: "SCRIPT1", type: "SCALAR" }), expectedDropStatement: `DROP SCRIPT "ext-schema"."SCRIPT1"` }, + { name: "adapter script", script: adapterDef({ name: "SCRIPT1" }), expectedDropStatement: `DROP ADAPTER SCRIPT "ext-schema"."SCRIPT1"` }, + ] + + tests.forEach(test => it(test.name, () => { + simulateSchemaExists() + uninstall("v1", [test.script]) + const calls = context.mocks.sqlExecute.mock.calls + expect(calls.length).toEqual(1) + expect(calls[0]).toEqual([test.expectedDropStatement]) + })) }) it("executes expected statements for two scripts", () => { @@ -72,6 +84,6 @@ describe("uninstall", () => { it("fails for wrong version", () => { expect(() => { uninstall("wrongVersion") }) - .toThrowError(new PreconditionFailedError(`Uninstalling version 'wrongVersion' not supported, try 'v1'.`)) + .toThrowError(new PreconditionFailedError(`Version 'wrongVersion' not supported, can only use 'v1'.`)) }) }) diff --git a/src/base/uninstall.ts b/src/base/uninstall.ts index bf05d0c..2020cdf 100644 --- a/src/base/uninstall.ts +++ b/src/base/uninstall.ts @@ -1,13 +1,7 @@ import { JavaBaseExtension, ScriptDefinition } from "."; import { Context } from "../context"; -import { NotFoundError } from "../error"; - - -export function uninstall(context: Context, extension: JavaBaseExtension, versionToUninstall: string): void { - if (extension.version !== versionToUninstall) { - throw new NotFoundError(`Uninstalling version '${versionToUninstall}' not supported, try '${extension.version}'.`) - } +export function uninstall(context: Context, extension: JavaBaseExtension): void { function extensionSchemaExists(): boolean { const result = context.sqlClient.query("SELECT 1 FROM SYS.EXA_ALL_SCHEMAS WHERE SCHEMA_NAME=?", context.extensionSchemaName) return result.rows.length > 0 @@ -17,9 +11,17 @@ export function uninstall(context: Context, extension: JavaBaseExtension, versio return `"${context.extensionSchemaName}"."${script.name}"` } + function getDropScriptStatement(script: ScriptDefinition): string { + if (script.type === "ADAPTER") { + return `DROP ADAPTER SCRIPT ${qualifiedName(script)}` + } else { + return `DROP SCRIPT ${qualifiedName(script)}` + } + } + if (extensionSchemaExists()) { // Drop commands fail when schema does not exist. extension.scripts.forEach(script => - context.sqlClient.execute(`DROP SCRIPT ${qualifiedName(script)}`) + context.sqlClient.execute(getDropScriptStatement(script)) ); } } diff --git a/src/base/upgrade.test.ts b/src/base/upgrade.test.ts index d343658..c02bdd0 100644 --- a/src/base/upgrade.test.ts +++ b/src/base/upgrade.test.ts @@ -3,11 +3,11 @@ import { beforeEach, describe, expect, it } from '@jest/globals'; import { PreconditionFailedError } from '../error'; import { ExaScriptsRow } from '../exasolSchema'; import { successResult } from './common'; -import { ScriptDefinition, VersionExtractor, convertBaseExtension } from './index'; +import { ScalarSetScriptDefinition, ScriptDefinition, VersionExtractor, convertBaseExtension } from './index'; import { ContextMock, createMockContext, emptyBaseExtension } from './test-utils'; -function def({ name = "name", type = "SET", args = "args", scriptClass = "script class" }: Partial): ScriptDefinition { - return { name, type, args, scriptClass }; +function def({ name = "name", type = "SET", parameters = "param", emitParameters = "emitParam", scriptClass = "script class" }: Partial): ScriptDefinition { + return { name, type, parameters, emitParameters, scriptClass }; } function script({ schema = "schema", name = "name", inputType, resultType = "EMITS", type = "UDF", text = "", comment }: Partial): ExaScriptsRow { @@ -63,7 +63,7 @@ describe("upgrade", () => { const executeCalls = context.mocks.sqlExecute.mock.calls expect(executeCalls.length).toBe(2) - expect(executeCalls[0][0]).toBe(`CREATE OR REPLACE JAVA SET SCRIPT "ext-schema"."SCRIPT_1"(...) EMITS (args) AS + expect(executeCalls[0][0]).toBe(`CREATE OR REPLACE JAVA SET SCRIPT "ext-schema"."SCRIPT_1"(param) EMITS (emitParam) AS %scriptclass com.example.Script; %jar /bucketfs/test-ext.jar;`) expect(executeCalls[1][0]).toBe(`COMMENT ON SCRIPT "ext-schema"."SCRIPT_1" IS 'Created by Extension Manager for test-ext v1'`) diff --git a/src/base/upgrade.ts b/src/base/upgrade.ts index 6ebe50a..ced7f07 100644 --- a/src/base/upgrade.ts +++ b/src/base/upgrade.ts @@ -31,6 +31,6 @@ export function upgrade(context: Context, extension: JavaBaseExtension): Upgrade if (previousVersion.result === newVersion) { throw new PreconditionFailedError(`Extension is already installed in latest version ${newVersion}`) } - installExtension(context, extension, newVersion) + installExtension(context, extension) return { previousVersion: previousVersion.result, newVersion }; } diff --git a/src/base/validateScripts.test.ts b/src/base/validateScripts.test.ts index 448a68f..9cf1b55 100644 --- a/src/base/validateScripts.test.ts +++ b/src/base/validateScripts.test.ts @@ -3,15 +3,15 @@ import { describe, expect, it } from '@jest/globals'; import { ExaScriptsRow } from '../exasolSchema'; import { AdapterScript } from './adapterScript'; import { failureResult, successResult } from './common'; -import { ScriptDefinition, VersionExtractor } from './index'; +import { ScalarSetScriptDefinition, ScriptDefinition, VersionExtractor } from './index'; import { InstalledScripts, validateInstalledScripts, validateVersions } from './validateScripts'; function script({ schema = "schema", name = "name", inputType, resultType = "EMITS", type = "UDF", text = "", comment }: Partial): ExaScriptsRow { return { schema, name, inputType, resultType, type, text, comment } } -function def({ name = "name", type = "SET", args = "args", scriptClass = "script class" }: Partial): ScriptDefinition { - return { name: name, type, args, scriptClass }; +function def({ name = "name", type = "SET", parameters = "param", emitParameters = "emitParam", scriptClass = "script class" }: Partial): ScriptDefinition { + return { name, type, parameters, emitParameters, scriptClass }; } function scriptWithVersion(name: string, version: string): AdapterScript { @@ -91,4 +91,4 @@ describe("validateScripts", () => { }) }) }) -}) \ No newline at end of file +}) diff --git a/src/parameters.ts b/src/parameters.ts index 5a69086..db00430 100644 --- a/src/parameters.ts +++ b/src/parameters.ts @@ -2,8 +2,8 @@ /** * The definition of a parameter. */ - export type Parameter = StringParameter | SelectParameter | BooleanParameter; - +export type Parameter = StringParameter | SelectParameter | BooleanParameter; + /** * Abstract base interface with common fields for parameter definitions. */ @@ -12,6 +12,8 @@ interface BaseParameter { id: string /** The name displayed to the user */ name: string + /** Detailed description displayed to the user e.g. in a tooltip */ + description?: string /** The type of the parameter */ type: string /** Defines if a value must be specified (`true`) or if it is optional (`false`). Default: `false`. */ @@ -19,6 +21,7 @@ interface BaseParameter { /** An optional condition */ condition?: Condition default?: string + /** Short hint that describes the expected value of the input field */ placeholder?: string readOnly?: boolean }