diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..d044295 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,4 @@ +.idea +.vscode +node_modules +release diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..f61c3ea --- /dev/null +++ b/.editorconfig @@ -0,0 +1,15 @@ +root = true + +[*] +indent_style = tab +indent_size = 4 +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true + +[*.md] +trim_trailing_whitespace = false + +[*.{sql,cql,hql,file,txt}] +insert_final_newline = false diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 9a656ee..5999623 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -8,4 +8,4 @@ You have no Jira task for this PR? Describe your changes here... You feel the need to provide technical explanations? You can do it here... -... \ No newline at end of file +... diff --git a/.github/workflows/jira_link_to_pr.yml b/.github/workflows/jira_link_to_pr.yml index 53efb67..10b8524 100644 --- a/.github/workflows/jira_link_to_pr.yml +++ b/.github/workflows/jira_link_to_pr.yml @@ -5,19 +5,19 @@ # follow the pattern: : name: jira-description-action on: - pull_request: - types: [opened, reopened] + pull_request: + types: [opened, reopened] jobs: - add-jira-description: - runs-on: ubuntu-latest - steps: - - uses: cakeinpanic/jira-description-action@v0.9.0 - name: jira-description-action - with: - github-token: ${{ secrets.GITHUB_TOKEN }} - jira-token: ${{ secrets.JIRA_TOKEN }} - jira-base-url: https://hackolade.atlassian.net - skip-branches: '^(develop|main|master)$' #optional - jira-project-key: 'HCK' #optional - use: 'both' - fail-when-jira-issue-not-found: false + add-jira-description: + runs-on: ubuntu-latest + steps: + - uses: cakeinpanic/jira-description-action@v0.9.0 + name: jira-description-action + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + jira-token: ${{ secrets.JIRA_TOKEN }} + jira-base-url: https://hackolade.atlassian.net + skip-branches: '^(develop|main|master)$' #optional + jira-project-key: 'HCK' #optional + use: 'both' + fail-when-jira-issue-not-found: false diff --git a/.github/workflows/notif-push-to-slack.yml b/.github/workflows/notif-push-to-slack.yml index 61315cd..db6972a 100644 --- a/.github/workflows/notif-push-to-slack.yml +++ b/.github/workflows/notif-push-to-slack.yml @@ -1,34 +1,34 @@ #https://docs.github.com/en/webhooks/webhook-events-and-payloads#push name: notify-push on: - push: - branches: - - develop + push: + branches: + - develop jobs: - notify: - runs-on: ubuntu-latest - env: - AUTHOR: ${{ github.event.pusher.name }} - COMMIT_PUSH_SOURCE: ${{ secrets.COMMIT_PUSH_SOURCE }} - steps: - - name: notify slack - id: slack - if: ${{ env.AUTHOR == env.COMMIT_PUSH_SOURCE }} - uses: slackapi/slack-github-action@v1.26.0 - with: - channel-id: 'develop-direct-pushes' - payload: | - { - "text": " ${{ github.event.head_commit.url }}", - "blocks": [ - { - "type": "section", - "text": { - "type": "mrkdwn", - "text": "Push: ${{ github.event.head_commit.url }}" - } - } - ] - } + notify: + runs-on: ubuntu-latest env: - SLACK_BOT_TOKEN: ${{ secrets.SLACK_BOT_TOKEN }} \ No newline at end of file + AUTHOR: ${{ github.event.pusher.name }} + COMMIT_PUSH_SOURCE: ${{ secrets.COMMIT_PUSH_SOURCE }} + steps: + - name: notify slack + id: slack + if: ${{ env.AUTHOR == env.COMMIT_PUSH_SOURCE }} + uses: slackapi/slack-github-action@v1.26.0 + with: + channel-id: 'develop-direct-pushes' + payload: | + { + "text": " ${{ github.event.head_commit.url }}", + "blocks": [ + { + "type": "section", + "text": { + "type": "mrkdwn", + "text": "Push: ${{ github.event.head_commit.url }}" + } + } + ] + } + env: + SLACK_BOT_TOKEN: ${{ secrets.SLACK_BOT_TOKEN }} diff --git a/.github/workflows/trigger-pr-tests-plugins.yml b/.github/workflows/trigger-pr-tests-plugins.yml index 3874282..a817768 100644 --- a/.github/workflows/trigger-pr-tests-plugins.yml +++ b/.github/workflows/trigger-pr-tests-plugins.yml @@ -1,19 +1,19 @@ name: Trigger PR tests (Plugins) on: - pull_request: - types: [auto_merge_enabled] + pull_request: + types: [auto_merge_enabled] jobs: - trigger-pr-tests-plugins: - name: Trigger PR tests (Plugins) - runs-on: ubuntu-latest - steps: - - name: Call TeamCity API endpoint - run: | - curl \ - -X POST \ - -H 'Authorization: Bearer ${{ secrets.TEAMCITY_TRIGGER_TESTS_TOKEN }}' \ - -H 'Content-Type: application/json' \ - -d '{"branchName": "pull/${{ github.event.number }}", "buildType": {"id": "${{ vars.TEAMCITY_BUILD_ID_FOR_TESTING_PLUGIN_PR }}"}, "properties": { "property": [ {"name": "BUILD_BRANCH", "value": "${{ github.event.pull_request.head.ref }}" }] }}' \ - ${{ vars.TEAMCITY_API_URL }}/buildQueue + trigger-pr-tests-plugins: + name: Trigger PR tests (Plugins) + runs-on: ubuntu-latest + steps: + - name: Call TeamCity API endpoint + run: | + curl \ + -X POST \ + -H 'Authorization: Bearer ${{ secrets.TEAMCITY_TRIGGER_TESTS_TOKEN }}' \ + -H 'Content-Type: application/json' \ + -d '{"branchName": "pull/${{ github.event.number }}", "buildType": {"id": "${{ vars.TEAMCITY_BUILD_ID_FOR_TESTING_PLUGIN_PR }}"}, "properties": { "property": [ {"name": "BUILD_BRANCH", "value": "${{ github.event.pull_request.head.ref }}" }] }}' \ + ${{ vars.TEAMCITY_API_URL }}/buildQueue diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..44b6064 --- /dev/null +++ b/.gitignore @@ -0,0 +1,9 @@ +.vscode/* +!.vscode/settings.json +!.vscode/extensions.json +.antlr +.idea +node_modules +.DS_Store +release +tscDist diff --git a/.npmrc b/.npmrc new file mode 100644 index 0000000..cffe8cd --- /dev/null +++ b/.npmrc @@ -0,0 +1 @@ +save-exact=true diff --git a/.oxfmtrc.json b/.oxfmtrc.json new file mode 100644 index 0000000..ec373ac --- /dev/null +++ b/.oxfmtrc.json @@ -0,0 +1,32 @@ +{ + "$schema": "./node_modules/oxfmt/configuration_schema.json", + "printWidth": 120, + "tabWidth": 4, + "useTabs": true, + "semi": true, + "singleQuote": true, + "jsxSingleQuote": false, + "quoteProps": "preserve", + "trailingComma": "all", + "bracketSpacing": true, + "bracketSameLine": false, + "arrowParens": "avoid", + "endOfLine": "lf", + "jsdoc": true, + "sortPackageJson": false, + "sortImports": { + "ignoreCase": true, + "newlinesBetween": true, + "groups": ["builtin", "external", ["parent", "sibling", "index"]] + }, + "ignorePatterns": [ + ".git", + ".vscode", + ".idea", + ".sonarlint", + "release", + "node_modules", + "package.json", + "package-lock.json" + ] +} diff --git a/.oxlintrc.json b/.oxlintrc.json new file mode 100644 index 0000000..18a6340 --- /dev/null +++ b/.oxlintrc.json @@ -0,0 +1,62 @@ +{ + "$schema": "./node_modules/oxlint/configuration_schema.json", + "plugins": ["eslint", "typescript", "unicorn", "oxc", "jsdoc"], + "jsPlugins": [ + { + "name": "jsdoc-js", + "specifier": "eslint-plugin-jsdoc" + } + ], + "categories": { + "correctness": "error", + "suspicious": "error", + "perf": "error", + "pedantic": "warn" + }, + "rules": { + "eslint/no-inline-comments": "off", + "max-classes-per-file": "off", + "max-lines": "off", + "max-lines-per-function": "off", + "no-bitwise": "warn", + "require-await": "off", + "typescript/prefer-readonly-parameter-types": "off", + "typescript/strict-boolean-expressions": "off", + "typescript/no-unsafe-assignment": "error", + "unicorn/require-module-specifiers": "off", + "unicorn/prefer-top-level-await": "off", + "unicorn/prefer-module": "off", + "jsdoc-js/require-jsdoc": [ + "error", + { + "require": { + "FunctionDeclaration": true, + "MethodDefinition": true, + "ClassDeclaration": true, + "ArrowFunctionExpression": true, + "FunctionExpression": true + } + } + ], + "jsdoc-js/check-param-names": "error", + "jsdoc-js/check-types": "error", + "jsdoc-js/no-undefined-types": "error", + "jsdoc-js/require-returns-check": "error", + "jsdoc/require-param": "error", + "jsdoc/require-param-name": "error", + "jsdoc/require-param-type": "error", + "jsdoc/require-returns": "error", + "jsdoc/require-returns-type": "error" + }, + "ignorePatterns": [ + "**/*.config.*", + "**/*.d.ts", + ".git", + ".idea", + ".vscode", + "build", + "node_modules", + "out/**/*", + "release" + ] +} diff --git a/.sonarcloud.properties b/.sonarcloud.properties new file mode 100644 index 0000000..cf867fb --- /dev/null +++ b/.sonarcloud.properties @@ -0,0 +1,4 @@ +# IMPORTANT: DO NOT MODIFY THIS FILE! +# We configure SonarCloud through its web UI, not through this file. The property `sonar.tests` is an exception because +# there is no UI for it. See documentation here: https://docs.sonarsource.com/sonarcloud/advanced-setup/analysis-scope/ +#sonar.tests=test diff --git a/.sonarlint/connectedMode.json b/.sonarlint/connectedMode.json new file mode 100644 index 0000000..76f2d2e --- /dev/null +++ b/.sonarlint/connectedMode.json @@ -0,0 +1 @@ +{ "sonarCloudOrganization": "hck", "projectKey": "hackolade_Db2-zOS" } diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 0000000..8dfdf34 --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,8 @@ +{ + "recommendations": [ + "oxc.oxc-vscode", + "sonarsource.sonarlint-vscode", + "streetsidesoftware.code-spell-checker", + "vitest.explorer" + ] +} diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..aed3b78 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,11 @@ +{ + "editor.formatOnSave": true, + "editor.formatOnSaveMode": "file", + "editor.defaultFormatter": "oxc.oxc-vscode", + "oxc.enable": true, + "oxc.typeAware": false, + "editor.codeActionsOnSave": { + "source.fixAll.oxlint": "always" + }, + "cSpell.words": ["jridgewell", "oxfmt", "oxlint", "tsgolint"] +} diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..261eeb9 --- /dev/null +++ b/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/README.md b/README.md index 763c535..b78d7bb 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,9 @@ # Db2-zOS -Plugin to enable Db2 zOS (Mainframe) database as a target in [Hackolade](https://hackolade.com) data modeling. Requires prior download of the Hackolade application from our [download page](https://hackolade.com/download.html) +Plugin to enable Db2 zOS (Mainframe) database as a target in [Hackolade](https://hackolade.com) data modeling. Requires prior download of the Hackolade application from our [download page](https://hackolade.com/download.html) -Hackolade exposes its core data modeling engine through a plugin architecture. Each plugin applies the Hackolade data modeling capabilities to a specific target technology, whether for data-at-rest (databases) or data-in-motion (communications.) Each plugin matches the specific aspects of the target in terms of terminology, storage model, data types, and communication protocol. +Hackolade exposes its core data modeling engine through a plugin architecture. Each plugin applies the Hackolade data modeling capabilities to a specific target technology, whether for data-at-rest (databases) or data-in-motion (communications.) Each plugin matches the specific aspects of the target in terms of terminology, storage model, data types, and communication protocol. -To enable data modeling capabilities for a target, you must first download and install the plugin, following these [instructions](https://hackolade.com/help/DownloadadditionalDBtargetplugin.html "Plugin download instructions"). +To enable data modeling capabilities for a target, you must first download and install the plugin, following these [instructions](https://hackolade.com/help/DownloadadditionalDBtargetplugin.html 'Plugin download instructions'). -Plugins can be customized by following these [instructions](https://hackolade.com/help/Userdefinedcustomproperties.html "Plugin customization instructions"). +Plugins can be customized by following these [instructions](https://hackolade.com/help/Userdefinedcustomproperties.html 'Plugin customization instructions'). diff --git a/buildConstants.js b/buildConstants.js new file mode 100644 index 0000000..c8061cf --- /dev/null +++ b/buildConstants.js @@ -0,0 +1,32 @@ +/** @type {typeof import('node:path')} */ +const path = require('path'); + +const DEFAULT_RELEASE_FOLDER_PATH = path.resolve(__dirname, 'release'); + +const EXCLUDED_EXTENSIONS = ['.js', '.g4', '.interp', '.tokens', '.d.ts']; +const EXCLUDED_FILES = [ + '.github', + '.DS_Store', + '.editorconfig', + '.git', + '.gitignore', + '.vscode', + '.idea', + '.dockerignore', + '.oxlintrc.json', + '.oxfmtrc.json', + '.sonarlint', + '.sonarcloud.properties', + 'tsconfig.json', + 'build', + 'release', + 'node_modules', + 'lint-staged.config.js', + 'scripts', +]; + +module.exports = { + DEFAULT_RELEASE_FOLDER_PATH, + EXCLUDED_EXTENSIONS, + EXCLUDED_FILES, +}; diff --git a/central_pane/dtdAbbreviation.json b/central_pane/dtdAbbreviation.json new file mode 100644 index 0000000..685b3b8 --- /dev/null +++ b/central_pane/dtdAbbreviation.json @@ -0,0 +1,9 @@ +{ + "map": "{...}", + "list": "[...]", + "string": "{ABC}", + "number": "{123}", + "bool": "{0/1}", + "bytes": "{BYTES}", + "null": "{null}" +} diff --git a/central_pane/style.json b/central_pane/style.json new file mode 100644 index 0000000..40e0e32 --- /dev/null +++ b/central_pane/style.json @@ -0,0 +1,57 @@ +{ + "entity": { + "erd": { + "width": 300 + } + }, + "field": { + "erd": [ + "keys", + "type", + [ + { + "value": { + "template": "[{array_size_limit}]", + "defaultValue": "x", + "key": "array_type.*.array_size_limit" + }, + "dependency": { + "type": "or", + "values": [ + { + "key": "array_type", + "minLength": 1 + }, + { + "key": "array_size_limit", + "exist": true + }, + { + "level": "definition", + "key": "array_type", + "minLength": 1 + }, + { + "level": "definition", + "key": "array_size_limit", + "exist": true + } + ] + }, + "width": "auto" + } + ], + "indexes", + { + "value": "AK", + "orderingNumbersBy": ["uniqueKey", "compositeUniqueKey"], + "dependency": { + "key": "alternateKey", + "value": true + }, + "width": 16 + }, + "refType" + ] + } +} diff --git a/esbuild.package.js b/esbuild.package.js new file mode 100644 index 0000000..e5109bd --- /dev/null +++ b/esbuild.package.js @@ -0,0 +1,93 @@ +/** @type {typeof import('node:fs')} */ +const fs = require('fs'); + +/** @type {typeof import('node:path')} */ +const path = require('path'); + +/** @type {typeof import('@hackolade/hck-esbuild-plugins-pack')} */ +const { copyFolderFiles, addReleaseFlag } = require('@hackolade/hck-esbuild-plugins-pack'); + +/** @type {typeof import('esbuild')} */ +const esbuild = require('esbuild'); + +/** @type {typeof import('esbuild-plugin-clean')} */ +const { clean } = require('esbuild-plugin-clean'); + +/** @type {typeof import('esbuild-plugin-copy')} */ +const { copy } = require('esbuild-plugin-copy'); + +/** @type {typeof import('./buildConstants')} */ +const { EXCLUDED_EXTENSIONS, EXCLUDED_FILES, DEFAULT_RELEASE_FOLDER_PATH } = require('./buildConstants'); + +/** @type {typeof import('./scripts/lib/commandOptions')} */ +const { getCommandOption, parseBooleanOption, readCommandOptions } = require('./scripts/lib/commandOptions'); + +/** + * Packages the plugin into the release folder. + * + * @returns {Promise} Resolves when the esbuild packaging step finishes. + */ +async function packagePlugin() { + const { default: packageData } = await import('./package.json', { + with: { type: 'json' }, + }); + const RELEASE_FOLDER_PATH = path.join(DEFAULT_RELEASE_FOLDER_PATH, `${packageData.name}-${packageData.version}`); + const commandOptions = readCommandOptions(process.argv.slice(2), ['write']); + const write = getCommandOption(commandOptions, 'write', { + defaultValue: true, + parse: parseBooleanOption, + }); + + /** + * Checks whether a packaging entry point exists on disk. + * + * @param {string} entryPoint Absolute path to a potential entry point. + * @returns {boolean} `true` when the entry point file exists. + */ + const entryPointExists = entryPoint => fs.existsSync(entryPoint); + + /** @type {string[]} */ + const entryPoints = [ + path.resolve(__dirname, 'forward_engineering', 'api.js'), + path.resolve(__dirname, 'api', 'fe.js'), + path.resolve(__dirname, 'forward_engineering', 'ddlProvider.js'), + path.resolve(__dirname, 'reverse_engineering', 'api.js'), + ].filter(entryPoint => entryPointExists(entryPoint)); + + await esbuild.build({ + entryPoints, + bundle: true, + keepNames: true, + platform: 'node', + target: 'node24', + outdir: RELEASE_FOLDER_PATH, + write, + minify: true, + logLevel: 'info', + plugins: write + ? [ + clean({ + patterns: [DEFAULT_RELEASE_FOLDER_PATH], + }), + copy({ + assets: { + from: [path.join('node_modules', 'lodash', '**', '*')], + to: [path.join('node_modules', 'lodash')], + }, + }), + copyFolderFiles({ + fromPath: __dirname, + targetFolderPath: RELEASE_FOLDER_PATH, + excludedExtensions: EXCLUDED_EXTENSIONS, + excludedFiles: EXCLUDED_FILES, + }), + addReleaseFlag(path.resolve(RELEASE_FOLDER_PATH, 'package.json')), + ] + : [], + }); +} + +packagePlugin().catch(error => { + console.error(error); + process.exitCode = 1; +}); diff --git a/forward_engineering/alterScript/alterScriptBuilder.js b/forward_engineering/alterScript/alterScriptBuilder.js new file mode 100644 index 0000000..6949768 --- /dev/null +++ b/forward_engineering/alterScript/alterScriptBuilder.js @@ -0,0 +1,123 @@ +/** + * @import { + * AlterScriptData, + * AlterScriptDto + * } from '../types/alterScript' + * @import {App} from '../types/ddlProvider' + */ + +const { commentIfDeactivated } = require('../utils/general'); +const { getAlterScriptDtos } = require('./alterScriptFromDeltaHelper'); + +/** + * Join alter script DTOs into a single script. Deactivated statements are always commented out, and drop statements are + * commented out as well unless the user opted into applying them. + * + * @param {AlterScriptDto[]} dtos Alter script DTOs. + * @param {boolean} shouldApplyDropStatements Whether drop statements should be applied. + * @returns {string} Alter script. + */ +const joinAlterScriptDtosIntoScript = (dtos, shouldApplyDropStatements) => { + return dtos + .flatMap(dto => + dto.scripts.map(scriptDto => { + if (dto.isActivated === false) { + return commentIfDeactivated(scriptDto.script, { isActivated: false, isPartOfLine: false }); + } + + if (shouldApplyDropStatements) { + return scriptDto.script; + } + + return commentIfDeactivated(scriptDto.script, { + isActivated: !scriptDto.isDropScript, + isPartOfLine: false, + }); + }), + ) + .map(scriptLine => scriptLine.trim()) + .filter(Boolean) + .join('\n\n'); +}; + +/** + * Check whether the user opted into applying drop statements. + * + * @param {AlterScriptData} data FE data. + * @returns {boolean} Whether drop statements should be applied. + */ +const shouldApplyDropStatements = data => { + return Boolean( + data.options?.additionalOptions?.some(option => option.id === 'applyDropStatements' && option.value), + ); +}; + +/** + * Reshape container-level FE data into the entity-level shape the delta helpers expect. + * + * @param {AlterScriptData} data FE data. + * @returns {AlterScriptData} Prepared FE data. + */ +const mapCoreDataForContainerLevelScripts = data => { + return { ...data, jsonSchema: data.collections?.[0] ?? data.jsonSchema }; +}; + +/** + * Build the entity-level alter script. + * + * @param {AlterScriptData} data FE data. + * @param {App} app App instance. + * @returns {string} Alter script. + */ +const buildEntityLevelAlterScript = (data, app) => { + return joinAlterScriptDtosIntoScript(getAlterScriptDtos(data, app), shouldApplyDropStatements(data)); +}; + +/** + * Build the container-level alter script. + * + * @param {AlterScriptData} data FE data. + * @param {App} app App instance. + * @returns {string} Alter script. + */ +const buildContainerLevelAlterScript = (data, app) => { + const preparedData = mapCoreDataForContainerLevelScripts(data); + + return joinAlterScriptDtosIntoScript( + getAlterScriptDtos(preparedData, app), + shouldApplyDropStatements(preparedData), + ); +}; + +/** + * Check whether the entity-level alter script contains statements that drop objects. + * + * @param {AlterScriptData} data FE data. + * @param {App} app App instance. + * @returns {boolean} Whether the script drops objects. + */ +const doesEntityLevelAlterScriptContainDropStatements = (data, app) => { + return getAlterScriptDtos(data, app).some( + dto => dto.isActivated && dto.scripts.some(scriptDto => scriptDto.isDropScript), + ); +}; + +/** + * Check whether the container-level alter script contains statements that drop objects. + * + * @param {AlterScriptData} data FE data. + * @param {App} app App instance. + * @returns {boolean} Whether the script drops objects. + */ +const doesContainerLevelAlterScriptContainDropStatements = (data, app) => { + return getAlterScriptDtos(mapCoreDataForContainerLevelScripts(data), app).some( + dto => dto.isActivated && dto.scripts.some(scriptDto => scriptDto.isDropScript), + ); +}; + +module.exports = { + doesEntityLevelAlterScriptContainDropStatements, + buildEntityLevelAlterScript, + buildContainerLevelAlterScript, + doesContainerLevelAlterScriptContainDropStatements, +}; diff --git a/forward_engineering/alterScript/alterScriptFromDeltaHelper.js b/forward_engineering/alterScript/alterScriptFromDeltaHelper.js new file mode 100644 index 0000000..43ec656 --- /dev/null +++ b/forward_engineering/alterScript/alterScriptFromDeltaHelper.js @@ -0,0 +1,337 @@ +/** + * @import { + * AlterRelationship, + * AlterScriptData, + * AlterScriptDto, + * AlterTable, + * DeltaBucket, + * DeltaModel, + * DeltaSection + * } from '../types/alterScript' + * @import {App} from '../types/ddlProvider' + */ + +const { getContainersScripts } = require('./alterScriptHelpers/alterContainerHelper'); +const { getModelDefinitionsScripts } = require('./alterScriptHelpers/alterModelDefinitionHelper'); +const { getEntitiesScripts } = require('./alterScriptHelpers/alterEntityHelper'); +const { + getDeleteForeignKeyScriptDtos, + getAddForeignKeyScriptDtos, + getModifyForeignKeyScriptDtos, +} = require('./alterScriptHelpers/alterForeignKeyHelper'); +const { getViewsScripts } = require('./alterScriptHelpers/alterViewHelper'); +const { getAddVersioningScriptDto, getEnableArchiveScriptDto } = require('./alterScriptHelpers/alterVersioningHelper'); +const { getSchemaOfAlterCollection, getSchemaNameFromCollection } = require('../utils/general'); + +/** + * Read the objects of one side of a delta section. The studio serializes a single object as-is and several objects as + * an array, so both shapes have to be handled. + * + * @template T + * @param {DeltaBucket | undefined} bucket Delta bucket. + * @returns {T[]} Objects of the bucket. + */ +const getItems = bucket => { + return [bucket?.items] + .flat() + .filter(item => item !== undefined) + .flatMap(item => Object.values(item.properties)); +}; + +/** + * Read the added, deleted and modified objects of a delta section. + * + * @template T + * @param {DeltaSection | undefined} section Delta section. + * @returns {{ added: T[]; deleted: T[]; modified: T[] }} Objects of the section. + */ +const getSectionItems = section => ({ + added: getItems(section?.properties?.added), + deleted: getItems(section?.properties?.deleted), + modified: getItems(section?.properties?.modified), +}); + +/** + * Build the container statements. Schemas can only be dropped once every table they hold is gone, so the deleted + * containers are reported separately and applied last. + * + * @param {{ collection: DeltaModel; app: App }} params Delta model and app instance. + * @returns {{ deletedContainersScriptDtos: AlterScriptDto[]; upsertedContainersScriptDtos: AlterScriptDto[] }} + * Container alter script DTOs. + */ +const getAlterContainersScriptDtos = ({ collection, app }) => { + const { added, deleted, modified } = getSectionItems(collection.properties?.containers); + const { getAddContainerScriptDto, getDeleteContainerScriptDto, getModifyContainerScriptDto } = + getContainersScripts(app); + + return { + deletedContainersScriptDtos: deleted + .map(container => getDeleteContainerScriptDto(container)) + .filter(scriptDto => scriptDto !== undefined), + upsertedContainersScriptDtos: [ + ...added.map(container => getAddContainerScriptDto(container)), + ...modified.flatMap(container => getModifyContainerScriptDto(container)), + ].filter(scriptDto => scriptDto !== undefined), + }; +}; + +/** + * Build the distinct type statements. Types are created before tables and reported separately when deleted so their + * drop statements can run after every dependent table and view has been removed. + * + * @param {{ collection: DeltaModel; app: App }} params Delta model and app instance. + * @returns {{ deletedTypesScriptDtos: AlterScriptDto[]; upsertedTypesScriptDtos: AlterScriptDto[] }} Type alter script + * DTOs. + */ +const getAlterTypesScriptDtos = ({ collection, app }) => { + const { added, deleted, modified } = getSectionItems(collection.properties?.modelDefinitions); + const { getAddTypeScriptDto, getDeleteTypeScriptDto, getModifyTypeScriptDtos } = getModelDefinitionsScripts(app); + + return { + deletedTypesScriptDtos: deleted + .map(definition => getDeleteTypeScriptDto(definition)) + .filter(scriptDto => scriptDto !== undefined), + upsertedTypesScriptDtos: [ + ...added.map(definition => getAddTypeScriptDto(definition)), + ...modified.flatMap(definition => getModifyTypeScriptDtos(definition)), + ].filter(scriptDto => scriptDto !== undefined), + }; +}; + +/** + * Build a GUID-to-schema lookup table of every entity in the current model/container batch, so a single entity's script + * builder can resolve cross-entity references (e.g. auxiliary base table, LIKE table, history table) that are plain + * GUID-valued fields rather than Studio-resolved ERD relationships. + * + * @param {{ added: AlterTable[]; deleted: AlterTable[]; modified: AlterTable[] }} params Entities of the delta section. + * @returns {Record} Entities keyed by GUID. + */ +const buildRelatedSchemas = ({ added, deleted, modified }) => { + /** @type {Record} */ + const relatedSchemas = {}; + + [...added, ...deleted, ...modified].forEach(item => { + const schema = getSchemaOfAlterCollection(item); + if (schema.id) { + // Merging role onto the delta item (above) overwrites its own compMod, losing keyspaceName, so the schema + // name is resolved from the unmerged item and reattached as bucketName for cross-entity consumers. + relatedSchemas[schema.id] = { ...schema, bucketName: getSchemaNameFromCollection({ collection: item }) }; + } + }); + + return relatedSchemas; +}; + +/** + * Build the table and column statements. + * + * @param {{ + * collection: DeltaModel; + * app: App; + * inlineDeltaRelationships: AlterRelationship[]; + * relatedSchemas: Record; + * }} params + * Delta model, app instance, relationships rendered inline in table definitions and sibling entities keyed by GUID. + * @returns {AlterScriptDto[]} Alter script DTOs. + */ +const getAlterCollectionScriptDtos = ({ collection, app, inlineDeltaRelationships, relatedSchemas }) => { + const { added, deleted, modified } = getSectionItems(collection.properties?.entities); + const { + getAddCollectionScriptDto, + getDeleteCollectionScriptDto, + getModifyCollectionScriptDtos, + getModifyCollectionKeysScriptDtos, + getModifyColumnScriptDtos, + getAddColumnScriptDtos, + getDeleteColumnScriptDtos, + } = getEntitiesScripts(app, inlineDeltaRelationships, relatedSchemas); + + return [ + ...deleted + .filter(item => item.role?.compMod?.deleted) + .map(item => getDeleteCollectionScriptDto(item)) + .filter(scriptDto => scriptDto !== undefined), + ...added + .filter(item => item.role?.compMod?.created) + .map(item => getAddCollectionScriptDto(item)) + .filter(scriptDto => scriptDto !== undefined), + ...deleted.filter(item => !item.role?.compMod?.deleted).flatMap(item => getDeleteColumnScriptDtos(item)), + ...modified.flatMap(item => getModifyCollectionScriptDtos(item)), + // Columns of a created table are already part of its CREATE TABLE, so only existing tables get ADD COLUMN. + ...added.filter(item => !item.role?.compMod?.created).flatMap(item => getAddColumnScriptDtos(item)), + ...modified.flatMap(item => getModifyColumnScriptDtos(item)), + ...modified.flatMap(item => getModifyCollectionKeysScriptDtos(item)), + ]; +}; + +/** + * Build the ALTER TABLE ... ADD VERSIONING and ALTER TABLE ... ENABLE ARCHIVE statements linking a table to its history + * table or archive table, for every added or modified entity. Runs after all entities in the batch are created, so the + * referenced tables already exist by the time it runs. + * + * @param {{ collection: DeltaModel; relatedSchemas: Record }} params Delta model and sibling + * entities keyed by GUID. + * @returns {AlterScriptDto[]} Alter script DTOs. + */ +const getAlterVersioningScriptDtos = ({ collection, relatedSchemas }) => { + const { added, modified } = getSectionItems(collection.properties?.entities); + const entities = [...added, ...modified]; + const addVersioningScriptDto = getAddVersioningScriptDto(relatedSchemas); + const enableArchiveScriptDto = getEnableArchiveScriptDto(relatedSchemas); + + return [ + ...entities.map(item => addVersioningScriptDto(item)), + ...entities.map(item => enableArchiveScriptDto(item)), + ].filter(dto => dto !== undefined); +}; + +/** + * Build the view statements. + * + * @param {{ collection: DeltaModel; app: App }} params Delta model and app instance. + * @returns {AlterScriptDto[]} Alter script DTOs. + */ +const getAlterViewScriptDtos = ({ collection, app }) => { + const { added, deleted, modified } = getSectionItems(collection.properties?.views); + const { getAddViewScriptDto, getDeleteViewScriptDto, getModifyViewScriptDtos } = getViewsScripts(app); + + return [ + ...deleted + .filter(view => view.role?.compMod?.deleted) + .map(view => getDeleteViewScriptDto(view)) + .filter(scriptDto => scriptDto !== undefined), + ...added + .filter(view => view.role?.compMod?.created) + .map(view => getAddViewScriptDto(view)) + .filter(scriptDto => scriptDto !== undefined), + ...modified.flatMap(view => getModifyViewScriptDtos(view)), + ]; +}; + +/** + * Build the foreign key statements, skipping the relationships already rendered inline in a CREATE TABLE. + * + * @param {{ collection: DeltaModel; ignoreRelationshipIDs: string[] }} params Delta model and relationships to skip. + * @returns {AlterScriptDto[]} Alter script DTOs. + */ +const getAlterRelationshipsScriptDtos = ({ collection, ignoreRelationshipIDs }) => { + const { added, deleted, modified } = getSectionItems(collection.properties?.relationships); + const ignoredIds = new Set(ignoreRelationshipIDs); + + return [ + ...getDeleteForeignKeyScriptDtos( + deleted.filter( + relationship => relationship.role.compMod?.deleted && !ignoredIds.has(relationship.role.id ?? ''), + ), + ), + ...getAddForeignKeyScriptDtos( + added.filter( + relationship => relationship.role.compMod?.created && !ignoredIds.has(relationship.role.id ?? ''), + ), + ), + ...getModifyForeignKeyScriptDtos( + modified.filter( + relationship => relationship.role.compMod?.modified && !ignoredIds.has(relationship.role.id ?? ''), + ), + ), + ]; +}; + +/** + * Collect the relationships that belong to newly created tables and therefore end up inside their CREATE TABLE + * statement instead of a separate ALTER TABLE. + * + * @param {{ collection: DeltaModel; options?: AlterScriptData['options'] }} params Delta model and script options. + * @returns {AlterRelationship[]} Inline relationships. + */ +const getInlineRelationships = ({ collection, options }) => { + if (options?.scriptGenerationOptions?.feActiveOptions?.foreignKeys !== 'inline') { + return []; + } + + const addedCollectionIDs = new Set( + getItems(collection.properties?.entities?.properties?.added) + .filter(item => item.role?.compMod?.created) + .map(item => item.role?.id), + ); + + return getItems(collection.properties?.relationships?.properties?.added).filter( + relationship => relationship.role.compMod?.created && addedCollectionIDs.has(relationship.role.childCollection), + ); +}; + +/** + * Drop the empty statements of a DTO, and the DTO itself when nothing is left. + * + * @param {AlterScriptDto} dto Alter script DTO. + * @returns {AlterScriptDto | undefined} Alter script DTO. + */ +const prettifyAlterScriptDto = dto => { + const nonEmptyScripts = dto.scripts + .map(scriptDto => ({ isDropScript: scriptDto.isDropScript, script: scriptDto.script.trim() })) + .filter(scriptDto => Boolean(scriptDto.script)); + + if (nonEmptyScripts.length === 0) { + return void 0; + } + + return { isActivated: dto.isActivated, scripts: nonEmptyScripts }; +}; + +/** + * Parse the delta model the studio serializes into the FE data. This is the single point where the untyped payload + * enters the plugin, so the assertion is kept here instead of letting `any` spread through the helpers. + * + * @param {string} json Serialized delta model. + * @returns {DeltaModel} Delta model. + */ +const parseDeltaModel = json => + // The studio owns the payload shape and there is nothing to validate it against, so the assertion is unchecked. + // oxlint-disable-next-line typescript/no-unsafe-type-assertion + /** @type {DeltaModel} */ (JSON.parse(json)); + +/** + * Build every alter script DTO of a delta model. + * + * @param {AlterScriptData} data FE data. + * @param {App} app App instance. + * @returns {AlterScriptDto[]} Alter script DTOs. + */ +const getAlterScriptDtos = (data, app) => { + const collection = parseDeltaModel(data.jsonSchema); + + if (!collection) { + throw new Error( + '"comparisonModelCollection" is not found. Alter script can be generated only from Delta model', + ); + } + + const inlineDeltaRelationships = getInlineRelationships({ collection, options: data.options }); + const ignoreRelationshipIDs = inlineDeltaRelationships + .map(relationship => relationship.role?.id) + .filter(id => id !== undefined); + const relatedSchemas = buildRelatedSchemas(getSectionItems(collection.properties?.entities)); + + const { deletedContainersScriptDtos, upsertedContainersScriptDtos } = getAlterContainersScriptDtos({ + collection, + app, + }); + const { deletedTypesScriptDtos, upsertedTypesScriptDtos } = getAlterTypesScriptDtos({ collection, app }); + + return [ + ...upsertedContainersScriptDtos, + ...upsertedTypesScriptDtos, + ...getAlterCollectionScriptDtos({ collection, app, inlineDeltaRelationships, relatedSchemas }), + ...getAlterVersioningScriptDtos({ collection, relatedSchemas }), + ...getAlterRelationshipsScriptDtos({ collection, ignoreRelationshipIDs }), + ...getAlterViewScriptDtos({ collection, app }), + ...deletedTypesScriptDtos, + ...deletedContainersScriptDtos, + ] + .map(dto => prettifyAlterScriptDto(dto)) + .filter(dto => dto !== undefined); +}; + +module.exports = { + getAlterScriptDtos, +}; diff --git a/forward_engineering/alterScript/alterScriptHelpers/alterContainerHelper.js b/forward_engineering/alterScript/alterScriptHelpers/alterContainerHelper.js new file mode 100644 index 0000000..dde0d87 --- /dev/null +++ b/forward_engineering/alterScript/alterScriptHelpers/alterContainerHelper.js @@ -0,0 +1,82 @@ +/** + * @import { + * AlterContainer, + * AlterScriptDto + * } from '../../types/alterScript' + * @import { + * App, + * DdlProvider + * } from '../../types/ddlProvider' + */ + +const { createAlterScriptDto } = require('../dto/alterScriptDto'); +const { wrapInQuotes } = require('../../utils/general'); +const { getModifiedCommentOnSchemaScriptDtos } = require('./containerHelpers/commentsHelper'); + +/** + * Build the CREATE SCHEMA statement for an added container. + * + * @param {DdlProvider} ddlProvider DDL provider. + * @returns {(containerData: AlterContainer) => AlterScriptDto | undefined} Add container script builder. + */ +const getAddContainerScriptDto = ddlProvider => containerData => { + const script = ddlProvider.createSchema({ + schemaName: containerData.role.name, + description: containerData.role.description, + isActivated: containerData.role.isActivated, + }); + + return createAlterScriptDto([script], true, false); +}; + +/** + * Build the DROP SCHEMA statement for a deleted container. + * + * @param {DdlProvider} ddlProvider DDL provider. + * @returns {(containerData: AlterContainer) => AlterScriptDto | undefined} Delete container script builder. + */ +const getDeleteContainerScriptDto = ddlProvider => containerData => { + const script = ddlProvider.dropSchema({ name: containerData.role.name }); + + return createAlterScriptDto([script], true, true); +}; + +/** + * Build the statements for a modified container. + * + * @returns {(containerData: AlterContainer) => AlterScriptDto[]} Modify container script builder. + */ +const getModifyContainerScriptDto = () => containerData => { + const commentScriptDto = getModifiedCommentOnSchemaScriptDtos({ + schemaName: wrapInQuotes(containerData.role.name), + compMod: containerData.role.compMod ?? {}, + isActivated: containerData.isActivated !== false, + }); + + return commentScriptDto ? [commentScriptDto] : []; +}; + +/** + * Build the container-level script builders bound to a DDL provider. + * + * @param {App} app App instance. + * @returns {{ + * getAddContainerScriptDto: (containerData: AlterContainer) => AlterScriptDto | undefined; + * getDeleteContainerScriptDto: (containerData: AlterContainer) => AlterScriptDto | undefined; + * getModifyContainerScriptDto: (containerData: AlterContainer) => AlterScriptDto[]; + * }} + * Container script builders. + */ +const getContainersScripts = app => { + const ddlProvider = require('../../ddlProvider/ddlProvider')(null, null, app); + + return { + getAddContainerScriptDto: getAddContainerScriptDto(ddlProvider), + getDeleteContainerScriptDto: getDeleteContainerScriptDto(ddlProvider), + getModifyContainerScriptDto: getModifyContainerScriptDto(), + }; +}; + +module.exports = { + getContainersScripts, +}; diff --git a/forward_engineering/alterScript/alterScriptHelpers/alterEntityHelper.js b/forward_engineering/alterScript/alterScriptHelpers/alterEntityHelper.js new file mode 100644 index 0000000..51f757d --- /dev/null +++ b/forward_engineering/alterScript/alterScriptHelpers/alterEntityHelper.js @@ -0,0 +1,265 @@ +/** + * @import { + * AlterCollection, + * AlterRelationship, + * AlterScriptDto + * } from '../../types/alterScript' + * @import { + * App, + * DdlProvider, + * ForeignKeyStatement + * } from '../../types/ddlProvider' + */ + +const toPairs = require('lodash/toPairs'); +const { createAlterScriptDto } = require('../dto/alterScriptDto'); +const { getModifiedCommentOnColumnScriptDtos } = require('./columnHelpers/commentsHelper'); +const { getModifyNonNullColumnsScriptDtos } = require('./columnHelpers/nonNullConstraintHelper'); +const { getUpdateTypesScriptDtos } = require('./columnHelpers/alterTypeHelper'); +const { getModifyCheckConstraintScriptDtos } = require('./entityHelpers/checkConstraintHelper'); +const { getRenameColumnScriptDtos } = require('./columnHelpers/alterColumnNameHelper'); +const { getModifyEntityCommentsScriptDtos } = require('./entityHelpers/commentsHelper'); +const { getModifyPkConstraintsScriptDtos } = require('./entityHelpers/primaryKeyHelper'); +const { getModifyUkConstraintsScriptDtos } = require('./entityHelpers/uniqueKeyHelper'); +const { getModifyIndexesScriptDtos } = require('./entityHelpers/indexesHelper'); +const { getModifiedDefaultColumnValueScriptDtos } = require('./columnHelpers/defaultValueHelper'); +const { + getEntityName, + getSchemaNameFromCollection, + getSchemaOfAlterCollection, + getFullCollectionName, + wrapInQuotes, +} = require('../../utils/general'); +const { getRelationshipName } = require('./alterForeignKeyHelper'); +const { createColumnDefinitionBySchema } = require('./createColumnDefinition'); +const { getRenameTableScriptDtos } = require('./entityHelpers/alterTableNameHelper'); + +/** + * Build the inline foreign key constraints of a newly added table. + * + * @param {{ + * collection: AlterCollection; + * inlineDeltaRelationships: AlterRelationship[]; + * ddlProvider: DdlProvider; + * schemaName: string; + * }} params + * Collection delta, its inline relationships, the DDL provider and the schema name. + * @returns {ForeignKeyStatement[]} Foreign key constraints. + */ +const getInlineForeignKeyConstraints = ({ collection, inlineDeltaRelationships, ddlProvider, schemaName }) => { + return inlineDeltaRelationships + .filter(relationship => relationship.role.childCollection === collection.role?.id) + .map(relationship => { + const compMod = relationship.role.compMod ?? {}; + + return ddlProvider.createForeignKeyConstraint( + { + name: getRelationshipName(relationship), + foreignKey: compMod.child?.collection?.fkFields ?? [], + primaryTable: compMod.parent?.collection?.name ?? '', + primaryKey: compMod.parent?.collection?.fkFields ?? [], + primaryTableActivated: compMod.parent?.collection?.isActivated, + foreignTableActivated: compMod.child?.collection?.isActivated, + primarySchemaName: compMod.parent?.bucket?.name, + customProperties: compMod.customProperties?.new, + }, + {}, + { schemaName }, + ); + }); +}; + +/** + * Build the CREATE TABLE statement for an added collection. + * + * @param {DdlProvider} ddlProvider DDL provider. + * @param {AlterRelationship[]} inlineDeltaRelationships Relationships rendered inline in the table definition. + * @param {Record} [relatedSchemas] Sibling entities of the model/container batch, keyed by + * entity GUID, used to resolve cross-entity references (e.g. auxiliary base table, LIKE table, history table). + * @returns {(collection: AlterCollection) => AlterScriptDto | undefined} Add collection script builder. + */ +const getAddCollectionScriptDto = (ddlProvider, inlineDeltaRelationships, relatedSchemas) => collection => { + const collectionSchema = getSchemaOfAlterCollection(collection); + const schemaName = getSchemaNameFromCollection({ collection }) ?? ''; + const schemaData = { schemaName }; + + const columnDefinitions = toPairs(collectionSchema.properties ?? {}).map(([name, column]) => + createColumnDefinitionBySchema({ + name, + jsonSchema: column, + parentJsonSchema: collectionSchema, + ddlProvider, + schemaData, + }), + ); + + const checkConstraints = (collectionSchema.chkConstr ?? []).map(checkConstraint => + ddlProvider.createCheckConstraint(ddlProvider.hydrateCheckConstraint(checkConstraint)), + ); + + const hydratedTable = ddlProvider.hydrateTable({ + tableData: { + name: getEntityName(collectionSchema), + columns: columnDefinitions.map(columnDefinition => ddlProvider.convertColumnDefinition(columnDefinition)), + checkConstraints, + foreignKeyConstraints: getInlineForeignKeyConstraints({ + collection, + inlineDeltaRelationships, + ddlProvider, + schemaName, + }), + schemaData, + columnDefinitions, + relatedSchemas, + }, + entityData: [collectionSchema], + jsonSchema: collectionSchema, + }); + const script = ddlProvider.createTable(hydratedTable, collectionSchema.isActivated); + + return createAlterScriptDto([script], true, false); +}; + +/** + * Build the DROP TABLE statement for a deleted collection. + * + * @param {DdlProvider} ddlProvider DDL provider. + * @returns {(collection: AlterCollection) => AlterScriptDto | undefined} Delete collection script builder. + */ +const getDeleteCollectionScriptDto = ddlProvider => collection => { + const collectionSchema = getSchemaOfAlterCollection(collection); + const script = ddlProvider.dropTable({ tableName: getFullCollectionName({ collectionSchema }) }); + + return createAlterScriptDto([script], true, true); +}; + +/** + * Build the statements for a modified collection, excluding its keys and columns. + * + * @param {AlterCollection} collection Collection delta. + * @returns {AlterScriptDto[]} Alter script DTOs. + */ +const getModifyCollectionScriptDtos = collection => { + return [ + ...getRenameTableScriptDtos(collection), + ...getModifyCheckConstraintScriptDtos(collection), + ...getModifyEntityCommentsScriptDtos(collection), + ]; +}; + +/** + * Build the key and index statements for a modified collection. + * + * @param {DdlProvider} ddlProvider DDL provider. + * @returns {(collection: AlterCollection) => AlterScriptDto[]} Modify keys script builder. + */ +const getModifyCollectionKeysScriptDtos = ddlProvider => collection => { + return [ + ...getModifyPkConstraintsScriptDtos(collection), + ...getModifyUkConstraintsScriptDtos(collection), + ...getModifyIndexesScriptDtos({ ddlProvider, collection }), + ]; +}; + +/** + * Build the ADD COLUMN statements of a collection. + * + * @param {DdlProvider} ddlProvider DDL provider. + * @returns {(collection: AlterCollection) => AlterScriptDto[]} Add column script builder. + */ +const getAddColumnScriptDtos = ddlProvider => collection => { + const collectionSchema = getSchemaOfAlterCollection(collection); + const fullTableName = getFullCollectionName({ collectionSchema }); + const schemaData = { schemaName: getSchemaNameFromCollection({ collection }) ?? '' }; + + return toPairs(collection.properties ?? {}) + .filter(([, jsonSchema]) => !jsonSchema.compMod) + .map(([name, jsonSchema]) => { + const columnDefinition = createColumnDefinitionBySchema({ + name, + jsonSchema, + parentJsonSchema: collectionSchema, + ddlProvider, + schemaData, + }); + const script = ddlProvider.addColumn({ + tableName: fullTableName, + columnDefinition: ddlProvider.convertColumnDefinition(columnDefinition), + }); + + return createAlterScriptDto([script], true, false); + }) + .filter(scriptDto => scriptDto !== undefined); +}; + +/** + * Build the DROP COLUMN statements of a collection. + * + * @param {DdlProvider} ddlProvider DDL provider. + * @returns {(collection: AlterCollection) => AlterScriptDto[]} Delete column script builder. + */ +const getDeleteColumnScriptDtos = ddlProvider => collection => { + const collectionSchema = getSchemaOfAlterCollection(collection); + const fullTableName = getFullCollectionName({ collectionSchema, preferAlterName: false }); + + return toPairs(collection.properties ?? {}) + .filter(([, jsonSchema]) => !jsonSchema.compMod) + .map(([name]) => { + const script = ddlProvider.dropColumn({ tableName: fullTableName, columnName: wrapInQuotes(name) }); + + return createAlterScriptDto([script], true, true); + }) + .filter(scriptDto => scriptDto !== undefined); +}; + +/** + * Build the column statements for a modified collection. + * + * @param {DdlProvider} ddlProvider DDL provider. + * @returns {(collection: AlterCollection) => AlterScriptDto[]} Modify column script builder. + */ +const getModifyColumnScriptDtos = ddlProvider => collection => { + return [ + ...getRenameColumnScriptDtos(collection), + ...getUpdateTypesScriptDtos(ddlProvider)(collection), + ...getModifyNonNullColumnsScriptDtos(collection), + ...getModifiedDefaultColumnValueScriptDtos({ collection }), + ...getModifiedCommentOnColumnScriptDtos(collection), + ]; +}; + +/** + * Build the entity-level script builders bound to a DDL provider. + * + * @param {App} app App instance. + * @param {AlterRelationship[]} inlineDeltaRelationships Relationships rendered inline in table definitions. + * @param {Record} [relatedSchemas] Sibling entities of the model/container batch, keyed by + * entity GUID, used to resolve cross-entity references. + * @returns {{ + * getAddCollectionScriptDto: (collection: AlterCollection) => AlterScriptDto | undefined; + * getDeleteCollectionScriptDto: (collection: AlterCollection) => AlterScriptDto | undefined; + * getModifyCollectionScriptDtos: (collection: AlterCollection) => AlterScriptDto[]; + * getModifyCollectionKeysScriptDtos: (collection: AlterCollection) => AlterScriptDto[]; + * getModifyColumnScriptDtos: (collection: AlterCollection) => AlterScriptDto[]; + * getAddColumnScriptDtos: (collection: AlterCollection) => AlterScriptDto[]; + * getDeleteColumnScriptDtos: (collection: AlterCollection) => AlterScriptDto[]; + * }} + * Entity script builders. + */ +const getEntitiesScripts = (app, inlineDeltaRelationships, relatedSchemas) => { + const ddlProvider = require('../../ddlProvider/ddlProvider')(null, null, app); + + return { + getAddCollectionScriptDto: getAddCollectionScriptDto(ddlProvider, inlineDeltaRelationships, relatedSchemas), + getDeleteCollectionScriptDto: getDeleteCollectionScriptDto(ddlProvider), + getModifyCollectionScriptDtos, + getModifyCollectionKeysScriptDtos: getModifyCollectionKeysScriptDtos(ddlProvider), + getModifyColumnScriptDtos: getModifyColumnScriptDtos(ddlProvider), + getAddColumnScriptDtos: getAddColumnScriptDtos(ddlProvider), + getDeleteColumnScriptDtos: getDeleteColumnScriptDtos(ddlProvider), + }; +}; + +module.exports = { + getEntitiesScripts, +}; diff --git a/forward_engineering/alterScript/alterScriptHelpers/alterForeignKeyHelper.js b/forward_engineering/alterScript/alterScriptHelpers/alterForeignKeyHelper.js new file mode 100644 index 0000000..09d1452 --- /dev/null +++ b/forward_engineering/alterScript/alterScriptHelpers/alterForeignKeyHelper.js @@ -0,0 +1,210 @@ +/** + * @import { + * AlterRelationship, + * AlterScriptDto + * } from '../../types/alterScript' + * @import {ForeignKeyStatement} from '../../types/ddlProvider' + */ + +const { createAlterScriptDto, createDropAndRecreateAlterScriptDto } = require('../dto/alterScriptDto'); +const { getNamePrefixedWithSchemaName, wrapInQuotes } = require('../../utils/general'); +const templates = require('../../ddlProvider/templates'); +const { assignTemplates } = require('../../utils/assignTemplates'); +const { getDefaultConstraintName } = require('../../ddlProvider/ddlHelpers/key/getDefaultConstraintName'); +const { CONSTRAINT_POSTFIX } = require('../../../shared/constants/constants'); + +/** + * Resolve the current name of a relationship, falling back to a generated name (matching the PK/UK pattern) when the + * relationship has none, so the FK statement is not silently dropped. + * + * @param {AlterRelationship} relationship Relationship delta. + * @returns {string} Relationship name. + */ +const getRelationshipName = relationship => { + const compMod = relationship.role.compMod; + const name = compMod?.code?.new ?? compMod?.name?.new ?? relationship.role.code ?? relationship.role.name ?? ''; + + return ( + name || + getDefaultConstraintName({ + entityName: compMod?.child?.collection?.name, + postfix: CONSTRAINT_POSTFIX.foreignKey, + }) + ); +}; + +/** + * Resolve the previous name of a relationship, falling back to a generated name (matching the PK/UK pattern) when the + * relationship has none, so the FK statement is not silently dropped. + * + * @param {AlterRelationship} relationship Relationship delta. + * @returns {string} Relationship name. + */ +const getOldRelationshipName = relationship => { + const compMod = relationship.role.compMod; + const name = compMod?.code?.old ?? compMod?.name?.old ?? relationship.role.code ?? relationship.role.name ?? ''; + + return ( + name || + getDefaultConstraintName({ + entityName: compMod?.child?.collection?.name, + postfix: CONSTRAINT_POSTFIX.foreignKey, + }) + ); +}; + +/** + * Build the ADD FOREIGN KEY statement for a relationship. + * + * @param {AlterRelationship} relationship Relationship delta. + * @returns {ForeignKeyStatement} Foreign key statement. + */ +const getAddForeignKeyStatement = relationship => { + const compMod = relationship.role.compMod ?? {}; + const ddlProvider = require('../../ddlProvider/ddlProvider')(null, null, null); + + return ddlProvider.createForeignKey( + { + name: getRelationshipName(relationship), + foreignKey: compMod.child?.collection?.fkFields ?? [], + primaryKey: compMod.parent?.collection?.fkFields ?? [], + customProperties: compMod.customProperties?.new, + foreignTable: compMod.child?.collection?.name, + foreignSchemaName: compMod.child?.bucket?.name, + foreignTableActivated: compMod.child?.collection?.isActivated, + primaryTable: compMod.parent?.collection?.name ?? '', + primarySchemaName: compMod.parent?.bucket?.name, + primaryTableActivated: compMod.parent?.collection?.isActivated, + }, + {}, + { schemaName: compMod.child?.bucket?.name ?? '' }, + ); +}; + +/** + * Build the DROP FOREIGN KEY statement for a relationship. + * + * @param {AlterRelationship} relationship Relationship delta. + * @returns {ForeignKeyStatement} Foreign key statement. + */ +const getDeleteForeignKeyStatement = relationship => { + const compMod = relationship.role.compMod ?? {}; + const statement = assignTemplates({ + template: templates.dropForeignKey, + templateData: { + tableName: getNamePrefixedWithSchemaName({ + name: compMod.child?.collection?.name ?? '', + schemaName: compMod.child?.bucket?.name, + }), + constraintName: wrapInQuotes(getOldRelationshipName(relationship)), + }, + }); + + return { + statement, + isActivated: Boolean(compMod.isActivated?.new) && Boolean(compMod.child?.collection?.isActivated), + }; +}; + +/** + * Check whether a relationship carries everything needed to build a foreign key. + * + * @param {AlterRelationship} relationship Relationship delta. + * @returns {boolean} Whether the foreign key can be added. + */ +const canRelationshipBeAdded = relationship => { + const compMod = relationship.role.compMod; + + if (!compMod) { + return false; + } + + return [ + getRelationshipName(relationship), + compMod.parent?.bucket, + compMod.parent?.collection, + compMod.parent?.collection?.fkFields?.length, + compMod.child?.bucket, + compMod.child?.collection, + compMod.child?.collection?.fkFields?.length, + ].every(Boolean); +}; + +/** + * Check whether a relationship carries everything needed to drop a foreign key. + * + * @param {AlterRelationship} relationship Relationship delta. + * @returns {boolean} Whether the foreign key can be dropped. + */ +const canRelationshipBeDeleted = relationship => { + const compMod = relationship.role.compMod; + + if (!compMod) { + return false; + } + + return [getOldRelationshipName(relationship), compMod.child?.bucket, compMod.child?.collection].every(Boolean); +}; + +/** + * Build the statements adding the foreign keys of new relationships. + * + * @param {AlterRelationship[]} addedRelationships Added relationships. + * @returns {AlterScriptDto[]} Alter script DTOs. + */ +const getAddForeignKeyScriptDtos = addedRelationships => { + return addedRelationships + .filter(relationship => canRelationshipBeAdded(relationship)) + .map(relationship => { + const statement = getAddForeignKeyStatement(relationship); + + return createAlterScriptDto([statement.statement], statement.isActivated, false); + }) + .filter(scriptDto => scriptDto !== undefined); +}; + +/** + * Build the statements dropping the foreign keys of removed relationships. + * + * @param {AlterRelationship[]} deletedRelationships Deleted relationships. + * @returns {AlterScriptDto[]} Alter script DTOs. + */ +const getDeleteForeignKeyScriptDtos = deletedRelationships => { + return deletedRelationships + .filter(relationship => canRelationshipBeDeleted(relationship)) + .map(relationship => { + const statement = getDeleteForeignKeyStatement(relationship); + + return createAlterScriptDto([statement.statement], statement.isActivated, true); + }) + .filter(scriptDto => scriptDto !== undefined); +}; + +/** + * Build the statements recreating the foreign keys of modified relationships. + * + * @param {AlterRelationship[]} modifiedRelationships Modified relationships. + * @returns {AlterScriptDto[]} Alter script DTOs. + */ +const getModifyForeignKeyScriptDtos = modifiedRelationships => { + return modifiedRelationships + .filter(relationship => canRelationshipBeAdded(relationship) && canRelationshipBeDeleted(relationship)) + .map(relationship => { + const deleteStatement = getDeleteForeignKeyStatement(relationship); + const addStatement = getAddForeignKeyStatement(relationship); + + return createDropAndRecreateAlterScriptDto( + deleteStatement.statement, + addStatement.statement, + deleteStatement.isActivated && addStatement.isActivated, + ); + }) + .filter(scriptDto => scriptDto !== undefined); +}; + +module.exports = { + getRelationshipName, + getDeleteForeignKeyScriptDtos, + getModifyForeignKeyScriptDtos, + getAddForeignKeyScriptDtos, +}; diff --git a/forward_engineering/alterScript/alterScriptHelpers/alterModelDefinitionHelper.js b/forward_engineering/alterScript/alterScriptHelpers/alterModelDefinitionHelper.js new file mode 100644 index 0000000..dd59b52 --- /dev/null +++ b/forward_engineering/alterScript/alterScriptHelpers/alterModelDefinitionHelper.js @@ -0,0 +1,302 @@ +/** + * @import { + * AlterModelDefinition, + * AlterModelDefinitionCompMod, + * AlterScriptDto + * } from '../../types/alterScript' + * @import { + * App, + * DdlProvider, + * HydratedColumn, + * PropertyPair + * } from '../../types/ddlProvider' + */ + +const isEqual = require('lodash/isEqual'); +const { createAlterScriptDto, createDropAndRecreateAlterScriptDto } = require('../dto/alterScriptDto'); +const { + checkFieldPropertiesChanged, + getNamePrefixedWithSchemaName, + getSchemaNameFromCollection, + getSchemaOfAlterCollection, +} = require('../../utils/general'); +const { + dropTypeCommentStatement, + getTypeCommentStatement, +} = require('../../ddlProvider/ddlHelpers/comment/commentHelper'); +const { assignTemplates } = require('../../utils/assignTemplates'); +const templates = require('../../ddlProvider/templates'); +const { createColumnDefinitionBySchema } = require('./createColumnDefinition'); + +const SOURCE_TYPE_PROPERTIES = [ + 'mode', + 'type', + 'length', + 'lengthSemantics', + 'precision', + 'scale', + 'fractSecPrecision', + 'withTimeZone', + 'characterSubtype', + 'ccsid', + 'inlineLength', +]; + +/** + * Check whether a comparison pair changed. + * + * @param {PropertyPair | undefined} pair Comparison pair. + * @returns {boolean} Whether the values differ. + */ +const hasPairChanged = pair => pair !== undefined && !isEqual(pair.old, pair.new); + +/** + * Check source-type comparison pairs that can appear on a definition's role compMod in some Studio payloads. + * + * @param {AlterModelDefinitionCompMod | undefined} compMod Comparison data. + * @returns {boolean} Whether a rendered source-type property changed. + */ +const hasSourceTypePairChanged = compMod => + hasPairChanged(compMod?.mode) || + hasPairChanged(compMod?.type) || + hasPairChanged(compMod?.length) || + hasPairChanged(compMod?.lengthSemantics) || + hasPairChanged(compMod?.precision) || + hasPairChanged(compMod?.scale) || + hasPairChanged(compMod?.fractSecPrecision) || + hasPairChanged(compMod?.withTimeZone) || + hasPairChanged(compMod?.characterSubtype) || + hasPairChanged(compMod?.ccsid) || + hasPairChanged(compMod?.inlineLength); + +/** + * Resolve the current definition name. + * + * @param {AlterModelDefinition} definitionData Definition delta. + * @returns {string} Current type name. + */ +const getTypeName = definitionData => { + const definitionSchema = getSchemaOfAlterCollection(definitionData); + + return ( + definitionData.role.compMod?.name?.new ?? + definitionData.compMod?.name?.new ?? + definitionData.compMod?.newField?.name ?? + definitionSchema.name ?? + '' + ); +}; + +/** + * Resolve the previous definition name for a drop during recreation. + * + * @param {AlterModelDefinition} definitionData Definition delta. + * @returns {string} Previous type name. + */ +const getOldTypeName = definitionData => { + return ( + definitionData.role.compMod?.name?.old ?? + definitionData.role.compMod?.collectionName?.old ?? + definitionData.compMod?.name?.old ?? + definitionData.compMod?.collectionName?.old ?? + definitionData.compMod?.oldField?.name ?? + getTypeName(definitionData) + ); +}; + +/** + * Resolve the current activation flag from either supported comparison shape. + * + * @param {AlterModelDefinition} definitionData Definition delta. + * @returns {boolean} Activation flag. + */ +const getIsActivated = definitionData => { + const definitionSchema = getSchemaOfAlterCollection(definitionData); + + return ( + definitionData.role.compMod?.isActivated?.new ?? + definitionData.compMod?.isActivated?.new ?? + definitionSchema.isActivated ?? + true + ); +}; + +/** + * Hydrate the current distinct type definition through the provider's standard column hydration path. + * + * @param {DdlProvider} ddlProvider DDL provider. + * @param {AlterModelDefinition} definitionData Definition delta. + * @returns {HydratedColumn} Hydrated UDT. + */ +const hydrateUdt = (ddlProvider, definitionData) => { + const definitionSchema = getSchemaOfAlterCollection(definitionData); + + return createColumnDefinitionBySchema({ + name: getTypeName(definitionData), + jsonSchema: { ...definitionSchema, compMod: undefined }, + parentJsonSchema: { required: [] }, + ddlProvider, + schemaData: { schemaName: getSchemaNameFromCollection({ collection: definitionData }) ?? '' }, + }); +}; + +/** + * Build the DROP TYPE statement used only by delta scripts. + * + * @param {{ name: string; schemaName?: string }} params Type name. + * @returns {string} Drop statement. + */ +const dropUdt = ({ name, schemaName }) => { + return assignTemplates({ + template: templates.dropType, + templateData: { + name: getNamePrefixedWithSchemaName({ name, schemaName }), + }, + }); +}; + +/** + * Check whether a modification changes the rendered type definition and therefore requires recreation. + * + * @param {AlterModelDefinition} definitionData Definition delta. + * @returns {boolean} Whether the type must be recreated. + */ +const shouldRecreateType = definitionData => { + const fieldCompMod = definitionData.compMod; + const oldField = fieldCompMod?.oldField; + const newField = fieldCompMod?.newField; + const didFieldChange = + oldField !== undefined && + newField !== undefined && + checkFieldPropertiesChanged({ oldField, newField }, [...SOURCE_TYPE_PROPERTIES, 'name']); + + return ( + didFieldChange || + hasSourceTypePairChanged(definitionData.compMod) || + hasSourceTypePairChanged(definitionData.role.compMod) || + hasPairChanged(definitionData.compMod?.name) || + hasPairChanged(definitionData.compMod?.collectionName) || + hasPairChanged(definitionData.role.compMod?.name) || + hasPairChanged(definitionData.role.compMod?.collectionName) + ); +}; + +/** + * Resolve a modified description from either the role-level pair or field snapshots. + * + * @param {AlterModelDefinition} definitionData Definition delta. + * @returns {PropertyPair} Description comparison pair. + */ +const getDescriptionChange = definitionData => { + const pair = definitionData.role.compMod?.description ?? definitionData.compMod?.description; + if (pair) { + return pair; + } + + const oldDescription = definitionData.compMod?.oldField?.description; + const newDescription = definitionData.compMod?.newField?.description; + + return { + old: typeof oldDescription === 'string' ? oldDescription : undefined, + new: typeof newDescription === 'string' ? newDescription : undefined, + }; +}; + +/** + * Build the CREATE TYPE statement for an added definition. + * + * @param {DdlProvider} ddlProvider DDL provider. + * @returns {(definitionData: AlterModelDefinition) => AlterScriptDto | undefined} Add type script builder. + */ +const getAddTypeScriptDto = ddlProvider => definitionData => { + const script = ddlProvider.createUdt(hydrateUdt(ddlProvider, definitionData)); + + return createAlterScriptDto([script], true, false); +}; + +/** + * Build the DROP TYPE statement for a deleted definition. + * + * @param {AlterModelDefinition} definitionData Definition delta. + * @returns {AlterScriptDto | undefined} Delete type script DTO. + */ +const getDeleteTypeScriptDto = definitionData => { + const script = dropUdt({ + name: getOldTypeName(definitionData), + schemaName: getSchemaNameFromCollection({ collection: definitionData }), + }); + + return createAlterScriptDto([script], true, true); +}; + +/** + * Build the statements for a modified distinct type. + * + * @param {DdlProvider} ddlProvider DDL provider. + * @returns {(definitionData: AlterModelDefinition) => AlterScriptDto[]} Modify type script builder. + */ +const getModifyTypeScriptDtos = ddlProvider => definitionData => { + const hydratedUdt = hydrateUdt(ddlProvider, definitionData); + const isActivated = getIsActivated(definitionData); + + if (shouldRecreateType(definitionData)) { + const dropScript = dropUdt({ + name: getOldTypeName(definitionData), + schemaName: hydratedUdt.schemaName, + }); + const createScript = ddlProvider.createUdt({ ...hydratedUdt, isActivated: true }); + const scriptDto = createDropAndRecreateAlterScriptDto(dropScript, createScript, isActivated); + + return scriptDto ? [scriptDto] : []; + } + + const description = getDescriptionChange(definitionData); + if (!hasPairChanged(description)) { + return []; + } + + const typeName = getNamePrefixedWithSchemaName({ + name: hydratedUdt.name, + schemaName: hydratedUdt.schemaName, + }); + if (description.new) { + const script = getTypeCommentStatement({ typeName, description: description.new }); + const scriptDto = createAlterScriptDto([script], isActivated, false); + + return scriptDto ? [scriptDto] : []; + } + + if (description.old) { + const script = dropTypeCommentStatement({ typeName }); + const scriptDto = createAlterScriptDto([script], isActivated, true); + + return scriptDto ? [scriptDto] : []; + } + + return []; +}; + +/** + * Build model-definition script builders bound to a DDL provider. + * + * @param {App} app App instance. + * @returns {{ + * getAddTypeScriptDto: (definitionData: AlterModelDefinition) => AlterScriptDto | undefined; + * getDeleteTypeScriptDto: (definitionData: AlterModelDefinition) => AlterScriptDto | undefined; + * getModifyTypeScriptDtos: (definitionData: AlterModelDefinition) => AlterScriptDto[]; + * }} + * Type script builders. + */ +const getModelDefinitionsScripts = app => { + const ddlProvider = require('../../ddlProvider/ddlProvider')(null, null, app); + + return { + getAddTypeScriptDto: getAddTypeScriptDto(ddlProvider), + getDeleteTypeScriptDto, + getModifyTypeScriptDtos: getModifyTypeScriptDtos(ddlProvider), + }; +}; + +module.exports = { + getModelDefinitionsScripts, +}; diff --git a/forward_engineering/alterScript/alterScriptHelpers/alterVersioningHelper.js b/forward_engineering/alterScript/alterScriptHelpers/alterVersioningHelper.js new file mode 100644 index 0000000..245e712 --- /dev/null +++ b/forward_engineering/alterScript/alterScriptHelpers/alterVersioningHelper.js @@ -0,0 +1,113 @@ +/** + * @import { + * AlterScriptDto, + * AlterTable + * } from '../../types/alterScript' + * @import {PeriodConfig} from '../../types/ddlProvider' + */ + +const { createAlterScriptDto } = require('../dto/alterScriptDto'); +const { + getEntityName, + getSchemaNameFromCollection, + getSchemaOfAlterCollection, + getNamePrefixedWithSchemaName, +} = require('../../utils/general'); +const templates = require('../../ddlProvider/templates'); +const { assignTemplates } = require('../../utils/assignTemplates'); + +/** + * Read the single period config out of a group property, which the studio may serialize as an object or as a one-item + * array depending on how it was last edited. + * + * @param {PeriodConfig | PeriodConfig[] | undefined} period Period group value. + * @returns {PeriodConfig | undefined} Period config. + */ +const getPeriodConfig = period => (Array.isArray(period) ? period[0] : period); + +/** + * Resolve a cross-entity GUID reference (e.g. history table, archive table) to its schema-qualified name. + * + * @param {{ relatedSchemas: Record; entityId?: string }} params Related entities and the referenced + * entity's GUID. + * @returns {string | undefined} Schema-qualified table name. + */ +const resolveRelatedTableName = ({ relatedSchemas, entityId }) => { + const relatedSchema = entityId ? relatedSchemas[entityId] : undefined; + const name = relatedSchema ? getEntityName(relatedSchema) : undefined; + + if (!name) { + return void 0; + } + + return getNamePrefixedWithSchemaName({ name, schemaName: relatedSchema?.bucketName }); +}; + +/** + * Build the ALTER TABLE ... ADD VERSIONING statement linking a system-period temporal table to its history table. + * + * @param {Record} relatedSchemas Sibling entities of the model/container batch, keyed by entity + * GUID. + * @returns {(collection: AlterTable) => AlterScriptDto | undefined} Add versioning script builder. + */ +const getAddVersioningScriptDto = relatedSchemas => collection => { + const collectionSchema = getSchemaOfAlterCollection(collection); + const historyTableId = getPeriodConfig(collectionSchema.periodForSystemTime)?.historyTable; + const historyTableName = resolveRelatedTableName({ relatedSchemas, entityId: historyTableId }); + + if (!historyTableName) { + return void 0; + } + + const script = assignTemplates({ + template: templates.addVersioning, + templateData: { + tableName: getNamePrefixedWithSchemaName({ + name: getEntityName(collectionSchema), + schemaName: getSchemaNameFromCollection({ collection }), + }), + historyTableName, + }, + }); + + return createAlterScriptDto([script], collectionSchema.isActivated ?? true, false); +}; + +/** + * Build the ALTER TABLE ... ENABLE ARCHIVE statement linking a table to its archive table. + * + * @param {Record} relatedSchemas Sibling entities of the model/container batch, keyed by entity + * GUID. + * @returns {(collection: AlterTable) => AlterScriptDto | undefined} Enable archive script builder. + */ +const getEnableArchiveScriptDto = relatedSchemas => collection => { + const collectionSchema = getSchemaOfAlterCollection(collection); + + if (!collectionSchema.archiveEnabled) { + return void 0; + } + + const archiveTableName = resolveRelatedTableName({ relatedSchemas, entityId: collectionSchema.archiveTable }); + + if (!archiveTableName) { + return void 0; + } + + const script = assignTemplates({ + template: templates.enableArchive, + templateData: { + tableName: getNamePrefixedWithSchemaName({ + name: getEntityName(collectionSchema), + schemaName: getSchemaNameFromCollection({ collection }), + }), + archiveTableName, + }, + }); + + return createAlterScriptDto([script], collectionSchema.isActivated ?? true, false); +}; + +module.exports = { + getAddVersioningScriptDto, + getEnableArchiveScriptDto, +}; diff --git a/forward_engineering/alterScript/alterScriptHelpers/alterViewHelper.js b/forward_engineering/alterScript/alterScriptHelpers/alterViewHelper.js new file mode 100644 index 0000000..1e9a60b --- /dev/null +++ b/forward_engineering/alterScript/alterScriptHelpers/alterViewHelper.js @@ -0,0 +1,106 @@ +/** + * @import { + * AlterScriptDto, + * AlterView, + * MapPropertiesFn + * } from '../../types/alterScript' + * @import { + * App, + * AppModule, + * DdlProvider + * } from '../../types/ddlProvider' + */ + +const { getModifyViewCommentsScriptDtos } = require('./viewHelpers/commentsHelper'); +const { getRenameViewScriptDtos } = require('./viewHelpers/alterNameHelper'); +const { createAlterScriptDto } = require('../dto/alterScriptDto'); +const { getSchemaOfAlterCollection } = require('../../utils/general'); +const { createView, dropView } = require('./viewHelpers/createDropViewHelper'); +const { getModifySelectStatementScriptDtos } = require('./viewHelpers/alterViewStatementHelper'); + +/** + * Check whether a runtime-loaded module exposes the property mapper used by view generation. + * + * @param {AppModule} appModule Runtime-loaded module. + * @returns {appModule is { mapProperties: MapPropertiesFn }} Whether the module exposes the mapper. + */ +const hasMapProperties = appModule => { + return ( + typeof appModule === 'object' && + appModule !== null && + 'mapProperties' in appModule && + typeof appModule.mapProperties === 'function' + ); +}; + +/** + * Build the CREATE VIEW statement for an added view. + * + * @param {DdlProvider} ddlProvider DDL provider. + * @param {MapPropertiesFn} mapProperties Property mapper. + * @returns {(view: AlterView) => AlterScriptDto | undefined} Add view script builder. + */ +const getAddViewScriptDto = (ddlProvider, mapProperties) => view => { + const script = createView({ ddlProvider, mapProperties, view }); + + return createAlterScriptDto([script], true, false); +}; + +/** + * Build the DROP VIEW statement for a deleted view. + * + * @param {DdlProvider} ddlProvider DDL provider. + * @returns {(view: AlterView) => AlterScriptDto | undefined} Delete view script builder. + */ +const getDeleteViewScriptDto = ddlProvider => view => { + const script = dropView({ ddlProvider, viewSchema: getSchemaOfAlterCollection(view) }); + + return createAlterScriptDto([script], true, true); +}; + +/** + * Build the statements for a modified view. + * + * @param {DdlProvider} ddlProvider DDL provider. + * @param {MapPropertiesFn} mapProperties Property mapper. + * @returns {(view: AlterView) => AlterScriptDto[]} Modify view script builder. + */ +const getModifyViewScriptDtos = (ddlProvider, mapProperties) => view => { + return [ + ...getRenameViewScriptDtos(view, ddlProvider, mapProperties), + ...getModifySelectStatementScriptDtos(view, ddlProvider, mapProperties), + ...getModifyViewCommentsScriptDtos(view), + ]; +}; + +/** + * Build the view script builders bound to a DDL provider. + * + * @param {App} app App instance. + * @returns {{ + * getAddViewScriptDto: (view: AlterView) => AlterScriptDto | undefined; + * getDeleteViewScriptDto: (view: AlterView) => AlterScriptDto | undefined; + * getModifyViewScriptDtos: (view: AlterView) => AlterScriptDto[]; + * }} + * View script builders. + */ +const getViewsScripts = app => { + const ddlProvider = require('../../ddlProvider/ddlProvider')(null, null, app); + const ddlFeUtils = app.require('@hackolade/ddl-fe-utils'); + + if (!hasMapProperties(ddlFeUtils)) { + throw new TypeError('@hackolade/ddl-fe-utils does not expose mapProperties'); + } + + const { mapProperties } = ddlFeUtils; + + return { + getAddViewScriptDto: getAddViewScriptDto(ddlProvider, mapProperties), + getDeleteViewScriptDto: getDeleteViewScriptDto(ddlProvider), + getModifyViewScriptDtos: getModifyViewScriptDtos(ddlProvider, mapProperties), + }; +}; + +module.exports = { + getViewsScripts, +}; diff --git a/forward_engineering/alterScript/alterScriptHelpers/columnHelpers/alterColumnNameHelper.js b/forward_engineering/alterScript/alterScriptHelpers/columnHelpers/alterColumnNameHelper.js new file mode 100644 index 0000000..7adbf1f --- /dev/null +++ b/forward_engineering/alterScript/alterScriptHelpers/columnHelpers/alterColumnNameHelper.js @@ -0,0 +1,70 @@ +/** + * @import { + * AlterCollection, + * AlterScriptDto + * } from '../../../types/alterScript' + */ + +const toPairs = require('lodash/toPairs'); +const { + getSchemaOfAlterCollection, + getFullCollectionName, + isParentContainerActivated, + isObjectInDeltaModelActivated, + wrapInQuotes, +} = require('../../../utils/general'); +const templates = require('../../../ddlProvider/templates'); +const { createAlterScriptDto } = require('../../dto/alterScriptDto'); +const { assignTemplates } = require('../../../utils/assignTemplates'); + +/** + * Build the RENAME COLUMN statement. + * + * @param {string} tableName Fully qualified table name. + * @param {string} oldColumnName Current column name. + * @param {string} newColumnName New column name. + * @returns {string} Rename statement. + */ +const alterColumnName = (tableName, oldColumnName, newColumnName) => { + return assignTemplates({ + template: templates.renameColumn, + templateData: { + tableName, + oldColumnName: wrapInQuotes(oldColumnName), + newColumnName: wrapInQuotes(newColumnName), + }, + }); +}; + +/** + * Build the rename statements for all renamed columns of a collection. + * + * @param {AlterCollection} collection Collection delta. + * @returns {AlterScriptDto[]} Alter script DTOs. + */ +const getRenameColumnScriptDtos = collection => { + const collectionSchema = getSchemaOfAlterCollection(collection); + const fullTableName = getFullCollectionName({ collectionSchema }); + const isContainerActivated = isParentContainerActivated(collection); + const isCollectionActivated = isObjectInDeltaModelActivated(collection); + + return toPairs(collection.properties ?? {}) + .map(([, jsonSchema]) => { + const oldName = jsonSchema.compMod?.oldField?.name; + const newName = jsonSchema.compMod?.newField?.name; + + if (!oldName || !newName || oldName === newName) { + return void 0; + } + + const isActivated = isContainerActivated && isCollectionActivated && Boolean(jsonSchema.isActivated); + const script = alterColumnName(fullTableName, oldName, newName); + + return createAlterScriptDto([script], isActivated, false); + }) + .filter(scriptDto => scriptDto !== undefined); +}; + +module.exports = { + getRenameColumnScriptDtos, +}; diff --git a/forward_engineering/alterScript/alterScriptHelpers/columnHelpers/alterTypeHelper.js b/forward_engineering/alterScript/alterScriptHelpers/columnHelpers/alterTypeHelper.js new file mode 100644 index 0000000..a0c1d8e --- /dev/null +++ b/forward_engineering/alterScript/alterScriptHelpers/columnHelpers/alterTypeHelper.js @@ -0,0 +1,104 @@ +/** + * @import { + * AlterCollection, + * AlterColumn, + * AlterScriptDto + * } from '../../../types/alterScript' + * @import {DdlProvider} from '../../../types/ddlProvider' + */ + +const toPairs = require('lodash/toPairs'); +const { createAlterScriptDto } = require('../../dto/alterScriptDto'); +const { + checkFieldPropertiesChanged, + getFullCollectionName, + wrapInQuotes, + isObjectInDeltaModelActivated, + isParentContainerActivated, + getSchemaOfAlterCollection, + getSchemaNameFromCollection, +} = require('../../../utils/general'); +const { assignTemplates } = require('../../../utils/assignTemplates'); +const templates = require('../../../ddlProvider/templates'); +const { createColumnDefinitionBySchema } = require('../createColumnDefinition'); +const { getColumnType } = require('../../../ddlProvider/ddlHelpers/columnDefinition/getColumnType'); + +/** + * Build the SET DATA TYPE statement. + * + * @param {string} tableName Fully qualified table name. + * @param {string} columnName Quoted column name. + * @param {string} dataType Column data type. + * @returns {string} Alter statement. + */ +const alterColumnType = (tableName, columnName, dataType) => { + return assignTemplates({ + template: templates.updateColumnType, + templateData: { tableName, columnName, dataType }, + }); +}; + +/** + * Check whether the length, precision or scale of a column changed. + * + * @param {AlterCollection} collection Collection delta. + * @param {string} oldFieldName Previous column name. + * @param {AlterColumn} jsonSchema Current column schema. + * @returns {boolean} Whether the type size changed. + */ +const hasTypeSizeChanged = (collection, oldFieldName, jsonSchema) => { + const oldProperty = collection.role?.properties?.[oldFieldName]; + + return ( + oldProperty?.length !== jsonSchema.length || + oldProperty?.precision !== jsonSchema.precision || + oldProperty?.scale !== jsonSchema.scale + ); +}; + +/** + * Build the data type statements for every column whose type changed. + * + * @param {DdlProvider} ddlProvider DDL provider. + * @returns {(collection: AlterCollection) => AlterScriptDto[]} Update types script builder. + */ +const getUpdateTypesScriptDtos = ddlProvider => collection => { + const collectionSchema = getSchemaOfAlterCollection(collection); + const fullTableName = getFullCollectionName({ collectionSchema }); + const isContainerActivated = isParentContainerActivated(collection); + const isCollectionActivated = isObjectInDeltaModelActivated(collection); + const schemaName = getSchemaNameFromCollection({ collection }); + + return toPairs(collection.properties ?? {}) + .filter(([name, jsonSchema]) => { + if (!jsonSchema.compMod) { + return false; + } + + if (checkFieldPropertiesChanged(jsonSchema.compMod, ['type', 'mode'])) { + return true; + } + + return hasTypeSizeChanged(collection, jsonSchema.compMod.oldField.name ?? name, jsonSchema); + }) + .map(([columnName, jsonSchema]) => { + const columnDefinition = createColumnDefinitionBySchema({ + name: columnName, + jsonSchema, + parentJsonSchema: collectionSchema, + ddlProvider, + schemaData: { schemaName: schemaName ?? '' }, + }); + + const dataType = getColumnType(columnDefinition).trim(); + const script = alterColumnType(fullTableName, wrapInQuotes(columnName), dataType); + const isActivated = isContainerActivated && isCollectionActivated && Boolean(jsonSchema.isActivated); + + return createAlterScriptDto([script], isActivated, false); + }) + .filter(scriptDto => scriptDto !== undefined); +}; + +module.exports = { + getUpdateTypesScriptDtos, +}; diff --git a/forward_engineering/alterScript/alterScriptHelpers/columnHelpers/commentsHelper.js b/forward_engineering/alterScript/alterScriptHelpers/columnHelpers/commentsHelper.js new file mode 100644 index 0000000..51bbc54 --- /dev/null +++ b/forward_engineering/alterScript/alterScriptHelpers/columnHelpers/commentsHelper.js @@ -0,0 +1,95 @@ +/** + * @import { + * AlterCollection, + * AlterScriptDto + * } from '../../../types/alterScript' + */ + +const toPairs = require('lodash/toPairs'); +const { createAlterScriptDto } = require('../../dto/alterScriptDto'); +const { + isObjectInDeltaModelActivated, + isParentContainerActivated, + getSchemaOfAlterCollection, + getFullCollectionName, +} = require('../../../utils/general'); +const { + getColumnCommentStatement, + dropTableColumnCommentStatement, +} = require('../../../ddlProvider/ddlHelpers/comment/commentHelper'); + +/** + * Build the COMMENT ON COLUMN statements for columns whose description was set or changed. + * + * @param {AlterCollection} collection Collection delta. + * @returns {(AlterScriptDto | undefined)[]} Alter script DTOs. + */ +const getUpdatedCommentOnColumnScriptDtos = collection => { + const isContainerActivated = isParentContainerActivated(collection); + const isCollectionActivated = isObjectInDeltaModelActivated(collection); + const collectionSchema = getSchemaOfAlterCollection(collection); + const tableName = getFullCollectionName({ collectionSchema }); + + return toPairs(collection.properties ?? {}) + .filter(([name, jsonSchema]) => { + const newComment = jsonSchema.description; + const oldName = jsonSchema.compMod?.oldField?.name ?? name; + const oldComment = collection.role?.properties?.[oldName]?.description; + + return Boolean(newComment) && newComment !== oldComment; + }) + .map(([columnName, jsonSchema]) => { + const isActivated = isContainerActivated && isCollectionActivated && Boolean(jsonSchema.isActivated); + const script = getColumnCommentStatement({ + tableName, + columnName, + description: jsonSchema.description, + }); + + return createAlterScriptDto([script], isActivated, false); + }); +}; + +/** + * Build the statements removing comments from columns whose description was cleared. + * + * @param {AlterCollection} collection Collection delta. + * @returns {(AlterScriptDto | undefined)[]} Alter script DTOs. + */ +const getDeletedCommentOnColumnScriptDtos = collection => { + const isContainerActivated = isParentContainerActivated(collection); + const isCollectionActivated = isObjectInDeltaModelActivated(collection); + const collectionSchema = getSchemaOfAlterCollection(collection); + const tableName = getFullCollectionName({ collectionSchema }); + + return toPairs(collection.properties ?? {}) + .filter(([name, jsonSchema]) => { + const oldName = jsonSchema.compMod?.oldField?.name ?? name; + const oldComment = collection.role?.properties?.[oldName]?.description; + + return Boolean(oldComment) && !jsonSchema.description; + }) + .map(([columnName, jsonSchema]) => { + const isActivated = isContainerActivated && isCollectionActivated && Boolean(jsonSchema.isActivated); + const script = dropTableColumnCommentStatement({ tableName, columnName }); + + return createAlterScriptDto([script], isActivated, true); + }); +}; + +/** + * Build all column comment statements for a collection. + * + * @param {AlterCollection} collection Collection delta. + * @returns {AlterScriptDto[]} Alter script DTOs. + */ +const getModifiedCommentOnColumnScriptDtos = collection => { + const updatedCommentScriptDtos = getUpdatedCommentOnColumnScriptDtos(collection); + const deletedCommentScriptDtos = getDeletedCommentOnColumnScriptDtos(collection); + + return [...updatedCommentScriptDtos, ...deletedCommentScriptDtos].filter(scriptDto => scriptDto !== undefined); +}; + +module.exports = { + getModifiedCommentOnColumnScriptDtos, +}; diff --git a/forward_engineering/alterScript/alterScriptHelpers/columnHelpers/defaultValueHelper.js b/forward_engineering/alterScript/alterScriptHelpers/columnHelpers/defaultValueHelper.js new file mode 100644 index 0000000..701c588 --- /dev/null +++ b/forward_engineering/alterScript/alterScriptHelpers/columnHelpers/defaultValueHelper.js @@ -0,0 +1,122 @@ +/** + * @import { + * AlterCollection, + * AlterScriptDto + * } from '../../../types/alterScript' + */ + +const toPairs = require('lodash/toPairs'); +const { createAlterScriptDto } = require('../../dto/alterScriptDto'); +const { + getFullCollectionName, + wrapInQuotes, + isObjectInDeltaModelActivated, + isParentContainerActivated, + getSchemaOfAlterCollection, +} = require('../../../utils/general'); +const { assignTemplates } = require('../../../utils/assignTemplates'); +const templates = require('../../../ddlProvider/templates'); + +/** + * Build the SET DEFAULT statement. + * + * @param {{ tableName: string; columnName: string; defaultValue: string | number | boolean }} params Statement parts. + * @returns {string} Alter statement. + */ +const updateColumnDefaultValue = ({ tableName, columnName, defaultValue }) => { + return assignTemplates({ + template: templates.updateColumnDefaultValue, + templateData: { tableName, columnName, defaultValue }, + }); +}; + +/** + * Build the DROP DEFAULT statement. + * + * @param {{ tableName: string; columnName: string }} params Statement parts. + * @returns {string} Alter statement. + */ +const dropColumnDefaultValue = ({ tableName, columnName }) => { + return assignTemplates({ + template: templates.dropColumnDefaultValue, + templateData: { tableName, columnName }, + }); +}; + +/** + * Build the statements for columns that got a new default value. + * + * @param {{ collection: AlterCollection }} params Collection delta. + * @returns {(AlterScriptDto | undefined)[]} Alter script DTOs. + */ +const getUpdatedDefaultColumnValueScriptDtos = ({ collection }) => { + const isContainerActivated = isParentContainerActivated(collection); + const isCollectionActivated = isObjectInDeltaModelActivated(collection); + const collectionSchema = getSchemaOfAlterCollection(collection); + + return toPairs(collection.properties ?? {}) + .filter(([name, jsonSchema]) => { + const oldName = jsonSchema.compMod?.oldField?.name ?? name; + const oldDefault = collection.role?.properties?.[oldName]?.default; + + return jsonSchema.default !== undefined && jsonSchema.default !== oldDefault; + }) + .map(([columnName, jsonSchema]) => { + const isActivated = isContainerActivated && isCollectionActivated && Boolean(jsonSchema.isActivated); + const script = updateColumnDefaultValue({ + tableName: getFullCollectionName({ collectionSchema }), + columnName: wrapInQuotes(columnName), + defaultValue: jsonSchema.default ?? '', + }); + + return createAlterScriptDto([script], isActivated, false); + }); +}; + +/** + * Build the statements for columns whose default value was removed. + * + * @param {{ collection: AlterCollection }} params Collection delta. + * @returns {(AlterScriptDto | undefined)[]} Alter script DTOs. + */ +const getDeletedDefaultColumnValueScriptDtos = ({ collection }) => { + const isContainerActivated = isParentContainerActivated(collection); + const isCollectionActivated = isObjectInDeltaModelActivated(collection); + const collectionSchema = getSchemaOfAlterCollection(collection); + + return toPairs(collection.properties ?? {}) + .filter(([name, jsonSchema]) => { + const oldName = jsonSchema.compMod?.oldField?.name ?? name; + const oldDefault = collection.role?.properties?.[oldName]?.default; + + return oldDefault !== undefined && jsonSchema.default === undefined; + }) + .map(([columnName, jsonSchema]) => { + const isActivated = isContainerActivated && isCollectionActivated && Boolean(jsonSchema.isActivated); + const script = dropColumnDefaultValue({ + tableName: getFullCollectionName({ collectionSchema }), + columnName: wrapInQuotes(columnName), + }); + + return createAlterScriptDto([script], isActivated, true); + }); +}; + +/** + * Build all default value statements for a collection. + * + * @param {{ collection: AlterCollection }} params Collection delta. + * @returns {AlterScriptDto[]} Alter script DTOs. + */ +const getModifiedDefaultColumnValueScriptDtos = ({ collection }) => { + const updatedDefaultValueScriptDtos = getUpdatedDefaultColumnValueScriptDtos({ collection }); + const droppedDefaultValueScriptDtos = getDeletedDefaultColumnValueScriptDtos({ collection }); + + return [...updatedDefaultValueScriptDtos, ...droppedDefaultValueScriptDtos].filter( + scriptDto => scriptDto !== undefined, + ); +}; + +module.exports = { + getModifiedDefaultColumnValueScriptDtos, +}; diff --git a/forward_engineering/alterScript/alterScriptHelpers/columnHelpers/nonNullConstraintHelper.js b/forward_engineering/alterScript/alterScriptHelpers/columnHelpers/nonNullConstraintHelper.js new file mode 100644 index 0000000..267d1b9 --- /dev/null +++ b/forward_engineering/alterScript/alterScriptHelpers/columnHelpers/nonNullConstraintHelper.js @@ -0,0 +1,107 @@ +/** + * @import { + * AlterCollection, + * AlterScriptDto + * } from '../../../types/alterScript' + */ + +const difference = require('lodash/difference'); +const toPairs = require('lodash/toPairs'); +const { createAlterScriptDto } = require('../../dto/alterScriptDto'); +const { + getFullCollectionName, + wrapInQuotes, + isObjectInDeltaModelActivated, + isParentContainerActivated, + getSchemaOfAlterCollection, +} = require('../../../utils/general'); +const { assignTemplates } = require('../../../utils/assignTemplates'); +const templates = require('../../../ddlProvider/templates'); + +/** + * Build the SET NOT NULL statement. + * + * @param {string} tableName Fully qualified table name. + * @param {string} columnName Quoted column name. + * @returns {string} Alter statement. + */ +const setNotNullConstraint = (tableName, columnName) => { + return assignTemplates({ + template: templates.alterNotNull, + templateData: { tableName, columnName }, + }); +}; + +/** + * Build the DROP NOT NULL statement. + * + * @param {string} tableName Fully qualified table name. + * @param {string} columnName Quoted column name. + * @returns {string} Alter statement. + */ +const dropNotNullConstraint = (tableName, columnName) => { + return assignTemplates({ + template: templates.dropNotNull, + templateData: { tableName, columnName }, + }); +}; + +/** + * Build the NOT NULL statements for every column whose requiredness changed. + * + * @param {AlterCollection} collection Collection delta. + * @returns {AlterScriptDto[]} Alter script DTOs. + */ +const getModifyNonNullColumnsScriptDtos = collection => { + const collectionSchema = getSchemaOfAlterCollection(collection); + const fullTableName = getFullCollectionName({ collectionSchema }); + + const isContainerActivated = isParentContainerActivated(collection); + const isCollectionActivated = isObjectInDeltaModelActivated(collection); + + const currentRequiredColumnNames = collection.required ?? []; + const previousRequiredColumnNames = collection.role?.required ?? []; + + const columnNamesToAddNotNullConstraint = difference(currentRequiredColumnNames, previousRequiredColumnNames); + const columnNamesToRemoveNotNullConstraint = difference(previousRequiredColumnNames, currentRequiredColumnNames); + + const columns = toPairs(collection.properties ?? {}); + + const addNotNullConstraintScriptDtos = columns + .filter(([name, jsonSchema]) => { + const oldName = jsonSchema.compMod?.oldField?.name ?? name; + return ( + columnNamesToAddNotNullConstraint.includes(name) && + !columnNamesToRemoveNotNullConstraint.includes(oldName) + ); + }) + .map(([name, jsonSchema]) => { + const isActivated = isContainerActivated && isCollectionActivated && Boolean(jsonSchema.isActivated); + const script = setNotNullConstraint(fullTableName, wrapInQuotes(name)); + + return createAlterScriptDto([script], isActivated, false); + }); + + const dropNotNullConstraintScriptDtos = columns + .filter(([name, jsonSchema]) => { + const oldName = jsonSchema.compMod?.oldField?.name ?? name; + return ( + columnNamesToRemoveNotNullConstraint.includes(oldName) && + !columnNamesToAddNotNullConstraint.includes(name) + ); + }) + .map(([name, jsonSchema]) => { + const isActivated = isContainerActivated && isCollectionActivated && Boolean(jsonSchema.isActivated); + const script = dropNotNullConstraint(fullTableName, wrapInQuotes(name)); + + return createAlterScriptDto([script], isActivated, true); + }); + + return [...addNotNullConstraintScriptDtos, ...dropNotNullConstraintScriptDtos].filter( + scriptDto => scriptDto !== undefined, + ); +}; + +module.exports = { + getModifyNonNullColumnsScriptDtos, +}; diff --git a/forward_engineering/alterScript/alterScriptHelpers/containerHelpers/commentsHelper.js b/forward_engineering/alterScript/alterScriptHelpers/containerHelpers/commentsHelper.js new file mode 100644 index 0000000..a5821ec --- /dev/null +++ b/forward_engineering/alterScript/alterScriptHelpers/containerHelpers/commentsHelper.js @@ -0,0 +1,37 @@ +/** + * @import {AlterScriptDto} from '../../../types/alterScript' + * @import {PropertyPair} from '../../../types/ddlProvider' + */ + +const { createAlterScriptDto } = require('../../dto/alterScriptDto'); +const { + getSchemaCommentStatement, + dropSchemaCommentStatement, +} = require('../../../ddlProvider/ddlHelpers/comment/commentHelper'); + +/** + * Build the comment statement for a modified schema. + * + * @param {{ schemaName: string; compMod: { description?: PropertyPair }; isActivated: boolean }} params Schema + * name, its comparison data and activation flag. + * @returns {AlterScriptDto | undefined} Alter script DTO, or undefined when the comment did not change. + */ +const getModifiedCommentOnSchemaScriptDtos = ({ schemaName, compMod, isActivated }) => { + const description = compMod.description ?? {}; + + if (description.new && description.new !== description.old) { + const script = getSchemaCommentStatement({ schemaName, description: description.new }); + return createAlterScriptDto([script], isActivated, false); + } + + if (description.old && !description.new) { + const script = dropSchemaCommentStatement({ schemaName }); + return createAlterScriptDto([script], isActivated, true); + } + + return void 0; +}; + +module.exports = { + getModifiedCommentOnSchemaScriptDtos, +}; diff --git a/forward_engineering/alterScript/alterScriptHelpers/createColumnDefinition.js b/forward_engineering/alterScript/alterScriptHelpers/createColumnDefinition.js new file mode 100644 index 0000000..59600b4 --- /dev/null +++ b/forward_engineering/alterScript/alterScriptHelpers/createColumnDefinition.js @@ -0,0 +1,128 @@ +/** + * @import { + * AlterCollection, + * AlterColumn + * } from '../../types/alterScript' + * @import { + * ColumnDefinitionInput, + * DdlProvider, + * HydratedColumn, + * SchemaData + * } from '../../types/ddlProvider' + */ + +const isNumber = require('lodash/isNumber'); +const { getEntityName } = require('../../utils/general'); + +/** + * Resolve whether a column is nullable from the required list of its parent. + * + * @param {AlterCollection} parentJsonSchema Parent collection schema. + * @param {string} propertyName Column name. + * @returns {boolean} Whether the column is nullable. + */ +const isNullable = (parentJsonSchema, propertyName) => { + if (!Array.isArray(parentJsonSchema.required)) { + return true; + } + + return !parentJsonSchema.required.includes(propertyName); +}; + +/** + * Resolve the default value of a column. + * + * @param {AlterColumn} jsonSchema Column schema. + * @returns {string | number | boolean | undefined} Default value. + */ +const getDefault = jsonSchema => { + if (jsonSchema.default === null) { + return 'NULL'; + } + + return jsonSchema.default ?? undefined; +}; + +/** + * Resolve the length of a column. + * + * @param {AlterColumn} jsonSchema Column schema. + * @returns {number | undefined} Length. + */ +const getLength = jsonSchema => { + if (isNumber(jsonSchema.length)) { + return jsonSchema.length; + } + + if (isNumber(jsonSchema.maxLength)) { + return jsonSchema.maxLength; + } + + return void 0; +}; + +/** + * Resolve the precision of a column. + * + * @param {AlterColumn} jsonSchema Column schema. + * @returns {number | undefined} Precision. + */ +const getPrecision = jsonSchema => { + if (isNumber(jsonSchema.precision)) { + return jsonSchema.precision; + } + + if (isNumber(jsonSchema.fractSecPrecision)) { + return jsonSchema.fractSecPrecision; + } + + return void 0; +}; + +/** + * Resolve the type of a column, following user defined type references. + * + * @param {AlterColumn} jsonSchema Column schema. + * @returns {string} Column type. + */ +const getType = jsonSchema => { + if (jsonSchema.$ref) { + return jsonSchema.$ref.split('/').pop() ?? ''; + } + + return jsonSchema.mode ?? jsonSchema.childType ?? jsonSchema.type ?? ''; +}; + +/** + * Build a hydrated column definition out of a delta model column. + * + * @param {{ + * name: string; + * jsonSchema: AlterColumn; + * parentJsonSchema: AlterCollection; + * ddlProvider: DdlProvider; + * schemaData: SchemaData; + * }} params + * Column data. + * @returns {HydratedColumn} Hydrated column. + */ +const createColumnDefinitionBySchema = ({ name, jsonSchema, parentJsonSchema, ddlProvider, schemaData }) => { + /** @type {ColumnDefinitionInput} */ + const columnDefinition = { + name, + entityName: getEntityName(parentJsonSchema), + type: getType(jsonSchema), + nullable: isNullable(parentJsonSchema, name), + default: getDefault(jsonSchema), + length: getLength(jsonSchema), + scale: isNumber(jsonSchema.scale) ? jsonSchema.scale : undefined, + precision: getPrecision(jsonSchema), + isActivated: jsonSchema.isActivated, + }; + + return ddlProvider.hydrateColumn({ columnDefinition, jsonSchema, schemaData }); +}; + +module.exports = { + createColumnDefinitionBySchema, +}; diff --git a/forward_engineering/alterScript/alterScriptHelpers/entityHelpers/alterTableNameHelper.js b/forward_engineering/alterScript/alterScriptHelpers/entityHelpers/alterTableNameHelper.js new file mode 100644 index 0000000..1bd4680 --- /dev/null +++ b/forward_engineering/alterScript/alterScriptHelpers/entityHelpers/alterTableNameHelper.js @@ -0,0 +1,51 @@ +/** + * @import { + * AlterCollection, + * AlterScriptDto + * } from '../../../types/alterScript' + */ + +const { createAlterScriptDto } = require('../../dto/alterScriptDto'); +const { + isParentContainerActivated, + isObjectInDeltaModelActivated, + getSchemaOfAlterCollection, + getSchemaNameFromCollection, + getNamePrefixedWithSchemaName, + wrapInQuotes, +} = require('../../../utils/general'); +const { assignTemplates } = require('../../../utils/assignTemplates'); +const templates = require('../../../ddlProvider/templates'); + +/** + * Build the RENAME TABLE statement for a renamed collection. + * + * @param {AlterCollection} collection Collection delta. + * @returns {AlterScriptDto[]} Alter script DTOs. + */ +const getRenameTableScriptDtos = collection => { + const collectionSchema = getSchemaOfAlterCollection(collection); + const { old: oldName, new: newName } = collectionSchema.compMod?.collectionName ?? {}; + + if (!oldName || !newName || oldName === newName) { + return []; + } + + const schemaName = getSchemaNameFromCollection({ collection: collectionSchema }); + const isContainerActivated = isParentContainerActivated(collection); + const isCollectionActivated = isContainerActivated && isObjectInDeltaModelActivated(collection); + + const script = assignTemplates({ + template: templates.renameTable, + templateData: { + oldTableName: getNamePrefixedWithSchemaName({ name: oldName, schemaName }), + newTableName: wrapInQuotes(newName), + }, + }); + + return [createAlterScriptDto([script], isCollectionActivated, false)].filter(scriptDto => scriptDto !== undefined); +}; + +module.exports = { + getRenameTableScriptDtos, +}; diff --git a/forward_engineering/alterScript/alterScriptHelpers/entityHelpers/checkConstraintHelper.js b/forward_engineering/alterScript/alterScriptHelpers/entityHelpers/checkConstraintHelper.js new file mode 100644 index 0000000..ed9acbe --- /dev/null +++ b/forward_engineering/alterScript/alterScriptHelpers/entityHelpers/checkConstraintHelper.js @@ -0,0 +1,181 @@ +/** + * @import { + * AlterCollection, + * AlterScriptDto, + * CheckConstraintHistoryEntry + * } from '../../../types/alterScript' + */ + +const uniq = require('lodash/uniq'); +const { createAlterScriptDto } = require('../../dto/alterScriptDto'); +const { + getFullCollectionName, + wrapInQuotes, + isParentContainerActivated, + isObjectInDeltaModelActivated, + getSchemaOfAlterCollection, +} = require('../../../utils/general'); +const { assignTemplates } = require('../../../utils/assignTemplates'); +const templates = require('../../../ddlProvider/templates'); + +/** + * Build the ADD CHECK constraint statement. + * + * @param {{ tableName: string; constraintName: string; expression: string; enforced?: string }} params Statement parts. + * @returns {string} Alter statement. + */ +const addCheckConstraint = ({ tableName, constraintName, expression, enforced }) => { + return assignTemplates({ + template: templates.alterCheckConstraint, + templateData: { + tableName, + constraintName, + expression, + enforced: enforced ? ` ${enforced}` : '', + }, + }); +}; + +/** + * Build the DROP CHECK constraint statement. + * + * @param {{ tableName: string; constraintName: string }} params Statement parts. + * @returns {string} Alter statement. + */ +const dropCheckConstraint = ({ tableName, constraintName }) => { + return assignTemplates({ + template: templates.dropCheckConstraint, + templateData: { tableName, constraintName }, + }); +}; + +/** + * Pair the previous and current version of every check constraint, keyed by its name. + * + * @param {AlterCollection} collection Collection delta. + * @returns {CheckConstraintHistoryEntry[]} Constraint history. + */ +const mapCheckConstraintNamesToChangeHistory = collection => { + const checkConstraintHistory = collection.compMod?.chkConstr; + + if (!checkConstraintHistory) { + return []; + } + + const newConstraints = checkConstraintHistory.new ?? []; + const oldConstraints = checkConstraintHistory.old ?? []; + const constraintNames = uniq([...newConstraints, ...oldConstraints].map(constraint => constraint.chkConstrName)); + + return constraintNames.map(chkConstrName => ({ + old: oldConstraints.find(constraint => constraint.chkConstrName === chkConstrName), + new: newConstraints.find(constraint => constraint.chkConstrName === chkConstrName), + })); +}; + +/** + * Build the statements dropping constraints that no longer exist. + * + * @param {CheckConstraintHistoryEntry[]} constraintHistory Constraint history. + * @param {string} fullTableName Fully qualified table name. + * @returns {(AlterScriptDto | undefined)[]} Alter script DTOs. + */ +const getDropCheckConstraintScriptDtos = (constraintHistory, fullTableName) => { + return constraintHistory + .filter(historyEntry => historyEntry.old?.constrExpression && !historyEntry.new?.constrExpression) + .map(historyEntry => { + const script = dropCheckConstraint({ + tableName: fullTableName, + constraintName: wrapInQuotes(historyEntry.old?.chkConstrName ?? ''), + }); + + return createAlterScriptDto([script], true, true); + }); +}; + +/** + * Build the statements adding newly created constraints. + * + * @param {CheckConstraintHistoryEntry[]} constraintHistory Constraint history. + * @param {string} fullTableName Fully qualified table name. + * @returns {(AlterScriptDto | undefined)[]} Alter script DTOs. + */ +const getAddCheckConstraintScriptDtos = (constraintHistory, fullTableName) => { + return constraintHistory + .filter(historyEntry => historyEntry.new?.constrExpression && !historyEntry.old?.constrExpression) + .map(historyEntry => { + const script = addCheckConstraint({ + tableName: fullTableName, + constraintName: wrapInQuotes(historyEntry.new?.chkConstrName ?? ''), + expression: historyEntry.new?.constrExpression ?? '', + enforced: historyEntry.new?.constrEnforced, + }); + + return createAlterScriptDto([script], true, false); + }); +}; + +/** + * Build the statements recreating constraints whose expression or enforcement changed. Db2 for z/OS cannot alter a + * check constraint in place, so it has to be dropped and added again. + * + * @param {CheckConstraintHistoryEntry[]} constraintHistory Constraint history. + * @param {string} fullTableName Fully qualified table name. + * @returns {(AlterScriptDto | undefined)[]} Alter script DTOs. + */ +const getUpdateCheckConstraintScriptDtos = (constraintHistory, fullTableName) => { + return constraintHistory + .filter(historyEntry => { + if (!historyEntry.old?.constrExpression || !historyEntry.new?.constrExpression) { + return false; + } + + return ( + historyEntry.old.constrExpression !== historyEntry.new.constrExpression || + historyEntry.old.constrEnforced !== historyEntry.new.constrEnforced + ); + }) + .flatMap(historyEntry => { + const dropConstraintScript = dropCheckConstraint({ + tableName: fullTableName, + constraintName: wrapInQuotes(historyEntry.old?.chkConstrName ?? ''), + }); + const addConstraintScript = addCheckConstraint({ + tableName: fullTableName, + constraintName: wrapInQuotes(historyEntry.new?.chkConstrName ?? ''), + expression: historyEntry.new?.constrExpression ?? '', + enforced: historyEntry.new?.constrEnforced, + }); + + return [ + createAlterScriptDto([dropConstraintScript], true, true), + createAlterScriptDto([addConstraintScript], true, false), + ]; + }); +}; + +/** + * Build all check constraint statements for a collection. + * + * @param {AlterCollection} collection Collection delta. + * @returns {AlterScriptDto[]} Alter script DTOs. + */ +const getModifyCheckConstraintScriptDtos = collection => { + const collectionSchema = getSchemaOfAlterCollection(collection); + const fullTableName = getFullCollectionName({ collectionSchema }); + const constraintHistory = mapCheckConstraintNamesToChangeHistory(collection); + + const isContainerActivated = isParentContainerActivated(collection); + const isCollectionActivated = isContainerActivated && isObjectInDeltaModelActivated(collection); + + return [ + ...getAddCheckConstraintScriptDtos(constraintHistory, fullTableName), + ...getDropCheckConstraintScriptDtos(constraintHistory, fullTableName), + ...getUpdateCheckConstraintScriptDtos(constraintHistory, fullTableName), + ] + .filter(scriptDto => scriptDto !== undefined) + .map(scriptDto => ({ isActivated: isCollectionActivated, scripts: scriptDto.scripts })); +}; + +module.exports = { + getModifyCheckConstraintScriptDtos, +}; diff --git a/forward_engineering/alterScript/alterScriptHelpers/entityHelpers/commentsHelper.js b/forward_engineering/alterScript/alterScriptHelpers/entityHelpers/commentsHelper.js new file mode 100644 index 0000000..aec98c9 --- /dev/null +++ b/forward_engineering/alterScript/alterScriptHelpers/entityHelpers/commentsHelper.js @@ -0,0 +1,82 @@ +/** + * @import { + * AlterCollection, + * AlterScriptDto + * } from '../../../types/alterScript' + */ + +const { createAlterScriptDto } = require('../../dto/alterScriptDto'); +const { + isObjectInDeltaModelActivated, + isParentContainerActivated, + getSchemaOfAlterCollection, + getFullCollectionName, +} = require('../../../utils/general'); +const { + getTableCommentStatement, + dropTableCommentStatement, +} = require('../../../ddlProvider/ddlHelpers/comment/commentHelper'); + +/** + * Build the COMMENT ON TABLE statement when the description was set or changed. + * + * @param {AlterCollection} collection Collection delta. + * @returns {AlterScriptDto | undefined} Alter script DTO. + */ +const getUpdatedCommentOnCollectionScriptDto = collection => { + const { old: oldComment, new: newComment } = collection.role?.compMod?.description ?? {}; + + if (!newComment || newComment === oldComment) { + return void 0; + } + + const collectionSchema = getSchemaOfAlterCollection(collection); + const isContainerActivated = isParentContainerActivated(collection); + const isCollectionActivated = isContainerActivated && isObjectInDeltaModelActivated(collection); + + const script = getTableCommentStatement({ + tableName: getFullCollectionName({ collectionSchema }), + description: newComment, + }); + + return createAlterScriptDto([script], isCollectionActivated, false); +}; + +/** + * Build the statement removing the table comment when the description was cleared. + * + * @param {AlterCollection} collection Collection delta. + * @returns {AlterScriptDto | undefined} Alter script DTO. + */ +const getDeletedCommentOnCollectionScriptDto = collection => { + const { old: oldComment, new: newComment } = collection.role?.compMod?.description ?? {}; + + if (!oldComment || newComment) { + return void 0; + } + + const collectionSchema = getSchemaOfAlterCollection(collection); + const isContainerActivated = isParentContainerActivated(collection); + const isCollectionActivated = isContainerActivated && isObjectInDeltaModelActivated(collection); + + const script = dropTableCommentStatement({ tableName: getFullCollectionName({ collectionSchema }) }); + + return createAlterScriptDto([script], isCollectionActivated, true); +}; + +/** + * Build all table comment statements for a collection. + * + * @param {AlterCollection} collection Collection delta. + * @returns {AlterScriptDto[]} Alter script DTOs. + */ +const getModifyEntityCommentsScriptDtos = collection => { + const updatedCommentScriptDto = getUpdatedCommentOnCollectionScriptDto(collection); + const deletedCommentScriptDto = getDeletedCommentOnCollectionScriptDto(collection); + + return [updatedCommentScriptDto, deletedCommentScriptDto].filter(scriptDto => scriptDto !== undefined); +}; + +module.exports = { + getModifyEntityCommentsScriptDtos, +}; diff --git a/forward_engineering/alterScript/alterScriptHelpers/entityHelpers/indexesHelper.js b/forward_engineering/alterScript/alterScriptHelpers/entityHelpers/indexesHelper.js new file mode 100644 index 0000000..fbd130b --- /dev/null +++ b/forward_engineering/alterScript/alterScriptHelpers/entityHelpers/indexesHelper.js @@ -0,0 +1,247 @@ +/** + * @import { + * AlterCollection, + * AlterIndex, + * AlterScriptDto + * } from '../../../types/alterScript' + * @import {DdlProvider} from '../../../types/ddlProvider' + */ + +const isEqual = require('lodash/isEqual'); +const { createAlterScriptDto } = require('../../dto/alterScriptDto'); +const { + getSchemaNameFromCollection, + getNamePrefixedWithSchemaName, + wrapInQuotes, + getSchemaOfAlterCollection, + getEntityName, + isObjectInDeltaModelActivated, +} = require('../../../utils/general'); +const { assignTemplates } = require('../../../utils/assignTemplates'); +const templates = require('../../../ddlProvider/templates'); +const { getModifyIndexCommentsScriptDtos } = require('../indexHelpers/commentsHelper'); +const { addNameToIndexKey } = require('../indexHelpers/addNameToIndexKey'); + +/** + * Db2 for z/OS can only rename an index and change its comment in place. Every other property is part of CREATE INDEX + * and therefore forces a drop and recreate. + * + * @type {(keyof AlterIndex)[]} + */ +const DROP_AND_RECREATE_INDEX_PROPERTIES = [ + 'indxType', + 'indxKey', + 'indxIncludeKey', + 'indxCompress', + 'indxNullKeys', + 'indxCluster', + 'indxPartitioned', + 'indxPadded', + 'indxUsingType', + 'indxStogroup', + 'indxVcat', + 'indxPriQty', + 'indxSecQty', + 'indxErase', + 'indxFreepage', + 'indxPctfree', + 'indxDefine', + 'indxBufferPool', + 'indxClose', + 'indxDefer', + 'indxCopy', + 'indxPiecesize', + 'indxPiecesizeUnit', + 'indxProperties', +]; + +/** + * Check whether an index has to be dropped and recreated. + * + * @param {{ oldIndex: AlterIndex; newIndex: AlterIndex }} params Index versions. + * @returns {boolean} Whether the index has to be recreated. + */ +const shouldDropAndRecreateIndex = ({ oldIndex, newIndex }) => + DROP_AND_RECREATE_INDEX_PROPERTIES.some(property => !isEqual(oldIndex[property], newIndex[property])); + +/** + * Check whether two index versions describe the same database index. + * + * @param {{ oldIndex: AlterIndex; newIndex: AlterIndex }} params Index versions. + * @returns {boolean} Whether both describe the same index. + */ +const isSameIndex = ({ oldIndex, newIndex }) => + (Boolean(oldIndex.id) && oldIndex.id === newIndex.id) || oldIndex.indxName === newIndex.indxName; + +/** + * Build the RENAME INDEX statement. + * + * @param {{ schemaName?: string; oldIndexName: string; newIndexName: string; isActivated: boolean }} params Rename + * parts. + * @returns {AlterScriptDto | undefined} Alter script DTO. + */ +const getRenameIndexScriptDto = ({ schemaName, oldIndexName, newIndexName, isActivated }) => { + const script = assignTemplates({ + template: templates.renameIndex, + templateData: { + oldIndexName: getNamePrefixedWithSchemaName({ name: oldIndexName, schemaName }), + newIndexName: wrapInQuotes(newIndexName), + }, + }); + + return createAlterScriptDto([script], isActivated, false); +}; + +/** + * Build the CREATE INDEX statement for an index of a collection. + * + * @param {{ index: AlterIndex; collection: AlterCollection; ddlProvider: DdlProvider }} params Index, its collection + * and the DDL provider. + * @returns {AlterScriptDto | undefined} Alter script DTO. + */ +const getCreateIndexScriptDto = ({ index, collection, ddlProvider }) => { + const collectionSchema = getSchemaOfAlterCollection(collection); + // `createIndex` comments the statement out unless both flags are set, and only `hydrateIndex` - which the alter + // flow does not go through - would otherwise fill `isParentActivated` in. + const isCollectionActivated = isObjectInDeltaModelActivated(collection); + const script = ddlProvider.createIndex(getEntityName(collectionSchema), { + ...addNameToIndexKey({ index, collection }), + isParentActivated: isCollectionActivated, + }); + + return createAlterScriptDto([script], isCollectionActivated && Boolean(index.isActivated), false); +}; + +/** + * Build the DROP INDEX statement for an index of a collection. + * + * @param {{ index: AlterIndex; collection: AlterCollection; ddlProvider: DdlProvider }} params Index, its collection + * and the DDL provider. + * @returns {AlterScriptDto | undefined} Alter script DTO. + */ +const getDeleteIndexScriptDto = ({ index, collection, ddlProvider }) => { + const fullIndexName = getNamePrefixedWithSchemaName({ + name: index.indxName ?? '', + schemaName: getSchemaNameFromCollection({ collection }), + }); + const script = ddlProvider.dropIndex(fullIndexName); + const isActivated = Boolean(index.isActivated) && isObjectInDeltaModelActivated(collection); + + return createAlterScriptDto([script], isActivated, true); +}; + +/** + * Read the previous and current indexes of a collection. + * + * @param {AlterCollection} collection Collection delta. + * @returns {{ oldIndexes: AlterIndex[]; newIndexes: AlterIndex[] }} Indexes. + */ +const getIndexDelta = collection => ({ + oldIndexes: collection.role?.compMod?.Indxs?.old ?? [], + newIndexes: collection.role?.compMod?.Indxs?.new ?? [], +}); + +/** + * Build the statements creating the indexes that appeared. + * + * @param {{ collection: AlterCollection; ddlProvider: DdlProvider }} params Collection delta and DDL provider. + * @returns {(AlterScriptDto | undefined)[]} Alter script DTOs. + */ +const getAddedIndexesScriptDtos = ({ collection, ddlProvider }) => { + const { oldIndexes } = getIndexDelta(collection); + const currentIndexes = collection.role?.Indxs ?? []; + + return currentIndexes + .filter(newIndex => !oldIndexes.some(oldIndex => isSameIndex({ oldIndex, newIndex }))) + .map(index => getCreateIndexScriptDto({ index, collection, ddlProvider })); +}; + +/** + * Build the statements dropping the indexes that disappeared. + * + * @param {{ collection: AlterCollection; ddlProvider: DdlProvider }} params Collection delta and DDL provider. + * @returns {(AlterScriptDto | undefined)[]} Alter script DTOs. + */ +const getDeletedIndexesScriptDtos = ({ collection, ddlProvider }) => { + const { oldIndexes, newIndexes } = getIndexDelta(collection); + + return oldIndexes + .filter(oldIndex => !newIndexes.some(newIndex => isSameIndex({ oldIndex, newIndex }))) + .map(index => getDeleteIndexScriptDto({ index, collection, ddlProvider })); +}; + +/** + * Build the statements for a single modified index. + * + * @param {{ + * newIndex: AlterIndex; + * oldIndex: AlterIndex; + * collection: AlterCollection; + * ddlProvider: DdlProvider; + * }} params + * Index versions, their collection and the DDL provider. + * @returns {(AlterScriptDto | undefined)[]} Alter script DTOs. + */ +const getModifyIndexScriptDtos = ({ newIndex, oldIndex, collection, ddlProvider }) => { + if (shouldDropAndRecreateIndex({ newIndex, oldIndex })) { + return [ + getDeleteIndexScriptDto({ index: oldIndex, collection, ddlProvider }), + getCreateIndexScriptDto({ index: newIndex, collection, ddlProvider }), + ]; + } + + const scriptDtos = []; + + if (oldIndex.indxName !== newIndex.indxName) { + scriptDtos.push( + getRenameIndexScriptDto({ + schemaName: getSchemaNameFromCollection({ collection }), + oldIndexName: oldIndex.indxName ?? '', + newIndexName: newIndex.indxName ?? '', + isActivated: isObjectInDeltaModelActivated(collection) && Boolean(newIndex.isActivated), + }), + ); + } + + scriptDtos.push(getModifyIndexCommentsScriptDtos({ newIndex, oldIndex, collection })); + + return scriptDtos; +}; + +/** + * Build the statements for every index that exists on both sides of the diff. + * + * @param {{ collection: AlterCollection; ddlProvider: DdlProvider }} params Collection delta and DDL provider. + * @returns {(AlterScriptDto | undefined)[]} Alter script DTOs. + */ +const getModifiedIndexesScriptDtos = ({ collection, ddlProvider }) => { + const { oldIndexes, newIndexes } = getIndexDelta(collection); + + return newIndexes.flatMap(newIndex => { + const oldIndex = oldIndexes.find(index => isSameIndex({ oldIndex: index, newIndex })); + + if (!oldIndex) { + return []; + } + + return getModifyIndexScriptDtos({ newIndex, oldIndex, collection, ddlProvider }); + }); +}; + +/** + * Build all index statements for a collection. + * + * @param {{ ddlProvider: DdlProvider; collection: AlterCollection }} params DDL provider and collection delta. + * @returns {AlterScriptDto[]} Alter script DTOs. + */ +const getModifyIndexesScriptDtos = ({ ddlProvider, collection }) => { + return [ + ...getDeletedIndexesScriptDtos({ collection, ddlProvider }), + ...getAddedIndexesScriptDtos({ collection, ddlProvider }), + ...getModifiedIndexesScriptDtos({ collection, ddlProvider }), + ].filter(scriptDto => scriptDto !== undefined); +}; + +module.exports = { + getModifyIndexesScriptDtos, +}; diff --git a/forward_engineering/alterScript/alterScriptHelpers/entityHelpers/keyConstraintsHelper.js b/forward_engineering/alterScript/alterScriptHelpers/entityHelpers/keyConstraintsHelper.js new file mode 100644 index 0000000..11d912d --- /dev/null +++ b/forward_engineering/alterScript/alterScriptHelpers/entityHelpers/keyConstraintsHelper.js @@ -0,0 +1,522 @@ +/** + * Diffing of primary and unique key constraints. + * + * Hackolade models a key that spans a single column either inline on that column or as a composite key holding one + * column. Both representations produce the same DDL, so moving between them must not recreate the constraint unless the + * key options changed as well. That is what the transition checks below establish. + * + * @import { + * AlterCollection, + * AlterColumn, + * AlterKeyKind, + * AlterScriptDto, + * ComparableKeyOptions, + * KeyScriptModification, + * KeyTransition + * } from '../../../types/alterScript' + * @import { + * CompositeKeyGroup, + * KeyConstraintColumn + * } from '../../../types/ddlProvider' + */ + +const differenceWith = require('lodash/differenceWith'); +const isEqual = require('lodash/isEqual'); +const orderBy = require('lodash/orderBy'); +const toPairs = require('lodash/toPairs'); +const { createAlterScriptDto } = require('../../dto/alterScriptDto'); +const { createKeyScriptModification, keyTransition, noKeyTransition } = require('../../dto/keyDto'); +const { + getFullCollectionName, + getSchemaOfAlterCollection, + getEntityName, + wrapInQuotes, + isParentContainerActivated, + isObjectInDeltaModelActivated, +} = require('../../../utils/general'); +const { getDefaultConstraintName } = require('../../../ddlProvider/ddlHelpers/key/getDefaultConstraintName'); + +const AMOUNT_OF_COLUMNS_IN_REGULAR_KEY = 1; + +/** + * Build the constraint name to fall back on when the user did not name a key. + * + * @param {string} entityName Table name. + * @param {AlterKeyKind} keyKind Key kind. + * @returns {string} Constraint name. + */ +const getKeyKindDefaultConstraintName = (entityName, keyKind) => + getDefaultConstraintName({ entityName, postfix: keyKind.constraintPostfix }); + +/** + * Keep only the options that end up in the generated DDL. + * + * @param {{ id?: string; constraintName?: string } | undefined} optionHolder Key or key options. + * @returns {ComparableKeyOptions} Comparable options. + */ +const extractComparableOptions = optionHolder => ({ + constraintName: optionHolder?.constraintName, + id: optionHolder?.id, +}); + +/** + * Read the comparable options of a key declared inline on a column. + * + * @param {AlterColumn} columnJsonSchema Column schema. + * @param {AlterKeyKind} keyKind Key kind. + * @returns {ComparableKeyOptions} Comparable options. + */ +const getRegularKeyOptions = (columnJsonSchema, keyKind) => + extractComparableOptions(columnJsonSchema[keyKind.keyOptionsProperty]); + +/** + * Check whether a column holds the key inline rather than as part of a composite key. + * + * @param {AlterColumn | undefined} columnJsonSchema Column schema. + * @param {AlterKeyKind} keyKind Key kind. + * @returns {boolean} Whether the key is declared inline. + */ +const isRegularKey = (columnJsonSchema, keyKind) => + Boolean(columnJsonSchema?.[keyKind.inlineKeyProperty]) && !columnJsonSchema?.[keyKind.compositeKeyProperty]; + +/** + * Check whether a column takes part in the key in any representation. + * + * @param {AlterColumn | undefined} columnJsonSchema Column schema. + * @param {AlterKeyKind} keyKind Key kind. + * @returns {boolean} Whether the column is part of the key. + */ +const isAnyKey = (columnJsonSchema, keyKind) => + Boolean(columnJsonSchema?.[keyKind.inlineKeyProperty]) || Boolean(columnJsonSchema?.[keyKind.compositeKeyProperty]); + +/** + * Check whether a set of composite keys and an inline key describe the same constraint with the same options. + * + * @param {{ compositeKeys: CompositeKeyGroup[]; columnOptions: ComparableKeyOptions; keyKind: AlterKeyKind }} params + * Composite keys, inline key options and key kind. + * @returns {boolean} Whether the options are equal. + */ +const areKeyOptionsEqual = ({ compositeKeys, columnOptions, keyKind }) => + compositeKeys.some(compositeKey => { + if (compositeKey[keyKind.compositeKeyProperty]?.length !== AMOUNT_OF_COLUMNS_IN_REGULAR_KEY) { + return false; + } + + return isEqual(extractComparableOptions(compositeKey), columnOptions); + }); + +/** + * Read the previous and current composite keys of a collection. + * + * @param {AlterCollection} collection Collection delta. + * @param {AlterKeyKind} keyKind Key kind. + * @returns {{ oldKeys: CompositeKeyGroup[]; newKeys: CompositeKeyGroup[] }} Composite keys. + */ +const getCompositeKeyDelta = (collection, keyKind) => { + const keyDelta = collection.role?.compMod?.[keyKind.compModProperty] ?? {}; + + return { + oldKeys: keyDelta.old ?? [], + newKeys: keyDelta.new ?? [], + }; +}; + +/** + * Check whether a composite key holding exactly one column and an inline key on the same column describe the same + * constraint. + * + * @param {{ + * compositeKeys: CompositeKeyGroup[]; + * columns: Record; + * keyKind: AlterKeyKind; + * }} params + * Composite keys of one side of the diff, columns of the other side and key kind. + * @returns {KeyTransition} Transition result. + */ +const getCompositeKeyTransition = ({ compositeKeys, columns, keyKind }) => { + const idsOfColumns = compositeKeys.flatMap( + compositeKey => compositeKey[keyKind.compositeKeyProperty]?.map(keyRef => keyRef.keyId) ?? [], + ); + + if (idsOfColumns.length !== AMOUNT_OF_COLUMNS_IN_REGULAR_KEY) { + return noKeyTransition(); + } + + const columnJsonSchema = Object.values(columns).find(column => column.GUID === idsOfColumns[0]); + + if (!columnJsonSchema || !isRegularKey(columnJsonSchema, keyKind)) { + return noKeyTransition(); + } + + const columnOptions = getRegularKeyOptions(columnJsonSchema, keyKind); + + return keyTransition(!areKeyOptionsEqual({ compositeKeys, columnOptions, keyKind })); +}; + +/** + * Check whether the composite keys of a collection actually changed. + * + * @param {{ oldKeys: CompositeKeyGroup[]; newKeys: CompositeKeyGroup[] }} params Composite keys. + * @returns {boolean} Whether the keys changed. + */ +const didCompositeKeysChange = ({ oldKeys, newKeys }) => { + if (oldKeys.length === 0 && newKeys.length === 0) { + return false; + } + + if (oldKeys.length !== newKeys.length) { + return true; + } + + return differenceWith(oldKeys, newKeys, (oldKey, newKey) => isEqual(oldKey, newKey)).length > 0; +}; + +/** + * Resolve the columns of a composite key by the identifiers it references. + * + * @param {{ compositeKey: CompositeKeyGroup; columns: Record; keyKind: AlterKeyKind }} params + * Composite key, available columns and key kind. + * @returns {KeyConstraintColumn[]} Constraint columns. + */ +const getCompositeKeyColumns = ({ compositeKey, columns, keyKind }) => + toPairs(columns) + .filter(([, jsonSchema]) => + compositeKey[keyKind.compositeKeyProperty]?.some(keyRef => keyRef.keyId === jsonSchema.GUID), + ) + .map(([name, jsonSchema]) => ({ name, isActivated: jsonSchema.isActivated })); + +/** + * Build the statements adding the composite keys that appeared or changed. + * + * @param {{ collection: AlterCollection; keyKind: AlterKeyKind }} params Collection delta and key kind. + * @returns {KeyScriptModification[]} Key statements. + */ +const getAddCompositeKeyScriptModifications = ({ collection, keyKind }) => { + const { oldKeys, newKeys } = getCompositeKeyDelta(collection, keyKind); + const transition = getCompositeKeyTransition({ + compositeKeys: newKeys, + columns: collection.role?.properties ?? {}, + keyKind, + }); + + if (transition.didTransitionHappen && !transition.wasKeyChangedInTransition) { + return []; + } + + if (!didCompositeKeysChange({ oldKeys, newKeys })) { + return []; + } + + const collectionSchema = getSchemaOfAlterCollection(collection); + const fullTableName = getFullCollectionName({ collectionSchema }); + const entityName = getEntityName(collectionSchema); + const isCollectionActivated = isParentContainerActivated(collection) && isObjectInDeltaModelActivated(collection); + + return newKeys + .map(compositeKey => { + const columns = getCompositeKeyColumns({ + compositeKey, + columns: collection.role?.properties ?? {}, + keyKind, + }); + + if (columns.length === 0) { + return void 0; + } + + const statement = keyKind.buildAlterStatement({ + tableName: fullTableName, + isParentActivated: isCollectionActivated, + keyConfig: { + keyType: keyKind.keyType, + name: compositeKey.constraintName ?? getKeyKindDefaultConstraintName(entityName, keyKind), + columns, + }, + }); + + return createKeyScriptModification({ + script: statement.statement, + fullTableName, + isDropScript: false, + isActivated: statement.isActivated, + }); + }) + .filter(scriptDto => scriptDto !== undefined) + .filter(scriptDto => Boolean(scriptDto.script)); +}; + +/** + * Build the statements dropping the composite keys that disappeared or changed. + * + * @param {{ collection: AlterCollection; keyKind: AlterKeyKind }} params Collection delta and key kind. + * @returns {KeyScriptModification[]} Key statements. + */ +const getDropCompositeKeyScriptModifications = ({ collection, keyKind }) => { + const { oldKeys, newKeys } = getCompositeKeyDelta(collection, keyKind); + const transition = getCompositeKeyTransition({ + compositeKeys: oldKeys, + columns: collection.properties ?? {}, + keyKind, + }); + + if (transition.didTransitionHappen && !transition.wasKeyChangedInTransition) { + return []; + } + + if (!didCompositeKeysChange({ oldKeys, newKeys })) { + return []; + } + + const collectionSchema = getSchemaOfAlterCollection(collection); + const fullTableName = getFullCollectionName({ collectionSchema }); + const entityName = getEntityName(collectionSchema); + const isCollectionActivated = isParentContainerActivated(collection) && isObjectInDeltaModelActivated(collection); + + return oldKeys + .map(compositeKey => { + const constraintName = compositeKey.constraintName ?? getKeyKindDefaultConstraintName(entityName, keyKind); + const script = keyKind.buildDropStatement({ + tableName: fullTableName, + constraintName: wrapInQuotes(constraintName), + }); + + return createKeyScriptModification({ + script, + fullTableName, + isDropScript: true, + isActivated: isCollectionActivated, + }); + }) + .filter(scriptDto => Boolean(scriptDto.script)); +}; + +/** + * Check whether a column that holds the key inline used to be part of a composite key with different options. + * + * @param {{ columnJsonSchema: AlterColumn; collection: AlterCollection; keyKind: AlterKeyKind }} params Column, its + * collection and the key kind. + * @returns {KeyTransition} Transition result. + */ +const getRegularKeyTransitionFromComposite = ({ columnJsonSchema, collection, keyKind }) => { + const oldName = columnJsonSchema.compMod?.oldField?.name ?? ''; + const oldColumnJsonSchema = collection.role?.properties?.[oldName]; + + if (!isRegularKey(columnJsonSchema, keyKind) || !isAnyKey(oldColumnJsonSchema, keyKind)) { + return noKeyTransition(); + } + + const { oldKeys, newKeys } = getCompositeKeyDelta(collection, keyKind); + const wasCompositeKey = oldKeys.some(compositeKey => + compositeKey[keyKind.compositeKeyProperty]?.some(keyRef => keyRef.keyId === oldColumnJsonSchema?.GUID), + ); + const isCompositeKey = newKeys.some(compositeKey => + compositeKey[keyKind.compositeKeyProperty]?.some(keyRef => keyRef.keyId === columnJsonSchema.GUID), + ); + + if (!wasCompositeKey || isCompositeKey) { + return noKeyTransition(); + } + + const columnOptions = getRegularKeyOptions(columnJsonSchema, keyKind); + + return keyTransition(!areKeyOptionsEqual({ compositeKeys: oldKeys, columnOptions, keyKind })); +}; + +/** + * Check whether a column that used to hold the key inline became part of a composite key with different options. + * + * @param {{ columnJsonSchema: AlterColumn; collection: AlterCollection; keyKind: AlterKeyKind }} params Column, its + * collection and the key kind. + * @returns {KeyTransition} Transition result. + */ +const getRegularKeyTransitionToComposite = ({ columnJsonSchema, collection, keyKind }) => { + const oldName = columnJsonSchema.compMod?.oldField?.name ?? ''; + const oldColumnJsonSchema = collection.role?.properties?.[oldName]; + + if (!isRegularKey(oldColumnJsonSchema, keyKind) || !isAnyKey(columnJsonSchema, keyKind)) { + return noKeyTransition(); + } + + const { oldKeys, newKeys } = getCompositeKeyDelta(collection, keyKind); + const wasCompositeKey = oldKeys.some(compositeKey => + compositeKey[keyKind.compositeKeyProperty]?.some(keyRef => keyRef.keyId === oldColumnJsonSchema?.GUID), + ); + const isCompositeKey = newKeys.some(compositeKey => + compositeKey[keyKind.compositeKeyProperty]?.some(keyRef => keyRef.keyId === columnJsonSchema.GUID), + ); + + if (!isCompositeKey || wasCompositeKey) { + return noKeyTransition(); + } + + const columnOptions = getRegularKeyOptions(oldColumnJsonSchema ?? {}, keyKind); + + return keyTransition(!areKeyOptionsEqual({ compositeKeys: newKeys, columnOptions, keyKind })); +}; + +/** + * Check whether the options of a key that stayed inline changed. + * + * @param {{ columnJsonSchema: AlterColumn; collection: AlterCollection; keyKind: AlterKeyKind }} params Column, its + * collection and the key kind. + * @returns {boolean} Whether the key has to be recreated. + */ +const wasRegularKeyModified = ({ columnJsonSchema, collection, keyKind }) => { + const oldName = columnJsonSchema.compMod?.oldField?.name ?? ''; + const oldColumnJsonSchema = collection.role?.properties?.[oldName]; + + if (!isRegularKey(columnJsonSchema, keyKind) || !isRegularKey(oldColumnJsonSchema, keyKind)) { + return false; + } + + return !isEqual( + getRegularKeyOptions(oldColumnJsonSchema ?? {}, keyKind), + getRegularKeyOptions(columnJsonSchema, keyKind), + ); +}; + +/** + * Build the statements adding the inline keys that appeared or changed. + * + * @param {{ collection: AlterCollection; keyKind: AlterKeyKind }} params Collection delta and key kind. + * @returns {KeyScriptModification[]} Key statements. + */ +const getAddRegularKeyScriptModifications = ({ collection, keyKind }) => { + const collectionSchema = getSchemaOfAlterCollection(collection); + const fullTableName = getFullCollectionName({ collectionSchema }); + const entityName = getEntityName(collectionSchema); + const isCollectionActivated = isParentContainerActivated(collection) && isObjectInDeltaModelActivated(collection); + + return toPairs(collection.properties ?? {}) + .filter(([, columnJsonSchema]) => { + const oldName = columnJsonSchema.compMod?.oldField?.name ?? ''; + const oldColumnJsonSchema = collection.role?.properties?.[oldName]; + + if (isRegularKey(columnJsonSchema, keyKind) && !isAnyKey(oldColumnJsonSchema, keyKind)) { + return true; + } + + const transition = getRegularKeyTransitionFromComposite({ columnJsonSchema, collection, keyKind }); + + if (transition.didTransitionHappen) { + return Boolean(transition.wasKeyChangedInTransition); + } + + return wasRegularKeyModified({ columnJsonSchema, collection, keyKind }); + }) + .map(([name, columnJsonSchema]) => { + const configuredConstraintName = columnJsonSchema[keyKind.keyOptionsProperty]?.constraintName?.trim(); + const constraintName = + configuredConstraintName === undefined || configuredConstraintName === '' + ? getKeyKindDefaultConstraintName(entityName, keyKind) + : configuredConstraintName; + const statement = keyKind.buildAlterStatement({ + tableName: fullTableName, + isParentActivated: isCollectionActivated, + keyConfig: { + keyType: keyKind.keyType, + name: constraintName, + columns: [{ name, isActivated: columnJsonSchema.isActivated }], + options: columnJsonSchema[keyKind.keyOptionsProperty], + }, + }); + + return createKeyScriptModification({ + script: statement.statement, + fullTableName, + isDropScript: false, + isActivated: statement.isActivated, + }); + }) + .filter(scriptDto => Boolean(scriptDto.script)); +}; + +/** + * Build the statements dropping the inline keys that disappeared or changed. + * + * @param {{ collection: AlterCollection; keyKind: AlterKeyKind }} params Collection delta and key kind. + * @returns {KeyScriptModification[]} Key statements. + */ +const getDropRegularKeyScriptModifications = ({ collection, keyKind }) => { + const collectionSchema = getSchemaOfAlterCollection(collection); + const fullTableName = getFullCollectionName({ collectionSchema }); + const entityName = getEntityName(collectionSchema); + const isCollectionActivated = isParentContainerActivated(collection) && isObjectInDeltaModelActivated(collection); + + return toPairs(collection.properties ?? {}) + .filter(([, columnJsonSchema]) => { + const oldName = columnJsonSchema.compMod?.oldField?.name ?? ''; + const oldColumnJsonSchema = collection.role?.properties?.[oldName]; + + if (isRegularKey(oldColumnJsonSchema, keyKind) && !isAnyKey(columnJsonSchema, keyKind)) { + return true; + } + + const transition = getRegularKeyTransitionToComposite({ columnJsonSchema, collection, keyKind }); + + if (transition.didTransitionHappen) { + return Boolean(transition.wasKeyChangedInTransition); + } + + return wasRegularKeyModified({ columnJsonSchema, collection, keyKind }); + }) + .map(([, columnJsonSchema]) => { + const oldName = columnJsonSchema.compMod?.oldField?.name ?? ''; + const oldColumnJsonSchema = collection.role?.properties?.[oldName]; + const configuredConstraintName = oldColumnJsonSchema?.[keyKind.keyOptionsProperty]?.constraintName?.trim(); + const constraintName = + configuredConstraintName === undefined || configuredConstraintName === '' + ? getKeyKindDefaultConstraintName(entityName, keyKind) + : configuredConstraintName; + const script = keyKind.buildDropStatement({ + tableName: fullTableName, + constraintName: wrapInQuotes(constraintName), + }); + + return createKeyScriptModification({ + script, + fullTableName, + isDropScript: true, + isActivated: isCollectionActivated, + }); + }) + .filter(scriptDto => Boolean(scriptDto.script)); +}; + +/** + * Order the statements so that, for a given table, the drop of a key always precedes its recreation. + * + * @param {KeyScriptModification[]} keyScriptModifications Key statements. + * @returns {KeyScriptModification[]} Ordered key statements. + */ +const sortKeyScriptModifications = keyScriptModifications => + orderBy( + keyScriptModifications, + [modification => modification.fullTableName, modification => !modification.isDropScript], + ['asc', 'asc'], + ); + +/** + * Build all statements for one key kind of a collection. + * + * @param {{ collection: AlterCollection; keyKind: AlterKeyKind }} params Collection delta and key kind. + * @returns {AlterScriptDto[]} Alter script DTOs. + */ +const getModifyKeyConstraintsScriptDtos = ({ collection, keyKind }) => { + const keyScriptModifications = [ + ...getDropCompositeKeyScriptModifications({ collection, keyKind }), + ...getAddCompositeKeyScriptModifications({ collection, keyKind }), + ...getDropRegularKeyScriptModifications({ collection, keyKind }), + ...getAddRegularKeyScriptModifications({ collection, keyKind }), + ]; + + return sortKeyScriptModifications(keyScriptModifications) + .map(modification => + createAlterScriptDto([modification.script], modification.isActivated, modification.isDropScript), + ) + .filter(scriptDto => scriptDto !== undefined); +}; + +module.exports = { + getModifyKeyConstraintsScriptDtos, +}; diff --git a/forward_engineering/alterScript/alterScriptHelpers/entityHelpers/primaryKeyHelper.js b/forward_engineering/alterScript/alterScriptHelpers/entityHelpers/primaryKeyHelper.js new file mode 100644 index 0000000..bc17d12 --- /dev/null +++ b/forward_engineering/alterScript/alterScriptHelpers/entityHelpers/primaryKeyHelper.js @@ -0,0 +1,59 @@ +/** + * @import { + * AlterCollection, + * AlterKeyKind, + * AlterScriptDto + * } from '../../../types/alterScript' + * @import { + * AlterKeyConfig, + * AlterKeyStatement + * } from '../../../types/ddlProvider' + */ + +const { alterPkConstraint, dropPK } = require('../../../ddlProvider/ddlHelpers/key/constraintsHelper'); +const { KEY_TYPE } = require('../../../ddlProvider/ddlHelpers/key/keyHelper'); +const { CONSTRAINT_POSTFIX } = require('../../../../shared/constants/constants'); +const { getModifyKeyConstraintsScriptDtos } = require('./keyConstraintsHelper'); + +/** + * Build an ADD PRIMARY KEY statement. + * + * @param {{ tableName: string; isParentActivated: boolean; keyConfig: AlterKeyConfig }} params Statement data. + * @returns {AlterKeyStatement} Key statement. + */ +const buildAlterStatement = ({ tableName, isParentActivated, keyConfig }) => + alterPkConstraint(tableName, isParentActivated, keyConfig); + +/** + * Build a DROP PRIMARY KEY statement. + * + * @param {{ tableName: string }} params Statement data. + * @returns {string} Drop statement. + */ +const buildDropStatement = ({ tableName }) => dropPK(tableName); + +/** @type {AlterKeyKind} */ +const PRIMARY_KEY_KIND = { + keyType: KEY_TYPE.primaryKey, + constraintPostfix: CONSTRAINT_POSTFIX.primaryKey, + compModProperty: 'primaryKey', + compositeKeyProperty: 'compositePrimaryKey', + inlineKeyProperty: 'primaryKey', + keyOptionsProperty: 'primaryKeyOptions', + buildAlterStatement, + // A table has at most one primary key, so Db2 for z/OS drops it without naming the constraint. + buildDropStatement, +}; + +/** + * Build all primary key statements for a collection. + * + * @param {AlterCollection} collection Collection delta. + * @returns {AlterScriptDto[]} Alter script DTOs. + */ +const getModifyPkConstraintsScriptDtos = collection => + getModifyKeyConstraintsScriptDtos({ collection, keyKind: PRIMARY_KEY_KIND }); + +module.exports = { + getModifyPkConstraintsScriptDtos, +}; diff --git a/forward_engineering/alterScript/alterScriptHelpers/entityHelpers/uniqueKeyHelper.js b/forward_engineering/alterScript/alterScriptHelpers/entityHelpers/uniqueKeyHelper.js new file mode 100644 index 0000000..9658233 --- /dev/null +++ b/forward_engineering/alterScript/alterScriptHelpers/entityHelpers/uniqueKeyHelper.js @@ -0,0 +1,58 @@ +/** + * @import { + * AlterCollection, + * AlterKeyKind, + * AlterScriptDto + * } from '../../../types/alterScript' + * @import { + * AlterKeyConfig, + * AlterKeyStatement + * } from '../../../types/ddlProvider' + */ + +const { alterUkConstraint, dropUkConstraint } = require('../../../ddlProvider/ddlHelpers/key/constraintsHelper'); +const { KEY_TYPE } = require('../../../ddlProvider/ddlHelpers/key/keyHelper'); +const { CONSTRAINT_POSTFIX } = require('../../../../shared/constants/constants'); +const { getModifyKeyConstraintsScriptDtos } = require('./keyConstraintsHelper'); + +/** + * Build an ADD UNIQUE statement. + * + * @param {{ tableName: string; isParentActivated: boolean; keyConfig: AlterKeyConfig }} params Statement data. + * @returns {AlterKeyStatement} Key statement. + */ +const buildAlterStatement = ({ tableName, isParentActivated, keyConfig }) => + alterUkConstraint(tableName, isParentActivated, keyConfig); + +/** + * Build a DROP UNIQUE statement. + * + * @param {{ tableName: string; constraintName: string }} params Statement data. + * @returns {string} Drop statement. + */ +const buildDropStatement = ({ tableName, constraintName }) => dropUkConstraint(tableName, constraintName); + +/** @type {AlterKeyKind} */ +const UNIQUE_KEY_KIND = { + keyType: KEY_TYPE.unique, + constraintPostfix: CONSTRAINT_POSTFIX.uniqueKey, + compModProperty: 'uniqueKey', + compositeKeyProperty: 'compositeUniqueKey', + inlineKeyProperty: 'unique', + keyOptionsProperty: 'uniqueKeyOptions', + buildAlterStatement, + buildDropStatement, +}; + +/** + * Build all unique key statements for a collection. + * + * @param {AlterCollection} collection Collection delta. + * @returns {AlterScriptDto[]} Alter script DTOs. + */ +const getModifyUkConstraintsScriptDtos = collection => + getModifyKeyConstraintsScriptDtos({ collection, keyKind: UNIQUE_KEY_KIND }); + +module.exports = { + getModifyUkConstraintsScriptDtos, +}; diff --git a/forward_engineering/alterScript/alterScriptHelpers/indexHelpers/addNameToIndexKey.js b/forward_engineering/alterScript/alterScriptHelpers/indexHelpers/addNameToIndexKey.js new file mode 100644 index 0000000..cb19dd1 --- /dev/null +++ b/forward_engineering/alterScript/alterScriptHelpers/indexHelpers/addNameToIndexKey.js @@ -0,0 +1,71 @@ +/** + * @import { + * AlterCollection, + * AlterIndex, + * AlterIndexKey + * } from '../../../types/alterScript' + */ + +const toPairs = require('lodash/toPairs'); +const { getSchemaNameFromCollection } = require('../../../utils/general'); + +/** + * Resolve a column name by the identifier an index key refers to. Deleted collections only keep their columns in + * `oldProperties`, so both places have to be considered. + * + * @param {{ columnId?: string; collection: AlterCollection }} params Column identifier and its collection. + * @returns {string | undefined} Column name. + */ +const getColumnNameById = ({ columnId, collection }) => { + const columns = collection.role?.properties ?? collection.properties ?? {}; + const namedColumn = toPairs(columns).find(([, jsonSchema]) => jsonSchema.GUID === columnId); + + if (namedColumn) { + return namedColumn[0]; + } + + return collection.role?.compMod?.oldProperties?.find(property => property.id === columnId)?.name; +}; + +/** + * Resolve the column names of an index key list. + * + * @param {{ keys?: AlterIndexKey[]; collection: AlterCollection }} params Index keys and their collection. + * @returns {AlterIndexKey[]} Index keys with resolved names. + */ +const mapKeysWithNames = ({ keys, collection }) => { + if (!keys?.length) { + return keys ?? []; + } + + return keys + .map(key => { + const name = key.name ?? getColumnNameById({ columnId: key.keyId, collection }); + + return { keyId: key.keyId, type: key.type, isActivated: key.isActivated, name }; + }) + .filter(key => Boolean(key.name)); +}; + +/** + * Resolve the column names of an index, which the delta model only references by identifier. + * + * @param {{ index: AlterIndex; collection: AlterCollection }} params Index and its collection. + * @returns {AlterIndex} Index with resolved key names. + */ +const addNameToIndexKey = ({ index, collection }) => { + if (!index.indxKey?.length) { + return index; + } + + return { + ...index, + schemaName: getSchemaNameFromCollection({ collection }), + indxKey: mapKeysWithNames({ keys: index.indxKey, collection }), + indxIncludeKey: mapKeysWithNames({ keys: index.indxIncludeKey, collection }), + }; +}; + +module.exports = { + addNameToIndexKey, +}; diff --git a/forward_engineering/alterScript/alterScriptHelpers/indexHelpers/commentsHelper.js b/forward_engineering/alterScript/alterScriptHelpers/indexHelpers/commentsHelper.js new file mode 100644 index 0000000..583b9b1 --- /dev/null +++ b/forward_engineering/alterScript/alterScriptHelpers/indexHelpers/commentsHelper.js @@ -0,0 +1,51 @@ +/** + * @import { + * AlterCollection, + * AlterIndex, + * AlterScriptDto + * } from '../../../types/alterScript' + */ + +const { createAlterScriptDto } = require('../../dto/alterScriptDto'); +const { + getSchemaNameFromCollection, + getNamePrefixedWithSchemaName, + isObjectInDeltaModelActivated, +} = require('../../../utils/general'); +const { + getIndexCommentStatement, + dropIndexCommentStatement, +} = require('../../../ddlProvider/ddlHelpers/comment/commentHelper'); + +/** + * Build the COMMENT ON INDEX statement when the index description changed. + * + * @param {{ newIndex: AlterIndex; oldIndex: AlterIndex; collection: AlterCollection }} params Index versions and their + * collection. + * @returns {AlterScriptDto | undefined} Alter script DTO. + */ +const getModifyIndexCommentsScriptDtos = ({ newIndex, oldIndex, collection }) => { + const newDescription = newIndex.indxDescription; + const oldDescription = oldIndex.indxDescription; + const indexName = getNamePrefixedWithSchemaName({ + name: newIndex.indxName ?? '', + schemaName: getSchemaNameFromCollection({ collection }), + }); + const isActivated = isObjectInDeltaModelActivated(collection) && Boolean(newIndex.isActivated); + + if (newDescription && newDescription !== oldDescription) { + const script = getIndexCommentStatement({ indexName, description: newDescription }); + return createAlterScriptDto([script], isActivated, false); + } + + if (oldDescription && !newDescription) { + const script = dropIndexCommentStatement({ indexName }); + return createAlterScriptDto([script], isActivated, true); + } + + return void 0; +}; + +module.exports = { + getModifyIndexCommentsScriptDtos, +}; diff --git a/forward_engineering/alterScript/alterScriptHelpers/viewHelpers/alterNameHelper.js b/forward_engineering/alterScript/alterScriptHelpers/viewHelpers/alterNameHelper.js new file mode 100644 index 0000000..4e1395e --- /dev/null +++ b/forward_engineering/alterScript/alterScriptHelpers/viewHelpers/alterNameHelper.js @@ -0,0 +1,43 @@ +/** + * @import { + * AlterScriptDto, + * AlterView, + * MapPropertiesFn + * } from '../../../types/alterScript' + * @import {DdlProvider} from '../../../types/ddlProvider' + */ + +const { createAlterScriptDto } = require('../../dto/alterScriptDto'); +const { getSchemaOfAlterCollection } = require('../../../utils/general'); +const { createView, dropView } = require('./createDropViewHelper'); + +/** + * Build the statements renaming a view. Db2 for z/OS has no RENAME VIEW, so the view is dropped and recreated. + * + * @param {AlterView} view View delta. + * @param {DdlProvider} ddlProvider DDL provider. + * @param {MapPropertiesFn} mapProperties Property mapper. + * @returns {AlterScriptDto[]} Alter script DTOs. + */ +const getRenameViewScriptDtos = (view, ddlProvider, mapProperties) => { + const viewSchema = getSchemaOfAlterCollection(view); + const { old: oldName, new: newName } = viewSchema.compMod?.name ?? {}; + + if (!oldName || !newName || oldName === newName) { + return []; + } + + const dropScript = dropView({ + ddlProvider, + viewSchema: { ...viewSchema, code: oldName, name: oldName }, + }); + const createScript = createView({ ddlProvider, mapProperties, view }); + + return [createAlterScriptDto([dropScript], true, true), createAlterScriptDto([createScript], true, false)].filter( + scriptDto => scriptDto !== undefined, + ); +}; + +module.exports = { + getRenameViewScriptDtos, +}; diff --git a/forward_engineering/alterScript/alterScriptHelpers/viewHelpers/alterViewStatementHelper.js b/forward_engineering/alterScript/alterScriptHelpers/viewHelpers/alterViewStatementHelper.js new file mode 100644 index 0000000..dd27c14 --- /dev/null +++ b/forward_engineering/alterScript/alterScriptHelpers/viewHelpers/alterViewStatementHelper.js @@ -0,0 +1,41 @@ +/** + * @import { + * AlterScriptDto, + * AlterView, + * MapPropertiesFn + * } from '../../../types/alterScript' + * @import {DdlProvider} from '../../../types/ddlProvider' + */ + +const { getSchemaOfAlterCollection } = require('../../../utils/general'); +const { createAlterScriptDto } = require('../../dto/alterScriptDto'); +const { createView, dropView } = require('./createDropViewHelper'); + +/** + * Build the statements applying a new select statement. Db2 for z/OS cannot alter the body of a view, so the view is + * dropped and recreated. + * + * @param {AlterView} view View delta. + * @param {DdlProvider} ddlProvider DDL provider. + * @param {MapPropertiesFn} mapProperties Property mapper. + * @returns {AlterScriptDto[]} Alter script DTOs. + */ +const getModifySelectStatementScriptDtos = (view, ddlProvider, mapProperties) => { + const viewSchema = getSchemaOfAlterCollection(view); + const { old: oldStatement, new: newStatement } = viewSchema.compMod?.selectStatement ?? {}; + + if (oldStatement === newStatement) { + return []; + } + + const dropScript = dropView({ viewSchema, ddlProvider }); + const createScript = createView({ ddlProvider, mapProperties, view }); + + return [createAlterScriptDto([dropScript], true, true), createAlterScriptDto([createScript], true, false)].filter( + scriptDto => scriptDto !== undefined, + ); +}; + +module.exports = { + getModifySelectStatementScriptDtos, +}; diff --git a/forward_engineering/alterScript/alterScriptHelpers/viewHelpers/commentsHelper.js b/forward_engineering/alterScript/alterScriptHelpers/viewHelpers/commentsHelper.js new file mode 100644 index 0000000..6c89e69 --- /dev/null +++ b/forward_engineering/alterScript/alterScriptHelpers/viewHelpers/commentsHelper.js @@ -0,0 +1,79 @@ +/** + * @import { + * AlterScriptDto, + * AlterView + * } from '../../../types/alterScript' + */ + +const { createAlterScriptDto } = require('../../dto/alterScriptDto'); +const { + isObjectInDeltaModelActivated, + isParentContainerActivated, + getFullCollectionName, + getSchemaOfAlterCollection, +} = require('../../../utils/general'); +const { + getTableCommentStatement, + dropTableCommentStatement, +} = require('../../../ddlProvider/ddlHelpers/comment/commentHelper'); + +/** + * Build the COMMENT ON statement when the view description was set or changed. + * + * @param {AlterView} view View delta. + * @returns {AlterScriptDto | undefined} Alter script DTO. + */ +const getUpsertCommentsScriptDto = view => { + const { old: oldComment, new: newComment } = view.role?.compMod?.description ?? {}; + + if (!newComment || newComment === oldComment) { + return void 0; + } + + const viewSchema = getSchemaOfAlterCollection(view); + const isViewActivated = isParentContainerActivated(view) && isObjectInDeltaModelActivated(view); + const script = getTableCommentStatement({ + tableName: getFullCollectionName({ collectionSchema: viewSchema }), + description: newComment, + }); + + return createAlterScriptDto([script], isViewActivated, false); +}; + +/** + * Build the statement removing the view comment when the description was cleared. + * + * @param {AlterView} view View delta. + * @returns {AlterScriptDto | undefined} Alter script DTO. + */ +const getDropCommentsScriptDto = view => { + const { old: oldComment, new: newComment } = view.role?.compMod?.description ?? {}; + + if (!oldComment || newComment) { + return void 0; + } + + const viewSchema = getSchemaOfAlterCollection(view); + const isViewActivated = isParentContainerActivated(view) && isObjectInDeltaModelActivated(view); + const script = dropTableCommentStatement({ + tableName: getFullCollectionName({ collectionSchema: viewSchema }), + }); + + return createAlterScriptDto([script], isViewActivated, true); +}; + +/** + * Build all view comment statements. + * + * @param {AlterView} view View delta. + * @returns {AlterScriptDto[]} Alter script DTOs. + */ +const getModifyViewCommentsScriptDtos = view => { + return [getUpsertCommentsScriptDto(view), getDropCommentsScriptDto(view)].filter( + scriptDto => scriptDto !== undefined, + ); +}; + +module.exports = { + getModifyViewCommentsScriptDtos, +}; diff --git a/forward_engineering/alterScript/alterScriptHelpers/viewHelpers/createDropViewHelper.js b/forward_engineering/alterScript/alterScriptHelpers/viewHelpers/createDropViewHelper.js new file mode 100644 index 0000000..da74d1a --- /dev/null +++ b/forward_engineering/alterScript/alterScriptHelpers/viewHelpers/createDropViewHelper.js @@ -0,0 +1,93 @@ +/** + * @import { + * AlterView, + * MapPropertiesFn, + * ViewDefinitionRef + * } from '../../../types/alterScript' + * @import { + * DdlProvider, + * HydratedViewColumn + * } from '../../../types/ddlProvider' + */ + +const { + getSchemaOfAlterCollection, + getSchemaNameFromCollection, + getFullCollectionName, + getEntityName, +} = require('../../../utils/general'); + +/** + * Build the column list of a view, resolving each property to the table column it selects. + * + * @param {{ + * viewSchema: AlterView; + * collectionRefsDefinitionsMap: Record; + * mapProperties: MapPropertiesFn; + * ddlProvider: DdlProvider; + * }} params + * View schema, its column references, the property mapper and the DDL provider. + * @returns {HydratedViewColumn[]} View columns. + */ +const getKeys = ({ viewSchema, collectionRefsDefinitionsMap, mapProperties, ddlProvider }) => { + return mapProperties(viewSchema, (propertyName, schema) => { + const definition = schema.refId ? collectionRefsDefinitionsMap[schema.refId] : undefined; + + if (!definition) { + return ddlProvider.hydrateViewColumn({ name: propertyName, isActivated: schema.isActivated }); + } + + const name = definition.name ?? propertyName; + + return ddlProvider.hydrateViewColumn({ + name, + alias: name === propertyName ? undefined : propertyName, + isActivated: schema.isActivated, + entityName: getEntityName(definition.collection?.[0] ?? {}), + dbName: definition.bucket?.[0]?.code ?? definition.bucket?.[0]?.name ?? '', + }); + }); +}; + +/** + * Build the CREATE VIEW statement. + * + * @param {{ ddlProvider: DdlProvider; mapProperties: MapPropertiesFn; view: AlterView }} params DDL provider, property + * mapper and view delta. + * @returns {string} Create statement. + */ +const createView = ({ ddlProvider, mapProperties, view }) => { + const viewSchema = getSchemaOfAlterCollection(view); + const schemaData = { schemaName: getSchemaNameFromCollection({ collection: viewSchema }) ?? '' }; + + const hydratedView = ddlProvider.hydrateView({ + viewData: { + name: viewSchema.code ?? viewSchema.name ?? '', + keys: getKeys({ + viewSchema, + ddlProvider, + mapProperties, + collectionRefsDefinitionsMap: view.compMod?.collectionData?.collectionRefsDefinitionsMap ?? {}, + }), + schemaData, + }, + entityData: [viewSchema], + }); + + return ddlProvider.createView(hydratedView, {}, viewSchema.isActivated); +}; + +/** + * Build the DROP VIEW statement. + * + * @param {{ ddlProvider: DdlProvider; viewSchema: AlterView }} params DDL provider and view schema. + * @returns {string} Drop statement. + */ +const dropView = ({ ddlProvider, viewSchema }) => { + return ddlProvider.dropView({ viewName: getFullCollectionName({ collectionSchema: viewSchema }) }); +}; + +module.exports = { + createView, + dropView, +}; diff --git a/forward_engineering/alterScript/dto/alterScriptDto.js b/forward_engineering/alterScript/dto/alterScriptDto.js new file mode 100644 index 0000000..ff95c26 --- /dev/null +++ b/forward_engineering/alterScript/dto/alterScriptDto.js @@ -0,0 +1,78 @@ +/** + * @import { + * AlterScriptDto, + * ModificationScript + * } from '../../types/alterScript' + */ + +/** + * Build one DTO per script, all sharing the same activation and drop flags. + * + * @param {(string | undefined)[]} scripts Generated statements. + * @param {boolean} isActivated Whether the statements are activated. + * @param {boolean} isDropScript Whether the statements drop objects. + * @returns {AlterScriptDto[]} Alter script DTOs. + */ +const createAlterScriptDtos = (scripts, isActivated, isDropScript) => { + return scripts + .filter(Boolean) + .map(String) + .map(script => ({ + isActivated, + scripts: [{ isDropScript, script }], + })); +}; + +/** + * Build a single DTO holding all non-empty scripts. + * + * @param {(string | undefined)[]} scripts Generated statements. + * @param {boolean} isActivated Whether the statements are activated. + * @param {boolean} isDropScript Whether the statements drop objects. + * @returns {AlterScriptDto | undefined} Alter script DTO, or undefined when there is nothing to run. + */ +const createAlterScriptDto = (scripts, isActivated, isDropScript) => { + const nonEmptyScripts = scripts.filter(Boolean).map(String); + + if (nonEmptyScripts.length === 0) { + return void 0; + } + + return { + isActivated, + scripts: nonEmptyScripts.map(script => ({ isDropScript, script })), + }; +}; + +/** + * Build a DTO that drops an object and recreates it, keeping both statements in the declared order. + * + * @param {string | undefined} dropScript Drop statement. + * @param {string | undefined} createScript Create statement. + * @param {boolean} isActivated Whether the statements are activated. + * @returns {AlterScriptDto | undefined} Alter script DTO, or undefined when there is nothing to run. + */ +const createDropAndRecreateAlterScriptDto = (dropScript, createScript, isActivated) => { + /** @type {ModificationScript[]} */ + const scripts = []; + + if (dropScript) { + scripts.push({ isDropScript: true, script: dropScript }); + } + + if (createScript) { + scripts.push({ isDropScript: false, script: createScript }); + } + + if (scripts.length === 0) { + return void 0; + } + + return { isActivated, scripts }; +}; + +module.exports = { + createAlterScriptDto, + createAlterScriptDtos, + createDropAndRecreateAlterScriptDto, +}; diff --git a/forward_engineering/alterScript/dto/keyDto.js b/forward_engineering/alterScript/dto/keyDto.js new file mode 100644 index 0000000..57cdf05 --- /dev/null +++ b/forward_engineering/alterScript/dto/keyDto.js @@ -0,0 +1,44 @@ +/** + * @import { + * KeyScriptModification, + * KeyTransition + * } from '../../types/alterScript' + */ + +/** + * Report that a key did not move between its regular and composite representation. + * + * @returns {KeyTransition} Transition result. + */ +const noKeyTransition = () => ({ didTransitionHappen: false }); + +/** + * Report that a key moved between its regular and composite representation. + * + * @param {boolean} wasKeyChangedInTransition Whether the key options changed along the way. + * @returns {KeyTransition} Transition result. + */ +const keyTransition = wasKeyChangedInTransition => ({ + didTransitionHappen: true, + wasKeyChangedInTransition, +}); + +/** + * Build a key statement paired with the table it belongs to, so that drop and create statements of the same table can + * be ordered afterwards. + * + * @param {KeyScriptModification} params Statement and its metadata. + * @returns {KeyScriptModification} Key script modification. + */ +const createKeyScriptModification = ({ script, fullTableName, isDropScript, isActivated }) => ({ + script, + fullTableName, + isDropScript, + isActivated, +}); + +module.exports = { + noKeyTransition, + keyTransition, + createKeyScriptModification, +}; diff --git a/forward_engineering/api.js b/forward_engineering/api.js new file mode 100644 index 0000000..5a0be82 --- /dev/null +++ b/forward_engineering/api.js @@ -0,0 +1,11 @@ +const { generateContainerScript } = require('./api/generateContainerScript'); +const { isDropInStatements } = require('./api/isDropInStatements'); +const { generateScript } = require('./api/generateScript'); + +module.exports = { + generateScript, + + generateContainerScript, + + isDropInStatements, +}; diff --git a/forward_engineering/api/generateContainerScript.js b/forward_engineering/api/generateContainerScript.js new file mode 100644 index 0000000..47e4be5 --- /dev/null +++ b/forward_engineering/api/generateContainerScript.js @@ -0,0 +1,38 @@ +/** + * @import { + * AlterScriptData, + * PluginCallback, + * PluginLogger + * } from '../types/alterScript' + * @import {App} from '../types/ddlProvider' + */ + +const { buildContainerLevelAlterScript } = require('../alterScript/alterScriptBuilder'); +const { toPluginError } = require('../utils/toPluginError'); + +/** + * Generate container-level alter script from a delta model. + * + * Must never return without either throwing or invoking the callback: the studio resolves the script request only from + * the callback and has no timeout, so a silent return hangs the UI. + * + * @param {AlterScriptData} data FE data. + * @param {PluginLogger} logger Logger. + * @param {PluginCallback} callback Callback. + * @param {App} app App instance. + * @returns {void} + */ +function generateContainerScript(data, logger, callback, app) { + try { + callback(null, buildContainerLevelAlterScript(data, app)); + } catch (error) { + const pluginError = toPluginError(error); + + logger.log('error', pluginError, 'Db2 for z/OS Forward-Engineering Error'); + callback(pluginError); + } +} + +module.exports = { + generateContainerScript, +}; diff --git a/forward_engineering/api/generateScript.js b/forward_engineering/api/generateScript.js new file mode 100644 index 0000000..b6dd272 --- /dev/null +++ b/forward_engineering/api/generateScript.js @@ -0,0 +1,38 @@ +/** + * @import { + * AlterScriptData, + * PluginCallback, + * PluginLogger + * } from '../types/alterScript' + * @import {App} from '../types/ddlProvider' + */ + +const { buildEntityLevelAlterScript } = require('../alterScript/alterScriptBuilder'); +const { toPluginError } = require('../utils/toPluginError'); + +/** + * Generate entity-level alter script from a delta model. + * + * Must never return without either throwing or invoking the callback: the studio resolves the script request only from + * the callback and has no timeout, so a silent return hangs the UI. + * + * @param {AlterScriptData} data FE data. + * @param {PluginLogger} logger Logger. + * @param {PluginCallback} callback Callback. + * @param {App} app App instance. + * @returns {void} + */ +function generateScript(data, logger, callback, app) { + try { + callback(null, buildEntityLevelAlterScript(data, app)); + } catch (error) { + const pluginError = toPluginError(error); + + logger.log('error', pluginError, 'Db2 for z/OS Forward-Engineering Error'); + callback(pluginError); + } +} + +module.exports = { + generateScript, +}; diff --git a/forward_engineering/api/isDropInStatements.js b/forward_engineering/api/isDropInStatements.js new file mode 100644 index 0000000..20d7187 --- /dev/null +++ b/forward_engineering/api/isDropInStatements.js @@ -0,0 +1,44 @@ +/** + * @import { + * AlterScriptData, + * PluginCallback, + * PluginLogger + * } from '../types/alterScript' + * @import {App} from '../types/ddlProvider' + */ + +const { + doesContainerLevelAlterScriptContainDropStatements, + doesEntityLevelAlterScriptContainDropStatements, +} = require('../alterScript/alterScriptBuilder'); +const { toPluginError } = require('../utils/toPluginError'); + +/** + * Detect whether the generated alter script contains statements that drop objects, so that the studio can warn the user + * before applying it. + * + * Must never return without either throwing or invoking the callback: the studio resolves the request only from the + * callback and has no timeout, so a silent return hangs the UI. + * + * @param {AlterScriptData} data FE data. + * @param {PluginLogger} _logger Logger. + * @param {PluginCallback} callback Callback. + * @param {App} app App instance. + * @returns {void} + */ +function isDropInStatements(data, _logger, callback, app) { + try { + const containsDropStatements = + data.level === 'container' + ? doesContainerLevelAlterScriptContainDropStatements(data, app) + : doesEntityLevelAlterScriptContainDropStatements(data, app); + + callback(null, containsDropStatements); + } catch (error) { + callback(toPluginError(error)); + } +} + +module.exports = { + isDropInStatements, +}; diff --git a/forward_engineering/config.json b/forward_engineering/config.json new file mode 100644 index 0000000..08abcce --- /dev/null +++ b/forward_engineering/config.json @@ -0,0 +1,433 @@ +{ + "type": "ddl", + "ddlType": "plugin", + "mode": "sql", + "fileExtensions": [ + { + "value": "sql", + "label": "SQL" + } + ], + "hasUpdateScript": false, + "applyScriptToInstance": false, + "combinedContainers": true, + "feLevelSelector": { + "container": true, + "model": true + }, + "compMode": { + "entity": true, + "container": true + }, + "namePrefix": "Db2 for z/OS", + "level": { + "container": true, + "entity": true, + "view": true + }, + "additionalOptions": [ + { + "id": "applyDropStatements", + "value": false, + "forUpdate": true, + "name": "Apply Drop Statements", + "isDropInStatements": true + } + ], + "scriptGenerationOptions": [ + { + "keyword": "primaryKeys", + "label": "FE_SCRIPT_GENERATION_OPTIONS___PRIMARY_KEYS", + "disabled": false, + "value": { + "inline": { + "default": true, + "disabled": false, + "disabledLabel": "" + }, + "separate": { + "default": false, + "disabled": false, + "disabledLabel": "" + }, + "ignore": { + "default": false, + "disabled": false, + "disabledLabel": "" + } + }, + "adapters": [ + { + "dependency": { + "key": "primaryKey", + "valueType": "array" + }, + "defaultValue": { + "primaryKey": [] + } + }, + { + "dependency": { + "key": "primaryKey", + "valueType": "object" + }, + + "defaultValue": { + "primaryKey": {} + } + }, + { + "dependency": { + "type": "or", + "values": [ + { + "key": "primaryKey", + "value": true + }, + { + "key": "compositePrimaryKey", + "value": true + } + ] + }, + "defaultValue": { + "primaryKey": false, + "compositePrimaryKey": false + } + } + ] + }, + { + "keyword": "foreignKeys", + "label": "FE_SCRIPT_GENERATION_OPTIONS___FOREIGN_KEYS", + "disabled": false, + "value": { + "inline": { + "default": true, + "disabled": false, + "disabledLabel": "" + }, + "separate": { + "default": false, + "disabled": false, + "disabledLabel": "" + }, + "ignore": { + "default": false, + "disabled": false, + "disabledLabel": "" + } + } + }, + { + "keyword": "uniqueConstraints", + "label": "FE_SCRIPT_GENERATION_OPTIONS___UNIQUE_KEYS", + "disabled": false, + "value": { + "inline": { + "default": true, + "disabled": false, + "disabledLabel": "" + }, + "separate": { + "default": false, + "disabled": false, + "disabledLabel": "" + }, + "ignore": { + "default": false, + "disabled": false, + "disabledLabel": "" + } + }, + "adapters": [ + { + "dependency": { + "key": "uniqueKey", + "valueType": "array" + }, + "defaultValue": { + "uniqueKey": [] + } + }, + { + "dependency": { + "key": "uniqueKey", + "valueType": "object" + }, + "defaultValue": { + "uniqueKey": {} + } + }, + { + "dependency": { + "type": "or", + "values": [ + { + "key": "unique", + "value": true + }, + { + "key": "compositeUniqueKey", + "value": true + }, + { + "key": "compMode", + "exist": true + } + ] + }, + "defaultValue": { + "unique": false, + "compositeUniqueKey": false + } + } + ] + }, + { + "keyword": "columnNotNullConstraints", + "label": "FE_SCRIPT_GENERATION_OPTIONS___COLUMN_NOT_NULL", + "disabled": false, + "value": { + "inline": { + "default": true, + "disabled": false, + "disabledLabel": "" + }, + "separate": { + "default": false, + "disabled": false, + "disabledLabel": "" + }, + "ignore": { + "default": false, + "disabled": false, + "disabledLabel": "" + } + }, + "adapters": [ + { + "dependency": { + "key": "required", + "value": true + }, + "defaultValue": { + "required": false + } + } + ] + }, + { + "keyword": "checkConstraints", + "label": "FE_SCRIPT_GENERATION_OPTIONS___CHECK_CONSTRAINTS", + "disabled": false, + "value": { + "inline": { + "default": true, + "disabled": false, + "disabledLabel": "" + }, + "separate": { + "default": false, + "disabled": false, + "disabledLabel": "" + }, + "ignore": { + "default": false, + "disabled": false, + "disabledLabel": "" + } + }, + "adapters": [ + { + "dependency": { + "type": "or", + "values": [ + { + "key": "chkConstr", + "valueType": "array" + }, + { + "key": "checkConstraint", + "valueType": "array" + } + ] + }, + "defaultValue": { + "chkConstr": [], + "checkConstraint": [] + } + } + ] + }, + { + "keyword": "columnDefaultValues", + "label": "FE_SCRIPT_GENERATION_OPTIONS___COLUMN_DEFAULT_VALUES", + "disabled": false, + "value": { + "inline": { + "default": true, + "disabled": false, + "disabledLabel": "" + }, + "separate": { + "default": false, + "disabled": false, + "disabledLabel": "" + }, + "ignore": { + "default": false, + "disabled": false, + "disabledLabel": "" + } + }, + "adapters": [ + { + "dependency": { + "key": "default", + "exist": true + }, + "defaultValue": { + "default": "" + } + } + ] + }, + { + "keyword": "tableComments", + "label": "FE_SCRIPT_GENERATION_OPTIONS___TABLE_COMMENTS", + "disabled": false, + "value": { + "inline": { + "default": false, + "disabled": true, + "disabledLabel": "" + }, + "separate": { + "default": true, + "disabled": false, + "disabledLabel": "" + }, + "ignore": { + "default": false, + "disabled": false, + "disabledLabel": "" + } + }, + "adapters": [ + { + "dependency": { + "type": "and", + "values": [ + { + "key": "collectionName", + "exist": true + }, + { + "key": "description", + "exist": true + } + ] + }, + "defaultValue": { + "description": "" + } + } + ] + }, + { + "keyword": "viewComments", + "label": "FE_SCRIPT_GENERATION_OPTIONS___VIEW_COMMENTS", + "disabled": false, + "value": { + "inline": { + "default": false, + "disabled": true, + "disabledLabel": "" + }, + "separate": { + "default": true, + "disabled": false, + "disabledLabel": "" + }, + "ignore": { + "default": false, + "disabled": false, + "disabledLabel": "" + } + }, + "adapters": [ + { + "dependency": { + "key": "viewOn", + "exist": true + }, + "defaultValue": { + "description": "" + } + } + ] + }, + { + "keyword": "columnComments", + "label": "FE_SCRIPT_GENERATION_OPTIONS___COLUMN_COMMENTS", + "disabled": false, + "value": { + "inline": { + "default": false, + "disabled": true, + "disabledLabel": "" + }, + "separate": { + "default": true, + "disabled": false, + "disabledLabel": "" + }, + "ignore": { + "default": false, + "disabled": false, + "disabledLabel": "" + } + }, + "adapters": [ + { + "dependency": { + "type": "and", + "values": [ + { + "type": "not", + "values": [ + { + "key": "type", + "value": "bucket" + }, + { + "level": "parent", + "key": "type", + "value": "definitions", + "inDepthParentSearch": true + } + ] + }, + { + "key": "collectionName", + "exist": false + }, + { + "key": "viewOn", + "exist": false + }, + { + "key": "description", + "exist": true + } + ] + }, + "defaultValue": { + "description": "" + } + } + ] + } + ] +} diff --git a/forward_engineering/configs/defaultTypes.js b/forward_engineering/configs/defaultTypes.js new file mode 100644 index 0000000..7485c7a --- /dev/null +++ b/forward_engineering/configs/defaultTypes.js @@ -0,0 +1,16 @@ +/** @import {DefaultTypesMap} from '../types/ddlProvider' */ + +/** @type {DefaultTypesMap} */ +module.exports = { + number: 'INTEGER', + char: 'VARCHAR', + string: 'VARCHAR', + datetime: 'TIMESTAMP', + date: 'DATE', + timestamp: 'TIMESTAMP', + binary: 'BINARY', + xml: 'XML', + rowid: 'ROWID', + object: 'VARCHAR', + default: 'CHAR', +}; diff --git a/forward_engineering/configs/descriptors.js b/forward_engineering/configs/descriptors.js new file mode 100644 index 0000000..db6646c --- /dev/null +++ b/forward_engineering/configs/descriptors.js @@ -0,0 +1,95 @@ +/** @import {TypeDescriptors} from '../types/ddlProvider' */ + +/** @type {TypeDescriptors} */ +module.exports = { + SMALLINT: { + capacity: 2, + }, + INT: { + capacity: 4, + }, + INTEGER: { + capacity: 4, + }, + BIGINT: { + capacity: 8, + }, + DECIMAL: { + mode: 'decimal', + }, + DEC: { + mode: 'decimal', + }, + NUMERIC: { + mode: 'decimal', + }, + FLOAT: { + capacity: 4, + mode: 'float', + }, + DOUBLE: { + capacity: 8, + mode: 'float', + }, + 'DOUBLE PRECISION': { + capacity: 8, + mode: 'float', + }, + REAL: { + capacity: 4, + mode: 'float', + }, + DECFLOAT: { + mode: 'decfloat', + }, + CHAR: { + mode: 'char', + }, + CHARACTER: { + mode: 'char', + }, + VARCHAR: { + mode: 'varchar', + }, + 'CHAR VARYING': { + mode: 'varchar', + }, + 'CHARACTER VARYING': { + mode: 'varchar', + }, + GRAPHIC: { + mode: 'graphic', + }, + VARGRAPHIC: { + mode: 'vargraphic', + }, + CLOB: { + mode: 'clob', + }, + 'CHARACTER LARGE OBJECT': { + mode: 'clob', + }, + DBCLOB: { + mode: 'dbclob', + }, + BINARY: { + mode: 'binary', + }, + VARBINARY: { + mode: 'varbinary', + }, + BLOB: { + mode: 'blob', + }, + DATE: { + format: 'YYYY-MM-DD', + }, + TIME: { + format: 'hh:mm:ss', + }, + TIMESTAMP: { + format: 'YYYY-MM-DD hh:mm:ss', + }, + XML: {}, + ROWID: {}, +}; diff --git a/forward_engineering/ddlProvider.js b/forward_engineering/ddlProvider.js new file mode 100644 index 0000000..a76ecbe --- /dev/null +++ b/forward_engineering/ddlProvider.js @@ -0,0 +1,4 @@ +// This file reexports actual DDL Provider. +// Core application needs this file to generate FE scripts + +module.exports = require('./ddlProvider/ddlProvider'); diff --git a/forward_engineering/ddlProvider/ddlHelpers/columnDefinition/getColumnConstraints.js b/forward_engineering/ddlProvider/ddlHelpers/columnDefinition/getColumnConstraints.js new file mode 100644 index 0000000..ffafc2e --- /dev/null +++ b/forward_engineering/ddlProvider/ddlHelpers/columnDefinition/getColumnConstraints.js @@ -0,0 +1,68 @@ +/** + * @import { + * ColumnConstraintParams, + * KeyOptions + * } from '../../../types/ddlProvider' + */ + +const trim = require('lodash/trim'); +const { getOptionsString } = require('../constraint/getOptionsString'); +const { getDefaultConstraintName } = require('../key/getDefaultConstraintName'); +const { CONSTRAINT_POSTFIX } = require('../../../../shared/constants/constants'); + +/** + * Resolve primary/unique key options. + * + * @param {ColumnConstraintParams} params Key flags. + * @returns {KeyOptions} Options object. + */ +const getOptions = ({ primaryKey, unique, primaryKeyOptions, uniqueKeyOptions }) => { + if (primaryKey) { + return primaryKeyOptions ?? {}; + } + + if (unique) { + return uniqueKeyOptions ?? {}; + } + + return {}; +}; + +/** + * Resolve the name of the key declared inline on a column, falling back to the name the alter script would later use to + * drop it. + * + * @param {ColumnConstraintParams} params Column constraint flags. + * @returns {string | undefined} Constraint name. + */ +const getConstraintName = ({ unique, primaryKey, primaryKeyOptions, uniqueKeyOptions, entityName }) => { + if (!primaryKey && !unique) { + return void 0; + } + + const options = getOptions({ primaryKey, unique, primaryKeyOptions, uniqueKeyOptions }); + const postfix = primaryKey ? CONSTRAINT_POSTFIX.primaryKey : CONSTRAINT_POSTFIX.uniqueKey; + + return trim(options.constraintName) || getDefaultConstraintName({ entityName, postfix }); +}; + +/** + * Build column constraint clauses. + * + * @param {ColumnConstraintParams} params Column constraint flags. + * @returns {string} Constraints DDL fragment. + */ +const getColumnConstraints = ({ nullable, unique, primaryKey, primaryKeyOptions, uniqueKeyOptions, entityName }) => { + const { constraintString, statement } = getOptionsString({ + ...getOptions({ primaryKey, unique, primaryKeyOptions, uniqueKeyOptions }), + constraintName: getConstraintName({ unique, primaryKey, primaryKeyOptions, uniqueKeyOptions, entityName }), + }); + const primaryKeyString = primaryKey ? ` PRIMARY KEY` : ''; + const uniqueKeyString = unique ? ` UNIQUE` : ''; + const nullableString = nullable ? '' : ' NOT NULL'; + return `${nullableString}${constraintString}${primaryKeyString}${uniqueKeyString}${statement}`; +}; + +module.exports = { + getColumnConstraints, +}; diff --git a/forward_engineering/ddlProvider/ddlHelpers/columnDefinition/getColumnDefault.js b/forward_engineering/ddlProvider/ddlHelpers/columnDefinition/getColumnDefault.js new file mode 100644 index 0000000..e19d833 --- /dev/null +++ b/forward_engineering/ddlProvider/ddlHelpers/columnDefinition/getColumnDefault.js @@ -0,0 +1,94 @@ +/** + * @import { + * ColumnDefaultParams, + * IdentityOptions + * } from '../../../types/ddlProvider' + */ + +const toUpper = require('lodash/toUpper'); +const { DATA_TYPES_WITH_IDENTITY, DATA_TYPE } = require('../../../../shared/constants/types'); + +/** + * Check whether a type can have identity. + * + * @param {{ type: string }} params Column type. + * @returns {boolean} Whether identity is allowed. + */ +const canHaveIdentity = ({ type }) => { + return DATA_TYPES_WITH_IDENTITY.includes(toUpper(type)); +}; + +/** + * Check whether a column is generated as identity. + * + * @param {{ identity?: IdentityOptions; type: string }} params Identity and type. + * @returns {boolean} Whether generated as identity. + */ +const isGeneratedAsIdentity = ({ identity, type }) => { + return canHaveIdentity({ type }) && !!identity?.generated; +}; + +/** + * Check whether a type is ROWID. + * + * @param {{ type: string }} params Column type. + * @returns {boolean} Whether type is ROWID. + */ +const isRowid = ({ type }) => toUpper(type) === DATA_TYPE.rowid; + +/** + * Build identity options clause. + * + * @param {IdentityOptions} params Identity options. + * @returns {string} Identity options clause. + */ +const getIdentityOptions = ({ start, increment, minValue, maxValue, cycle, cache, cacheValue, order }) => { + const startWith = start ? `START WITH ${start}` : ''; + const incrementBy = increment ? `INCREMENT BY ${increment}` : ''; + const minimumValue = minValue ? `MINVALUE ${minValue}` : ''; + const maximumValue = maxValue ? `MAXVALUE ${maxValue}` : ''; + const cacheOption = cacheValue ? `CACHE ${cacheValue}` : cache; + + return [startWith, incrementBy, cycle, minimumValue, maximumValue, cacheOption, order].filter(Boolean).join(', '); +}; + +/** + * Build column default / identity / generated clause. + * + * @param {ColumnDefaultParams} params Column default params. + * @returns {string} Default clause. + */ +const getColumnDefault = ({ + default: defaultValue, + identity, + type, + generated, + generatedColumn, + columnGenerationExpression, +}) => { + if (isRowid({ type }) && generated) { + return ` GENERATED ${generated}`; + } + + if (generatedColumn && columnGenerationExpression) { + return ` GENERATED ALWAYS AS (${columnGenerationExpression})`; + } + + const isGeneratedIdentity = isGeneratedAsIdentity({ identity, type }); + + if (isGeneratedIdentity && identity) { + const identityOptions = getIdentityOptions(identity); + + return ` GENERATED ${identity.generated} AS IDENTITY (${identityOptions})`; + } + + if (defaultValue || defaultValue === 0) { + return ` WITH DEFAULT ${defaultValue}`; + } + + return ''; +}; + +module.exports = { + getColumnDefault, +}; diff --git a/forward_engineering/ddlProvider/ddlHelpers/columnDefinition/getColumnType.js b/forward_engineering/ddlProvider/ddlHelpers/columnDefinition/getColumnType.js new file mode 100644 index 0000000..3689ff8 --- /dev/null +++ b/forward_engineering/ddlProvider/ddlHelpers/columnDefinition/getColumnType.js @@ -0,0 +1,251 @@ +/** + * @import { + * HydratedColumn, + * LengthWithMultiplierParams, + * ScalePrecisionParams + * } from '../../../types/ddlProvider' + */ + +const isNumber = require('lodash/isNumber'); +const toUpper = require('lodash/toUpper'); +const { + DATA_TYPES_WITH_LENGTH_MULTIPLIER, + DATA_TYPES_WITH_LENGTH, + DATA_TYPES_WITH_PRECISION, + DATA_TYPES_WITH_CHARACTER_SUBTYPE, + DATA_TYPES_WITH_CCSID, + DATA_TYPES_WITH_INLINE_LENGTH, + DATA_TYPE, +} = require('../../../../shared/constants/types'); + +/** + * Add length with multiplier clause. + * + * @param {LengthWithMultiplierParams} params Type params. + * @returns {string} Type clause. + */ +const addLengthWithMultiplier = ({ type, length, lengthSemantics }) => { + return ` ${type}(${length}${toUpper(lengthSemantics)})`; +}; + +/** + * Add length clause. + * + * @param {{ type: string; length: number }} params Type params. + * @returns {string} Type clause. + */ +const addLength = ({ type, length }) => { + return ` ${type}(${length})`; +}; + +/** + * Add scale and precision clause. + * + * @param {ScalePrecisionParams} params Type params. + * @returns {string} Type clause. + */ +const addScalePrecision = ({ type, precision, scale }) => { + if (isNumber(scale)) { + return ` ${type}(${precision ?? '*'},${scale})`; + } + + if (isNumber(precision)) { + return ` ${type}(${precision})`; + } + + return ` ${type}`; +}; + +/** + * Add precision clause. + * + * @param {{ type: string; precision: number }} params Type params. + * @returns {string} Type clause. + */ +const addPrecision = ({ type, precision }) => { + if (isNumber(precision)) { + return ` ${type}(${precision})`; + } + return ` ${type}`; +}; + +/** + * Build TIMESTAMP type clause. + * + * @param {{ fractSecPrecision?: number; withTimeZone?: boolean }} params Timestamp params. + * @returns {string} Type clause. + */ +const getTimestampType = ({ fractSecPrecision, withTimeZone }) => { + const fractSecPrecisionString = isNumber(fractSecPrecision) ? `(${fractSecPrecision})` : ''; + const timeZoneString = withTimeZone ? ' WITH TIME ZONE' : ''; + + return ` TIMESTAMP${fractSecPrecisionString}${timeZoneString}`; +}; + +/** + * Build character subtype clause. + * + * @param {{ characterSubtype?: string }} params Character subtype. + * @returns {string} Subtype clause. + */ +const getCharacterSubtypeClause = ({ characterSubtype }) => { + if (!characterSubtype) { + return ''; + } + + return ` FOR ${toUpper(characterSubtype)} DATA`; +}; + +/** + * Build CCSID clause. + * + * @param {{ ccsid?: number }} params CCSID value. + * @returns {string} CCSID clause. + */ +const getCcsidClause = ({ ccsid }) => { + if (!isNumber(ccsid)) { + return ''; + } + + return ` CCSID ${ccsid}`; +}; + +/** + * Build INLINE LENGTH clause. + * + * @param {{ inlineLength?: number }} params Inline length. + * @returns {string} Inline length clause. + */ +const getInlineLengthClause = ({ inlineLength }) => { + if (!isNumber(inlineLength)) { + return ''; + } + + return ` INLINE LENGTH ${inlineLength}`; +}; + +/** + * Check length multiplier support. + * + * @param {{ type: string }} params Column type. + * @returns {boolean} Whether type supports length multiplier. + */ +const canHaveLengthMultiplier = ({ type }) => DATA_TYPES_WITH_LENGTH_MULTIPLIER.includes(type); + +/** + * Check length support. + * + * @param {{ type: string }} params Column type. + * @returns {boolean} Whether type supports length. + */ +const canHaveLength = ({ type }) => DATA_TYPES_WITH_LENGTH.includes(type); + +/** + * Check precision support. + * + * @param {{ type: string }} params Column type. + * @returns {boolean} Whether type supports precision. + */ +const canHavePrecision = ({ type }) => DATA_TYPES_WITH_PRECISION.includes(type); + +/** + * Check scale support. + * + * @param {{ type: string }} params Column type. + * @returns {boolean} Whether type supports scale. + */ +const canHaveScale = ({ type }) => type === DATA_TYPE.decimal; + +/** + * Check TIMESTAMP type. + * + * @param {{ type: string }} params Column type. + * @returns {boolean} Whether type is TIMESTAMP. + */ +const isTimestamp = ({ type }) => type === DATA_TYPE.timestamp; + +/** + * Check ROWID type. + * + * @param {{ type: string }} params Column type. + * @returns {boolean} Whether type is ROWID. + */ +const isRowid = ({ type }) => type === DATA_TYPE.rowid; + +/** + * Check character subtype support. + * + * @param {{ type: string }} params Column type. + * @returns {boolean} Whether type supports character subtype. + */ +const canHaveCharacterSubtype = ({ type }) => DATA_TYPES_WITH_CHARACTER_SUBTYPE.includes(type); + +/** + * Check CCSID support. + * + * @param {{ type: string }} params Column type. + * @returns {boolean} Whether type supports CCSID. + */ +const canHaveCcsid = ({ type }) => DATA_TYPES_WITH_CCSID.includes(type); + +/** + * Check inline length support. + * + * @param {{ type: string }} params Column type. + * @returns {boolean} Whether type supports inline length. + */ +const canHaveInlineLength = ({ type }) => DATA_TYPES_WITH_INLINE_LENGTH.includes(type); + +/** + * Build column type DDL fragment. + * + * @param {HydratedColumn} columnDefinition Column definition. + * @returns {string} Column type DDL. + */ +const getColumnType = ({ + type, + length, + lengthSemantics, + precision, + scale, + fractSecPrecision, + withTimeZone, + isUDTRef, + schemaName, + characterSubtype, + ccsid, + inlineLength, +}) => { + const hasLength = isNumber(length); + let typeStatement = ''; + + if (isRowid({ type })) { + typeStatement = ` ${type}`; + } else if (hasLength && lengthSemantics && canHaveLengthMultiplier({ type }) && canHaveLength({ type })) { + typeStatement = addLengthWithMultiplier({ type, length, lengthSemantics }); + } else if (hasLength && canHaveLength({ type })) { + typeStatement = addLength({ type, length }); + } else if (canHavePrecision({ type }) && canHaveScale({ type })) { + typeStatement = addScalePrecision({ type, precision, scale }); + } else if (canHavePrecision({ type }) && isNumber(precision)) { + typeStatement = addPrecision({ type, precision }); + } else if (isTimestamp({ type })) { + typeStatement = getTimestampType({ fractSecPrecision, withTimeZone }); + } else if (isUDTRef && schemaName) { + typeStatement = ` "${schemaName}"."${type}"`; + } else { + typeStatement = ` ${type}`; + } + + const characterSubtypeClause = canHaveCharacterSubtype({ type }) + ? getCharacterSubtypeClause({ characterSubtype }) + : ''; + const ccsidClause = canHaveCcsid({ type }) ? getCcsidClause({ ccsid }) : ''; + const inlineLengthClause = canHaveInlineLength({ type }) ? getInlineLengthClause({ inlineLength }) : ''; + + return `${typeStatement}${characterSubtypeClause}${ccsidClause}${inlineLengthClause}`; +}; + +module.exports = { + getColumnType, +}; diff --git a/forward_engineering/ddlProvider/ddlHelpers/comment/commentHelper.js b/forward_engineering/ddlProvider/ddlHelpers/comment/commentHelper.js new file mode 100644 index 0000000..e4e2619 --- /dev/null +++ b/forward_engineering/ddlProvider/ddlHelpers/comment/commentHelper.js @@ -0,0 +1,240 @@ +/** + * @import { + * ColumnCommentParams, + * CommentStatementParams, + * HydratedColumn + * } from '../../../types/ddlProvider' + */ + +const trim = require('lodash/trim'); +const templates = require('../../templates'); +const { assignTemplates } = require('../../../utils/assignTemplates'); +const { wrapInQuotes, commentIfDeactivated, wrapInSingleQuotes } = require('../../../utils/general'); + +/** @enum {string} */ +const OBJECT_TYPE = { + schema: 'SCHEMA', + column: 'COLUMN', + table: 'TABLE', + index: 'INDEX', + type: 'TYPE', +}; + +/** @enum {string} */ +const COMMENT_MODE = { + set: 'set', + remove: 'remove', +}; + +/** + * Escape single quotes in a description. + * + * @param {string} description Description text. + * @returns {string} Escaped description. + */ +const escapeSpecialCharacters = description => description.replaceAll("'", "''"); + +/** + * Build a COMMENT ON statement. + * + * @param {CommentStatementParams} params Comment params. + * @returns {string} Comment statement. + */ +const getCommentStatement = ({ objectName, objectType, description, mode = COMMENT_MODE.set }) => { + if (mode === COMMENT_MODE.set && !description) { + return ''; + } + + return assignTemplates({ + template: templates.comment, + templateData: { + objectType, + objectName: trim(objectName), + comment: wrapInSingleQuotes({ name: escapeSpecialCharacters(description ?? '') }), + }, + }); +}; + +/** + * Build a column comment statement. + * + * @param {ColumnCommentParams} params Column comment params. + * @returns {string} Comment statement. + */ +const getColumnCommentStatement = ({ tableName, columnName, description }) => { + const objectName = tableName + '.' + wrapInQuotes(columnName); + return getCommentStatement({ + objectName, + objectType: OBJECT_TYPE.column, + description, + mode: COMMENT_MODE.set, + }); +}; + +/** + * Build a table comment statement. + * + * @param {{ tableName: string; description?: string }} params Table comment params. + * @returns {string} Comment statement. + */ +const getTableCommentStatement = ({ tableName, description }) => { + return getCommentStatement({ + objectName: tableName, + objectType: OBJECT_TYPE.table, + description, + mode: COMMENT_MODE.set, + }); +}; + +/** + * Build an index comment statement. + * + * @param {{ indexName: string; description?: string }} params Index comment params. + * @returns {string} Comment statement. + */ +const getIndexCommentStatement = ({ indexName, description }) => { + return getCommentStatement({ + objectName: indexName, + objectType: OBJECT_TYPE.index, + description, + mode: COMMENT_MODE.set, + }); +}; + +/** + * Build a drop-style index comment (empty comment). + * + * @param {{ indexName: string }} params Index name. + * @returns {string} Comment statement. + */ +const dropIndexCommentStatement = ({ indexName }) => { + return getCommentStatement({ + objectName: indexName, + objectType: OBJECT_TYPE.index, + description: '', + mode: COMMENT_MODE.remove, + }); +}; + +/** + * Build a distinct type comment statement. + * + * @param {{ typeName: string; description?: string }} params Type comment params. + * @returns {string} Comment statement. + */ +const getTypeCommentStatement = ({ typeName, description }) => { + return getCommentStatement({ + objectName: typeName, + objectType: OBJECT_TYPE.type, + description, + mode: COMMENT_MODE.set, + }); +}; + +/** + * Build a statement that removes a distinct type comment. + * + * @param {{ typeName: string }} params Type name. + * @returns {string} Comment statement. + */ +const dropTypeCommentStatement = ({ typeName }) => { + return getCommentStatement({ + objectName: typeName, + objectType: OBJECT_TYPE.type, + description: '', + mode: COMMENT_MODE.remove, + }); +}; + +/** + * Build a schema comment statement. + * + * @param {{ schemaName: string; description?: string }} params Schema comment params. + * @returns {string} Comment statement. + */ +const getSchemaCommentStatement = ({ schemaName, description }) => { + return getCommentStatement({ + objectName: schemaName, + objectType: OBJECT_TYPE.schema, + description, + mode: COMMENT_MODE.set, + }); +}; + +/** + * Build column comments for a table. + * + * @param {{ tableName: string; columnDefinitions?: HydratedColumn[] }} params Column definitions. + * @returns {string} Joined comment statements. + */ +const getColumnComments = ({ tableName, columnDefinitions }) => { + const columns = columnDefinitions ?? []; + return columns + .filter(columnDefinition => columnDefinition.comment) + .map(columnDefinition => { + const comment = getColumnCommentStatement({ + tableName, + columnName: columnDefinition.name, + description: columnDefinition.comment, + }); + + return commentIfDeactivated(comment, { isActivated: columnDefinition.isActivated ?? true }); + }) + .join('\n'); +}; + +/** + * Build the statement removing a schema comment. + * + * @param {{ schemaName: string }} params Schema name. + * @returns {string} Comment statement. + */ +const dropSchemaCommentStatement = ({ schemaName }) => + getCommentStatement({ + objectName: schemaName, + objectType: OBJECT_TYPE.schema, + description: '', + mode: COMMENT_MODE.remove, + }); + +/** + * Build the statement removing a table comment. + * + * @param {{ tableName: string }} params Table name. + * @returns {string} Comment statement. + */ +const dropTableCommentStatement = ({ tableName }) => + getCommentStatement({ + objectName: tableName, + objectType: OBJECT_TYPE.table, + description: '', + mode: COMMENT_MODE.remove, + }); + +/** + * Build the statement removing a column comment. + * + * @param {{ tableName: string; columnName: string }} params Table and column names. + * @returns {string} Comment statement. + */ +const dropTableColumnCommentStatement = ({ tableName, columnName }) => + getCommentStatement({ + objectName: tableName + '.' + wrapInQuotes(columnName), + objectType: OBJECT_TYPE.column, + description: '', + mode: COMMENT_MODE.remove, + }); + +module.exports = { + getColumnCommentStatement, + getSchemaCommentStatement, + getTableCommentStatement, + getIndexCommentStatement, + dropIndexCommentStatement, + getTypeCommentStatement, + dropTypeCommentStatement, + getColumnComments, + dropSchemaCommentStatement, + dropTableCommentStatement, + dropTableColumnCommentStatement, +}; diff --git a/forward_engineering/ddlProvider/ddlHelpers/constraint/getOptionsString.js b/forward_engineering/ddlProvider/ddlHelpers/constraint/getOptionsString.js new file mode 100644 index 0000000..c47eebc --- /dev/null +++ b/forward_engineering/ddlProvider/ddlHelpers/constraint/getOptionsString.js @@ -0,0 +1,27 @@ +/** + * @import { + * ConstraintOptionsResult, + * KeyOptions + * } from '../../../types/ddlProvider' + */ + +const { wrapInQuotes } = require('../../../utils/general'); + +/** + * Build constraint option fragments for Db2 for z/OS. Only constraint names are modeled; LUW-only clauses are ignored. + * + * @param {KeyOptions} params Constraint options. + * @returns {ConstraintOptionsResult} Constraint fragments. + */ +const getOptionsString = ({ constraintName }) => { + const constraintString = constraintName ? ` CONSTRAINT ${wrapInQuotes(constraintName.trim())}` : ''; + + return { + constraintString, + statement: '', + }; +}; + +module.exports = { + getOptionsString, +}; diff --git a/forward_engineering/ddlProvider/ddlHelpers/index/getIndexName.js b/forward_engineering/ddlProvider/ddlHelpers/index/getIndexName.js new file mode 100644 index 0000000..4791168 --- /dev/null +++ b/forward_engineering/ddlProvider/ddlHelpers/index/getIndexName.js @@ -0,0 +1,21 @@ +/** @import {IndexData} from '../../../types/ddlProvider' */ + +const { getNamePrefixedWithSchemaName } = require('../../../utils/general'); + +/** + * Build a schema-qualified index name for CREATE INDEX. + * + * @param {{ index: IndexData }} params Index data. + * @returns {string} Prefixed index name. + */ +const getIndexName = ({ index }) => { + if (!index.indxName) { + return ''; + } + + return ` ${getNamePrefixedWithSchemaName({ name: index.indxName, schemaName: index.schemaName })}`; +}; + +module.exports = { + getIndexName, +}; diff --git a/forward_engineering/ddlProvider/ddlHelpers/index/getIndexOptions.js b/forward_engineering/ddlProvider/ddlHelpers/index/getIndexOptions.js new file mode 100644 index 0000000..04ffc5e --- /dev/null +++ b/forward_engineering/ddlProvider/ddlHelpers/index/getIndexOptions.js @@ -0,0 +1,178 @@ +/** + * @import { + * IndexData, + * IndexKeyRef + * } from '../../../types/ddlProvider' + */ + +const isNumber = require('lodash/isNumber'); +const toUpper = require('lodash/toUpper'); +const trim = require('lodash/trim'); +const { getBasicValue, getOptionsByConfigs } = require('../options/getOptionsByConfigs'); +const { wrapInQuotes, columnMapToStringWithOrder } = require('../../../utils/general'); + +/** + * Convert a value to upper case. + * + * @param {string} value Value to convert. + * @returns {string} Upper-case value. + */ +const toUpperCase = value => toUpper(value); + +/** + * Format a value in upper case, with an optional prefix. + * + * @param {string} prefix Clause prefix. + * @returns {(value: string) => string} Value formatter. + */ +const getUpperCaseValue = prefix => getBasicValue({ prefix, modifier: toUpperCase }); + +/** + * Build index key list clause. The model stores the order as `ascending`/`descending`, which Db2 for z/OS spells + * `ASC`/`DESC`. + * + * @param {IndexKeyRef[]} [keys] Index keys. + * @returns {string} Keys clause. + */ +const getIndexKeys = (keys = []) => { + if (keys.length === 0) { + return ''; + } + + const keysClause = keys.map(({ name, type }) => columnMapToStringWithOrder({ name: name ?? '', type })).join(', '); + + return `(${keysClause})`; +}; + +/** + * Build INCLUDE column list for unique indexes. An INCLUDE column list carries names only - the columns are not part of + * the index key, so they take no ordering. + * + * @param {IndexKeyRef[] | undefined} keys Include keys. + * @param {IndexData} index Index data. + * @returns {string} INCLUDE clause. + */ +const getIncludeIndexKeys = (keys, index) => { + const isUnique = index.indxType === 'unique' || index.indxType === 'uniqueWhereNotNull'; + if (!isUnique || index.indxNullKeys === 'exclude' || !keys?.length) { + return ''; + } + + const includeIndexKeys = `(${keys.map(({ name }) => wrapInQuotes(name ?? '')).join(', ')})`; + + return getBasicValue({ prefix: 'INCLUDE' })(includeIndexKeys); +}; + +/** + * Build USING STOGROUP / VCAT clause. + * + * @param {unknown} _value Unused key value. + * @param {IndexData} index Index data. + * @returns {string} USING clause. + */ +const getUsingClause = (_value, index) => { + if (index.indxUsingType === 'STOGROUP' && index.indxStogroup) { + const parts = [`USING STOGROUP ${index.indxStogroup}`]; + if (isNumber(index.indxPriQty)) { + parts.push(`PRIQTY ${index.indxPriQty}`); + } + if (isNumber(index.indxSecQty)) { + parts.push(`SECQTY ${index.indxSecQty}`); + } + if (index.indxErase) { + parts.push(`ERASE ${index.indxErase}`); + } + + return parts.join(' '); + } + + if (index.indxUsingType === 'VCAT' && index.indxVcat) { + return `USING VCAT ${index.indxVcat}`; + } + + return ''; +}; + +/** + * Build NULL KEYS clause. + * + * @param {string} value Include/exclude value. + * @returns {string} NULL KEYS clause. + */ +const getNullKeysClause = value => { + if (value === 'include') { + return 'INCLUDE NULL KEYS'; + } + if (value === 'exclude') { + return 'EXCLUDE NULL KEYS'; + } + + return ''; +}; + +/** + * Build PIECESIZE clause. + * + * @param {unknown} value Piecesize value. + * @param {IndexData} index Index data. + * @returns {string} PIECESIZE clause. + */ +const getPiecesizeClause = (value, index) => { + if (!isNumber(value)) { + return ''; + } + + const unit = index.indxPiecesizeUnit ? ` ${index.indxPiecesizeUnit}` : ''; + + return `PIECESIZE ${value}${unit}`; +}; + +/** + * Build PARTITIONED clause. + * + * @param {boolean | undefined} value Partitioned flag. + * @returns {string} PARTITIONED clause. + */ +const getPartitionedClause = value => (value ? 'PARTITIONED' : ''); + +/** + * Append the raw index properties the user typed as an escape hatch for clauses the UI does not model. + * + * @param {string | undefined} value Raw DDL fragment. + * @returns {string} Trimmed properties. + */ +const getIndexProperties = value => trim(value); + +/** + * Build index options clause for CREATE INDEX. + * + * @param {{ index: IndexData }} params Index data. + * @returns {string} Index options. + */ +const getIndexOptions = ({ index }) => { + const configs = [ + { key: 'indxKey', getValue: getIndexKeys }, + { key: 'indxIncludeKey', getValue: getIncludeIndexKeys }, + { key: 'indxCluster', getValue: getUpperCaseValue('') }, + { key: 'indxPartitioned', getValue: getPartitionedClause }, + { key: 'indxPadded', getValue: getUpperCaseValue('') }, + { key: 'indxUsingType', getValue: getUsingClause }, + { key: 'indxFreepage', getValue: getBasicValue({ prefix: 'FREEPAGE' }) }, + { key: 'indxPctfree', getValue: getBasicValue({ prefix: 'PCTFREE' }) }, + { key: 'indxDefine', getValue: getUpperCaseValue('DEFINE') }, + { key: 'indxCompress', getValue: getUpperCaseValue('COMPRESS') }, + { key: 'indxNullKeys', getValue: getNullKeysClause }, + { key: 'indxBufferPool', getValue: getBasicValue({ prefix: 'BUFFERPOOL' }) }, + { key: 'indxClose', getValue: getUpperCaseValue('CLOSE') }, + { key: 'indxDefer', getValue: getUpperCaseValue('DEFER') }, + { key: 'indxPiecesize', getValue: getPiecesizeClause }, + { key: 'indxCopy', getValue: getUpperCaseValue('COPY') }, + { key: 'indxProperties', getValue: getIndexProperties }, + ]; + + return getOptionsByConfigs({ configs, data: index }); +}; + +module.exports = { + getIndexOptions, +}; diff --git a/forward_engineering/ddlProvider/ddlHelpers/index/getIndexType.js b/forward_engineering/ddlProvider/ddlHelpers/index/getIndexType.js new file mode 100644 index 0000000..4309ce8 --- /dev/null +++ b/forward_engineering/ddlProvider/ddlHelpers/index/getIndexType.js @@ -0,0 +1,23 @@ +/** @import {IndexData} from '../../../types/ddlProvider' */ + +/** + * Map UI index type to CREATE INDEX type clause. + * + * @param {{ index: IndexData }} params Index data. + * @returns {string} Index type clause. + */ +const getIndexType = ({ index }) => { + if (index.indxType === 'uniqueWhereNotNull') { + return ' UNIQUE WHERE NOT NULL'; + } + + if (index.indxType === 'unique') { + return ' UNIQUE'; + } + + return ''; +}; + +module.exports = { + getIndexType, +}; diff --git a/forward_engineering/ddlProvider/ddlHelpers/jsonSchema/jsonSchemaHelper.js b/forward_engineering/ddlProvider/ddlHelpers/jsonSchema/jsonSchemaHelper.js new file mode 100644 index 0000000..84af221 --- /dev/null +++ b/forward_engineering/ddlProvider/ddlHelpers/jsonSchema/jsonSchemaHelper.js @@ -0,0 +1,94 @@ +/** + * @import { + * FieldNameLookupParams, + * IdToNameMap, + * WalkableSchema, + * WalkSchemaParams + * } from '../../../types/ddlProvider' + */ + +/** + * Resolve a schema item name. + * + * @param {{ item?: WalkableSchema }} params Schema item. + * @returns {string} Item name. + */ +const getName = ({ item }) => { + const schemaItem = item ?? {}; + return schemaItem.code ?? schemaItem.collectionName ?? schemaItem.name ?? ''; +}; + +/** + * Walk schema properties recursively. + * + * @param {WalkSchemaParams} params Walk params. + * @returns {void} + */ +const eachProperty = ({ jsonSchema, path, callback }) => { + if (jsonSchema.properties) { + Object.entries(jsonSchema.properties).forEach(([propertyName, property]) => { + const nextPath = property.GUID ? [...path, property.GUID] : path; + + callback({ propertyName, property, path: nextPath }); + + eachProperty({ jsonSchema: property, path: nextPath, callback }); + }); + } + + if (jsonSchema.items) { + const items = Array.isArray(jsonSchema.items) ? jsonSchema.items : [jsonSchema.items]; + + items.forEach((item, i) => { + const nextPath = item.GUID ? [...path, item.GUID] : path; + + callback({ propertyName: String(i), property: item, path: nextPath }); + + eachProperty({ jsonSchema: item, path: nextPath, callback }); + }); + } +}; + +/** + * Build GUID-to-name lookup table. + * + * @param {{ jsonSchema?: WalkableSchema }} params JSON schema. + * @returns {IdToNameMap} Id to name map. + */ +const getIdToNameHashTable = ({ jsonSchema }) => { + const schema = jsonSchema ?? {}; + /** @type {Record} */ + const IdToNameHashTable = {}; + + /** + * Collect a property name. + * + * @param {{ propertyName: string; property: WalkableSchema }} params Property info. + * @returns {void} + */ + const callback = ({ propertyName, property }) => { + if (property.GUID) { + IdToNameHashTable[property.GUID] = getName({ item: property }) || propertyName; + } + }; + + eachProperty({ jsonSchema: schema, path: [], callback }); + + return IdToNameHashTable; +}; + +/** + * Resolve a field list name from a key ref. + * + * @param {FieldNameLookupParams} params Lookup params. + * @returns {string} Field name. + */ +const resolveFieldListName = ({ keyRef, idToNameHashTable }) => { + const keyId = keyRef?.[0]?.keyId; + return keyId ? idToNameHashTable[keyId] || '' : ''; +}; + +module.exports = { + getIdToNameHashTable, + getName, + resolveFieldListName, +}; diff --git a/forward_engineering/ddlProvider/ddlHelpers/key/constraintsHelper.js b/forward_engineering/ddlProvider/ddlHelpers/key/constraintsHelper.js new file mode 100644 index 0000000..1bf9620 --- /dev/null +++ b/forward_engineering/ddlProvider/ddlHelpers/key/constraintsHelper.js @@ -0,0 +1,117 @@ +/** + * @import { + * AlterKeyConfig, + * AlterKeyStatement + * } from '../../../types/ddlProvider' + */ + +const { checkAllKeysDeactivated, getColumnsList, wrapInQuotes } = require('../../../utils/general'); +const { assignTemplates } = require('../../../utils/assignTemplates'); +const templates = require('../../templates'); + +/** + * Build key options for alter PK/UK statements. Db2 for z/OS accepts no constraint attributes beyond the name and the + * column list, so the options fragment is always empty. + * + * @param {AlterKeyConfig} keyData Key data. + * @param {boolean} isParentActivated Parent activation. + * @returns {{ constraintName: string; columns: string; options: string; isActivated: boolean }} Template data and + * activation flag. + */ +const getKeyOptions = (keyData, isParentActivated) => { + const constraintName = wrapInQuotes(keyData.name.trim()); + const columnsList = keyData.columns ?? []; + const isAllColumnsDeactivated = checkAllKeysDeactivated({ keys: columnsList }); + const columns = + columnsList.length === 0 ? '' : getColumnsList(columnsList, isAllColumnsDeactivated, isParentActivated); + + return { + constraintName, + columns, + options: '', + isActivated: !isAllColumnsDeactivated && isParentActivated, + }; +}; + +/** + * Build ALTER TABLE ADD PRIMARY KEY statement. + * + * @param {string} tableName Table name. + * @param {boolean} isParentActivated Parent activation. + * @param {AlterKeyConfig} keyData Key data. + * @returns {AlterKeyStatement} Statement and activation flag. + */ +const alterPkConstraint = (tableName, isParentActivated, keyData) => { + const { isActivated, ...templateData } = getKeyOptions(keyData, isParentActivated); + + return { + statement: assignTemplates({ + template: templates.alterPkConstraint, + templateData: { + tableName, + ...templateData, + }, + }), + isActivated, + }; +}; + +/** + * Build DROP PRIMARY KEY statement. + * + * @param {string} tableName Table name. + * @returns {string} DDL. + */ +const dropPK = tableName => { + return assignTemplates({ + template: templates.dropPK, + templateData: { tableName }, + }); +}; + +/** + * Build ALTER TABLE ADD UNIQUE statement. + * + * @param {string} tableName Table name. + * @param {boolean} isParentActivated Parent activation. + * @param {AlterKeyConfig} keyData Key data. + * @returns {AlterKeyStatement} Statement and activation flag. + */ +const alterUkConstraint = (tableName, isParentActivated, keyData) => { + const { isActivated, ...templateData } = getKeyOptions(keyData, isParentActivated); + + return { + statement: assignTemplates({ + template: templates.alterUkConstraint, + templateData: { + tableName, + ...templateData, + }, + }), + isActivated, + }; +}; + +/** + * Build DROP UNIQUE statement. + * + * @param {string} tableName Table name. + * @param {string} constraintName Constraint name. + * @returns {string} DDL. + */ +const dropUkConstraint = (tableName, constraintName) => { + return assignTemplates({ + template: templates.dropUkConstraint, + templateData: { + tableName, + constraintName, + }, + }); +}; + +module.exports = { + alterPkConstraint, + dropPK, + alterUkConstraint, + dropUkConstraint, +}; diff --git a/forward_engineering/ddlProvider/ddlHelpers/key/getDefaultConstraintName.js b/forward_engineering/ddlProvider/ddlHelpers/key/getDefaultConstraintName.js new file mode 100644 index 0000000..3f15655 --- /dev/null +++ b/forward_engineering/ddlProvider/ddlHelpers/key/getDefaultConstraintName.js @@ -0,0 +1,15 @@ +/** + * Build the constraint name to fall back on when the user did not name a key. + * + * Both the CREATE and the ALTER path have to agree on it: if a table is created without an explicit constraint name, + * Db2 for z/OS assigns a system-generated one, and a later `DROP UNIQUE` built from a name the plugin invented would + * not match anything in the catalog. + * + * @param {{ entityName?: string; postfix: string }} params Table name and constraint postfix. + * @returns {string} Constraint name, or an empty string when the table name is unknown. + */ +const getDefaultConstraintName = ({ entityName, postfix }) => (entityName ? [entityName, postfix].join('_') : ''); + +module.exports = { + getDefaultConstraintName, +}; diff --git a/forward_engineering/ddlProvider/ddlHelpers/key/keyHelper.js b/forward_engineering/ddlProvider/ddlHelpers/key/keyHelper.js new file mode 100644 index 0000000..7feda21 --- /dev/null +++ b/forward_engineering/ddlProvider/ddlHelpers/key/keyHelper.js @@ -0,0 +1,282 @@ +/** + * @import { + * ForeignKeyCustomPropertiesParams, + * HydrateKeyOptionsParams, + * JsonSchema, + * JsonSchemaColumn, + * KeyConstraint, + * KeyConstraintColumn, + * KeyPropertyLookupParams, + * KeyRef + * } from '../../../types/ddlProvider' + */ + +const isEmpty = require('lodash/isEmpty'); +const trim = require('lodash/trim'); +const { wrapInQuotes, commentIfDeactivated, checkIsKeyActivated } = require('../../../utils/general'); +const { CONSTRAINT_POSTFIX } = require('../../../../shared/constants/constants'); +const { getDefaultConstraintName } = require('./getDefaultConstraintName'); + +/** @enum {string} */ +const KEY_TYPE = { + primaryKey: 'PRIMARY KEY', + unique: 'UNIQUE', +}; + +/** + * Map schema properties with an iteratee. + * + * @param {JsonSchema} jsonSchema JSON schema. + * @param {(entry: [string, JsonSchemaColumn]) => KeyConstraint | null} iteratee Mapper. + * @returns {(KeyConstraint | null)[]} Mapped properties. + */ +const mapProperties = (jsonSchema, iteratee) => { + return Object.entries(jsonSchema.properties ?? {}).map(entry => iteratee(entry)); +}; + +/** + * Check whether a column is a unique key. + * + * @param {{ column: JsonSchemaColumn }} params Column. + * @returns {boolean} Whether unique key. + */ +const isUniqueKey = ({ column }) => { + return Boolean(!column.compositeUniqueKey && column.unique); +}; + +/** + * Check whether a unique key is inline. + * + * @param {{ column: JsonSchemaColumn }} params Column. + * @returns {boolean} Whether inline unique. + */ +const isInlineUnique = ({ column }) => { + return isUniqueKey({ column }) && !trim(column.uniqueKeyOptions?.constraintName); +}; + +/** + * Check whether a column is a primary key. + * + * @param {{ column: JsonSchemaColumn }} params Column. + * @returns {boolean} Whether primary key. + */ +const isPrimaryKey = ({ column }) => { + return Boolean(!column.compositeUniqueKey && !column.compositePrimaryKey && column.primaryKey); +}; + +/** + * Check whether a primary key is inline. + * + * @param {{ column: JsonSchemaColumn }} params Column. + * @returns {boolean} Whether inline primary key. + */ +const isInlinePrimaryKey = ({ column }) => { + return isPrimaryKey({ column }) && !trim(column.primaryKeyOptions?.constraintName); +}; + +/** + * Hydrate key constraint options. An unnamed key gets the same fallback name the alter script uses, so that a + * constraint created here can be dropped again later. + * + * @param {HydrateKeyOptionsParams} params Key options. + * @returns {KeyConstraint} Hydrated key options. + */ +const hydrateKeyOptions = ({ columnName, isActivated, options, keyType, entityName }) => { + const postfix = keyType === KEY_TYPE.primaryKey ? CONSTRAINT_POSTFIX.primaryKey : CONSTRAINT_POSTFIX.uniqueKey; + + return { + keyType, + columns: [ + { + name: columnName, + isActivated: isActivated, + }, + ], + constraintName: trim(options?.constraintName) || getDefaultConstraintName({ entityName, postfix }), + }; +}; + +/** + * Find property name by key id. + * + * @param {KeyPropertyLookupParams} params Lookup params. + * @returns {string | undefined} Property name. + */ +const findName = ({ keyId, properties }) => { + return Object.keys(properties).find(name => properties[name].GUID === keyId); +}; + +/** + * Check whether a key is activated. + * + * @param {KeyPropertyLookupParams} params Lookup params. + * @returns {boolean} Activation flag. + */ +const checkIfActivated = ({ keyId, properties }) => { + const key = Object.values(properties).find(prop => prop.GUID === keyId); + + return key?.isActivated ?? true; +}; + +/** + * Resolve key columns from refs. + * + * @param {{ jsonSchema: JsonSchema; keys?: KeyRef[] }} params Keys input. + * @returns {KeyConstraintColumn[]} Resolved keys. + */ +const getKeys = ({ jsonSchema, keys }) => { + const keyList = keys ?? []; + const properties = jsonSchema.properties ?? {}; + return keyList.map(key => { + const name = findName({ keyId: key.keyId, properties }); + const isActivated = checkIfActivated({ keyId: key.keyId, properties }); + + return { + name, + isActivated, + type: key.type, + }; + }); +}; + +/** + * Get composite primary key constraints. + * + * @param {{ jsonSchema: JsonSchema; entityName?: string }} params Schema and table name. + * @returns {KeyConstraint[]} Primary key constraints. + */ +const getCompositePrimaryKeys = ({ jsonSchema, entityName }) => { + if (!Array.isArray(jsonSchema.primaryKey)) { + return []; + } + + return jsonSchema.primaryKey + .filter(primaryKey => !isEmpty(primaryKey.compositePrimaryKey)) + .map(primaryKey => + Object.assign(hydrateKeyOptions({ options: primaryKey, keyType: KEY_TYPE.primaryKey, entityName }), { + columns: getKeys({ keys: primaryKey.compositePrimaryKey, jsonSchema }), + }), + ); +}; + +/** + * Get composite unique key constraints. + * + * @param {{ jsonSchema: JsonSchema; entityName?: string }} params Schema and table name. + * @returns {KeyConstraint[]} Unique key constraints. + */ +const getCompositeUniqueKeys = ({ jsonSchema, entityName }) => { + if (!Array.isArray(jsonSchema.uniqueKey)) { + return []; + } + + return jsonSchema.uniqueKey + .filter(uniqueKey => !isEmpty(uniqueKey.compositeUniqueKey)) + .map(uniqueKey => + Object.assign(hydrateKeyOptions({ options: uniqueKey, keyType: KEY_TYPE.unique, entityName }), { + columns: getKeys({ keys: uniqueKey.compositeUniqueKey, jsonSchema }), + }), + ); +}; + +/** + * Collect table-level key constraints. + * + * @param {{ jsonSchema: JsonSchema; entityName?: string }} params Schema and table name. + * @returns {KeyConstraint[]} Key constraints. + */ +const getTableKeyConstraints = ({ jsonSchema, entityName }) => { + if (!jsonSchema.properties) { + return []; + } + + const uniqueConstraints = mapProperties(jsonSchema, ([name, column]) => { + if (!isUniqueKey({ column }) || isInlineUnique({ column })) { + return null; + } + return hydrateKeyOptions({ + columnName: name, + isActivated: column.isActivated, + options: column.uniqueKeyOptions, + keyType: KEY_TYPE.unique, + entityName, + }); + }).filter(constraint => constraint !== null); + + const primaryKeyConstraints = mapProperties(jsonSchema, ([name, column]) => { + if (!isPrimaryKey({ column }) || isInlinePrimaryKey({ column })) { + return null; + } + return hydrateKeyOptions({ + columnName: name, + isActivated: column.isActivated, + options: column.primaryKeyOptions, + keyType: KEY_TYPE.primaryKey, + entityName, + }); + }).filter(constraint => constraint !== null); + + return [ + ...primaryKeyConstraints, + ...getCompositePrimaryKeys({ jsonSchema, entityName }), + ...uniqueConstraints, + ...getCompositeUniqueKeys({ jsonSchema, entityName }), + ]; +}; + +/** + * Convert foreign keys to a quoted list string. + * + * @param {{ keys: KeyConstraintColumn[] | string }} params Keys. + * @returns {string} Keys string. + */ +const foreignKeysToString = ({ keys }) => { + if (Array.isArray(keys)) { + const activatedKeys = keys.filter(key => checkIsKeyActivated({ key })).map(key => wrapInQuotes(trim(key.name))); + const deactivatedKeys = keys + .filter(key => !checkIsKeyActivated({ key })) + .map(key => wrapInQuotes(trim(key.name))); + const deactivatedKeysAsString = + deactivatedKeys.length > 0 + ? commentIfDeactivated(deactivatedKeys.join(', '), { isActivated: false, isPartOfLine: true }) + : ''; + + return activatedKeys.join(', ') + deactivatedKeysAsString; + } + return keys; +}; + +/** + * Convert active foreign keys to a list string. + * + * @param {{ keys: KeyConstraintColumn[] }} params Keys. + * @returns {string} Keys string. + */ +const foreignActiveKeysToString = ({ keys }) => { + return keys.map(key => trim(key.name)).join(', '); +}; + +/** + * Build ON DELETE / ENFORCED clauses for foreign keys. + * + * @param {ForeignKeyCustomPropertiesParams} params Custom properties. + * @returns {string} Foreign key action clauses. + */ +const customPropertiesForForeignKey = ({ customProperties }) => { + const properties = customProperties ?? {}; + const { relationshipOnDelete, relationshipEnforced } = properties; + const relationshipOnDeleteClause = relationshipOnDelete ? ' ON DELETE ' + relationshipOnDelete : ''; + const relationshipEnforcedClause = relationshipEnforced ? ' ' + relationshipEnforced : ''; + + return relationshipOnDeleteClause + relationshipEnforcedClause; +}; + +module.exports = { + getTableKeyConstraints, + isInlineUnique, + isInlinePrimaryKey, + foreignKeysToString, + foreignActiveKeysToString, + customPropertiesForForeignKey, + KEY_TYPE, +}; diff --git a/forward_engineering/ddlProvider/ddlHelpers/options/getOptionsByConfigs.js b/forward_engineering/ddlProvider/ddlHelpers/options/getOptionsByConfigs.js new file mode 100644 index 0000000..d708e89 --- /dev/null +++ b/forward_engineering/ddlProvider/ddlHelpers/options/getOptionsByConfigs.js @@ -0,0 +1,73 @@ +/** + * @import { + * BasicValueParams, + * OptionsByConfigsParams + * } from '../../../types/ddlProvider' + */ + +const get = require('lodash/get'); +const trim = require('lodash/trim'); + +/** + * Check whether an option carries a value worth rendering. Zero is a meaningful setting for Db2 options such as + * `PCTFREE 0` and `FREEPAGE 0`, so it cannot be filtered out along with the empty and disabled ones. + * + * @param {unknown} value Option value. + * @returns {boolean} Whether the option has to be rendered. + */ +const hasOptionValue = value => value !== undefined && value !== null && value !== '' && value !== false; + +/** + * Build a basic prefixed/postfixed value formatter. + * + * @template T + * @param {BasicValueParams} params Formatter options. + * @returns {(value: T) => string} Value formatter. + */ +const getBasicValue = ({ prefix = '', postfix = '', modifier }) => { + /** + * Identity passthrough. + * + * @param {T} value Input value. + * @returns {T} Same value. + */ + const resolveModifier = + modifier ?? + /** + * @param {T} value Input value. + * @returns {T} Same value. + */ + (value => value); + /** + * Format a value. + * + * @param {T} value Input value. + * @returns {string} Formatted value. + */ + return value => + hasOptionValue(value) + ? [prefix, String(resolveModifier(value)), postfix] + .filter(Boolean) + .map(part => trim(part)) + .join(' ') + : ''; +}; + +/** + * @param {OptionsByConfigsParams} params Configs and data. + * @returns {string} Options string. + */ +const getOptionsByConfigs = ({ configs, data }) => { + const statements = configs + .filter(({ key }) => hasOptionValue(get(data, key))) + .map(({ key, getValue }) => getValue(get(data, key), data)) + .filter(Boolean) + .join('\n\t'); + + return getBasicValue({ prefix: ' ' })(statements); +}; + +module.exports = { + getBasicValue, + getOptionsByConfigs, +}; diff --git a/forward_engineering/ddlProvider/ddlHelpers/table/getTableOptions.js b/forward_engineering/ddlProvider/ddlHelpers/table/getTableOptions.js new file mode 100644 index 0000000..60ea998 --- /dev/null +++ b/forward_engineering/ddlProvider/ddlHelpers/table/getTableOptions.js @@ -0,0 +1,387 @@ +/** + * @import { + * CreateTableParams, + * HydratedPartitioning, + * InClauseParams, + * OptionConfig, + * TableOptionsBlock, + * TemporalPeriodsParams + * } from '../../../types/ddlProvider' + */ + +const isNumber = require('lodash/isNumber'); +const toUpper = require('lodash/toUpper'); +const { wrapInQuotes, columnMapToStringWithOrder } = require('../../../utils/general'); +const { getOptionsByConfigs, getBasicValue } = require('../options/getOptionsByConfigs'); + +/** + * Build IN clause for table options. + * + * @param {InClauseParams} params IN clause params. + * @returns {string} IN clause. + */ +const getInClause = ({ inClauseType, databaseName, table_tablespace_name, acceleratorName }) => { + if (inClauseType === 'tablespace') { + if (databaseName && table_tablespace_name) { + return `IN ${databaseName}.${table_tablespace_name}`; + } + if (table_tablespace_name) { + return `IN ${table_tablespace_name}`; + } + return ''; + } + + if (inClauseType === 'database') { + return databaseName ? `IN DATABASE ${databaseName}` : ''; + } + + if (inClauseType === 'accelerator') { + return acceleratorName ? `IN ACCELERATOR ${acceleratorName}` : ''; + } + + return ''; +}; + +/** + * Build structured table options. + * + * @param {{ tableOptions?: TableOptionsBlock; inClauseType?: string }} params Table options. + * @returns {string} Options string. + */ +const getStructuredTableOptions = ({ tableOptions, inClauseType }) => { + const options = tableOptions ?? {}; + if (inClauseType === 'accelerator') { + return ''; + } + + const isExistingTablespace = inClauseType === 'tablespace'; + + /** @type {OptionConfig[]} */ + const configs = [ + { + key: 'editProc', + /** + * Format EDITPROC option. + * + * @param {string} value Edit proc name. + * @returns {string} Option clause. + */ + getValue: value => { + const rowAttributes = options.editProcRowAttributes ? ` ${options.editProcRowAttributes}` : ''; + return `EDITPROC ${value}${rowAttributes}`; + }, + }, + { + key: 'validProc', + getValue: getBasicValue({ prefix: 'VALIDPROC' }), + }, + { + key: 'audit', + getValue: getBasicValue({ prefix: 'AUDIT' }), + }, + { + key: 'obid', + /** + * Format OBID option. + * + * @param {number} value OBID value. + * @returns {string} Option clause. + */ + getValue: value => (isNumber(value) ? `OBID ${value}` : ''), + }, + { + key: 'dataCapture', + getValue: getBasicValue({ prefix: 'DATA CAPTURE' }), + }, + { + key: 'withRestrictOnDrop', + /** + * Format WITH RESTRICT ON DROP option. + * + * @param {boolean} value Flag. + * @returns {string} Option clause. + */ + getValue: value => (value ? 'WITH RESTRICT ON DROP' : ''), + }, + { + key: 'ccsid', + getValue: getBasicValue({ prefix: 'CCSID' }), + }, + { + key: 'volatile', + /** + * Format VOLATILE option. + * + * @param {string} value Volatile mode. + * @returns {string} Option clause. + */ + getValue: value => { + if (value === 'VOLATILE') { + return 'VOLATILE'; + } + if (value === 'NOT VOLATILE') { + return 'NOT VOLATILE'; + } + return ''; + }, + }, + { + key: 'logged', + /** + * Format LOGGED option. + * + * @param {string} value Logged value. + * @returns {string} Option clause. + */ + getValue: value => (isExistingTablespace ? '' : (value ?? '')), + }, + { + key: 'compress', + /** + * Format COMPRESS option. + * + * @param {string} value Compress mode. + * @returns {string} Option clause. + */ + getValue: value => (isExistingTablespace ? '' : value ? `COMPRESS ${value}` : ''), + }, + { + key: 'append', + getValue: getBasicValue({ prefix: 'APPEND' }), + }, + { + key: 'dssize', + /** + * Format DSSIZE option. + * + * @param {number} value Size in G. + * @returns {string} Option clause. + */ + getValue: value => (isExistingTablespace || !isNumber(value) ? '' : `DSSIZE ${value} G`), + }, + { + key: 'bufferPool', + /** + * Format BUFFERPOOL option. + * + * @param {string} value Buffer pool name. + * @returns {string} Option clause. + */ + getValue: value => (isExistingTablespace ? '' : value ? `BUFFERPOOL ${value}` : ''), + }, + { + key: 'memberCluster', + /** + * Format MEMBER CLUSTER option. + * + * @param {boolean} value Flag. + * @returns {string} Option clause. + */ + getValue: value => (isExistingTablespace || !value ? '' : 'MEMBER CLUSTER'), + }, + { + key: 'trackMod', + /** + * Format TRACKMOD option. + * + * @param {string} value Trackmod mode. + * @returns {string} Option clause. + */ + getValue: value => (isExistingTablespace ? '' : value ? `TRACKMOD ${value}` : ''), + }, + { + key: 'pageNum', + /** + * Format PAGENUM option. + * + * @param {string} value Page number mode. + * @returns {string} Option clause. + */ + getValue: value => (value ? `PAGENUM ${value}` : ''), + }, + { + key: 'keyLabelMode', + /** + * Format KEY LABEL option. + * + * @param {string} value Key label mode. + * @returns {string} Option clause. + */ + getValue: value => { + if (value === 'NO KEY LABEL') { + return 'NO KEY LABEL'; + } + if (value === 'KEY LABEL' && options.keyLabelName) { + return `KEY LABEL ${options.keyLabelName}`; + } + return ''; + }, + }, + ]; + + const data = { + ...options, + withRestrictOnDrop: options.withRestrictOnDrop ? true : undefined, + memberCluster: options.memberCluster ? true : undefined, + }; + + return getOptionsByConfigs({ configs, data }); +}; + +/** + * Build partitioning clause. + * + * @param {{ partitioning?: HydratedPartitioning }} params Partitioning data. + * @returns {string} Partitioning clause. + */ +const getPartitioningClause = ({ partitioning }) => { + if (!partitioning?.partitionBy) { + return ''; + } + + if (partitioning.partitionBy === 'SIZE') { + if (!isNumber(partitioning.everySize)) { + return ''; + } + return `PARTITION BY SIZE EVERY ${partitioning.everySize} G`; + } + + if (partitioning.partitionBy === 'RANGE') { + // NULLS LAST belongs to each partition-expression, not to the key list as a whole. + const nullsLast = partitioning.nullsLast ? ' NULLS LAST' : ''; + const keyColumns = (partitioning.partitionKey ?? []) + .map(key => columnMapToStringWithOrder(key) + nullsLast) + .join(', '); + + if (!keyColumns) { + return ''; + } + + const partitions = (partitioning.partitions ?? []) + .filter(partition => isNumber(partition.partitionNumber) && partition.endingAt) + .map(partition => { + const inclusive = partition.inclusive ? ' INCLUSIVE' : ''; + return `PARTITION ${partition.partitionNumber} ENDING AT (${partition.endingAt})${inclusive}`; + }); + + const partitionsClause = partitions.length > 0 ? ` (\n\t\t${partitions.join(',\n\t\t')}\n\t)` : ''; + + return `PARTITION BY RANGE (${keyColumns})${partitionsClause}`; + } + + return ''; +}; + +/** + * Build temporal period clauses. + * + * @param {TemporalPeriodsParams} params Period data. + * @returns {string} Period clauses. + */ +const getTemporalPeriodsClause = ({ periodForSystemTime, periodForBusinessTime }) => { + const clauses = []; + + if (periodForSystemTime?.startColumn && periodForSystemTime?.endColumn) { + clauses.push( + `PERIOD FOR SYSTEM_TIME (${wrapInQuotes(periodForSystemTime.startColumn)}, ${wrapInQuotes(periodForSystemTime.endColumn)})`, + ); + } + + if (periodForBusinessTime?.startColumn && periodForBusinessTime?.endColumn) { + const endInclusive = periodForBusinessTime.endInclusive + ? ` ${toUpper(periodForBusinessTime.endInclusive)}` + : ''; + clauses.push( + `PERIOD FOR BUSINESS_TIME (${wrapInQuotes(periodForBusinessTime.startColumn)}, ${wrapInQuotes(periodForBusinessTime.endColumn)}${endInclusive})`, + ); + } + + return clauses.join('\n\t'); +}; + +/** + * Build the AS (fullselect) clause and refresh/maintenance options of a materialized query table. + * + * @param {Partial} tableData Table data. + * @returns {string} MQT clause. + */ +const getMqtClause = ({ mqtQuery, mqtDataOption, mqtRefresh, mqtMaintainedBy, mqtQueryOptimization }) => { + if (!mqtQuery) { + return ''; + } + + const refresh = mqtRefresh ? `REFRESH ${mqtRefresh}` : ''; + const maintainedBy = mqtMaintainedBy ? `MAINTAINED BY ${mqtMaintainedBy}` : ''; + + return [`AS (${mqtQuery})`, mqtDataOption, refresh, maintainedBy, mqtQueryOptimization] + .filter(Boolean) + .join('\n\t'); +}; + +/** + * Build full table options clause. + * + * @param {Partial} tableData Table data. + * @returns {string} Table options DDL. + */ +const getTableOptions = tableData => { + if (tableData.auxiliary) { + /** @type {OptionConfig[]} */ + const configs = [ + { + key: 'auxiliaryBaseTable', + getValue: getBasicValue({ prefix: 'STORES' }), + }, + { + key: 'auxiliaryBaseColumn', + getValue: getBasicValue({ prefix: 'COLUMN', modifier: wrapInQuotes }), + }, + { + key: 'auxiliaryAppend', + getValue: getBasicValue({ + prefix: 'APPEND', + /** + * Uppercase auxiliary append value. + * + * @param {string} value Append value. + * @returns {string} Uppercased value. + */ + modifier: value => toUpper(value), + }), + }, + { + key: 'auxiliaryPart', + getValue: getBasicValue({ prefix: 'PART' }), + }, + ]; + + return getOptionsByConfigs({ configs, data: tableData }); + } + + const mqtClause = tableData.tableKind === 'materializedQuery' ? getMqtClause(tableData) : ''; + const inClause = getInClause(tableData); + const structuredOptions = getStructuredTableOptions(tableData); + const partitioning = tableData.inClauseType === 'accelerator' ? '' : getPartitioningClause(tableData); + const temporal = + tableData.inClauseType === 'accelerator' + ? '' + : getTemporalPeriodsClause({ + periodForSystemTime: tableData.periodForSystemTime, + periodForBusinessTime: tableData.periodForBusinessTime, + }); + const tableProperties = tableData.tableProperties ?? ''; + + const statements = [mqtClause, inClause, structuredOptions.trim(), partitioning, temporal, tableProperties] + .filter(Boolean) + .join('\n\t'); + + return statements ? ` ${statements}` : ''; +}; + +module.exports = { + getTableOptions, + getInClause, + getPartitioningClause, + getTemporalPeriodsClause, + getMqtClause, +}; diff --git a/forward_engineering/ddlProvider/ddlHelpers/table/getTableProps.js b/forward_engineering/ddlProvider/ddlHelpers/table/getTableProps.js new file mode 100644 index 0000000..af05789 --- /dev/null +++ b/forward_engineering/ddlProvider/ddlHelpers/table/getTableProps.js @@ -0,0 +1,136 @@ +/** + * @import { + * DividedConstraints, + * ForeignKeyStatement, + * KeyConstraint, + * TablePropsParams + * } from '../../../types/ddlProvider' + */ + +const templates = require('../../templates'); +const { assignTemplates } = require('../../../utils/assignTemplates'); +const { + getColumnsList, + checkAllKeysDeactivated, + commentIfDeactivated, + wrapInQuotes, + divideIntoActivatedAndDeactivated, +} = require('../../../utils/general'); +const { getOptionsString } = require('../constraint/getOptionsString'); +const { joinActivatedAndDeactivatedStatements } = require('../../../utils/joinActivatedAndDeactivatedStatements'); + +/** + * Extract constraint statement text. + * + * @param {ForeignKeyStatement} key Constraint object. + * @returns {string} Statement. + */ +const getKeyStatement = key => key.statement; + +/** + * Generate activated/deactivated constraints string. + * + * @param {{ dividedConstraints: DividedConstraints; isParentActivated: boolean }} params Constraint groups. + * @returns {string} Constraints DDL fragment. + */ +const generateConstraintsString = ({ dividedConstraints, isParentActivated }) => { + const { activatedItems, deactivatedItems } = dividedConstraints; + const deactivatedItemsAsString = commentIfDeactivated(deactivatedItems.join(',\n\t'), { + isActivated: !isParentActivated, + isPartOfLine: true, + }); + const activatedConstraints = + activatedItems.length > 0 ? ',\n\t' + dividedConstraints.activatedItems.join(',\n\t') : ''; + + const deactivatedConstraints = deactivatedItems.length > 0 ? '\n\t' + deactivatedItemsAsString : ''; + + return activatedConstraints + deactivatedConstraints; +}; + +/** + * Create a key constraint statement. + * + * @param {{ keyData: KeyConstraint; isParentActivated: boolean }} params Key data. + * @returns {ForeignKeyStatement} Constraint statement. + */ +const createKeyConstraint = ({ keyData, isParentActivated }) => { + const isAllColumnsDeactivated = checkAllKeysDeactivated({ keys: keyData.columns }); + const columns = getColumnsList(keyData.columns, isAllColumnsDeactivated, isParentActivated); + const options = getOptionsString(keyData).statement; + const constraintName = keyData.constraintName ? `CONSTRAINT ${wrapInQuotes(keyData.constraintName)} ` : ''; + + return { + statement: assignTemplates({ + template: templates.createKeyConstraint, + templateData: { + constraintName, + keyType: keyData.keyType, + columns, + options, + }, + }), + isActivated: !isAllColumnsDeactivated, + }; +}; + +/** + * Divide key constraints by activation. + * + * @param {{ keyConstraints: KeyConstraint[]; isActivated: boolean }} params Key constraints. + * @returns {DividedConstraints} Divided constraints. + */ +const getDividedKeysConstraints = ({ keyConstraints, isActivated }) => { + const keys = keyConstraints.map(keyData => createKeyConstraint({ keyData, isParentActivated: isActivated })); + + return divideIntoActivatedAndDeactivated({ items: keys, mapFunction: getKeyStatement }); +}; + +/** + * Divide foreign key constraints by activation. + * + * @param {{ foreignKeyConstraints: ForeignKeyStatement[] }} params Foreign keys. + * @returns {DividedConstraints} Divided constraints. + */ +const getDividedForeignKeyConstraints = ({ foreignKeyConstraints }) => { + return divideIntoActivatedAndDeactivated({ items: foreignKeyConstraints, mapFunction: getKeyStatement }); +}; + +/** + * Build table properties DDL fragment. + * + * @param {TablePropsParams} params Table props input. + * @returns {string} Table props DDL. + */ +const getTableProps = ({ columns, foreignKeyConstraints, keyConstraints, checkConstraints, isActivated }) => { + const dividedKeysConstraints = getDividedKeysConstraints({ keyConstraints, isActivated }); + const dividedForeignKeyConstraints = getDividedForeignKeyConstraints({ foreignKeyConstraints }); + const keyConstraintsString = generateConstraintsString({ + dividedConstraints: dividedKeysConstraints, + isParentActivated: isActivated, + }); + const foreignKeyConstraintsString = generateConstraintsString({ + dividedConstraints: dividedForeignKeyConstraints, + isParentActivated: isActivated, + }); + const checkConstraintsString = generateConstraintsString({ + dividedConstraints: { activatedItems: checkConstraints ?? [], deactivatedItems: [] }, + isParentActivated: isActivated, + }); + const columnsString = joinActivatedAndDeactivatedStatements({ statements: columns, indent: '\n\t' }); + + const tableProps = assignTemplates({ + template: templates.createTableProps, + templateData: { + columns: columnsString, + foreignKeyConstraints: foreignKeyConstraintsString, + keyConstraints: keyConstraintsString, + checkConstraints: checkConstraintsString, + }, + }); + + return tableProps ? `\n(\n\t${tableProps}\n)` : ''; +}; + +module.exports = { + getTableProps, +}; diff --git a/forward_engineering/ddlProvider/ddlHelpers/table/getTableType.js b/forward_engineering/ddlProvider/ddlHelpers/table/getTableType.js new file mode 100644 index 0000000..a5d4f75 --- /dev/null +++ b/forward_engineering/ddlProvider/ddlHelpers/table/getTableType.js @@ -0,0 +1,21 @@ +/** + * Resolve table type clause. + * + * @param {{ auxiliary?: boolean; tableKind?: string }} params Table flags. + * @returns {string} Table type clause. + */ +const getTableType = ({ auxiliary, tableKind }) => { + if (auxiliary) { + return ' AUXILIARY'; + } + + if (tableKind === 'globalTemporary') { + return ' GLOBAL TEMPORARY'; + } + + return ''; +}; + +module.exports = { + getTableType, +}; diff --git a/forward_engineering/ddlProvider/ddlHelpers/table/hydrateAuxiliaryTableData.js b/forward_engineering/ddlProvider/ddlHelpers/table/hydrateAuxiliaryTableData.js new file mode 100644 index 0000000..1a1cfe5 --- /dev/null +++ b/forward_engineering/ddlProvider/ddlHelpers/table/hydrateAuxiliaryTableData.js @@ -0,0 +1,50 @@ +/** + * @import { + * HydrateAuxiliaryTableParams, + * HydratedTable + * } from '../../../types/ddlProvider' + */ + +const { getNamePrefixedWithSchemaName } = require('../../../utils/general'); +const { getName, getIdToNameHashTable } = require('../jsonSchema/jsonSchemaHelper'); + +/** + * Hydrate auxiliary table options. + * + * @param {HydrateAuxiliaryTableParams} params Table data. + * @returns {Partial} Auxiliary table data. + */ +const hydrateAuxiliaryTableData = ({ tableData, detailsTab }) => { + const isAuxiliary = detailsTab.tableKind === 'auxiliary'; + + if (!isAuxiliary) { + return {}; + } + + const auxiliaryBaseTableJsonSchema = detailsTab.auxiliaryBaseTable + ? tableData.relatedSchemas?.[detailsTab.auxiliaryBaseTable] + : undefined; + const auxiliaryBaseTableSchemaName = auxiliaryBaseTableJsonSchema?.bucketName; + const idToNameHashTable = getIdToNameHashTable({ jsonSchema: auxiliaryBaseTableJsonSchema }); + const auxiliaryBaseTableName = getName({ item: auxiliaryBaseTableJsonSchema }); + const auxiliaryBaseTable = + auxiliaryBaseTableName && + getNamePrefixedWithSchemaName({ + name: auxiliaryBaseTableName, + schemaName: auxiliaryBaseTableSchemaName, + }); + const auxiliaryBaseColumnKey = detailsTab.auxiliaryBaseColumn?.[0]?.keyId; + const auxiliaryBaseColumn = auxiliaryBaseColumnKey ? idToNameHashTable[auxiliaryBaseColumnKey] : undefined; + + return { + auxiliary: isAuxiliary, + auxiliaryAppend: detailsTab.auxiliaryAppend, + auxiliaryPart: detailsTab.auxiliaryPart, + auxiliaryBaseTable, + auxiliaryBaseColumn, + }; +}; + +module.exports = { + hydrateAuxiliaryTableData, +}; diff --git a/forward_engineering/ddlProvider/ddlHelpers/table/hydrateGlobalTemporaryTableData.js b/forward_engineering/ddlProvider/ddlHelpers/table/hydrateGlobalTemporaryTableData.js new file mode 100644 index 0000000..7a8a19b --- /dev/null +++ b/forward_engineering/ddlProvider/ddlHelpers/table/hydrateGlobalTemporaryTableData.js @@ -0,0 +1,39 @@ +/** + * @import { + * HydratedTable, + * HydrateGlobalTemporaryTableParams + * } from '../../../types/ddlProvider' + */ + +const { getNamePrefixedWithSchemaName } = require('../../../utils/general'); +const { getName } = require('../jsonSchema/jsonSchemaHelper'); + +/** + * Hydrate global temporary table options. + * + * @param {HydrateGlobalTemporaryTableParams} params Table data. + * @returns {Partial} Global temporary table data. + */ +const hydrateGlobalTemporaryTableData = ({ tableData, detailsTab }) => { + if (detailsTab.tableKind !== 'globalTemporary') { + return {}; + } + + const likeTableJsonSchema = detailsTab.likeTable ? tableData.relatedSchemas?.[detailsTab.likeTable] : undefined; + const likeTableSchemaName = likeTableJsonSchema?.bucketName; + const likeTableName = getName({ item: likeTableJsonSchema }); + const likeTable = + likeTableName && + getNamePrefixedWithSchemaName({ + name: likeTableName, + schemaName: likeTableSchemaName, + }); + + return { + likeTable, + }; +}; + +module.exports = { + hydrateGlobalTemporaryTableData, +}; diff --git a/forward_engineering/ddlProvider/ddlHelpers/table/hydrateZosTableData.js b/forward_engineering/ddlProvider/ddlHelpers/table/hydrateZosTableData.js new file mode 100644 index 0000000..cbb05b3 --- /dev/null +++ b/forward_engineering/ddlProvider/ddlHelpers/table/hydrateZosTableData.js @@ -0,0 +1,81 @@ +/** + * @import { + * HydratedPartitioning, + * HydratedTemporalPeriod, + * HydratePartitioningParams, + * HydrateTemporalPeriodParams + * } from '../../../types/ddlProvider' + */ + +const { getIdToNameHashTable, resolveFieldListName } = require('../jsonSchema/jsonSchemaHelper'); + +/** + * Hydrate partitioning options. + * + * @param {HydratePartitioningParams} params Partitioning input. + * @returns {HydratedPartitioning | null} Partitioning data. + */ +const hydratePartitioning = ({ jsonSchema, partitioning }) => { + const partitioningConfig = Array.isArray(partitioning) ? partitioning[0] : partitioning; + + if (!partitioningConfig?.partitionBy) { + return null; + } + + const idToNameHashTable = getIdToNameHashTable({ jsonSchema }); + const partitionKey = (partitioningConfig.partitionKey ?? []) + .map(key => { + const name = key.keyId ? idToNameHashTable[key.keyId] : undefined; + if (!name) { + return null; + } + + return { + name, + type: key.type, + isActivated: key.isActivated ?? true, + }; + }) + .filter(key => key !== null); + + return { + partitionBy: partitioningConfig.partitionBy, + everySize: partitioningConfig.everySize, + nullsLast: partitioningConfig.nullsLast, + partitionKey, + partitions: partitioningConfig.partitions ?? [], + }; +}; + +/** + * Hydrate temporal period options. + * + * @param {HydrateTemporalPeriodParams} params Period input. + * @returns {HydratedTemporalPeriod | null} Period data. + */ +const hydrateTemporalPeriod = ({ jsonSchema, period }) => { + const periodConfig = Array.isArray(period) ? period[0] : period; + + if (!periodConfig) { + return null; + } + + const idToNameHashTable = getIdToNameHashTable({ jsonSchema }); + const startColumn = resolveFieldListName({ keyRef: periodConfig.startColumn, idToNameHashTable }); + const endColumn = resolveFieldListName({ keyRef: periodConfig.endColumn, idToNameHashTable }); + + if (!startColumn || !endColumn) { + return null; + } + + return { + startColumn, + endColumn, + endInclusive: periodConfig.endInclusive, + }; +}; + +module.exports = { + hydratePartitioning, + hydrateTemporalPeriod, +}; diff --git a/forward_engineering/ddlProvider/ddlHelpers/view/getViewData.js b/forward_engineering/ddlProvider/ddlHelpers/view/getViewData.js new file mode 100644 index 0000000..b331a82 --- /dev/null +++ b/forward_engineering/ddlProvider/ddlHelpers/view/getViewData.js @@ -0,0 +1,73 @@ +/** + * @import { + * HydratedViewColumn, + * ViewData + * } from '../../../types/ddlProvider' + */ + +const { wrapInQuotes, getNamePrefixedWithSchemaName } = require('../../../utils/general'); + +/** + * Build a key expression with optional alias. + * + * @param {{ key?: HydratedViewColumn }} params Key object. + * @returns {string} Key expression. + */ +const getKeyWithAlias = ({ key }) => { + if (!key) { + return ''; + } + + if (key.alias) { + return `${wrapInQuotes(key.name)} as ${wrapInQuotes(key.alias)}`; + } + + return wrapInQuotes(key.name); +}; + +/** + * @param {{ keys?: HydratedViewColumn[] }} params View keys. + * @returns {ViewData} View data. + */ +const getViewData = ({ keys }) => { + if (!Array.isArray(keys)) { + return { tables: [], columns: [] }; + } + + return keys.reduce( + (result, key) => { + if (!key.tableName) { + result.columns.push({ + statement: getKeyWithAlias({ key }), + isActivated: key.isActivated, + }); + + return result; + } + + const tableName = getNamePrefixedWithSchemaName({ + name: key.tableName, + schemaName: key.dbName, + }); + + if (!result.tables.includes(tableName)) { + result.tables.push(tableName); + } + + result.columns.push({ + statement: `${tableName}.${getKeyWithAlias({ key })}`, + isActivated: key.isActivated, + }); + + return result; + }, + /** @type {ViewData} */ ({ + tables: [], + columns: [], + }), + ); +}; + +module.exports = { + getViewData, +}; diff --git a/forward_engineering/ddlProvider/ddlProvider.js b/forward_engineering/ddlProvider/ddlProvider.js new file mode 100644 index 0000000..45455d0 --- /dev/null +++ b/forward_engineering/ddlProvider/ddlProvider.js @@ -0,0 +1,904 @@ +/** + * @import { + * CheckConstraintInput, + * ContainerData, + * CreateSchemaParams, + * CreateTableParams, + * DdlProvider, + * DropSchemaParams, + * ForeignKeyInput, + * ForeignKeyStatement, + * HydrateColumnParams, + * HydratedCheckConstraint, + * HydratedColumn, + * HydratedTable, + * HydratedView, + * HydratedViewColumn, + * HydrateTableParams, + * HydrateViewColumnParams, + * HydrateViewParams, + * IndexData, + * JsonSchemaColumn, + * SchemaData, + * TypeDescriptors, + * ViewSelectColumn + * } from '../types/ddlProvider' + */ + +const get = require('lodash/get'); +const isEmpty = require('lodash/isEmpty'); +const toUpper = require('lodash/toUpper'); +const trim = require('lodash/trim'); +const templates = require('./templates'); +const defaultTypes = require('../configs/defaultTypes.js'); +const descriptors = require('../configs/descriptors.js'); +const { + commentIfDeactivated: commentDeactivatedStatement, + wrapInQuotes, + getNamePrefixedWithSchemaName, + checkAllKeysDeactivated, + hasType, + setTab, +} = require('../utils/general.js'); +const { assignTemplates } = require('../utils/assignTemplates'); +const keyHelper = require('./ddlHelpers/key/keyHelper.js'); +const { getColumnType } = require('./ddlHelpers/columnDefinition/getColumnType.js'); +const { getColumnDefault } = require('./ddlHelpers/columnDefinition/getColumnDefault.js'); +const { getColumnConstraints } = require('./ddlHelpers/columnDefinition/getColumnConstraints.js'); +const { + getTableCommentStatement, + getColumnComments, + getSchemaCommentStatement, + getIndexCommentStatement, + getTypeCommentStatement, +} = require('./ddlHelpers/comment/commentHelper.js'); +const { getTableProps } = require('./ddlHelpers/table/getTableProps.js'); +const { getTableOptions } = require('./ddlHelpers/table/getTableOptions.js'); +const { getViewData } = require('./ddlHelpers/view/getViewData.js'); +const { getTableType } = require('./ddlHelpers/table/getTableType.js'); +const { hydrateAuxiliaryTableData } = require('./ddlHelpers/table/hydrateAuxiliaryTableData.js'); +const { hydrateGlobalTemporaryTableData } = require('./ddlHelpers/table/hydrateGlobalTemporaryTableData.js'); +const { hydratePartitioning, hydrateTemporalPeriod } = require('./ddlHelpers/table/hydrateZosTableData.js'); +const { joinActivatedAndDeactivatedStatements } = require('../utils/joinActivatedAndDeactivatedStatements'); +const { getIndexName } = require('./ddlHelpers/index/getIndexName.js'); +const { getIndexType } = require('./ddlHelpers/index/getIndexType.js'); +const { getIndexOptions } = require('./ddlHelpers/index/getIndexOptions.js'); + +/** + * Format view columns as a string. + * + * @param {{ columns: ViewSelectColumn[] }} params View columns. + * @returns {string} Columns string. + */ +const getViewColumnsAsString = ({ columns }) => { + const indent = '\n\t\t'; + const statements = columns.map(({ statement, isActivated }) => { + return commentDeactivatedStatement(statement, { isActivated, isPartOfLine: false }); + }); + + return indent + joinActivatedAndDeactivatedStatements({ statements, delimiter: ',', indent }); +}; + +/** + * Build WITH CHECK OPTION clause. + * + * @param {{ withCheckOption?: boolean; checkTestingScope?: string }} params Check option params. + * @returns {string} Check option clause. + */ +const getWithCheckOptionClause = ({ withCheckOption, checkTestingScope }) => { + if (!withCheckOption) { + return ''; + } + + const scope = checkTestingScope ?? 'CASCADED'; + return `\n\tWITH ${scope} CHECK OPTION`; +}; + +/** + * Resolve default type mapping. + * + * @param {string} type Type name. + * @returns {string | undefined} Default type. + */ +const getDefaultType = type => defaultTypes[type]; + +/** + * Get type descriptors. + * + * @returns {TypeDescriptors} Type descriptors. + */ +const getTypesDescriptors = () => descriptors; + +/** + * Check whether a type is supported. + * + * @param {string} type Type name. + * @returns {boolean} Whether type exists. + */ +const providerHasType = type => hasType({ descriptors, type }); + +/** + * Hydrate schema data. + * + * @param {ContainerData} containerData Container data. + * @returns {SchemaData} Hydrated schema. + */ +const hydrateSchema = containerData => ({ + schemaName: containerData.name, + isActivated: containerData.isActivated, + description: containerData.description, +}); + +/** + * Create schema DDL. + * + * @param {CreateSchemaParams} params Schema params. + * @returns {string} Schema DDL. + */ +const createSchema = ({ schemaName, description, isActivated = true }) => { + const wrappedSchemaName = wrapInQuotes(schemaName); + const schemaStatement = assignTemplates({ + template: templates.createSchema, + templateData: { + schemaName: wrappedSchemaName, + }, + }); + + const comment = getSchemaCommentStatement({ schemaName: wrappedSchemaName, description }); + const commentStatement = comment ? '\n' + comment + '\n' : '\n'; + + return commentDeactivatedStatement(schemaStatement + commentStatement, { isActivated }); +}; + +/** + * Drop schema DDL. + * + * @param {DropSchemaParams} params Schema params. + * @returns {string} Drop schema DDL. + */ +const dropSchema = ({ name, isActivated = true }) => { + const dropSchemaStatement = assignTemplates({ + template: templates.dropSchema, + templateData: { + schemaName: wrapInQuotes(name), + }, + }); + + return commentDeactivatedStatement(dropSchemaStatement, { isActivated }); +}; + +/** + * Alter schema DDL. + * + * @param {string} schemaName Schema name. + * @returns {string} Alter schema DDL. + */ +const alterSchema = schemaName => + assignTemplates({ + template: templates.alterSchema, + templateData: { + schemaName: wrapInQuotes(schemaName), + }, + }); + +/** + * Create distinct type DDL. + * + * @param {HydratedColumn} udt User-defined type data. + * @returns {string} Type DDL. + */ +const createUdt = ({ name, schemaName, comment, isActivated = true, ...sourceTypeParams }) => { + const wrappedName = getNamePrefixedWithSchemaName({ name, schemaName }); + const sourceType = getColumnType({ + ...sourceTypeParams, + name, + primaryKey: false, + unique: false, + isUDTRef: false, + }).trim(); + const typeStatement = assignTemplates({ + template: templates.createType, + templateData: { name: wrappedName, sourceType }, + }); + const commentStatement = getTypeCommentStatement({ typeName: wrappedName, description: comment }); + const commentDdl = commentStatement ? '\n' + commentStatement + '\n' : '\n'; + + return commentDeactivatedStatement(typeStatement + commentDdl, { isActivated }); +}; + +/** + * Hydrate column definition. + * + * @param {HydrateColumnParams} params Column input. + * @returns {HydratedColumn} Hydrated column. + */ +const hydrateColumn = ({ columnDefinition, jsonSchema, schemaData, definitionJsonSchema }) => { + const definitionSchema = definitionJsonSchema ?? {}; + const isUDTRef = !!jsonSchema.$ref; + const type = isUDTRef ? (columnDefinition.type ?? '') : toUpper(jsonSchema.mode ?? jsonSchema.type); + const itemsSchema = Array.isArray(jsonSchema.items) ? jsonSchema.items[0] : jsonSchema.items; + const itemsType = toUpper(itemsSchema?.mode ?? itemsSchema?.type ?? ''); + + return { + name: columnDefinition.name, + entityName: columnDefinition.entityName, + type, + ofType: jsonSchema.ofType, + notPersistable: jsonSchema.notPersistable, + size: jsonSchema.size, + primaryKey: keyHelper.isInlinePrimaryKey({ column: jsonSchema }), + primaryKeyOptions: jsonSchema.primaryKeyOptions, + unique: keyHelper.isInlineUnique({ column: jsonSchema }), + uniqueKeyOptions: jsonSchema.uniqueKeyOptions, + nullable: columnDefinition.nullable, + default: columnDefinition.default, + comment: jsonSchema.refDescription ?? jsonSchema.description ?? definitionSchema.description, + isActivated: columnDefinition.isActivated, + scale: columnDefinition.scale, + precision: columnDefinition.precision, + length: columnDefinition.length, + schemaName: schemaData.schemaName, + fractSecPrecision: jsonSchema.fractSecPrecision, + withTimeZone: jsonSchema.withTimeZone, + lengthSemantics: jsonSchema.lengthSemantics, + identity: jsonSchema.identity, + characterSubtype: jsonSchema.characterSubtype, + ccsid: jsonSchema.ccsid, + inlineLength: jsonSchema.inlineLength, + generatedColumn: jsonSchema.generatedColumn, + columnGenerationExpression: jsonSchema.columnGenerationExpression, + generated: jsonSchema.generated, + isUDTRef, + itemsType, + }; +}; + +/** + * Merge JSON schema column with definition schema. + * + * @param {JsonSchemaColumn} jsonSchema Column JSON schema. + * @param {JsonSchemaColumn} definitionJsonSchema Definition schema. + * @returns {JsonSchemaColumn} Merged schema. + */ +const hydrateJsonSchemaColumn = (jsonSchema, definitionJsonSchema) => { + if (!jsonSchema.$ref || isEmpty(definitionJsonSchema)) { + return jsonSchema; + } + const { $ref: _ref, ...jsonSchemaWithoutRef } = jsonSchema; + + return { ...definitionJsonSchema, ...jsonSchemaWithoutRef }; +}; + +/** + * Convert hydrated column to DDL. + * + * @param {HydratedColumn} columnDefinition Column definition. + * @param {string} [template] Column template. + * @returns {string} Column DDL. + */ +const convertColumnDefinition = (columnDefinition, template = templates.columnDefinition) => { + const statement = assignTemplates({ + template, + templateData: { + name: wrapInQuotes(columnDefinition.name), + type: getColumnType(columnDefinition), + default: getColumnDefault(columnDefinition), + constraints: getColumnConstraints(columnDefinition), + }, + }); + + return commentDeactivatedStatement(statement, { isActivated: columnDefinition.isActivated }); +}; + +/** + * Hydrate check constraint. + * + * @param {CheckConstraintInput} checkConstraint Check constraint data. + * @returns {HydratedCheckConstraint} Hydrated constraint. + */ +const hydrateCheckConstraint = checkConstraint => ({ + name: checkConstraint.chkConstrName, + expression: checkConstraint.constrExpression, + comments: checkConstraint.constrComments, + description: checkConstraint.constrDescription, + enforced: checkConstraint.constrEnforced, +}); + +/** + * Create check constraint DDL. + * + * @param {HydratedCheckConstraint} params Check constraint data. + * @returns {string} Check constraint fragment. + */ +const createCheckConstraint = ({ name, expression, enforced } = {}) => { + if (!expression) { + return ''; + } + + return assignTemplates({ + template: templates.checkConstraint, + templateData: { + name: name ? `CONSTRAINT ${wrapInQuotes(name)} ` : '', + expression: trim(expression).replace(/^\(([\s\S]*)\)$/u, '$1'), + enforced: enforced ? ` ${enforced}` : '', + }, + }); +}; + +/** + * Create foreign key constraint fragment. + * + * @param {ForeignKeyInput} constraint Constraint data. + * @param {unknown} _dbData Database data. + * @param {SchemaData} [schemaData] Schema data. + * @returns {ForeignKeyStatement} Constraint statement. + */ +const createForeignKeyConstraint = (constraint, _dbData, schemaData) => { + const { + name, + foreignKey, + primaryTable, + primaryKey, + primaryTableActivated, + foreignTableActivated, + primarySchemaName, + customProperties, + } = constraint; + const isAllPrimaryKeysDeactivated = checkAllKeysDeactivated({ keys: primaryKey }); + const isAllForeignKeysDeactivated = checkAllKeysDeactivated({ keys: foreignKey }); + const isActivated = Boolean( + !isAllPrimaryKeysDeactivated && !isAllForeignKeysDeactivated && primaryTableActivated && foreignTableActivated, + ); + + const onDelete = keyHelper.customPropertiesForForeignKey({ customProperties }); + const primaryTableName = getNamePrefixedWithSchemaName({ + name: primaryTable, + schemaName: primarySchemaName ?? schemaData?.schemaName, + }); + const constraintName = name ? `CONSTRAINT ${wrapInQuotes(name)}` : ''; + const foreignKeyName = + typeof foreignKey === 'string' + ? foreignKey + : isActivated + ? keyHelper.foreignKeysToString({ keys: foreignKey }) + : keyHelper.foreignActiveKeysToString({ keys: foreignKey }); + const primaryKeyName = + typeof primaryKey === 'string' + ? primaryKey + : isActivated + ? keyHelper.foreignKeysToString({ keys: primaryKey }) + : keyHelper.foreignActiveKeysToString({ keys: primaryKey }); + + const foreignKeyStatement = assignTemplates({ + template: templates.createForeignKeyConstraint, + templateData: { + primaryTable: primaryTableName, + name: constraintName, + foreignKey: foreignKeyName, + primaryKey: primaryKeyName, + onDelete, + }, + }); + + return { + statement: trim(foreignKeyStatement), + isActivated, + }; +}; + +/** + * Create standalone foreign key DDL. + * + * @param {ForeignKeyInput} constraint Constraint data. + * @param {unknown} _dbData Database data. + * @param {SchemaData} [schemaData] Schema data. + * @returns {ForeignKeyStatement} Foreign key statement. + */ +const createForeignKey = (constraint, _dbData, schemaData) => { + const { + name, + foreignTable, + foreignKey, + primaryTable, + primaryKey, + primaryTableActivated, + foreignTableActivated, + foreignSchemaName, + primarySchemaName, + customProperties, + } = constraint; + const isAllPrimaryKeysDeactivated = checkAllKeysDeactivated({ keys: primaryKey }); + const isAllForeignKeysDeactivated = checkAllKeysDeactivated({ keys: foreignKey }); + const isActivated = Boolean( + !isAllPrimaryKeysDeactivated && !isAllForeignKeysDeactivated && primaryTableActivated && foreignTableActivated, + ); + + const onDelete = keyHelper.customPropertiesForForeignKey({ customProperties }); + const primaryTableName = getNamePrefixedWithSchemaName({ + name: primaryTable, + schemaName: primarySchemaName ?? schemaData?.schemaName, + }); + const foreignTableName = getNamePrefixedWithSchemaName({ + name: foreignTable ?? '', + schemaName: foreignSchemaName ?? schemaData?.schemaName, + }); + const constraintName = name ? wrapInQuotes(name) : ''; + const foreignKeyName = + typeof foreignKey === 'string' + ? foreignKey + : isActivated + ? keyHelper.foreignKeysToString({ keys: foreignKey }) + : keyHelper.foreignActiveKeysToString({ keys: foreignKey }); + const primaryKeyName = + typeof primaryKey === 'string' + ? primaryKey + : isActivated + ? keyHelper.foreignKeysToString({ keys: primaryKey }) + : keyHelper.foreignActiveKeysToString({ keys: primaryKey }); + + const foreignKeyStatement = assignTemplates({ + template: templates.createForeignKey, + templateData: { + primaryTable: primaryTableName, + foreignTable: foreignTableName, + name: constraintName, + foreignKey: foreignKeyName, + primaryKey: primaryKeyName, + onDelete, + }, + }); + + return { + statement: trim(foreignKeyStatement) + '\n', + isActivated, + }; +}; + +/** + * Hydrate table data. + * + * @param {HydrateTableParams} params Table input. + * @returns {HydratedTable} Hydrated table. + */ +const hydrateTable = ({ tableData, entityData, jsonSchema }) => { + const detailsTab = entityData[0] ?? {}; + const auxiliaryTableData = hydrateAuxiliaryTableData({ tableData, detailsTab }); + const globalTemporaryTableData = hydrateGlobalTemporaryTableData({ tableData, detailsTab }); + const partitioning = hydratePartitioning({ jsonSchema, partitioning: detailsTab.partitioning }); + const periodForSystemTime = hydrateTemporalPeriod({ + jsonSchema, + period: detailsTab.periodForSystemTime, + }); + const periodForBusinessTime = hydrateTemporalPeriod({ + jsonSchema, + period: detailsTab.periodForBusinessTime, + }); + + return { + ...tableData, + ...auxiliaryTableData, + ...globalTemporaryTableData, + keyConstraints: keyHelper.getTableKeyConstraints({ jsonSchema, entityName: tableData.name }), + description: detailsTab.description, + tableProperties: detailsTab.tableProperties, + inClauseType: detailsTab.inClauseType, + databaseName: detailsTab.databaseName, + table_tablespace_name: detailsTab.table_tablespace_name, + acceleratorName: detailsTab.acceleratorName, + tableOptions: detailsTab.tableOptions, + partitioning: partitioning ?? undefined, + periodForSystemTime: periodForSystemTime ?? undefined, + periodForBusinessTime: periodForBusinessTime ?? undefined, + tableKind: detailsTab.tableKind, + gttCcsid: detailsTab.gttCcsid, + mqtQuery: detailsTab.mqtQuery, + mqtDataOption: detailsTab.mqtDataOption, + mqtRefresh: detailsTab.mqtRefresh, + mqtMaintainedBy: detailsTab.mqtMaintainedBy, + mqtQueryOptimization: detailsTab.mqtQueryOptimization, + }; +}; + +/** + * Create table DDL. + * + * @param {CreateTableParams} tableData Table data. + * @param {boolean} [isActivated] Activation flag. + * @returns {string} Table DDL. + */ +const createTable = (tableData, isActivated = true) => { + const { + columnDefinitions, + columns, + foreignKeyConstraints, + keyConstraints, + checkConstraints, + name, + schemaData, + auxiliary, + auxiliaryBaseTable, + auxiliaryBaseColumn, + auxiliaryAppend, + auxiliaryPart, + tableKind, + likeTable, + gttCcsid, + mqtQuery, + mqtDataOption, + mqtRefresh, + mqtMaintainedBy, + mqtQueryOptimization, + inClauseType, + databaseName, + table_tablespace_name, + acceleratorName, + tableOptions, + partitioning, + periodForSystemTime, + periodForBusinessTime, + description, + tableProperties, + } = tableData; + const tableType = getTableType({ auxiliary, tableKind }); + const tableName = getNamePrefixedWithSchemaName({ name, schemaName: schemaData.schemaName }); + const comment = getTableCommentStatement({ tableName, description }); + + if (auxiliary) { + const auxiliaryOptions = getTableOptions({ + auxiliary: true, + auxiliaryBaseTable, + auxiliaryBaseColumn, + auxiliaryAppend, + auxiliaryPart, + }); + const createTableStatement = assignTemplates({ + template: templates.createAuxiliaryTable, + templateData: { + name: tableName, + tableType, + tableOptions: auxiliaryOptions, + }, + }); + const commentStatement = comment ? '\n' + comment + '\n' : '\n'; + + return commentDeactivatedStatement(createTableStatement + commentStatement, { + isActivated, + }); + } + + if (tableKind === 'globalTemporary') { + const globalTemporaryTableProps = likeTable + ? ` LIKE ${likeTable}` + : getTableProps({ + columns: columns ?? [], + foreignKeyConstraints: [], + keyConstraints: [], + checkConstraints: [], + isActivated, + }); + const createTableStatement = assignTemplates({ + template: templates.createTable, + templateData: { + name: tableName, + tableProps: globalTemporaryTableProps, + tableType, + tableOptions: gttCcsid ? ` CCSID ${gttCcsid}` : '', + }, + }); + const commentStatement = comment ? '\n' + comment + '\n' : '\n'; + + return commentDeactivatedStatement(createTableStatement + commentStatement, { + isActivated, + }); + } + + const isMaterializedQuery = tableKind === 'materializedQuery'; + const tableProps = isMaterializedQuery + ? '' + : getTableProps({ + columns: columns ?? [], + foreignKeyConstraints: foreignKeyConstraints ?? [], + keyConstraints: keyConstraints ?? [], + checkConstraints: checkConstraints ?? [], + isActivated, + }); + const renderedTableOptions = getTableOptions({ + tableKind, + mqtQuery, + mqtDataOption, + mqtRefresh, + mqtMaintainedBy, + mqtQueryOptimization, + inClauseType, + databaseName, + table_tablespace_name, + acceleratorName, + tableOptions, + partitioning, + periodForSystemTime, + periodForBusinessTime, + tableProperties, + }); + + const columnComments = getColumnComments({ tableName, columnDefinitions }); + const commentStatements = comment || columnComments ? '\n' + comment + columnComments : '\n'; + + const createTableDdl = assignTemplates({ + template: templates.createTable, + templateData: { + name: tableName, + tableProps, + tableType, + tableOptions: renderedTableOptions, + }, + }); + + return commentDeactivatedStatement(createTableDdl + commentStatements, { + isActivated, + }); +}; + +/** + * Drop table DDL. + * + * @param {{ tableName: string }} params Table name. + * @returns {string} Drop table DDL. + */ +const dropTable = ({ tableName }) => assignTemplates({ template: templates.dropTable, templateData: { tableName } }); + +/** + * Add column DDL. + * + * @param {{ tableName: string; columnDefinition: string }} params Add column params. + * @returns {string} Add column DDL. + */ +const addColumn = ({ tableName, columnDefinition }) => + assignTemplates({ template: templates.addColumn, templateData: { tableName, columnDefinition } }); + +/** + * Drop column DDL. + * + * @param {{ tableName: string; columnName: string }} params Drop column params. + * @returns {string} Drop column DDL. + */ +const dropColumn = ({ tableName, columnName }) => + assignTemplates({ template: templates.dropColumn, templateData: { tableName, columnName } }); + +/** + * Drop view DDL. + * + * @param {{ viewName: string }} params View name. + * @returns {string} Drop view DDL. + */ +const dropView = ({ viewName }) => assignTemplates({ template: templates.dropView, templateData: { viewName } }); + +/** + * Hydrate index data. + * + * @param {IndexData} indexData Index data. + * @param {unknown} [tableData] Table data. + * @param {SchemaData} [schemaData] Schema data. + * @returns {IndexData} Hydrated index. + */ +const hydrateIndex = (indexData, tableData, schemaData) => { + const isParentActivated = get(tableData, '[0].isActivated', true); + + return { + ...indexData, + schemaName: schemaData?.schemaName, + isParentActivated, + }; +}; + +/** + * Create index DDL. + * + * @param {string} tableName Table name. + * @param {IndexData} index Index data. + * @returns {string} Index DDL. + */ +const createIndex = (tableName, index) => { + if (!index?.indxName || !index?.indxKey?.length) { + return ''; + } + + const indexName = getIndexName({ index }); + const indexType = getIndexType({ index }); + const indexOptions = getIndexOptions({ index }); + const indexTableName = getNamePrefixedWithSchemaName({ name: tableName, schemaName: index.schemaName }); + const statement = assignTemplates({ + template: templates.createIndex, + templateData: { indexType, indexName, indexOptions, indexTableName }, + }); + const commentStatement = getIndexCommentStatement({ + indexName: trim(indexName), + description: index.indxDescription, + }); + + let finalStatement = commentDeactivatedStatement(statement, { + isActivated: Boolean(index.isActivated && index.isParentActivated), + }); + + if (commentStatement) { + finalStatement += + '\n' + + commentDeactivatedStatement(commentStatement, { + isPartOfLine: true, + isActivated: Boolean(index.isActivated && index.isParentActivated), + }) + + '\n'; + } + + return finalStatement; +}; + +/** + * Drop index DDL. + * + * @param {string} name Index name. + * @returns {string} Drop index DDL. + */ +const dropIndex = name => { + if (!name) { + return ''; + } + + return assignTemplates({ template: templates.dropIndex, templateData: { name } }); +}; + +/** + * Hydrate view column. + * + * @param {HydrateViewColumnParams} data View column data. + * @returns {HydratedViewColumn} Hydrated view column. + */ +const hydrateViewColumn = data => ({ + name: data.name, + tableName: data.entityName, + alias: data.alias, + isActivated: data.isActivated, + dbName: data.dbName, +}); + +/** + * Hydrate view data. + * + * @param {HydrateViewParams} params View input. + * @returns {HydratedView} Hydrated view. + */ +const hydrateView = ({ viewData, entityData }) => { + const detailsTab = entityData[0] ?? {}; + + return { + name: viewData.name, + keys: viewData.keys, + selectStatement: detailsTab.selectStatement, + tableName: viewData.tableName, + schemaName: viewData.schemaData?.schemaName ?? viewData.schemaName, + description: detailsTab.description, + viewProperties: detailsTab.viewProperties, + withCheckOption: detailsTab.withCheckOption, + checkTestingScope: detailsTab.checkTestingScope, + }; +}; + +/** + * Create view DDL. + * + * @param {HydratedView} viewData View data. + * @param {unknown} _dbData Database data. + * @param {boolean} [isActivated] Activation flag. + * @returns {string} View DDL. + */ +const createView = (viewData, _dbData, isActivated = true) => { + const viewName = getNamePrefixedWithSchemaName({ name: viewData.name, schemaName: viewData.schemaName }); + + const { columns, tables } = getViewData({ keys: viewData.keys }); + const columnsAsString = getViewColumnsAsString({ columns }); + const commentStatement = getTableCommentStatement({ + tableName: viewName, + description: viewData.description, + }); + const comment = commentStatement ? '\n' + commentStatement + `\n` : '\n'; + const viewProperties = viewData.viewProperties ? ' \n' + setTab({ text: viewData.viewProperties }) : ''; + const withCheckOption = getWithCheckOptionClause({ + withCheckOption: viewData.withCheckOption, + checkTestingScope: viewData.checkTestingScope, + }); + const viewColumns = columns.length > 0 ? ` (${columnsAsString}\n\t)` : ''; + + const rawSelectStatement = viewData.selectStatement ?? ''; + const selectStatement = trim(rawSelectStatement) + ? trim(setTab({ text: rawSelectStatement })) + : assignTemplates({ + template: templates.viewSelectStatement, + templateData: { + tableName: tables.join(', '), + keys: columnsAsString, + }, + }); + + const statement = assignTemplates({ + template: templates.createView, + templateData: { + name: viewName, + viewColumns, + viewProperties, + withCheckOption, + selectStatement, + }, + }); + + return commentDeactivatedStatement(statement + comment, { isActivated }); +}; + +/** + * Comment a statement when deactivated. + * + * @param {string} statement Statement text. + * @param {{ isActivated?: boolean; isPartOfLine?: boolean }} [data] Comment options. + * @param {boolean} [isPartOfLine] Inline flag. + * @returns {string} Possibly commented statement. + */ +const commentIfDeactivated = (statement, data = {}, isPartOfLine) => + commentDeactivatedStatement(statement, { + isActivated: data.isActivated ?? true, + isPartOfLine: data.isPartOfLine ?? isPartOfLine, + }); + +/** + * Force-comment a statement. + * + * @param {string} statement Statement text. + * @returns {string} Commented statement. + */ +const commentStatement = statement => commentDeactivatedStatement(statement, { isActivated: false }); + +/** + * Prepare a quoted identifier. + * + * @param {string} name Identifier. + * @returns {string} Quoted name. + */ +const prepareName = name => wrapInQuotes(name); + +/** + * Create Db2 z/OS DDL provider. + * + * @param {unknown} _baseProvider Base provider. + * @param {unknown} _options Provider options. + * @param {unknown} _app App instance. + * @returns {DdlProvider} DDL provider. + */ +module.exports = (_baseProvider, _options, _app) => ({ + getDefaultType, + getTypesDescriptors, + hasType: providerHasType, + hydrateSchema, + createSchema, + dropSchema, + alterSchema, + createUdt, + hydrateColumn, + hydrateJsonSchemaColumn, + convertColumnDefinition, + hydrateCheckConstraint, + createCheckConstraint, + createForeignKeyConstraint, + createForeignKey, + hydrateTable, + createTable, + dropTable, + addColumn, + dropColumn, + dropView, + hydrateIndex, + createIndex, + dropIndex, + hydrateViewColumn, + hydrateView, + createView, + commentIfDeactivated, + commentStatement, + prepareName, +}); diff --git a/forward_engineering/ddlProvider/templates.js b/forward_engineering/ddlProvider/templates.js new file mode 100644 index 0000000..e92a4f3 --- /dev/null +++ b/forward_engineering/ddlProvider/templates.js @@ -0,0 +1,81 @@ +module.exports = { + createSchema: 'CREATE SCHEMA ${schemaName};', + + dropSchema: 'DROP SCHEMA ${schemaName} RESTRICT;', + + alterSchema: 'ALTER SCHEMA ${schemaName};', + + createType: 'CREATE TYPE ${name} AS ${sourceType};', + + dropType: 'DROP TYPE ${name};', + + createTable: 'CREATE${tableType} TABLE ${name}${tableProps}${tableOptions};', + + dropTable: 'DROP TABLE ${tableName};', + + addColumn: 'ALTER TABLE ${tableName} ADD COLUMN ${columnDefinition};', + + dropColumn: 'ALTER TABLE ${tableName} DROP COLUMN ${columnName};', + + createAuxiliaryTable: 'CREATE${tableType} TABLE ${name}${tableOptions};', + + addVersioning: 'ALTER TABLE ${tableName} ADD VERSIONING USE HISTORY TABLE ${historyTableName};', + + enableArchive: 'ALTER TABLE ${tableName} ENABLE ARCHIVE USE ARCHIVE TABLE ${archiveTableName};', + + comment: '\nCOMMENT ON ${objectType} ${objectName} IS ${comment};\n', + + createTableProps: '${columns}${keyConstraints}${checkConstraints}${foreignKeyConstraints}', + + columnDefinition: '${name}${type}${default}${constraints}', + + createForeignKey: + 'ALTER TABLE ${foreignTable} ADD CONSTRAINT ${name} FOREIGN KEY (${foreignKey}) REFERENCES ${primaryTable} (${primaryKey})${onDelete};', + + dropForeignKey: 'ALTER TABLE ${tableName} DROP FOREIGN KEY ${constraintName};', + + createForeignKeyConstraint: + '${name} FOREIGN KEY (${foreignKey}) REFERENCES ${primaryTable} (${primaryKey})${onDelete}', + + checkConstraint: '${name}CHECK (${expression})${enforced}', + + createKeyConstraint: '${constraintName}${keyType}${columns}${options}', + + createView: 'CREATE VIEW ${name}${viewColumns}${viewProperties}${withCheckOption}\n\tAS ${selectStatement};', + + viewSelectStatement: 'SELECT ${keys}\n\tFROM ${tableName}', + + createIndex: 'CREATE${indexType} INDEX${indexName} ON ${indexTableName}${indexOptions};\n', + + dropView: 'DROP VIEW ${viewName};', + + alterPkConstraint: 'ALTER TABLE ${tableName} ADD CONSTRAINT ${constraintName} PRIMARY KEY${columns}${options};', + + dropPK: 'ALTER TABLE ${tableName} DROP PRIMARY KEY;', + + alterNotNull: 'ALTER TABLE ${tableName} ALTER COLUMN ${columnName} SET NOT NULL;', + + dropNotNull: 'ALTER TABLE ${tableName} ALTER COLUMN ${columnName} DROP NOT NULL;', + + alterUkConstraint: 'ALTER TABLE ${tableName} ADD CONSTRAINT ${constraintName} UNIQUE${columns}${options};', + + dropUkConstraint: 'ALTER TABLE ${tableName} DROP UNIQUE ${constraintName};', + + alterCheckConstraint: 'ALTER TABLE ${tableName} ADD CONSTRAINT ${constraintName} CHECK (${expression})${enforced};', + + dropCheckConstraint: 'ALTER TABLE ${tableName} DROP CHECK ${constraintName};', + + updateColumnType: 'ALTER TABLE ${tableName} ALTER COLUMN ${columnName} SET DATA TYPE ${dataType};', + + updateColumnDefaultValue: 'ALTER TABLE ${tableName} ALTER COLUMN ${columnName} SET DEFAULT ${defaultValue};', + + dropColumnDefaultValue: 'ALTER TABLE ${tableName} ALTER COLUMN ${columnName} DROP DEFAULT;', + + renameColumn: 'ALTER TABLE ${tableName} RENAME COLUMN ${oldColumnName} TO ${newColumnName};', + + renameTable: 'RENAME TABLE ${oldTableName} TO ${newTableName};', + + renameIndex: 'RENAME INDEX ${oldIndexName} TO ${newIndexName};', + + dropIndex: 'DROP INDEX ${name};', +}; diff --git a/forward_engineering/types/alterScript.d.ts b/forward_engineering/types/alterScript.d.ts new file mode 100644 index 0000000..1baf19e --- /dev/null +++ b/forward_engineering/types/alterScript.d.ts @@ -0,0 +1,309 @@ +/** DTOs describing the Hackolade delta model and the alter-script pipeline for Db2 for z/OS. */ + +import { + AlterKeyConfig, + AlterKeyStatement, + CheckConstraintInput, + CompMod, + CompositeKeyGroup, + EntityDetailsTab, + ForeignKeyInput, + HydratedViewColumn, + IndexData, + IndexKeyRef, + JsonSchemaColumn, + KeyConstraintColumn, + PropertyPair, +} from './ddlProvider'; + +export type ModificationScript = { + script: string; + isDropScript: boolean; +}; + +export type AlterScriptDto = { + isActivated?: boolean; + scripts: ModificationScript[]; +}; + +/** + * Result of checking whether a key moved between its regular (single column) and composite representation. When a + * transition happened, the key only has to be recreated if its options changed along the way. + */ +export type KeyTransition = { + didTransitionHappen: boolean; + wasKeyChangedInTransition?: boolean; +}; + +export type KeyScriptModification = { + script: string; + fullTableName: string; + isDropScript: boolean; + isActivated: boolean; +}; + +export type FieldSnapshot = Record & { + name?: string; +}; + +export type AlterColumnCompMod = { + oldField: FieldSnapshot; + newField: FieldSnapshot; +}; + +export type AlterColumn = JsonSchemaColumn & { + compMod?: AlterColumnCompMod; + default?: string | number | boolean | null; + length?: number; + maxLength?: number; + precision?: number; + scale?: number; + hasMaxLength?: boolean; + childType?: string; + expression?: string; + refId?: string; +}; + +export type AlterIndexKey = IndexKeyRef & { + keyId?: string; +}; + +export type AlterIndex = Omit & { + id?: string; + indxKey?: AlterIndexKey[]; + indxIncludeKey?: AlterIndexKey[]; +}; + +export type AlterCollectionCompMod = CompMod & { + created?: boolean; + deleted?: boolean; + modified?: boolean; + name?: PropertyPair; + code?: PropertyPair; + description?: PropertyPair; + selectStatement?: PropertyPair; + primaryKey?: PropertyPair; + uniqueKey?: PropertyPair; + chkConstr?: PropertyPair; + Indxs?: PropertyPair; + oldProperties?: Array; + collectionData?: { + collectionRefsDefinitionsMap?: Record; + }; +}; + +export type AlterCollectionRole = { + id?: string; + name?: string; + code?: string; + collectionName?: string; + description?: string; + isActivated?: boolean; + required?: string[]; + properties?: Record; + chkConstr?: CheckConstraintInput[]; + Indxs?: AlterIndex[]; + compMod?: AlterCollectionCompMod; +}; + +export type AlterCollection = { + role?: AlterCollectionRole; + compMod?: AlterCollectionCompMod; + properties?: Record; + required?: string[]; + isActivated?: boolean; + id?: string; + name?: string; + code?: string; + collectionName?: string; + description?: string; + chkConstr?: CheckConstraintInput[]; + Indxs?: AlterIndex[]; + /** Schema name resolved for a `relatedSchemas` entry; not part of the studio's own delta payload. */ + bucketName?: string; +}; + +export type AlterContainerRole = { + name: string; + description?: string; + isActivated?: boolean; + compMod?: { + description?: PropertyPair; + }; +}; + +export type AlterContainer = { + role: AlterContainerRole; + isActivated?: boolean; +}; + +export type AlterModelDefinitionCompMod = CompMod & + Partial & { + created?: boolean; + deleted?: boolean; + modified?: boolean; + name?: PropertyPair; + description?: PropertyPair; + mode?: PropertyPair; + type?: PropertyPair; + length?: PropertyPair; + lengthSemantics?: PropertyPair; + precision?: PropertyPair; + scale?: PropertyPair; + fractSecPrecision?: PropertyPair; + withTimeZone?: PropertyPair; + characterSubtype?: PropertyPair; + ccsid?: PropertyPair; + inlineLength?: PropertyPair; + }; + +export type AlterModelDefinitionRole = { + name: string; + description?: string; + isActivated?: boolean; + compMod?: AlterModelDefinitionCompMod; +}; + +export type AlterModelDefinition = JsonSchemaColumn & { + name?: string; + length?: number; + lengthSemantics?: string; + precision?: number; + scale?: number; + description?: string; + isActivated?: boolean; + compMod?: AlterModelDefinitionCompMod; + role: AlterModelDefinitionRole; +}; + +export type ViewDefinitionRef = { + name?: string; + definition?: JsonSchemaColumn; + collection?: Array<{ code?: string; collectionName?: string }>; + bucket?: Array<{ code?: string; name?: string }>; +}; + +export type AlterView = AlterCollection & EntityDetailsTab; + +export type AlterTable = AlterCollection & EntityDetailsTab; + +/** + * `mapProperties` from `@hackolade/ddl-fe-utils`: iterates the properties of a view schema and collects the mapped + * column definitions. + */ +export type MapPropertiesFn = ( + viewSchema: object, + callback: (propertyName: string, propertySchema: AlterColumn) => HydratedViewColumn, +) => HydratedViewColumn[]; + +export type RelationshipEndpoint = { + bucket?: { name?: string }; + collection?: { + name?: string; + fkFields?: KeyConstraintColumn[]; + isActivated?: boolean; + }; +}; + +export type AlterRelationshipCompMod = { + created?: boolean; + deleted?: boolean; + modified?: boolean; + name?: PropertyPair; + code?: PropertyPair; + isActivated?: PropertyPair; + customProperties?: PropertyPair; + parent?: RelationshipEndpoint; + child?: RelationshipEndpoint; +}; + +export type AlterRelationshipRole = { + id?: string; + name?: string; + code?: string; + childCollection?: string; + compMod?: AlterRelationshipCompMod; +}; + +export type AlterRelationship = { + role: AlterRelationshipRole; +}; + +/** + * The Db2 for z/OS specifics of a key kind (primary or unique), so that the composite/regular key diffing can be + * expressed once and reused for both. + */ +export type AlterKeyKind = { + keyType: string; + constraintPostfix: string; + compModProperty: 'primaryKey' | 'uniqueKey'; + compositeKeyProperty: 'compositePrimaryKey' | 'compositeUniqueKey'; + inlineKeyProperty: 'primaryKey' | 'unique'; + keyOptionsProperty: 'primaryKeyOptions' | 'uniqueKeyOptions'; + buildAlterStatement: (params: { + tableName: string; + isParentActivated: boolean; + keyConfig: AlterKeyConfig; + }) => AlterKeyStatement; + buildDropStatement: (params: { tableName: string; constraintName: string }) => string; +}; + +/** The only key options Db2 for z/OS renders, and therefore the only ones worth diffing. */ +export type ComparableKeyOptions = { + id?: string; + constraintName?: string; +}; + +export type CheckConstraintHistoryEntry = { + old?: CheckConstraintInput; + new?: CheckConstraintInput; +}; + +export type DeltaItemWrapper = { + properties: Record; +}; + +export type DeltaBucket = { + items?: DeltaItemWrapper | Array>; +}; + +export type DeltaSection = { + properties?: { + added?: DeltaBucket; + deleted?: DeltaBucket; + modified?: DeltaBucket; + }; +}; + +export type DeltaModel = { + properties?: { + containers?: DeltaSection; + entities?: DeltaSection; + views?: DeltaSection; + relationships?: DeltaSection; + modelDefinitions?: DeltaSection; + }; +}; + +export type AlterScriptGenerationOptions = { + additionalOptions?: Array<{ id?: string; value?: unknown }>; + scriptGenerationOptions?: { + feActiveOptions?: { + foreignKeys?: string; + }; + }; +}; + +export type AlterScriptData = { + jsonSchema: string; + collections?: string[]; + internalDefinitions?: Record; + options?: AlterScriptGenerationOptions; + level?: string; +}; + +export type PluginLogger = { + log: (type: string, data: object, title?: string) => void; +}; + +export type PluginCallback = (error: object | null, result?: unknown) => void; diff --git a/forward_engineering/types/ddlProvider.d.ts b/forward_engineering/types/ddlProvider.d.ts new file mode 100644 index 0000000..98741a8 --- /dev/null +++ b/forward_engineering/types/ddlProvider.d.ts @@ -0,0 +1,765 @@ +/** Shared DTOs and ddlProvider method signatures for Db2 for z/OS forward engineering. */ + +export type AppModule = unknown; + +export interface App { + require: (libName: string) => AppModule; + utils: object; +} + +export type BaseProvider = object; + +export type DdlProviderOptions = { + isUpdateScript?: boolean; + additionalOptions?: unknown; + origin?: string; + fakerLocalization?: string; + showIndexStatementsInEndDdl?: boolean; + targetScriptOptions?: { keyword?: string }; +}; + +export type KeyRef = { + keyId?: string; + type?: string; + name?: string; + isActivated?: boolean; +}; + +export type FieldListRef = KeyRef[]; + +export type IdentityOptions = { + generated?: string; + start?: number; + increment?: number; + cycle?: string; + minValue?: number; + maxValue?: number; + cache?: string; + cacheValue?: number; + order?: string; +}; + +export type ColumnDefinitionInput = { + name: string; + entityName?: string; + type?: string; + nullable?: boolean; + default?: DefaultValue; + isActivated?: boolean; + scale?: number; + precision?: number; + length?: number; +}; + +export type KeyOptions = { + id?: string; + constraintName?: string; +}; + +export type JsonSchemaColumn = { + $ref?: string; + mode?: string; + type?: string; + description?: string; + refDescription?: string; + primaryKey?: boolean; + unique?: boolean; + compositePrimaryKey?: boolean; + compositeUniqueKey?: boolean; + primaryKeyOptions?: KeyOptions; + uniqueKeyOptions?: KeyOptions; + fractSecPrecision?: number; + withTimeZone?: boolean; + lengthSemantics?: string; + identity?: IdentityOptions; + characterSubtype?: string; + ccsid?: number; + inlineLength?: number; + generatedColumn?: boolean; + columnGenerationExpression?: string; + generated?: string; + items?: JsonSchemaColumn | JsonSchemaColumn[]; + ofType?: string; + notPersistable?: boolean; + size?: string | number; + checkConstraints?: unknown; + GUID?: string; + isActivated?: boolean; + code?: string; + name?: string; + collectionName?: string; + bucketName?: string; + properties?: Record; +}; + +export type CompositeKeyGroup = { + constraintName?: string; + compositePrimaryKey?: KeyRef[]; + compositeUniqueKey?: KeyRef[]; + indexComment?: string; + alternateKey?: boolean; +} & KeyOptions; + +export type JsonSchema = JsonSchemaColumn & { + properties?: Record; + primaryKey?: CompositeKeyGroup[]; + uniqueKey?: CompositeKeyGroup[]; +}; + +export type HydratedColumn = { + name: string; + entityName?: string; + type: string; + ofType?: string; + notPersistable?: boolean; + size?: string | number; + primaryKey: boolean; + primaryKeyOptions?: KeyOptions; + unique: boolean; + uniqueKeyOptions?: KeyOptions; + nullable?: boolean; + default?: DefaultValue; + comment?: string; + isActivated?: boolean; + scale?: number; + precision?: number; + length?: number; + schemaName?: string; + fractSecPrecision?: number; + withTimeZone?: boolean; + lengthSemantics?: string; + identity?: IdentityOptions; + characterSubtype?: string; + ccsid?: number; + inlineLength?: number; + generatedColumn?: boolean; + columnGenerationExpression?: string; + generated?: string; + isUDTRef?: boolean; + itemsType?: string; +}; + +export type SchemaData = { + schemaName: string; + isActivated?: boolean; + description?: string; +}; + +export type ContainerData = { + name: string; + isActivated?: boolean; + description?: string; +}; + +export type CreateSchemaParams = { + schemaName: string; + description?: string; + isActivated?: boolean; +}; + +export type DropSchemaParams = { + name: string; + isActivated?: boolean; +}; + +export type TableOptionsBlock = { + editProc?: string; + editProcRowAttributes?: string; + validProc?: string; + audit?: string; + obid?: number; + dataCapture?: string; + withRestrictOnDrop?: boolean; + ccsid?: string; + volatile?: string; + logged?: string; + compress?: string; + append?: string; + dssize?: number; + bufferPool?: string; + memberCluster?: boolean; + trackMod?: string; + pageNum?: string; + keyLabelMode?: string; + keyLabelName?: string; +}; + +export type PartitionEntry = { + partitionNumber?: number; + endingAt?: string; + inclusive?: boolean; +}; + +export type PartitioningConfig = { + partitionBy?: string; + everySize?: number; + partitionKey?: KeyRef[]; + nullsLast?: boolean; + partitions?: PartitionEntry[]; +}; + +export type PeriodConfig = { + startColumn?: FieldListRef; + endColumn?: FieldListRef; + endInclusive?: string; + historyTable?: string; +}; + +export type HydratedTemporalPeriod = { + startColumn?: string; + endColumn?: string; + endInclusive?: string; +}; + +export type HydratedPartitionKey = { + name: string; + type?: string; + isActivated?: boolean; +}; + +export type HydratedPartitioning = { + partitionBy?: string; + everySize?: number; + partitionKey?: HydratedPartitionKey[]; + nullsLast?: boolean; + partitions?: PartitionEntry[]; +}; + +export type EntityDetailsTab = { + description?: string; + tableProperties?: string; + inClauseType?: string; + databaseName?: string; + table_tablespace_name?: string; + acceleratorName?: string; + tableOptions?: TableOptionsBlock; + partitioning?: PartitioningConfig | PartitioningConfig[]; + periodForSystemTime?: PeriodConfig | PeriodConfig[]; + periodForBusinessTime?: PeriodConfig | PeriodConfig[]; + auxiliaryBaseTable?: string; + auxiliaryBaseColumn?: FieldListRef; + auxiliaryAppend?: string; + auxiliaryPart?: number; + tableKind?: string; + likeTable?: string; + gttCcsid?: string; + mqtQuery?: string; + mqtDataOption?: string; + mqtRefresh?: string; + mqtMaintainedBy?: string; + mqtQueryOptimization?: string; + archiveEnabled?: boolean; + archiveTable?: string; + selectStatement?: string; + withCheckOption?: boolean; + checkTestingScope?: string; + viewProperties?: string; +}; + +export type KeyConstraintColumn = { + name?: string; + isActivated?: boolean; + type?: string; +}; + +export type KeyConstraint = { + keyType: string; + constraintName?: string; + columns: KeyConstraintColumn[]; +}; + +export type AlterKeyConfig = { + keyType: string; + name: string; + columns: KeyConstraintColumn[]; + options?: KeyOptions; +}; + +export type AlterKeyStatement = { + statement: string; + isActivated: boolean; +}; + +export type ForeignKeyStatement = { + statement: string; + isActivated: boolean; +}; + +export type ForeignKeyInput = { + name?: string; + foreignKey: KeyConstraintColumn[] | string; + primaryTable: string; + primaryKey: KeyConstraintColumn[] | string; + primaryTableActivated?: boolean; + foreignTableActivated?: boolean; + primarySchemaName?: string; + foreignSchemaName?: string; + foreignTable?: string; + customProperties?: { + relationshipOnDelete?: string; + relationshipEnforced?: string; + }; +}; + +export type HydratedTable = { + name: string; + schemaData: SchemaData; + relatedSchemas?: Record; + keyConstraints?: KeyConstraint[]; + checkConstraints?: string[]; + description?: string; + tableProperties?: string; + auxiliary?: boolean; + auxiliaryAppend?: string; + auxiliaryPart?: number; + auxiliaryBaseTable?: string; + auxiliaryBaseColumn?: string; + tableKind?: string; + likeTable?: string; + gttCcsid?: string; + mqtQuery?: string; + mqtDataOption?: string; + mqtRefresh?: string; + mqtMaintainedBy?: string; + mqtQueryOptimization?: string; + inClauseType?: string; + databaseName?: string; + table_tablespace_name?: string; + acceleratorName?: string; + tableOptions?: TableOptionsBlock; + partitioning?: HydratedPartitioning; + periodForSystemTime?: HydratedTemporalPeriod; + periodForBusinessTime?: HydratedTemporalPeriod; + columnDefinitions?: HydratedColumn[]; + columns?: string[]; + foreignKeyConstraints?: ForeignKeyStatement[]; +}; + +export type CreateTableParams = { + columnDefinitions?: HydratedColumn[]; + columns?: string[]; + foreignKeyConstraints?: ForeignKeyStatement[]; + keyConstraints?: KeyConstraint[]; + checkConstraints?: string[]; + name: string; + schemaData: SchemaData; + description?: string; + tableProperties?: string; + auxiliary?: boolean; + auxiliaryAppend?: string; + auxiliaryPart?: number; + auxiliaryBaseTable?: string; + auxiliaryBaseColumn?: string; + tableKind?: string; + likeTable?: string; + gttCcsid?: string; + mqtQuery?: string; + mqtDataOption?: string; + mqtRefresh?: string; + mqtMaintainedBy?: string; + mqtQueryOptimization?: string; + inClauseType?: string; + databaseName?: string; + table_tablespace_name?: string; + acceleratorName?: string; + tableOptions?: TableOptionsBlock; + partitioning?: HydratedPartitioning; + periodForSystemTime?: HydratedTemporalPeriod; + periodForBusinessTime?: HydratedTemporalPeriod; +}; + +export type HydratedViewColumn = { + name: string; + tableName?: string; + alias?: string; + isActivated?: boolean; + dbName?: string; +}; + +export type HydratedView = { + name: string; + keys?: HydratedViewColumn[]; + selectStatement?: string; + tableName?: string; + schemaName?: string; + schemaData?: SchemaData; + description?: string; + viewProperties?: string; + withCheckOption?: boolean; + checkTestingScope?: string; +}; + +export type CheckConstraintInput = { + chkConstrName?: string; + constrExpression?: string; + constrComments?: string; + constrDescription?: string; + constrEnforced?: string; +}; + +export type HydratedCheckConstraint = { + name?: string; + expression?: string; + comments?: string; + description?: string; + enforced?: string; +}; + +export type IndexKeyRef = { + name?: string; + type?: string; + isActivated?: boolean; +}; + +export type IndexData = { + indxName?: string; + indxType?: string; + indxKey?: IndexKeyRef[]; + indxIncludeKey?: IndexKeyRef[]; + indxCompress?: string; + indxNullKeys?: string; + indxCluster?: string; + indxPartitioned?: boolean; + indxPadded?: string; + indxUsingType?: string; + indxStogroup?: string; + indxVcat?: string; + indxPriQty?: number; + indxSecQty?: number; + indxErase?: string; + indxFreepage?: number; + indxPctfree?: number; + indxDefine?: string; + indxBufferPool?: string; + indxClose?: string; + indxDefer?: string; + indxCopy?: string; + indxPiecesize?: number; + indxPiecesizeUnit?: string; + indxProperties?: string; + indxDescription?: string; + indxComments?: string; + isActivated?: boolean; + schemaName?: string; + isParentActivated?: boolean; +}; + +export type ViewSelectColumn = { + statement: string; + isActivated?: boolean; +}; + +export type TypeDescriptors = Record>; + +export type DefaultTypesMap = Record; + +export type TemplateData = Record; + +export type OptionConfig = { + key: string; + getValue: (value: any, data: object) => string; +}; + +export type ActivatedKey = { + name?: string; + isActivated?: boolean; + statement?: string; + type?: string; +}; + +export type DefaultValue = string | number | boolean; + +export type CompMod = { + collectionName?: PropertyPair; + keyspaceName?: string; + isActivated?: PropertyPair; + bucketProperties?: { isActivated?: boolean }; +}; + +export type ModelObject = { + code?: string; + collectionName?: string; + name?: string; + compMod?: CompMod; + role?: { properties?: unknown; isActivated?: boolean; compMod?: CompMod }; +}; + +export type JsonSchemaPropertyCallback = (params: { + propertyName: string; + property: WalkableSchema; + path: string[]; +}) => void; + +export type DividedConstraints = { + activatedItems: string[]; + deactivatedItems: string[]; +}; + +export type ColumnConstraintParams = { + nullable?: boolean; + unique: boolean; + primaryKey: boolean; + primaryKeyOptions?: KeyOptions; + uniqueKeyOptions?: KeyOptions; + entityName?: string; +}; + +export type ColumnDefaultParams = { + default?: DefaultValue; + identity?: IdentityOptions; + type: string; + generated?: string; + generatedColumn?: boolean; + columnGenerationExpression?: string; +}; + +export type HydratePartitioningParams = { + jsonSchema: JsonSchema; + partitioning?: PartitioningConfig | PartitioningConfig[]; +}; + +export type HydrateTemporalPeriodParams = { + jsonSchema: JsonSchema; + period?: PeriodConfig | PeriodConfig[]; +}; + +export type HydrateAuxiliaryTableParams = { + tableData: HydratedTable; + detailsTab: EntityDetailsTab; +}; + +export type HydrateGlobalTemporaryTableParams = { + tableData: HydratedTable; + detailsTab: EntityDetailsTab; +}; + +export type ConstraintOptionsResult = { + constraintString: string; + statement: string; +}; + +export type OptionsByConfigsParams = { + configs: OptionConfig[]; + data: object; +}; + +export type DelimiterParams = { + index: number; + numberOfStatements: number; + lastIndexOfActivatedStatement: number; + delimiter: string; +}; + +export type JoinStatementsParams = { + statements: string[]; + delimiter?: string; + indent?: string; +}; + +export type TablePropsParams = { + columns: string[]; + foreignKeyConstraints: ForeignKeyStatement[]; + keyConstraints: KeyConstraint[]; + checkConstraints?: string[]; + isActivated: boolean; +}; + +export type TemporalPeriodsParams = { + periodForSystemTime?: HydratedTemporalPeriod; + periodForBusinessTime?: HydratedTemporalPeriod; +}; + +export type ViewData = { + tables: string[]; + columns: ViewSelectColumn[]; +}; + +export type HydrateColumnParams = { + columnDefinition: ColumnDefinitionInput; + jsonSchema: JsonSchemaColumn; + schemaData: SchemaData; + definitionJsonSchema?: JsonSchemaColumn; +}; + +export type HydrateTableParams = { + tableData: HydratedTable; + entityData: EntityDetailsTab[]; + jsonSchema: JsonSchema; +}; + +export type InClauseParams = { + inClauseType?: string; + databaseName?: string; + table_tablespace_name?: string; + acceleratorName?: string; +}; + +export type BasicValueParams = { + prefix?: string; + postfix?: string; + modifier?: (value: T) => T; +}; + +export type DivideItemsParams = { + items: T[]; + mapFunction: (item: T) => K; +}; + +export type DividedItems = { + activatedItems: K[]; + deactivatedItems: K[]; +}; + +export type CommentDeactivatedOptions = { + isActivated?: boolean; + isPartOfLine?: boolean; + inlineComment?: string; +}; + +export type FieldComparisonParams = { + oldField: Record; + newField: Record; +}; + +export type PropertyChanges = Record; + +export type ToArrayParams = { + value: T | T[]; +}; + +export type PropertyPair = { + new?: T; + old?: T; +}; + +export type CommentStatementParams = { + objectName: string; + objectType: string; + description?: string; + mode?: string; +}; + +export type ColumnCommentParams = { + tableName: string; + columnName: string; + description?: string; +}; + +export type HydrateKeyOptionsParams = { + columnName?: string; + isActivated?: boolean; + options?: KeyOptions | CompositeKeyGroup; + keyType: string; + entityName?: string; +}; + +export type KeyPropertyLookupParams = { + keyId?: string; + properties: Record; +}; + +export type ForeignKeyCustomPropertiesParams = { + customProperties?: ForeignKeyInput['customProperties']; +}; + +export type IdToNameMap = Record; + +/** + * The subset of a JSON schema the recursive walker needs. `items` is modeled as a schema rather than as the `{ mode, + * type }` pair `JsonSchemaColumn` carries, so that `Array.isArray` narrows to a schema list instead of `any[]`. + */ +export type WalkableSchema = { + GUID?: string; + code?: string; + name?: string; + collectionName?: string; + properties?: Record; + items?: WalkableSchema | WalkableSchema[]; +}; + +export type WalkSchemaParams = { + jsonSchema: WalkableSchema; + path: string[]; + callback: JsonSchemaPropertyCallback; +}; + +export type FieldNameLookupParams = { + keyRef?: { keyId?: string }[]; + idToNameHashTable: IdToNameMap; +}; + +export type LengthWithMultiplierParams = { + type: string; + length: number; + lengthSemantics: string; +}; + +export type ScalePrecisionParams = { + type: string; + precision?: number; + scale?: number; +}; + +export type HydrateViewColumnParams = { + name: string; + entityName?: string; + alias?: string; + isActivated?: boolean; + dbName?: string; +}; + +export type HydrateViewParams = { + viewData: HydratedView; + entityData: EntityDetailsTab[]; +}; + +export type DdlProvider = { + getDefaultType(type: string): string | undefined; + getTypesDescriptors(): TypeDescriptors; + hasType(type: string): boolean; + + hydrateSchema(containerData: ContainerData, data?: unknown): SchemaData; + createSchema(params: CreateSchemaParams): string; + dropSchema(params: DropSchemaParams): string; + alterSchema(schemaName: string, data?: unknown): string; + createUdt(udt: HydratedColumn, dbData?: unknown): string; + + hydrateColumn(params: HydrateColumnParams): HydratedColumn; + hydrateJsonSchemaColumn(jsonSchema: JsonSchemaColumn, definitionJsonSchema: JsonSchemaColumn): JsonSchemaColumn; + convertColumnDefinition(columnDefinition: HydratedColumn, template?: string): string; + addColumn(params: { tableName: string; columnDefinition: string }): string; + dropColumn(params: { tableName: string; columnName: string }): string; + + hydrateTable(params: HydrateTableParams): HydratedTable; + createTable(params: CreateTableParams, isActivated?: boolean): string; + dropTable(params: { tableName: string }): string; + + createForeignKeyConstraint(params: ForeignKeyInput, dbData?: unknown, schemaData?: SchemaData): ForeignKeyStatement; + createForeignKey(params: ForeignKeyInput, dbData?: unknown, schemaData?: SchemaData): ForeignKeyStatement; + + hydrateViewColumn(data: HydrateViewColumnParams): HydratedViewColumn; + hydrateView(params: HydrateViewParams): HydratedView; + createView(viewData: HydratedView, dbData?: unknown, isActivated?: boolean): string; + dropView(params: { viewName: string }): string; + + hydrateCheckConstraint(checkConstraint: CheckConstraintInput): HydratedCheckConstraint; + createCheckConstraint(params?: HydratedCheckConstraint): string; + + hydrateIndex(indexData: IndexData, tableData?: unknown, schemaData?: SchemaData): IndexData; + createIndex(tableName?: string, index?: IndexData): string; + dropIndex(name?: string): string; + + commentIfDeactivated( + statement: string, + data?: { isActivated?: boolean; isPartOfLine?: boolean }, + isPartOfLine?: boolean, + ): string; + commentStatement(statement: string): string; + prepareName(name: string): string; +}; + +export type DdlProviderFactory = ( + baseProvider: BaseProvider | null, + options: DdlProviderOptions | null, + app: App | null, +) => DdlProvider; diff --git a/forward_engineering/utils/assignTemplates.js b/forward_engineering/utils/assignTemplates.js new file mode 100644 index 0000000..756dbe0 --- /dev/null +++ b/forward_engineering/utils/assignTemplates.js @@ -0,0 +1,46 @@ +/** @import {TemplateData} from '../types/ddlProvider' */ + +/** + * Build template placeholder regexp. + * + * @param {string} [modifiers] RegExp flags. + * @returns {RegExp} Template regexp. + */ +const createTemplateRegExp = (modifiers = '') => new RegExp('\\$\\{(.*?)\\}', `${modifiers}u`); + +/** + * Find all template placeholders. + * + * @param {string} str Template string. + * @returns {string[]} Matched placeholders. + */ +const getAllTemplates = str => str.match(createTemplateRegExp('gi')) ?? []; + +/** + * Parse placeholder name. + * + * @param {string} str Placeholder string. + * @returns {string | undefined} Template name. + */ +const parseTemplate = str => (str.match(createTemplateRegExp('i')) ?? [])[1]; + +/** + * Replace template placeholders with values. + * + * @param {{ template: string; templateData: TemplateData }} params Template input. + * @returns {string} Rendered string. + */ +const assignTemplates = ({ template: templateString, templateData }) => { + return getAllTemplates(templateString).reduce((result, item) => { + const templateName = parseTemplate(item); + + return result.replace(item, () => { + const value = templateName ? templateData[templateName] : undefined; + return value || value === 0 ? String(value) : ''; + }); + }, templateString); +}; + +module.exports = { + assignTemplates, +}; diff --git a/forward_engineering/utils/general.js b/forward_engineering/utils/general.js new file mode 100644 index 0000000..19d39e7 --- /dev/null +++ b/forward_engineering/utils/general.js @@ -0,0 +1,346 @@ +/** + * @import { + * ActivatedKey, + * CommentDeactivatedOptions, + * DividedItems, + * DivideItemsParams, + * FieldComparisonParams, + * KeyConstraintColumn, + * ModelObject, + * PropertyChanges, + * PropertyPair, + * ToArrayParams, + * TypeDescriptors + * } from '../types/ddlProvider' + */ + +const isEqual = require('lodash/isEqual'); +const omit = require('lodash/omit'); +const toLower = require('lodash/toLower'); +const { INLINE_COMMENT } = require('../../shared/constants/constants'); + +/** + * Prefix each line with a tab. + * + * @param {{ text: string; tab?: string }} params Text and tab. + * @returns {string} Indented text. + */ +const setTab = ({ text, tab }) => { + const indent = tab ?? '\t'; + return text + .split('\n') + .map(line => indent + line) + .join('\n'); +}; + +/** + * Check whether descriptors include a type. + * + * @param {{ descriptors: TypeDescriptors; type: string }} params Descriptors and type. + * @returns {boolean} Whether type exists. + */ +const hasType = ({ descriptors, type }) => { + return Object.keys(descriptors) + .map(key => toLower(key)) + .includes(toLower(type)); +}; + +/** + * Check whether a key is activated. + * + * @param {{ key?: ActivatedKey | KeyConstraintColumn }} params Key object. + * @returns {boolean} Activation flag. + */ +const checkIsKeyActivated = ({ key }) => { + return key?.isActivated ?? true; +}; + +/** + * Check whether all keys are deactivated. + * + * @param {{ keys: ActivatedKey[] | KeyConstraintColumn[] | string }} params Keys list. + * @returns {boolean} Whether all keys are deactivated. + */ +const checkAllKeysDeactivated = ({ keys }) => { + if (!Array.isArray(keys)) { + return false; + } + return keys.length > 0 ? keys.every(key => !checkIsKeyActivated({ key })) : false; +}; + +/** + * Split items into activated and deactivated mapped lists. + * + * @template {ActivatedKey} T + * @template {any} K + * @param {DivideItemsParams} params Items and mapper. + * @returns {DividedItems} Divided items. + */ +const divideIntoActivatedAndDeactivated = ({ items, mapFunction }) => { + const activatedItems = items.filter(item => checkIsKeyActivated({ key: item })).map(item => mapFunction(item)); + const deactivatedItems = items.filter(item => !checkIsKeyActivated({ key: item })).map(item => mapFunction(item)); + + return { activatedItems, deactivatedItems }; +}; + +/** + * Comment a statement when deactivated. + * + * @param {string} statement Statement text. + * @param {CommentDeactivatedOptions} options Comment options. + * @returns {string} Possibly commented statement. + */ +const commentIfDeactivated = (statement, { isActivated, isPartOfLine, inlineComment }) => { + const commentMarker = inlineComment ?? INLINE_COMMENT; + if (isActivated) { + return statement; + } + + if (isPartOfLine) { + return '/* ' + statement + ' */'; + } + + if (statement.includes('\n')) { + return '/*\n' + statement + ' */\n'; + } + return commentMarker + ' ' + statement; +}; + +/** + * Wrap a value in double quotes. + * + * @param {string} str Value. + * @returns {string} Quoted value. + */ +const wrapInQuotes = str => `"${str}"`; + +/** + * Wrap a name in single quotes. + * + * @param {{ name: string }} params Name value. + * @returns {string} Quoted name. + */ +const wrapInSingleQuotes = ({ name }) => `'${name}'`; + +/** + * Remove all quotes from a string. + * + * @param {string} str Input string. + * @returns {string} Unquoted string. + */ +const removeAllQuotes = str => str.replaceAll(/['"]/gu, ''); + +/** + * Prefix a name with an optional schema. + * + * @param {{ name: string; schemaName?: string }} params Name parts. + * @returns {string} Qualified name. + */ +const getNamePrefixedWithSchemaName = ({ name, schemaName }) => { + if (schemaName) { + return `${wrapInQuotes(schemaName)}.${wrapInQuotes(name)}`; + } + + return wrapInQuotes(name); +}; + +/** + * Map a column to a quoted name. + * + * @param {{ name?: string }} params Column. + * @returns {string} Quoted column name. + */ +const columnMapToString = ({ name }) => wrapInQuotes(name ?? ''); + +/** + * Map a column to a quoted name with order. + * + * @param {{ name: string; type?: string }} params Column and order. + * @returns {string} Column expression. + */ +const columnMapToStringWithOrder = ({ name, type }) => { + const order = type === 'descending' ? ' DESC' : type === 'ascending' ? ' ASC' : ''; + return wrapInQuotes(name) + order; +}; + +/** + * Build a columns list with activation comments. + * + * @param {KeyConstraintColumn[]} columns Columns. + * @param {boolean} isAllColumnsDeactivated Whether all columns are deactivated. + * @param {boolean} isParentActivated Whether parent is activated. + * @param {(column: KeyConstraintColumn) => string} [mapColumn] Column mapper. + * @returns {string} Columns list string. + */ +const getColumnsList = (columns, isAllColumnsDeactivated, isParentActivated, mapColumn = columnMapToString) => { + const dividedColumns = divideIntoActivatedAndDeactivated({ items: columns, mapFunction: mapColumn }); + const deactivatedColumnsAsString = dividedColumns?.deactivatedItems?.length + ? commentIfDeactivated(dividedColumns.deactivatedItems.join(', '), { + isActivated: false, + isPartOfLine: true, + }) + : ''; + + return !isAllColumnsDeactivated && isParentActivated + ? ' (' + dividedColumns.activatedItems.join(', ') + deactivatedColumnsAsString + ')' + : ' (' + columns.map(column => mapColumn(column)).join(', ') + ')'; +}; + +/** + * Normalize a value to an array. + * + * @template {object} T + * @param {ToArrayParams} params Value. + * @returns {T[]} Array value. + */ +const toArray = ({ value }) => (Array.isArray(value) ? value : [value]); + +/** + * Get altered entity name. + * + * @param {ModelObject} entityData Entity data. + * @returns {string | undefined} Alter name. + */ +const getAlterEntityName = entityData => { + return entityData?.compMod?.collectionName?.new; +}; + +/** + * Get entity display name. + * + * @param {ModelObject} entityData Entity data. + * @returns {string} Entity name. + */ +const getEntityName = entityData => { + return entityData?.code ?? entityData?.collectionName ?? entityData?.name ?? ''; +}; + +/** + * Get schema name from a collection. + * + * @param {{ collection: ModelObject }} params Collection. + * @returns {string | undefined} Schema name. + */ +const getSchemaNameFromCollection = ({ collection }) => { + return collection.compMod?.keyspaceName; +}; + +/** + * Build a fully qualified collection name. + * + * @param {{ collectionSchema: ModelObject; preferAlterName?: boolean }} params Collection schema. + * @returns {string} Qualified name. + */ +const getFullCollectionName = ({ collectionSchema, preferAlterName = true }) => { + let name = ''; + + if (preferAlterName) { + name = getAlterEntityName(collectionSchema) ?? ''; + } + + name = name || getEntityName(collectionSchema); + + const schemaName = getSchemaNameFromCollection({ collection: collectionSchema }); + return getNamePrefixedWithSchemaName({ name, schemaName }); +}; + +/** + * Get schema of an alter collection. + * + * @template {ModelObject} T + * @param {T} collection Collection. + * @returns {T} Merged schema. + */ +const getSchemaOfAlterCollection = collection => { + return { ...collection, ...omit(collection?.role, 'properties') }; +}; + +/** + * Check whether an object is activated in a delta model. + * + * @param {ModelObject} modelObject Model object. + * @returns {boolean} Activation flag. + */ +const isObjectInDeltaModelActivated = modelObject => { + return modelObject.compMod?.isActivated?.new ?? modelObject.role?.isActivated ?? false; +}; + +/** + * Check whether parent container is activated. + * + * @param {ModelObject} collection Collection. + * @returns {boolean} Activation flag. + */ +const isParentContainerActivated = collection => { + return Boolean( + collection?.compMod?.bucketProperties?.isActivated ?? collection?.role?.compMod?.bucketProperties?.isActivated, + ); +}; + +/** + * Check whether field properties changed. + * + * @param {FieldComparisonParams} compMod CompMod object. + * @param {string[]} propertiesToCheck Properties to check. + * @returns {boolean} Whether any property changed. + */ +const checkFieldPropertiesChanged = (compMod, propertiesToCheck) => { + return propertiesToCheck.some(prop => compMod?.oldField[prop] !== compMod?.newField[prop]); +}; + +/** + * Compare old and new property values. + * + * @template T + * @param {PropertyPair} params Property pair. + * @returns {boolean} Whether values differ. + */ +const compareProperties = ({ new: newProperty, old: oldProperty }) => { + if (!newProperty && !oldProperty) { + return false; + } + return !isEqual(newProperty, oldProperty); +}; + +/** + * Collect updated properties from compMod. + * + * @param {PropertyChanges} compMod CompMod object. + * @param {string[]} properties Property names. + * @returns {Record} Changed properties with new values. + */ +const getUpdatedProperties = (compMod, properties) => { + /** @type {Record} */ + const updatedProperties = {}; + properties.forEach(property => { + const propCompMod = compMod[property] ?? {}; + if (compareProperties(propCompMod) && propCompMod.new !== undefined) { + updatedProperties[property] = propCompMod.new; + } + }); + return updatedProperties; +}; + +module.exports = { + setTab, + hasType, + checkAllKeysDeactivated, + checkIsKeyActivated, + divideIntoActivatedAndDeactivated, + commentIfDeactivated, + wrapInQuotes, + wrapInSingleQuotes, + removeAllQuotes, + getNamePrefixedWithSchemaName, + getColumnsList, + columnMapToStringWithOrder, + toArray, + getFullCollectionName, + getEntityName, + getSchemaOfAlterCollection, + isObjectInDeltaModelActivated, + isParentContainerActivated, + getSchemaNameFromCollection, + getUpdatedProperties, + checkFieldPropertiesChanged, +}; diff --git a/forward_engineering/utils/joinActivatedAndDeactivatedStatements.js b/forward_engineering/utils/joinActivatedAndDeactivatedStatements.js new file mode 100644 index 0000000..29a6a45 --- /dev/null +++ b/forward_engineering/utils/joinActivatedAndDeactivatedStatements.js @@ -0,0 +1,57 @@ +/** + * @import { + * DelimiterParams, + * JoinStatementsParams + * } from '../types/ddlProvider' + */ + +const { INLINE_COMMENT } = require('../../shared/constants/constants'); + +/** + * Resolve statement delimiter. + * + * @param {DelimiterParams} params Delimiter options. + * @returns {string} Delimiter string. + */ +const getDelimiter = ({ index, numberOfStatements, lastIndexOfActivatedStatement, delimiter }) => { + const isLastStatement = index === numberOfStatements - 1; + const isLastActivatedStatement = index === lastIndexOfActivatedStatement; + + if (isLastStatement) { + return ''; + } + + if (isLastActivatedStatement) { + return ' --' + delimiter; + } + + return delimiter; +}; + +/** + * Join activated and deactivated statements. + * + * @param {JoinStatementsParams} params Join options. + * @returns {string} Joined statements. + */ +const joinActivatedAndDeactivatedStatements = ({ statements, delimiter = ',', indent = '\n' }) => { + const lastIndexOfActivatedStatement = statements.findLastIndex(statement => !statement.startsWith(INLINE_COMMENT)); + const numberOfStatements = statements.length; + + return statements + .map((statement, index) => { + const currentDelimiter = getDelimiter({ + index, + numberOfStatements, + lastIndexOfActivatedStatement, + delimiter, + }); + + return statement + currentDelimiter; + }) + .join(indent); +}; + +module.exports = { + joinActivatedAndDeactivatedStatements, +}; diff --git a/forward_engineering/utils/toPluginError.js b/forward_engineering/utils/toPluginError.js new file mode 100644 index 0000000..9baab53 --- /dev/null +++ b/forward_engineering/utils/toPluginError.js @@ -0,0 +1,17 @@ +/** + * Normalize a thrown value into the error shape the studio expects from plugin callbacks. + * + * @param {unknown} error Thrown value. + * @returns {{ message: string; stack?: string }} Plugin error. + */ +const toPluginError = error => { + if (error instanceof Error) { + return { message: error.message, stack: error.stack }; + } + + return { message: String(error) }; +}; + +module.exports = { + toPluginError, +}; diff --git a/jsonSchemaProperties.json b/jsonSchemaProperties.json new file mode 100644 index 0000000..999ca8a --- /dev/null +++ b/jsonSchemaProperties.json @@ -0,0 +1,14 @@ +{ + "unneededFieldProps": [ + "collectionName", + "name", + "users", + "indexes", + "collectionUsers", + "compositeClusteringKey", + "compositePartitionKey", + "SecIDxs", + "compositeKey" + ], + "removeIfPropsNegative": ["partitionKey", "sortKey"] +} diff --git a/lint-staged.config.js b/lint-staged.config.js new file mode 100644 index 0000000..4139c06 --- /dev/null +++ b/lint-staged.config.js @@ -0,0 +1,17 @@ +/* + * Copyright © 2016-2026 by IntegrIT S.A. dba Hackolade. All rights reserved. + * + * The copyright to the computer software herein is the property of IntegrIT S.A. + * The software may be used and/or copied only with the written permission of + * IntegrIT S.A. or in accordance with the terms and conditions stipulated in + * the agreement/contract under which the software has been supplied. + */ +module.exports = { + '*.{js,cjs}': [ + 'oxfmt --no-error-on-unmatched-pattern', + 'npm run lint', + // Prevent lint-staged from appending filenames, so tsc loads tsconfig.json. + () => 'npm run types:check', + ], + '*.{json,css,scss}': ['oxfmt --no-error-on-unmatched-pattern'], +}; diff --git a/localization/en.json b/localization/en.json new file mode 100644 index 0000000..adc5edf --- /dev/null +++ b/localization/en.json @@ -0,0 +1,167 @@ +{ + "WELCOME_PAGE___REVERSE_ENGINEER_DESCRIPTION": "Create a Hackolade model from an existing DB instance", + "MAIN_MENU___ADD_BUCKET": "Add Schema", + "MAIN_MENU___ADD_COLLECTION": "Add Table", + "MAIN_MENU___ADD_VIEW": "Add View", + "MAIN_MENU___ADD_RELATIONSHIP": "Add Relationship", + "MAIN_MENU___ADD_ATTRIBUTE": "Add Column", + "MAIN_MENU___INSERT_FIELD": "Insert Column", + "MAIN_MENU___APPEND_FIELD": "Append Column", + "MAIN_MENU___REVERSE_DB_COLLECTIONS": "Db2 databases...", + "MAIN_MENU___FORWARD_CHANGE_COLLECTIONS": "Db2 Alter Script", + "TOOLBAR___ADD_BUCKET": "Add schema", + "TOOLBAR___ADD_COLLECTION": "Add table", + "TOOLBAR___ADD_VIEW": "Add View", + "TOOLBAR___ADD_RELATIONSHIP": "Add Relationship", + "TOOLBAR___ADD_ATTRIBUTE": "Add Column", + "TOOLBAR___INSERT_FIELD": "Insert Column", + "TOOLBAR___APPEND_FIELD": "Append Column", + "TOOLBAR___FIELD_DETAILS": "Field details", + "TOOLBAR___SHOW_FOREIGN_MASTER": "Toggle foreign master", + "TOOLBAR___SHOW_MODEL_VIEW": "Toggle model views", + "TOOLBAR___DISTRIBUTE_ORTHOGONALLY": "Distribute tables orthogonally", + "OBJECT___BROWSER_BUCKET": "Schemas", + "OBJECT___BROWSER_NOT_IN_BUCKET": "Undefined Schema", + "OBJECT___BROWSER_COLLECTION": "Tables", + "OBJECT___BROWSER_VIEWS": "Views", + "OBJECT___BROWSER_DEFINITIONS": "User-Defined Types", + "OBJECT___BROWSER_FIELDS": "Columns", + "PROPERTIES_PANE___BUCKET_NAME": "Schema name", + "PROPERTIES_PANE___VIEW_NAME": "View name", + "PROPERTIES_PANE___COLLECTION_NAME": "Table", + "PROPERTIES_PANE___FOREIGN_COLLECTION": "Foreign table", + "PROPERTIES_PANE___FOREIGN_FIELD": "Foreign field", + "PROPERTIES_PANE___PARENT_COLLECTION": "Parent table", + "PROPERTIES_PANE___PARENT_FIELD": "Parent field", + "PROPERTIES_PANE___PARENT_CARDINALITY": "Parent cardinality", + "PROPERTIES_PANE___CHILD_COLLECTION": "Child table", + "PROPERTIES_PANE___CHILD_FIELD": "Child field", + "PROPERTIES_PANE___CHILD_CARDINALITY": "Child cardinality", + "PROPERTIES_PANE___PRIMARY_KEY": "Primary key", + "PROPERTIES_PANE___COLLECTION_BUCKET": "Schema", + "PROPERTIES_PANE___VIEW_ON": "View on", + "PROPERTIES_PANE___PIPELINE": "Pipeline", + "PROPERTIES_PANE___COLLATION": "Collation", + "PROPERTIES_PANE___LOCALE": "Locale", + "PROPERTIES_PANE___VARIANT": "Variant", + "PROPERTIES_PANE___STRENGTH": "Strength", + "PROPERTIES_PANE___CASE_LEVEL": "Case level", + "PROPERTIES_PANE___CASE_FIRST": "Case first", + "PROPERTIES_PANE___NUMERIC_ORDERING": "Numeric ordering", + "PROPERTIES_PANE___ALTERNATE": "Alternate", + "PROPERTIES_PANE___MAX_VARIABLE": "Max variable", + "PROPERTIES_PANE___BACKWARDS": "Backwards", + "PROPERTIES_PANE___NORMALIZATION": "Normalization", + "PROPERTIES_PANE___NAME": "Business Name", + "PROPERTIES_PANE___CODE": "Technical name", + "PROPERTIES_PANE___DESCRIPTIONS": "comments", + "CENTRAL_PANE___TAB_MONGODB_VIEW_SCRIPT": "Create View Script", + "CONTEXT_MENU___ADD_BUCKET": "Add schema", + "CONTEXT_MENU___ADD_COLLECTION": "Add table", + "CONTEXT_MENU___ADD_VIEW": "Add view", + "CONTEXT_MENU___ALIGN_COLLECTIONS": "Align tables", + "CONTEXT_MENU___ADD_ATTRIBUTE": "Add Column", + "CONTEXT_MENU___INSERT_ATTRIBUTE": "Insert Column", + "CONTEXT_MENU___APPEND_ATTRIBUTE": "Append Column", + "CONTEXT_MENU___OPEN_COLLECTION_IN_NEW_TAB": "Open table in new tab", + "CONTEXT_MENU___FIELD": "Column", + "CONTEXT_MENU___PATTERN_FIELD": "Pattern Column", + "CONTEXT_MENU___ARRAY_ITEM": "Array Item", + "MODAL_WINDOW___COLLECTION": "table:", + "MODAL_WINDOW___EMPTY_MODEL_MESSAGE": "The model does not contain any schemas.", + "MODAL_WINDOW___FIELD_INFERENCE": "Field Inference", + "MODAL_WINDOW___KEEP_FIELD_ORDER": "Keep field order", + "MODAL_WINDOW___CANNOT_CONNECT_TO_DB": "Cannot connect to Db2 server", + "MODAL_WINDOW___SUCCESSFULLY_CONNECT_TO_DB": "Successfully connected to Db2 server", + "MODAL_WINDOW___UNABLE_CONNECT_TO_DB": "Unable to connect to Db2 server", + "MODAL_WINDOW___DB_ENTITIES_SELECTION_TITLE": "Table selection", + "MODAL_WINDOW___RECORDS_MAX": "Documents max", + "MODAL_WINDOW___SUBDOCUMENT_IN_CHILD": "Sub-document in child", + "MODAL_WINDOW___ARRAY_IN_PARENT": "Array in parent", + "MODAL_WINDOW___INCLUDE_EMPTY_COLLECTION": "Include empty tables", + "MODAL_WINDOW___CREATE_COLLECTION": "Create table", + "MODAL_WINDOW___CREATE_BUCKET": "Create schema", + "MODAL_WINDOW___ALL_COLLECTIONS": "and all nested tables", + "MODAL_WINDOW___CONNENTION_ERROR": "The Db2 instance you are connected to does not contain any schemas.", + "MODAL_WINDOW___CONTAIN_BUCKETS": "schemas", + "MODAL_WINDOW___CONTAIN_COLLECTIONS": "tables", + "MODAL_WINDOW___CONTAIN_BUCKET": "schemas", + "MODAL_WINDOW___CONTAIN_COLLECTION": "tables", + "MODAL_WINDOW___DB_CONNECTION_PROCESS": "Db2 Reverse-Engineering Process", + "MODAL_WINDOW___DB_CONNECTIONS_LIST_TITLE": "Db2 Connections", + "PROGRESS_BAR___DATABASE": "Schema", + "PROGRESS_BAR___COLLECTION": "Table", + "PROGRESS_BAR___PROCESS": "Process", + "DOCUMENTATION___PARENT_COLLECTION": "Parent Table", + "DOCUMENTATION___PARENT_FIELD": "Parent Column", + "DOCUMENTATION___PARENT_CARDINALITY": "Parent Cardinality", + "DOCUMENTATION___CHILD_COLLECTION": "Child Table", + "DOCUMENTATION___CHILD_FIELD": "Child Column", + "DOCUMENTATION___CHILD_CARDINALITY": "Child Cardinality", + "DOCUMENTATION___FIELD": "Column", + "DOCUMENTATION___COLLECTIONS": "Tables", + "DOCUMENTATION___COLLECTION": "Table", + "DOCUMENTATION___BUCKETS": "Schemas", + "DOCUMENTATION___BUCKET": "Schema", + "DOCUMENTATION___FIELDS": "Column", + "DOCUMENTATION___CHILD_FIELDS": "Child column(s)", + "DOCUMENTATION___PHYSICAL_MODEL": "Db2 Physical Model", + "DOCUMENTATION___VIEWS": "Views", + "DOCUMENTATION___VIEW": "View", + "TOOLTIPS___FOREIGN_COLLECTION": "foreign table", + "TOOLTIPS___COLLECTION_BUCKET": "Schema", + "TOOLTIPS___FOREIGN_FIELD": "foreign column", + "TOOLTIPS___PARENT_COLLECTION": "parent table", + "TOOLTIPS___PARENT_FIELD": "parent column", + "TOOLTIPS___CHILD_COLLECTION": "child table", + "TOOLTIPS___CHILD_FIELD": "child column", + "TOOLTIPS___VIEW_ON": "view on", + "TOOLTIPS___PIPELINE": "pipeline", + "TOOLTIPS___COLLATION": "collation", + "TOOLTIPS___LOCALE": "locale", + "TOOLTIPS___VARIANT": "variant", + "TOOLTIPS___STRENGTH": "strength", + "TOOLTIPS___CASE_LEVEL": "case level", + "TOOLTIPS___CASE_FIRST": "case first", + "TOOLTIPS___NUMERIC_ORDERING": "numeric ordering", + "TOOLTIPS___ALTERNATE": "alternate", + "TOOLTIPS___MAX_VARIABLE": "max variable", + "TOOLTIPS___BACKWARDS": "Backwards", + "TOOLTIPS___NORMALIZATION": "Normalization", + "MAIN_MENU___FORWARD_DB_BUCKETS": "Db2 DDL...", + "NEW___MODEL_NAME": "New model", + "NEW___BUCKET_NAME": "New schema", + "NEW___COLLECTION_NAME": "New table", + "NEW___FIELD_NAME": "New volumn", + "NEW___PATTERN_FIELD_NAME": "^New Pattern Column$", + "COLLECTION_SCHEMA_DEFINITION_NAME": "Table definitions", + "COLLECTION_SCHEMA_DEFINITION_TYPE": "document", + "MONGODB_SCRIPT_WARNING_MESSAGE": "This view is not associated to a type (viewOn property).", + "TYPE": {}, + "CENTRAL_PANE___TAB_MODEL_DEFINITIONS": "User-Defined Types", + "CONTEXT_MENU___ADD_MODEL_REFERENCE": "User-Defined Type", + "CONTEXT_MENU___GO_TO_DEFINITION": "Go to User-Defined Type", + "DOCUMENTATION___DB_DEFINITIONS": "User-Defined Types", + "CONTEXT_MENU___CONVERT_TO_PATTERN_FIELD": "Convert to Pattern Column", + "CONTEXT_MENU___CONVERT_PATTERN_TO_REGULAR_FIELD": "Convert to Regular Column", + + "MAIN_MENU___ATTRIBUTES": "Columns in Table Boxes", + "MAIN_MENU___DESCRIPTIONS": "Description in Table Boxes", + "MAIN_MENU___HIDE_ALL_ATTRIBUTES": "Empty Table Boxes", + "MAIN_MENU___REQUIRED_ATTRIBUTES": "Required Columns", + "MAIN_MENU___NULLABLE_ATTRIBUTES": "Nullable Columns", + "TOOLBAR___ATTRIBUTES": "Columns in table boxes", + "TOOLBAR___DESCRIPTIONS": "Description in table boxes", + "TOOLBAR___HIDE_ALL_ATTRIBUTES": "Empty table boxes", + "TOOLBAR___REQUIRED_ATTRIBUTES": "Required columns", + "TOOLBAR___NULLABLE_ATTRIBUTES": "Nullable columns", + "MODAL_WINDOW___OPTIONS_DISPLAY_ERD_V_ENTITY_BOX_CONTENT": "Display of table box content", + "MODAL_WINDOW___OPTIONS_DISPLAY_ERD_V_FIELDS": "Columns", + "MODAL_WINDOW___OPTIONS_DISPLAY_REQUIRED_ATTRIBUTES": "Required columns", + "MODAL_WINDOW___OPTIONS_DISPLAY_NULLABLE_ATTRIBUTES": "Nullable columns", + + "CUSTOM_SCRIPT_CONTAINER_VAR_NAME": "Schema name", + "CUSTOM_SCRIPT_CONTAINER_VAR": "schemaName", + "CUSTOM_SCRIPT_ENTITY_VAR_NAME": "Table name", + "CUSTOM_SCRIPT_ENTITY_VAR": "tableName" +} diff --git a/logo.png b/logo.png new file mode 100644 index 0000000..ab6dcae Binary files /dev/null and b/logo.png differ diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..64464ce --- /dev/null +++ b/package-lock.json @@ -0,0 +1,3787 @@ +{ + "name": "Db2-zOS", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "Db2-zOS", + "version": "0.1.0", + "dependencies": { + "lodash": "4.18.1" + }, + "devDependencies": { + "@hackolade/hck-esbuild-plugins-pack": "0.0.1", + "@types/lodash": "4.17.16", + "@types/node": "24.13.3", + "esbuild": "0.28.1", + "esbuild-node-externals": "1.23.1", + "esbuild-plugin-clean": "1.0.1", + "esbuild-plugin-copy": "2.1.1", + "eslint-plugin-jsdoc": "63.3.2", + "lint-staged": "17.2.0", + "oxfmt": "0.61.0", + "oxlint": "1.76.0", + "oxlint-tsgolint": "7.0.2001", + "simple-git-hooks": "2.13.1", + "typescript": "7.0.2" + }, + "engines": { + "hackolade": "7.7.10", + "hackoladePlugin": "1.2.0", + "node": ">=24.0.0" + } + }, + "node_modules/@es-joy/jsdoccomment": { + "version": "0.91.0", + "resolved": "https://registry.npmjs.org/@es-joy/jsdoccomment/-/jsdoccomment-0.91.0.tgz", + "integrity": "sha512-vgqlMGNNhZxwDYbUNIHj3Hskb4R28iqdXx90ufHyt/NeuTQkeqjTDslAs9I0/GCAfbxP5BpH5WsL1R1fht5Lxg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.9", + "@typescript-eslint/types": "^8.65.0", + "comment-parser": "1.4.7", + "esquery": "^1.7.0", + "jsdoc-type-pratt-parser": "~8.0.0" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } + }, + "node_modules/@es-joy/resolve.exports": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@es-joy/resolve.exports/-/resolve.exports-1.2.0.tgz", + "integrity": "sha512-Q9hjxWI5xBM+qW2enxfe8wDKdFWMfd0Z29k5ZJnuBqD/CasY5Zryj09aCA6owbGATWz+39p5uIdaHXpopOcG8g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.1.tgz", + "integrity": "sha512-Svl7tq8k/08+p6CXPpRjQ1fKX+1odH/BQbb48fV6fj3CWHhsoIOoY87w1oHXm0qEpkIK3ZfVgp0hed3XBXzXMQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.1.tgz", + "integrity": "sha512-0k2F129Xdio1TdJfzJ8sy1Q47vUD2NnwdhiAf7drUN1EBTfPf4hsFCtmMgu/6m8JSzsBrlmVjudMBQqOfG8usQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.1.tgz", + "integrity": "sha512-34EGEbCIAgosYz6goLcopX6Mo7NyGv9tfwEM2/7Ce2VcVRk568iSvniGWcUXIy7wEDR1wzolcxcriFVrWYcwBg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.1.tgz", + "integrity": "sha512-dbwY7ltSMDWsRatcRpCnES4F+im88OCUgGZjy52shC7GqHRE/cYlxNbB4Z4UpJswpcc4Qxd2oE/ufM0p61IKng==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.1.tgz", + "integrity": "sha512-TZbWkQY7kvTAXbXUT7uVACR5cMHsDiSz9z7ZKAX/RTq/WJEk3QyRr0wZpNhBDX+/0CtdqUIJlOiodQcta6tY3Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.1.tgz", + "integrity": "sha512-zfdzgK9ACBNZLI/CyHTOx81SyNbM6YXn7rxSgX97VjyiPl9W1i4Ka4fgKECEoFCKGpvBj5qArWIGgQjOwkgskQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.1.tgz", + "integrity": "sha512-wG2EA8ENdEI0qhkSZMjfqrdY+ziCYCPMmtZjjIwOmXFjmyzEHn+UUxk5of+SYsjtfs3VpnlC7QLzSI5hY/rOAw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.28.1.tgz", + "integrity": "sha512-i7dZ9vQgnvSCzi/rYCXNgtF/U+eKZNJBzu3eTQbRgHnM7tNSizLOkRFAl3qzVc/Op/u5YkHHa4pf/3DOYHthLQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.28.1.tgz", + "integrity": "sha512-qVXBOHQS+d5Y722GwJzJUtOLlX7km3CraOaGormF1pDtPd2C/l1SHRPgjLunLGe51Sh5YYWKMFDyV4SxgMQYTQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.28.1.tgz", + "integrity": "sha512-yHs+0uc8+nvEAfAfxrWQKK5peSNzBc4PegcMO0EJ2hT71uA7vB8Ihg2e77R2P7SG5uYjPbHlLLmve4LLLRCf0g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.28.1.tgz", + "integrity": "sha512-d1z4ZuP0ajrfz/FhGT4vv278rX8KnPPJx8i5+AtK7TYbx9Le9F1hyzurZpkEyjkGa9dUGhQow4C1NmeGvqxN2w==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.28.1.tgz", + "integrity": "sha512-M5sRjUVZrkm1OAPR3dlOYzNmN+loZKGVi1VUQGrwuqLcbR6qeAz+famMhjASeH3YVKvZz+zT1jlh/keC3Rj/lg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.1.tgz", + "integrity": "sha512-mRObBZeHh2OxcBFPWE/FjylkRgZdYuiTR3vaTozquCGOH14iP9oN4x4Ge81CoIDYQrXmIxpFumJBu5MtZpnQJQ==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.1.tgz", + "integrity": "sha512-slScBsMAb3GFDcdrCgLwZtPYRoH2H/youv10QiZyRjmsP48fznoveWytSgCI/R0ZcUgpc0ZhIUEx6LHts8yrfQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.1.tgz", + "integrity": "sha512-kw0owk1o0GFETUJyW0jc0G4Yzs0BHZn0JDZ8JRT088vjJYX777BAs1fDGxAC+q831qOs2DTC96mNsG2opdfyyQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.1.tgz", + "integrity": "sha512-/lAIjX8aYFRByhh6L5rYtPEDRqa9de/4V/juOXcta5frjvzXO4/sqEtyytse0g3zZFuWu5cDN0MkLz2qRDD2Ag==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.28.1.tgz", + "integrity": "sha512-u/anNYF2mmVOEDwLtnQ1wOr3EZ9sTNGLWrsYGYwHWzGA3Si84IOkHXlbWTD1NB+9/1lcnweYKO54uhxZydNzfA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.1.tgz", + "integrity": "sha512-oks0DYbLwWMmaakTsCb+zL4E+aHRVLom9IJZOAthMQEPiQmydXHkziYEsGYRx0uNV/IjEKGAV941JzH02pflqw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.1.tgz", + "integrity": "sha512-aeL6lAnN89Hz43Mlh1G8ARasbuoYvSITDEx0tHh5b7jJnHcssqgjy9Yx430GDpmCa6OyrKoS0aNRjKundRizGg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.1.tgz", + "integrity": "sha512-MEFJe5C3R8pwXdZ5Y21oo6m7ePiS0d9pWucn99O/wvyJZChoIQKrQDxKrGeW8F5+T0okTHesAmDeiHDTIq0V/Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.1.tgz", + "integrity": "sha512-i/ZLIOafE0Z8cI/XANJAixoJL/uRAoS2xOA3rb0xN+KK0K177cMAsQYkzHtBrtMXAKuAc7HGgcWiZ/sRC1Nxgw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.1.tgz", + "integrity": "sha512-ge+Z7EXFNt2BO1oAMsVpiQ8EwndV9i1xXerAeTIK7AtPs3bKFXQM7nlRxDSIUIMeueR1CNXxqztLzdNeReKBJg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.1.tgz", + "integrity": "sha512-BEjgtECkL3vY+SaSQ6nzVfiALUeFxpawyp8Jmf5PtYhf1Ug40N1h/hxlhts+f1FvSvarEigdxS3BlSMI2PJLcQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.1.tgz", + "integrity": "sha512-lCv9eK/H6ZJWbE7bh2nw54CZ9M2nupBxJcTsdk/QQnWkdSjKGuxmmH8/GWrlT1eMmZfn4dGcCjRte397WqfQXA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.28.1.tgz", + "integrity": "sha512-zvb/mB2bSCoJOpoCBgYKKpX6YM6mJBlBUVUtVj41DlZJVEB6/0CKlRYxP5wWl1C1ILiCoAU5wZZ4q1P3qeS6Eg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.1.tgz", + "integrity": "sha512-bm4Mowrv+GXMlpWX++EcXw/iLyd1o3+bJkC2DkWXYVvgZCqD/bSj9ctZeAMC3cIxgjRVR2Dufaiu4YPxr5gW1A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.10.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.10.1.tgz", + "integrity": "sha512-cuadcxVFE8sDK6iWJbs8Sn0av2Nrh2QSGQhVlBW9AaAHqHwjWsZHT8LJ4hFGPh7ASBV2deFdM7H/DPjulmh8rg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", + "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/config-array": { + "version": "0.23.5", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.23.5.tgz", + "integrity": "sha512-Y3kKLvC1dvTOT+oGlqNQ1XLqK6D1HU2YXPc52NmAlJZbMMWDzGYXMiPRJ8TYD39muD/OTjlZmNJ4ib7dvSrMBA==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@eslint/object-schema": "^3.0.5", + "debug": "^4.3.1", + "minimatch": "^10.2.4" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } + }, + "node_modules/@eslint/config-helpers": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.7.0.tgz", + "integrity": "sha512-DObd/KKUsU+FaFv4PLxSRenpXfQWmPXXP3pPZ6/K1PCrMu2vQpMDMuQe/BqYeoLcz8ro0bVDF1RxOJgfVEdhUw==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@eslint/core": "^1.2.1" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } + }, + "node_modules/@eslint/core": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-1.2.1.tgz", + "integrity": "sha512-MwcE1P+AZ4C6DWlpin/OmOA54mmIZ/+xZuJiQd4SyB29oAJjN30UW9wkKNptW2ctp4cEsvhlLY/CsQ1uoHDloQ==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } + }, + "node_modules/@eslint/object-schema": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-3.0.5.tgz", + "integrity": "sha512-vqTaUEgxzm+YDSdElad6PiRoX4t8VGDjCtt05zn4nU810UIx/uNEV7/lZJ6KwFThKZOzOxzXy48da+No7HZaMw==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } + }, + "node_modules/@eslint/plugin-kit": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.7.2.tgz", + "integrity": "sha512-+CNAzxglkrpNf/kKywqQfk74QjtceuOE7Qm+AF8miRvPF/wmmK5+OJOgVh3AVTT3RP2mH3+FOaxlE5v72owk0A==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@eslint/core": "^1.2.1", + "levn": "^0.4.1" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } + }, + "node_modules/@hackolade/hck-esbuild-plugins-pack": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/@hackolade/hck-esbuild-plugins-pack/-/hck-esbuild-plugins-pack-0.0.1.tgz", + "integrity": "sha512-Y4e3BJ28KOc9NWF3pfTOMlyWImfQx5h8gE+eQF0QWBOZO1M/TuAadiChUmu6QgrbBXzz2dAW0Fetl5IO5bolsw==", + "dev": true, + "license": "MIT", + "dependencies": { + "fs-extra": "^11.1.1" + }, + "peerDependencies": { + "esbuild": ">= 0.17.10" + } + }, + "node_modules/@humanfs/core": { + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.2.tgz", + "integrity": "sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@humanfs/types": "^0.15.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node": { + "version": "0.16.8", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.8.tgz", + "integrity": "sha512-gE1eQNZ3R++kTzFUpdGlpmy8kDZD/MLyHqDwqjkVQI0JMdI1D51sy1H958PNXYkM2rAac7e5/CnIKZrHtPh3BQ==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@humanfs/core": "^0.19.2", + "@humanfs/types": "^0.15.0", + "@humanwhocodes/retry": "^0.4.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/types": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/@humanfs/types/-/types-0.15.0.tgz", + "integrity": "sha512-ZZ1w0aoQkwuUuC7Yf+7sdeaNfqQiiLcSRbfI08oAxqLtpXQr9AIVX7Ay7HLDuiLYAaFPu8oBYNq/QIi9URHJ3Q==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/retry": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", + "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@oxfmt/binding-android-arm-eabi": { + "version": "0.61.0", + "resolved": "https://registry.npmjs.org/@oxfmt/binding-android-arm-eabi/-/binding-android-arm-eabi-0.61.0.tgz", + "integrity": "sha512-BaS+1OVvg9sr+Xav0+KdWedQRcAzrdoEcwMZeqoc2F6ieC1s/t5eM35YQoRPQ7vAqkZ+p3tbQb1r9I9mrV5oGA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxfmt/binding-android-arm64": { + "version": "0.61.0", + "resolved": "https://registry.npmjs.org/@oxfmt/binding-android-arm64/-/binding-android-arm64-0.61.0.tgz", + "integrity": "sha512-of8atAV0M1egGcVOMbgZCvc10sFOP3ayQBNQV5h5G3fNq8gACdEswfFk9bzGrdbM23rtg0Coxi7np7oPLcueNw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxfmt/binding-darwin-arm64": { + "version": "0.61.0", + "resolved": "https://registry.npmjs.org/@oxfmt/binding-darwin-arm64/-/binding-darwin-arm64-0.61.0.tgz", + "integrity": "sha512-7l8+5ov4BGwtAcmpzvEik/TG3bciwyw/S3e6j5GKH7pcQqcgCVxD3AuJeP6upto+SOTBKQ4wrrdbMt0gq8fHSQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxfmt/binding-darwin-x64": { + "version": "0.61.0", + "resolved": "https://registry.npmjs.org/@oxfmt/binding-darwin-x64/-/binding-darwin-x64-0.61.0.tgz", + "integrity": "sha512-Fnz4dDDXBb7udk+DmwelNjxbD6yptyxwCqwCH2ebo4RVLxVsRfFsn/AHJC49KIltPrVokamGv4SSOsiV50DTxQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxfmt/binding-freebsd-x64": { + "version": "0.61.0", + "resolved": "https://registry.npmjs.org/@oxfmt/binding-freebsd-x64/-/binding-freebsd-x64-0.61.0.tgz", + "integrity": "sha512-mddOebKNCP+AucmzfNsk3jgbr681qAUvgMqi865GW5gWLJ/AnzXbvjQRrny0e++NAN8aphav/aRSrfFxNsNjpA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxfmt/binding-linux-arm-gnueabihf": { + "version": "0.61.0", + "resolved": "https://registry.npmjs.org/@oxfmt/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-0.61.0.tgz", + "integrity": "sha512-svx59iYL+DbaZGZUIoice4W0CjRXGExnbz7Re+awIb60rVxBS2KrU7Hnlx+nZYanLGLpjneUEgo/VFEKkSZAyQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxfmt/binding-linux-arm-musleabihf": { + "version": "0.61.0", + "resolved": "https://registry.npmjs.org/@oxfmt/binding-linux-arm-musleabihf/-/binding-linux-arm-musleabihf-0.61.0.tgz", + "integrity": "sha512-BYK9MPJPCf6d+fLKMTruThmEyCtHzQ1zLcsrTlUVkmnoXIaHAbfpeLYQwX1tkjs7W11dyzoi6HFvKcdnvX1zNg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxfmt/binding-linux-arm64-gnu": { + "version": "0.61.0", + "resolved": "https://registry.npmjs.org/@oxfmt/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-0.61.0.tgz", + "integrity": "sha512-QUaCNLq2/EC6G5ljOuFanl9Lgw6ZWp4co7rs4+KOMUzbGfA4Lq58FHRjjF9sVIG+93XSbo343MxFATrOU1qctA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxfmt/binding-linux-arm64-musl": { + "version": "0.61.0", + "resolved": "https://registry.npmjs.org/@oxfmt/binding-linux-arm64-musl/-/binding-linux-arm64-musl-0.61.0.tgz", + "integrity": "sha512-S6uvJ6MXnRXl+zTs0CARNDvkE+cymj0EVWEKKsyKnlLlqTyQJBjw5s4D2pSIOZc+S46cy4STefzcr/sm0VzVPA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxfmt/binding-linux-ppc64-gnu": { + "version": "0.61.0", + "resolved": "https://registry.npmjs.org/@oxfmt/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-0.61.0.tgz", + "integrity": "sha512-6VDlRcytvZG6UlSIdAFKDLbppo9tvPxrWzle6vHldYFMeuDPQEfMKrkwezp7FaBq1wik9ra554ZZeRPsyIkFpg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxfmt/binding-linux-riscv64-gnu": { + "version": "0.61.0", + "resolved": "https://registry.npmjs.org/@oxfmt/binding-linux-riscv64-gnu/-/binding-linux-riscv64-gnu-0.61.0.tgz", + "integrity": "sha512-KkBTYbzExpbmn15XjKPLu2fRV2PVlq+KWt+brad5rwIa03vdYoaDRWiS7raHII/dCTR6Ro4UpYUCH4t6lif4WQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxfmt/binding-linux-riscv64-musl": { + "version": "0.61.0", + "resolved": "https://registry.npmjs.org/@oxfmt/binding-linux-riscv64-musl/-/binding-linux-riscv64-musl-0.61.0.tgz", + "integrity": "sha512-69tzIq7sJLVB9dxYYtvMzcSSsnZHSO+U2U19O2RqDqgj6+Q4O7HjSXdaszbcgqzhsUwzSH7z5kWvk8nmf6BHTg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxfmt/binding-linux-s390x-gnu": { + "version": "0.61.0", + "resolved": "https://registry.npmjs.org/@oxfmt/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-0.61.0.tgz", + "integrity": "sha512-Oqi/N0OvtOVXsPKAOOhKgGH3msRYF8BLJaNBbWiupRiKoKVyc8JRKPCfarkQJC+RgP9U8raUKLe+bNwd0HUMiA==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxfmt/binding-linux-x64-gnu": { + "version": "0.61.0", + "resolved": "https://registry.npmjs.org/@oxfmt/binding-linux-x64-gnu/-/binding-linux-x64-gnu-0.61.0.tgz", + "integrity": "sha512-3TKwv/ed4uwJSemAA8P9XcoqETpjQI4waquF9UilhA9Mn/dhr1PdUEXWlL74mtc6ZNfmKPA9+NEJm01nRF8CVA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxfmt/binding-linux-x64-musl": { + "version": "0.61.0", + "resolved": "https://registry.npmjs.org/@oxfmt/binding-linux-x64-musl/-/binding-linux-x64-musl-0.61.0.tgz", + "integrity": "sha512-uFso4u4nLkVSlMCpgjyvWV60Gt7GvDQHnk1mmRxHIkZTMB0ljpUKwCD9FYGgN9H97x2wYl0UwEjgRZaPIuhEhw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxfmt/binding-openharmony-arm64": { + "version": "0.61.0", + "resolved": "https://registry.npmjs.org/@oxfmt/binding-openharmony-arm64/-/binding-openharmony-arm64-0.61.0.tgz", + "integrity": "sha512-keGLkzeOvkMpNmPp4hffXWpfoSsY6e1K8++KXD4mSSfxdvM8q9QUDsYY689TB1k6Co832DZn1MnaaVx6cIBMWQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxfmt/binding-win32-arm64-msvc": { + "version": "0.61.0", + "resolved": "https://registry.npmjs.org/@oxfmt/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-0.61.0.tgz", + "integrity": "sha512-VzsAISkFxmNhJ5LBDEL9VuH6tJsVJMtqYit2LyIUf/HLnsCe4Pg9SMOjjVQzGWt0bnpyfJ94CrqTqcpNZzK+ug==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxfmt/binding-win32-ia32-msvc": { + "version": "0.61.0", + "resolved": "https://registry.npmjs.org/@oxfmt/binding-win32-ia32-msvc/-/binding-win32-ia32-msvc-0.61.0.tgz", + "integrity": "sha512-xv4t7yzwJoYaLB6Zv28B3W+j7brEjsyv50rLTAQgmxJzddce9fAMCxed8dSAkbWES0zz2J29nYK5FaTuD2YBHg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxfmt/binding-win32-x64-msvc": { + "version": "0.61.0", + "resolved": "https://registry.npmjs.org/@oxfmt/binding-win32-x64-msvc/-/binding-win32-x64-msvc-0.61.0.tgz", + "integrity": "sha512-6EZXFkqOwxdDYjIn3TSNnPk3ST5E5GiYd4FiM6UF/mCL/LZSfr6D6UygTfW3R1PCQP2quCKpCEGRlij8E3VYbg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint-tsgolint/darwin-arm64": { + "version": "7.0.2001", + "resolved": "https://registry.npmjs.org/@oxlint-tsgolint/darwin-arm64/-/darwin-arm64-7.0.2001.tgz", + "integrity": "sha512-CUJEdbSZ54+Xy9OXqOhWLTKZKV0BBiV7C2i/ygyVmXtkUNXx5YCzN8DpSSshTAKktoL7S+tnQ/ftFG/i7X896w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@oxlint-tsgolint/darwin-x64": { + "version": "7.0.2001", + "resolved": "https://registry.npmjs.org/@oxlint-tsgolint/darwin-x64/-/darwin-x64-7.0.2001.tgz", + "integrity": "sha512-pXfBb5BqONCcgrXQNUZWXgiYmRSWJzd97S8i41VVOh6ut0tyo+cJ5FKFpczDHxiVNfj/3e7c9B4MtztNdpIVCw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@oxlint-tsgolint/linux-arm64": { + "version": "7.0.2001", + "resolved": "https://registry.npmjs.org/@oxlint-tsgolint/linux-arm64/-/linux-arm64-7.0.2001.tgz", + "integrity": "sha512-roP7zujb/QDPzDwEKsFFpzNHHy91/Y7oX9vQXk78ekyZtcQj1QXDIMH33gjDdHBfRl4K9pZ36xhRgrP4Zr+R8A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@oxlint-tsgolint/linux-x64": { + "version": "7.0.2001", + "resolved": "https://registry.npmjs.org/@oxlint-tsgolint/linux-x64/-/linux-x64-7.0.2001.tgz", + "integrity": "sha512-UDezNqdECVmngu2TPnjaS1YoAmcTaBoI5lV9vk3VahBxoi+I5r9k3iJTT7qZoYWOXTD/7T7bNcwRgrocR6BscQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@oxlint-tsgolint/win32-arm64": { + "version": "7.0.2001", + "resolved": "https://registry.npmjs.org/@oxlint-tsgolint/win32-arm64/-/win32-arm64-7.0.2001.tgz", + "integrity": "sha512-uJZhqB6pdXLuN+AD1F5082byyQti/NPmJA77GtcFlmT2HzRelqbNls3SaIqxpjdFgvSBF9g0yOKGBkGFg7kX8Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@oxlint-tsgolint/win32-x64": { + "version": "7.0.2001", + "resolved": "https://registry.npmjs.org/@oxlint-tsgolint/win32-x64/-/win32-x64-7.0.2001.tgz", + "integrity": "sha512-FkDRm8hx9OwzGQqyWG1tO5QrTLRApff9DzSgpz9QZau37BR8d1VYKOxMLGf6shPZntJFoTwIIJYT68VndYDCog==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@oxlint/binding-android-arm-eabi": { + "version": "1.76.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-android-arm-eabi/-/binding-android-arm-eabi-1.76.0.tgz", + "integrity": "sha512-ZHIE5Zt9AsPDcY4nOlofXt0YfneEeo+QrKMPcPzLf2Z6Q8VtV2W73d7SFJ920WUwyik783u/doKCs3KXdwG+7w==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-android-arm64": { + "version": "1.76.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-android-arm64/-/binding-android-arm64-1.76.0.tgz", + "integrity": "sha512-shm/ngQilHK6bs+ElJWa4oHfNj5vL1Gl/iVEJldTQjpr0/67oSgr0KUpbmcnLig5Fo0v/l6j2567A7TOL89ONA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-darwin-arm64": { + "version": "1.76.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-darwin-arm64/-/binding-darwin-arm64-1.76.0.tgz", + "integrity": "sha512-rvJmrAPKSQ9aWJ6wIS6CK2tJjwzfW0ApQH9qokq6sfDvmHwoyIHxHFMq7z7i7GiV6fdE6s8qvBqWKPTu8RmT6Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-darwin-x64": { + "version": "1.76.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-darwin-x64/-/binding-darwin-x64-1.76.0.tgz", + "integrity": "sha512-U/zYdb7VYKGY6pA9Vd2rYl9O/HlCylcOlb5PGPvVLtg+oLGsk6H3XGKEMHKyqD3nmmtmlmwb/8SwU2vfSAtvMw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-freebsd-x64": { + "version": "1.76.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-freebsd-x64/-/binding-freebsd-x64-1.76.0.tgz", + "integrity": "sha512-WvKG9CAriuo0XNiFzpXjDngUZcRGFNpaK2kLyMUsnJlShxkT96u+BpJQ3KqdQwGOrvI14L6V8bAwXwAYNNY6Jg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-linux-arm-gnueabihf": { + "version": "1.76.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.76.0.tgz", + "integrity": "sha512-qJ5+RH99TqFRq3UCDxkW0zJJu9c+OAHFY72vGlxZLEpuO+MpKo3POgqb8sYipL9KYm8XY6ofb0HsOuvY6hQNqQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-linux-arm-musleabihf": { + "version": "1.76.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-arm-musleabihf/-/binding-linux-arm-musleabihf-1.76.0.tgz", + "integrity": "sha512-PvPCVptkgVARsucgIqFQQcSmJ6xc6GtnVB5bRBekRahTc9eObMtjHfMjy5M+C2tHt5UCMttWM9RuSk/H9NqYeg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-linux-arm64-gnu": { + "version": "1.76.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.76.0.tgz", + "integrity": "sha512-3KeFDx8Bu4HPAXbuHZOr/oHvN+QT+JQhMw/NYPz7Z071xLSsG27Jfh9PIQVEY7hk1I+jr43ExqRIeJ6VKk2yLw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-linux-arm64-musl": { + "version": "1.76.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.76.0.tgz", + "integrity": "sha512-oPFkkKTgl0K/EIg9fQ8oA3IGcI05/Mq1en04iFa41mmNPT+6KEiByVazTOZZJiHMBBrbsns1YJ2e1Scqwzesjw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-linux-ppc64-gnu": { + "version": "1.76.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.76.0.tgz", + "integrity": "sha512-gN7yZ0eqflA5Fhf1wvHxGUltIV3FsvmB1zhNMDEK9vSHhc7E6qg9CuPeBgPZab66Tjzq6w6kHAtNEvnTHf4cyw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-linux-riscv64-gnu": { + "version": "1.76.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-riscv64-gnu/-/binding-linux-riscv64-gnu-1.76.0.tgz", + "integrity": "sha512-S/HqMbn22mQrjtErUxEoS/a55u8kIeXvreIxiJu5G7Le3UecEd6SQZxrDIpuhtgaFnsY/nVra3ytP+pRljDilA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-linux-riscv64-musl": { + "version": "1.76.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-riscv64-musl/-/binding-linux-riscv64-musl-1.76.0.tgz", + "integrity": "sha512-ZIga3097VJZolGZk6SrIAUokIGfRkxRlhiHDUznZptGBfwrhD7pNfD1rzEzsCwvk/1DX0A1bLz+liuNh5QKIVQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-linux-s390x-gnu": { + "version": "1.76.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.76.0.tgz", + "integrity": "sha512-ZGiiA7pFzMJSyMWYZTVlPgbTsx+Vl8ihLGMIujPwaslUF7kIPPWAbVmAlTc+9lWDV+DCiB8Ikixu+lSHeOIIWQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-linux-x64-gnu": { + "version": "1.76.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.76.0.tgz", + "integrity": "sha512-JLiy5WuvEBFTT6ErIFV35SLzi0R7Iri6MKU6dZbTxfIx8pndbbPs3Mj780nMipBFcPkti+okAPOJ9POKkHFEgg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-linux-x64-musl": { + "version": "1.76.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-x64-musl/-/binding-linux-x64-musl-1.76.0.tgz", + "integrity": "sha512-z7lgKQtbo/I1NIe8G5NHLesxJDv0tRSUWTpXKb9Pm3E9nKFKfO4IOSDtFroKgXtOYb0jQbcdH+0wzTyMXVes+A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-openharmony-arm64": { + "version": "1.76.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-openharmony-arm64/-/binding-openharmony-arm64-1.76.0.tgz", + "integrity": "sha512-JOjKymIpb9QcYfEhZsN6h4V9Ivd474W38cNIBRv6bg2TbIvogbMTH0Mg6YWW9TiRDqfcX+/Hyfsbo5vcSE5guQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-win32-arm64-msvc": { + "version": "1.76.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.76.0.tgz", + "integrity": "sha512-pqDWZiwcmByWUEm1NFUBNiT6aentCcaoMWJv0HbXEmuYermJ4sg8ppVrshubYP2MZ6SHccJJcpr6x469PuDFIw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-win32-ia32-msvc": { + "version": "1.76.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-win32-ia32-msvc/-/binding-win32-ia32-msvc-1.76.0.tgz", + "integrity": "sha512-Ba0O659kgMv6pwO3z9PdO+K3aMxQRaw9HnG+e6AtOfgwcKFvYilciQYBoUBmxfQvOCKZe1SwjMkuB542NkuDMQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-win32-x64-msvc": { + "version": "1.76.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.76.0.tgz", + "integrity": "sha512-5qcirPHO8nKfkoowEVWtpAoVTcYDy6g0UT0NGic450Qv8J2NrOqg4uQ8QppRP4MDTC7Xx47lbZnmadTH03CGGA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@sindresorhus/base62": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/base62/-/base62-1.0.0.tgz", + "integrity": "sha512-TeheYy0ILzBEI/CO55CP6zJCSdSWeRtGnHy8U8dWSUH4I68iqTsy7HkMktR4xakThc9jotkPQUXT4ITdbV7cHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@types/esrecurse": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@types/esrecurse/-/esrecurse-4.3.1.tgz", + "integrity": "sha512-xJBAbDifo5hpffDBuHl0Y8ywswbiAp/Wi7Y/GtAgSlZyIABppyurxVueOPE8LUQOxdlgi6Zqce7uoEpqNTeiUw==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/@types/estree": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/@types/lodash": { + "version": "4.17.16", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.16.tgz", + "integrity": "sha512-HX7Em5NYQAXKW+1T+FiuG27NGwzJfCX3s1GjOa7ujxZa52kjJLOr4FUxT+giF6Tgxv1e+/czV/iTtBw27WTU9g==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "24.13.3", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.13.3.tgz", + "integrity": "sha512-Dh8vAsV36ig5wa9OX4pXvMc9D3Veibfw2wix0CUwYODLD8nkj9UsLjASr49nPg+2eKzxhBV+v7L8pXvT4e639Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~7.18.0" + } + }, + "node_modules/@typescript-eslint/types": { + "version": "8.65.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.65.0.tgz", + "integrity": "sha512-JSSwWNy+H0E/01jJEM+hrX6N0OFDzFzeIhHFSAS01tlVaevpG8cFyYRPhS5yjGOvBUx3sqQHVMjCL1CAZZMxBg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript/typescript-aix-ppc64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-aix-ppc64/-/typescript-aix-ppc64-7.0.2.tgz", + "integrity": "sha512-MTKKkWB7p/0E9xi1d1tHtZ5PiLkGEMIq88pK2CubZjOsLtYTLqhgIgi6zepFa+9GHZ6h05NMCkQxGKiPXMxXtQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-darwin-arm64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-darwin-arm64/-/typescript-darwin-arm64-7.0.2.tgz", + "integrity": "sha512-gowzar9MwS/aRWp6f3a4KUqzRjAZjOsmGNCM6LcTgXum+dBfgsBVMN+AgvOCCbguXyick6LJhpBszxMebJ8syA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-darwin-x64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-darwin-x64/-/typescript-darwin-x64-7.0.2.tgz", + "integrity": "sha512-SZ9xZInqApNlNGc9s0W1VSsktYSOe9cFqNOIqmN1Gs8SmkjKZYFt017G4VwPxASInODuAdbTW7sXiFUf893RgA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-freebsd-arm64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-freebsd-arm64/-/typescript-freebsd-arm64-7.0.2.tgz", + "integrity": "sha512-W5NH4y/J0plIIS5b2xvTEkU7JFxyqdMAOgf+Ilhl0vHQXKO5dZoxd+C/jEtq56c4F3wk71RB4BMRQ2XdI+bwYQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-freebsd-x64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-freebsd-x64/-/typescript-freebsd-x64-7.0.2.tgz", + "integrity": "sha512-UMGDx5sTpzNw3WiPebH7l90IWfJggEd+egHt/q6p7/Cm3zqoV7VxkGXt+3DxPIw8CcmvAB0j3sVVfbhX+M4Tpw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-linux-arm": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-arm/-/typescript-linux-arm-7.0.2.tgz", + "integrity": "sha512-gffT3xPz9sR7j/YJExkyPntrI0P2EP9XbOyWzth2/Gs0RstK+90RBcO0ncXoXy/beYll1SXw846Nf2zdnEz0QQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-linux-arm64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-arm64/-/typescript-linux-arm64-7.0.2.tgz", + "integrity": "sha512-Qh4eU4/y3yDjnfjjyPYihMj5/ODIlmt+Bzu17OI+fiSRDW57QmU5SiN63exPRNJPKUzcc1INa1NXdrJ+MqHjUQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-linux-loong64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-loong64/-/typescript-linux-loong64-7.0.2.tgz", + "integrity": "sha512-uEHck9i8hoAzXPiYRib1O7miOnz23SxIeVl6F4LXox+qov1K35jHcEW6VHKvZI+pyvl7fZEP4MCU5LYvIq1GuQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-linux-mips64el": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-mips64el/-/typescript-linux-mips64el-7.0.2.tgz", + "integrity": "sha512-R4KvAMnE43W5Qeqb0Ly56O3mWMWIAgsMyz36DCaycd5nbg/9kzm0liw3JocfRqyJY0KPmzFjbswozXyW0DnIYA==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-linux-ppc64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-ppc64/-/typescript-linux-ppc64-7.0.2.tgz", + "integrity": "sha512-DORx5b3sd/4S7eayxm4FQv+A7CrkUIGRaHiwI8oiHTAI1fAPWhF4J0vAlkC8biAlHSVVwxMQ3tjZ2/DVbnQiiA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-linux-riscv64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-riscv64/-/typescript-linux-riscv64-7.0.2.tgz", + "integrity": "sha512-wf0jqEDOjrPRnKwYRyyJDRo11KMbvMFrU+q4zqKyChODBzvlkbhNQfKvLxQCcwTpdDaXSHZTVuh0JoCrKCUMHQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-linux-s390x": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-s390x/-/typescript-linux-s390x-7.0.2.tgz", + "integrity": "sha512-IkwJc3L7yhytWd/ewjyxNDfOmswCm9GWMJT/ue/dU4aZNbwZeYAetq42VyLmsmSjvoX7z74X6ZaYCtzAr0EuGw==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-linux-x64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-linux-x64/-/typescript-linux-x64-7.0.2.tgz", + "integrity": "sha512-EYdf2cNg7rgCWJnxCdJ+F3V39O8ihb37eHAu1LK8oAFizgTQbPOK7zHHXbPt8rX24COqODXeI3sIf0fCXG7H/A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-netbsd-arm64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-netbsd-arm64/-/typescript-netbsd-arm64-7.0.2.tgz", + "integrity": "sha512-+polYF4MF04aPpO5FTkHran9yUQDSXqy5GiSDKpsll5jy3l3+g9QLhpf39T+ePtefhXLOGrLl0QIjkQP6VnelA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-netbsd-x64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-netbsd-x64/-/typescript-netbsd-x64-7.0.2.tgz", + "integrity": "sha512-8YIT0EHM/3dq10ZOVF/A7pc/YSMtbcecct4rWtexrnSCHOPcpC2KTLXfTCR6vDpnSiY12heNb1GiN/wu+T/FyA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-openbsd-arm64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-openbsd-arm64/-/typescript-openbsd-arm64-7.0.2.tgz", + "integrity": "sha512-APT8+ClYnuYm1u9+kgGXoMj2VzWzcymwh2gNSQVySHfkRDGOTVkoWLjCmOQSaO+PoqQ57B0flRp9SA+7GnnkzQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-openbsd-x64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-openbsd-x64/-/typescript-openbsd-x64-7.0.2.tgz", + "integrity": "sha512-yX7s+Q0Dln0Dt9tEzZsAjXXR/+ytBM7AlglaqyeMPxQszJ1JhlJdZ6jLA+IzldHtflX81em7lDao1xXu+aRRkg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-sunos-x64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-sunos-x64/-/typescript-sunos-x64-7.0.2.tgz", + "integrity": "sha512-dLJDGaLZ1D4HPQn62u1n8mBDkJREwMsAkCdkwd4Ieqw+x3TUyTsqY0YiBCtE6H6OzzgGk3iuZ3vFWRS+E8/d1g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-win32-arm64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-win32-arm64/-/typescript-win32-arm64-7.0.2.tgz", + "integrity": "sha512-Gyl1Vy6OsWesLzmq+EP0Fb7b4Nid5232AvcA2SFcdYreldpNtYFFofPjnt62y9hQy7VTaZp65ICJjuAQRaVcIQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/@typescript/typescript-win32-x64": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@typescript/typescript-win32-x64/-/typescript-win32-x64-7.0.2.tgz", + "integrity": "sha512-0BQ3HkAHHlKLSp1qRvf3SUhGpGsDuhB/jgFw75guyqbxJqEaS0Cw/VFO8i2nHglJUzQCRtMMR/IBAKE3ETMC4g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=16.20.0" + } + }, + "node_modules/acorn": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.18.0.tgz", + "integrity": "sha512-lGq+9yr1/GuAWaVYIHRjvvySG5/4VfKIvC8EWxStPdcDh/Ka7FG3twP6v4d5BkravUilhIAsG4Qj83t02LWUPQ==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/aggregate-error": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", + "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", + "dev": true, + "license": "MIT", + "dependencies": { + "clean-stack": "^2.0.0", + "indent-string": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/ajv": { + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.15.0.tgz", + "integrity": "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "license": "ISC", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/are-docs-informative": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/are-docs-informative/-/are-docs-informative-0.0.2.tgz", + "integrity": "sha512-ixiS0nLNNG5jNQzgZJNoUpBKdo9yTYZMGJ+QgT2jmjR7G7+QHRCc4v6LQ3NgE7EBJq+o0ams3waJwkrlBom8Ig==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14" + } + }, + "node_modules/array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/brace-expansion": { + "version": "5.0.9", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.9.tgz", + "integrity": "sha512-ScQ4IuvIEF1TMlP7Zt+vjJ//9zlPb2SDcxWxM3bk8s6t6GGdJ7KO1dCcTidOPJKePW30LE/2cT7wCyPho9/Wxg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/clean-stack": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", + "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/comment-parser": { + "version": "1.4.7", + "resolved": "https://registry.npmjs.org/comment-parser/-/comment-parser-1.4.7.tgz", + "integrity": "sha512-0h+uSNtQGW3D98eQt3jJ8L06Fves8hncB4V/PKdw/Qb8Hnk19VaKuTr55UNRYiSoVa7WwrFls+rh3ux9agmkeQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 12.0.0" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/del": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/del/-/del-6.1.1.tgz", + "integrity": "sha512-ua8BhapfP0JUJKC/zV9yHHDW/rDoDxP4Zhn3AkA6/xT6gY7jYXJiaeyBZznYVujhZZET+UgcbZiQ7sN3WqcImg==", + "dev": true, + "license": "MIT", + "dependencies": { + "globby": "^11.0.1", + "graceful-fs": "^4.2.4", + "is-glob": "^4.0.1", + "is-path-cwd": "^2.2.0", + "is-path-inside": "^3.0.2", + "p-map": "^4.0.0", + "rimraf": "^3.0.2", + "slash": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/empathic": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/empathic/-/empathic-2.0.1.tgz", + "integrity": "sha512-YGRs8knHhKHVShLkFET/rWAU8kmHbOV5LwN938RHI0pljAJ1Gf6SzXsSmRaEzcXTtOOmVqJ5+WtQPL5uigY50Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14" + } + }, + "node_modules/esbuild": { + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.1.tgz", + "integrity": "sha512-HrJrvZv5ayxBzPfwphOoNzkzOIIlifzk0KJrGK2c8R4+LKpMtpYLQeUdjnwjWv/LZlkH2laZk+4w78pi99D4Vw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.28.1", + "@esbuild/android-arm": "0.28.1", + "@esbuild/android-arm64": "0.28.1", + "@esbuild/android-x64": "0.28.1", + "@esbuild/darwin-arm64": "0.28.1", + "@esbuild/darwin-x64": "0.28.1", + "@esbuild/freebsd-arm64": "0.28.1", + "@esbuild/freebsd-x64": "0.28.1", + "@esbuild/linux-arm": "0.28.1", + "@esbuild/linux-arm64": "0.28.1", + "@esbuild/linux-ia32": "0.28.1", + "@esbuild/linux-loong64": "0.28.1", + "@esbuild/linux-mips64el": "0.28.1", + "@esbuild/linux-ppc64": "0.28.1", + "@esbuild/linux-riscv64": "0.28.1", + "@esbuild/linux-s390x": "0.28.1", + "@esbuild/linux-x64": "0.28.1", + "@esbuild/netbsd-arm64": "0.28.1", + "@esbuild/netbsd-x64": "0.28.1", + "@esbuild/openbsd-arm64": "0.28.1", + "@esbuild/openbsd-x64": "0.28.1", + "@esbuild/openharmony-arm64": "0.28.1", + "@esbuild/sunos-x64": "0.28.1", + "@esbuild/win32-arm64": "0.28.1", + "@esbuild/win32-ia32": "0.28.1", + "@esbuild/win32-x64": "0.28.1" + } + }, + "node_modules/esbuild-node-externals": { + "version": "1.23.1", + "resolved": "https://registry.npmjs.org/esbuild-node-externals/-/esbuild-node-externals-1.23.1.tgz", + "integrity": "sha512-aU/Bxaq9KU5SUnsCVP0a2Kj74qZz0QpRH2qvcQ4tRQ7yGYcmzUPOdXXA2hd7o55OjChduOwsFYP5EPJH01gn2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "empathic": "^2.0.0" + }, + "engines": { + "node": ">=12" + }, + "peerDependencies": { + "esbuild": "0.12 - 0.28" + } + }, + "node_modules/esbuild-plugin-clean": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/esbuild-plugin-clean/-/esbuild-plugin-clean-1.0.1.tgz", + "integrity": "sha512-ul606g0wX6oeobBgi3EqpZtCBCwNwCDivvnshsNS5pUsRylKoxUnDqK0ZIyPinlMbP6s8Opc9y2zOeY1Plhe8Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.1.2", + "del": "^6.0.0" + }, + "peerDependencies": { + "esbuild": ">= 0.14.0" + } + }, + "node_modules/esbuild-plugin-copy": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/esbuild-plugin-copy/-/esbuild-plugin-copy-2.1.1.tgz", + "integrity": "sha512-Bk66jpevTcV8KMFzZI1P7MZKZ+uDcrZm2G2egZ2jNIvVnivDpodZI+/KnpL3Jnap0PBdIHU7HwFGB8r+vV5CVw==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^4.1.2", + "chokidar": "^3.5.3", + "fs-extra": "^10.0.1", + "globby": "^11.0.3" + }, + "peerDependencies": { + "esbuild": ">= 0.14.0" + } + }, + "node_modules/esbuild-plugin-copy/node_modules/fs-extra": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", + "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "10.8.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-10.8.0.tgz", + "integrity": "sha512-nuKKvN+oIBO0koN7Tm7dlkmnkc21mtt0QJLwAKzjLq14y6lRTdVG36MZHJ8eQHwdJMwZbQNMlPOYedMq/oVJvQ==", + "dev": true, + "license": "MIT", + "peer": true, + "workspaces": [ + "packages/*" + ], + "dependencies": { + "@eslint-community/eslint-utils": "^4.8.0", + "@eslint-community/regexpp": "^4.12.2", + "@eslint/config-array": "^0.23.5", + "@eslint/config-helpers": "^0.7.0", + "@eslint/core": "^1.2.1", + "@eslint/plugin-kit": "^0.7.2", + "@humanfs/node": "^0.16.6", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.4.2", + "@types/estree": "^1.0.6", + "ajv": "^6.14.0", + "cross-spawn": "^7.0.6", + "debug": "^4.3.2", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^9.1.2", + "eslint-visitor-keys": "^5.0.1", + "espree": "^11.2.0", + "esquery": "^1.7.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^8.0.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "minimatch": "^10.2.5", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } + } + }, + "node_modules/eslint-plugin-jsdoc": { + "version": "63.3.2", + "resolved": "https://registry.npmjs.org/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-63.3.2.tgz", + "integrity": "sha512-5B3oO23iqbNJC/ru7uqIY80wmY66De1Q0+5kXQGHuLrGze/rL0Zw/YktMcqgYQ+kdHmgvY1q5TpRgl3yZMLmhQ==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@es-joy/jsdoccomment": "~0.91.0", + "@es-joy/resolve.exports": "1.2.0", + "are-docs-informative": "^0.0.2", + "comment-parser": "1.4.7", + "debug": "^4.4.3", + "escape-string-regexp": "^4.0.0", + "espree": "^11.2.0", + "esquery": "^1.7.0", + "html-entities": "^2.6.0", + "object-deep-merge": "^2.0.1", + "parse-imports-exports": "^0.2.4", + "semver": "^7.8.5", + "spdx-expression-parse": "^5.0.0", + "to-valid-identifier": "^1.0.0" + }, + "engines": { + "node": "^22.13.0 || >=24" + }, + "peerDependencies": { + "eslint": "^7.0.0 || ^8.0.0 || ^9.0.0 || ^10.0.0" + } + }, + "node_modules/eslint-scope": { + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-9.1.2.tgz", + "integrity": "sha512-xS90H51cKw0jltxmvmHy2Iai1LIqrfbw57b79w/J7MfvDfkIkFZ+kj6zC3BjtUwh150HsSSdxXZcsuv72miDFQ==", + "dev": true, + "license": "BSD-2-Clause", + "peer": true, + "dependencies": { + "@types/esrecurse": "^4.3.1", + "@types/estree": "^1.0.8", + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", + "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "license": "ISC", + "peer": true, + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/espree": { + "version": "11.2.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-11.2.0.tgz", + "integrity": "sha512-7p3DrVEIopW1B1avAGLuCSh1jubc01H2JHc8B4qqGblmg5gI9yumBgACjWo4JlIc04ufug4xJ3SQI8HkS/Rgzw==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.16.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^5.0.1" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esquery": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz", + "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "license": "BSD-2-Clause", + "peer": true, + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "license": "BSD-2-Clause", + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/fast-glob": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.8" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/fastq": { + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz", + "integrity": "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==", + "dev": true, + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/file-entry-cache": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "flat-cache": "^4.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.4" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/flatted": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.3.tgz", + "integrity": "sha512-/zipXxyO6rGvuNGDiULY9MvEGSkb2gaG4GGH4ygMi0ZZzyMHdUZBmntJmx5x1G2VuPytCwGN4xsJP6cw+sK+vQ==", + "dev": true, + "license": "ISC", + "peer": true + }, + "node_modules/fs-extra": { + "version": "11.4.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.4.0.tgz", + "integrity": "sha512-EQsFzMUJkCKGr1ePqlYADkIUmHW1s3ZXr5Yqy6wbGrfUCphpl2maM/kyOIRA2HpP3AaFQTZXD4ldjek+nccddA==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=14.14" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true, + "license": "ISC" + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "dev": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/html-entities": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-2.6.0.tgz", + "integrity": "sha512-kig+rMn/QOVRvr7c86gQ8lWXq+Hkv6CbAH1hLu+RG338StTpE8Z0b44SDVaqVu7HGKf27frdmUYEs9hTUX/cLQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/mdevils" + }, + { + "type": "patreon", + "url": "https://patreon.com/mdevils" + } + ], + "license": "MIT" + }, + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "dev": true, + "license": "ISC", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "license": "MIT", + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-path-cwd": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-2.2.0.tgz", + "integrity": "sha512-w942bTcih8fdJPJmQHFzkS76NEP8Kzzvmw92cXsazb8intwLqPibPPdXf4ANdKV3rYMuuQYGIWtvz9JilB3NFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC", + "peer": true + }, + "node_modules/jsdoc-type-pratt-parser": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/jsdoc-type-pratt-parser/-/jsdoc-type-pratt-parser-8.0.0.tgz", + "integrity": "sha512-uQu/fXVqVaMg6gM8/E5G5+eygVcZ1NV0Z51CvqhNa2bDWxvHMl484ETr6vph4oPyC+KUcbP/w2W2pewfCiR9aQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/jsonfile": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.1.tgz", + "integrity": "sha512-zwOTdL3rFQ/lRdBnntKVOX6k5cKJwEc1HdilT71BWEu7J41gXIB2MRp+vxduPSwZJPWBxEzv4yH1wYLJGUHX4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/lint-staged": { + "version": "17.2.0", + "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-17.2.0.tgz", + "integrity": "sha512-FchGnFe4i4B1C/a35SPU9bNGPEHSC1+1iV0plLjzBmKVe9klZrlRfSgK6Cw4VeHyqOXbJUXP0vON61uRftNQ0A==", + "dev": true, + "license": "MIT", + "dependencies": { + "picomatch": "^4.0.5", + "string-argv": "^0.3.2", + "tinyexec": "^1.2.4" + }, + "bin": { + "lint-staged": "bin/lint-staged.js" + }, + "engines": { + "node": ">=22.22.1" + }, + "funding": { + "url": "https://opencollective.com/lint-staged" + }, + "optionalDependencies": { + "yaml": "^2.9.0" + } + }, + "node_modules/lint-staged/node_modules/picomatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.5.tgz", + "integrity": "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash": { + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz", + "integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==", + "license": "MIT" + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/minimatch": { + "version": "10.2.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.6.tgz", + "integrity": "sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.8" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-deep-merge": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/object-deep-merge/-/object-deep-merge-2.0.1.tgz", + "integrity": "sha512-aKttDKcU3pyZqKcCkDhsMn70WmZFG2JGDQLP9EcLyTSIFQRCPWLAmBZRLJnrVUrhPG1jETEEbfdgbNtJf1LyMg==", + "dev": true, + "license": "MIT" + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/oxfmt": { + "version": "0.61.0", + "resolved": "https://registry.npmjs.org/oxfmt/-/oxfmt-0.61.0.tgz", + "integrity": "sha512-DxdHBEMYpcEnHoUHjjOigUqV2TYKsvxLwUPXnVYBjgFdqrcQ/91OtwubtZ2PUodCs3sStI8R5Qw3fKNGK4e8wQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinypool": "2.1.0" + }, + "bin": { + "oxfmt": "bin/oxfmt" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/sponsors/Boshen" + }, + "optionalDependencies": { + "@oxfmt/binding-android-arm-eabi": "0.61.0", + "@oxfmt/binding-android-arm64": "0.61.0", + "@oxfmt/binding-darwin-arm64": "0.61.0", + "@oxfmt/binding-darwin-x64": "0.61.0", + "@oxfmt/binding-freebsd-x64": "0.61.0", + "@oxfmt/binding-linux-arm-gnueabihf": "0.61.0", + "@oxfmt/binding-linux-arm-musleabihf": "0.61.0", + "@oxfmt/binding-linux-arm64-gnu": "0.61.0", + "@oxfmt/binding-linux-arm64-musl": "0.61.0", + "@oxfmt/binding-linux-ppc64-gnu": "0.61.0", + "@oxfmt/binding-linux-riscv64-gnu": "0.61.0", + "@oxfmt/binding-linux-riscv64-musl": "0.61.0", + "@oxfmt/binding-linux-s390x-gnu": "0.61.0", + "@oxfmt/binding-linux-x64-gnu": "0.61.0", + "@oxfmt/binding-linux-x64-musl": "0.61.0", + "@oxfmt/binding-openharmony-arm64": "0.61.0", + "@oxfmt/binding-win32-arm64-msvc": "0.61.0", + "@oxfmt/binding-win32-ia32-msvc": "0.61.0", + "@oxfmt/binding-win32-x64-msvc": "0.61.0" + }, + "peerDependencies": { + "svelte": "^5.0.0", + "vite-plus": "*" + }, + "peerDependenciesMeta": { + "svelte": { + "optional": true + }, + "vite-plus": { + "optional": true + } + } + }, + "node_modules/oxlint": { + "version": "1.76.0", + "resolved": "https://registry.npmjs.org/oxlint/-/oxlint-1.76.0.tgz", + "integrity": "sha512-6QoFioEU4fNdiUx/2Eo6TRd6NG7H7njnRCz8rhB66cZmMHDTqcm1Rjvl8Wry+ZTQMBAmyb4Mlf62Mk5X+eHSOw==", + "dev": true, + "license": "MIT", + "bin": { + "oxlint": "bin/oxlint" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/sponsors/Boshen" + }, + "optionalDependencies": { + "@oxlint/binding-android-arm-eabi": "1.76.0", + "@oxlint/binding-android-arm64": "1.76.0", + "@oxlint/binding-darwin-arm64": "1.76.0", + "@oxlint/binding-darwin-x64": "1.76.0", + "@oxlint/binding-freebsd-x64": "1.76.0", + "@oxlint/binding-linux-arm-gnueabihf": "1.76.0", + "@oxlint/binding-linux-arm-musleabihf": "1.76.0", + "@oxlint/binding-linux-arm64-gnu": "1.76.0", + "@oxlint/binding-linux-arm64-musl": "1.76.0", + "@oxlint/binding-linux-ppc64-gnu": "1.76.0", + "@oxlint/binding-linux-riscv64-gnu": "1.76.0", + "@oxlint/binding-linux-riscv64-musl": "1.76.0", + "@oxlint/binding-linux-s390x-gnu": "1.76.0", + "@oxlint/binding-linux-x64-gnu": "1.76.0", + "@oxlint/binding-linux-x64-musl": "1.76.0", + "@oxlint/binding-openharmony-arm64": "1.76.0", + "@oxlint/binding-win32-arm64-msvc": "1.76.0", + "@oxlint/binding-win32-ia32-msvc": "1.76.0", + "@oxlint/binding-win32-x64-msvc": "1.76.0" + }, + "peerDependencies": { + "oxlint-tsgolint": ">=7.0.2001", + "vite-plus": "*" + }, + "peerDependenciesMeta": { + "oxlint-tsgolint": { + "optional": true + }, + "vite-plus": { + "optional": true + } + } + }, + "node_modules/oxlint-tsgolint": { + "version": "7.0.2001", + "resolved": "https://registry.npmjs.org/oxlint-tsgolint/-/oxlint-tsgolint-7.0.2001.tgz", + "integrity": "sha512-KjK/XLcXr1DSyonKhsuFqJRiuKqcyG9j3LJ8nkOsrLzGvodBPqzHOKauy10asLMDI0sUpvb+1sxlzff3udZvfg==", + "dev": true, + "license": "MIT", + "bin": { + "tsgolint": "bin/tsgolint.js" + }, + "optionalDependencies": { + "@oxlint-tsgolint/darwin-arm64": "7.0.2001", + "@oxlint-tsgolint/darwin-x64": "7.0.2001", + "@oxlint-tsgolint/linux-arm64": "7.0.2001", + "@oxlint-tsgolint/linux-x64": "7.0.2001", + "@oxlint-tsgolint/win32-arm64": "7.0.2001", + "@oxlint-tsgolint/win32-x64": "7.0.2001" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-map": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", + "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "aggregate-error": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parse-imports-exports": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/parse-imports-exports/-/parse-imports-exports-0.2.4.tgz", + "integrity": "sha512-4s6vd6dx1AotCx/RCI2m7t7GCh5bDRUtGNvRfHSP2wbBQdMi67pPe7mtzmgwcaQ8VKK/6IB7Glfyu3qdZJPybQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "parse-statements": "1.0.11" + } + }, + "node_modules/parse-statements": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/parse-statements/-/parse-statements-1.0.11.tgz", + "integrity": "sha512-HlsyYdMBnbPQ9Jr/VgJ1YF4scnldvJpJxCVx6KgqPL4dxppsWrJHCIIxQXMJrqGnsRkNPATbeMJ8Yxu7JMsYcA==", + "dev": true, + "license": "MIT" + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/picomatch": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/reserved-identifiers": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/reserved-identifiers/-/reserved-identifiers-1.2.0.tgz", + "integrity": "sha512-yE7KUfFvaBFzGPs5H3Ops1RevfUEsDc5Iz65rOwWg4lE8HJSYtle77uul3+573457oHvBKuHYDl/xqUkKpEEdw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/reusify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", + "dev": true, + "license": "ISC", + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/semver": { + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/simple-git-hooks": { + "version": "2.13.1", + "resolved": "https://registry.npmjs.org/simple-git-hooks/-/simple-git-hooks-2.13.1.tgz", + "integrity": "sha512-WszCLXwT4h2k1ufIXAgsbiTOazqqevFCIncOuUBZJ91DdvWcC5+OFkluWRQPrcuSYd8fjq+o2y1QfWqYMoAToQ==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "simple-git-hooks": "cli.js" + } + }, + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/spdx-exceptions": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.5.0.tgz", + "integrity": "sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w==", + "dev": true, + "license": "CC-BY-3.0" + }, + "node_modules/spdx-expression-parse": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-5.0.0.tgz", + "integrity": "sha512-vngmw3Rgn+o2arXNbnZaj5UtOEBuWBfvaI+Wc8GFfykIhA5/vdK9/Sp/XkLv63dykz2rxKDvKEHupF5P0FORcQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/spdx-license-ids": { + "version": "3.0.23", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.23.tgz", + "integrity": "sha512-CWLcCCH7VLu13TgOH+r8p1O/Znwhqv/dbb6lqWy67G+pT1kHmeD/+V36AVb/vq8QMIQwVShJ6Ssl5FPh0fuSdw==", + "dev": true, + "license": "CC0-1.0" + }, + "node_modules/string-argv": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/string-argv/-/string-argv-0.3.2.tgz", + "integrity": "sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.6.19" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/tinyexec": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.2.4.tgz", + "integrity": "sha512-SHf/r48b7vOrjve9PxJo3MN5v5yuyjHvdUcrQffT3WXMUfnGmHDVbC4k3sHJaJTgZCwpUplIaAo5ANtMyp3YHg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/tinypool": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-2.1.0.tgz", + "integrity": "sha512-Pugqs6M0m7Lv1I7FtxN4aoyToKg1C4tu+/381vH35y8oENM/Ai7f7C4StcoK4/+BSw9ebcS8jRiVrORFKCALLw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^20.0.0 || >=22.0.0" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/to-valid-identifier": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/to-valid-identifier/-/to-valid-identifier-1.0.0.tgz", + "integrity": "sha512-41wJyvKep3yT2tyPqX/4blcfybknGB4D+oETKLs7Q76UiPqRpUJK3hr1nxelyYO0PHKVzJwlu0aCeEAsGI6rpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sindresorhus/base62": "^1.0.0", + "reserved-identifiers": "^1.0.0" + }, + "engines": { + "node": ">=20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/typescript": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-7.0.2.tgz", + "integrity": "sha512-8FYau96o3NKOhbjKi/qNvG/W5jhzxkbdm5sj9AbZ/5T5sWqn3hJgLfGx27sRKZWTvyzCP8dLRBTf5tBTSRVUNA==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc" + }, + "engines": { + "node": ">=16.20.0" + }, + "optionalDependencies": { + "@typescript/typescript-aix-ppc64": "7.0.2", + "@typescript/typescript-darwin-arm64": "7.0.2", + "@typescript/typescript-darwin-x64": "7.0.2", + "@typescript/typescript-freebsd-arm64": "7.0.2", + "@typescript/typescript-freebsd-x64": "7.0.2", + "@typescript/typescript-linux-arm": "7.0.2", + "@typescript/typescript-linux-arm64": "7.0.2", + "@typescript/typescript-linux-loong64": "7.0.2", + "@typescript/typescript-linux-mips64el": "7.0.2", + "@typescript/typescript-linux-ppc64": "7.0.2", + "@typescript/typescript-linux-riscv64": "7.0.2", + "@typescript/typescript-linux-s390x": "7.0.2", + "@typescript/typescript-linux-x64": "7.0.2", + "@typescript/typescript-netbsd-arm64": "7.0.2", + "@typescript/typescript-netbsd-x64": "7.0.2", + "@typescript/typescript-openbsd-arm64": "7.0.2", + "@typescript/typescript-openbsd-x64": "7.0.2", + "@typescript/typescript-sunos-x64": "7.0.2", + "@typescript/typescript-win32-arm64": "7.0.2", + "@typescript/typescript-win32-x64": "7.0.2" + } + }, + "node_modules/undici-types": { + "version": "7.18.2", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz", + "integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==", + "dev": true, + "license": "MIT" + }, + "node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "license": "BSD-2-Clause", + "peer": true, + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "peer": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/yaml": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.9.0.tgz", + "integrity": "sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA==", + "dev": true, + "license": "ISC", + "optional": true, + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14.6" + }, + "funding": { + "url": "https://github.com/sponsors/eemeli" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..52e01b2 --- /dev/null +++ b/package.json @@ -0,0 +1,100 @@ +{ + "name": "Db2-zOS", + "version": "0.1.0", + "type": "commonjs", + "author": "hackolade", + "engines": { + "hackolade": "7.7.10", + "hackoladePlugin": "1.2.0", + "node": ">=24.0.0" + }, + "contributes": { + "target": { + "applicationTarget": "Db2-zOS", + "title": "Db2 for z/OS", + "versions": [ + "v13" + ] + }, + "features": { + "nestedCollections": false, + "disablePatternField": true, + "disableMultipleTypes": true, + "enableForwardEngineering": true, + "enableReverseEngineering": { + "jsonDocument": true, + "jsonSchema": true, + "ddl": true, + "xsd": true, + "excel": true, + "plugin": false + }, + "disableChoices": true, + "enableJsonType": true, + "useJsonTypesWithComplexTypes": true, + "reverseSchemaIntoOneColumn": true, + "disableDenormalization": true, + "enableComplexTypesNormalization": true, + "generateAssociativeEntitiesForManyToManyRelationships": true, + "restrictNestedFieldsAsPrimaryKey": false, + "views": { + "enabled": true, + "viewLevel": "model", + "disablePipelines": true, + "includeViews": true + }, + "relationships": { + "compositeRelationships": { + "allowRelationshipsByProperties": [ + "primaryKey", + "unique", + "compositeUniqueKey", + "compositePrimaryKey" + ] + } + }, + "FEScriptCommentsSupported": true, + "enableFetchSystemEntitiesCheckbox": true, + "discoverRelationships": true, + "enableKeysMultipleAbrr": true + } + }, + "description": "Hackolade plugin for IBM Db2 for z/OS", + "disabled": false, + "simple-git-hooks": { + "pre-commit": "npx lint-staged --config lint-staged.config.js", + "pre-push": "npm run check" + }, + "scripts": { + "check": "npm run format:check && npm run lint && npm run types:check && npm run bundle:check", + "types:check": "tsc --noEmit", + "lint": "oxlint --type-aware --deny-warnings .", + "bundle:check": "node esbuild.package.js --write=false", + "package": "node esbuild.package.js", + "format": "oxfmt", + "format:check": "oxfmt --check", + "prepare": "simple-git-hooks" + }, + "devDependencies": { + "@hackolade/hck-esbuild-plugins-pack": "0.0.1", + "@types/lodash": "4.17.16", + "@types/node": "24.13.3", + "esbuild": "0.28.1", + "esbuild-node-externals": "1.23.1", + "esbuild-plugin-clean": "1.0.1", + "esbuild-plugin-copy": "2.1.1", + "eslint-plugin-jsdoc": "63.3.2", + "lint-staged": "17.2.0", + "oxfmt": "0.61.0", + "oxlint": "1.76.0", + "oxlint-tsgolint": "7.0.2001", + "simple-git-hooks": "2.13.1", + "typescript": "7.0.2" + }, + "overrides": { + "minimatch": "10.2.6" + }, + "dependencies": { + "lodash": "4.18.1" + } +} diff --git a/polyglot/adapter.json b/polyglot/adapter.json new file mode 100644 index 0000000..a67e412 --- /dev/null +++ b/polyglot/adapter.json @@ -0,0 +1,89 @@ +/** + * + * { + * "add": { + * "entity": [], + * "container": [], + * "model": [], + * "view": [], + * "field": { + * "": [] + * } + * }, + * "delete": { + * "entity": [], + * "container": [], + * "model": [], + * "view": [], + * "field": { + * "": [] + * } + * }, + * "modify": { + * "entity": [ + * { + * "from": { }, + * "to": { } + * } + * ], + * "container": [], + * "model": [], + * "view": [], + * "field": [] + * }, + * } + */ +{ + "modify": { + "field": [ + { + "from": { + "type": "varchar", + "hasMaxLength": true + }, + "to": { + "length": 32704 + } + }, + { + "from": { + "type": "varchar", + "mode": "char", + "hasMaxLength": true + }, + "to": { + "length": 255 + } + }, + { + "from": { + "type": "nvarchar", + "hasMaxLength": true + }, + "to": { + "length": 32704 + } + }, + { + "from": { + "type": "nvarchar", + "mode": "nchar", + "hasMaxLength": true + }, + "to": { + "length": 255 + } + }, + { + "from": { + "type": "binary", + "mode": "varbinary", + "hasMaxLength": true + }, + "to": { + "length": 32704 + } + } + ] + } +} diff --git a/polyglot/convertAdapter.json b/polyglot/convertAdapter.json new file mode 100644 index 0000000..dbc6c98 --- /dev/null +++ b/polyglot/convertAdapter.json @@ -0,0 +1,86 @@ +/** + * + * { + * "add": { + * "entity": [], + * "container": [], + * "model": [], + * "view": [], + * "field": { + * "": [] + * } + * }, + * "delete": { + * "entity": [], + * "container": [], + * "model": [], + * "view": [], + * "field": { + * "": [] + * } + * }, + * "modify": { + * "entity": [ + * { + * "from": { }, + * "to": { } + * } + * ], + * "container": [], + * "model": [], + * "view": [], + * "field": [] + * }, + * } + */ +{ + "modify": { + "field": [ + { + "from": { + "mode": "varchar", + "length": 32704 + }, + "to": { + "hasMaxLength": true + } + }, + { + "from": { + "mode": "nvarchar", + "length": 32704 + }, + "to": { + "hasMaxLength": true + } + }, + { + "from": { + "mode": "varbinary", + "length": 32704 + }, + "to": { + "hasMaxLength": true + } + }, + { + "from": { + "mode": "char", + "length": 255 + }, + "to": { + "hasMaxLength": true + } + }, + { + "from": { + "mode": "nchar", + "length": 255 + }, + "to": { + "hasMaxLength": true + } + } + ] + } +} diff --git a/properties_pane/container_level/containerLevelConfig.json b/properties_pane/container_level/containerLevelConfig.json new file mode 100644 index 0000000..edf27b9 --- /dev/null +++ b/properties_pane/container_level/containerLevelConfig.json @@ -0,0 +1,210 @@ +/* +* Copyright © 2016-2024 by IntegrIT S.A. dba Hackolade. All rights reserved. +* +* The copyright to the computer software herein is the property of IntegrIT S.A. +* The software may be used and/or copied only with the written permission of +* IntegrIT S.A. or in accordance with the terms and conditions stipulated in +* the agreement/contract under which the software has been supplied. + + +In order to define custom properties for any object's properties pane, you may copy/paste from the following, +making sure that you maintain a proper JSON format. + + { + "propertyName": "Simple text", + "propertyKeyword": "simpletextProp", + "propertyType": "text" + }, + { + "propertyName": "Text area", + "propertyKeyword": "textareaProp", + "propertyTooltip": "Popup for multi-line text entry", + "propertyType": "details", + "template": "textarea" + }, + { + "propertyName": "Dropdown selection", + "propertyKeyword": "dropdownProp", + "propertyTooltip": "Select from list of options", + "propertyType": "select", + "options": [ + "Option 1", + "Option 2", + "Option 3", + "Option 4" + ] + }, + { + "propertyName": "Checkbox", + "propertyKeyword": "checkboxProp", + "propertyType": "checkbox" + }, + { + "propertyName": "Group", + "propertyKeyword": "grpProp", + "propertyType": "group", + "propertyTooltip": "", + "structure": [ + { + "propertyName": "Simple Grp Text", + "propertyKeyword": "simpleGrpText", + "propertyTooltip": "", + "propertyType": "text" + }, + { + "propertyName": "Group Number", + "propertyKeyword": "grpNumber", + "propertyType": "numeric", + "valueType": "number", + "allowNegative": false + } + ] + }, +// “groupInput” can have the following states - 0 items, 1 item, and many items. +// “blockInput” has only 2 states - 0 items or 1 item. +// This gives us an easy way to represent it as an object and not as an array internally which is beneficial for processing +// and forward-engineering in particular. + { + "propertyName": "Block", + "propertyType": "block", + "propertyKeyword": "grpProp", + "propertyTooltip": "", + "structure": [ + { + "propertyName": "Simple Grp Text", + "propertyKeyword": "simpleGrpText", + "propertyTooltip": "", + "propertyType": "text" + }, + { + "propertyName": "Group Number", + "propertyKeyword": "grpNumber", + "propertyType": "numeric", + "valueType": "number", + "allowNegative": false + } + ] + }, + { + "propertyName": "Field List", + "propertyKeyword": "keyList", + "propertyType": "fieldList", + "template": "orderedList" + }, + { + "propertyName": "List with attribute", + "propertyKeyword": "keyListOrder", + "propertyType": "fieldList", + "template": "orderedList", + "attributeList": [ + "ascending", + "descending" + ] + } + +*/ +[ + { + "lowerTab": "Details", + "structure": [ + { + "propertyName": "Comments", + "propertyKeyword": "description", + "shouldValidate": false, + "propertyTooltip": "comments", + "addTimestampButton": false, + "propertyType": "details", + "template": "codeEditor", + "templateOptions": { + "editorDialect": "markdown" + } + }, + { + "propertyName": "Custom scripts", + "propertyType": "block", + "propertyKeyword": "customScripts", + "propertyTooltip": "", + "structure": [ + { + "propertyName": "Before CREATE SCHEMA", + "propertyKeyword": "beforeCreateContainer", + "propertyType": "details", + "markdown": false, + "template": "codeEditor", + "templateOptions": { + "editorDialect": "sql", + "customScriptVariables": true + } + }, + { + "propertyName": "After CREATE SCHEMA", + "propertyKeyword": "afterCreateContainer", + "propertyType": "details", + "markdown": false, + "template": "codeEditor", + "templateOptions": { + "editorDialect": "sql", + "customScriptVariables": true + } + }, + { + "propertyName": "Before each CREATE TABLE", + "propertyKeyword": "beforeCreateEntity", + "propertyType": "details", + "markdown": false, + "template": "codeEditor", + "templateOptions": { + "editorDialect": "sql", + "customScriptVariables": true + } + }, + { + "propertyName": "After each CREATE TABLE", + "propertyKeyword": "afterCreateEntity", + "propertyType": "details", + "markdown": false, + "template": "codeEditor", + "templateOptions": { + "editorDialect": "sql", + "customScriptVariables": true + } + }, + { + "propertyName": "Before each CREATE VIEW", + "propertyKeyword": "beforeCreateView", + "propertyType": "details", + "markdown": false, + "template": "codeEditor", + "templateOptions": { + "editorDialect": "sql", + "customScriptVariables": true + } + }, + { + "propertyName": "After each CREATE VIEW", + "propertyKeyword": "afterCreateView", + "propertyType": "details", + "markdown": false, + "template": "codeEditor", + "templateOptions": { + "editorDialect": "sql", + "customScriptVariables": true + } + } + ] + }, + { + "propertyName": "Remarks", + "propertyKeyword": "comments", + "propertyTooltip": "remarks", + "addTimestampButton": true, + "propertyType": "details", + "template": "codeEditor", + "templateOptions": { + "editorDialect": "markdown" + } + } + ], + "containerLevelKeys": [] + } +] diff --git a/properties_pane/defaultData.json b/properties_pane/defaultData.json new file mode 100644 index 0000000..b488a15 --- /dev/null +++ b/properties_pane/defaultData.json @@ -0,0 +1,62 @@ +{ + "model": { + "modelName": "new_model", + "dbVersion": "v13.x", + "dbVendor": "Db2 for z/OS" + }, + "container": { + "name": "new_schema" + }, + "collection": { + "collectionName": "new_table", + "memory_optimized": false, + "collectionUsers": [], + "collation": {} + }, + "field": { + "name": "new_column", + "primaryKey": false, + "unique": false + }, + "patternField": { + "name": "^[a-zA-Z0-9_.-]+$" + }, + "multipleField": { + "primaryKey": true + }, + "subschema": {}, + "arrayItem": {}, + "choice": {}, + "relationship": { + "parentCardinality": "1", + "childCardinality": "0..n" + }, + "user": {}, + "view": { + "name": "new_view", + "viewOn": "", + "pipeline": "" + }, + "UDFs": { + "name": "new_udf", + "udfID": "", + "udfDescription": "", + "udfFunction": "", + "udfComments": "" + }, + "UDAs": { + "name": "new_uda", + "udfID": "", + "udfDescription": "", + "udfFunction": "", + "udfComments": "" + }, + "SecIdxs": { + "name": "new_secondary_index", + "SecIndxKey": "", + "SecIndxID": "", + "SecIndxDescription": "", + "SecIndxFunction": "", + "SecIndxComments": "" + } +} diff --git a/properties_pane/entity_level/entityLevelConfig.json b/properties_pane/entity_level/entityLevelConfig.json new file mode 100644 index 0000000..dece027 --- /dev/null +++ b/properties_pane/entity_level/entityLevelConfig.json @@ -0,0 +1,1336 @@ +/* +* Copyright © 2016-2024 by IntegrIT S.A. dba Hackolade. All rights reserved. +* +* The copyright to the computer software herein is the property of IntegrIT S.A. +* The software may be used and/or copied only with the written permission of +* IntegrIT S.A. or in accordance with the terms and conditions stipulated in +* the agreement/contract under which the software has been supplied. + +In order to define custom properties for any object's properties pane, you may copy/paste from the following, +making sure that you maintain a proper JSON format. + + { + "propertyName": "Simple text", + "propertyKeyword": "simpletextProp", + "propertyType": "text", + "sampleGen": "&containerName|&entityName|&random|" + }, + { + "propertyName": "Text area", + "propertyKeyword": "textareaProp", + "propertyTooltip": "Popup for multi-line text entry", + "propertyType": "details", + "template": "textarea" + }, + { + "propertyName": "Dropdown selection", + "propertyKeyword": "dropdownProp", + "propertyTooltip": "Select from list of options", + "propertyType": "select", + "options": [ + "Option 1", + "Option 2", + "Option 3", + "Option 4" + ] + }, + { + "propertyName": "Numeric", + "propertyKeyword": "numericProp", + "propertyValidate": true, + "propertyType": "numeric", + "valueType": "number", + "allowNegative": false, + "sampleGen": "&containerName|&entityName|&random|" + }, + { + "propertyName": "Checkbox", + "propertyKeyword": "checkboxProp", + "propertyType": "checkbox" + }, + { + "propertyName": "Group", + "propertyKeyword": "grpProp", + "propertyType": "group", + "propertyTooltip": "", + "structure": [ + { + "propertyName": "Simple Grp Text", + "propertyKeyword": "simpleGrpText", + "propertyTooltip": "", + "propertyType": "text" + }, + { + "propertyName": "Group Number", + "propertyKeyword": "grpNumber", + "propertyType": "numeric", + "valueType": "number", + "allowNegative": false + } + ] + }, +// “groupInput” can have the following states - 0 items, 1 item, and many items. +// “blockInput” has only 2 states - 0 items or 1 item. +// This gives us an easy way to represent it as an object and not as an array internally which is beneficial for processing +// and forward-engineering in particular. + { + "propertyName": "Block", + "propertyKeyword": "grpProp", + "propertyType": "block", + "propertyTooltip": "", + "structure": [ + { + "propertyName": "Simple Grp Text", + "propertyKeyword": "simpleGrpText", + "propertyTooltip": "", + "propertyType": "text" + }, + { + "propertyName": "Group Number", + "propertyKeyword": "grpNumber", + "propertyType": "numeric", + "valueType": "number", + "allowNegative": false + } + ] + }, + { + "propertyName": "Field List", + "propertyKeyword": "keyList", + "propertyType": "fieldList", + "template": "orderedList" + }, + { + "propertyName": "List with attribute", + "propertyKeyword": "keyListOrder", + "propertyType": "fieldList", + "template": "orderedList", + "attributeList": [ + "ascending", + "descending" + ] + } + +*/ +[ + { + "lowerTab": "Details", + "structure": [ + { + "propertyName": "Comments", + "propertyKeyword": "description", + "shouldValidate": false, + "propertyTooltip": "comments", + "addTimestampButton": false, + "propertyType": "details", + "template": "codeEditor", + "templateOptions": { + "editorDialect": "markdown" + } + }, + { + "propertyName": "Table kind", + "propertyKeyword": "tableKind", + "propertyTooltip": "Special CREATE TABLE forms. Leave blank for a regular base table.", + "propertyType": "select", + "options": [ + "", + { + "name": "Auxiliary", + "value": "auxiliary" + }, + { + "name": "Global temporary", + "value": "globalTemporary" + }, + { + "name": "Materialized query", + "value": "materializedQuery" + } + ] + }, + { + "propertyName": "Table", + "propertyKeyword": "auxiliaryBaseTable", + "propertyTooltip": "Identifies the base table that is to be stored in the auxiliary table.", + "propertyType": "selecthashed", + "template": "entities", + "withEmptyOption": true, + "excludeCurrent": true, + "dependency": { + "key": "tableKind", + "value": "auxiliary" + } + }, + { + "propertyName": "Column", + "propertyKeyword": "auxiliaryBaseColumn", + "propertyTooltip": "Identifies the column of the base table that is to be stored in the auxiliary table.", + "propertyType": "fieldList", + "template": "orderedList", + "dataToLoad": "auxiliaryBaseTable", + "templateOptions": { + "maxFields": 1 + }, + "dependency": { + "key": "tableKind", + "value": "auxiliary" + } + }, + { + "propertyName": "Append", + "propertyKeyword": "auxiliaryAppend", + "propertyTooltip": "Specifies whether append processing is used for the auxiliary table. The APPEND clause must not be specified for a table in a work file table space.", + "propertyType": "select", + "options": ["", "yes", "no"], + "dependency": { + "key": "tableKind", + "value": "auxiliary" + } + }, + { + "propertyName": "Part", + "propertyKeyword": "auxiliaryPart", + "propertyTooltip": "Specifies the partition of the base table for which the auxiliary table is to store the specified column.", + "propertyType": "numeric", + "valueType": "integer", + "allowNegative": false, + "dependency": { + "key": "tableKind", + "value": "auxiliary" + } + }, + { + "propertyName": "Like table", + "propertyKeyword": "likeTable", + "propertyTooltip": "Optional LIKE base-table clause. When set, the global temporary table's columns are copied from the referenced table instead of the columns modeled below.", + "propertyType": "selecthashed", + "template": "entities", + "withEmptyOption": true, + "excludeCurrent": true, + "dependency": { + "key": "tableKind", + "value": "globalTemporary" + } + }, + { + "propertyName": "CCSID", + "propertyKeyword": "gttCcsid", + "propertyTooltip": "CCSID ASCII, EBCDIC, or UNICODE for the encoding scheme of the global temporary table.", + "propertyType": "select", + "options": ["", "ASCII", "EBCDIC", "UNICODE"], + "dependency": { + "key": "tableKind", + "value": "globalTemporary" + } + }, + { + "propertyName": "MQT query", + "propertyKeyword": "mqtQuery", + "propertyTooltip": "Fullselect used in CREATE TABLE ... AS (fullselect) for the materialized query table.", + "propertyType": "details", + "template": "codeEditor", + "templateOptions": { + "editorDialect": "sql" + }, + "markdown": false, + "dependency": { + "key": "tableKind", + "value": "materializedQuery" + } + }, + { + "propertyName": "MQT data option", + "propertyKeyword": "mqtDataOption", + "propertyTooltip": "WITH DATA populates the table immediately. DATA INITIALLY DEFERRED leaves it empty until REFRESH TABLE is run.", + "propertyType": "select", + "options": ["", "WITH DATA", "DATA INITIALLY DEFERRED"], + "dependency": { + "key": "tableKind", + "value": "materializedQuery" + } + }, + { + "propertyName": "MQT refresh", + "propertyKeyword": "mqtRefresh", + "propertyTooltip": "REFRESH DEFERRED or REFRESH IMMEDIATE for the materialized query table.", + "propertyType": "select", + "options": ["", "DEFERRED", "IMMEDIATE"], + "dependency": { + "key": "tableKind", + "value": "materializedQuery" + } + }, + { + "propertyName": "MQT maintained by", + "propertyKeyword": "mqtMaintainedBy", + "propertyTooltip": "MAINTAINED BY SYSTEM, USER, or FEDERATED_TOOL.", + "propertyType": "select", + "options": ["", "SYSTEM", "USER", "FEDERATED_TOOL"], + "dependency": { + "key": "tableKind", + "value": "materializedQuery" + } + }, + { + "propertyName": "MQT query optimization", + "propertyKeyword": "mqtQueryOptimization", + "propertyTooltip": "ENABLE QUERY OPTIMIZATION or DISABLE QUERY OPTIMIZATION for query rewrite against this materialized query table.", + "propertyType": "select", + "options": ["", "ENABLE QUERY OPTIMIZATION", "DISABLE QUERY OPTIMIZATION"], + "dependency": { + "key": "tableKind", + "value": "materializedQuery" + } + }, + { + "propertyName": "Archive enabled", + "propertyKeyword": "archiveEnabled", + "propertyTooltip": "ENABLE ARCHIVE USE ARCHIVE TABLE. Rows deleted from this table are automatically moved into the archive table when SYSIBMADM.MOVE_TO_ARCHIVE is set.", + "propertyType": "checkbox", + "dependency": { + "type": "not", + "values": { + "key": "tableKind", + "value": ["auxiliary", "globalTemporary", "materializedQuery"] + } + } + }, + { + "propertyName": "Archive table", + "propertyKeyword": "archiveTable", + "propertyTooltip": "Table that stores rows deleted from this table. Generates an ALTER TABLE ... ENABLE ARCHIVE USE ARCHIVE TABLE statement once both tables exist.", + "propertyType": "selecthashed", + "template": "entities", + "withEmptyOption": true, + "excludeCurrent": true, + "dependency": { + "key": "archiveEnabled", + "value": true + } + }, + { + "propertyName": "IN clause", + "propertyKeyword": "inClauseType", + "propertyTooltip": "Placement of the table: IN database.tablespace, IN DATABASE database-name, or IN ACCELERATOR accelerator-name.", + "propertyType": "select", + "options": ["", "tablespace", "database", "accelerator"], + "dependency": { + "type": "not", + "values": { + "key": "tableKind", + "value": ["auxiliary", "globalTemporary"] + } + } + }, + { + "propertyName": "Database name", + "propertyKeyword": "databaseName", + "propertyTooltip": "Database name for IN database-name.table-space-name or IN DATABASE database-name. If omitted with a tablespace, DSNDB04 may be used.", + "propertyType": "text", + "dependency": { + "type": "and", + "values": [ + { + "type": "not", + "values": { + "key": "tableKind", + "value": ["auxiliary", "globalTemporary"] + } + }, + { + "key": "inClauseType", + "value": ["tablespace", "database"] + } + ] + } + }, + { + "propertyName": "Tablespace name", + "propertyKeyword": "table_tablespace_name", + "propertyTooltip": "Table space name for IN database-name.table-space-name. Must identify an existing table space when specified.", + "propertyType": "text", + "dependency": { + "type": "and", + "values": [ + { + "type": "not", + "values": { + "key": "tableKind", + "value": ["auxiliary", "globalTemporary"] + } + }, + { + "key": "inClauseType", + "value": "tablespace" + } + ] + } + }, + { + "propertyName": "Accelerator name", + "propertyKeyword": "acceleratorName", + "propertyTooltip": "Accelerator name for IN ACCELERATOR. Creates an accelerator-only table.", + "propertyType": "text", + "dependency": { + "type": "and", + "values": [ + { + "type": "not", + "values": { + "key": "tableKind", + "value": ["auxiliary", "globalTemporary"] + } + }, + { + "key": "inClauseType", + "value": "accelerator" + } + ] + } + }, + { + "propertyName": "Table options", + "propertyKeyword": "tableOptions", + "propertyType": "block", + "propertyTooltip": "Db2 for z/OS CREATE TABLE physical and behavioral options. Several options apply only when the table space is created implicitly (not when an existing table space or IN ACCELERATOR is specified).", + "dependency": { + "type": "not", + "values": { + "key": "tableKind", + "value": ["auxiliary", "globalTemporary"] + } + }, + "structure": [ + { + "propertyName": "Edit procedure", + "propertyKeyword": "editProc", + "propertyTooltip": "EDITPROC program-name. Identifies the user-written edit procedure for the table.", + "propertyType": "text" + }, + { + "propertyName": "Editproc row attributes", + "propertyKeyword": "editProcRowAttributes", + "propertyTooltip": "WITH ROW ATTRIBUTES (default) or WITHOUT ROW ATTRIBUTES for the edit procedure parameter list.", + "propertyType": "select", + "options": ["", "WITH ROW ATTRIBUTES", "WITHOUT ROW ATTRIBUTES"], + "dependency": { + "type": "and", + "values": [ + { + "key": "editProc", + "exist": true + }, + { + "key": "editProc", + "isEmpty": false + } + ] + } + }, + { + "propertyName": "Validation procedure", + "propertyKeyword": "validProc", + "propertyTooltip": "VALIDPROC program-name. Must not be specified with IN ACCELERATOR.", + "propertyType": "text" + }, + { + "propertyName": "Audit", + "propertyKeyword": "audit", + "propertyTooltip": "AUDIT NONE, CHANGES, or ALL. Must not be specified with IN ACCELERATOR.", + "propertyType": "select", + "options": ["", "NONE", "CHANGES", "ALL"] + }, + { + "propertyName": "OBID", + "propertyKeyword": "obid", + "propertyTooltip": "OBID integer. Identifier for the object's internal descriptor. Must be greater than 1 and unused in the database. If omitted, Db2 generates a value.", + "propertyType": "numeric", + "valueType": "integer", + "allowNegative": false, + "minValue": 2 + }, + { + "propertyName": "Data capture", + "propertyKeyword": "dataCapture", + "propertyTooltip": "DATA CAPTURE NONE or CHANGES. Do not specify CHANGES for tables in NOT LOGGED table spaces. Not valid for accelerator-only tables.", + "propertyType": "select", + "options": ["", "NONE", "CHANGES"] + }, + { + "propertyName": "With restrict on drop", + "propertyKeyword": "withRestrictOnDrop", + "propertyTooltip": "WITH RESTRICT ON DROP. Prevents the table, its database, and its table space from being dropped.", + "propertyType": "checkbox" + }, + { + "propertyName": "CCSID", + "propertyKeyword": "ccsid", + "propertyTooltip": "CCSID ASCII, EBCDIC, or UNICODE for the encoding scheme of the table.", + "propertyType": "select", + "options": ["", "ASCII", "EBCDIC", "UNICODE"] + }, + { + "propertyName": "Volatile", + "propertyKeyword": "volatile", + "propertyTooltip": "VOLATILE or NOT VOLATILE CARDINALITY. VOLATILE prefers index access. Must not be specified with IN ACCELERATOR.", + "propertyType": "select", + "options": ["", "VOLATILE", "NOT VOLATILE"] + }, + { + "propertyName": "Logged", + "propertyKeyword": "logged", + "propertyTooltip": "LOGGED or NOT LOGGED. Applies only when the table space is created implicitly. Do not specify when IN table-space-name or IN ACCELERATOR is used.", + "propertyType": "select", + "options": ["", "LOGGED", "NOT LOGGED"] + }, + { + "propertyName": "Compress", + "propertyKeyword": "compress", + "propertyTooltip": "COMPRESS NO, YES, YES FIXEDLENGTH, or YES HUFFMAN. Applies only to an implicitly created table space. Do not specify when IN table-space-name or IN ACCELERATOR is used.", + "propertyType": "select", + "options": ["", "NO", "YES", "YES FIXEDLENGTH", "YES HUFFMAN"] + }, + { + "propertyName": "Append", + "propertyKeyword": "append", + "propertyTooltip": "APPEND YES or NO. Must not be specified for a table in a work file table space.", + "propertyType": "select", + "options": ["", "YES", "NO"] + }, + { + "propertyName": "DSSIZE (G)", + "propertyKeyword": "dssize", + "propertyTooltip": "DSSIZE integer G for an implicitly created table space. Do not specify with IN table-space-name, IN ACCELERATOR, or PARTITION BY SIZE EVERY n G.", + "propertyType": "numeric", + "valueType": "integer", + "allowNegative": false + }, + { + "propertyName": "Buffer pool", + "propertyKeyword": "bufferPool", + "propertyTooltip": "BUFFERPOOL bpname for an implicitly created table space. Do not specify when IN table-space-name or IN ACCELERATOR is used.", + "propertyType": "text" + }, + { + "propertyName": "Member cluster", + "propertyKeyword": "memberCluster", + "propertyTooltip": "MEMBER CLUSTER for an implicitly created table space. Do not specify when IN table-space-name or IN ACCELERATOR is used.", + "propertyType": "checkbox" + }, + { + "propertyName": "Trackmod", + "propertyKeyword": "trackMod", + "propertyTooltip": "TRACKMOD YES or NO for an implicitly created table space. Do not specify when IN table-space-name or IN ACCELERATOR is used.", + "propertyType": "select", + "options": ["", "YES", "NO"] + }, + { + "propertyName": "Pagenum", + "propertyKeyword": "pageNum", + "propertyTooltip": "PAGENUM RELATIVE or ABSOLUTE for an implicitly created partition-by-range table space. PAGENUM RELATIVE is allowed only when a partitioning clause is specified.", + "propertyType": "select", + "options": ["", "RELATIVE", "ABSOLUTE"] + }, + { + "propertyName": "Key label", + "propertyKeyword": "keyLabelMode", + "propertyTooltip": "KEY LABEL key-label-name or NO KEY LABEL for table-level encryption. Not valid for accelerator-only or auxiliary tables.", + "propertyType": "select", + "options": ["", "KEY LABEL", "NO KEY LABEL"] + }, + { + "propertyName": "Key label name", + "propertyKeyword": "keyLabelName", + "propertyTooltip": "ICSF key label used to encrypt table spaces and index spaces associated with the table.", + "propertyType": "text", + "dependency": { + "key": "keyLabelMode", + "value": "KEY LABEL" + } + } + ] + }, + { + "propertyName": "Partitioning", + "propertyKeyword": "partitioning", + "propertyType": "group", + "groupItemLimit": 1, + "propertyTooltip": "PARTITION BY SIZE (partition-by-growth) or PARTITION BY RANGE (partition-by-range). Must not be specified with IN ACCELERATOR.", + "dependency": { + "type": "not", + "values": { + "key": "tableKind", + "value": ["auxiliary", "globalTemporary"] + } + }, + "structure": [ + { + "propertyName": "Partition by", + "propertyKeyword": "partitionBy", + "propertyTooltip": "SIZE for partition-by-growth, or RANGE for table-controlled range partitioning.", + "propertyType": "select", + "options": ["", "SIZE", "RANGE"] + }, + { + "propertyName": "Every size (G)", + "propertyKeyword": "everySize", + "propertyTooltip": "PARTITION BY SIZE EVERY integer G. Integer must not be greater than 256. If an existing table space is specified, integer must match its DSSIZE.", + "propertyType": "numeric", + "valueType": "integer", + "allowNegative": false, + "maxValue": 256, + "dependency": { + "key": "partitionBy", + "value": "SIZE" + } + }, + { + "propertyName": "Key", + "propertyKeyword": "partitionKey", + "propertyTooltip": "Ordered partitioning key columns for PARTITION BY RANGE (column-name [NULLS LAST] ASC|DESC).", + "propertyType": "compositeKeySetter", + "disabledItemStrategy": "default", + "isCompositeKey": true, + "setPrimaryKey": false, + "template": "collectiontree", + "abbr": "PT", + "dependency": { + "key": "partitionBy", + "value": "RANGE" + } + }, + { + "propertyName": "Nulls last", + "propertyKeyword": "nullsLast", + "propertyTooltip": "NULLS LAST on the partition-expression. Applies to the partitioning key columns.", + "propertyType": "checkbox", + "dependency": { + "key": "partitionBy", + "value": "RANGE" + } + }, + { + "propertyName": "Partitions", + "propertyType": "group", + "propertyKeyword": "partitions", + "propertyTooltip": "PARTITION integer ENDING AT (constant | MAXVALUE | MINVALUE) [INCLUSIVE].", + "dependency": { + "key": "partitionBy", + "value": "RANGE" + }, + "structure": [ + { + "propertyName": "Partition number", + "propertyKeyword": "partitionNumber", + "propertyTooltip": "PARTITION integer identifier.", + "propertyType": "numeric", + "valueType": "integer", + "allowNegative": false, + "minValue": 1 + }, + { + "propertyName": "Ending at", + "propertyKeyword": "endingAt", + "propertyTooltip": "ENDING AT values: comma-separated constants, or MAXVALUE / MINVALUE.", + "propertyType": "details", + "template": "textarea", + "markdown": false + }, + { + "propertyName": "Inclusive", + "propertyKeyword": "inclusive", + "propertyTooltip": "INCLUSIVE — specified range values are included in the data partition.", + "propertyType": "checkbox" + } + ] + } + ] + }, + { + "propertyName": "Period for system time", + "propertyKeyword": "periodForSystemTime", + "erdIndexAbbr": "ST", + "propertyType": "group", + "groupItemLimit": 1, + "propertyTooltip": "PERIOD FOR SYSTEM_TIME (begin-column-name, end-column-name). Must not be specified with IN ACCELERATOR.", + "dependency": { + "type": "not", + "values": { + "key": "tableKind", + "value": ["auxiliary", "globalTemporary", "materializedQuery"] + } + }, + "structure": [ + { + "propertyName": "Start column", + "propertyKeyword": "startColumn", + "propertyType": "fieldList", + "template": "orderedList", + "templateOptions": { + "maxFields": 1 + } + }, + { + "propertyName": "End column", + "propertyKeyword": "endColumn", + "propertyType": "fieldList", + "template": "orderedList", + "templateOptions": { + "maxFields": 1 + } + }, + { + "propertyName": "History table", + "propertyKeyword": "historyTable", + "propertyTooltip": "Optional table that stores prior row versions. When set, an ALTER TABLE ... ADD VERSIONING USE HISTORY TABLE statement is generated once both tables exist.", + "propertyType": "selecthashed", + "template": "entities", + "withEmptyOption": true, + "excludeCurrent": true + } + ] + }, + { + "propertyName": "Period for business time", + "propertyKeyword": "periodForBusinessTime", + "erdIndexAbbr": "BT", + "propertyType": "group", + "groupItemLimit": 1, + "propertyTooltip": "PERIOD FOR BUSINESS_TIME (begin-column-name, end-column-name [EXCLUSIVE | INCLUSIVE]). Must not be specified with IN ACCELERATOR.", + "dependency": { + "type": "not", + "values": { + "key": "tableKind", + "value": ["auxiliary", "globalTemporary", "materializedQuery"] + } + }, + "structure": [ + { + "propertyName": "Start column", + "propertyKeyword": "startColumn", + "propertyType": "fieldList", + "template": "orderedList", + "templateOptions": { + "maxFields": 1 + } + }, + { + "propertyName": "End column", + "propertyKeyword": "endColumn", + "propertyType": "fieldList", + "template": "orderedList", + "templateOptions": { + "maxFields": 1 + } + }, + { + "propertyName": "End inclusive", + "propertyKeyword": "endInclusive", + "propertyTooltip": "EXCLUSIVE or INCLUSIVE for the BUSINESS_TIME end column.", + "propertyType": "select", + "options": ["", "EXCLUSIVE", "INCLUSIVE"] + } + ] + }, + { + "propertyName": "Table properties", + "propertyKeyword": "tableProperties", + "propertyTooltip": "Optional raw DDL fragments appended after structured table options for clauses not modeled in the UI.", + "propertyType": "details", + "template": "textarea", + "markdown": false, + "dependency": { + "type": "not", + "values": { + "key": "tableKind", + "value": ["auxiliary", "globalTemporary"] + } + } + }, + { + "propertyName": "Custom scripts", + "propertyType": "block", + "propertyKeyword": "customScripts", + "propertyTooltip": "", + "structure": [ + { + "propertyName": "Before CREATE TABLE", + "propertyKeyword": "beforeCreateEntity", + "propertyType": "details", + "markdown": false, + "template": "codeEditor", + "templateOptions": { + "editorDialect": "sql", + "customScriptVariables": true + } + }, + { + "propertyName": "After CREATE TABLE", + "propertyKeyword": "afterCreateEntity", + "propertyType": "details", + "markdown": false, + "template": "codeEditor", + "templateOptions": { + "editorDialect": "sql", + "customScriptVariables": true + } + } + ] + }, + { + "propertyName": "Remarks", + "propertyKeyword": "comments", + "propertyTooltip": "remarks", + "addTimestampButton": true, + "propertyType": "details", + "template": "codeEditor", + "templateOptions": { + "editorDialect": "markdown" + }, + "valueType": "string" + } + ], + "columnsRatio": [3.7, 5] + }, + { + "lowerTab": "Composite keys", + "structure": [ + { + "propertyName": "Primary key", + "propertyType": "group", + "groupItemLimit": 1, + "propertyKeyword": "primaryKey", + "propertyTooltip": { + "disabled": [ + { + "tooltip": "Remove the existing single column primary key definition prior to unlock the possibility to create a composite primary key definition for this table", + "dependency": { + "type": "and", + "values": [ + { + "level": "children", + "key": "primaryKey", + "value": true + }, + { + "type": "not", + "values": { + "level": "children", + "key": "compositePrimaryKey", + "value": true + } + } + ] + } + }, + { + "tooltip": "Remove or update the existing composite primary key definition prior to unlock the possibility to create a new composite primary key definition for this table", + "dependency": { + "key": "primaryKey", + "minLength": 1 + } + } + ] + }, + "structure": [ + { + "propertyName": "Constraint name", + "propertyKeyword": "constraintName", + "propertyTooltip": "Optional CONSTRAINT name for the PRIMARY KEY. On Db2 for z/OS a unique index is required for PRIMARY KEY (implicitly created in some cases; otherwise create one under Indexes).", + "propertyType": "text", + "validation": { + "indexKey": "compositePrimaryKey", + "message": "A primary key constraint cannot be created without any primary key selected" + } + }, + { + "propertyName": "Key", + "propertyKeyword": "compositePrimaryKey", + "propertyType": "primaryKeySetter", + "requiredProperty": true, + "abbr": "pk" + }, + { + "propertyName": "Comment", + "propertyKeyword": "indexComment", + "propertyTooltip": "Optional comment for the primary key constraint.", + "addTimestampButton": false, + "propertyType": "details", + "template": "codeEditor", + "templateOptions": { + "editorDialect": "markdown" + } + } + ], + "disabledOnCondition": [ + { + "level": "children", + "value": { + "type": "and", + "values": [ + { + "key": "primaryKey", + "value": true + }, + { + "type": "or", + "values": [ + { + "key": "compositePrimaryKey", + "value": false + }, + { + "key": "compositePrimaryKey", + "exist": false + } + ] + }, + { + "type": "or", + "values": [ + { + "key": "compositeUniqueKey", + "value": false + }, + { + "key": "compositeUniqueKey", + "exist": false + } + ] + } + ] + } + } + ] + }, + { + "propertyName": "Unique key", + "propertyType": "group", + "propertyKeyword": "uniqueKey", + "propertyTooltip": "Table-level UNIQUE constraint. On Db2 for z/OS a unique index is required (implicitly created in some cases; otherwise create one under Indexes).", + "structure": [ + { + "propertyName": "Constraint name", + "propertyKeyword": "constraintName", + "propertyTooltip": "Optional CONSTRAINT name for the UNIQUE key.", + "propertyType": "text", + "validation": { + "indexKey": "compositeUniqueKey", + "message": "A unique key constraint cannot be created without any unique key selected" + } + }, + { + "propertyName": "Key", + "propertyKeyword": "compositeUniqueKey", + "propertyType": "compositeKeySetter", + "disabledItemStrategy": "default", + "setPrimaryKey": false, + "template": "collectiontree", + "requiredProperty": true, + "isCompositeKey": true, + "abbr": "uk" + }, + { + "propertyName": "Comment", + "propertyKeyword": "indexComment", + "propertyTooltip": "Optional comment for the unique key constraint.", + "addTimestampButton": false, + "propertyType": "details", + "template": "codeEditor", + "templateOptions": { + "editorDialect": "markdown" + } + }, + { + "propertyName": "Alternate key", + "propertyKeyword": "alternateKey", + "propertyTooltip": "Marks the unique key as an alternate key for ERD display.", + "propertyType": "checkbox", + "setFieldPropertyBy": "compositeUniqueKey" + } + ] + } + ] + }, + { + "lowerTab": "Indexes", + "structure": [ + { + "propertyName": "Index", + "propertyType": "group", + "propertyKeyword": "Indxs", + "propertyTooltip": "Db2 for z/OS CREATE INDEX definition.", + "structure": [ + { + "propertyName": "Name", + "propertyKeyword": "indxName", + "propertyTooltip": "Index name (optionally schema-qualified in FE as schema.index-name).", + "propertyType": "text", + "validation": { + "required": true + } + }, + { + "propertyName": "Activated", + "propertyKeyword": "isActivated", + "propertyTooltip": "Deactivated indexes are excluded from forward-engineering scripts.", + "propertyType": "checkbox", + "defaultValue": true + }, + { + "propertyName": "Type", + "propertyKeyword": "indxType", + "propertyTooltip": "Blank for a non-unique index, UNIQUE, or UNIQUE WHERE NOT NULL.", + "propertyType": "select", + "options": [ + "", + { + "name": "UNIQUE", + "value": "unique" + }, + { + "name": "UNIQUE WHERE NOT NULL", + "value": "uniqueWhereNotNull" + } + ] + }, + { + "propertyName": "Keys", + "propertyKeyword": "indxKey", + "propertyTooltip": "Index key columns with ASC, DESC, or RANDOM ordering.", + "propertyType": "fieldList", + "template": "orderedList", + "attributeList": ["asc", "desc", "random"], + "validation": { + "minLength": 1, + "required": true + } + }, + { + "propertyName": "Include keys", + "propertyKeyword": "indxIncludeKey", + "propertyType": "fieldList", + "propertyTooltip": "INCLUDE (column-name) for unique indexes. Included columns are not used to enforce uniqueness. Not valid with EXCLUDE NULL KEYS.", + "template": "orderedList", + "attributeList": [], + "dependency": { + "type": "and", + "values": [ + { + "type": "or", + "values": [ + { + "key": "indxType", + "value": "unique" + }, + { + "key": "indxType", + "value": "uniqueWhereNotNull" + } + ] + }, + { + "type": "not", + "values": { + "key": "indxNullKeys", + "value": "exclude" + } + } + ] + } + }, + { + "propertyName": "Cluster", + "propertyKeyword": "indxCluster", + "propertyTooltip": "CLUSTER or NOT CLUSTER. Specifies whether the index is the clustering index for the table.", + "propertyType": "select", + "options": ["", "CLUSTER", "NOT CLUSTER"] + }, + { + "propertyName": "Partitioned", + "propertyKeyword": "indxPartitioned", + "propertyTooltip": "PARTITIONED. Data-partitioned secondary index on a partition-by-range table space.", + "propertyType": "checkbox" + }, + { + "propertyName": "Padded", + "propertyKeyword": "indxPadded", + "propertyTooltip": "PADDED or NOT PADDED for varying-length string columns in the index key.", + "propertyType": "select", + "options": ["", "PADDED", "NOT PADDED"] + }, + { + "propertyName": "Using", + "propertyKeyword": "indxUsingType", + "propertyTooltip": "USING STOGROUP or USING VCAT for index data set definition.", + "propertyType": "select", + "options": [ + "", + { + "name": "STOGROUP", + "value": "STOGROUP" + }, + { + "name": "VCAT", + "value": "VCAT" + } + ] + }, + { + "propertyName": "Storage group", + "propertyKeyword": "indxStogroup", + "propertyTooltip": "STOGROUP stogroup-name.", + "propertyType": "text", + "dependency": { + "key": "indxUsingType", + "value": "STOGROUP" + } + }, + { + "propertyName": "PRIQTY", + "propertyKeyword": "indxPriQty", + "propertyTooltip": "Primary space allocation quantity in kilobytes for USING STOGROUP.", + "propertyType": "numeric", + "valueType": "integer", + "allowNegative": true, + "dependency": { + "key": "indxUsingType", + "value": "STOGROUP" + } + }, + { + "propertyName": "SECQTY", + "propertyKeyword": "indxSecQty", + "propertyTooltip": "Secondary space allocation quantity in kilobytes for USING STOGROUP.", + "propertyType": "numeric", + "valueType": "integer", + "allowNegative": true, + "dependency": { + "key": "indxUsingType", + "value": "STOGROUP" + } + }, + { + "propertyName": "ERASE", + "propertyKeyword": "indxErase", + "propertyTooltip": "ERASE YES or NO for USING STOGROUP.", + "propertyType": "select", + "options": ["", "YES", "NO"], + "dependency": { + "key": "indxUsingType", + "value": "STOGROUP" + } + }, + { + "propertyName": "VCAT name", + "propertyKeyword": "indxVcat", + "propertyTooltip": "USING VCAT catalog-name.", + "propertyType": "text", + "dependency": { + "key": "indxUsingType", + "value": "VCAT" + } + }, + { + "propertyName": "FREEPAGE", + "propertyKeyword": "indxFreepage", + "propertyTooltip": "FREEPAGE integer. How often to leave a free page when the index is created.", + "propertyType": "numeric", + "valueType": "integer", + "allowNegative": false, + "minValue": 0 + }, + { + "propertyName": "PCTFREE", + "propertyKeyword": "indxPctfree", + "propertyTooltip": "PCTFREE integer. Percentage of each index page to leave free.", + "propertyType": "numeric", + "valueType": "integer", + "allowNegative": false, + "minValue": 0, + "maxValue": 99 + }, + { + "propertyName": "DEFINE", + "propertyKeyword": "indxDefine", + "propertyTooltip": "DEFINE YES or NO. DEFINE NO applies only when USING STOGROUP is specified.", + "propertyType": "select", + "options": ["", "YES", "NO"] + }, + { + "propertyName": "Compress", + "propertyKeyword": "indxCompress", + "propertyType": "select", + "propertyTooltip": "COMPRESS YES or NO. COMPRESS YES requires an 8 KB, 16 KB, or 32 KB buffer pool.", + "options": ["", "YES", "NO"] + }, + { + "propertyName": "Null keys", + "propertyKeyword": "indxNullKeys", + "propertyType": "select", + "propertyTooltip": "INCLUDE NULL KEYS (default) or EXCLUDE NULL KEYS. EXCLUDE NULL KEYS cannot be specified with INCLUDE columns.", + "options": [ + "", + { + "name": "INCLUDE NULL KEYS", + "value": "include" + }, + { + "name": "EXCLUDE NULL KEYS", + "value": "exclude" + } + ] + }, + { + "propertyName": "BUFFERPOOL", + "propertyKeyword": "indxBufferPool", + "propertyTooltip": "BUFFERPOOL bpname. Must identify an activated 4 KB, 8 KB, 16 KB, or 32 KB buffer pool.", + "propertyType": "text" + }, + { + "propertyName": "CLOSE", + "propertyKeyword": "indxClose", + "propertyTooltip": "CLOSE YES or NO. Controls whether the data sets can be closed when the index is not in use.", + "propertyType": "select", + "options": ["", "YES", "NO"] + }, + { + "propertyName": "DEFER", + "propertyKeyword": "indxDefer", + "propertyTooltip": "DEFER YES or NO. DEFER YES builds the index later (index is in rebuild-pending status).", + "propertyType": "select", + "options": ["", "YES", "NO"] + }, + { + "propertyName": "PIECESIZE", + "propertyKeyword": "indxPiecesize", + "propertyTooltip": "Maximum addressability of each data set for a non-partitioned index. Must be a power of two.", + "propertyType": "numeric", + "valueType": "integer", + "allowNegative": false, + "minValue": 1 + }, + { + "propertyName": "PIECESIZE unit", + "propertyKeyword": "indxPiecesizeUnit", + "propertyTooltip": "Unit for PIECESIZE: K, M, or G.", + "propertyType": "select", + "options": ["", "K", "M", "G"], + "dependency": { + "type": "and", + "values": [ + { + "key": "indxPiecesize", + "exist": true + }, + { + "key": "indxPiecesize", + "isEmpty": false + } + ] + } + }, + { + "propertyName": "COPY", + "propertyKeyword": "indxCopy", + "propertyTooltip": "COPY YES or NO. Controls whether the COPY utility is allowed for the index.", + "propertyType": "select", + "options": ["", "YES", "NO"] + }, + { + "propertyName": "Index properties", + "propertyKeyword": "indxProperties", + "propertyTooltip": "Optional raw DDL fragments appended after structured index options (e.g. PARTITION BY RANGE, GBPCACHE, BUSINESS_TIME WITHOUT OVERLAPS).", + "propertyType": "details", + "template": "textarea", + "markdown": false + }, + { + "propertyName": "Description", + "propertyKeyword": "indxDescription", + "propertyTooltip": "Emitted as COMMENT ON INDEX.", + "propertyType": "details", + "template": "codeEditor", + "templateOptions": { + "editorDialect": "markdown" + } + }, + { + "propertyName": "Comments", + "propertyKeyword": "indxComments", + "propertyTooltip": "Internal modeling comments (not emitted as DDL).", + "addTimestampButton": false, + "propertyType": "details", + "template": "codeEditor", + "templateOptions": { + "editorDialect": "markdown" + } + } + ] + } + ] + }, + { + "lowerTab": "Check Constraints", + "structure": [ + { + "propertyName": "Check Constraint", + "propertyType": "group", + "propertyKeyword": "chkConstr", + "propertyTooltip": "Table check constraint: CONSTRAINT name CHECK (search-condition) [ENFORCED | NOT ENFORCED].", + "structure": [ + { + "propertyName": "Name", + "propertyKeyword": "chkConstrName", + "propertyTooltip": "Optional CONSTRAINT name for the check constraint.", + "propertyType": "text" + }, + { + "propertyName": "Expression", + "propertyKeyword": "constrExpression", + "propertyTooltip": "Search condition for CHECK (...). Outer parentheses are optional.", + "propertyType": "details", + "template": "textarea", + "markdown": false, + "validation": { + "required": true + } + }, + { + "propertyName": "Enforced", + "propertyKeyword": "constrEnforced", + "propertyTooltip": "ENFORCED (default) validates data. NOT ENFORCED defines an informational constraint.", + "propertyType": "select", + "options": [ + "", + { + "name": "ENFORCED", + "value": "ENFORCED" + }, + { + "name": "NOT ENFORCED", + "value": "NOT ENFORCED" + } + ] + }, + { + "propertyName": "Description", + "propertyKeyword": "constrDescription", + "propertyTooltip": "Description of the check constraint.", + "propertyType": "details", + "template": "textarea" + }, + { + "propertyName": "Comments", + "propertyKeyword": "constrComments", + "propertyTooltip": "Internal modeling comments (not emitted as DDL).", + "addTimestampButton": false, + "propertyType": "details", + "template": "textarea" + } + ] + } + ] + } +] diff --git a/properties_pane/field_level/fieldLevelConfig.json b/properties_pane/field_level/fieldLevelConfig.json new file mode 100644 index 0000000..cefafb3 --- /dev/null +++ b/properties_pane/field_level/fieldLevelConfig.json @@ -0,0 +1,3375 @@ +/* +* Copyright © 2016-2024 by IntegrIT S.A. dba Hackolade. All rights reserved. +* +* The copyright to the computer software herein is the property of IntegrIT S.A. +* The software may be used and/or copied only with the written permission of +* IntegrIT S.A. or in accordance with the terms and conditions stipulated in +* the agreement/contract under which the software has been supplied. + + +In order to define custom properties for any object's properties pane, you may copy/paste from the following, +making sure that you maintain a proper JSON format. + + { + "propertyName": "Simple text", + "propertyKeyword": "simpletextProp", + "propertyType": "text", + "sampleGen": "&containerName|&entityName|&random|" + }, + { + "propertyName": "Text area", + "propertyKeyword": "textareaProp", + "propertyTooltip": "Popup for multi-line text entry", + "propertyType": "details", + "template": "textarea" + }, + { + "propertyName": "Dropdown selection", + "propertyKeyword": "dropdownProp", + "propertyTooltip": "Select from list of options", + "propertyType": "select", + "options": [ + "Option 1", + "Option 2", + "Option 3", + "Option 4" + ] + }, + { + "propertyName": "Numeric", + "propertyKeyword": "numericProp", + "propertyValidate": true, + "propertyType": "numeric", + "valueType": "number", + "allowNegative": false, + "sampleGen": "&containerName|&entityName|&random|" + }, + { + "propertyName": "Checkbox", + "propertyKeyword": "checkboxProp", + "propertyType": "checkbox" + }, + { + "propertyName": "Group", + "propertyType": "group", + "propertyKeyword": "grpProp", + "propertyTooltip": "", + "structure": [ + { + "propertyName": "Simple Grp Text", + "propertyKeyword": "simpleGrpText", + "propertyTooltip": "", + "propertyType": "text" + }, + { + "propertyName": "Group Number", + "propertyKeyword": "grpNumber", + "propertyType": "numeric", + "valueType": "number", + "allowNegative": false + } + ] + }, +// “groupInput” can have the following states - 0 items, 1 item, and many items. +// “blockInput” has only 2 states - 0 items or 1 item. +// This gives us an easy way to represent it as an object and not as an array internally which is beneficial for processing +// and forward-engineering in particular. + { + "propertyName": "Block", + "propertyType": "block", + "propertyKeyword": "grpProp", + "propertyTooltip": "", + "structure": [ + { + "propertyName": "Simple Grp Text", + "propertyKeyword": "simpleGrpText", + "propertyTooltip": "", + "propertyType": "text" + }, + { + "propertyName": "Group Number", + "propertyKeyword": "grpNumber", + "propertyType": "numeric", + "valueType": "number", + "allowNegative": false + } + ] + }, + { + "propertyName": "Field List", + "propertyKeyword": "keyList", + "propertyType": "fieldList", + "template": "orderedList" + }, + { + "propertyName": "List with attribute", + "propertyKeyword": "keyListOrder", + "propertyType": "fieldList", + "template": "orderedList", + "attributeList": [ + "ascending", + "descending" + ] + } + +*/ + +{ + "lowerTab": "JsonDetails", + "structure": { + "char": [ + "name", + "code", + "schemaId", + "isActivated", + "reference", + "type", + { + "propertyName": "Subtype", + "propertyKeyword": "mode", + "propertyType": "select", + "options": ["char", "varchar", "clob", "graphic", "vargraphic", "dbclob"], + "data": "options", + "valueType": "string" + }, + { + "propertyName": "Synonym", + "propertyKeyword": "synonym", + "propertyTooltip": "Select from list of options", + "propertyType": "select", + "options": ["", "character"], + "defaultValue": "", + "dependency": { + "key": "mode", + "value": "char" + } + }, + { + "propertyName": "Synonym", + "propertyKeyword": "synonym", + "propertyTooltip": "Select from list of options", + "propertyType": "select", + "options": ["", "char varying", "character varying"], + "defaultValue": "", + "dependency": { + "key": "mode", + "value": "varchar" + } + }, + { + "propertyName": "Synonym", + "propertyKeyword": "synonym", + "propertyTooltip": "Select from list of options", + "propertyType": "select", + "options": ["", "char large object", "character large object"], + "defaultValue": "", + "dependency": { + "key": "mode", + "value": "clob" + } + }, + { + "propertyName": "Length", + "propertyKeyword": "length", + "propertyType": "numeric", + "valueType": "number", + "propertyTooltip": "Setting for Length must be between 1 and 255", + "minValue": 1, + "maxValue": 255, + "step": 1, + "typeDecorator": true, + "dependency": { + "key": "mode", + "value": "char" + } + }, + { + "propertyName": "Length", + "propertyKeyword": "length", + "propertyType": "numeric", + "valueType": "number", + "propertyTooltip": "Setting for Length must be between 1 and 32704", + "minValue": 1, + "maxValue": 32704, + "step": 1, + "typeDecorator": true, + "dependency": { + "key": "mode", + "value": "varchar" + } + }, + { + "propertyName": "Length", + "propertyKeyword": "length", + "propertyType": "numeric", + "valueType": "number", + "propertyTooltip": "Setting for Length must be between 1 and 2147483647", + "minValue": 1, + "maxValue": 2147483647, + "step": 1, + "typeDecorator": true, + "dependency": { + "key": "mode", + "value": "clob" + } + }, + { + "propertyName": "Length", + "propertyKeyword": "length", + "propertyType": "numeric", + "valueType": "number", + "propertyTooltip": "Setting for Length must be between 1 and 127", + "minValue": 1, + "maxValue": 127, + "step": 1, + "typeDecorator": true, + "dependency": { + "key": "mode", + "value": "graphic" + } + }, + { + "propertyName": "Length", + "propertyKeyword": "length", + "propertyType": "numeric", + "valueType": "number", + "propertyTooltip": "Setting for Length must be between 1 and 16352", + "minValue": 1, + "maxValue": 16352, + "step": 1, + "typeDecorator": true, + "dependency": { + "key": "mode", + "value": "vargraphic" + } + }, + { + "propertyName": "Length", + "propertyKeyword": "length", + "propertyType": "numeric", + "valueType": "number", + "propertyTooltip": "Setting for Length must be between 1 and 1073741823", + "minValue": 1, + "maxValue": 1073741823, + "step": 1, + "typeDecorator": true, + "dependency": { + "key": "mode", + "value": "dbclob" + } + }, + { + "propertyName": "Length semantics", + "propertyKeyword": "lengthSemantics", + "propertyType": "select", + "options": ["", "K", "M", "G"], + "dependency": { + "type": "and", + "values": [ + { + "key": "mode", + "value": ["clob", "dbclob"] + } + ] + } + }, + { + "propertyName": "Character subtype", + "propertyKeyword": "characterSubtype", + "propertyTooltip": "FOR SBCS DATA, FOR MIXED DATA, or FOR BIT DATA", + "propertyType": "select", + "options": ["", "SBCS", "MIXED", "BIT"], + "dependency": { + "type": "and", + "values": [ + { + "key": "mode", + "value": ["char", "varchar", "clob"] + } + ] + } + }, + { + "propertyName": "CCSID", + "propertyKeyword": "ccsid", + "propertyTooltip": "Coded character set identifier for the column", + "propertyType": "numeric", + "valueType": "number", + "allowNegative": false, + "minValue": 1, + "dependency": { + "type": "and", + "values": [ + { + "key": "mode", + "value": ["char", "varchar", "clob", "graphic", "vargraphic", "dbclob"] + } + ] + } + }, + { + "propertyName": "Inline length", + "propertyKeyword": "inlineLength", + "propertyTooltip": "Number of LOB bytes stored in the base table row (0-32680)", + "propertyType": "numeric", + "valueType": "number", + "minValue": 0, + "maxValue": 32680, + "step": 1, + "dependency": { + "key": "mode", + "value": "clob" + } + }, + { + "propertyName": "Inline length", + "propertyKeyword": "inlineLength", + "propertyTooltip": "Number of DBCLOB double-byte characters stored inline (0-16340)", + "propertyType": "numeric", + "valueType": "number", + "minValue": 0, + "maxValue": 16340, + "step": 1, + "dependency": { + "key": "mode", + "value": "dbclob" + } + }, + { + "propertyName": "JSON Type", + "propertyKeyword": "physicalType", + "propertyType": "select", + "hidden": true + }, + { + "propertyName": "JSON Types", + "propertyKeyword": "subtype", + "propertyType": "select", + "dependency": { + "key": "mode", + "value": "clob" + }, + "options": [ + { + "name": " ", + "value": "string" + }, + { + "name": "object", + "value": "object" + }, + { + "name": "array", + "value": "array" + } + ], + "defaultValue": "string" + }, + { + "propertyName": "Default", + "propertyKeyword": "default", + "shouldValidate": true, + "fieldType": "dynamicField", + "changeAction": "changeDefaultValueOfField", + "shadowState": true, + "valueType": "asFieldType", + "dependency": { + "type": "and", + "values": [ + { + "type": "or", + "values": [ + { + "key": "now", + "value": false + }, + { + "key": "now", + "exist": false + } + ] + } + ] + } + }, + { + "propertyName": "Comments", + "propertyKeyword": "description", + "propertyTooltip": "comments", + "addTimestampButton": false, + "propertyType": "details", + "template": "textarea" + }, + { + "propertyName": "Not null", + "propertyKeyword": "required", + "enableForReference": true, + "propertyType": "checkbox", + "dependency": { + "type": "or", + "values": [ + { + "key": "primaryKey", + "value": true + }, + { + "key": "unique", + "value": true + }, + { + "key": "compositeUniqueKey", + "value": true + } + ] + }, + "disabled": true, + "defaultValue": true + }, + { + "propertyName": "Not null", + "propertyKeyword": "required", + "enableForReference": true, + "propertyType": "checkbox", + "dependency": { + "type": "not", + "values": { + "type": "or", + "values": [ + { + "key": "primaryKey", + "value": true + }, + { + "key": "unique", + "value": true + }, + { + "key": "compositeUniqueKey", + "value": true + } + ] + } + }, + "defaultValue": false + }, + { + "propertyName": "Primary key", + "propertyKeyword": "compositePrimaryKey", + "propertyNameFull": "Composite Primary Key", + "propertyType": "checkbox", + "propertyTooltip": { + "disabled": [ + { + "tooltip": "This column is part of the table composite primary key definition. Please refer to this definition if you want more information or to update the Primary Key definition", + "dependency": { + "key": "compositePrimaryKey", + "value": true + } + }, + { + "tooltip": "Remove the existing composite primary key definition prior to unlock the possibility to mark this single column as the new primary key for this table", + "dependency": { + "level": "siblings", + "key": "compositePrimaryKey", + "value": true + } + }, + { + "tooltip": "Another column has already been selected as primary key. You must unselect it prior to either selecting this column, or creating a compound primary key.", + "dependency": { + "level": "siblings", + "key": "primaryKey", + "value": true + } + } + ] + }, + "dependency": { + "type": "and", + "values": [ + { + "type": "or", + "values": [ + { + "key": "compositePrimaryKey", + "value": true + }, + { + "level": "siblings", + "key": "compositePrimaryKey", + "value": true + }, + { + "level": "siblings", + "value": { + "type": "and", + "values": [ + { + "key": "primaryKey", + "value": true + }, + { + "type": "or", + "values": [ + { + "key": "compositeUniqueKey", + "value": false + }, + { + "key": "compositeUniqueKey", + "exist": false + } + ] + } + ] + } + } + ] + } + ] + }, + "disabled": true + }, + { + "propertyName": "Primary key", + "propertyKeyword": "primaryKey", + "propertyType": "checkbox", + "dependency": { + "type": "and", + "values": [ + { + "type": "not", + "values": { + "level": "siblings", + "key": "compositePrimaryKey", + "value": true + } + }, + { + "type": "not", + "values": [ + { + "level": "siblings", + "value": { + "type": "and", + "values": [ + { + "key": "primaryKey", + "value": true + }, + { + "type": "or", + "values": [ + { + "key": "compositeUniqueKey", + "value": false + }, + { + "key": "compositeUniqueKey", + "exist": false + } + ] + } + ] + } + } + ] + }, + { + "type": "and", + "values": [ + { + "type": "or", + "values": [ + { + "key": "compositePrimaryKey", + "value": false + }, + { + "key": "compositePrimaryKey", + "exist": false + } + ] + }, + { + "type": "or", + "values": [ + { + "key": "compositeUniqueKey", + "value": false + }, + { + "key": "compositeUniqueKey", + "exist": false + } + ] + } + ] + }, + { + "type": "not", + "values": { + "key": "unique", + "value": true + } + } + ] + }, + "disabledOnCondition": { + "type": "and", + "values": [ + { + "type": "not", + "values": { + "level": "parent", + "key": "type", + "value": "object" + } + } + ] + } + }, + { + "propertyName": "Primary key options", + "propertyType": "block", + "propertyKeyword": "primaryKeyOptions", + "enableForReference": true, + "propertyTooltip": "Primary key options", + "dependency": { + "type": "and", + "values": [ + { + "key": "primaryKey", + "value": true + }, + { + "type": "or", + "values": [ + { + "key": "compositePrimaryKey", + "value": false + }, + { + "key": "compositePrimaryKey", + "exist": false + } + ] + }, + { + "type": "or", + "values": [ + { + "key": "compositeUniqueKey", + "value": false + }, + { + "key": "compositeUniqueKey", + "exist": false + } + ] + } + ] + }, + "structure": [ + { + "propertyName": "Constraint name", + "propertyKeyword": "constraintName", + "propertyType": "text" + } + ] + }, + { + "propertyName": "Unique", + "propertyKeyword": "compositeUniqueKey", + "propertyNameFull": "Composite Unique Key", + "propertyType": "checkbox", + "dependency": { + "type": "and", + "values": [ + { + "key": "compositeUniqueKey", + "value": true + } + ] + }, + "disabled": true + }, + { + "propertyName": "Unique", + "propertyKeyword": "unique", + "enableForReference": true, + "propertyType": "checkbox", + "dependency": { + "type": "and", + "values": [ + { + "type": "or", + "values": [ + { + "type": "or", + "values": [ + { + "key": "primaryKey", + "value": false + }, + { + "key": "primaryKey", + "exist": false + } + ] + }, + { + "key": "compositePrimaryKey", + "value": true + } + ] + }, + { + "type": "or", + "values": [ + { + "key": "compositeUniqueKey", + "value": false + }, + { + "key": "compositeUniqueKey", + "exist": false + } + ] + } + ] + } + }, + { + "propertyName": "Unique key options", + "propertyType": "block", + "propertyKeyword": "uniqueKeyOptions", + "enableForReference": true, + "propertyTooltip": "Unique key options", + "dependency": { + "type": "and", + "values": [ + { + "key": "unique", + "value": true + }, + { + "type": "or", + "values": [ + { + "key": "compositePrimaryKey", + "value": false + }, + { + "key": "compositePrimaryKey", + "exist": false + } + ] + }, + { + "type": "or", + "values": [ + { + "key": "compositeUniqueKey", + "value": false + }, + { + "key": "compositeUniqueKey", + "exist": false + } + ] + } + ] + }, + "structure": [ + { + "propertyName": "Constraint name", + "propertyKeyword": "constraintName", + "propertyType": "text" + } + ] + }, + { + "propertyName": "Alternate Key", + "propertyKeyword": "alternateKey", + "defaultValue": false, + "enableForReference": true, + "propertyType": "checkbox", + "dependency": { + "type": "or", + "values": [ + { + "key": "unique", + "value": true + }, + { + "key": "compositeUniqueKey", + "value": true + } + ] + }, + "disabledOnCondition": [ + { + "key": "compositeUniqueKey", + "value": true + } + ] + }, + "foreignCollection", + "foreignField", + "relationshipType", + "relationshipName", + "cardinality", + "minLength", + "maxLength", + "pattern", + "format", + "enum", + "sample", + "fakerFunction", + { + "propertyName": "Remarks", + "propertyKeyword": "comments", + "shouldValidate": false, + "propertyTooltip": "remarks", + "addTimestampButton": true, + "propertyType": "details", + "template": "codeEditor", + "templateOptions": { + "editorDialect": "markdown" + } + } + ], + "number": [ + "name", + "code", + "schemaId", + "isActivated", + "reference", + "type", + { + "propertyName": "Subtype", + "propertyKeyword": "mode", + "propertyType": "select", + "options": ["integer", "smallint", "bigint", "decimal", "float", "real", "double", "decfloat"], + "data": "options", + "valueType": "string" + }, + { + "propertyName": "Synonym", + "propertyKeyword": "synonym", + "propertyTooltip": "Select from list of options", + "propertyType": "select", + "options": ["", "int"], + "defaultValue": "", + "dependency": { + "key": "mode", + "value": "integer" + } + }, + { + "propertyName": "Synonym", + "propertyKeyword": "synonym", + "propertyTooltip": "Select from list of options", + "propertyType": "select", + "options": ["", "dec", "numeric", "num"], + "defaultValue": "", + "dependency": { + "key": "mode", + "value": "decimal" + } + }, + { + "propertyName": "Synonym", + "propertyKeyword": "synonym", + "propertyTooltip": "Select from list of options", + "propertyType": "select", + "options": ["", "double precision"], + "defaultValue": "", + "dependency": { + "key": "mode", + "value": "double" + } + }, + { + "propertyName": "Precision", + "propertyKeyword": "precision", + "propertyType": "numeric", + "valueType": "number", + "propertyTooltip": "Setting for Precision must be between 1 and 31", + "minValue": 1, + "maxValue": 31, + "typeDecorator": true, + "dependency": { + "key": "mode", + "value": "decimal" + } + }, + { + "propertyName": "Scale", + "propertyKeyword": "scale", + "propertyType": "numeric", + "valueType": "number", + "allowNegative": false, + "typeDecorator": true, + "dependency": { + "key": "mode", + "value": "decimal" + } + }, + { + "propertyName": "Precision", + "propertyKeyword": "precision", + "propertyType": "numeric", + "valueType": "number", + "propertyTooltip": "FLOAT precision in bits: 1-21 is single precision (REAL), 22-53 is double precision (DOUBLE)", + "minValue": 1, + "maxValue": 53, + "typeDecorator": true, + "dependency": { + "key": "mode", + "value": "float" + } + }, + { + "propertyName": "Precision", + "propertyKeyword": "precision", + "propertyType": "select", + "options": ["", "16", "34"], + "propertyTooltip": "DECFLOAT precision must be 16 or 34", + "typeDecorator": true, + "dependency": { + "key": "mode", + "value": "decfloat" + } + }, + { + "propertyName": "Default", + "propertyKeyword": "default", + "shouldValidate": true, + "fieldType": "dynamicField", + "changeAction": "changeDefaultValueOfField", + "shadowState": true, + "valueType": "asFieldType", + "dependency": { + "type": "and", + "values": [ + { + "type": "or", + "values": [ + { + "key": "now", + "value": false + }, + { + "key": "now", + "exist": false + } + ] + } + ] + } + }, + { + "propertyName": "Comments", + "propertyKeyword": "description", + "propertyTooltip": "comments", + "addTimestampButton": false, + "propertyType": "details", + "template": "textarea" + }, + { + "propertyName": "Not null", + "propertyKeyword": "required", + "enableForReference": true, + "propertyType": "checkbox", + "dependency": { + "type": "or", + "values": [ + { + "key": "primaryKey", + "value": true + }, + { + "key": "unique", + "value": true + }, + { + "key": "compositeUniqueKey", + "value": true + } + ] + }, + "disabled": true, + "defaultValue": true + }, + { + "propertyName": "Not null", + "propertyKeyword": "required", + "enableForReference": true, + "propertyType": "checkbox", + "dependency": { + "type": "not", + "values": { + "type": "or", + "values": [ + { + "key": "primaryKey", + "value": true + }, + { + "key": "unique", + "value": true + }, + { + "key": "compositeUniqueKey", + "value": true + } + ] + } + }, + "defaultValue": false + }, + { + "propertyName": "Identity", + "propertyType": "block", + "propertyKeyword": "identity", + "propertyTooltip": "Creates an identity column in a table", + "structure": [ + { + "propertyName": "Generated", + "propertyKeyword": "generated", + "propertyTooltip": "Select type of value generation", + "propertyType": "select", + "options": ["", "ALWAYS", "BY DEFAULT"] + }, + { + "propertyName": "Start", + "propertyKeyword": "start", + "propertyTooltip": "Is the value that is used for the very first row loaded into the table", + "propertyType": "numeric", + "valueType": "number", + "step": 1, + "dependency": { + "type": "not", + "values": { + "type": "or", + "values": [ + { + "key": "generated", + "exist": false + }, + { + "key": "generated", + "value": "" + } + ] + } + }, + "minValue": 1 + }, + { + "propertyName": "Increment", + "propertyKeyword": "increment", + "propertyTooltip": "Is the incremental value that is added to the identity value of the previous row that was loaded.", + "propertyType": "numeric", + "valueType": "number", + "step": 1, + "dependency": { + "type": "not", + "values": { + "type": "or", + "values": [ + { + "key": "generated", + "exist": false + }, + { + "key": "generated", + "value": "" + } + ] + } + }, + "minValue": 1 + }, + { + "propertyName": "Cycle", + "propertyKeyword": "cycle", + "propertyTooltip": "The CYCLE or NO CYCLE option determines whether Db2 wraps values when it has generated all values between the START WITH value and MAXVALUE.", + "propertyType": "select", + "options": ["", "CYCLE", "NO CYCLE"], + "dependency": { + "type": "not", + "values": { + "type": "or", + "values": [ + { + "key": "generated", + "exist": false + }, + { + "key": "generated", + "value": "" + } + ] + } + } + }, + { + "propertyName": "Min value", + "propertyKeyword": "minValue", + "propertyTooltip": "", + "propertyType": "numeric", + "valueType": "number", + "step": 1, + "dependency": { + "type": "not", + "values": { + "type": "or", + "values": [ + { + "key": "generated", + "exist": false + }, + { + "key": "generated", + "value": "" + } + ] + } + } + }, + { + "propertyName": "Max value", + "propertyKeyword": "maxValue", + "propertyTooltip": "", + "propertyType": "numeric", + "valueType": "number", + "step": 1, + "dependency": { + "type": "not", + "values": { + "type": "or", + "values": [ + { + "key": "generated", + "exist": false + }, + { + "key": "generated", + "value": "" + } + ] + } + } + }, + { + "propertyName": "Cache", + "propertyKeyword": "cache", + "propertyTooltip": "Specify how many values of the sequence the database preallocates and keeps in memory for faster access. Specify NOCACHE to indicate that values of the sequence are not preallocated. If you omit both CACHE and NOCACHE, then the database caches 20 sequence numbers by default.", + "propertyType": "select", + "options": ["", "CACHE", "NO CACHE"], + "dependency": { + "type": "not", + "values": { + "type": "or", + "values": [ + { + "key": "generated", + "exist": false + }, + { + "key": "generated", + "value": "" + } + ] + } + } + }, + { + "propertyName": "Cache value", + "propertyKeyword": "cacheValue", + "propertyType": "numeric", + "valueType": "number", + "propertyTooltip": "This integer value can have 28 or fewer digits. The minimum value for this parameter is 2.", + "minValue": 2, + "defaultValue": 20, + "dependency": { + "key": "cache", + "value": "CACHE" + } + }, + { + "propertyName": "Order", + "propertyKeyword": "order", + "propertyTooltip": "Specify ORDER to guarantee that sequence numbers are generated in order of request. Specify NO ORDER if you do not want to guarantee sequence numbers are generated in order of request.", + "propertyType": "select", + "options": ["", "ORDER", "NO ORDER"], + "dependency": { + "type": "not", + "values": { + "type": "or", + "values": [ + { + "key": "generated", + "exist": false + }, + { + "key": "generated", + "value": "" + } + ] + } + } + } + ], + "dependency": { + "type": "or", + "values": [ + { + "key": "mode", + "value": "integer" + }, + { + "key": "mode", + "value": "smallint" + }, + { + "key": "mode", + "value": "bigint" + }, + { + "key": "mode", + "value": "decimal" + } + ] + } + }, + { + "propertyName": "Primary key", + "propertyKeyword": "compositePrimaryKey", + "propertyNameFull": "Composite Primary Key", + "propertyType": "checkbox", + "propertyTooltip": { + "disabled": [ + { + "tooltip": "This column is part of the table composite primary key definition. Please refer to this definition if you want more information or to update the Primary Key definition", + "dependency": { + "key": "compositePrimaryKey", + "value": true + } + }, + { + "tooltip": "Remove the existing composite primary key definition prior to unlock the possibility to mark this single column as the new primary key for this table", + "dependency": { + "level": "siblings", + "key": "compositePrimaryKey", + "value": true + } + }, + { + "tooltip": "Another column has already been selected as primary key. You must unselect it prior to either selecting this column, or creating a compound primary key.", + "dependency": { + "level": "siblings", + "key": "primaryKey", + "value": true + } + } + ] + }, + "dependency": { + "type": "and", + "values": [ + { + "type": "or", + "values": [ + { + "key": "compositePrimaryKey", + "value": true + }, + { + "level": "siblings", + "key": "compositePrimaryKey", + "value": true + }, + { + "level": "siblings", + "value": { + "type": "and", + "values": [ + { + "key": "primaryKey", + "value": true + }, + { + "type": "or", + "values": [ + { + "key": "compositeUniqueKey", + "value": false + }, + { + "key": "compositeUniqueKey", + "exist": false + } + ] + } + ] + } + } + ] + } + ] + }, + "disabled": true + }, + { + "propertyName": "Primary key", + "propertyKeyword": "primaryKey", + "propertyType": "checkbox", + "dependency": { + "type": "and", + "values": [ + { + "type": "not", + "values": { + "level": "siblings", + "key": "compositePrimaryKey", + "value": true + } + }, + { + "type": "not", + "values": [ + { + "level": "siblings", + "value": { + "type": "and", + "values": [ + { + "key": "primaryKey", + "value": true + }, + { + "type": "or", + "values": [ + { + "key": "compositeUniqueKey", + "value": false + }, + { + "key": "compositeUniqueKey", + "exist": false + } + ] + } + ] + } + } + ] + }, + { + "type": "and", + "values": [ + { + "type": "or", + "values": [ + { + "key": "compositePrimaryKey", + "value": false + }, + { + "key": "compositePrimaryKey", + "exist": false + } + ] + }, + { + "type": "or", + "values": [ + { + "key": "compositeUniqueKey", + "value": false + }, + { + "key": "compositeUniqueKey", + "exist": false + } + ] + } + ] + }, + { + "type": "not", + "values": { + "key": "unique", + "value": true + } + } + ] + }, + "disabledOnCondition": { + "type": "and", + "values": [ + { + "type": "not", + "values": { + "level": "parent", + "key": "type", + "value": "object" + } + } + ] + }, + "enableForReference": true + }, + { + "propertyName": "Primary key options", + "propertyType": "block", + "propertyKeyword": "primaryKeyOptions", + "enableForReference": true, + "propertyTooltip": "Primary key options", + "dependency": { + "type": "and", + "values": [ + { + "key": "primaryKey", + "value": true + }, + { + "type": "or", + "values": [ + { + "key": "compositePrimaryKey", + "value": false + }, + { + "key": "compositePrimaryKey", + "exist": false + } + ] + }, + { + "type": "or", + "values": [ + { + "key": "compositeUniqueKey", + "value": false + }, + { + "key": "compositeUniqueKey", + "exist": false + } + ] + } + ] + }, + "structure": [ + { + "propertyName": "Constraint name", + "propertyKeyword": "constraintName", + "propertyType": "text" + } + ] + }, + { + "propertyName": "Unique", + "propertyKeyword": "compositeUniqueKey", + "propertyNameFull": "Composite Unique Key", + "propertyType": "checkbox", + "dependency": { + "type": "and", + "values": [ + { + "key": "compositeUniqueKey", + "value": true + } + ] + }, + "disabled": true + }, + { + "propertyName": "Unique", + "propertyKeyword": "unique", + "enableForReference": true, + "propertyType": "checkbox", + "dependency": { + "type": "and", + "values": [ + { + "type": "or", + "values": [ + { + "type": "or", + "values": [ + { + "key": "primaryKey", + "value": false + }, + { + "key": "primaryKey", + "exist": false + } + ] + }, + { + "key": "compositePrimaryKey", + "value": true + } + ] + }, + { + "type": "or", + "values": [ + { + "key": "compositeUniqueKey", + "value": false + }, + { + "key": "compositeUniqueKey", + "exist": false + } + ] + } + ] + } + }, + { + "propertyName": "Unique key options", + "propertyType": "block", + "propertyKeyword": "uniqueKeyOptions", + "enableForReference": true, + "propertyTooltip": "Unique key options", + "dependency": { + "type": "and", + "values": [ + { + "key": "unique", + "value": true + }, + { + "type": "or", + "values": [ + { + "key": "compositePrimaryKey", + "value": false + }, + { + "key": "compositePrimaryKey", + "exist": false + } + ] + }, + { + "type": "or", + "values": [ + { + "key": "compositeUniqueKey", + "value": false + }, + { + "key": "compositeUniqueKey", + "exist": false + } + ] + } + ] + }, + "structure": [ + { + "propertyName": "Constraint name", + "propertyKeyword": "constraintName", + "propertyType": "text" + } + ] + }, + { + "propertyName": "Alternate Key", + "propertyKeyword": "alternateKey", + "defaultValue": false, + "enableForReference": true, + "propertyType": "checkbox", + "dependency": { + "type": "or", + "values": [ + { + "key": "unique", + "value": true + }, + { + "key": "compositeUniqueKey", + "value": true + } + ] + }, + "disabledOnCondition": [ + { + "key": "compositeUniqueKey", + "value": true + } + ] + }, + "foreignCollection", + "foreignField", + "relationshipType", + "relationshipName", + "cardinality", + "unit", + "minimum", + { + "fieldKeyword": "exclusiveMinimum", + "dependency": { + "type": "and", + "values": [ + { + "key": "jsonSchemaSpec", + "value": "draft-04" + } + ] + } + }, + "maximum", + { + "fieldKeyword": "exclusiveMaximum", + "dependency": { + "type": "and", + "values": [ + { + "key": "jsonSchemaSpec", + "value": "draft-04" + } + ] + } + }, + "multipleOf", + "divisibleBy", + "pattern", + "enum", + { + "fieldKeyword": "sample", + "dependency": { + "type": "and", + "values": [ + { + "key": "jsonSchemaSpec", + "value": "draft-04" + } + ] + } + }, + "fakerFunction", + { + "propertyName": "Remarks", + "propertyKeyword": "comments", + "shouldValidate": false, + "propertyTooltip": "remarks", + "addTimestampButton": true, + "propertyType": "details", + "template": "codeEditor", + "templateOptions": { + "editorDialect": "markdown" + } + } + ], + "datetime": [ + "name", + "code", + "isActivated", + "reference", + "sampleName", + "schemaId", + "refPath", + "type", + { + "propertyName": "Subtype", + "propertyKeyword": "mode", + "propertyType": "select", + "options": ["date", "time", "timestamp"], + "data": "options", + "valueType": "string" + }, + { + "propertyName": "Fractional seconds precision", + "propertyKeyword": "fractSecPrecision", + "propertyType": "numeric", + "valueType": "number", + "propertyTooltip": "Setting for precision must be between 0 and 12", + "minValue": 0, + "maxValue": 12, + "step": 1, + "typeDecorator": true, + "dependency": { + "type": "and", + "values": [ + { + "key": "mode", + "value": ["timestamp"] + } + ] + } + }, + { + "propertyName": "With time zone", + "propertyKeyword": "withTimeZone", + "propertyType": "checkbox", + "dependency": { + "type": "and", + "values": [ + { + "key": "mode", + "value": ["timestamp"] + } + ] + } + }, + { + "propertyName": "Comments", + "propertyKeyword": "description", + "propertyTooltip": "comments", + "addTimestampButton": false, + "propertyType": "details", + "template": "codeEditor", + "templateOptions": { + "editorDialect": "markdown" + } + }, + { + "propertyName": "Not null", + "propertyKeyword": "required", + "enableForReference": true, + "propertyType": "checkbox", + "dependency": { + "type": "or", + "values": [ + { + "key": "primaryKey", + "value": true + }, + { + "key": "unique", + "value": true + }, + { + "key": "compositeUniqueKey", + "value": true + } + ] + }, + "disabled": true, + "defaultValue": true + }, + { + "propertyName": "Not null", + "propertyKeyword": "required", + "enableForReference": true, + "propertyType": "checkbox", + "dependency": { + "type": "not", + "values": { + "type": "or", + "values": [ + { + "key": "primaryKey", + "value": true + }, + { + "key": "unique", + "value": true + }, + { + "key": "compositeUniqueKey", + "value": true + } + ] + } + }, + "defaultValue": false + }, + { + "propertyName": "Primary key", + "propertyKeyword": "compositePrimaryKey", + "propertyNameFull": "Composite Primary Key", + "propertyType": "checkbox", + "propertyTooltip": { + "disabled": [ + { + "tooltip": "This column is part of the table composite primary key definition. Please refer to this definition if you want more information or to update the Primary Key definition", + "dependency": { + "key": "compositePrimaryKey", + "value": true + } + }, + { + "tooltip": "Remove the existing composite primary key definition prior to unlock the possibility to mark this single column as the new primary key for this table", + "dependency": { + "level": "siblings", + "key": "compositePrimaryKey", + "value": true + } + }, + { + "tooltip": "Another column has already been selected as primary key. You must unselect it prior to either selecting this column, or creating a compound primary key.", + "dependency": { + "level": "siblings", + "key": "primaryKey", + "value": true + } + } + ] + }, + "dependency": { + "type": "and", + "values": [ + { + "type": "or", + "values": [ + { + "key": "compositePrimaryKey", + "value": true + }, + { + "level": "siblings", + "key": "compositePrimaryKey", + "value": true + }, + { + "level": "siblings", + "value": { + "type": "and", + "values": [ + { + "key": "primaryKey", + "value": true + }, + { + "type": "or", + "values": [ + { + "key": "compositeUniqueKey", + "value": false + }, + { + "key": "compositeUniqueKey", + "exist": false + } + ] + } + ] + } + } + ] + } + ] + }, + "disabled": true + }, + { + "propertyName": "Primary key", + "propertyKeyword": "primaryKey", + "propertyType": "checkbox", + "dependency": { + "type": "and", + "values": [ + { + "type": "not", + "values": { + "level": "siblings", + "key": "compositePrimaryKey", + "value": true + } + }, + { + "type": "not", + "values": [ + { + "level": "siblings", + "value": { + "type": "and", + "values": [ + { + "key": "primaryKey", + "value": true + }, + { + "type": "or", + "values": [ + { + "key": "compositeUniqueKey", + "value": false + }, + { + "key": "compositeUniqueKey", + "exist": false + } + ] + } + ] + } + } + ] + }, + { + "type": "and", + "values": [ + { + "type": "or", + "values": [ + { + "key": "compositePrimaryKey", + "value": false + }, + { + "key": "compositePrimaryKey", + "exist": false + } + ] + }, + { + "type": "or", + "values": [ + { + "key": "compositeUniqueKey", + "value": false + }, + { + "key": "compositeUniqueKey", + "exist": false + } + ] + } + ] + }, + { + "type": "not", + "values": { + "key": "unique", + "value": true + } + } + ] + }, + "disabledOnCondition": { + "type": "and", + "values": [ + { + "type": "not", + "values": { + "level": "parent", + "key": "type", + "value": "object" + } + } + ] + }, + "enableForReference": true + }, + { + "propertyName": "Primary key options", + "propertyType": "block", + "propertyKeyword": "primaryKeyOptions", + "enableForReference": true, + "propertyTooltip": "Primary key options", + "dependency": { + "type": "and", + "values": [ + { + "key": "primaryKey", + "value": true + }, + { + "type": "or", + "values": [ + { + "key": "compositePrimaryKey", + "value": false + }, + { + "key": "compositePrimaryKey", + "exist": false + } + ] + }, + { + "type": "or", + "values": [ + { + "key": "compositeUniqueKey", + "value": false + }, + { + "key": "compositeUniqueKey", + "exist": false + } + ] + } + ] + }, + "structure": [ + { + "propertyName": "Constraint name", + "propertyKeyword": "constraintName", + "propertyType": "text" + } + ] + }, + { + "propertyName": "Unique", + "propertyKeyword": "compositeUniqueKey", + "propertyNameFull": "Composite Unique Key", + "propertyType": "checkbox", + "dependency": { + "type": "and", + "values": [ + { + "key": "compositeUniqueKey", + "value": true + } + ] + }, + "disabled": true + }, + { + "propertyName": "Unique", + "propertyKeyword": "unique", + "enableForReference": true, + "propertyType": "checkbox", + "dependency": { + "type": "and", + "values": [ + { + "type": "or", + "values": [ + { + "type": "or", + "values": [ + { + "key": "primaryKey", + "value": false + }, + { + "key": "primaryKey", + "exist": false + } + ] + }, + { + "key": "compositePrimaryKey", + "value": true + } + ] + }, + { + "type": "or", + "values": [ + { + "key": "compositeUniqueKey", + "value": false + }, + { + "key": "compositeUniqueKey", + "exist": false + } + ] + } + ] + } + }, + { + "propertyName": "Unique key options", + "propertyType": "block", + "propertyKeyword": "uniqueKeyOptions", + "enableForReference": true, + "propertyTooltip": "Unique key options", + "dependency": { + "type": "and", + "values": [ + { + "key": "unique", + "value": true + }, + { + "type": "or", + "values": [ + { + "key": "compositePrimaryKey", + "value": false + }, + { + "key": "compositePrimaryKey", + "exist": false + } + ] + }, + { + "type": "or", + "values": [ + { + "key": "compositeUniqueKey", + "value": false + }, + { + "key": "compositeUniqueKey", + "exist": false + } + ] + } + ] + }, + "structure": [ + { + "propertyName": "Constraint name", + "propertyKeyword": "constraintName", + "propertyType": "text" + } + ] + }, + { + "propertyName": "Alternate Key", + "propertyKeyword": "alternateKey", + "defaultValue": false, + "enableForReference": true, + "propertyType": "checkbox", + "dependency": { + "type": "or", + "values": [ + { + "key": "unique", + "value": true + }, + { + "key": "compositeUniqueKey", + "value": true + } + ] + }, + "disabledOnCondition": [ + { + "key": "compositeUniqueKey", + "value": true + } + ] + }, + "foreignCollection", + "foreignField", + "relationshipType", + "relationshipName", + "cardinality", + { + "propertyName": "Default", + "propertyKeyword": "default", + "shouldValidate": true, + "fieldType": "dynamicField", + "changeAction": "changeDefaultValueOfField", + "shadowState": true, + "valueType": "asFieldType", + "dependency": { + "type": "and", + "values": [ + { + "type": "or", + "values": [ + { + "key": "now", + "value": false + }, + { + "key": "now", + "exist": false + } + ] + } + ] + } + }, + "pattern", + "format", + "enum", + "sample", + "fakerFunction", + { + "propertyName": "Remarks", + "propertyKeyword": "comments", + "shouldValidate": false, + "propertyTooltip": "remarks", + "addTimestampButton": true, + "propertyType": "details", + "template": "codeEditor", + "templateOptions": { + "editorDialect": "markdown" + } + } + ], + "binary": [ + "name", + "code", + "sampleName", + "schemaId", + "isActivated", + "reference", + "refPath", + "type", + { + "propertyName": "Subtype", + "propertyKeyword": "mode", + "propertyType": "select", + "defaultValue": "binary", + "options": ["binary", "varbinary", "blob"], + "data": "options", + "valueType": "string" + }, + { + "propertyName": "Length", + "propertyKeyword": "length", + "propertyType": "numeric", + "valueType": "number", + "propertyTooltip": "Setting for Length must be between 1 and 255", + "minValue": 1, + "maxValue": 255, + "step": 1, + "typeDecorator": true, + "dependency": { + "key": "mode", + "value": "binary" + }, + "defaultValue": 1 + }, + { + "propertyName": "Length", + "propertyKeyword": "length", + "propertyType": "numeric", + "valueType": "number", + "propertyTooltip": "Setting for Length must be between 1 and 32704", + "minValue": 1, + "maxValue": 32704, + "step": 1, + "typeDecorator": true, + "dependency": { + "key": "mode", + "value": "varbinary" + } + }, + { + "propertyName": "Length", + "propertyKeyword": "length", + "propertyType": "numeric", + "valueType": "number", + "propertyTooltip": "Setting for Length must be between 1 and 2147483647", + "minValue": 1, + "maxValue": 2147483647, + "step": 1, + "typeDecorator": true, + "dependency": { + "key": "mode", + "value": "blob" + } + }, + { + "propertyName": "Length semantics", + "propertyKeyword": "lengthSemantics", + "propertyType": "select", + "options": ["", "K", "M", "G"], + "dependency": { + "key": "mode", + "value": "blob" + } + }, + { + "propertyName": "Inline length", + "propertyKeyword": "inlineLength", + "propertyTooltip": "Number of BLOB bytes stored in the base table row (0-32680)", + "propertyType": "numeric", + "valueType": "number", + "minValue": 0, + "maxValue": 32680, + "step": 1, + "dependency": { + "key": "mode", + "value": "blob" + } + }, + { + "propertyName": "Comments", + "propertyKeyword": "description", + "propertyTooltip": "comments", + "addTimestampButton": false, + "propertyType": "details", + "template": "textarea" + }, + { + "propertyName": "Not null", + "propertyKeyword": "required", + "enableForReference": true, + "propertyType": "checkbox", + "dependency": { + "type": "or", + "values": [ + { + "key": "primaryKey", + "value": true + }, + { + "key": "unique", + "value": true + }, + { + "key": "compositeUniqueKey", + "value": true + } + ] + }, + "disabled": true, + "defaultValue": true + }, + { + "propertyName": "Not null", + "propertyKeyword": "required", + "enableForReference": true, + "propertyType": "checkbox", + "dependency": { + "type": "not", + "values": { + "type": "or", + "values": [ + { + "key": "primaryKey", + "value": true + }, + { + "key": "unique", + "value": true + }, + { + "key": "compositeUniqueKey", + "value": true + } + ] + } + }, + "defaultValue": false + }, + { + "propertyName": "Primary key", + "propertyKeyword": "compositePrimaryKey", + "propertyNameFull": "Composite Primary Key", + "propertyType": "checkbox", + "propertyTooltip": { + "disabled": [ + { + "tooltip": "This column is part of the table composite primary key definition. Please refer to this definition if you want more information or to update the Primary Key definition", + "dependency": { + "key": "compositePrimaryKey", + "value": true + } + }, + { + "tooltip": "Remove the existing composite primary key definition prior to unlock the possibility to mark this single column as the new primary key for this table", + "dependency": { + "level": "siblings", + "key": "compositePrimaryKey", + "value": true + } + }, + { + "tooltip": "Another column has already been selected as primary key. You must unselect it prior to either selecting this column, or creating a compound primary key.", + "dependency": { + "level": "siblings", + "key": "primaryKey", + "value": true + } + } + ] + }, + "dependency": { + "type": "and", + "values": [ + { + "type": "or", + "values": [ + { + "key": "compositePrimaryKey", + "value": true + }, + { + "level": "siblings", + "key": "compositePrimaryKey", + "value": true + }, + { + "level": "siblings", + "value": { + "type": "and", + "values": [ + { + "key": "primaryKey", + "value": true + }, + { + "type": "or", + "values": [ + { + "key": "compositeUniqueKey", + "value": false + }, + { + "key": "compositeUniqueKey", + "exist": false + } + ] + } + ] + } + } + ] + } + ] + }, + "disabled": true + }, + { + "propertyName": "Primary key", + "propertyKeyword": "primaryKey", + "propertyType": "checkbox", + "dependency": { + "type": "and", + "values": [ + { + "type": "not", + "values": { + "level": "siblings", + "key": "compositePrimaryKey", + "value": true + } + }, + { + "type": "not", + "values": [ + { + "level": "siblings", + "value": { + "type": "and", + "values": [ + { + "key": "primaryKey", + "value": true + }, + { + "type": "or", + "values": [ + { + "key": "compositeUniqueKey", + "value": false + }, + { + "key": "compositeUniqueKey", + "exist": false + } + ] + } + ] + } + } + ] + }, + { + "type": "and", + "values": [ + { + "type": "or", + "values": [ + { + "key": "compositePrimaryKey", + "value": false + }, + { + "key": "compositePrimaryKey", + "exist": false + } + ] + }, + { + "type": "or", + "values": [ + { + "key": "compositeUniqueKey", + "value": false + }, + { + "key": "compositeUniqueKey", + "exist": false + } + ] + } + ] + }, + { + "type": "not", + "values": { + "key": "unique", + "value": true + } + } + ] + }, + "disabledOnCondition": { + "type": "and", + "values": [ + { + "type": "not", + "values": { + "level": "parent", + "key": "type", + "value": "object" + } + } + ] + }, + "enableForReference": true + }, + { + "propertyName": "Primary key options", + "propertyType": "block", + "propertyKeyword": "primaryKeyOptions", + "enableForReference": true, + "propertyTooltip": "Primary key options", + "dependency": { + "type": "and", + "values": [ + { + "key": "primaryKey", + "value": true + }, + { + "type": "or", + "values": [ + { + "key": "compositePrimaryKey", + "value": false + }, + { + "key": "compositePrimaryKey", + "exist": false + } + ] + }, + { + "type": "or", + "values": [ + { + "key": "compositeUniqueKey", + "value": false + }, + { + "key": "compositeUniqueKey", + "exist": false + } + ] + } + ] + }, + "structure": [ + { + "propertyName": "Constraint name", + "propertyKeyword": "constraintName", + "propertyType": "text" + } + ] + }, + { + "propertyName": "Unique", + "propertyKeyword": "compositeUniqueKey", + "propertyNameFull": "Composite Unique Key", + "propertyType": "checkbox", + "dependency": { + "type": "and", + "values": [ + { + "key": "compositeUniqueKey", + "value": true + } + ] + }, + "disabled": true + }, + { + "propertyName": "Unique", + "propertyKeyword": "unique", + "enableForReference": true, + "propertyType": "checkbox", + "dependency": { + "type": "and", + "values": [ + { + "type": "or", + "values": [ + { + "type": "or", + "values": [ + { + "key": "primaryKey", + "value": false + }, + { + "key": "primaryKey", + "exist": false + } + ] + }, + { + "key": "compositePrimaryKey", + "value": true + } + ] + }, + { + "type": "or", + "values": [ + { + "key": "compositeUniqueKey", + "value": false + }, + { + "key": "compositeUniqueKey", + "exist": false + } + ] + } + ] + } + }, + { + "propertyName": "Unique key options", + "propertyType": "block", + "propertyKeyword": "uniqueKeyOptions", + "enableForReference": true, + "propertyTooltip": "Unique key options", + "dependency": { + "type": "and", + "values": [ + { + "key": "unique", + "value": true + }, + { + "type": "or", + "values": [ + { + "key": "compositePrimaryKey", + "value": false + }, + { + "key": "compositePrimaryKey", + "exist": false + } + ] + }, + { + "type": "or", + "values": [ + { + "key": "compositeUniqueKey", + "value": false + }, + { + "key": "compositeUniqueKey", + "exist": false + } + ] + } + ] + }, + "structure": [ + { + "propertyName": "Constraint name", + "propertyKeyword": "constraintName", + "propertyType": "text" + } + ] + }, + { + "propertyName": "Alternate Key", + "propertyKeyword": "alternateKey", + "defaultValue": false, + "enableForReference": true, + "propertyType": "checkbox", + "dependency": { + "type": "or", + "values": [ + { + "key": "unique", + "value": true + }, + { + "key": "compositeUniqueKey", + "value": true + } + ] + }, + "disabledOnCondition": [ + { + "key": "compositeUniqueKey", + "value": true + } + ] + }, + "foreignCollection", + "foreignField", + "relationshipType", + "relationshipName", + "cardinality", + "minLength", + "maxLength", + "pattern", + "format", + "enum", + "sample", + "fakerFunction", + { + "propertyName": "Remarks", + "propertyKeyword": "comments", + "shouldValidate": false, + "propertyTooltip": "remarks", + "addTimestampButton": true, + "propertyType": "details", + "template": "codeEditor", + "templateOptions": { + "editorDialect": "markdown" + } + } + ], + "xml": [ + "name", + "code", + "sampleName", + "schemaId", + "refPath", + "type", + { + "propertyName": "Comments", + "propertyKeyword": "description", + "propertyTooltip": "comments", + "addTimestampButton": false, + "propertyType": "details", + "template": "codeEditor", + "templateOptions": { + "editorDialect": "markdown" + } + }, + { + "propertyName": "Generated column", + "propertyKeyword": "generatedColumn", + "propertyTooltip": "A generated column is a special column that is always computed from other columns", + "propertyType": "checkbox" + }, + { + "propertyName": "Generation expression", + "propertyKeyword": "columnGenerationExpression", + "propertyTooltip": "An SQL statement for computing values for this column", + "propertyType": "details", + "template": "codeEditor", + "templateOptions": { + "editorDialect": "pgsql" + }, + "markdown": false, + "dependency": { + "key": "generatedColumn", + "value": true + } + }, + { + "propertyName": "Not null", + "propertyKeyword": "required", + "enableForReference": true, + "propertyType": "checkbox" + }, + "pattern", + "enum", + "sample", + { + "propertyName": "Remarks", + "propertyKeyword": "comments", + "shouldValidate": false, + "propertyTooltip": "remarks", + "addTimestampButton": true, + "propertyType": "details", + "template": "codeEditor", + "templateOptions": { + "editorDialect": "markdown" + } + }, + { + "propertyName": "Alternate Key", + "propertyKeyword": "alternateKey", + "defaultValue": false, + "enableForReference": true, + "propertyType": "checkbox", + "dependency": { + "type": "or", + "values": [ + { + "key": "unique", + "value": true + }, + { + "key": "compositeUniqueKey", + "value": true + } + ] + }, + "disabledOnCondition": [ + { + "key": "compositeUniqueKey", + "value": true + } + ] + } + ], + "rowid": [ + "name", + "code", + "isActivated", + "sampleName", + "schemaId", + "refPath", + "reference", + "type", + { + "propertyName": "Subtype", + "propertyKeyword": "mode", + "propertyType": "select", + "options": ["rowid"], + "data": "options", + "valueType": "string", + "defaultValue": "rowid" + }, + { + "propertyName": "Generated", + "propertyKeyword": "generated", + "propertyTooltip": "ROWID values are GENERATED ALWAYS or GENERATED BY DEFAULT", + "propertyType": "select", + "options": ["ALWAYS", "BY DEFAULT"], + "defaultValue": "ALWAYS" + }, + { + "propertyName": "Comments", + "propertyKeyword": "description", + "propertyTooltip": "comments", + "addTimestampButton": false, + "propertyType": "details", + "template": "textarea" + }, + { + "propertyName": "Not null", + "propertyKeyword": "required", + "enableForReference": true, + "propertyType": "checkbox", + "dependency": { + "type": "or", + "values": [ + { + "key": "primaryKey", + "value": true + }, + { + "key": "unique", + "value": true + }, + { + "key": "compositeUniqueKey", + "value": true + } + ] + }, + "disabled": true, + "defaultValue": true + }, + { + "propertyName": "Not null", + "propertyKeyword": "required", + "enableForReference": true, + "propertyType": "checkbox", + "dependency": { + "type": "not", + "values": { + "type": "or", + "values": [ + { + "key": "primaryKey", + "value": true + }, + { + "key": "unique", + "value": true + }, + { + "key": "compositeUniqueKey", + "value": true + } + ] + } + }, + "defaultValue": false + }, + { + "propertyName": "Primary key", + "propertyKeyword": "compositePrimaryKey", + "propertyNameFull": "Composite Primary Key", + "propertyType": "checkbox", + "propertyTooltip": { + "disabled": [ + { + "tooltip": "This column is part of the table composite primary key definition. Please refer to this definition if you want more information or to update the Primary Key definition", + "dependency": { + "key": "compositePrimaryKey", + "value": true + } + }, + { + "tooltip": "Remove the existing composite primary key definition prior to unlock the possibility to mark this single column as the new primary key for this table", + "dependency": { + "level": "siblings", + "key": "compositePrimaryKey", + "value": true + } + }, + { + "tooltip": "Another column has already been selected as primary key. You must unselect it prior to either selecting this column, or creating a compound primary key.", + "dependency": { + "level": "siblings", + "key": "primaryKey", + "value": true + } + } + ] + }, + "dependency": { + "type": "and", + "values": [ + { + "type": "or", + "values": [ + { + "key": "compositePrimaryKey", + "value": true + }, + { + "level": "siblings", + "key": "compositePrimaryKey", + "value": true + }, + { + "level": "siblings", + "value": { + "type": "and", + "values": [ + { + "key": "primaryKey", + "value": true + }, + { + "type": "or", + "values": [ + { + "key": "compositeUniqueKey", + "value": false + }, + { + "key": "compositeUniqueKey", + "exist": false + } + ] + } + ] + } + } + ] + } + ] + }, + "disabled": true + }, + { + "propertyName": "Primary key", + "propertyKeyword": "primaryKey", + "propertyType": "checkbox", + "dependency": { + "type": "and", + "values": [ + { + "type": "not", + "values": { + "level": "siblings", + "key": "compositePrimaryKey", + "value": true + } + }, + { + "type": "not", + "values": [ + { + "level": "siblings", + "value": { + "type": "and", + "values": [ + { + "key": "primaryKey", + "value": true + }, + { + "type": "or", + "values": [ + { + "key": "compositeUniqueKey", + "value": false + }, + { + "key": "compositeUniqueKey", + "exist": false + } + ] + } + ] + } + } + ] + }, + { + "type": "and", + "values": [ + { + "type": "or", + "values": [ + { + "key": "compositePrimaryKey", + "value": false + }, + { + "key": "compositePrimaryKey", + "exist": false + } + ] + }, + { + "type": "or", + "values": [ + { + "key": "compositeUniqueKey", + "value": false + }, + { + "key": "compositeUniqueKey", + "exist": false + } + ] + } + ] + }, + { + "type": "not", + "values": { + "key": "unique", + "value": true + } + } + ] + }, + "disabledOnCondition": { + "type": "and", + "values": [ + { + "type": "not", + "values": { + "level": "parent", + "key": "type", + "value": "object" + } + } + ] + }, + "enableForReference": true + }, + { + "propertyName": "Primary key options", + "propertyType": "block", + "propertyKeyword": "primaryKeyOptions", + "enableForReference": true, + "propertyTooltip": "Primary key options", + "dependency": { + "type": "and", + "values": [ + { + "key": "primaryKey", + "value": true + }, + { + "type": "or", + "values": [ + { + "key": "compositePrimaryKey", + "value": false + }, + { + "key": "compositePrimaryKey", + "exist": false + } + ] + }, + { + "type": "or", + "values": [ + { + "key": "compositeUniqueKey", + "value": false + }, + { + "key": "compositeUniqueKey", + "exist": false + } + ] + } + ] + }, + "structure": [ + { + "propertyName": "Constraint name", + "propertyKeyword": "constraintName", + "propertyType": "text" + } + ] + }, + { + "propertyName": "Unique", + "propertyKeyword": "compositeUniqueKey", + "propertyNameFull": "Composite Unique Key", + "propertyType": "checkbox", + "dependency": { + "type": "and", + "values": [ + { + "key": "compositeUniqueKey", + "value": true + } + ] + }, + "disabled": true + }, + { + "propertyName": "Unique", + "propertyKeyword": "unique", + "enableForReference": true, + "propertyType": "checkbox", + "dependency": { + "type": "and", + "values": [ + { + "type": "or", + "values": [ + { + "type": "or", + "values": [ + { + "key": "primaryKey", + "value": false + }, + { + "key": "primaryKey", + "exist": false + } + ] + }, + { + "key": "compositePrimaryKey", + "value": true + } + ] + }, + { + "type": "or", + "values": [ + { + "key": "compositeUniqueKey", + "value": false + }, + { + "key": "compositeUniqueKey", + "exist": false + } + ] + } + ] + } + }, + { + "propertyName": "Unique key options", + "propertyType": "block", + "propertyKeyword": "uniqueKeyOptions", + "enableForReference": true, + "propertyTooltip": "Unique key options", + "dependency": { + "type": "and", + "values": [ + { + "key": "unique", + "value": true + }, + { + "type": "or", + "values": [ + { + "key": "compositePrimaryKey", + "value": false + }, + { + "key": "compositePrimaryKey", + "exist": false + } + ] + }, + { + "type": "or", + "values": [ + { + "key": "compositeUniqueKey", + "value": false + }, + { + "key": "compositeUniqueKey", + "exist": false + } + ] + } + ] + }, + "structure": [ + { + "propertyName": "Constraint name", + "propertyKeyword": "constraintName", + "propertyType": "text" + } + ] + }, + { + "propertyName": "Alternate Key", + "propertyKeyword": "alternateKey", + "defaultValue": false, + "enableForReference": true, + "propertyType": "checkbox", + "dependency": { + "type": "or", + "values": [ + { + "key": "unique", + "value": true + }, + { + "key": "compositeUniqueKey", + "value": true + } + ] + }, + "disabledOnCondition": [ + { + "key": "compositeUniqueKey", + "value": true + } + ] + }, + "foreignCollection", + "foreignField", + "relationshipType", + "relationshipName", + "cardinality", + { + "propertyName": "Default", + "propertyKeyword": "default", + "shouldValidate": true, + "fieldType": "dynamicField", + "changeAction": "changeDefaultValueOfField", + "shadowState": true, + "valueType": "asFieldType", + "dependency": { + "type": "and", + "values": [ + { + "type": "or", + "values": [ + { + "key": "now", + "value": false + }, + { + "key": "now", + "exist": false + } + ] + } + ] + } + }, + "minLength", + "maxLength", + "pattern", + "format", + "enum", + "sample", + "fakerFunction", + { + "propertyName": "Remarks", + "propertyKeyword": "comments", + "shouldValidate": false, + "propertyTooltip": "remarks", + "addTimestampButton": true, + "propertyType": "details", + "template": "codeEditor", + "templateOptions": { + "editorDialect": "markdown" + } + } + ], + "object": [ + "name", + "code", + "schemaId", + "isActivated", + "type", + { + "propertyName": "Subtype", + "propertyKeyword": "subtype", + "propertyType": "select", + "options": [ + { + "name": "object", + "value": "object" + } + ], + "defaultValue": "object" + }, + { + "propertyName": "Comments", + "propertyKeyword": "description", + "propertyTooltip": "comments", + "addTimestampButton": false, + "propertyType": "details", + "template": "codeEditor", + "templateOptions": { + "editorDialect": "markdown" + } + }, + "minProperties", + "maxProperties", + "additionalProperties", + { + "propertyName": "Remarks", + "propertyKeyword": "comments", + "shouldValidate": false, + "propertyTooltip": "remarks", + "addTimestampButton": true, + "propertyType": "details", + "template": "codeEditor", + "templateOptions": { + "editorDialect": "markdown" + } + } + ] + } +} diff --git a/properties_pane/model_level/modelLevelConfig.json b/properties_pane/model_level/modelLevelConfig.json new file mode 100644 index 0000000..7e5fd90 --- /dev/null +++ b/properties_pane/model_level/modelLevelConfig.json @@ -0,0 +1,214 @@ +/* +* Copyright © 2016-2024 by IntegrIT S.A. dba Hackolade. All rights reserved. +* +* The copyright to the computer software herein is the property of IntegrIT S.A. +* The software may be used and/or copied only with the written permission of +* IntegrIT S.A. or in accordance with the terms and conditions stipulated in +* the agreement/contract under which the software has been supplied. + +In order to define custom properties for any object's properties pane, you may copy/paste from the following, +making sure that you maintain a proper JSON format. + + { + "propertyName": "Simple text", + "propertyKeyword": "simpletextProp", + "shouldValidate": false, + "propertyType": "text" + }, + { + "propertyName": "Text area", + "propertyKeyword": "textareaProp", + "propertyValidate": false, + "propertyTooltip": "Popup for multi-line text entry", + "propertyType": "details", + "template": "textarea" + }, + { + "propertyName": "Dropdown selection", + "propertyKeyword": "dropdownProp", + "shouldValidate": false, + "propertyTooltip": "Select from list of options", + "propertyType": "select", + "options": [ + "Option 1", + "Option 2", + "Option 3", + "Option 4" + ] + }, + { + "propertyName": "Numeric", + "propertyKeyword": "numericProp", + "propertyValidate": true, + "propertyType": "text", + "valueType": "number" + }, + { + "propertyName": "Checkbox", + "propertyKeyword": "checkboxProp", + "shouldValidate": false, + "propertyType": "checkbox" + } + +*/ +[ + { + "lowerTab": "Details", + "structure": [ + { + "propertyName": "DB vendor", + "propertyKeyword": "dbVendor", + "shouldValidate": false, + "propertyTooltip": "DB vendor", + "propertyType": "select", + "options": ["Db2 for z/OS"], + "disabledOption": true + }, + { + "propertyName": "DB version", + "propertyKeyword": "dbVersion", + "shouldValidate": false, + "propertyTooltip": "DB version", + "propertyType": "select", + "options": ["v13.x"], + "disabledOption": false + }, + { + "propertyName": "Custom scripts", + "propertyType": "block", + "propertyKeyword": "customScripts", + "propertyTooltip": "", + "structure": [ + { + "propertyName": "Header script", + "propertyKeyword": "headerScript", + "propertyType": "details", + "markdown": false, + "template": "codeEditor", + "templateOptions": { + "editorDialect": "sql", + "customScriptVariables": true + } + }, + { + "propertyName": "Footer script", + "propertyKeyword": "footerScript", + "propertyType": "details", + "markdown": false, + "template": "codeEditor", + "templateOptions": { + "editorDialect": "sql", + "customScriptVariables": true + } + }, + { + "propertyName": "Before each CREATE SCHEMA", + "propertyKeyword": "beforeCreateContainer", + "propertyType": "details", + "markdown": false, + "template": "codeEditor", + "templateOptions": { + "editorDialect": "sql", + "customScriptVariables": true + } + }, + { + "propertyName": "After each CREATE SCHEMA", + "propertyKeyword": "afterCreateContainer", + "propertyType": "details", + "markdown": false, + "template": "codeEditor", + "templateOptions": { + "editorDialect": "sql", + "customScriptVariables": true + } + }, + { + "propertyName": "Before each CREATE TABLE", + "propertyKeyword": "beforeCreateEntity", + "propertyType": "details", + "markdown": false, + "template": "codeEditor", + "templateOptions": { + "editorDialect": "sql", + "customScriptVariables": true + } + }, + { + "propertyName": "After each CREATE TABLE", + "propertyKeyword": "afterCreateEntity", + "propertyType": "details", + "markdown": false, + "template": "codeEditor", + "templateOptions": { + "editorDialect": "sql", + "customScriptVariables": true + } + }, + { + "propertyName": "Before each CREATE VIEW", + "propertyKeyword": "beforeCreateView", + "propertyType": "details", + "markdown": false, + "template": "codeEditor", + "templateOptions": { + "editorDialect": "sql", + "customScriptVariables": true + } + }, + { + "propertyName": "After each CREATE VIEW", + "propertyKeyword": "afterCreateView", + "propertyType": "details", + "markdown": false, + "template": "codeEditor", + "templateOptions": { + "editorDialect": "sql", + "customScriptVariables": true + } + } + ] + }, + { + "propertyName": "Comments", + "propertyKeyword": "comments", + "shouldValidate": false, + "propertyTooltip": "comments", + "addTimestampButton": false, + "propertyType": "details", + "template": "codeEditor", + "templateOptions": { + "editorDialect": "markdown" + } + } + ] + }, + { + "lowerTab": "Relationships", + "structure": [ + { + "propertyName": "On Delete", + "propertyKeyword": "relationshipOnDelete", + "propertyType": "select", + "options": ["", "NO ACTION", "RESTRICT", "CASCADE", "SET NULL"] + }, + { + "propertyName": "Enforced", + "propertyKeyword": "relationshipEnforced", + "propertyTooltip": "ENFORCED (default) validates data on insert/update/delete. NOT ENFORCED defines an informational referential constraint.", + "propertyType": "select", + "options": [ + "", + { + "name": "ENFORCED", + "value": "ENFORCED" + }, + { + "name": "NOT ENFORCED", + "value": "NOT ENFORCED" + } + ] + } + ] + } +] diff --git a/properties_pane/samples.json b/properties_pane/samples.json new file mode 100644 index 0000000..ff5ddec --- /dev/null +++ b/properties_pane/samples.json @@ -0,0 +1,42 @@ +[ + { + "validateAs": "number", + "constraintsFromField": { + "min": "minimum", + "exclusiveMin": "exclusiveMinimum", + "max": "maximum", + "exclusiveMax": "exclusiveMaximum", + "multipleOf": "multipleOf", + "divisibleBy": "divisibleBy" + }, + "dependency": { + "type": "and", + "values": [ + { + "key": "childType", + "value": "numeric" + }, + { + "key": "default", + "valueType": "number" + } + ] + } + }, + { + "validateAs": "string", + "dependency": { + "type": "and", + "values": [ + { + "key": "childType", + "value": "numeric" + }, + { + "key": "default", + "valueType": "string" + } + ] + } + } +] diff --git a/properties_pane/view_level/viewLevelConfig.json b/properties_pane/view_level/viewLevelConfig.json new file mode 100644 index 0000000..ff3d190 --- /dev/null +++ b/properties_pane/view_level/viewLevelConfig.json @@ -0,0 +1,219 @@ +/* +* Copyright © 2016-2024 by IntegrIT S.A. dba Hackolade. All rights reserved. +* +* The copyright to the computer software herein is the property of IntegrIT S.A. +* The software may be used and/or copied only with the written permission of +* IntegrIT S.A. or in accordance with the terms and conditions stipulated in +* the agreement/contract under which the software has been supplied. +In order to define custom properties for any object's properties pane, you may copy/paste from the following, +making sure that you maintain a proper JSON format. + + { + "propertyName": "Simple text", + "propertyKeyword": "simpletextProp", + "propertyType": "text", + "sampleGen": "&containerName|&entityName|&random|" + }, + { + "propertyName": "Text area", + "propertyKeyword": "textareaProp", + "propertyTooltip": "Popup for multi-line text entry", + "propertyType": "details", + "template": "textarea" + }, + { + "propertyName": "Dropdown selection", + "propertyKeyword": "dropdownProp", + "propertyTooltip": "Select from list of options", + "propertyType": "select", + "options": [ + "Option 1", + "Option 2", + "Option 3", + "Option 4" + ] + }, + { + "propertyName": "Numeric", + "propertyKeyword": "numericProp", + "propertyValidate": true, + "propertyType": "numeric", + "valueType": "number", + "allowNegative": false, + "sampleGen": "&containerName|&entityName|&random|" + }, + { + "propertyName": "Checkbox", + "propertyKeyword": "checkboxProp", + "propertyType": "checkbox" + }, + { + "propertyName": "Group", + "propertyType": "group", + "propertyKeyword": "grpProp", + "propertyTooltip": "", + "structure": [ + { + "propertyName": "Simple Grp Text", + "propertyKeyword": "simpleGrpText", + "propertyTooltip": "", + "propertyType": "text" + }, + { + "propertyName": "Group Number", + "propertyKeyword": "grpNumber", + "propertyType": "numeric", + "valueType": "number", + "allowNegative": false + } + ] + }, +// “groupInput” can have the following states - 0 items, 1 item, and many items. +// “blockInput” has only 2 states - 0 items or 1 item. +// This gives us an easy way to represent it as an object and not as an array internally which is beneficial for processing +// and forward-engineering in particular. + { + "propertyName": "Block", + "propertyType": "block", + "propertyKeyword": "grpProp", + "propertyTooltip": "", + "structure": [ + { + "propertyName": "Simple Grp Text", + "propertyKeyword": "simpleGrpText", + "propertyTooltip": "", + "propertyType": "text" + }, + { + "propertyName": "Group Number", + "propertyKeyword": "grpNumber", + "propertyType": "numeric", + "valueType": "number", + "allowNegative": false + } + ] + }, + { + "propertyName": "Field List", + "propertyKeyword": "keyList", + "propertyType": "fieldList", + "template": "orderedList" + }, + { + "propertyName": "List with attribute", + "propertyKeyword": "keyListOrder", + "propertyType": "fieldList", + "template": "orderedList", + "attributeList": [ + "ascending", + "descending" + ] + } + +*/ + +[ + { + "lowerTab": "Details", + "structure": [ + { + "propertyName": "Comments", + "propertyKeyword": "description", + "propertyTooltip": "comments", + "propertyType": "details", + "addTimestampButton": false, + "template": "codeEditor", + "templateOptions": { + "editorDialect": "markdown" + } + }, + { + "propertyName": "As query", + "propertyKeyword": "selectStatement", + "propertyTooltip": "AS [WITH common-table-expression] fullselect. Defines the view as the rows that would result if the fullselect were executed. The fullselect must not contain a period specification.", + "propertyType": "details", + "template": "codeEditor", + "templateOptions": { + "editorDialect": "sql" + }, + "markdown": false + }, + { + "propertyName": "With check option", + "propertyKeyword": "withCheckOption", + "propertyTooltip": "WITH CASCADED CHECK OPTION or WITH LOCAL CHECK OPTION. Specifies that every row that is inserted or updated through the view must conform to the definition of the view.", + "propertyType": "checkbox" + }, + { + "propertyName": "Check testing scope", + "propertyKeyword": "checkTestingScope", + "propertyTooltip": "CASCADED (default): insert and update operations must satisfy the search conditions of this view and all underlying views. LOCAL: operations must satisfy this view and underlying views that themselves were defined with a check option.", + "propertyType": "select", + "options": ["CASCADED", "LOCAL"], + "defaultValue": "CASCADED", + "dependency": { + "key": "withCheckOption", + "value": true + } + }, + { + "propertyName": "View properties", + "propertyKeyword": "viewProperties", + "propertyTooltip": "Optional raw DDL fragments for clauses not modeled in the UI.", + "propertyType": "details", + "template": "textarea", + "markdown": false + }, + { + "propertyName": "Custom scripts", + "propertyType": "block", + "propertyKeyword": "customScripts", + "propertyTooltip": "", + "structure": [ + { + "propertyName": "Before CREATE VIEW", + "propertyKeyword": "beforeCreateView", + "propertyType": "details", + "markdown": false, + "template": "codeEditor", + "templateOptions": { + "editorDialect": "sql", + "customScriptVariables": true + } + }, + { + "propertyName": "After CREATE VIEW", + "propertyKeyword": "afterCreateView", + "propertyType": "details", + "markdown": false, + "template": "codeEditor", + "templateOptions": { + "editorDialect": "sql", + "customScriptVariables": true + } + } + ] + }, + { + "propertyName": "Remarks", + "propertyKeyword": "comments", + "propertyTooltip": "remarks", + "propertyType": "details", + "addTimestampButton": true, + "markdown": true, + "template": "codeEditor", + "templateOptions": { + "editorDialect": "markdown" + } + }, + { + "propertyKeyword": "pipeline", + "hidden": true + }, + { + "propertyKeyword": "viewOn", + "hidden": true + } + ] + } +] diff --git a/reverse_engineering/api.js b/reverse_engineering/api.js new file mode 100644 index 0000000..0ac5ddc --- /dev/null +++ b/reverse_engineering/api.js @@ -0,0 +1,66 @@ +/** @typedef {(error?: unknown, result?: unknown, info?: unknown) => void} Callback */ + +/** + * Reverse engineering from a live instance is not implemented yet. The studio resolves these requests only from the + * callback and has no timeout, so every stub has to answer rather than return silently. + */ +const NOT_IMPLEMENTED_MESSAGE = 'Reverse engineering from a Db2 for z/OS instance is not supported yet.'; + +/** + * Disconnect stub. Nothing is ever connected, so this succeeds without doing any work. + * + * @param {unknown} _connectionInfo Connection info. + * @param {unknown} _appLogger App logger. + * @param {Callback} callback Callback. + * @returns {void} Nothing; the result is delivered through the callback. + */ +const disconnect = (_connectionInfo, _appLogger, callback) => { + callback(); +}; + +/** + * Schema names stub. + * + * @param {unknown} _connectionInfo Connection info. + * @param {unknown} _appLogger App logger. + * @param {Callback} callback Callback. + * @param {unknown} _app App instance. + * @returns {void} Nothing; the result is delivered through the callback. + */ +const getSchemaNames = (_connectionInfo, _appLogger, callback, _app) => { + callback(new Error(NOT_IMPLEMENTED_MESSAGE)); +}; + +/** + * Collection names stub. + * + * @param {unknown} _connectionInfo Connection info. + * @param {unknown} _appLogger App logger. + * @param {Callback} callback Callback. + * @param {unknown} _app App instance. + * @returns {void} Nothing; the result is delivered through the callback. + */ +const getDbCollectionsNames = (_connectionInfo, _appLogger, callback, _app) => { + callback(new Error(NOT_IMPLEMENTED_MESSAGE)); +}; + +/** + * Collections data stub. + * + * @param {unknown} _connectionInfo Connection info. + * @param {unknown} _appLogger App logger. + * @param {Callback} callback Callback. + * @param {unknown} _app App instance. + * @returns {void} Nothing; the result is delivered through the callback. + */ +const getDbCollectionsData = (_connectionInfo, _appLogger, callback, _app) => { + callback(new Error(NOT_IMPLEMENTED_MESSAGE)); +}; + +module.exports = { + disconnect, + // testConnection, + getSchemaNames, + getDbCollectionsNames, + getDbCollectionsData, +}; diff --git a/reverse_engineering/config.json b/reverse_engineering/config.json new file mode 100644 index 0000000..f10bb54 --- /dev/null +++ b/reverse_engineering/config.json @@ -0,0 +1,10 @@ +{ + "errors": { + "NO_DATABASES": "There is no database in the Db2 instance", + "WRONG_CONNECTION": "Cannot connect to Db2 instance" + }, + "defaultDdlType": "db2", + "excludeDocKind": ["id"], + "connectionList": ["name", "host", "port", "userName"], + "helpUrl": "" +} diff --git a/reverse_engineering/connection_settings_modal/connectionSettingsModalConfig.json b/reverse_engineering/connection_settings_modal/connectionSettingsModalConfig.json new file mode 100644 index 0000000..fe51488 --- /dev/null +++ b/reverse_engineering/connection_settings_modal/connectionSettingsModalConfig.json @@ -0,0 +1 @@ +[] diff --git a/scripts/lib/commandOptions.js b/scripts/lib/commandOptions.js new file mode 100644 index 0000000..b9a91ec --- /dev/null +++ b/scripts/lib/commandOptions.js @@ -0,0 +1,82 @@ +/** @import {CommandOptionDefinition} from './commandOptions.types' */ + +/** @type {typeof import('node:util')} */ +const { parseArgs } = require('node:util'); + +/** + * Reads supported command-line options as raw strings. + * + * @param {string[]} args Command-line arguments excluding the Node executable and script path. + * @param {string[]} optionNames Supported argument names. + * @returns {Map} Raw option values keyed by argument name. + */ +function readCommandOptions(args, optionNames) { + /** @type {Record} */ + const options = Object.fromEntries( + optionNames.map(optionName => [ + optionName, + { + type: 'string', + }, + ]), + ); + + const { values } = parseArgs({ + args, + options, + strict: true, + }); + + /** @type {Map} */ + const commandOptions = new Map(); + + for (const [optionName, value] of Object.entries(values)) { + if (typeof value !== 'string') { + throw new TypeError(`--${optionName} must have a value`); + } + + commandOptions.set(optionName, value); + } + + return commandOptions; +} + +/** + * Gets and converts a parsed command-line option. + * + * @template T + * @param {Map} options Raw command-line options. + * @param {string} optionName Argument name. + * @param {CommandOptionDefinition} definition Default value and converter for the argument. + * @returns {T} Converted option value. + */ +function getCommandOption(options, optionName, definition) { + const value = options.get(optionName); + + return value === undefined ? definition.defaultValue : definition.parse(value, optionName); +} + +/** + * Converts a command-line option value to a boolean. + * + * @param {string} value Raw option value. + * @param {string} optionName Option name used in validation errors. + * @returns {boolean} Parsed boolean value. + */ +function parseBooleanOption(value, optionName) { + if (value === 'true') { + return true; + } + + if (value === 'false') { + return false; + } + + throw new TypeError(`--${optionName} must be either "true" or "false"; received "${value}"`); +} + +module.exports = { + getCommandOption, + parseBooleanOption, + readCommandOptions, +}; diff --git a/scripts/lib/commandOptions.types.d.ts b/scripts/lib/commandOptions.types.d.ts new file mode 100644 index 0000000..717cd30 --- /dev/null +++ b/scripts/lib/commandOptions.types.d.ts @@ -0,0 +1,4 @@ +export type CommandOptionDefinition = { + readonly defaultValue: T; + readonly parse: (value: string, optionName: string) => T; +}; diff --git a/shared/constants/constants.js b/shared/constants/constants.js new file mode 100644 index 0000000..2fd59a6 --- /dev/null +++ b/shared/constants/constants.js @@ -0,0 +1,22 @@ +/** @enum {string} */ +const OBJECT_TYPE = { + table: 'TABLE', + view: 'VIEW', +}; + +const INLINE_COMMENT = '--'; + +const CONSTRAINT_POSTFIX = { + primaryKey: 'pk', + foreignKey: 'fk', + uniqueKey: 'uk', + notNull: 'nn', + check: 'check', + default: 'default', +}; + +module.exports = { + OBJECT_TYPE, + INLINE_COMMENT, + CONSTRAINT_POSTFIX, +}; diff --git a/shared/constants/types.js b/shared/constants/types.js new file mode 100644 index 0000000..a65751f --- /dev/null +++ b/shared/constants/types.js @@ -0,0 +1,77 @@ +/** @enum {string} */ +const DATA_TYPE = { + char: 'CHAR', + varchar: 'VARCHAR', + clob: 'CLOB', + graphic: 'GRAPHIC', + vargraphic: 'VARGRAPHIC', + dbclob: 'DBCLOB', + integer: 'INTEGER', + smallint: 'SMALLINT', + bigint: 'BIGINT', + decimal: 'DECIMAL', + float: 'FLOAT', + real: 'REAL', + double: 'DOUBLE', + decfloat: 'DECFLOAT', + date: 'DATE', + time: 'TIME', + timestamp: 'TIMESTAMP', + binary: 'BINARY', + varbinary: 'VARBINARY', + blob: 'BLOB', + xml: 'XML', + rowid: 'ROWID', +}; + +const DATA_TYPES_WITH_LENGTH_MULTIPLIER = [ + DATA_TYPE.char, + DATA_TYPE.varchar, + DATA_TYPE.clob, + DATA_TYPE.graphic, + DATA_TYPE.vargraphic, + DATA_TYPE.dbclob, + DATA_TYPE.binary, + DATA_TYPE.varbinary, + DATA_TYPE.blob, +]; + +const DATA_TYPES_WITH_LENGTH = [ + DATA_TYPE.char, + DATA_TYPE.varchar, + DATA_TYPE.clob, + DATA_TYPE.graphic, + DATA_TYPE.vargraphic, + DATA_TYPE.dbclob, + DATA_TYPE.binary, + DATA_TYPE.varbinary, + DATA_TYPE.blob, +]; + +const DATA_TYPES_WITH_PRECISION = [DATA_TYPE.decimal, DATA_TYPE.float, DATA_TYPE.decfloat]; + +const DATA_TYPES_WITH_IDENTITY = [DATA_TYPE.integer, DATA_TYPE.smallint, DATA_TYPE.bigint, DATA_TYPE.decimal]; + +const DATA_TYPES_WITH_CHARACTER_SUBTYPE = [DATA_TYPE.char, DATA_TYPE.varchar, DATA_TYPE.clob]; + +const DATA_TYPES_WITH_CCSID = [ + DATA_TYPE.char, + DATA_TYPE.varchar, + DATA_TYPE.clob, + DATA_TYPE.graphic, + DATA_TYPE.vargraphic, + DATA_TYPE.dbclob, +]; + +const DATA_TYPES_WITH_INLINE_LENGTH = [DATA_TYPE.clob, DATA_TYPE.dbclob, DATA_TYPE.blob]; + +module.exports = { + DATA_TYPE, + DATA_TYPES_WITH_LENGTH_MULTIPLIER, + DATA_TYPES_WITH_LENGTH, + DATA_TYPES_WITH_PRECISION, + DATA_TYPES_WITH_IDENTITY, + DATA_TYPES_WITH_CHARACTER_SUBTYPE, + DATA_TYPES_WITH_CCSID, + DATA_TYPES_WITH_INLINE_LENGTH, +}; diff --git a/shared/types/hck-esbuild-plugins-pack.d.ts b/shared/types/hck-esbuild-plugins-pack.d.ts new file mode 100644 index 0000000..da8571b --- /dev/null +++ b/shared/types/hck-esbuild-plugins-pack.d.ts @@ -0,0 +1,13 @@ +declare module '@hackolade/hck-esbuild-plugins-pack' { + import type { Plugin } from 'esbuild'; + + export interface CopyFolderFilesOptions { + fromPath: string; + targetFolderPath: string; + excludedExtensions?: string[]; + excludedFiles?: string[]; + } + + export function copyFolderFiles(options: CopyFolderFilesOptions): Plugin; + export function addReleaseFlag(packageJsonPath: string): Plugin; +} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..6b7f436 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,25 @@ +{ + "compilerOptions": { + "allowJs": true, + "checkJs": true, + "noEmit": true, + "target": "ES2024", + "lib": ["ES2024"], + "module": "Node16", + "moduleResolution": "Node16", + "noUnusedParameters": true, + "noUnusedLocals": true, + "noImplicitThis": true, + "noImplicitAny": true, + "alwaysStrict": true, + "skipLibCheck": true, + "strict": true, + "useUnknownInCatchVariables": true, + "resolveJsonModule": true, + "forceConsistentCasingInFileNames": true, + "typeRoots": ["./node_modules/@types", "shared/types"], + "types": ["node"] + }, + "include": ["**/*.js", "**/*.cjs", "shared/types/**/*.d.ts", "forward_engineering/types/**/*.d.ts"], + "exclude": ["**/node_modules/**", "release/**/*"] +} diff --git a/types/binary.json b/types/binary.json new file mode 100644 index 0000000..9a2165d --- /dev/null +++ b/types/binary.json @@ -0,0 +1,13 @@ +{ + "name": "binary", + "erdAbbreviation": "", + "dtdAbbreviation": "{BIN}", + "parentType": "string", + "useSample": false, + "hiddenOnEntity": "view", + "defaultValues": { + "primaryKey": false, + "mode": "binary", + "length": 10 + } +} diff --git a/types/char.json b/types/char.json new file mode 100644 index 0000000..cb90374 --- /dev/null +++ b/types/char.json @@ -0,0 +1,48 @@ +{ + "name": "char", + "erdAbbreviation": "", + "dtdAbbreviation": "{ABC}", + "parentType": "string", + "useSample": true, + "sample": "", + "default": true, + "hiddenOnEntity": "view", + "jsonType": { + "order": 1, + "jsonRoot": true, + "source": { + "key": "synonym", + "value": "json" + } + }, + "defaultValues": { + "primaryKey": false, + "relationshipType": "", + "parentRelationship": "", + "childRelationships": [], + "foreignCollection": "", + "foreignField": [], + "default": "", + "minLength": "", + "maxLength": "", + "pattern": "", + "enum": [], + "sample": "", + "comments": "", + "mode": "varchar", + "length": 20 + }, + "subtypes": { + "object": { + "parentType": "jsonObject", + "childValueType": ["jsonString", "jsonNumber", "jsonObject", "jsonArray", "jsonBoolean", "jsonNull"] + }, + "array": { + "parentType": "jsonArray", + "childValueType": ["jsonString", "jsonNumber", "jsonObject", "jsonArray", "jsonBoolean", "jsonNull"] + }, + "string": { + "parentType": "string" + } + } +} diff --git a/types/datetime.json b/types/datetime.json new file mode 100644 index 0000000..9a401ee --- /dev/null +++ b/types/datetime.json @@ -0,0 +1,94 @@ +{ + "name": "datetime", + "erdAbbreviation": "
", + "dtdAbbreviation": "{dt}", + "parentType": "string", + "useSample": true, + "sample": "2011-02-03 04:05:00+0000", + "hiddenOnEntity": "view", + "defaultValues": { + "primaryKey": false, + "default": "", + "enum": [], + "sample": "", + "comments": "", + "pattern": "", + "format": "", + "mode": "date" + }, + "descriptor": [ + { + "schema": { + "mode": "date" + }, + "format": "YYYY-MM-DD" + }, + { + "schema": { + "mode": "time" + }, + "format": "hh:mm:ss" + }, + { + "schema": { + "mode": "time" + }, + "format": "hh:mm:ss.nnn" + }, + { + "schema": { + "mode": "time" + }, + "format": "hh:mm:ss.nnnnnn" + }, + { + "schema": { + "mode": "time" + }, + "format": "hh:mm:ss.nnnnnnnnn" + }, + { + "schema": { + "mode": "timestamp" + }, + "format": "YYYY-MM-DD hh:mm:ss" + }, + { + "schema": { + "mode": "timestamp", + "fractSecPrecision": 3 + }, + "format": "YYYY-MM-DD hh:mm:ss.nnn" + }, + { + "schema": { + "mode": "timestamp", + "fractSecPrecision": 6 + }, + "format": "YYYY-MM-DD hh:mm:ss.nnnnnn" + }, + { + "schema": { + "mode": "timestamp", + "fractSecPrecision": 9 + }, + "format": "YYYY-MM-DD hh:mm:ss.nnnnnnnnn" + }, + { + "schema": { + "mode": "timestamp", + "fractSecPrecision": 3, + "withTimeZone": true + }, + "format": "YYYY-MM-DD hh:mm:ss.nnnZ" + }, + { + "schema": { + "mode": "timestamp", + "fractSecPrecision": 6, + "withTimeZone": true + }, + "format": "YYYY-MM-DD hh:mm:ss.nnnnnnZ" + } + ] +} diff --git a/types/number.json b/types/number.json new file mode 100644 index 0000000..a13ca63 --- /dev/null +++ b/types/number.json @@ -0,0 +1,113 @@ +{ + "name": "number", + "erdAbbreviation": "", + "dtdAbbreviation": "{123}", + "parentType": "numeric", + "sample": 15, + "useSample": true, + "hiddenOnEntity": "view", + "defaultValues": { + "unit": "", + "minimum": "", + "exclusiveMinimum": false, + "maximum": "", + "exclusiveMaximum": false, + "multipleOf": "", + "divisibleBy": "", + "default": "", + "primaryKey": false, + "relationshipType": "", + "parentRelationship": "", + "childRelationships": [], + "foreignCollection": "", + "foreignField": [], + "enum": [], + "mode": "integer", + "sample": "" + }, + "descriptor": [ + { + "schema": { + "mode": "smallint" + }, + "mode": "smallint" + }, + { + "schema": { + "mode": "smallint" + }, + "capacity": 2 + }, + { + "schema": { + "mode": "integer" + }, + "capacity": 4 + }, + { + "schema": { + "mode": "integer" + }, + "mode": "integer" + }, + { + "schema": { + "mode": "bigint" + }, + "capacity": 8 + }, + { + "schema": { + "mode": "decimal" + }, + "mode": "decimal" + }, + { + "schema": { + "mode": "decimal" + }, + "capacity": 8 + }, + { + "schema": { + "mode": "decfloat" + }, + "mode": "floating" + }, + { + "schema": { + "mode": "real" + }, + "capacity": 4, + "mode": "floating" + }, + { + "schema": { + "mode": "double" + }, + "capacity": 8, + "mode": "floating" + }, + { + "schema": { + "mode": "smallint" + }, + "capacity": 2, + "mode": "serial" + }, + { + "schema": { + "mode": "integer" + }, + "capacity": 4, + "mode": "serial" + }, + { + "schema": { + "mode": "bigint" + }, + "capacity": 8, + "mode": "serial" + } + ] +} diff --git a/types/object.json b/types/object.json new file mode 100644 index 0000000..d5d23e8 --- /dev/null +++ b/types/object.json @@ -0,0 +1,15 @@ +{ + "name": "object", + "parentType": "document", + "structureType": true, + "hiddenOnEntity": "view", + "defaultValues": { + "subtype": "object", + "properties": [] + }, + "subtypes": { + "object": { + "childValueType": ["char", "binary", "datetime", "number", "rowid", "xml", "object"] + } + } +} diff --git a/types/rowid.json b/types/rowid.json new file mode 100644 index 0000000..87e649a --- /dev/null +++ b/types/rowid.json @@ -0,0 +1,25 @@ +{ + "name": "rowid", + "erdAbbreviation": "", + "dtdAbbreviation": "{rowid}", + "parentType": "string", + "useSample": true, + "hiddenOnEntity": "view", + "sample": "50554d6e-29bb-11e5-b345-feff819cdc9e", + "defaultValues": { + "primaryKey": false, + "relationshipType": "", + "parentRelationship": "", + "childRelationships": [], + "foreignCollection": "", + "foreignField": [], + "default": "", + "minLength": "", + "maxLength": "", + "enum": [], + "sample": "", + "pattern": "", + "mode": "rowid", + "comments": "" + } +} diff --git a/types/xml.json b/types/xml.json new file mode 100644 index 0000000..40c3054 --- /dev/null +++ b/types/xml.json @@ -0,0 +1,11 @@ +{ + "name": "xml", + "erdAbbreviation": "", + "dtdAbbreviation": "{xml}", + "parentType": "binary", + "useSample": false, + "hiddenOnEntity": "view", + "defaultValues": { + "primaryKey": false + } +} diff --git a/validation/validationRegularExpressions.json b/validation/validationRegularExpressions.json new file mode 100644 index 0000000..3078f2f --- /dev/null +++ b/validation/validationRegularExpressions.json @@ -0,0 +1,3 @@ +{ + "code": "^[A-Za-z_0-9$#@]{1,128}$" +}