From 142a5427b97c8143f9b25a5e9e2a68c7357b1791 Mon Sep 17 00:00:00 2001 From: Vitalii Bedletskyi Date: Tue, 28 Jul 2026 16:03:08 +0300 Subject: [PATCH 01/15] HCK-17114: implement base repo setup for plugin --- .dockerignore | 4 + .editorconfig | 15 + .github/PULL_REQUEST_TEMPLATE.md | 2 +- .github/workflows/jira_link_to_pr.yml | 30 +- .github/workflows/notif-push-to-slack.yml | 58 +- .../workflows/trigger-pr-tests-plugins.yml | 28 +- .gitignore | 9 + .npmrc | 1 + .oxfmtrc.json | 32 + .oxlintrc.json | 62 + .sonarcloud.properties | 4 + .sonarlint/connectedMode.json | 1 + .vscode/extensions.json | 8 + .vscode/settings.json | 11 + LICENSE | 201 + README.md | 8 +- buildConstants.js | 33 + esbuild.package.js | 87 + lint-staged.config.js | 17 + package-lock.json | 3635 +++++++++++++++++ package.json | 97 + scripts/lib/commandOptions.js | 82 + scripts/lib/commandOptions.types.d.ts | 4 + tsconfig.json | 25 + types/hck-esbuild-plugins-pack.d.ts | 13 + 25 files changed, 4404 insertions(+), 63 deletions(-) create mode 100644 .dockerignore create mode 100644 .editorconfig create mode 100644 .gitignore create mode 100644 .npmrc create mode 100644 .oxfmtrc.json create mode 100644 .oxlintrc.json create mode 100644 .sonarcloud.properties create mode 100644 .sonarlint/connectedMode.json create mode 100644 .vscode/extensions.json create mode 100644 .vscode/settings.json create mode 100644 LICENSE create mode 100644 buildConstants.js create mode 100644 esbuild.package.js create mode 100644 lint-staged.config.js create mode 100644 package-lock.json create mode 100644 package.json create mode 100644 scripts/lib/commandOptions.js create mode 100644 scripts/lib/commandOptions.types.d.ts create mode 100644 tsconfig.json create mode 100644 types/hck-esbuild-plugins-pack.d.ts 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..e1f6392 --- /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": { + "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", + "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", + "forward_engineering/node_modules", + "node_modules", + "out/**/*", + "release", + "reverse_engineering/node_modules" + ] +} 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..1126007 --- /dev/null +++ b/buildConstants.js @@ -0,0 +1,33 @@ +/** @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']; +const EXCLUDED_FILES = [ + '.github', + '.DS_Store', + '.editorconfig', + '.git', + '.gitignore', + '.vscode', + '.idea', + '.dockerignore', + '.oxlintrc.json', + '.oxfmtrc.json', + '.sonarlint', + '.sonarcloud.properties', + 'tsconfig.json', + 'types', + 'build', + 'release', + 'node_modules', + 'lint-staged.config.js', + 'scripts', +]; + +module.exports = { + DEFAULT_RELEASE_FOLDER_PATH, + EXCLUDED_EXTENSIONS, + EXCLUDED_FILES, +}; diff --git a/esbuild.package.js b/esbuild.package.js new file mode 100644 index 0000000..bac67ea --- /dev/null +++ b/esbuild.package.js @@ -0,0 +1,87 @@ +/** @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('./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() { + /** @type {{ name: string; version: string }} */ + const packageData = JSON.parse(fs.readFileSync(path.resolve(__dirname, 'package.json'), 'utf8')); + 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)); + + // if (entryPoints.length === 0) { + // throw new Error('No packaging entry points found.'); + // } + + 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], + }), + 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/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/package-lock.json b/package-lock.json new file mode 100644 index 0000000..5b44f9b --- /dev/null +++ b/package-lock.json @@ -0,0 +1,3635 @@ +{ + "name": "Db2-zOS", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "Db2-zOS", + "version": "0.1.0", + "devDependencies": { + "@hackolade/hck-esbuild-plugins-pack": "0.0.1", + "@types/node": "24.13.3", + "esbuild": "0.28.1", + "esbuild-node-externals": "1.23.1", + "esbuild-plugin-clean": "1.0.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/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/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/brace-expansion": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.8.tgz", + "integrity": "sha512-JZyDyq3D4AUifKTPOB7DELf6XsB3WdPuNxCtob1vFXPsSXhdAiHBWJ/tJ8HAc9aH84BK+5JFZLNkJKx3G9kzQg==", + "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/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/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/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-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/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/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/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..bec153c --- /dev/null +++ b/package.json @@ -0,0 +1,97 @@ +{ + "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 z/OS", + "versions": [ + "v12", + "v13" + ] + }, + "features": { + "nestedCollections": false, + "disablePatternField": true, + "disableMultipleTypes": true, + "enableForwardEngineering": true, + "disableReverseEngineering": false, + "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": "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 .", + "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/node": "24.13.3", + "esbuild": "0.28.1", + "esbuild-node-externals": "1.23.1", + "esbuild-plugin-clean": "1.0.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" + } +} 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/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..c78f5c6 --- /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", "./types"], + "types": ["node"] + }, + "include": ["**/*.js", "**/*.cjs", "types/**/*.d.ts"], + "exclude": ["**/node_modules/**", "release/**/*"] +} diff --git a/types/hck-esbuild-plugins-pack.d.ts b/types/hck-esbuild-plugins-pack.d.ts new file mode 100644 index 0000000..da8571b --- /dev/null +++ b/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; +} From 502e4ea661d9c389a80e2b9fa5dbc75b98e83324 Mon Sep 17 00:00:00 2001 From: Vitalii Bedletskyi Date: Wed, 29 Jul 2026 17:07:47 +0300 Subject: [PATCH 02/15] fix linter errors --- .oxlintrc.json | 2 ++ esbuild.package.js | 5 +++-- package.json | 2 +- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/.oxlintrc.json b/.oxlintrc.json index e1f6392..a1ef3c6 100644 --- a/.oxlintrc.json +++ b/.oxlintrc.json @@ -14,6 +14,7 @@ "pedantic": "warn" }, "rules": { + "eslint/no-inline-comments": "off", "max-classes-per-file": "off", "max-lines": "off", "max-lines-per-function": "off", @@ -21,6 +22,7 @@ "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", diff --git a/esbuild.package.js b/esbuild.package.js index bac67ea..954ac93 100644 --- a/esbuild.package.js +++ b/esbuild.package.js @@ -25,8 +25,9 @@ const { getCommandOption, parseBooleanOption, readCommandOptions } = require('./ * @returns {Promise} Resolves when the esbuild packaging step finishes. */ async function packagePlugin() { - /** @type {{ name: string; version: string }} */ - const packageData = JSON.parse(fs.readFileSync(path.resolve(__dirname, 'package.json'), 'utf8')); + 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', { diff --git a/package.json b/package.json index bec153c..f16f261 100644 --- a/package.json +++ b/package.json @@ -70,7 +70,7 @@ "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 .", + "lint": "oxlint --type-aware --deny-warnings .", "bundle:check": "node esbuild.package.js --write=false", "package": "node esbuild.package.js", "format": "oxfmt", From 73258fc0c1624924b76c8036ce9347cd689014d8 Mon Sep 17 00:00:00 2001 From: Vitalii Bedletskyi Date: Wed, 5 Aug 2026 14:15:30 +0300 Subject: [PATCH 03/15] add base plugin configs and folder --- central_pane/dtdAbbreviation.json | 9 + central_pane/style.json | 57 + forward_engineering/api.js | 26 + forward_engineering/api/applyToInstance.js | 21 + .../api/generateContainerScript.js | 15 + forward_engineering/api/generateScript.js | 15 + forward_engineering/api/isDropInStatements.js | 19 + forward_engineering/config.json | 433 ++ forward_engineering/ddlProvider.js | 4 + jsonSchemaProperties.json | 14 + localization/en.json | 167 + logo.png | Bin 0 -> 16624 bytes package.json | 1 - polyglot/adapter.json | 89 + polyglot/convertAdapter.json | 86 + .../container_level/containerLevelConfig.json | 210 + properties_pane/defaultData.json | 62 + .../entity_level/entityLevelConfig.json | 967 +++++ .../field_level/fieldLevelConfig.json | 3479 +++++++++++++++++ .../model_level/modelLevelConfig.json | 203 + properties_pane/samples.json | 42 + .../view_level/viewLevelConfig.json | 219 ++ reverse_engineering/api.js | 229 ++ reverse_engineering/config.json | 10 + .../connectionSettingsModalConfig.json | 1 + .../types}/hck-esbuild-plugins-pack.d.ts | 0 tsconfig.json | 4 +- types/binary.json | 13 + types/char.json | 48 + types/datetime.json | 94 + types/number.json | 113 + types/object.json | 23 + types/rowid.json | 25 + types/xml.json | 11 + validation/validationRegularExpressions.json | 3 + 35 files changed, 6709 insertions(+), 3 deletions(-) create mode 100644 central_pane/dtdAbbreviation.json create mode 100644 central_pane/style.json create mode 100644 forward_engineering/api.js create mode 100644 forward_engineering/api/applyToInstance.js create mode 100644 forward_engineering/api/generateContainerScript.js create mode 100644 forward_engineering/api/generateScript.js create mode 100644 forward_engineering/api/isDropInStatements.js create mode 100644 forward_engineering/config.json create mode 100644 forward_engineering/ddlProvider.js create mode 100644 jsonSchemaProperties.json create mode 100644 localization/en.json create mode 100644 logo.png create mode 100644 polyglot/adapter.json create mode 100644 polyglot/convertAdapter.json create mode 100644 properties_pane/container_level/containerLevelConfig.json create mode 100644 properties_pane/defaultData.json create mode 100644 properties_pane/entity_level/entityLevelConfig.json create mode 100644 properties_pane/field_level/fieldLevelConfig.json create mode 100644 properties_pane/model_level/modelLevelConfig.json create mode 100644 properties_pane/samples.json create mode 100644 properties_pane/view_level/viewLevelConfig.json create mode 100644 reverse_engineering/api.js create mode 100644 reverse_engineering/config.json create mode 100644 reverse_engineering/connection_settings_modal/connectionSettingsModalConfig.json rename {types => shared/types}/hck-esbuild-plugins-pack.d.ts (100%) create mode 100644 types/binary.json create mode 100644 types/char.json create mode 100644 types/datetime.json create mode 100644 types/number.json create mode 100644 types/object.json create mode 100644 types/rowid.json create mode 100644 types/xml.json create mode 100644 validation/validationRegularExpressions.json 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/forward_engineering/api.js b/forward_engineering/api.js new file mode 100644 index 0000000..64f965d --- /dev/null +++ b/forward_engineering/api.js @@ -0,0 +1,26 @@ +const { generateContainerScript } = require('./api/generateContainerScript'); +const { isDropInStatements } = require('./api/isDropInStatements'); +const { applyToInstance } = require('./api/applyToInstance'); +const { generateScript } = require('./api/generateScript'); + +module.exports = { + generateScript, + + generateViewScript(data, logger, callback, app) { + throw new Error('Not implemented'); + }, + + generateContainerScript, + + getDatabases(connectionInfo, logger, callback, app) { + throw new Error('Not implemented'); + }, + + applyToInstance, + + testConnection() { + throw new Error('Not implemented'); + }, + + isDropInStatements, +}; diff --git a/forward_engineering/api/applyToInstance.js b/forward_engineering/api/applyToInstance.js new file mode 100644 index 0000000..d4279fb --- /dev/null +++ b/forward_engineering/api/applyToInstance.js @@ -0,0 +1,21 @@ + + +async function applyToInstance(connectionInfo, logger, callback, app) { + // const applyToInstanceLogger = logHelper.createLogger({ + // title: 'Apply to instance', + // hiddenKeys: connectionInfo.hiddenKeys, + // logger, + // }); + + // try { + // const connection = await connectionHelper.connect({ connectionInfo, logger: applyToInstanceLogger }); + // await instanceHelper.executeQuery({ connection, query: connectionInfo.script, ddl: true }); + + // callback(); + // } catch (err) { + // applyToInstanceLogger.error(err); + // callback(err); + // } +} + +module.exports = { applyToInstance }; diff --git a/forward_engineering/api/generateContainerScript.js b/forward_engineering/api/generateContainerScript.js new file mode 100644 index 0000000..66865ec --- /dev/null +++ b/forward_engineering/api/generateContainerScript.js @@ -0,0 +1,15 @@ + +function generateContainerScript(data, logger, callback, app) { + // try { + // const script = buildContainerLevelAlterScript(data, app); + // callback(null, script); + // } catch (error) { + // logger.log('error', { message: error.message, stack: error.stack }, 'Db2 Forward-Engineering Error'); + + // callback({ message: error.message, stack: error.stack }); + // } +} + +module.exports = { + generateContainerScript, +}; diff --git a/forward_engineering/api/generateScript.js b/forward_engineering/api/generateScript.js new file mode 100644 index 0000000..33976b2 --- /dev/null +++ b/forward_engineering/api/generateScript.js @@ -0,0 +1,15 @@ + +function generateScript(data, logger, callback, app) { + // try { + // const script = buildEntityLevelAlterScript(data, app); + // callback(null, script); + // } catch (error) { + // logger.log('error', { message: error.message, stack: error.stack }, 'Oracle Forward-Engineering Error'); + + // callback({ message: error.message, stack: error.stack }); + // } +} + +module.exports = { + generateScript, +}; diff --git a/forward_engineering/api/isDropInStatements.js b/forward_engineering/api/isDropInStatements.js new file mode 100644 index 0000000..0273afe --- /dev/null +++ b/forward_engineering/api/isDropInStatements.js @@ -0,0 +1,19 @@ + + +function isDropInStatements(data, logger, callback, app) { + // try { + // if (data.level === 'container') { + // const containsDropStatements = doesContainerLevelAlterScriptContainDropStatements(data, app); + // callback(null, containsDropStatements); + // } else { + // const containsDropStatements = doesEntityLevelAlterScriptContainDropStatements(data, app); + // callback(null, containsDropStatements); + // } + // } catch (e) { + // callback({ message: e.message, stack: e.stack }); + // } +} + +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/ddlProvider.js b/forward_engineering/ddlProvider.js new file mode 100644 index 0000000..90a7453 --- /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/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/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 0000000000000000000000000000000000000000..ab6dcae0961762a1fe7f1ff7b0d7c9f5e68d7f7e GIT binary patch literal 16624 zcmdVCWmFtd7cGbc2!TM5AVGr@+?`Gc5L^@7-8Hxe_u#=pAh^3X1os4o1{!y08kYv9 z_`Wx9t(iA(&9C_}i`A>)R^6&wxAr-EpL6daQbp<0%NK86AR!^Wl$DWGLqbAMe)>E| z2U_s^rhfuo&n!PEen3L1iN``1qXB3D(rdP8E_L@CsoO=>ybMvnmrJCg6Tr-tfB809USd`4nEsWV z1Xc(>`*)Vb9kVh_%(R1VF3iI?d$EcqT}X z#_M=?vMgD3()iyS8ewyy2ommMm?6If2NU2e{3e=z?)%Ia#rJtR@;(BG6)zQvO!60t z1U&{-`qeulu2lk5$w3S8(K z>B2Tjf+F*MDWp$U?HcNe4|;fC2}O>Kpp&Jj#`Z+^G@k=c=`heG`>v9#+N}>2R6HFl zm`7ia+#)r4yLwCUH>bc=!Wu&X$-m*C41Gq*ET9S5^Q9;L^%BgelZApn4#(+@6BlR3 zH8vX_-c7&os?e&nQ3Pv3u?7M;~)tfZf(Isn@B`H}zkAvj@-uKXpOJ(cRD}RRMgf6kJ zFYjyOog*UZ(Qmp`d($-SkTi;fkwExz{7!FAh}Wa>@5SZu@vIfSkp~D255+PG4N&AS zy4!i+Bf4Nz;Q~(x4b5G&@A?Sa?JL73wVbHfLhig6G)j7LdM$G`Ny$V(kaZ*he)L_*jn5m3mAd_9*s=I@B>^hkEs$OtnW48g4H!zyitH$4CPm%Gp& zj*0Q`tb>r8SgTX7OK*wfdGqGGU_l4>E3`B;lP1XPX<)essOfJhd_|@me8c);HwGG- z^op09&wn7>*~+3u(k0jQ4k!xWbP!8Q_C`3o|Fo*8!+BQr8}m2MiNNPfdC7tXQk34n zy|MegRJvr=^{HUxv$i=J8j~z3MC0LzyI4j!J?56gYE$pQpNI&VgCwkT)IyZkiub>_ z8InH+VMG|365u7wmXX0l4m*_zuw?le^9b>%LO@F}gQy4@Ic(}q{@=1`ore~@2%c38 z|0^X)Nwvb4PN?umt-I-Ip5(V(hW`kS@e=M8HGkq?I~gk5?UN3hzYhTW(9_T`TcV~B zfCc>WGhD)4P@EVuX6R{Z!NZK=+1HA1O61zBc#`#2K^}guf&Ozsy(|TXGoARuC0)$^ zR}zwvzG=96lC+E@vGmD}U46Y}Dp3&;h*yFxjvzbMJaXtfM^wb1e-V;kZbz2YAzPjq z0f%Dlk;gd=O`--vbsDLmk37>xVE%oaj-=$I(x+8opWE&g{9&Q@IZ)N;2pJvWY)|b} z#|3o>Qp~JFk9we6V=DpG&&O0T`egM8;%|g_cng_S1XKl&iyj1W@Jhx=07e#cPV>jm zYX=FTZ`X?ZQj(Hyk}$HG?*tBEh&!|Ort8;0r?i?w_ny9G0y)k4@2ZlL84j;F>(@YS zjLCS1#&3Q^L<|}bB5ja8Onp}=*N0-?P-I6$m?hH&l3z3AZQ@haJRv47ILP5dQGyf+ z`%2^;9^Q~3MPUAFW(aqNXNAubE`^_U$}-U+;-n=dxo6)7niEh(fTm$ql9F_$FVSun zdk2(pPDI`S9kqnIeujno+WP~*P3`SZ@XPa0n82uY#!+q;c3sjUBIq29dgwWTYf65_ zK{~pRqghtW70>?liw4{yBSVM3R+#Cqe$)hgSefXLqZsM9T8XxYuvrP+r=+FTne@F# zW!E1bpbAismBr-aVo zG)3GGjSlB){KFHDPf>PP;CH+(n=+@T+yMd&j^LdMoTJ4C)82^L4#JW(qgS!g%q%P$ z*&Zv#lLfM)={(^LwsQ>jZDKx`X4fa@Y!dK%s{4pfRDQ`B8E>E_gA)@wS+3)<2lt=# z^^ZKBr&CWzW18pM*xKIXyft2P`k}?zIFQ?CQq%EJ-hlZGg^-x|EZPw9Es}td+kTPv zxOoFdI!-}}xlM*{-6M^R&-dC1M|hXo&~$g9jxn6dzZu&0OSNK+98;~~0i=mT zV8xR*iBWkwFP!q1 z9C7l||Ng*mLog2d_V*|zW7d16I8Zq9NUh-6D9`;0nn{=`K`w}u=6#hxhv$Atk>}|U zDQ^!+=4)u;*d+-AOPZf_TwElY4~rAo09nT}@(`5g^R!@>=-J|HV>^7H>o0~hU zPDW-5;ZKkW(KPf+NlZ+H-M2eJI}z)|;4bH^`ka*vrP{i>bWngeN9VOkepU2FJSZnW zUR>DJKtxDLNc0hLHkv8GVGSV>a-jn~z~dvO^Zh-VWbGh@rKM4A$E_nU74C0EpElxa zYq>e5r1Di^hjkMxrjHB?KUP~R4?H4BcSbnC=Qx6^fzpN|9#Fp~s4+i;HePgnp)tGu zYN~ER{J?`av!c9w^lC$eLGr%QS$oXQvuq{rtK+fVfB9s7N(`Ou zNJ2?%!n3Teld=G+?)V1jfME1GYE;Lvf8Xo5MUpQ#qh&wwP{~l@cav<>YU)2Ik z1HX$eQBhIc?hMK1+LAPQvz7X71G$b1wV{Bq;Xi-Idp)lZx54RgywvgF`vBnSa?uv> zAgXu#gfCQebmXXCan>5p2?(T}p1M5T!vfR|4PhQv8GViR?uYYh9wek6whp8ud!Or{ z;^X1bCaVAp`J5i`+FTM(E-pqz0uN~BwHK8A)=iB;?t}!`Lw_NeNL){&J>>dygya5r zWiAYtN~z8LD5B4#|F+rh&Yg%&$E>~G>+#`!Z?+;6z6Z_Osd3qoDUy)yjus(zy2|~xw%1abL=6XH8g5{Z%&i@YeE0&*XZ^p ze`8gFAA~=KVRJ1W7sw_F-W}FG-F?LeCe|NAYI@jZsZEsYdx~k9f9|iKxYve1{?@WOw8N#30k>@Nc+5Aa*8{j} zz~Yz>KJ_$S)_25%9)1Am7pHeVjQ<*zNSA7!keO-gYwqOk-U!vdGY~B-JF>8}G}TUp zYkka9QG18lRw+-1&$Vbl{Ys|nUB6|Hmqi^x)P`V|=X#{|MaI~4%5A!YDU)n+Ov=Sx zqYfzP_D8w$AmCMphlgV#8h(dilm1!l4$iy~0YJ&;AC^=6)#tIEAFyQ1DCzv<)YMxF zX|>lxL@vY3MZ5VxDUGFZBMw*>B5IEXu3Yhef8MoHsF#ajF9J329 z?W6HbfeFvdz3ZV)pQ~eXw`o;H@8iJ)+W)xIw*ktn9RWIIW2bWX6KBQc<-Forx=+k8 z*J-{pp51@f?7A~vfGsySJxvn-+Gu~eq#sgVRi!Lf9$C#AjYli}3LBj)duCe@XMo&Q zVX+SvK9@=tKbuDvX%7LM=DJ|qKfX7BuL8&k+;-K_-Y%MdrCP%pZN)36s2B?`NK8ul z$VAA!;^5}iP!pQ{QX)@9N3ki?adzpmrlvqEF53tV2M5Q|IQkm{-M!1aaz54nz`VDb027ZBSSJpeexDN0xyv@hZXD8c(M9ll^BE@-PpEG%(a_%^Q+^lJd zU}F8Rs?o!Ju;>!*Q(#V5Dx1Gq=K5V8!0u1NL7NMfx%4b7BZK@3Y3+cQv7RdAYhy^~ z%^p`tEHGyGcW|iu=RQ?|a0h;Yix<{uvGy-}K(o(F8}d{lfO!SCL5c~KMd1e_CfY=R z9^kcyG+v05OCKp$eF0fv(?@cb)ce>*GA>Yht|E(HuLc)5GHR!wbl&c>m>$KF3ni{} z_@Msn^z-SDr*h<7`BJ8#s;g^TKX!j~kzoS>Pm$N-SkD1`e_2mXWfi5E$O#hF8hjg0V(T-O!8e*MqKQe}1Nc?m4_;nE0!vP)=H}-9;fX3#g^HOL zhlc~sa`y~}US&4|@Rl<}iz^v@J)L995^n21_OoS$YfAqcC zO6y+~-784iovk1%%JDA!r)4>o(bpeK7R$gaD^8zGCnt@0`jy2t#{o{)GbR8X1?D4R z(w@N-yuGWbu}aw;r^74& zi}v@Y0{*l2Pbikq_7Bq-80u6o;?!Oz{K@g)VWY5mAu* zUs>?Ks0G0AWQ&xt0e8hHxcf6Wrh;M!5QHh325ZACRp(FOJZ{+$yxq;$UDhESaDE=% zv*dd=)_y)I&kTJK+R5svXj+LnXqDdA4wZPTQK>fsD6^A`tJAYHuhk$7K|mh*>O-eF zj}X72*-dWG_Y4Cb+t0=~ZV##kYFTOTr~$URTJeboG|pU8XQR6ulPo?q`W_Sv>hQkc zUaZ`^#Gh$$H1s~-1^7$$3}Ek7K^%_u6A8H{2!OoXiv!F0F<2#WX=$lRPuNRqc!o}= z@6DCmpRb4W3CO7EB+E*<;(mx@o3cWA1KD*i12rP*!l_^VUtM`*QMHC+ zIZsU^VAxPHv-%VB!bK+r*?jCFg|)RVRiMYS+{e)vQl6M)%4TNPkzY@ZmY)DUZvmLG zR;oE&CE!5;6H}iB-zN;uMFYaLbrXnf{~tu^f6!>P=#)qZFx-N9+P^x>2{EEA+sMO1 zJK$=4fHkgpTyw0mmBRn zuh+32E}Oulu@^r~d`cOEliOa9g(W%^i}(~!7H?o_zCBYthu@oY+p5^(U;`u4dcDzu~1s4&0A0EprGHxPh+x@yrMaBc_O zG`%^9uNW|7Kp(;5;06-sKvf|WWArLr@jUpmFlo|B94$l(L$kZ?M!z6XEgEG#GpF&{~#ueeV$^k0Cn%OyS?RMFEr1R|a%4<&2u zu{E3mX#WY=vFv{js;K-eJx=2GI^8UYF>VLW0b-TgWii>r*)trOk3oSq+6!?T*UJ?J zS6n363|j`=L+v2N4(>~XcXE7~SXmX? z{=_JnL-;Q)E<`WqO?|Q&g{=NkSGMlIBOj2pQSF)?bS8ht!qT!=m|0v|31kH(M9&nI zg!kx7rvh|K^L+2A9!|mmE!q`?+Ol6-_8dKU%;0cW5J$aG!Fatla%|-oaHw6{mf2`@ zRn?a2cPgr?-GFZ0(8W&gy-FO|dtl<=P__|*eU6aMU$HLAZX9?}{K44`JKZoVO4hYfRnnM#%oZ1-fEpFba-S!nsFYiIy6oJZ%pZHVc9Qq4PnHx@ri z=9`Xp*C^dn5E<aR#L@o^4GV#zuO@>jMTa~gcBQ8VbNIndlOR(^Fbrfhv>R+v zpn38ynDcl)*fM!7Ysq?!)apo4soMk5!xKNniZf(T_a3;PMt!M9fH-mt$OqJkHQzyj(3{!3a)S=HO-#(= zkMN{P7@UK;l7|D&2!{_)kuetv0uCVTC0UrccIPo@q_!9s^XcYLXWw^OUkm?*-!ERndpS8{@h$R z;Py_pMq)b{Z?9mmndxa?N3yFW*9imp8-Xk5qlP*ENTxNL&)!PazEacn{Na9pl5SxJJL!`Awao@Zcw4H{VrnJ$1c?8*qJbWl}>z z<$s;`uP!p|e*5ZS$|v8tJ}sjQHuAb;_oMA@zAP~6rz_}r@BOYQ)JPiwJ1Z)_gaK-- zsk7W^pvPziUo;e8jnOQjDVWOSB0%aT#l=6Xt6v2!)LGU! zEDM8s@TvB&rOeW{M$%k2Vg+25oClcGc|QP2JbHTi_Xw9cz!=VD{Xs=MgbT1SAms+6 zl+FMesq?@02KNk+Jmv3>ms)_c!eYq;xB*fIf5gvyf-Wb?xyUnGzj;WPNze5B{SaWZ z>D=Zo96K(E0HLoPUHD8uO898bEZHdF@&4+`MQGJq;Q?_R=c2pt%aEq0q!UvfZ#R%j zvYxBVh8oiqbawhz@ub+;*l4x6+v`vu6e1;Q?4X_5lap#s*~dhB`5$6F`~Z{v27-XS zG^<8Or~~3XL~SU-6cA{@EF@_fc=dTg0LWU`!GNR39?$LB7=sE80RAqLPw|9e9J zukHi_fd83{`v2r?$-tjm9@s#d_Jx)x@AR3-b0B-`i!34h5y;K3-LML z@OVhr2e%YZ&?hTwN(<&go*fI{dfSOyIIjNKQ6(tJYpu+%ciXb7Tt zm(s6~Ta@AR#B-|ca!Djt`H_1m8u>_irVI4Lu*WTq1`>(@b}AF?8z2kSt_1SKnrFw2 zUY|oeR0t{<$xEU@kRiF;&Go*n*g!H=mcS#kq^*$W^Eh!^tMmMT-11{o_4I7c`7|1f zcV;5F%;ffuv)88kwChD6g-ou$oE%f08C^Vox9IKb%M+3BumyvpMe5KTnKjXYTYw9RCy#odXEb&h|9tr(nkcAnpm%k9`*6kW(~{N}u>p2$6cN>FZ{Z zw?E=Rxw~Q!vmaO$G>TFEUffM`RZ*ax-Z`1wy(L$fj>ZwPx6b?`W2{6bp%n464wCoh zETWlf|Cd|xoQ8^lX7_e!1uMH@-zSK?mSID}7~k)5NDF1V_w((3f|z=5qIilQF&@oy zNo3n;9ZDM|1P_}djHiXAm1P#gi}9ZRGm!R1hc|dl=vj7WWKlD&{?Iy4yFd2x?uip& z_@7%{$s8;1A|lIHHlYWgn0S-LylJ#oW#K;t@+iVxzs@C)Pwl;ub`$=7ETtc7PeYDb z_Z9M@&@p>pfNY1;ZG+r>+A)%m=q02Ph`H05KH|#-ThaaTIk0qwSmK_+8z;KmM-9+N zeuhl`1YUMP)lJ~z!=W*eW~I#O)>~W?ylQf#V;905oEywfM7a63ADk!eNP!iEh0ZME zH1JUVh@pkgJ-?GgE8(o1P}bre5jgm}#ikF8lwM-p zFYhSg9$B0HH_;HVV%tdNWj6xHsc2&nAyLmwwk4m#Si)_>+vSm}d%Ke%1>pz0+a`)~ z+}9N`_)*vE^A6!BRnAx6GiSOFi%zlKkKXRUGrPeGzGAV&sNY2W&yV!@4=w#YY~!Wf zDgTr)B2o-OQ8#$T3%&p{02;F={>?JJHI$X5`GC4Pw{A7HhkYmF5(9R-x$+Ip#Ub4l z3-2JgM0#p+xqMmUSgdzeI21i z(^}z24tnUg^@qd@>NnfpkRRh8MVZ7V6gq9qc2K=GPabJD>1|^Z3lf1RI4=F@KZMs8 zZvEXoZNrCh=$g{>t=dexVa&`_+YyojrJGP(k`NZ=1i2?ZXwQ{9#N(DCiVl!#zHBqQqH8&rRV4;tZxFx-j0+PdqF4?9yWKl2v?MdKsLev1<^kVmh! zqBFca?60AyB)ybx-MqN=w#YCm9K9i-m95FMVS((kJU=wQm+o>ItVP|#aAn5cBEY7z z{BnhRM=`J_kF#;_2FujVo&0rQ6Ka2v;@3i(bk!T=MzZ)Y? z6=es>oxH5SuB^%&_m=Im>1K5F$@fbz%g4|n;zulzvau`w?X?73%YW=YK3y%1pR*i} zBTI=1wVvgXL)-|Jx(@%;>xN|y++~V0__HnNqGf_}FNkvtZbjX9A$L8fcC5nBPWLjV zf{t&p_cW)2gN3ozo5yMQre^IZYO>178NQ0gghJ#p_-j|gTAWW7La^z41&B6|(I!sj z(3ERy+?O5tSlPNvzs%UI%yXu6ee-3fyykhhs~)k+h48N(J?sSXc-oKo08aY)HcvuF zDWKs#%eF6&lieq$Rrg4`xDqndx_B|ln*Fkd^JXDiu=Gr(Qv4(>>+cSeX-&)?SK@{+ z<#0V)ouA0h=2R@n%Vv;CSL!Z*<7WrsmAKGq1{95mw^H|{bFQoc@y&raH%GC3E^=2~ zqRoe1`=)NOY46}B=fej>k$&-9)u?)v*=|%<;{hZndHQIq;2xJQ9K4HPqV>DZL|-Z_ zPvKW8-&aW?#g!O5&BYvbjlMQ^RB|@f*ybQhzNI45DqKc?Cy&~V*THoVwXNBc%T^De zgXNK%+=ci+TMPF%`vpD0)ltvolKN#Tp0xmuOK!63O z+^&W(5 z+0|oog@}A?y;-7p%Kt$RK4hq2owQ`K?=Wnhh}%+S*Q z<~HO`v~nk5w>r7yJS{1;ZbLxz7MeKVY4%T&R?&UrXfR!Dpx<#iS(WvIw=@<0-!FduJh;!z1JQ>i*8#l^IQT?+)HYO5!U=EZpgBT28T-F|8eyFV-;B z)ZU~jDwgMX3&2y3k6fZglTXr;)g|ygiarndal7?NT8B(^nkp-66j5Na@cENX39m}# zu(h379C6Q!%_&BK8aP2LtG_9^ldV>23T zI(z;@CJcEI$-y|vOLS4g>6Xw7J5z0o=SOi5M7&q}16k11O?4upq-4PVFo^js*Nh2x zn1R~j=wq{+#xIxxGxB`vjBDH@cLtEq50V(o4Av^8A z1}bDocOyQiAjA|8EY~9YEm@_P8fzk&8*Hcjhpl0ZooSdrAl7TM?S`)tzp2Xt zv&t$$JUJ>rO;pNZf3q5l~H(aB*JJJ^byH;~F)!^u);7 zF{bxq+y0JN@wM>V_6W~d4j38Q{_Y-3;9$Mi_9slKYkWzDQ6UJH=VSzSpCSZy?FA41U+{neN@&xX3hbmS8K z9G#$DVXwQtbaM2r4GTyE_iIaV@TR+o#9R-7Ln%Aa zcG3~O924og9`Og+#X8ns4#^oZ>svcmb&I`@o++4rQoauFcbyF^4FypUu+US*N_qO; z*nXZ)wdk*K5{v&3>L*ye(G`-ZquG)Wd(pa?Qo_AD^DdoXUAlz-yUJq7X$@Pn*5c^- zsry~6?U&}AZwK&vN=BFaVtJw3k&}=nZnv=k1$~~^*a{*_m@I<3zS3$Ecs;B%4k4v7ihtP5F>2NQ!_h@JS z_N4<+IlVJ&rI1tNgzvRMY=>KJro!jP&IiZ%lWIvNbZgtw>zv3HJ)FItTu}}`(x<&AakMR2TdSS zF*CaB+_X#=iFR5h0q44bavcm`am1t_5A{YTn3_97ibpU>uXR&2VMg1R9Ft&ZttN1f zA5x>Ss>Je$ZLOB>tm{?x7x5edL3~I1_CBBSNAQmyr zSvl~`fy523G264f#ws~&wq0Jo8DAn&QGodIkP4y{Cc?>zLk<<2&(wO^I|a{Py6knMykhSf%DJ$okl(0Oj| zK5ZYW5(Mo?epbKYQGTs}S_W|eml0zAR$u-;9^5h)CB$5baIj1V+-ANS9d-b)mAQ2ibi1>KaX1t zxz}A$R%xz=0GFnLe94-SBfc%O^T_*+ZHu_tgj?OCT#7Dg9J`0PFyIF#VpS;=+s~us zhR0oiaD;U3Os4-rG*OFnxNu)>MN2I9F12IuH0t%M3N+NFGro~v8HxdSk+6W+wrF-V zirB`7E`;TeFSpj)S-)lTCogCd%s59}24{-Kq=l3MkL_EXhProDTyC+J#P)@6w=Y6u z+}2~sHa-;OOnGMyjtoF;MbmwoAJmVTys*m^if0%Ufo(qhfD2Nw#mrzO8D96G_3t}i z6dNuP3wQ-mhiC3c@Q!le3I<#DLuK!l8l^K04O=2dESI|D7LSY$4hw*-ObG>?&Nc7I zUpBRl7{roJ)_@*G9~{Q1PIw}wdvW*uPfS+(o9$L7yv~~+3l$p|(lr~_bSl%G84c|W zpQ9h2FztOb1pxc0%YD!dzslC!#M!KHXkvIH&-6iNG#Vbi#UVj05E_cl=F~GUxWsub z;XvlvkRTP^=Bl4YX1OvDZM*WB+d34W2thKefiC*0)TJO!RUQEjBkR65wO1}w__$2i zo0D&+!`8DdzYcLQu$Z~GGqL>W6U(pKDa+3M(dlKePjuH}Eb*-QVwI)&p*nh$k$YkT zYzgYUm(N+@%a`9^r~tCibfJ;1(Tg(K39qll;Awz9{d1yYbA$ium16b$1`( zoyH;`RjP5AAI+yL(WQB=T73uI2jOjL9G{K3X(^T@(C_)!@8+S`XT=B2h*d+nEvx$KY0FaG5Yy;dqw+I^!KgJlaqvi%~QM~Sq6`QTfup*Ajuqj2yW_;AI~y-wVR; zMtqgaY_FGNK+XpUjP*##t4PNS;2m?u|2+C`YlN<>Sb&b3 zTR7gBOi@r_~0$)aHDR8Ju? zq^0K{M4~@QLr0Y{(v=y^XSVcCR;WB(w&dZYHo`m$jsT52BeT4Z3o5Dn&1c?Q)nvI? zq>GjHilf{&lkE!x-+}EFHg~Z-$m!)F28Ueg>(wCDxwGiEAnuj3*py76oow45&Byl7 zLy{FPd7c-T^76*jew~#F;vV<4 z!nHIP_(@%?hnMz(&G-+O*XjcPz~<#UX29F9=BT{Drb{-TAq#wl^Ru%SDeXpglrH%} zs>3RGxp3>Q$Mr4I*0%@HR%-(B8>*=vP3Byd3imJTr4sYR2zVr}uberh0Q5&5yfe&)@tAYHEw04H(y+ zsx-Hq5niUVeac^Rjpi$IQi*g!u4bp_RESF^P8L4!+m`YJ0UBo0=j1Q%-G9=5@!GIe z^lsRj*ce!mP#UzmFK+^8b(g>l#%K30TB+KBi8gNR3A*tkaImnQ;otvtVeL!Y!%%HE zJ8MsCV?++vwPF_z?<00!?BBQZaYaCyLtJV()*S|rhU@s9rn;(bDVZ52AdRHGjD)aR z_S2l?Gi~HbB4-o#P}{!L2=kQPvsGti>WLAX;Q71$Z6SvoJkP)Pw)n?_xD9^%C0a}S z62{H!|Bob!WJ?Pfy$O)C!5NFD3YF}Kl;r}Ky^Jp*rn~k;XL=dF z{o8#^!_3tTl*%b^H8d(A zUu@`tCsah!R=lz?Nty)|Sc!TrGPum=V_la28gi9;ey=$e)jxd!&U9ro<|vQquKn~B z7LaYnP!9BB#1p8f>UPhT+8cR#+YWoN-@ou7ZCTKA^W#gfs<%!G>1x zK?jbV;>*_( zVwBq*i8#C(MF`P4F(laCdzHQ8wFEvPZJc$8g6?Y-U~*s6G+d8-(_*8LIP&s5hY7Vq zp3_z+={>k3a01eqbUqqaQwYShhd?Pz9JtnQhhxi`9!ufcj+OFlSvXmIt8}ywy=pg1 z<*JNwllVzi(%bQr=|#S>fwT3;OVNlKp7MJqO2Jd~xV`l2QTD)#*O{KyJ$KgPB;atr z8r$wQ+2ww?VYpuJzvWM)%j@Nn99kNko>b;*`bq~bgcPTXqzl_UhKOjH@?mh0=g80@ zSN;8GVX!MbnoYjjtCf~3+p|=HfO+KFELNASSNdnIE*F8hCmYC&|K2tYYOzkRW=neM z3kg0fZ@~`kYl_OcQwgZ#tAn2%Q7;6&5(?5&IuH$WMbG)OrZ4^7_SS$pXW;I@;!R$6dVa8+jYB#-iye@O#R>M? z=3Csb(X_aX#u|7V_*{x$Q)(knY~9Pl0>Q!XIdX(; zrO^E2{l8T>hk=Q$r|kynMh$0?8--#~R-=Eot>kocddXi_<|1u_^)`Bl2K*#(CsLc; zTh`yydG7_KHkUJWgQSMDCp!Gq;Pepoq|NR!OJ`Kyucga9TDww%s=bo7$QG{399Is= zf!tLMdT&po>n@}*9{M&KaK7MU-4pD`TNkwr(`R>icj9*#u!=h)C>pn^@omFiPglEx ze>m%}5SoYd_H})S+e74(i|e?ww2mS`$S};~$%h5fKt|X=ug6m=g zx5cTSZMv`Y%dDwDl4j=!8r5S0#lLJtUDWlK`U&P=)Sp*9c4>fQl6O`4k3UtQYgFBX zrv1Z$pab|cF^BUd##|k(9^hJ7mrh%Q3Wd%HCQ7JC)VelP+@M;4`Y7*ar2t-y2{xLH9uz z9Be+(B4E#N6OH51X&Tl_stOZmjG61KJX;!)_`gGFWH(yb-HUFTBvG*`TSEYFamq8Z2-k%zTc#=N5pOIgQec{94@L$L#L(fAy z2t^3~sVv_t(fSg$#}6zh0}Crp)eZlz3(A;WWL8r0cEE0TMm~O(4G%(Ky9N9|i@Zqj zHe|)8A~?Wmu@lh@e-3~R8k)#OyWpl3px#81Zpv%K&Mp<$KLzX-=EaTv!cWP9Jon#9 zp-|_F*ZiqX;-C7j>$-~}#UL*%SFWRrQ8X8&yajv^N5UOrKCvfj|n z3UdYL1B=oM=6*O(!f?bW%n7&;t$mNX<@=8kk?0odHRG^iplM^YsCEq9N`@(6-@f$_ z*fKX}Ikw)!W1IIjPp_qxvYgaE9B3ppXH2~^=n3d-=}sN_YLp567a1YbuN6h0H+_=p zh#!^GfTA^>NF*g`2HNDxKHE;=M_grwxWFx6d$yx``VRo>^43*vCUR=;1Umhcy$1X+ zn65wwm56%(PeuDd}P6HsR|roCdD1wPa@Zv)!dA6UPcE1!xe0jJv@ zz)>9iuQD&Ma){zO&{ENNfLl5mZ2dxmwx!Kp5e%D03z&Kl+{bAEl&2GjI!JPLu-NH{-=RIPK$+}K8 z?8Cb9nke^UOvXwr?{~v>L7VH2;f3~WT z=F3Co(V4udEA=5R3+wOnj-T=0EQ7^4H-nxhUmnn+JdveAgON z5B?>NPUejNj8~fJP_=x;w5mMKr6=yH4A# z0ORS^WA*Qm!qVYIF1xX0dP#FE=81s7R-SfflATMbGzJ&5hY5i^peY~am?wz($ujp=p!E-h*Z*bgY`RNQvvFoSki zt-Q>%8ds1&7W{f3@k02BI_I-Q=OH<8>nJ8CjtjRY*M(p2)MGQ`nWo@^zXwptcipR{ zQH8`0>3~!8p0)dwl!1d;G&M5p=Jh0yJx!BF@_y&4{;k0-=VB@W-dqwBk;kj@&R}^B z%}fVTuV{QKk=>ouW!$ea>*K&E3ro5;z|F;*$gJ&VcGN9pOOO2L81tei8n6O~Pe9S) znqR2-a#tul2c0X61&5d49NBLcp@u2OF6wG;dN*Afnp#GluFk}0xkQ5nx)w~d)r-m4 zEH%ViBKx2#3yPxQhGL?dQ8y0N3(QxQF3e_vqz@NK*dcKppf4jWz8nf=ER4NLry?xtSvZKw(qN z%fAg@MYZOT27c)n(a`wp)ZjpTaOV6oqQP4G9)d3?Y4dnJ_g86XYLdF!o(T>=xw*+v zbkPI8>YUB>(A+zUQIFr664oc15pj||Amjs3V9yl45?;deJkko*s#s5dA65(694zpX zt^;>E9%mP+o5~a}UyXz2a*rb`Nhh@*Xx@b-c*&9lc1njLAIW<&CZoqw>O@dbMjvFq zjOqUs4t)z0jfY>o&fy~~PkSpI9Gzx&D)Osv7^q)f{r53={vy~}=yfK+i6HslsR%1x zLWq`KzbQlN6{Qwz!=hqyeS*e6dOV#A>6yTb2QAVGq-e?FVI!=BKV zNxdfp%y`YiiJ>757|dhX7G5E4j=-ASq(_pNO^) zE_v`a`oiEx+!|?bazBS|N8n!q(0`TE`f6tU)m+%r#T@uT;^yS$W#i;w;}-hNDJ0Cv uBh1Uq!pSMj$r-Bq(fj|>!QRo#+QRF9-{Bnnt{>=tBrByPSt()k?f(F^`Rug- literal 0 HcmV?d00001 diff --git a/package.json b/package.json index f16f261..0cfbf3a 100644 --- a/package.json +++ b/package.json @@ -22,7 +22,6 @@ "disablePatternField": true, "disableMultipleTypes": true, "enableForwardEngineering": true, - "disableReverseEngineering": false, "enableReverseEngineering": { "jsonDocument": true, "jsonSchema": true, diff --git a/polyglot/adapter.json b/polyglot/adapter.json new file mode 100644 index 0000000..fbd453f --- /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..f590d7a --- /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..f07ca9b --- /dev/null +++ b/properties_pane/entity_level/entityLevelConfig.json @@ -0,0 +1,967 @@ +/* +* 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": "Auxiliary", + "propertyKeyword": "auxiliary", + "propertyTooltip": "CREATE AUXILIARY TABLE used to store LOB column data for a base table.", + "propertyType": "checkbox" + }, + { + "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": "auxiliary", + "value": true + } + }, + { + "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": "auxiliary", + "value": true + } + }, + { + "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": "auxiliary", + "value": true + } + }, + { + "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": "auxiliary", + "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": "auxiliary", + "value": true + } + } + }, + { + "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": "auxiliary", + "value": true + } + }, + { + "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": "auxiliary", + "value": true + } + }, + { + "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": "auxiliary", + "value": true + } + }, + { + "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": "auxiliary", + "value": true + } + }, + "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": "auxiliary", + "value": true + } + }, + "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": "auxiliary", + "value": true + } + }, + "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": "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": "auxiliary", + "value": true + } + }, + "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": "auxiliary", + "value": true + } + } + }, + { + "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": "", + "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": "comment", + "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": "", + "structure": [ + { + "propertyName": "Constraint name", + "propertyKeyword": "constraintName", + "propertyTooltip": "", + "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": "comment", + "addTimestampButton": false, + "propertyType": "details", + "template": "codeEditor", + "templateOptions": { + "editorDialect": "markdown" + } + }, + { + "propertyName": "Alternate key", + "propertyKeyword": "alternateKey", + "propertyTooltip": "", + "propertyType": "checkbox", + "setFieldPropertyBy": "compositeUniqueKey" + } + ] + } + ] + }, + { + "lowerTab": "Indexes", + "structure": [ + { + "propertyName": "Index", + "propertyType": "group", + "propertyKeyword": "Indxs", + "propertyTooltip": "", + "structure": [ + { + "propertyName": "Name", + "propertyKeyword": "indxName", + "propertyTooltip": "", + "propertyType": "text", + "validation": { + "required": true + } + }, + { + "propertyName": "Activated", + "propertyKeyword": "isActivated", + "propertyTooltip": "Deactivated item will be not included in FE script", + "propertyType": "checkbox", + "defaultValue": true + }, + { + "propertyName": "Type", + "propertyKeyword": "indxType", + "propertyType": "select", + "options": ["", "unique"] + }, + { + "propertyName": "Keys", + "propertyKeyword": "indxKey", + "propertyType": "fieldList", + "template": "orderedList", + "attributeList": ["asc", "desc", "random"], + "validation": { + "minLength": 1, + "required": true + } + }, + { + "propertyName": "Include keys", + "propertyKeyword": "indxIncludeKey", + "propertyType": "fieldList", + "propertyTooltip": "Introduces a clause that specifies additional columns to be appended to the set of index key columns. Any columns included with this clause are not used to enforce uniqueness.", + "template": "orderedList", + "attributeList": [], + "dependency": { + "key": "indxType", + "value": "unique" + } + }, + { + "propertyName": "Compress", + "propertyKeyword": "indxCompress", + "propertyType": "select", + "propertyTooltip": "Specifies whether index compression is enabled.", + "options": ["", "yes", "no"] + }, + { + "propertyName": "Null keys", + "propertyKeyword": "indxNullKeys", + "propertyType": "select", + "propertyTooltip": "'Include' specifies that an index entry is created when all parts of the index key contain the null value.", + "options": ["", "include", "exclude"] + }, + { + "propertyName": "Tablespace", + "propertyKeyword": "indxTablespace", + "propertyTooltip": "Specify the tablespace in which Db2 Database creates the table. If you omit TABLESPACE, then the database creates that item in the default tablespace of the owner of the schema containing the table.", + "propertyType": "text" + }, + { + "propertyName": "Description", + "propertyKeyword": "indxDescription", + "propertyTooltip": "description", + "propertyType": "details", + "template": "codeEditor", + "templateOptions": { + "editorDialect": "markdown" + } + }, + { + "propertyName": "Comments", + "propertyKeyword": "indxComments", + "propertyTooltip": "comments", + "addTimestampButton": false, + "propertyType": "details", + "template": "codeEditor", + "templateOptions": { + "editorDialect": "markdown" + } + } + ] + } + ] + }, + { + "lowerTab": "Check Constraints", + "structure": [ + { + "propertyName": "Check Constraint", + "propertyType": "group", + "propertyKeyword": "chkConstr", + "propertyTooltip": "", + "structure": [ + { + "propertyName": "Name", + "propertyKeyword": "chkConstrName", + "propertyTooltip": "", + "propertyType": "text" + }, + { + "propertyName": "Expression", + "propertyKeyword": "constrExpression", + "propertyTooltip": "Expression", + "propertyType": "details", + "template": "textarea", + "markdown": false, + "validation": { + "required": true + } + }, + { + "propertyName": "Description", + "propertyKeyword": "constrDescription", + "propertyTooltip": "description", + "propertyType": "details", + "template": "textarea" + }, + { + "propertyName": "Comments", + "propertyKeyword": "constrComments", + "propertyTooltip": "comments", + "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..f38edac --- /dev/null +++ b/properties_pane/field_level/fieldLevelConfig.json @@ -0,0 +1,3479 @@ +/* +* 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..5d760b4 --- /dev/null +++ b/properties_pane/model_level/modelLevelConfig.json @@ -0,0 +1,203 @@ +/* +* 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": "On Update", + "propertyKeyword": "relationshipOnUpdate", + "propertyType": "select", + "options": ["", "NO ACTION", "RESTRICT"] + } + ] + } +] 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..b979fff --- /dev/null +++ b/reverse_engineering/api.js @@ -0,0 +1,229 @@ +// /** +// * @typedef {import('../shared/types').App} App +// * @typedef {import('../shared/types').AppLogger} AppLogger +// * @typedef {import('../shared/types').ConnectionInfo} ConnectionInfo +// * @typedef {import('../shared/types').Logger} Logger +// * @typedef {import('../shared/types').Callback} Callback +// */ + +// const { identity } = require('lodash'); +// const { mapSeries } = require('async'); +// const { connectionHelper } = require('../shared/helpers/connectionHelper'); +// const { instanceHelper } = require('../shared/helpers/instanceHelper'); +// const { logHelper } = require('../shared/helpers/logHelper'); +// const { OBJECT_TYPE } = require('../constants/constants'); +// const { nameHelper } = require('../shared/helpers/nameHelper'); +// const { testConnection } = require('../shared/api/testConnection'); + +// /** +// * @param {ConnectionInfo} connectionInfo +// * @param {AppLogger} appLogger +// * @param {Callback} callback +// */ +const disconnect = async (connectionInfo, appLogger, callback) => { + // try { + // await connectionHelper.disconnect(); + // callback(); + // } catch (error) { + // const logger = logHelper.createLogger({ + // title: 'Disconnect from database', + // hiddenKeys: connectionInfo.hiddenKeys, + // logger: appLogger, + // }); + + // logger.error(error); + // callback(error); + // } +}; + +// /** +// * @param {ConnectionInfo} connectionInfo +// * @param {AppLogger} appLogger +// * @param {Callback} callback +// * @param {App} app +// */ +const getSchemaNames = async (connectionInfo, appLogger, callback, app) => { + // const logger = logHelper.createLogger({ + // title: 'Retrieve schema names', + // hiddenKeys: connectionInfo.hiddenKeys, + // logger: appLogger, + // }); + + // try { + // const connection = await connectionHelper.connect({ connectionInfo, logger }); + // const schemaNames = await instanceHelper.getSchemaNames({ connection }); + + // callback(null, schemaNames); + // } catch (error) { + // logger.error(error); + // callback(error); + // } +}; + +// /** +// * @param {ConnectionInfo} connectionInfo +// * @param {AppLogger} appLogger +// * @param {Callback} callback +// * @param {App} app +// */ +const getDbCollectionsNames = async (connectionInfo, appLogger, callback, app) => { + // const logger = logHelper.createLogger({ + // title: 'Retrieve table names', + // hiddenKeys: connectionInfo.hiddenKeys, + // logger: appLogger, + // }); + + // try { + // const connection = await connectionHelper.connect({ connectionInfo, logger }); + // const dbVersion = await instanceHelper.getDbVersion({ connection }); + // logger.info('Db version: ' + dbVersion); + + // logger.info('Get table and schema names'); + // logger.info(connectionInfo); + + // const tableNames = await instanceHelper.getDatabasesWithTableNames({ + // connection, + // objectType: OBJECT_TYPE.table, + // includeSystemCollection: connectionInfo.includeSystemCollection, + // tableNameModifier: identity, + // }); + + // logger.info('Get views and schema names'); + + // const viewNames = await instanceHelper.getDatabasesWithTableNames({ + // connection, + // objectType: OBJECT_TYPE.view, + // includeSystemCollection: connectionInfo.includeSystemCollection, + // tableNameModifier: nameHelper.setViewSign, + // }); + // const allDatabaseNames = [...Object.keys(tableNames), ...Object.keys(viewNames)]; + // const dbCollectionNames = allDatabaseNames.map(dbName => { + // const dbCollections = [...(tableNames[dbName] || []), ...(viewNames[dbName] || [])]; + + // return { + // dbName, + // dbCollections, + // isEmpty: !dbCollections.length, + // }; + // }); + + // logger.info('Names retrieved successfully'); + + // callback(null, dbCollectionNames); + // } catch (error) { + // logger.error(error); + // callback(error); + // } +}; + +// /** +// * @param {ConnectionInfo} data +// * @param {AppLogger} appLogger +// * @param {Callback} callback +// * @param {App} app +// */ +const getDbCollectionsData = async (connectionInfo, appLogger, callback, app) => { + // const logger = logHelper.createLogger({ + // title: 'Retrieve table names', + // hiddenKeys: connectionInfo.hiddenKeys, + // logger: appLogger, + // }); + + // try { + // const collections = connectionInfo.collectionData.collections; + // const dataBaseNames = connectionInfo.collectionData.dataBaseNames; + // const connection = await connectionHelper.connect({ connectionInfo, logger }); + + // const dbVersion = await instanceHelper.getDbVersion({ connection }); + // logger.info('Db version: ' + dbVersion); + // logger.progress('Start reverse engineering ...'); + + // const result = await mapSeries(dataBaseNames, async schemaName => { + // const tables = (collections[schemaName] || []).filter(name => !nameHelper.isViewName(name)); + // const views = (collections[schemaName] || []).filter(nameHelper.isViewName).map(nameHelper.getViewName); + // const bucketInfo = await instanceHelper.getSchemaProperties({ connection, schemaName, logger }); + // logger.info(`Parsing schema "${schemaName}"`); + // logger.progress(`Parsing schema "${schemaName}"`, schemaName); + + // const result = await mapSeries(tables, async tableName => { + // logger.info(`Get create table statement "${tableName}"`); + // logger.progress(`Get create table statement`, schemaName, tableName); + + // const ddl = await instanceHelper.getTableDdl({ + // connection, + // schemaName, + // tableName, + // objectType: OBJECT_TYPE.table, + // logger, + // }); + + // return { + // dbName: schemaName, + // collectionName: tableName, + // entityLevel: {}, + // documents: [], + // views: [], + // standardDoc: {}, + // ddl: { + // script: ddl, + // type: 'db2', + // takeAllDdlProperties: true, + // }, + // emptyBucket: false, + // bucketInfo: { + // ...bucketInfo, + // }, + // modelDefinitions: {}, + // }; + // }); + + // const viewData = await mapSeries(views, async viewName => { + // logger.info(`Get create view statement "${viewName}"`); + // logger.progress(`Get create view statement`, schemaName, viewName); + + // const ddl = await instanceHelper.getTableDdl({ + // connection, + // schemaName, + // tableName: viewName, + // objectType: OBJECT_TYPE.view, + // logger, + // }); + + // return { + // name: viewName, + // ddl: { + // script: ddl, + // type: 'db2', + // takeAllDdlProperties: true, + // }, + // }; + // }); + + // if (viewData.length) { + // return [ + // ...result, + // { + // dbName: schemaName, + // views: viewData, + // emptyBucket: false, + // }, + // ]; + // } + + // return result; + // }); + + // callback(null, result.flat(), { dbVersion, database_name: connectionInfo.database }); + // } catch (error) { + // logger.error(error); + // callback(error); + // } +}; + +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/types/hck-esbuild-plugins-pack.d.ts b/shared/types/hck-esbuild-plugins-pack.d.ts similarity index 100% rename from types/hck-esbuild-plugins-pack.d.ts rename to shared/types/hck-esbuild-plugins-pack.d.ts diff --git a/tsconfig.json b/tsconfig.json index c78f5c6..82372bb 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -17,9 +17,9 @@ "useUnknownInCatchVariables": true, "resolveJsonModule": true, "forceConsistentCasingInFileNames": true, - "typeRoots": ["./node_modules/@types", "./types"], + "typeRoots": ["./node_modules/@types", "shared/types"], "types": ["node"] }, - "include": ["**/*.js", "**/*.cjs", "types/**/*.d.ts"], + "include": ["**/*.js", "**/*.cjs", "shared/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..06f5f80 --- /dev/null +++ b/types/object.json @@ -0,0 +1,23 @@ +{ + "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}$" +} From 88f14160e4ef536a061ee4e6c16456f9736701a1 Mon Sep 17 00:00:00 2001 From: Vitalii Bedletskyi Date: Mon, 10 Aug 2026 14:51:46 +0300 Subject: [PATCH 04/15] update properties pane configs --- polyglot/adapter.json | 8 +- polyglot/convertAdapter.json | 8 +- .../field_level/fieldLevelConfig.json | 154 +++--------------- reverse_engineering/api.js | 91 +++++------ types/object.json | 10 +- 5 files changed, 74 insertions(+), 197 deletions(-) diff --git a/polyglot/adapter.json b/polyglot/adapter.json index fbd453f..a67e412 100644 --- a/polyglot/adapter.json +++ b/polyglot/adapter.json @@ -33,7 +33,7 @@ * }, * } */ - { +{ "modify": { "field": [ { @@ -55,7 +55,7 @@ "length": 255 } }, - { + { "from": { "type": "nvarchar", "hasMaxLength": true @@ -74,10 +74,10 @@ "length": 255 } }, - { + { "from": { "type": "binary", - "mode": "varbinary", + "mode": "varbinary", "hasMaxLength": true }, "to": { diff --git a/polyglot/convertAdapter.json b/polyglot/convertAdapter.json index f590d7a..dbc6c98 100644 --- a/polyglot/convertAdapter.json +++ b/polyglot/convertAdapter.json @@ -33,7 +33,7 @@ * }, * } */ - { +{ "modify": { "field": [ { @@ -45,7 +45,7 @@ "hasMaxLength": true } }, - { + { "from": { "mode": "nvarchar", "length": 32704 @@ -54,7 +54,7 @@ "hasMaxLength": true } }, - { + { "from": { "mode": "varbinary", "length": 32704 @@ -80,7 +80,7 @@ "to": { "hasMaxLength": true } - }, + } ] } } diff --git a/properties_pane/field_level/fieldLevelConfig.json b/properties_pane/field_level/fieldLevelConfig.json index f38edac..cefafb3 100644 --- a/properties_pane/field_level/fieldLevelConfig.json +++ b/properties_pane/field_level/fieldLevelConfig.json @@ -128,14 +128,7 @@ making sure that you maintain a proper JSON format. "propertyName": "Subtype", "propertyKeyword": "mode", "propertyType": "select", - "options": [ - "char", - "varchar", - "clob", - "graphic", - "vargraphic", - "dbclob" - ], + "options": ["char", "varchar", "clob", "graphic", "vargraphic", "dbclob"], "data": "options", "valueType": "string" }, @@ -144,10 +137,7 @@ making sure that you maintain a proper JSON format. "propertyKeyword": "synonym", "propertyTooltip": "Select from list of options", "propertyType": "select", - "options": [ - "", - "character" - ], + "options": ["", "character"], "defaultValue": "", "dependency": { "key": "mode", @@ -159,11 +149,7 @@ making sure that you maintain a proper JSON format. "propertyKeyword": "synonym", "propertyTooltip": "Select from list of options", "propertyType": "select", - "options": [ - "", - "char varying", - "character varying" - ], + "options": ["", "char varying", "character varying"], "defaultValue": "", "dependency": { "key": "mode", @@ -175,11 +161,7 @@ making sure that you maintain a proper JSON format. "propertyKeyword": "synonym", "propertyTooltip": "Select from list of options", "propertyType": "select", - "options": [ - "", - "char large object", - "character large object" - ], + "options": ["", "char large object", "character large object"], "defaultValue": "", "dependency": { "key": "mode", @@ -280,21 +262,13 @@ making sure that you maintain a proper JSON format. "propertyName": "Length semantics", "propertyKeyword": "lengthSemantics", "propertyType": "select", - "options": [ - "", - "K", - "M", - "G" - ], + "options": ["", "K", "M", "G"], "dependency": { "type": "and", "values": [ { "key": "mode", - "value": [ - "clob", - "dbclob" - ] + "value": ["clob", "dbclob"] } ] } @@ -304,22 +278,13 @@ making sure that you maintain a proper JSON format. "propertyKeyword": "characterSubtype", "propertyTooltip": "FOR SBCS DATA, FOR MIXED DATA, or FOR BIT DATA", "propertyType": "select", - "options": [ - "", - "SBCS", - "MIXED", - "BIT" - ], + "options": ["", "SBCS", "MIXED", "BIT"], "dependency": { "type": "and", "values": [ { "key": "mode", - "value": [ - "char", - "varchar", - "clob" - ] + "value": ["char", "varchar", "clob"] } ] } @@ -337,14 +302,7 @@ making sure that you maintain a proper JSON format. "values": [ { "key": "mode", - "value": [ - "char", - "varchar", - "clob", - "graphic", - "vargraphic", - "dbclob" - ] + "value": ["char", "varchar", "clob", "graphic", "vargraphic", "dbclob"] } ] } @@ -893,16 +851,7 @@ making sure that you maintain a proper JSON format. "propertyName": "Subtype", "propertyKeyword": "mode", "propertyType": "select", - "options": [ - "integer", - "smallint", - "bigint", - "decimal", - "float", - "real", - "double", - "decfloat" - ], + "options": ["integer", "smallint", "bigint", "decimal", "float", "real", "double", "decfloat"], "data": "options", "valueType": "string" }, @@ -911,10 +860,7 @@ making sure that you maintain a proper JSON format. "propertyKeyword": "synonym", "propertyTooltip": "Select from list of options", "propertyType": "select", - "options": [ - "", - "int" - ], + "options": ["", "int"], "defaultValue": "", "dependency": { "key": "mode", @@ -926,12 +872,7 @@ making sure that you maintain a proper JSON format. "propertyKeyword": "synonym", "propertyTooltip": "Select from list of options", "propertyType": "select", - "options": [ - "", - "dec", - "numeric", - "num" - ], + "options": ["", "dec", "numeric", "num"], "defaultValue": "", "dependency": { "key": "mode", @@ -943,10 +884,7 @@ making sure that you maintain a proper JSON format. "propertyKeyword": "synonym", "propertyTooltip": "Select from list of options", "propertyType": "select", - "options": [ - "", - "double precision" - ], + "options": ["", "double precision"], "defaultValue": "", "dependency": { "key": "mode", @@ -997,11 +935,7 @@ making sure that you maintain a proper JSON format. "propertyName": "Precision", "propertyKeyword": "precision", "propertyType": "select", - "options": [ - "", - "16", - "34" - ], + "options": ["", "16", "34"], "propertyTooltip": "DECFLOAT precision must be 16 or 34", "typeDecorator": true, "dependency": { @@ -1107,11 +1041,7 @@ making sure that you maintain a proper JSON format. "propertyKeyword": "generated", "propertyTooltip": "Select type of value generation", "propertyType": "select", - "options": [ - "", - "ALWAYS", - "BY DEFAULT" - ] + "options": ["", "ALWAYS", "BY DEFAULT"] }, { "propertyName": "Start", @@ -1168,11 +1098,7 @@ making sure that you maintain a proper JSON format. "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" - ], + "options": ["", "CYCLE", "NO CYCLE"], "dependency": { "type": "not", "values": { @@ -1243,11 +1169,7 @@ making sure that you maintain a proper JSON format. "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" - ], + "options": ["", "CACHE", "NO CACHE"], "dependency": { "type": "not", "values": { @@ -1283,11 +1205,7 @@ making sure that you maintain a proper JSON format. "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" - ], + "options": ["", "ORDER", "NO ORDER"], "dependency": { "type": "not", "values": { @@ -1767,11 +1685,7 @@ making sure that you maintain a proper JSON format. "propertyName": "Subtype", "propertyKeyword": "mode", "propertyType": "select", - "options": [ - "date", - "time", - "timestamp" - ], + "options": ["date", "time", "timestamp"], "data": "options", "valueType": "string" }, @@ -1790,9 +1704,7 @@ making sure that you maintain a proper JSON format. "values": [ { "key": "mode", - "value": [ - "timestamp" - ] + "value": ["timestamp"] } ] } @@ -1806,9 +1718,7 @@ making sure that you maintain a proper JSON format. "values": [ { "key": "mode", - "value": [ - "timestamp" - ] + "value": ["timestamp"] } ] } @@ -2304,11 +2214,7 @@ making sure that you maintain a proper JSON format. "propertyKeyword": "mode", "propertyType": "select", "defaultValue": "binary", - "options": [ - "binary", - "varbinary", - "blob" - ], + "options": ["binary", "varbinary", "blob"], "data": "options", "valueType": "string" }, @@ -2362,12 +2268,7 @@ making sure that you maintain a proper JSON format. "propertyName": "Length semantics", "propertyKeyword": "lengthSemantics", "propertyType": "select", - "options": [ - "", - "K", - "M", - "G" - ], + "options": ["", "K", "M", "G"], "dependency": { "key": "mode", "value": "blob" @@ -2936,9 +2837,7 @@ making sure that you maintain a proper JSON format. "propertyName": "Subtype", "propertyKeyword": "mode", "propertyType": "select", - "options": [ - "rowid" - ], + "options": ["rowid"], "data": "options", "valueType": "string", "defaultValue": "rowid" @@ -2948,10 +2847,7 @@ making sure that you maintain a proper JSON format. "propertyKeyword": "generated", "propertyTooltip": "ROWID values are GENERATED ALWAYS or GENERATED BY DEFAULT", "propertyType": "select", - "options": [ - "ALWAYS", - "BY DEFAULT" - ], + "options": ["ALWAYS", "BY DEFAULT"], "defaultValue": "ALWAYS" }, { diff --git a/reverse_engineering/api.js b/reverse_engineering/api.js index b979fff..6732479 100644 --- a/reverse_engineering/api.js +++ b/reverse_engineering/api.js @@ -11,16 +11,19 @@ // const { connectionHelper } = require('../shared/helpers/connectionHelper'); // const { instanceHelper } = require('../shared/helpers/instanceHelper'); // const { logHelper } = require('../shared/helpers/logHelper'); -// const { OBJECT_TYPE } = require('../constants/constants'); +// const { OBJECT_TYPE } = require('../shared/constants/constants'); // const { nameHelper } = require('../shared/helpers/nameHelper'); // const { testConnection } = require('../shared/api/testConnection'); -// /** -// * @param {ConnectionInfo} connectionInfo -// * @param {AppLogger} appLogger -// * @param {Callback} callback -// */ -const disconnect = async (connectionInfo, appLogger, callback) => { +/** + * Disconnect stub. + * + * @param {unknown} _connectionInfo Connection info. + * @param {unknown} _appLogger App logger. + * @param {unknown} _callback Callback. + * @returns {Promise} + */ +const disconnect = async (_connectionInfo, _appLogger, _callback) => { // try { // await connectionHelper.disconnect(); // callback(); @@ -30,29 +33,29 @@ const disconnect = async (connectionInfo, appLogger, callback) => { // hiddenKeys: connectionInfo.hiddenKeys, // logger: appLogger, // }); - // logger.error(error); // callback(error); // } }; -// /** -// * @param {ConnectionInfo} connectionInfo -// * @param {AppLogger} appLogger -// * @param {Callback} callback -// * @param {App} app -// */ -const getSchemaNames = async (connectionInfo, appLogger, callback, app) => { +/** + * Schema names stub. + * + * @param {unknown} _connectionInfo Connection info. + * @param {unknown} _appLogger App logger. + * @param {unknown} _callback Callback. + * @param {unknown} _app App instance. + * @returns {Promise} + */ +const getSchemaNames = async (_connectionInfo, _appLogger, _callback, _app) => { // const logger = logHelper.createLogger({ // title: 'Retrieve schema names', // hiddenKeys: connectionInfo.hiddenKeys, // logger: appLogger, // }); - // try { // const connection = await connectionHelper.connect({ connectionInfo, logger }); // const schemaNames = await instanceHelper.getSchemaNames({ connection }); - // callback(null, schemaNames); // } catch (error) { // logger.error(error); @@ -60,36 +63,34 @@ const getSchemaNames = async (connectionInfo, appLogger, callback, app) => { // } }; -// /** -// * @param {ConnectionInfo} connectionInfo -// * @param {AppLogger} appLogger -// * @param {Callback} callback -// * @param {App} app -// */ -const getDbCollectionsNames = async (connectionInfo, appLogger, callback, app) => { +/** + * Collection names stub. + * + * @param {unknown} _connectionInfo Connection info. + * @param {unknown} _appLogger App logger. + * @param {unknown} _callback Callback. + * @param {unknown} _app App instance. + * @returns {Promise} + */ +const getDbCollectionsNames = async (_connectionInfo, _appLogger, _callback, _app) => { // const logger = logHelper.createLogger({ // title: 'Retrieve table names', // hiddenKeys: connectionInfo.hiddenKeys, // logger: appLogger, // }); - // try { // const connection = await connectionHelper.connect({ connectionInfo, logger }); // const dbVersion = await instanceHelper.getDbVersion({ connection }); // logger.info('Db version: ' + dbVersion); - // logger.info('Get table and schema names'); // logger.info(connectionInfo); - // const tableNames = await instanceHelper.getDatabasesWithTableNames({ // connection, // objectType: OBJECT_TYPE.table, // includeSystemCollection: connectionInfo.includeSystemCollection, // tableNameModifier: identity, // }); - // logger.info('Get views and schema names'); - // const viewNames = await instanceHelper.getDatabasesWithTableNames({ // connection, // objectType: OBJECT_TYPE.view, @@ -99,16 +100,13 @@ const getDbCollectionsNames = async (connectionInfo, appLogger, callback, app) = // const allDatabaseNames = [...Object.keys(tableNames), ...Object.keys(viewNames)]; // const dbCollectionNames = allDatabaseNames.map(dbName => { // const dbCollections = [...(tableNames[dbName] || []), ...(viewNames[dbName] || [])]; - // return { // dbName, // dbCollections, // isEmpty: !dbCollections.length, // }; // }); - // logger.info('Names retrieved successfully'); - // callback(null, dbCollectionNames); // } catch (error) { // logger.error(error); @@ -116,39 +114,37 @@ const getDbCollectionsNames = async (connectionInfo, appLogger, callback, app) = // } }; -// /** -// * @param {ConnectionInfo} data -// * @param {AppLogger} appLogger -// * @param {Callback} callback -// * @param {App} app -// */ -const getDbCollectionsData = async (connectionInfo, appLogger, callback, app) => { +/** + * Collections data stub. + * + * @param {unknown} _connectionInfo Connection info. + * @param {unknown} _appLogger App logger. + * @param {unknown} _callback Callback. + * @param {unknown} _app App instance. + * @returns {Promise} + */ +const getDbCollectionsData = async (_connectionInfo, _appLogger, _callback, _app) => { // const logger = logHelper.createLogger({ // title: 'Retrieve table names', // hiddenKeys: connectionInfo.hiddenKeys, // logger: appLogger, // }); - // try { // const collections = connectionInfo.collectionData.collections; // const dataBaseNames = connectionInfo.collectionData.dataBaseNames; // const connection = await connectionHelper.connect({ connectionInfo, logger }); - // const dbVersion = await instanceHelper.getDbVersion({ connection }); // logger.info('Db version: ' + dbVersion); // logger.progress('Start reverse engineering ...'); - // const result = await mapSeries(dataBaseNames, async schemaName => { // const tables = (collections[schemaName] || []).filter(name => !nameHelper.isViewName(name)); // const views = (collections[schemaName] || []).filter(nameHelper.isViewName).map(nameHelper.getViewName); // const bucketInfo = await instanceHelper.getSchemaProperties({ connection, schemaName, logger }); // logger.info(`Parsing schema "${schemaName}"`); // logger.progress(`Parsing schema "${schemaName}"`, schemaName); - // const result = await mapSeries(tables, async tableName => { // logger.info(`Get create table statement "${tableName}"`); // logger.progress(`Get create table statement`, schemaName, tableName); - // const ddl = await instanceHelper.getTableDdl({ // connection, // schemaName, @@ -156,7 +152,6 @@ const getDbCollectionsData = async (connectionInfo, appLogger, callback, app) => // objectType: OBJECT_TYPE.table, // logger, // }); - // return { // dbName: schemaName, // collectionName: tableName, @@ -176,11 +171,9 @@ const getDbCollectionsData = async (connectionInfo, appLogger, callback, app) => // modelDefinitions: {}, // }; // }); - // const viewData = await mapSeries(views, async viewName => { // logger.info(`Get create view statement "${viewName}"`); // logger.progress(`Get create view statement`, schemaName, viewName); - // const ddl = await instanceHelper.getTableDdl({ // connection, // schemaName, @@ -188,7 +181,6 @@ const getDbCollectionsData = async (connectionInfo, appLogger, callback, app) => // objectType: OBJECT_TYPE.view, // logger, // }); - // return { // name: viewName, // ddl: { @@ -198,7 +190,6 @@ const getDbCollectionsData = async (connectionInfo, appLogger, callback, app) => // }, // }; // }); - // if (viewData.length) { // return [ // ...result, @@ -209,10 +200,8 @@ const getDbCollectionsData = async (connectionInfo, appLogger, callback, app) => // }, // ]; // } - // return result; // }); - // callback(null, result.flat(), { dbVersion, database_name: connectionInfo.database }); // } catch (error) { // logger.error(error); diff --git a/types/object.json b/types/object.json index 06f5f80..d5d23e8 100644 --- a/types/object.json +++ b/types/object.json @@ -9,15 +9,7 @@ }, "subtypes": { "object": { - "childValueType": [ - "char", - "binary", - "datetime", - "number", - "rowid", - "xml", - "object" - ] + "childValueType": ["char", "binary", "datetime", "number", "rowid", "xml", "object"] } } } From 5337bfa31f175dec89d637c82abbf59c9c347824 Mon Sep 17 00:00:00 2001 From: Vitalii Bedletskyi Date: Mon, 10 Aug 2026 14:52:10 +0300 Subject: [PATCH 05/15] add base FE implementation --- .oxlintrc.json | 12 + forward_engineering/api.js | 27 +- forward_engineering/api/applyToInstance.js | 33 +- .../api/generateContainerScript.js | 25 +- forward_engineering/api/generateScript.js | 25 +- forward_engineering/api/isDropInStatements.js | 28 +- forward_engineering/config.json | 3 +- forward_engineering/configs/defaultTypes.js | 16 + forward_engineering/configs/descriptors.js | 95 +++ forward_engineering/ddlProvider.js | 2 +- .../columnDefinition/getColumnConstraints.js | 46 ++ .../columnDefinition/getColumnDefault.js | 94 +++ .../columnDefinition/getColumnType.js | 250 ++++++ .../ddlHelpers/comment/commentHelper.js | 129 +++ .../ddlHelpers/constraint/getOptionsString.js | 31 + .../ddlHelpers/jsonSchema/jsonSchemaHelper.js | 95 +++ .../ddlProvider/ddlHelpers/key/keyHelper.js | 276 +++++++ .../ddlHelpers/options/getOptionsByConfigs.js | 63 ++ .../ddlHelpers/table/getTableOptions.js | 365 +++++++++ .../ddlHelpers/table/getTableProps.js | 131 +++ .../ddlHelpers/table/getTableType.js | 17 + .../table/hydrateAuxiliaryTableData.js | 48 ++ .../ddlHelpers/table/hydrateZosTableData.js | 81 ++ .../ddlHelpers/view/getViewData.js | 73 ++ .../ddlProvider/ddlProvider.js | 751 ++++++++++++++++++ forward_engineering/ddlProvider/templates.js | 61 ++ forward_engineering/types/ddlProvider.d.ts | 678 ++++++++++++++++ forward_engineering/utils/assignTemplates.js | 46 ++ forward_engineering/utils/general.js | 343 ++++++++ .../joinActivatedAndDeactivatedStatements.js | 57 ++ package-lock.json | 17 + package.json | 6 +- shared/constants/constants.js | 22 + shared/constants/types.js | 77 ++ tsconfig.json | 2 +- 35 files changed, 3967 insertions(+), 58 deletions(-) create mode 100644 forward_engineering/configs/defaultTypes.js create mode 100644 forward_engineering/configs/descriptors.js create mode 100644 forward_engineering/ddlProvider/ddlHelpers/columnDefinition/getColumnConstraints.js create mode 100644 forward_engineering/ddlProvider/ddlHelpers/columnDefinition/getColumnDefault.js create mode 100644 forward_engineering/ddlProvider/ddlHelpers/columnDefinition/getColumnType.js create mode 100644 forward_engineering/ddlProvider/ddlHelpers/comment/commentHelper.js create mode 100644 forward_engineering/ddlProvider/ddlHelpers/constraint/getOptionsString.js create mode 100644 forward_engineering/ddlProvider/ddlHelpers/jsonSchema/jsonSchemaHelper.js create mode 100644 forward_engineering/ddlProvider/ddlHelpers/key/keyHelper.js create mode 100644 forward_engineering/ddlProvider/ddlHelpers/options/getOptionsByConfigs.js create mode 100644 forward_engineering/ddlProvider/ddlHelpers/table/getTableOptions.js create mode 100644 forward_engineering/ddlProvider/ddlHelpers/table/getTableProps.js create mode 100644 forward_engineering/ddlProvider/ddlHelpers/table/getTableType.js create mode 100644 forward_engineering/ddlProvider/ddlHelpers/table/hydrateAuxiliaryTableData.js create mode 100644 forward_engineering/ddlProvider/ddlHelpers/table/hydrateZosTableData.js create mode 100644 forward_engineering/ddlProvider/ddlHelpers/view/getViewData.js create mode 100644 forward_engineering/ddlProvider/ddlProvider.js create mode 100644 forward_engineering/ddlProvider/templates.js create mode 100644 forward_engineering/types/ddlProvider.d.ts create mode 100644 forward_engineering/utils/assignTemplates.js create mode 100644 forward_engineering/utils/general.js create mode 100644 forward_engineering/utils/joinActivatedAndDeactivatedStatements.js create mode 100644 shared/constants/constants.js create mode 100644 shared/constants/types.js diff --git a/.oxlintrc.json b/.oxlintrc.json index a1ef3c6..da84345 100644 --- a/.oxlintrc.json +++ b/.oxlintrc.json @@ -60,5 +60,17 @@ "out/**/*", "release", "reverse_engineering/node_modules" + ], + "overrides": [ + { + "files": ["forward_engineering/**/*.js"], + "rules": { + "typescript/no-unsafe-assignment": "off", + "typescript/no-unsafe-member-access": "off", + "typescript/no-unsafe-argument": "off", + "typescript/no-unsafe-return": "off", + "typescript/no-unsafe-call": "off" + } + } ] } diff --git a/forward_engineering/api.js b/forward_engineering/api.js index 64f965d..8386628 100644 --- a/forward_engineering/api.js +++ b/forward_engineering/api.js @@ -6,18 +6,41 @@ const { generateScript } = require('./api/generateScript'); module.exports = { generateScript, - generateViewScript(data, logger, callback, app) { + /** + * Generate view script stub. + * + * @param {unknown} _data Script data. + * @param {unknown} _logger Logger. + * @param {unknown} _callback Callback. + * @param {unknown} _app App instance. + * @returns {never} Always throws. + */ + generateViewScript(_data, _logger, _callback, _app) { throw new Error('Not implemented'); }, generateContainerScript, - getDatabases(connectionInfo, logger, callback, app) { + /** + * Get databases stub. + * + * @param {unknown} _connectionInfo Connection info. + * @param {unknown} _logger Logger. + * @param {unknown} _callback Callback. + * @param {unknown} _app App instance. + * @returns {never} Always throws. + */ + getDatabases(_connectionInfo, _logger, _callback, _app) { throw new Error('Not implemented'); }, applyToInstance, + /** + * Test connection stub. + * + * @returns {never} Always throws. + */ testConnection() { throw new Error('Not implemented'); }, diff --git a/forward_engineering/api/applyToInstance.js b/forward_engineering/api/applyToInstance.js index d4279fb..bc80166 100644 --- a/forward_engineering/api/applyToInstance.js +++ b/forward_engineering/api/applyToInstance.js @@ -1,21 +1,18 @@ - - -async function applyToInstance(connectionInfo, logger, callback, app) { - // const applyToInstanceLogger = logHelper.createLogger({ - // title: 'Apply to instance', - // hiddenKeys: connectionInfo.hiddenKeys, - // logger, - // }); - - // try { - // const connection = await connectionHelper.connect({ connectionInfo, logger: applyToInstanceLogger }); - // await instanceHelper.executeQuery({ connection, query: connectionInfo.script, ddl: true }); - - // callback(); - // } catch (err) { - // applyToInstanceLogger.error(err); - // callback(err); - // } +/** + * Apply DDL to instance stub. + * + * 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 {unknown} _connectionInfo Connection info. + * @param {unknown} _logger Logger. + * @param {(...args: unknown[]) => void} _callback Callback. + * @param {unknown} _app App instance. + * @returns {never} Always throws. + */ +function applyToInstance(_connectionInfo, _logger, _callback, _app) { + // Apply to instance is out of scope for ddlProvider kickoff. + throw new Error('Not implemented'); } module.exports = { applyToInstance }; diff --git a/forward_engineering/api/generateContainerScript.js b/forward_engineering/api/generateContainerScript.js index 66865ec..095562c 100644 --- a/forward_engineering/api/generateContainerScript.js +++ b/forward_engineering/api/generateContainerScript.js @@ -1,13 +1,18 @@ - -function generateContainerScript(data, logger, callback, app) { - // try { - // const script = buildContainerLevelAlterScript(data, app); - // callback(null, script); - // } catch (error) { - // logger.log('error', { message: error.message, stack: error.stack }, 'Db2 Forward-Engineering Error'); - - // callback({ message: error.message, stack: error.stack }); - // } +/** + * Generate container script stub. + * + * 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 {unknown} _data Script data. + * @param {unknown} _logger Logger. + * @param {(...args: unknown[]) => void} _callback Callback. + * @param {unknown} _app App instance. + * @returns {never} Always throws. + */ +function generateContainerScript(_data, _logger, _callback, _app) { + // Comp-mode / alter script generation is out of scope for ddlProvider kickoff. + throw new Error('Not implemented'); } module.exports = { diff --git a/forward_engineering/api/generateScript.js b/forward_engineering/api/generateScript.js index 33976b2..65a3ca3 100644 --- a/forward_engineering/api/generateScript.js +++ b/forward_engineering/api/generateScript.js @@ -1,13 +1,18 @@ - -function generateScript(data, logger, callback, app) { - // try { - // const script = buildEntityLevelAlterScript(data, app); - // callback(null, script); - // } catch (error) { - // logger.log('error', { message: error.message, stack: error.stack }, 'Oracle Forward-Engineering Error'); - - // callback({ message: error.message, stack: error.stack }); - // } +/** + * Generate entity script stub. + * + * 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 {unknown} _data Script data. + * @param {unknown} _logger Logger. + * @param {(...args: unknown[]) => void} _callback Callback. + * @param {unknown} _app App instance. + * @returns {never} Always throws. + */ +function generateScript(_data, _logger, _callback, _app) { + // Comp-mode / alter script generation is out of scope for ddlProvider kickoff. + throw new Error('Not implemented'); } module.exports = { diff --git a/forward_engineering/api/isDropInStatements.js b/forward_engineering/api/isDropInStatements.js index 0273afe..706f737 100644 --- a/forward_engineering/api/isDropInStatements.js +++ b/forward_engineering/api/isDropInStatements.js @@ -1,17 +1,17 @@ - - -function isDropInStatements(data, logger, callback, app) { - // try { - // if (data.level === 'container') { - // const containsDropStatements = doesContainerLevelAlterScriptContainDropStatements(data, app); - // callback(null, containsDropStatements); - // } else { - // const containsDropStatements = doesEntityLevelAlterScriptContainDropStatements(data, app); - // callback(null, containsDropStatements); - // } - // } catch (e) { - // callback({ message: e.message, stack: e.stack }); - // } +/** + * Detect drop statements. + * + * Reports that no DROP statements are produced, which holds while alter script generation is not implemented. The + * callback must always be invoked: the studio has no timeout on this request. + * + * @param {unknown} _data Script data. + * @param {unknown} _logger Logger. + * @param {(...args: unknown[]) => void} callback Callback. + * @param {unknown} _app App instance. + * @returns {void} + */ +function isDropInStatements(_data, _logger, callback, _app) { + callback(null, false); } module.exports = { diff --git a/forward_engineering/config.json b/forward_engineering/config.json index 08abcce..4902d7e 100644 --- a/forward_engineering/config.json +++ b/forward_engineering/config.json @@ -17,7 +17,8 @@ }, "compMode": { "entity": true, - "container": true + "container": true, + "useDdlProvider": true }, "namePrefix": "Db2 for z/OS", "level": { 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 index 90a7453..a76ecbe 100644 --- a/forward_engineering/ddlProvider.js +++ b/forward_engineering/ddlProvider.js @@ -1,4 +1,4 @@ // This file reexports actual DDL Provider. // Core application needs this file to generate FE scripts -// module.exports = require('./ddlProvider/ddlProvider'); +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..d00ea72 --- /dev/null +++ b/forward_engineering/ddlProvider/ddlHelpers/columnDefinition/getColumnConstraints.js @@ -0,0 +1,46 @@ +/** + * @import { + * ColumnConstraintParams, + * KeyOptions + * } from '../../../types/ddlProvider' + */ + +const { getOptionsString } = require('../constraint/getOptionsString'); + +/** + * 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 {}; +}; + +/** + * Build column constraint clauses. + * + * @param {ColumnConstraintParams} params Column constraint flags. + * @returns {string} Constraints DDL fragment. + */ +const getColumnConstraints = ({ nullable, unique, primaryKey, primaryKeyOptions, uniqueKeyOptions }) => { + const { constraintString, statement } = getOptionsString( + getOptions({ primaryKey, unique, primaryKeyOptions, uniqueKeyOptions }), + ); + 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..8585019 --- /dev/null +++ b/forward_engineering/ddlProvider/ddlHelpers/columnDefinition/getColumnDefault.js @@ -0,0 +1,94 @@ +/** + * @import { + * ColumnDefaultParams, + * IdentityOptions + * } from '../../../types/ddlProvider' + */ + +const lodash = require('lodash'); +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(lodash.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 }) => lodash.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..1b06bdf --- /dev/null +++ b/forward_engineering/ddlProvider/ddlHelpers/columnDefinition/getColumnType.js @@ -0,0 +1,250 @@ +/** + * @import { + * HydratedColumn, + * LengthWithMultiplierParams, + * ScalePrecisionParams + * } from '../../../types/ddlProvider' + */ + +const lodash = require('lodash'); +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}${lodash.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 (lodash.isNumber(scale)) { + return ` ${type}(${precision ?? '*'},${scale})`; + } + + if (lodash.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 (lodash.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 = lodash.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 ${lodash.toUpper(characterSubtype)} DATA`; +}; + +/** + * Build CCSID clause. + * + * @param {{ ccsid?: number }} params CCSID value. + * @returns {string} CCSID clause. + */ +const getCcsidClause = ({ ccsid }) => { + if (!lodash.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 (!lodash.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 = lodash.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 }) && lodash.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..c5db5cb --- /dev/null +++ b/forward_engineering/ddlProvider/ddlHelpers/comment/commentHelper.js @@ -0,0 +1,129 @@ +/** + * @import { + * ColumnCommentParams, + * CommentStatementParams, + * HydratedColumn + * } from '../../../types/ddlProvider' + */ + +const lodash = require('lodash'); +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', +}; + +/** @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: lodash.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 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'); +}; + +module.exports = { + getColumnCommentStatement, + getSchemaCommentStatement, + getTableCommentStatement, + getColumnComments, +}; diff --git a/forward_engineering/ddlProvider/ddlHelpers/constraint/getOptionsString.js b/forward_engineering/ddlProvider/ddlHelpers/constraint/getOptionsString.js new file mode 100644 index 0000000..7531d2b --- /dev/null +++ b/forward_engineering/ddlProvider/ddlHelpers/constraint/getOptionsString.js @@ -0,0 +1,31 @@ +/** + * @import { + * ConstraintOptionsResult, + * KeyOptions + * } from '../../../types/ddlProvider' + */ + +const { wrapInQuotes } = require('../../../utils/general'); + +/** + * Build constraint option fragments. + * + * @param {KeyOptions} params Constraint options. + * @returns {ConstraintOptionsResult} Constraint fragments. + */ +const getOptionsString = ({ constraintName, deferClause, rely, validate, indexClause, exceptionClause }) => { + const constraintString = constraintName ? ` CONSTRAINT ${wrapInQuotes(constraintName.trim())}` : ''; + const statement = [deferClause, rely, indexClause, validate, exceptionClause] + .filter(Boolean) + .map(option => ` ${option}`) + .join(''); + + return { + constraintString, + statement, + }; +}; + +module.exports = { + getOptionsString, +}; diff --git a/forward_engineering/ddlProvider/ddlHelpers/jsonSchema/jsonSchemaHelper.js b/forward_engineering/ddlProvider/ddlHelpers/jsonSchema/jsonSchemaHelper.js new file mode 100644 index 0000000..6d7c6e6 --- /dev/null +++ b/forward_engineering/ddlProvider/ddlHelpers/jsonSchema/jsonSchemaHelper.js @@ -0,0 +1,95 @@ +/** + * @import { + * FieldNameLookupParams, + * IdToNameMap, + * JsonSchema, + * JsonSchemaColumn, + * WalkSchemaParams + * } from '../../../types/ddlProvider' + */ + +/** + * Resolve a schema item name. + * + * @param {{ item?: JsonSchemaColumn }} 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?: JsonSchema }} 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: JsonSchemaColumn }} 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/keyHelper.js b/forward_engineering/ddlProvider/ddlHelpers/key/keyHelper.js new file mode 100644 index 0000000..786c996 --- /dev/null +++ b/forward_engineering/ddlProvider/ddlHelpers/key/keyHelper.js @@ -0,0 +1,276 @@ +/** + * @import { + * ForeignKeyCustomPropertiesParams, + * HydrateKeyOptionsParams, + * JsonSchema, + * JsonSchemaColumn, + * KeyConstraint, + * KeyConstraintColumn, + * KeyPropertyLookupParams, + * KeyRef + * } from '../../../types/ddlProvider' + */ + +const lodash = require('lodash'); +const { wrapInQuotes, commentIfDeactivated, checkIsKeyActivated } = require('../../../utils/general'); + +/** @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 }) && !lodash.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 }) && !lodash.trim(column.primaryKeyOptions?.constraintName); +}; + +/** + * Hydrate key constraint options. + * + * @param {HydrateKeyOptionsParams} params Key options. + * @returns {KeyConstraint} Hydrated key options. + */ +const hydrateKeyOptions = ({ columnName, isActivated, options, keyType }) => { + return { + keyType, + columns: [ + { + name: columnName, + isActivated: isActivated, + }, + ], + ...lodash.pickBy(options ?? {}, value => !lodash.isNil(value)), + }; +}; + +/** + * 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 }} params Schema. + * @returns {KeyConstraint[]} Primary key constraints. + */ +const getCompositePrimaryKeys = ({ jsonSchema }) => { + if (!Array.isArray(jsonSchema.primaryKey)) { + return []; + } + + return jsonSchema.primaryKey + .filter(primaryKey => !lodash.isEmpty(primaryKey.compositePrimaryKey)) + .map(primaryKey => + Object.assign(hydrateKeyOptions({ options: primaryKey, keyType: KEY_TYPE.primaryKey }), { + columns: getKeys({ keys: primaryKey.compositePrimaryKey, jsonSchema }), + }), + ); +}; + +/** + * Get composite unique key constraints. + * + * @param {{ jsonSchema: JsonSchema }} params Schema. + * @returns {KeyConstraint[]} Unique key constraints. + */ +const getCompositeUniqueKeys = ({ jsonSchema }) => { + if (!Array.isArray(jsonSchema.uniqueKey)) { + return []; + } + + return jsonSchema.uniqueKey + .filter(uniqueKey => !lodash.isEmpty(uniqueKey.compositeUniqueKey)) + .map(uniqueKey => + Object.assign(hydrateKeyOptions({ options: uniqueKey, keyType: KEY_TYPE.unique }), { + columns: getKeys({ keys: uniqueKey.compositeUniqueKey, jsonSchema }), + }), + ); +}; + +/** + * Collect table-level key constraints. + * + * @param {{ jsonSchema: JsonSchema }} params Schema. + * @returns {KeyConstraint[]} Key constraints. + */ +const getTableKeyConstraints = ({ jsonSchema }) => { + 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, + }); + }).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, + }); + }).filter(constraint => constraint !== null); + + return [ + ...primaryKeyConstraints, + ...getCompositePrimaryKeys({ jsonSchema }), + ...uniqueConstraints, + ...getCompositeUniqueKeys({ jsonSchema }), + ]; +}; + +/** + * 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(lodash.trim(key.name))); + const deactivatedKeys = keys + .filter(key => !checkIsKeyActivated({ key })) + .map(key => wrapInQuotes(lodash.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 => lodash.trim(key.name)).join(', '); +}; + +/** + * Build ON DELETE / ON UPDATE clauses for foreign keys. + * + * @param {ForeignKeyCustomPropertiesParams} params Custom properties. + * @returns {string} Foreign key action clauses. + */ +const customPropertiesForForeignKey = ({ customProperties }) => { + const properties = customProperties ?? {}; + const { relationshipOnDelete, relationshipOnUpdate } = properties; + const relationshipOnDeleteClause = relationshipOnDelete ? ' ON DELETE ' + relationshipOnDelete : ''; + const relationshipOnUpdateClause = relationshipOnUpdate ? ' ON UPDATE ' + relationshipOnUpdate : ''; + + return relationshipOnDeleteClause + relationshipOnUpdateClause; +}; + +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..229c617 --- /dev/null +++ b/forward_engineering/ddlProvider/ddlHelpers/options/getOptionsByConfigs.js @@ -0,0 +1,63 @@ +/** + * @import { + * BasicValueParams, + * OptionsByConfigsParams + * } from '../../../types/ddlProvider' + */ + +const lodash = require('lodash'); + +/** + * 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 => + value + ? [prefix, String(resolveModifier(value)), postfix] + .filter(Boolean) + .map(part => lodash.trim(part)) + .join(' ') + : ''; +}; + +/** + * @param {OptionsByConfigsParams} params Configs and data. + * @returns {string} Options string. + */ +const getOptionsByConfigs = ({ configs, data }) => { + const statements = configs + .filter(({ key }) => lodash.get(data, key)) + .map(({ key, getValue }) => getValue(lodash.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..985294d --- /dev/null +++ b/forward_engineering/ddlProvider/ddlHelpers/table/getTableOptions.js @@ -0,0 +1,365 @@ +/** + * @import { + * CreateTableParams, + * HydratedPartitioning, + * InClauseParams, + * OptionConfig, + * TableOptionsBlock, + * TemporalPeriodsParams + * } from '../../../types/ddlProvider' + */ + +const lodash = require('lodash'); +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 => (lodash.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 || !lodash.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 (!lodash.isNumber(partitioning.everySize)) { + return ''; + } + return `PARTITION BY SIZE EVERY ${partitioning.everySize} G`; + } + + if (partitioning.partitionBy === 'RANGE') { + const keyColumns = (partitioning.partitionKey ?? []) + .map(key => columnMapToStringWithOrder(key)) + .filter(Boolean) + .join(', '); + + if (!keyColumns) { + return ''; + } + + const nullsLast = partitioning.nullsLast ? ' NULLS LAST' : ''; + const partitions = (partitioning.partitions ?? []) + .filter(partition => lodash.isNumber(partition.partitionNumber) && partition.endingAt) + .map(partition => { + const inclusive = partition.inclusive ? ' INCLUSIVE' : ''; + return `PARTITION ${partition.partitionNumber} ENDING AT (${partition.endingAt})${inclusive}`; + }) + .join('\n\t'); + + const partitionsClause = partitions ? `\n\t${partitions}` : ''; + return `PARTITION BY RANGE (${keyColumns})${nullsLast}${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 + ? ` ${lodash.toUpper(periodForBusinessTime.endInclusive)}` + : ''; + clauses.push( + `PERIOD FOR BUSINESS_TIME (${wrapInQuotes(periodForBusinessTime.startColumn)}, ${wrapInQuotes(periodForBusinessTime.endColumn)}${endInclusive})`, + ); + } + + return clauses.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 => lodash.toUpper(value), + }), + }, + { + key: 'auxiliaryPart', + getValue: getBasicValue({ prefix: 'PART' }), + }, + ]; + + return getOptionsByConfigs({ configs, data: 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 = [inClause, structuredOptions.trim(), partitioning, temporal, tableProperties] + .filter(Boolean) + .join('\n\t'); + + return statements ? ` ${statements}` : ''; +}; + +module.exports = { + getTableOptions, + getInClause, + getPartitioningClause, + getTemporalPeriodsClause, +}; diff --git a/forward_engineering/ddlProvider/ddlHelpers/table/getTableProps.js b/forward_engineering/ddlProvider/ddlHelpers/table/getTableProps.js new file mode 100644 index 0000000..6f2115f --- /dev/null +++ b/forward_engineering/ddlProvider/ddlHelpers/table/getTableProps.js @@ -0,0 +1,131 @@ +/** + * @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, 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 columnsString = joinActivatedAndDeactivatedStatements({ statements: columns, indent: '\n\t' }); + + const tableProps = assignTemplates({ + template: templates.createTableProps, + templateData: { + columns: columnsString, + foreignKeyConstraints: foreignKeyConstraintsString, + keyConstraints: keyConstraintsString, + }, + }); + + 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..deba088 --- /dev/null +++ b/forward_engineering/ddlProvider/ddlHelpers/table/getTableType.js @@ -0,0 +1,17 @@ +/** + * Resolve table type clause. + * + * @param {{ auxiliary?: boolean }} params Table flags. + * @returns {string} Table type clause. + */ +const getTableType = ({ auxiliary }) => { + if (auxiliary) { + return ' AUXILIARY'; + } + + 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..cd97a98 --- /dev/null +++ b/forward_engineering/ddlProvider/ddlHelpers/table/hydrateAuxiliaryTableData.js @@ -0,0 +1,48 @@ +/** + * @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 }) => { + if (!detailsTab.auxiliary) { + 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: detailsTab.auxiliary, + auxiliaryAppend: detailsTab.auxiliaryAppend, + auxiliaryPart: detailsTab.auxiliaryPart, + auxiliaryBaseTable, + auxiliaryBaseColumn, + }; +}; + +module.exports = { + hydrateAuxiliaryTableData, +}; 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..f5a3dc1 --- /dev/null +++ b/forward_engineering/ddlProvider/ddlProvider.js @@ -0,0 +1,751 @@ +/** + * @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 lodash = require('lodash'); +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, +} = 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 { hydratePartitioning, hydrateTemporalPeriod } = require('./ddlHelpers/table/hydrateZosTableData.js'); +const { joinActivatedAndDeactivatedStatements } = require('../utils/joinActivatedAndDeactivatedStatements'); + +/** + * 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), + }, + }); + +/** + * 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 ?? '') : lodash.toUpper(jsonSchema.mode ?? jsonSchema.type); + const itemsType = lodash.toUpper(jsonSchema.items?.mode ?? jsonSchema.items?.type ?? ''); + + return { + name: columnDefinition.name, + 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 || lodash.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, +}); + +/** + * Create check constraint DDL. + * + * @returns {string} Empty stub. + */ +const createCheckConstraint = () => ''; + +/** + * 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: lodash.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: lodash.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 partitioning = hydratePartitioning({ jsonSchema, partitioning: detailsTab.partitioning }); + const periodForSystemTime = hydrateTemporalPeriod({ + jsonSchema, + period: detailsTab.periodForSystemTime, + }); + const periodForBusinessTime = hydrateTemporalPeriod({ + jsonSchema, + period: detailsTab.periodForBusinessTime, + }); + + return { + ...tableData, + ...auxiliaryTableData, + keyConstraints: keyHelper.getTableKeyConstraints({ jsonSchema }), + 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, + }; +}; + +/** + * 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, + name, + schemaData, + auxiliary, + auxiliaryBaseTable, + auxiliaryBaseColumn, + auxiliaryAppend, + auxiliaryPart, + inClauseType, + databaseName, + table_tablespace_name, + acceleratorName, + tableOptions, + partitioning, + periodForSystemTime, + periodForBusinessTime, + description, + tableProperties, + } = tableData; + const tableType = getTableType({ auxiliary }); + 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, + }); + } + + const tableProps = getTableProps({ + columns: columns ?? [], + foreignKeyConstraints: foreignKeyConstraints ?? [], + keyConstraints: keyConstraints ?? [], + isActivated, + }); + const renderedTableOptions = getTableOptions({ + 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) => ({ + ...indexData, + schemaName: schemaData?.schemaName, +}); + +/** + * Create index DDL. + * + * @returns {string} Empty stub. + */ +const createIndex = () => ''; + +/** + * Drop index DDL. + * + * @returns {string} Empty stub. + */ +const dropIndex = () => ''; + +/** + * 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 = lodash.trim(rawSelectStatement) + ? lodash.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, + 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..c010616 --- /dev/null +++ b/forward_engineering/ddlProvider/templates.js @@ -0,0 +1,61 @@ +module.exports = { + createSchema: 'CREATE SCHEMA ${schemaName};', + + dropSchema: 'DROP SCHEMA ${schemaName} RESTRICT;', + + alterSchema: 'ALTER SCHEMA ${schemaName};', + + 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};', + + comment: '\nCOMMENT ON ${objectType} ${objectName} IS ${comment};\n', + + createTableProps: '${columns}${keyConstraints}${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}', + + createKeyConstraint: '${constraintName}${keyType}${columns}${options}', + + createView: 'CREATE VIEW ${name}${viewColumns}${viewProperties}${withCheckOption}\n\tAS ${selectStatement};', + + viewSelectStatement: 'SELECT ${keys}\n\tFROM ${tableName}', + + 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};', + + 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};', +}; diff --git a/forward_engineering/types/ddlProvider.d.ts b/forward_engineering/types/ddlProvider.d.ts new file mode 100644 index 0000000..516c7d0 --- /dev/null +++ b/forward_engineering/types/ddlProvider.d.ts @@ -0,0 +1,678 @@ +/** 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; + type?: string; + nullable?: boolean; + default?: string | number; + isActivated?: boolean; + scale?: number; + precision?: number; + length?: number; +}; + +export type KeyOptions = { + constraintName?: string; + deferClause?: string; + rely?: string; + validate?: string; + indexClause?: string; + exceptionClause?: 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?: { mode?: string; type?: string }; + 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[]; + items?: JsonSchema | JsonSchema[]; +}; + +export type HydratedColumn = { + name: string; + type: string; + ofType?: string; + notPersistable?: boolean; + size?: string | number; + primaryKey: boolean; + primaryKeyOptions?: KeyOptions; + unique: boolean; + uniqueKeyOptions?: KeyOptions; + nullable?: boolean; + default?: string | number; + 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; +}; + +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[]; + auxiliary?: boolean; + auxiliaryBaseTable?: string; + auxiliaryBaseColumn?: FieldListRef; + auxiliaryAppend?: string; + auxiliaryPart?: number; + 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[]; + deferClause?: string; + rely?: string; + validate?: string; + indexClause?: string; + exceptionClause?: string; +}; + +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; + relationshipOnUpdate?: string; + }; +}; + +export type HydratedTable = { + name: string; + schemaData?: SchemaData; + relatedSchemas?: Record; + keyConstraints?: KeyConstraint[]; + description?: string; + tableProperties?: string; + auxiliary?: boolean; + auxiliaryAppend?: string; + auxiliaryPart?: number; + auxiliaryBaseTable?: string; + auxiliaryBaseColumn?: 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[]; + name: string; + schemaData: SchemaData; + description?: string; + tableProperties?: string; + auxiliary?: boolean; + auxiliaryAppend?: string; + auxiliaryPart?: number; + auxiliaryBaseTable?: string; + auxiliaryBaseColumn?: 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; +}; + +export type HydratedCheckConstraint = { + name?: string; + expression?: string; + comments?: string; + description?: string; +}; + +export type IndexData = { + indxName?: string; + indxKey?: unknown[]; + 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; + +export type CompMod = { + collectionName?: { new?: string }; + keyspaceName?: string; + isActivated?: { new?: boolean }; + 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: JsonSchemaColumn; + path: string[]; +}) => void; + +export type DividedConstraints = { + activatedItems: string[]; + deactivatedItems: string[]; +}; + +export type ColumnConstraintParams = { + nullable?: boolean; + unique: boolean; + primaryKey: boolean; + primaryKeyOptions?: KeyOptions; + uniqueKeyOptions?: KeyOptions; +}; + +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 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[]; + 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; +}; + +export type KeyPropertyLookupParams = { + keyId?: string; + properties: Record; +}; + +export type ForeignKeyCustomPropertiesParams = { + customProperties?: ForeignKeyInput['customProperties']; +}; + +export type IdToNameMap = Record; + +export type WalkSchemaParams = { + jsonSchema: JsonSchemaColumn; + 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; + + 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?: { name?: string; expression?: string }): 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, +) => 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..66ca41f --- /dev/null +++ b/forward_engineering/utils/general.js @@ -0,0 +1,343 @@ +/** + * @import { + * ActivatedKey, + * CommentDeactivatedOptions, + * DividedItems, + * DivideItemsParams, + * FieldComparisonParams, + * KeyConstraintColumn, + * ModelObject, + * PropertyChanges, + * PropertyPair, + * ToArrayParams, + * TypeDescriptors + * } from '../types/ddlProvider' + */ + +const lodash = require('lodash'); +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 => lodash.toLower(key)) + .includes(lodash.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. + * + * @param {ModelObject} collection Collection. + * @returns {ModelObject} Merged schema. + */ +const getSchemaOfAlterCollection = collection => { + return { ...collection, ...lodash.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 !lodash.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/package-lock.json b/package-lock.json index 5b44f9b..40d847c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7,8 +7,12 @@ "": { "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", @@ -1499,6 +1503,13 @@ "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", @@ -2891,6 +2902,12 @@ "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", diff --git a/package.json b/package.json index 0cfbf3a..9add005 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,7 @@ "contributes": { "target": { "applicationTarget": "Db2-zOS", - "title": "Db2 z/OS", + "title": "Db2 for z/OS", "versions": [ "v12", "v13" @@ -78,6 +78,7 @@ }, "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", @@ -92,5 +93,8 @@ }, "overrides": { "minimatch": "10.2.6" + }, + "dependencies": { + "lodash": "4.18.1" } } 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/tsconfig.json b/tsconfig.json index 82372bb..6b7f436 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -20,6 +20,6 @@ "typeRoots": ["./node_modules/@types", "shared/types"], "types": ["node"] }, - "include": ["**/*.js", "**/*.cjs", "shared/types/**/*.d.ts"], + "include": ["**/*.js", "**/*.cjs", "shared/types/**/*.d.ts", "forward_engineering/types/**/*.d.ts"], "exclude": ["**/node_modules/**", "release/**/*"] } From d639f17a55b550546c04d0e7ef582b598824c47d Mon Sep 17 00:00:00 2001 From: Vitalii Bedletskyi Date: Mon, 10 Aug 2026 16:48:15 +0300 Subject: [PATCH 06/15] implement base logic for alter script generation and script generation options --- esbuild.package.js | 6 +- .../alterScript/alterScriptBuilder.js | 123 +++++ .../alterScript/alterScriptFromDeltaHelper.js | 243 ++++++++ .../alterContainerHelper.js | 82 +++ .../alterScriptHelpers/alterEntityHelper.js | 262 +++++++++ .../alterForeignKeyHelper.js | 192 +++++++ .../alterScriptHelpers/alterViewHelper.js | 106 ++++ .../columnHelpers/alterColumnNameHelper.js | 71 +++ .../columnHelpers/alterTypeHelper.js | 105 ++++ .../columnHelpers/commentsHelper.js | 97 ++++ .../columnHelpers/defaultValueHelper.js | 124 +++++ .../columnHelpers/nonNullConstraintHelper.js | 112 ++++ .../containerHelpers/commentsHelper.js | 37 ++ .../createColumnDefinition.js | 126 +++++ .../entityHelpers/alterTableNameHelper.js | 51 ++ .../entityHelpers/checkConstraintHelper.js | 183 ++++++ .../entityHelpers/commentsHelper.js | 82 +++ .../entityHelpers/indexesHelper.js | 241 ++++++++ .../entityHelpers/keyConstraintsHelper.js | 520 ++++++++++++++++++ .../entityHelpers/primaryKeyHelper.js | 59 ++ .../entityHelpers/uniqueKeyHelper.js | 58 ++ .../indexHelpers/addNameToIndexKey.js | 71 +++ .../indexHelpers/commentsHelper.js | 51 ++ .../viewHelpers/alterNameHelper.js | 43 ++ .../viewHelpers/alterViewStatementHelper.js | 41 ++ .../viewHelpers/commentsHelper.js | 79 +++ .../viewHelpers/createDropViewHelper.js | 93 ++++ .../alterScript/dto/alterScriptDto.js | 78 +++ forward_engineering/alterScript/dto/keyDto.js | 44 ++ forward_engineering/api.js | 38 -- forward_engineering/api/applyToInstance.js | 18 - .../api/generateContainerScript.js | 36 +- forward_engineering/api/generateScript.js | 36 +- forward_engineering/api/isDropInStatements.js | 43 +- forward_engineering/config.json | 3 +- .../ddlHelpers/comment/commentHelper.js | 78 +++ .../ddlHelpers/constraint/getOptionsString.js | 10 +- .../ddlHelpers/index/getIndexName.js | 21 + .../ddlHelpers/index/getIndexOptions.js | 176 ++++++ .../ddlHelpers/index/getIndexType.js | 23 + .../ddlHelpers/key/constraintsHelper.js | 117 ++++ .../ddlProvider/ddlHelpers/key/keyHelper.js | 2 +- .../ddlHelpers/table/getTableProps.js | 7 +- .../ddlProvider/ddlProvider.js | 90 ++- forward_engineering/ddlProvider/templates.js | 14 +- forward_engineering/types/alterScript.d.ts | 265 +++++++++ forward_engineering/types/ddlProvider.d.ts | 77 ++- forward_engineering/utils/general.js | 5 +- forward_engineering/utils/toPluginError.js | 17 + .../entity_level/entityLevelConfig.json | 290 +++++++++- 50 files changed, 4589 insertions(+), 157 deletions(-) create mode 100644 forward_engineering/alterScript/alterScriptBuilder.js create mode 100644 forward_engineering/alterScript/alterScriptFromDeltaHelper.js create mode 100644 forward_engineering/alterScript/alterScriptHelpers/alterContainerHelper.js create mode 100644 forward_engineering/alterScript/alterScriptHelpers/alterEntityHelper.js create mode 100644 forward_engineering/alterScript/alterScriptHelpers/alterForeignKeyHelper.js create mode 100644 forward_engineering/alterScript/alterScriptHelpers/alterViewHelper.js create mode 100644 forward_engineering/alterScript/alterScriptHelpers/columnHelpers/alterColumnNameHelper.js create mode 100644 forward_engineering/alterScript/alterScriptHelpers/columnHelpers/alterTypeHelper.js create mode 100644 forward_engineering/alterScript/alterScriptHelpers/columnHelpers/commentsHelper.js create mode 100644 forward_engineering/alterScript/alterScriptHelpers/columnHelpers/defaultValueHelper.js create mode 100644 forward_engineering/alterScript/alterScriptHelpers/columnHelpers/nonNullConstraintHelper.js create mode 100644 forward_engineering/alterScript/alterScriptHelpers/containerHelpers/commentsHelper.js create mode 100644 forward_engineering/alterScript/alterScriptHelpers/createColumnDefinition.js create mode 100644 forward_engineering/alterScript/alterScriptHelpers/entityHelpers/alterTableNameHelper.js create mode 100644 forward_engineering/alterScript/alterScriptHelpers/entityHelpers/checkConstraintHelper.js create mode 100644 forward_engineering/alterScript/alterScriptHelpers/entityHelpers/commentsHelper.js create mode 100644 forward_engineering/alterScript/alterScriptHelpers/entityHelpers/indexesHelper.js create mode 100644 forward_engineering/alterScript/alterScriptHelpers/entityHelpers/keyConstraintsHelper.js create mode 100644 forward_engineering/alterScript/alterScriptHelpers/entityHelpers/primaryKeyHelper.js create mode 100644 forward_engineering/alterScript/alterScriptHelpers/entityHelpers/uniqueKeyHelper.js create mode 100644 forward_engineering/alterScript/alterScriptHelpers/indexHelpers/addNameToIndexKey.js create mode 100644 forward_engineering/alterScript/alterScriptHelpers/indexHelpers/commentsHelper.js create mode 100644 forward_engineering/alterScript/alterScriptHelpers/viewHelpers/alterNameHelper.js create mode 100644 forward_engineering/alterScript/alterScriptHelpers/viewHelpers/alterViewStatementHelper.js create mode 100644 forward_engineering/alterScript/alterScriptHelpers/viewHelpers/commentsHelper.js create mode 100644 forward_engineering/alterScript/alterScriptHelpers/viewHelpers/createDropViewHelper.js create mode 100644 forward_engineering/alterScript/dto/alterScriptDto.js create mode 100644 forward_engineering/alterScript/dto/keyDto.js delete mode 100644 forward_engineering/api/applyToInstance.js create mode 100644 forward_engineering/ddlProvider/ddlHelpers/index/getIndexName.js create mode 100644 forward_engineering/ddlProvider/ddlHelpers/index/getIndexOptions.js create mode 100644 forward_engineering/ddlProvider/ddlHelpers/index/getIndexType.js create mode 100644 forward_engineering/ddlProvider/ddlHelpers/key/constraintsHelper.js create mode 100644 forward_engineering/types/alterScript.d.ts create mode 100644 forward_engineering/utils/toPluginError.js diff --git a/esbuild.package.js b/esbuild.package.js index 954ac93..0698d89 100644 --- a/esbuild.package.js +++ b/esbuild.package.js @@ -45,9 +45,9 @@ async function packagePlugin() { /** @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, '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)); 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..07d89a2 --- /dev/null +++ b/forward_engineering/alterScript/alterScriptFromDeltaHelper.js @@ -0,0 +1,243 @@ +/** + * @import { + * AlterRelationship, + * AlterScriptData, + * AlterScriptDto, + * DeltaBucket, + * DeltaModel, + * DeltaSection + * } from '../types/alterScript' + * @import {App} from '../types/ddlProvider' + */ + +const { getContainersScripts } = require('./alterScriptHelpers/alterContainerHelper'); +const { getEntitiesScripts } = require('./alterScriptHelpers/alterEntityHelper'); +const { + getDeleteForeignKeyScriptDtos, + getAddForeignKeyScriptDtos, + getModifyForeignKeyScriptDtos, +} = require('./alterScriptHelpers/alterForeignKeyHelper'); +const { getViewsScripts } = require('./alterScriptHelpers/alterViewHelper'); + +/** + * 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 table and column statements. + * + * @param {{ collection: DeltaModel; app: App; inlineDeltaRelationships: AlterRelationship[] }} params Delta model, app + * instance and relationships rendered inline in table definitions. + * @returns {AlterScriptDto[]} Alter script DTOs. + */ +const getAlterCollectionScriptDtos = ({ collection, app, inlineDeltaRelationships }) => { + const { added, deleted, modified } = getSectionItems(collection.properties?.entities); + const { + getAddCollectionScriptDto, + getDeleteCollectionScriptDto, + getModifyCollectionScriptDtos, + getModifyCollectionKeysScriptDtos, + getModifyColumnScriptDtos, + getAddColumnScriptDtos, + getDeleteColumnScriptDtos, + } = getEntitiesScripts(app, inlineDeltaRelationships); + + 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)), + ...added.flatMap(item => getAddColumnScriptDtos(item)), + ...modified.flatMap(item => getModifyColumnScriptDtos(item)), + ...modified.flatMap(item => getModifyCollectionKeysScriptDtos(item)), + ]; +}; + +/** + * 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 }; +}; + +/** + * 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) => { + /** @type {DeltaModel} */ + const collection = JSON.parse(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 { deletedContainersScriptDtos, upsertedContainersScriptDtos } = getAlterContainersScriptDtos({ + collection, + app, + }); + + return [ + ...upsertedContainersScriptDtos, + ...getAlterCollectionScriptDtos({ collection, app, inlineDeltaRelationships }), + ...getAlterRelationshipsScriptDtos({ collection, ignoreRelationshipIDs }), + ...getAlterViewScriptDtos({ collection, app }), + ...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..2191146 --- /dev/null +++ b/forward_engineering/alterScript/alterScriptHelpers/alterEntityHelper.js @@ -0,0 +1,262 @@ +/** + * @import { + * AlterCollection, + * AlterRelationship, + * AlterScriptDto + * } from '../../types/alterScript' + * @import { + * App, + * DdlProvider, + * ForeignKeyStatement + * } from '../../types/ddlProvider' + */ + +const lodash = require('lodash'); +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. + * @returns {(collection: AlterCollection) => AlterScriptDto | undefined} Add collection script builder. + */ +const getAddCollectionScriptDto = (ddlProvider, inlineDeltaRelationships) => collection => { + const collectionSchema = getSchemaOfAlterCollection(collection); + const schemaName = getSchemaNameFromCollection({ collection }) ?? ''; + const schemaData = { schemaName }; + + const columnDefinitions = lodash.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, + }, + 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 lodash + .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 lodash + .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. + * @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) => { + const ddlProvider = require('../../ddlProvider/ddlProvider')(null, null, app); + + return { + getAddCollectionScriptDto: getAddCollectionScriptDto(ddlProvider, inlineDeltaRelationships), + 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..cc1297d --- /dev/null +++ b/forward_engineering/alterScript/alterScriptHelpers/alterForeignKeyHelper.js @@ -0,0 +1,192 @@ +/** + * @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'); + +/** + * Resolve the current name of a relationship. + * + * @param {AlterRelationship} relationship Relationship delta. + * @returns {string} Relationship name. + */ +const getRelationshipName = relationship => { + const compMod = relationship.role.compMod; + + return compMod?.code?.new ?? compMod?.name?.new ?? relationship.role.code ?? relationship.role.name ?? ''; +}; + +/** + * Resolve the previous name of a relationship. + * + * @param {AlterRelationship} relationship Relationship delta. + * @returns {string} Relationship name. + */ +const getOldRelationshipName = relationship => { + const compMod = relationship.role.compMod; + + return compMod?.code?.old ?? compMod?.name?.old ?? relationship.role.code ?? relationship.role.name ?? ''; +}; + +/** + * 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/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..ee4a5ee --- /dev/null +++ b/forward_engineering/alterScript/alterScriptHelpers/columnHelpers/alterColumnNameHelper.js @@ -0,0 +1,71 @@ +/** + * @import { + * AlterCollection, + * AlterScriptDto + * } from '../../../types/alterScript' + */ + +const lodash = require('lodash'); +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 lodash + .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..6954ecc --- /dev/null +++ b/forward_engineering/alterScript/alterScriptHelpers/columnHelpers/alterTypeHelper.js @@ -0,0 +1,105 @@ +/** + * @import { + * AlterCollection, + * AlterColumn, + * AlterScriptDto + * } from '../../../types/alterScript' + * @import {DdlProvider} from '../../../types/ddlProvider' + */ + +const lodash = require('lodash'); +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 lodash + .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..bb7c767 --- /dev/null +++ b/forward_engineering/alterScript/alterScriptHelpers/columnHelpers/commentsHelper.js @@ -0,0 +1,97 @@ +/** + * @import { + * AlterCollection, + * AlterScriptDto + * } from '../../../types/alterScript' + */ + +const lodash = require('lodash'); +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 lodash + .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 lodash + .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..f30a278 --- /dev/null +++ b/forward_engineering/alterScript/alterScriptHelpers/columnHelpers/defaultValueHelper.js @@ -0,0 +1,124 @@ +/** + * @import { + * AlterCollection, + * AlterScriptDto + * } from '../../../types/alterScript' + */ + +const lodash = require('lodash'); +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 lodash + .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 lodash + .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..28fbfc0 --- /dev/null +++ b/forward_engineering/alterScript/alterScriptHelpers/columnHelpers/nonNullConstraintHelper.js @@ -0,0 +1,112 @@ +/** + * @import { + * AlterCollection, + * AlterScriptDto + * } from '../../../types/alterScript' + */ + +const lodash = require('lodash'); +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 = lodash.difference( + currentRequiredColumnNames, + previousRequiredColumnNames, + ); + const columnNamesToRemoveNotNullConstraint = lodash.difference( + previousRequiredColumnNames, + currentRequiredColumnNames, + ); + + const columns = lodash.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..4769ded --- /dev/null +++ b/forward_engineering/alterScript/alterScriptHelpers/createColumnDefinition.js @@ -0,0 +1,126 @@ +/** + * @import { + * AlterCollection, + * AlterColumn + * } from '../../types/alterScript' + * @import { + * ColumnDefinitionInput, + * DdlProvider, + * HydratedColumn, + * SchemaData + * } from '../../types/ddlProvider' + */ + +const lodash = require('lodash'); + +/** + * 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 (lodash.isNumber(jsonSchema.length)) { + return jsonSchema.length; + } + + if (lodash.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 (lodash.isNumber(jsonSchema.precision)) { + return jsonSchema.precision; + } + + if (lodash.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, + type: getType(jsonSchema), + nullable: isNullable(parentJsonSchema, name), + default: getDefault(jsonSchema), + length: getLength(jsonSchema), + scale: lodash.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..0778acf --- /dev/null +++ b/forward_engineering/alterScript/alterScriptHelpers/entityHelpers/checkConstraintHelper.js @@ -0,0 +1,183 @@ +/** + * @import { + * AlterCollection, + * AlterScriptDto, + * CheckConstraintHistoryEntry + * } from '../../../types/alterScript' + */ + +const lodash = require('lodash'); +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 = lodash.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..d63da5d --- /dev/null +++ b/forward_engineering/alterScript/alterScriptHelpers/entityHelpers/indexesHelper.js @@ -0,0 +1,241 @@ +/** + * @import { + * AlterCollection, + * AlterIndex, + * AlterScriptDto + * } from '../../../types/alterScript' + * @import {DdlProvider} from '../../../types/ddlProvider' + */ + +const lodash = require('lodash'); +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 => !lodash.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); + const script = ddlProvider.createIndex(getEntityName(collectionSchema), addNameToIndexKey({ index, collection })); + + return createAlterScriptDto([script], true, 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..4354b72 --- /dev/null +++ b/forward_engineering/alterScript/alterScriptHelpers/entityHelpers/keyConstraintsHelper.js @@ -0,0 +1,520 @@ +/** + * 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 lodash = require('lodash'); +const { createAlterScriptDto } = require('../../dto/alterScriptDto'); +const { createKeyScriptModification, keyTransition, noKeyTransition } = require('../../dto/keyDto'); +const { + getFullCollectionName, + getSchemaOfAlterCollection, + getEntityName, + wrapInQuotes, + isParentContainerActivated, + isObjectInDeltaModelActivated, +} = require('../../../utils/general'); + +const AMOUNT_OF_COLUMNS_IN_REGULAR_KEY = 1; + +/** + * Build the constraint name Db2 for z/OS falls back to when the user did not provide one. + * + * @param {string} entityName Table name. + * @param {AlterKeyKind} keyKind Key kind. + * @returns {string} Constraint name. + */ +const getDefaultConstraintName = (entityName, keyKind) => [entityName, keyKind.constraintPostfix].join('_'); + +/** + * 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 lodash.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 lodash.differenceWith(oldKeys, newKeys, (oldKey, newKey) => lodash.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 }) => + lodash + .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 ?? getDefaultConstraintName(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 ?? getDefaultConstraintName(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 !lodash.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 lodash + .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 === '' + ? getDefaultConstraintName(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 lodash + .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 === '' + ? getDefaultConstraintName(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 => + lodash.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..7e9d4e6 --- /dev/null +++ b/forward_engineering/alterScript/alterScriptHelpers/indexHelpers/addNameToIndexKey.js @@ -0,0 +1,71 @@ +/** + * @import { + * AlterCollection, + * AlterIndex, + * AlterIndexKey + * } from '../../../types/alterScript' + */ + +const lodash = require('lodash'); +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 = lodash.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 index 8386628..5a0be82 100644 --- a/forward_engineering/api.js +++ b/forward_engineering/api.js @@ -1,49 +1,11 @@ const { generateContainerScript } = require('./api/generateContainerScript'); const { isDropInStatements } = require('./api/isDropInStatements'); -const { applyToInstance } = require('./api/applyToInstance'); const { generateScript } = require('./api/generateScript'); module.exports = { generateScript, - /** - * Generate view script stub. - * - * @param {unknown} _data Script data. - * @param {unknown} _logger Logger. - * @param {unknown} _callback Callback. - * @param {unknown} _app App instance. - * @returns {never} Always throws. - */ - generateViewScript(_data, _logger, _callback, _app) { - throw new Error('Not implemented'); - }, - generateContainerScript, - /** - * Get databases stub. - * - * @param {unknown} _connectionInfo Connection info. - * @param {unknown} _logger Logger. - * @param {unknown} _callback Callback. - * @param {unknown} _app App instance. - * @returns {never} Always throws. - */ - getDatabases(_connectionInfo, _logger, _callback, _app) { - throw new Error('Not implemented'); - }, - - applyToInstance, - - /** - * Test connection stub. - * - * @returns {never} Always throws. - */ - testConnection() { - throw new Error('Not implemented'); - }, - isDropInStatements, }; diff --git a/forward_engineering/api/applyToInstance.js b/forward_engineering/api/applyToInstance.js deleted file mode 100644 index bc80166..0000000 --- a/forward_engineering/api/applyToInstance.js +++ /dev/null @@ -1,18 +0,0 @@ -/** - * Apply DDL to instance stub. - * - * 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 {unknown} _connectionInfo Connection info. - * @param {unknown} _logger Logger. - * @param {(...args: unknown[]) => void} _callback Callback. - * @param {unknown} _app App instance. - * @returns {never} Always throws. - */ -function applyToInstance(_connectionInfo, _logger, _callback, _app) { - // Apply to instance is out of scope for ddlProvider kickoff. - throw new Error('Not implemented'); -} - -module.exports = { applyToInstance }; diff --git a/forward_engineering/api/generateContainerScript.js b/forward_engineering/api/generateContainerScript.js index 095562c..47e4be5 100644 --- a/forward_engineering/api/generateContainerScript.js +++ b/forward_engineering/api/generateContainerScript.js @@ -1,18 +1,36 @@ /** - * Generate container script stub. + * @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 {unknown} _data Script data. - * @param {unknown} _logger Logger. - * @param {(...args: unknown[]) => void} _callback Callback. - * @param {unknown} _app App instance. - * @returns {never} Always throws. + * @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) { - // Comp-mode / alter script generation is out of scope for ddlProvider kickoff. - throw new Error('Not implemented'); +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 = { diff --git a/forward_engineering/api/generateScript.js b/forward_engineering/api/generateScript.js index 65a3ca3..b6dd272 100644 --- a/forward_engineering/api/generateScript.js +++ b/forward_engineering/api/generateScript.js @@ -1,18 +1,36 @@ /** - * Generate entity script stub. + * @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 {unknown} _data Script data. - * @param {unknown} _logger Logger. - * @param {(...args: unknown[]) => void} _callback Callback. - * @param {unknown} _app App instance. - * @returns {never} Always throws. + * @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) { - // Comp-mode / alter script generation is out of scope for ddlProvider kickoff. - throw new Error('Not implemented'); +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 = { diff --git a/forward_engineering/api/isDropInStatements.js b/forward_engineering/api/isDropInStatements.js index 706f737..20d7187 100644 --- a/forward_engineering/api/isDropInStatements.js +++ b/forward_engineering/api/isDropInStatements.js @@ -1,17 +1,42 @@ /** - * Detect drop statements. + * @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. * - * Reports that no DROP statements are produced, which holds while alter script generation is not implemented. The - * callback must always be invoked: the studio has no timeout on this request. + * 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 {unknown} _data Script data. - * @param {unknown} _logger Logger. - * @param {(...args: unknown[]) => void} callback Callback. - * @param {unknown} _app App instance. + * @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) { - callback(null, false); +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 = { diff --git a/forward_engineering/config.json b/forward_engineering/config.json index 4902d7e..08abcce 100644 --- a/forward_engineering/config.json +++ b/forward_engineering/config.json @@ -17,8 +17,7 @@ }, "compMode": { "entity": true, - "container": true, - "useDdlProvider": true + "container": true }, "namePrefix": "Db2 for z/OS", "level": { diff --git a/forward_engineering/ddlProvider/ddlHelpers/comment/commentHelper.js b/forward_engineering/ddlProvider/ddlHelpers/comment/commentHelper.js index c5db5cb..c8e7428 100644 --- a/forward_engineering/ddlProvider/ddlHelpers/comment/commentHelper.js +++ b/forward_engineering/ddlProvider/ddlHelpers/comment/commentHelper.js @@ -16,6 +16,7 @@ const OBJECT_TYPE = { schema: 'SCHEMA', column: 'COLUMN', table: 'TABLE', + index: 'INDEX', }; /** @enum {string} */ @@ -84,6 +85,36 @@ const getTableCommentStatement = ({ tableName, description }) => { }); }; +/** + * 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 schema comment statement. * @@ -121,9 +152,56 @@ const getColumnComments = ({ tableName, columnDefinitions }) => { .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, getColumnComments, + dropSchemaCommentStatement, + dropTableCommentStatement, + dropTableColumnCommentStatement, }; diff --git a/forward_engineering/ddlProvider/ddlHelpers/constraint/getOptionsString.js b/forward_engineering/ddlProvider/ddlHelpers/constraint/getOptionsString.js index 7531d2b..c47eebc 100644 --- a/forward_engineering/ddlProvider/ddlHelpers/constraint/getOptionsString.js +++ b/forward_engineering/ddlProvider/ddlHelpers/constraint/getOptionsString.js @@ -8,21 +8,17 @@ const { wrapInQuotes } = require('../../../utils/general'); /** - * Build constraint option fragments. + * 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, deferClause, rely, validate, indexClause, exceptionClause }) => { +const getOptionsString = ({ constraintName }) => { const constraintString = constraintName ? ` CONSTRAINT ${wrapInQuotes(constraintName.trim())}` : ''; - const statement = [deferClause, rely, indexClause, validate, exceptionClause] - .filter(Boolean) - .map(option => ` ${option}`) - .join(''); return { constraintString, - statement, + statement: '', }; }; 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..8748990 --- /dev/null +++ b/forward_engineering/ddlProvider/ddlHelpers/index/getIndexOptions.js @@ -0,0 +1,176 @@ +/** + * @import { + * IndexData, + * IndexKeyRef + * } from '../../../types/ddlProvider' + */ + +const lodash = require('lodash'); +const { getBasicValue, getOptionsByConfigs } = require('../options/getOptionsByConfigs'); +const { wrapInQuotes } = require('../../../utils/general'); + +/** + * Convert a value to upper case. + * + * @param {string} value Value to convert. + * @returns {string} Upper-case value. + */ +const toUpperCase = value => lodash.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. + * + * @param {IndexKeyRef[]} [keys] Index keys. + * @returns {string} Keys clause. + */ +const getIndexKeys = (keys = []) => { + if (keys.length === 0) { + return ''; + } + + const keysClause = keys + .map(({ name, type }) => wrapInQuotes(name ?? '') + getUpperCaseValue(' ')(type ?? '')) + .join(', '); + + return `(${keysClause})`; +}; + +/** + * Build INCLUDE column list for unique indexes. + * + * @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') { + return ''; + } + + const includeIndexKeys = getIndexKeys(keys); + + 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 (lodash.isNumber(index.indxPriQty)) { + parts.push(`PRIQTY ${index.indxPriQty}`); + } + if (lodash.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 (!lodash.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 => lodash.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/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/keyHelper.js b/forward_engineering/ddlProvider/ddlHelpers/key/keyHelper.js index 786c996..0e41146 100644 --- a/forward_engineering/ddlProvider/ddlHelpers/key/keyHelper.js +++ b/forward_engineering/ddlProvider/ddlHelpers/key/keyHelper.js @@ -86,7 +86,7 @@ const hydrateKeyOptions = ({ columnName, isActivated, options, keyType }) => { isActivated: isActivated, }, ], - ...lodash.pickBy(options ?? {}, value => !lodash.isNil(value)), + constraintName: options?.constraintName, }; }; diff --git a/forward_engineering/ddlProvider/ddlHelpers/table/getTableProps.js b/forward_engineering/ddlProvider/ddlHelpers/table/getTableProps.js index 6f2115f..af05789 100644 --- a/forward_engineering/ddlProvider/ddlHelpers/table/getTableProps.js +++ b/forward_engineering/ddlProvider/ddlHelpers/table/getTableProps.js @@ -101,7 +101,7 @@ const getDividedForeignKeyConstraints = ({ foreignKeyConstraints }) => { * @param {TablePropsParams} params Table props input. * @returns {string} Table props DDL. */ -const getTableProps = ({ columns, foreignKeyConstraints, keyConstraints, isActivated }) => { +const getTableProps = ({ columns, foreignKeyConstraints, keyConstraints, checkConstraints, isActivated }) => { const dividedKeysConstraints = getDividedKeysConstraints({ keyConstraints, isActivated }); const dividedForeignKeyConstraints = getDividedForeignKeyConstraints({ foreignKeyConstraints }); const keyConstraintsString = generateConstraintsString({ @@ -112,6 +112,10 @@ const getTableProps = ({ columns, foreignKeyConstraints, keyConstraints, isActiv dividedConstraints: dividedForeignKeyConstraints, isParentActivated: isActivated, }); + const checkConstraintsString = generateConstraintsString({ + dividedConstraints: { activatedItems: checkConstraints ?? [], deactivatedItems: [] }, + isParentActivated: isActivated, + }); const columnsString = joinActivatedAndDeactivatedStatements({ statements: columns, indent: '\n\t' }); const tableProps = assignTemplates({ @@ -120,6 +124,7 @@ const getTableProps = ({ columns, foreignKeyConstraints, keyConstraints, isActiv columns: columnsString, foreignKeyConstraints: foreignKeyConstraintsString, keyConstraints: keyConstraintsString, + checkConstraints: checkConstraintsString, }, }); diff --git a/forward_engineering/ddlProvider/ddlProvider.js b/forward_engineering/ddlProvider/ddlProvider.js index f5a3dc1..e127940 100644 --- a/forward_engineering/ddlProvider/ddlProvider.js +++ b/forward_engineering/ddlProvider/ddlProvider.js @@ -46,6 +46,7 @@ const { getTableCommentStatement, getColumnComments, getSchemaCommentStatement, + getIndexCommentStatement, } = require('./ddlHelpers/comment/commentHelper.js'); const { getTableProps } = require('./ddlHelpers/table/getTableProps.js'); const { getTableOptions } = require('./ddlHelpers/table/getTableOptions.js'); @@ -54,6 +55,9 @@ const { getTableType } = require('./ddlHelpers/table/getTableType.js'); const { hydrateAuxiliaryTableData } = require('./ddlHelpers/table/hydrateAuxiliaryTableData.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. @@ -265,14 +269,29 @@ const hydrateCheckConstraint = checkConstraint => ({ expression: checkConstraint.constrExpression, comments: checkConstraint.constrComments, description: checkConstraint.constrDescription, + enforced: checkConstraint.constrEnforced, }); /** * Create check constraint DDL. * - * @returns {string} Empty stub. + * @param {HydratedCheckConstraint} params Check constraint data. + * @returns {string} Check constraint fragment. */ -const createCheckConstraint = () => ''; +const createCheckConstraint = ({ name, expression, enforced } = {}) => { + if (!expression) { + return ''; + } + + return assignTemplates({ + template: templates.checkConstraint, + templateData: { + name: name ? `CONSTRAINT ${wrapInQuotes(name)} ` : '', + expression: lodash.trim(expression).replace(/^\(([\s\S]*)\)$/u, '$1'), + enforced: enforced ? ` ${enforced}` : '', + }, + }); +}; /** * Create foreign key constraint fragment. @@ -452,6 +471,7 @@ const createTable = (tableData, isActivated = true) => { columns, foreignKeyConstraints, keyConstraints, + checkConstraints, name, schemaData, auxiliary, @@ -501,6 +521,7 @@ const createTable = (tableData, isActivated = true) => { columns: columns ?? [], foreignKeyConstraints: foreignKeyConstraints ?? [], keyConstraints: keyConstraints ?? [], + checkConstraints: checkConstraints ?? [], isActivated, }); const renderedTableOptions = getTableOptions({ @@ -571,28 +592,75 @@ const dropView = ({ viewName }) => assignTemplates({ template: templates.dropVie * Hydrate index data. * * @param {IndexData} indexData Index data. - * @param {unknown} [_tableData] Table data. + * @param {unknown} [tableData] Table data. * @param {SchemaData} [schemaData] Schema data. * @returns {IndexData} Hydrated index. */ -const hydrateIndex = (indexData, _tableData, schemaData) => ({ - ...indexData, - schemaName: schemaData?.schemaName, -}); +const hydrateIndex = (indexData, tableData, schemaData) => { + const isParentActivated = lodash.get(tableData, '[0].isActivated', true); + + return { + ...indexData, + schemaName: schemaData?.schemaName, + isParentActivated, + }; +}; /** * Create index DDL. * - * @returns {string} Empty stub. + * @param {string} tableName Table name. + * @param {IndexData} index Index data. + * @returns {string} Index DDL. */ -const createIndex = () => ''; +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: lodash.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. * - * @returns {string} Empty stub. + * @param {string} name Index name. + * @returns {string} Drop index DDL. */ -const dropIndex = () => ''; +const dropIndex = name => { + if (!name) { + return ''; + } + + return assignTemplates({ template: templates.dropIndex, templateData: { name } }); +}; /** * Hydrate view column. diff --git a/forward_engineering/ddlProvider/templates.js b/forward_engineering/ddlProvider/templates.js index c010616..be7d435 100644 --- a/forward_engineering/ddlProvider/templates.js +++ b/forward_engineering/ddlProvider/templates.js @@ -17,7 +17,7 @@ module.exports = { comment: '\nCOMMENT ON ${objectType} ${objectName} IS ${comment};\n', - createTableProps: '${columns}${keyConstraints}${foreignKeyConstraints}', + createTableProps: '${columns}${keyConstraints}${checkConstraints}${foreignKeyConstraints}', columnDefinition: '${name}${type}${default}${constraints}', @@ -29,12 +29,16 @@ module.exports = { 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};', @@ -49,6 +53,10 @@ module.exports = { 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};', @@ -58,4 +66,8 @@ module.exports = { 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..d625a6b --- /dev/null +++ b/forward_engineering/types/alterScript.d.ts @@ -0,0 +1,265 @@ +/** 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[]; +}; + +export type AlterContainerRole = { + name: string; + description?: string; + isActivated?: boolean; + compMod?: { + description?: PropertyPair; + }; +}; + +export type AlterContainer = { + role: AlterContainerRole; + isActivated?: boolean; +}; + +export type ViewDefinitionRef = { + name?: string; + definition?: JsonSchemaColumn; + collection?: Array<{ code?: string; collectionName?: string }>; + bucket?: Array<{ code?: string; name?: string }>; +}; + +export type AlterView = 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; + }; +}; + +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 index 516c7d0..35d2763 100644 --- a/forward_engineering/types/ddlProvider.d.ts +++ b/forward_engineering/types/ddlProvider.d.ts @@ -43,7 +43,7 @@ export type ColumnDefinitionInput = { name: string; type?: string; nullable?: boolean; - default?: string | number; + default?: DefaultValue; isActivated?: boolean; scale?: number; precision?: number; @@ -51,12 +51,8 @@ export type ColumnDefinitionInput = { }; export type KeyOptions = { + id?: string; constraintName?: string; - deferClause?: string; - rely?: string; - validate?: string; - indexClause?: string; - exceptionClause?: string; }; export type JsonSchemaColumn = { @@ -121,7 +117,7 @@ export type HydratedColumn = { unique: boolean; uniqueKeyOptions?: KeyOptions; nullable?: boolean; - default?: string | number; + default?: DefaultValue; comment?: string; isActivated?: boolean; scale?: number; @@ -259,11 +255,18 @@ export type KeyConstraint = { keyType: string; constraintName?: string; columns: KeyConstraintColumn[]; - deferClause?: string; - rely?: string; - validate?: string; - indexClause?: string; - exceptionClause?: string; +}; + +export type AlterKeyConfig = { + keyType: string; + name: string; + columns: KeyConstraintColumn[]; + options?: KeyOptions; +}; + +export type AlterKeyStatement = { + statement: string; + isActivated: boolean; }; export type ForeignKeyStatement = { @@ -289,9 +292,10 @@ export type ForeignKeyInput = { export type HydratedTable = { name: string; - schemaData?: SchemaData; + schemaData: SchemaData; relatedSchemas?: Record; keyConstraints?: KeyConstraint[]; + checkConstraints?: string[]; description?: string; tableProperties?: string; auxiliary?: boolean; @@ -317,6 +321,7 @@ export type CreateTableParams = { columns?: string[]; foreignKeyConstraints?: ForeignKeyStatement[]; keyConstraints?: KeyConstraint[]; + checkConstraints?: string[]; name: string; schemaData: SchemaData; description?: string; @@ -362,6 +367,7 @@ export type CheckConstraintInput = { constrExpression?: string; constrComments?: string; constrDescription?: string; + constrEnforced?: string; }; export type HydratedCheckConstraint = { @@ -369,11 +375,43 @@ export type HydratedCheckConstraint = { expression?: string; comments?: string; description?: string; + enforced?: string; +}; + +export type IndexKeyRef = { + name?: string; + type?: string; + isActivated?: boolean; }; export type IndexData = { indxName?: string; - indxKey?: unknown[]; + 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; @@ -402,12 +440,12 @@ export type ActivatedKey = { type?: string; }; -export type DefaultValue = string | number; +export type DefaultValue = string | number | boolean; export type CompMod = { - collectionName?: { new?: string }; + collectionName?: PropertyPair; keyspaceName?: string; - isActivated?: { new?: boolean }; + isActivated?: PropertyPair; bucketProperties?: { isActivated?: boolean }; }; @@ -489,6 +527,7 @@ export type TablePropsParams = { columns: string[]; foreignKeyConstraints: ForeignKeyStatement[]; keyConstraints: KeyConstraint[]; + checkConstraints?: string[]; isActivated: boolean; }; @@ -656,7 +695,7 @@ export type DdlProvider = { dropView(params: { viewName: string }): string; hydrateCheckConstraint(checkConstraint: CheckConstraintInput): HydratedCheckConstraint; - createCheckConstraint(params?: { name?: string; expression?: string }): string; + createCheckConstraint(params?: HydratedCheckConstraint): string; hydrateIndex(indexData: IndexData, tableData?: unknown, schemaData?: SchemaData): IndexData; createIndex(tableName?: string, index?: IndexData): string; @@ -674,5 +713,5 @@ export type DdlProvider = { export type DdlProviderFactory = ( baseProvider: BaseProvider | null, options: DdlProviderOptions | null, - app: App, + app: App | null, ) => DdlProvider; diff --git a/forward_engineering/utils/general.js b/forward_engineering/utils/general.js index 66ca41f..79e5e24 100644 --- a/forward_engineering/utils/general.js +++ b/forward_engineering/utils/general.js @@ -245,8 +245,9 @@ const getFullCollectionName = ({ collectionSchema, preferAlterName = true }) => /** * Get schema of an alter collection. * - * @param {ModelObject} collection Collection. - * @returns {ModelObject} Merged schema. + * @template {ModelObject} T + * @param {T} collection Collection. + * @returns {T} Merged schema. */ const getSchemaOfAlterCollection = collection => { return { ...collection, ...lodash.omit(collection?.role, 'properties') }; 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/properties_pane/entity_level/entityLevelConfig.json b/properties_pane/entity_level/entityLevelConfig.json index f07ca9b..df45444 100644 --- a/properties_pane/entity_level/entityLevelConfig.json +++ b/properties_pane/entity_level/entityLevelConfig.json @@ -704,7 +704,7 @@ making sure that you maintain a proper JSON format. { "propertyName": "Constraint name", "propertyKeyword": "constraintName", - "propertyTooltip": "", + "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", @@ -721,7 +721,7 @@ making sure that you maintain a proper JSON format. { "propertyName": "Comment", "propertyKeyword": "indexComment", - "propertyTooltip": "comment", + "propertyTooltip": "Optional comment for the primary key constraint.", "addTimestampButton": false, "propertyType": "details", "template": "codeEditor", @@ -775,12 +775,12 @@ making sure that you maintain a proper JSON format. "propertyName": "Unique key", "propertyType": "group", "propertyKeyword": "uniqueKey", - "propertyTooltip": "", + "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": "", + "propertyTooltip": "Optional CONSTRAINT name for the UNIQUE key.", "propertyType": "text", "validation": { "indexKey": "compositeUniqueKey", @@ -801,7 +801,7 @@ making sure that you maintain a proper JSON format. { "propertyName": "Comment", "propertyKeyword": "indexComment", - "propertyTooltip": "comment", + "propertyTooltip": "Optional comment for the unique key constraint.", "addTimestampButton": false, "propertyType": "details", "template": "codeEditor", @@ -812,7 +812,7 @@ making sure that you maintain a proper JSON format. { "propertyName": "Alternate key", "propertyKeyword": "alternateKey", - "propertyTooltip": "", + "propertyTooltip": "Marks the unique key as an alternate key for ERD display.", "propertyType": "checkbox", "setFieldPropertyBy": "compositeUniqueKey" } @@ -827,12 +827,12 @@ making sure that you maintain a proper JSON format. "propertyName": "Index", "propertyType": "group", "propertyKeyword": "Indxs", - "propertyTooltip": "", + "propertyTooltip": "Db2 for z/OS CREATE INDEX definition.", "structure": [ { "propertyName": "Name", "propertyKeyword": "indxName", - "propertyTooltip": "", + "propertyTooltip": "Index name (optionally schema-qualified in FE as schema.index-name).", "propertyType": "text", "validation": { "required": true @@ -841,19 +841,31 @@ making sure that you maintain a proper JSON format. { "propertyName": "Activated", "propertyKeyword": "isActivated", - "propertyTooltip": "Deactivated item will be not included in FE script", + "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": ["", "unique"] + "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"], @@ -866,38 +878,245 @@ making sure that you maintain a proper JSON format. "propertyName": "Include keys", "propertyKeyword": "indxIncludeKey", "propertyType": "fieldList", - "propertyTooltip": "Introduces a clause that specifies additional columns to be appended to the set of index key columns. Any columns included with this clause are not used to enforce uniqueness.", + "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": { - "key": "indxType", - "value": "unique" + "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": "Specifies whether index compression is enabled.", - "options": ["", "yes", "no"] + "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' specifies that an index entry is created when all parts of the index key contain the null value.", - "options": ["", "include", "exclude"] + "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": "Tablespace", - "propertyKeyword": "indxTablespace", - "propertyTooltip": "Specify the tablespace in which Db2 Database creates the table. If you omit TABLESPACE, then the database creates that item in the default tablespace of the owner of the schema containing the table.", + "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": "description", + "propertyTooltip": "Emitted as COMMENT ON INDEX.", "propertyType": "details", "template": "codeEditor", "templateOptions": { @@ -907,7 +1126,7 @@ making sure that you maintain a proper JSON format. { "propertyName": "Comments", "propertyKeyword": "indxComments", - "propertyTooltip": "comments", + "propertyTooltip": "Internal modeling comments (not emitted as DDL).", "addTimestampButton": false, "propertyType": "details", "template": "codeEditor", @@ -926,18 +1145,18 @@ making sure that you maintain a proper JSON format. "propertyName": "Check Constraint", "propertyType": "group", "propertyKeyword": "chkConstr", - "propertyTooltip": "", + "propertyTooltip": "Table check constraint: CONSTRAINT name CHECK (search-condition) [ENFORCED | NOT ENFORCED].", "structure": [ { "propertyName": "Name", "propertyKeyword": "chkConstrName", - "propertyTooltip": "", + "propertyTooltip": "Optional CONSTRAINT name for the check constraint.", "propertyType": "text" }, { "propertyName": "Expression", "propertyKeyword": "constrExpression", - "propertyTooltip": "Expression", + "propertyTooltip": "Search condition for CHECK (...). Outer parentheses are optional.", "propertyType": "details", "template": "textarea", "markdown": false, @@ -945,17 +1164,34 @@ making sure that you maintain a proper JSON format. "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", + "propertyTooltip": "Description of the check constraint.", "propertyType": "details", "template": "textarea" }, { "propertyName": "Comments", "propertyKeyword": "constrComments", - "propertyTooltip": "comments", + "propertyTooltip": "Internal modeling comments (not emitted as DDL).", "addTimestampButton": false, "propertyType": "details", "template": "textarea" From 9615c5073cbe2fe557253430a16fca7a715ad9f5 Mon Sep 17 00:00:00 2001 From: Vitalii Bedletskyi Date: Tue, 11 Aug 2026 12:19:31 +0300 Subject: [PATCH 07/15] fix review remarks and known issues --- .oxlintrc.json | 16 +-------- .../alterScript/alterScriptFromDeltaHelper.js | 18 ++++++++-- .../createColumnDefinition.js | 2 ++ .../entityHelpers/indexesHelper.js | 10 ++++-- .../entityHelpers/keyConstraintsHelper.js | 14 ++++---- .../columnDefinition/getColumnConstraints.js | 30 ++++++++++++++--- .../ddlHelpers/index/getIndexOptions.js | 16 ++++----- .../ddlHelpers/jsonSchema/jsonSchemaHelper.js | 9 +++-- .../key/getDefaultConstraintName.js | 15 +++++++++ .../ddlProvider/ddlHelpers/key/keyHelper.js | 33 +++++++++++-------- .../ddlHelpers/options/getOptionsByConfigs.js | 13 ++++++-- .../ddlHelpers/table/getTableOptions.js | 14 ++++---- .../ddlProvider/ddlProvider.js | 6 ++-- forward_engineering/types/ddlProvider.d.ts | 24 +++++++++++--- 14 files changed, 149 insertions(+), 71 deletions(-) create mode 100644 forward_engineering/ddlProvider/ddlHelpers/key/getDefaultConstraintName.js diff --git a/.oxlintrc.json b/.oxlintrc.json index da84345..18a6340 100644 --- a/.oxlintrc.json +++ b/.oxlintrc.json @@ -55,22 +55,8 @@ ".idea", ".vscode", "build", - "forward_engineering/node_modules", "node_modules", "out/**/*", - "release", - "reverse_engineering/node_modules" - ], - "overrides": [ - { - "files": ["forward_engineering/**/*.js"], - "rules": { - "typescript/no-unsafe-assignment": "off", - "typescript/no-unsafe-member-access": "off", - "typescript/no-unsafe-argument": "off", - "typescript/no-unsafe-return": "off", - "typescript/no-unsafe-call": "off" - } - } + "release" ] } diff --git a/forward_engineering/alterScript/alterScriptFromDeltaHelper.js b/forward_engineering/alterScript/alterScriptFromDeltaHelper.js index 07d89a2..5657b73 100644 --- a/forward_engineering/alterScript/alterScriptFromDeltaHelper.js +++ b/forward_engineering/alterScript/alterScriptFromDeltaHelper.js @@ -101,7 +101,8 @@ const getAlterCollectionScriptDtos = ({ collection, app, inlineDeltaRelationship .filter(scriptDto => scriptDto !== undefined), ...deleted.filter(item => !item.role?.compMod?.deleted).flatMap(item => getDeleteColumnScriptDtos(item)), ...modified.flatMap(item => getModifyCollectionScriptDtos(item)), - ...added.flatMap(item => getAddColumnScriptDtos(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)), ]; @@ -200,6 +201,18 @@ const prettifyAlterScriptDto = dto => { 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. * @@ -208,8 +221,7 @@ const prettifyAlterScriptDto = dto => { * @returns {AlterScriptDto[]} Alter script DTOs. */ const getAlterScriptDtos = (data, app) => { - /** @type {DeltaModel} */ - const collection = JSON.parse(data.jsonSchema); + const collection = parseDeltaModel(data.jsonSchema); if (!collection) { throw new Error( diff --git a/forward_engineering/alterScript/alterScriptHelpers/createColumnDefinition.js b/forward_engineering/alterScript/alterScriptHelpers/createColumnDefinition.js index 4769ded..71ae5ba 100644 --- a/forward_engineering/alterScript/alterScriptHelpers/createColumnDefinition.js +++ b/forward_engineering/alterScript/alterScriptHelpers/createColumnDefinition.js @@ -12,6 +12,7 @@ */ const lodash = require('lodash'); +const { getEntityName } = require('../../utils/general'); /** * Resolve whether a column is nullable from the required list of its parent. @@ -109,6 +110,7 @@ const createColumnDefinitionBySchema = ({ name, jsonSchema, parentJsonSchema, dd /** @type {ColumnDefinitionInput} */ const columnDefinition = { name, + entityName: getEntityName(parentJsonSchema), type: getType(jsonSchema), nullable: isNullable(parentJsonSchema, name), default: getDefault(jsonSchema), diff --git a/forward_engineering/alterScript/alterScriptHelpers/entityHelpers/indexesHelper.js b/forward_engineering/alterScript/alterScriptHelpers/entityHelpers/indexesHelper.js index d63da5d..2337101 100644 --- a/forward_engineering/alterScript/alterScriptHelpers/entityHelpers/indexesHelper.js +++ b/forward_engineering/alterScript/alterScriptHelpers/entityHelpers/indexesHelper.js @@ -101,9 +101,15 @@ const getRenameIndexScriptDto = ({ schemaName, oldIndexName, newIndexName, isAct */ const getCreateIndexScriptDto = ({ index, collection, ddlProvider }) => { const collectionSchema = getSchemaOfAlterCollection(collection); - const script = ddlProvider.createIndex(getEntityName(collectionSchema), addNameToIndexKey({ index, 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], true, false); + return createAlterScriptDto([script], isCollectionActivated && Boolean(index.isActivated), false); }; /** diff --git a/forward_engineering/alterScript/alterScriptHelpers/entityHelpers/keyConstraintsHelper.js b/forward_engineering/alterScript/alterScriptHelpers/entityHelpers/keyConstraintsHelper.js index 4354b72..9955375 100644 --- a/forward_engineering/alterScript/alterScriptHelpers/entityHelpers/keyConstraintsHelper.js +++ b/forward_engineering/alterScript/alterScriptHelpers/entityHelpers/keyConstraintsHelper.js @@ -31,17 +31,19 @@ const { isParentContainerActivated, isObjectInDeltaModelActivated, } = require('../../../utils/general'); +const { getDefaultConstraintName } = require('../../../ddlProvider/ddlHelpers/key/getDefaultConstraintName'); const AMOUNT_OF_COLUMNS_IN_REGULAR_KEY = 1; /** - * Build the constraint name Db2 for z/OS falls back to when the user did not provide one. + * 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 getDefaultConstraintName = (entityName, keyKind) => [entityName, keyKind.constraintPostfix].join('_'); +const getKeyKindDefaultConstraintName = (entityName, keyKind) => + getDefaultConstraintName({ entityName, postfix: keyKind.constraintPostfix }); /** * Keep only the options that end up in the generated DDL. @@ -225,7 +227,7 @@ const getAddCompositeKeyScriptModifications = ({ collection, keyKind }) => { isParentActivated: isCollectionActivated, keyConfig: { keyType: keyKind.keyType, - name: compositeKey.constraintName ?? getDefaultConstraintName(entityName, keyKind), + name: compositeKey.constraintName ?? getKeyKindDefaultConstraintName(entityName, keyKind), columns, }, }); @@ -270,7 +272,7 @@ const getDropCompositeKeyScriptModifications = ({ collection, keyKind }) => { return oldKeys .map(compositeKey => { - const constraintName = compositeKey.constraintName ?? getDefaultConstraintName(entityName, keyKind); + const constraintName = compositeKey.constraintName ?? getKeyKindDefaultConstraintName(entityName, keyKind); const script = keyKind.buildDropStatement({ tableName: fullTableName, constraintName: wrapInQuotes(constraintName), @@ -405,7 +407,7 @@ const getAddRegularKeyScriptModifications = ({ collection, keyKind }) => { const configuredConstraintName = columnJsonSchema[keyKind.keyOptionsProperty]?.constraintName?.trim(); const constraintName = configuredConstraintName === undefined || configuredConstraintName === '' - ? getDefaultConstraintName(entityName, keyKind) + ? getKeyKindDefaultConstraintName(entityName, keyKind) : configuredConstraintName; const statement = keyKind.buildAlterStatement({ tableName: fullTableName, @@ -464,7 +466,7 @@ const getDropRegularKeyScriptModifications = ({ collection, keyKind }) => { const configuredConstraintName = oldColumnJsonSchema?.[keyKind.keyOptionsProperty]?.constraintName?.trim(); const constraintName = configuredConstraintName === undefined || configuredConstraintName === '' - ? getDefaultConstraintName(entityName, keyKind) + ? getKeyKindDefaultConstraintName(entityName, keyKind) : configuredConstraintName; const script = keyKind.buildDropStatement({ tableName: fullTableName, diff --git a/forward_engineering/ddlProvider/ddlHelpers/columnDefinition/getColumnConstraints.js b/forward_engineering/ddlProvider/ddlHelpers/columnDefinition/getColumnConstraints.js index d00ea72..950e8e7 100644 --- a/forward_engineering/ddlProvider/ddlHelpers/columnDefinition/getColumnConstraints.js +++ b/forward_engineering/ddlProvider/ddlHelpers/columnDefinition/getColumnConstraints.js @@ -5,7 +5,10 @@ * } from '../../../types/ddlProvider' */ +const lodash = require('lodash'); const { getOptionsString } = require('../constraint/getOptionsString'); +const { getDefaultConstraintName } = require('../key/getDefaultConstraintName'); +const { CONSTRAINT_POSTFIX } = require('../../../../shared/constants/constants'); /** * Resolve primary/unique key options. @@ -25,16 +28,35 @@ const getOptions = ({ primaryKey, unique, primaryKeyOptions, 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 lodash.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 }) => { - const { constraintString, statement } = getOptionsString( - getOptions({ primaryKey, unique, primaryKeyOptions, uniqueKeyOptions }), - ); +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'; diff --git a/forward_engineering/ddlProvider/ddlHelpers/index/getIndexOptions.js b/forward_engineering/ddlProvider/ddlHelpers/index/getIndexOptions.js index 8748990..62306c3 100644 --- a/forward_engineering/ddlProvider/ddlHelpers/index/getIndexOptions.js +++ b/forward_engineering/ddlProvider/ddlHelpers/index/getIndexOptions.js @@ -7,7 +7,7 @@ const lodash = require('lodash'); const { getBasicValue, getOptionsByConfigs } = require('../options/getOptionsByConfigs'); -const { wrapInQuotes } = require('../../../utils/general'); +const { wrapInQuotes, columnMapToStringWithOrder } = require('../../../utils/general'); /** * Convert a value to upper case. @@ -26,7 +26,8 @@ const toUpperCase = value => lodash.toUpper(value); const getUpperCaseValue = prefix => getBasicValue({ prefix, modifier: toUpperCase }); /** - * Build index key list clause. + * 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. @@ -36,15 +37,14 @@ const getIndexKeys = (keys = []) => { return ''; } - const keysClause = keys - .map(({ name, type }) => wrapInQuotes(name ?? '') + getUpperCaseValue(' ')(type ?? '')) - .join(', '); + const keysClause = keys.map(({ name, type }) => columnMapToStringWithOrder({ name: name ?? '', type })).join(', '); return `(${keysClause})`; }; /** - * Build INCLUDE column list for unique indexes. + * 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. @@ -52,11 +52,11 @@ const getIndexKeys = (keys = []) => { */ const getIncludeIndexKeys = (keys, index) => { const isUnique = index.indxType === 'unique' || index.indxType === 'uniqueWhereNotNull'; - if (!isUnique || index.indxNullKeys === 'exclude') { + if (!isUnique || index.indxNullKeys === 'exclude' || !keys?.length) { return ''; } - const includeIndexKeys = getIndexKeys(keys); + const includeIndexKeys = `(${keys.map(({ name }) => wrapInQuotes(name ?? '')).join(', ')})`; return getBasicValue({ prefix: 'INCLUDE' })(includeIndexKeys); }; diff --git a/forward_engineering/ddlProvider/ddlHelpers/jsonSchema/jsonSchemaHelper.js b/forward_engineering/ddlProvider/ddlHelpers/jsonSchema/jsonSchemaHelper.js index 6d7c6e6..84af221 100644 --- a/forward_engineering/ddlProvider/ddlHelpers/jsonSchema/jsonSchemaHelper.js +++ b/forward_engineering/ddlProvider/ddlHelpers/jsonSchema/jsonSchemaHelper.js @@ -2,8 +2,7 @@ * @import { * FieldNameLookupParams, * IdToNameMap, - * JsonSchema, - * JsonSchemaColumn, + * WalkableSchema, * WalkSchemaParams * } from '../../../types/ddlProvider' */ @@ -11,7 +10,7 @@ /** * Resolve a schema item name. * - * @param {{ item?: JsonSchemaColumn }} params Schema item. + * @param {{ item?: WalkableSchema }} params Schema item. * @returns {string} Item name. */ const getName = ({ item }) => { @@ -52,7 +51,7 @@ const eachProperty = ({ jsonSchema, path, callback }) => { /** * Build GUID-to-name lookup table. * - * @param {{ jsonSchema?: JsonSchema }} params JSON schema. + * @param {{ jsonSchema?: WalkableSchema }} params JSON schema. * @returns {IdToNameMap} Id to name map. */ const getIdToNameHashTable = ({ jsonSchema }) => { @@ -63,7 +62,7 @@ const getIdToNameHashTable = ({ jsonSchema }) => { /** * Collect a property name. * - * @param {{ propertyName: string; property: JsonSchemaColumn }} params Property info. + * @param {{ propertyName: string; property: WalkableSchema }} params Property info. * @returns {void} */ const callback = ({ propertyName, property }) => { 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 index 0e41146..b46bada 100644 --- a/forward_engineering/ddlProvider/ddlHelpers/key/keyHelper.js +++ b/forward_engineering/ddlProvider/ddlHelpers/key/keyHelper.js @@ -13,6 +13,8 @@ const lodash = require('lodash'); const { wrapInQuotes, commentIfDeactivated, checkIsKeyActivated } = require('../../../utils/general'); +const { CONSTRAINT_POSTFIX } = require('../../../../shared/constants/constants'); +const { getDefaultConstraintName } = require('./getDefaultConstraintName'); /** @enum {string} */ const KEY_TYPE = { @@ -72,12 +74,15 @@ const isInlinePrimaryKey = ({ column }) => { }; /** - * Hydrate key constraint options. + * 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 }) => { +const hydrateKeyOptions = ({ columnName, isActivated, options, keyType, entityName }) => { + const postfix = keyType === KEY_TYPE.primaryKey ? CONSTRAINT_POSTFIX.primaryKey : CONSTRAINT_POSTFIX.uniqueKey; + return { keyType, columns: [ @@ -86,7 +91,7 @@ const hydrateKeyOptions = ({ columnName, isActivated, options, keyType }) => { isActivated: isActivated, }, ], - constraintName: options?.constraintName, + constraintName: lodash.trim(options?.constraintName) || getDefaultConstraintName({ entityName, postfix }), }; }; @@ -136,10 +141,10 @@ const getKeys = ({ jsonSchema, keys }) => { /** * Get composite primary key constraints. * - * @param {{ jsonSchema: JsonSchema }} params Schema. + * @param {{ jsonSchema: JsonSchema; entityName?: string }} params Schema and table name. * @returns {KeyConstraint[]} Primary key constraints. */ -const getCompositePrimaryKeys = ({ jsonSchema }) => { +const getCompositePrimaryKeys = ({ jsonSchema, entityName }) => { if (!Array.isArray(jsonSchema.primaryKey)) { return []; } @@ -147,7 +152,7 @@ const getCompositePrimaryKeys = ({ jsonSchema }) => { return jsonSchema.primaryKey .filter(primaryKey => !lodash.isEmpty(primaryKey.compositePrimaryKey)) .map(primaryKey => - Object.assign(hydrateKeyOptions({ options: primaryKey, keyType: KEY_TYPE.primaryKey }), { + Object.assign(hydrateKeyOptions({ options: primaryKey, keyType: KEY_TYPE.primaryKey, entityName }), { columns: getKeys({ keys: primaryKey.compositePrimaryKey, jsonSchema }), }), ); @@ -156,10 +161,10 @@ const getCompositePrimaryKeys = ({ jsonSchema }) => { /** * Get composite unique key constraints. * - * @param {{ jsonSchema: JsonSchema }} params Schema. + * @param {{ jsonSchema: JsonSchema; entityName?: string }} params Schema and table name. * @returns {KeyConstraint[]} Unique key constraints. */ -const getCompositeUniqueKeys = ({ jsonSchema }) => { +const getCompositeUniqueKeys = ({ jsonSchema, entityName }) => { if (!Array.isArray(jsonSchema.uniqueKey)) { return []; } @@ -167,7 +172,7 @@ const getCompositeUniqueKeys = ({ jsonSchema }) => { return jsonSchema.uniqueKey .filter(uniqueKey => !lodash.isEmpty(uniqueKey.compositeUniqueKey)) .map(uniqueKey => - Object.assign(hydrateKeyOptions({ options: uniqueKey, keyType: KEY_TYPE.unique }), { + Object.assign(hydrateKeyOptions({ options: uniqueKey, keyType: KEY_TYPE.unique, entityName }), { columns: getKeys({ keys: uniqueKey.compositeUniqueKey, jsonSchema }), }), ); @@ -176,10 +181,10 @@ const getCompositeUniqueKeys = ({ jsonSchema }) => { /** * Collect table-level key constraints. * - * @param {{ jsonSchema: JsonSchema }} params Schema. + * @param {{ jsonSchema: JsonSchema; entityName?: string }} params Schema and table name. * @returns {KeyConstraint[]} Key constraints. */ -const getTableKeyConstraints = ({ jsonSchema }) => { +const getTableKeyConstraints = ({ jsonSchema, entityName }) => { if (!jsonSchema.properties) { return []; } @@ -193,6 +198,7 @@ const getTableKeyConstraints = ({ jsonSchema }) => { isActivated: column.isActivated, options: column.uniqueKeyOptions, keyType: KEY_TYPE.unique, + entityName, }); }).filter(constraint => constraint !== null); @@ -205,14 +211,15 @@ const getTableKeyConstraints = ({ jsonSchema }) => { isActivated: column.isActivated, options: column.primaryKeyOptions, keyType: KEY_TYPE.primaryKey, + entityName, }); }).filter(constraint => constraint !== null); return [ ...primaryKeyConstraints, - ...getCompositePrimaryKeys({ jsonSchema }), + ...getCompositePrimaryKeys({ jsonSchema, entityName }), ...uniqueConstraints, - ...getCompositeUniqueKeys({ jsonSchema }), + ...getCompositeUniqueKeys({ jsonSchema, entityName }), ]; }; diff --git a/forward_engineering/ddlProvider/ddlHelpers/options/getOptionsByConfigs.js b/forward_engineering/ddlProvider/ddlHelpers/options/getOptionsByConfigs.js index 229c617..15c8856 100644 --- a/forward_engineering/ddlProvider/ddlHelpers/options/getOptionsByConfigs.js +++ b/forward_engineering/ddlProvider/ddlHelpers/options/getOptionsByConfigs.js @@ -7,6 +7,15 @@ const lodash = require('lodash'); +/** + * 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. * @@ -35,7 +44,7 @@ const getBasicValue = ({ prefix = '', postfix = '', modifier }) => { * @returns {string} Formatted value. */ return value => - value + hasOptionValue(value) ? [prefix, String(resolveModifier(value)), postfix] .filter(Boolean) .map(part => lodash.trim(part)) @@ -49,7 +58,7 @@ const getBasicValue = ({ prefix = '', postfix = '', modifier }) => { */ const getOptionsByConfigs = ({ configs, data }) => { const statements = configs - .filter(({ key }) => lodash.get(data, key)) + .filter(({ key }) => hasOptionValue(lodash.get(data, key))) .map(({ key, getValue }) => getValue(lodash.get(data, key), data)) .filter(Boolean) .join('\n\t'); diff --git a/forward_engineering/ddlProvider/ddlHelpers/table/getTableOptions.js b/forward_engineering/ddlProvider/ddlHelpers/table/getTableOptions.js index 985294d..a125d5b 100644 --- a/forward_engineering/ddlProvider/ddlHelpers/table/getTableOptions.js +++ b/forward_engineering/ddlProvider/ddlHelpers/table/getTableOptions.js @@ -246,26 +246,26 @@ const getPartitioningClause = ({ partitioning }) => { } 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)) - .filter(Boolean) + .map(key => columnMapToStringWithOrder(key) + nullsLast) .join(', '); if (!keyColumns) { return ''; } - const nullsLast = partitioning.nullsLast ? ' NULLS LAST' : ''; const partitions = (partitioning.partitions ?? []) .filter(partition => lodash.isNumber(partition.partitionNumber) && partition.endingAt) .map(partition => { const inclusive = partition.inclusive ? ' INCLUSIVE' : ''; return `PARTITION ${partition.partitionNumber} ENDING AT (${partition.endingAt})${inclusive}`; - }) - .join('\n\t'); + }); + + const partitionsClause = partitions.length > 0 ? ` (\n\t\t${partitions.join(',\n\t\t')}\n\t)` : ''; - const partitionsClause = partitions ? `\n\t${partitions}` : ''; - return `PARTITION BY RANGE (${keyColumns})${nullsLast}${partitionsClause}`; + return `PARTITION BY RANGE (${keyColumns})${partitionsClause}`; } return ''; diff --git a/forward_engineering/ddlProvider/ddlProvider.js b/forward_engineering/ddlProvider/ddlProvider.js index e127940..19cd5ce 100644 --- a/forward_engineering/ddlProvider/ddlProvider.js +++ b/forward_engineering/ddlProvider/ddlProvider.js @@ -186,10 +186,12 @@ const hydrateColumn = ({ columnDefinition, jsonSchema, schemaData, definitionJso const definitionSchema = definitionJsonSchema ?? {}; const isUDTRef = !!jsonSchema.$ref; const type = isUDTRef ? (columnDefinition.type ?? '') : lodash.toUpper(jsonSchema.mode ?? jsonSchema.type); - const itemsType = lodash.toUpper(jsonSchema.items?.mode ?? jsonSchema.items?.type ?? ''); + const itemsSchema = Array.isArray(jsonSchema.items) ? jsonSchema.items[0] : jsonSchema.items; + const itemsType = lodash.toUpper(itemsSchema?.mode ?? itemsSchema?.type ?? ''); return { name: columnDefinition.name, + entityName: columnDefinition.entityName, type, ofType: jsonSchema.ofType, notPersistable: jsonSchema.notPersistable, @@ -444,7 +446,7 @@ const hydrateTable = ({ tableData, entityData, jsonSchema }) => { return { ...tableData, ...auxiliaryTableData, - keyConstraints: keyHelper.getTableKeyConstraints({ jsonSchema }), + keyConstraints: keyHelper.getTableKeyConstraints({ jsonSchema, entityName: tableData.name }), description: detailsTab.description, tableProperties: detailsTab.tableProperties, inClauseType: detailsTab.inClauseType, diff --git a/forward_engineering/types/ddlProvider.d.ts b/forward_engineering/types/ddlProvider.d.ts index 35d2763..d6324d2 100644 --- a/forward_engineering/types/ddlProvider.d.ts +++ b/forward_engineering/types/ddlProvider.d.ts @@ -41,6 +41,7 @@ export type IdentityOptions = { export type ColumnDefinitionInput = { name: string; + entityName?: string; type?: string; nullable?: boolean; default?: DefaultValue; @@ -77,7 +78,7 @@ export type JsonSchemaColumn = { generatedColumn?: boolean; columnGenerationExpression?: string; generated?: string; - items?: { mode?: string; type?: string }; + items?: JsonSchemaColumn | JsonSchemaColumn[]; ofType?: string; notPersistable?: boolean; size?: string | number; @@ -103,11 +104,11 @@ export type JsonSchema = JsonSchemaColumn & { properties?: Record; primaryKey?: CompositeKeyGroup[]; uniqueKey?: CompositeKeyGroup[]; - items?: JsonSchema | JsonSchema[]; }; export type HydratedColumn = { name: string; + entityName?: string; type: string; ofType?: string; notPersistable?: boolean; @@ -459,7 +460,7 @@ export type ModelObject = { export type JsonSchemaPropertyCallback = (params: { propertyName: string; - property: JsonSchemaColumn; + property: WalkableSchema; path: string[]; }) => void; @@ -474,6 +475,7 @@ export type ColumnConstraintParams = { primaryKey: boolean; primaryKeyOptions?: KeyOptions; uniqueKeyOptions?: KeyOptions; + entityName?: string; }; export type ColumnDefaultParams = { @@ -617,6 +619,7 @@ export type HydrateKeyOptionsParams = { isActivated?: boolean; options?: KeyOptions | CompositeKeyGroup; keyType: string; + entityName?: string; }; export type KeyPropertyLookupParams = { @@ -630,8 +633,21 @@ export type ForeignKeyCustomPropertiesParams = { 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: JsonSchemaColumn; + jsonSchema: WalkableSchema; path: string[]; callback: JsonSchemaPropertyCallback; }; From 5177ebf95cd041a26d8acd7410a9cf10167a7bf1 Mon Sep 17 00:00:00 2001 From: Vitalii Bedletskyi Date: Tue, 11 Aug 2026 12:21:50 +0300 Subject: [PATCH 08/15] fix git hooks and npm audit --- package-lock.json | 6 +- package.json | 2 +- reverse_engineering/api.js | 193 +++++-------------------------------- 3 files changed, 29 insertions(+), 172 deletions(-) diff --git a/package-lock.json b/package-lock.json index 40d847c..b8a4dee 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1976,9 +1976,9 @@ } }, "node_modules/brace-expansion": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.8.tgz", - "integrity": "sha512-JZyDyq3D4AUifKTPOB7DELf6XsB3WdPuNxCtob1vFXPsSXhdAiHBWJ/tJ8HAc9aH84BK+5JFZLNkJKx3G9kzQg==", + "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": { diff --git a/package.json b/package.json index 9add005..36034c2 100644 --- a/package.json +++ b/package.json @@ -63,7 +63,7 @@ "description": "Hackolade plugin for IBM Db2 for z/OS", "disabled": false, "simple-git-hooks": { - "pre-commit": "lint-staged --config lint-staged.config.js", + "pre-commit": "npx lint-staged --config lint-staged.config.js", "pre-push": "npm run check" }, "scripts": { diff --git a/reverse_engineering/api.js b/reverse_engineering/api.js index 6732479..f7b2189 100644 --- a/reverse_engineering/api.js +++ b/reverse_engineering/api.js @@ -1,11 +1,3 @@ -// /** -// * @typedef {import('../shared/types').App} App -// * @typedef {import('../shared/types').AppLogger} AppLogger -// * @typedef {import('../shared/types').ConnectionInfo} ConnectionInfo -// * @typedef {import('../shared/types').Logger} Logger -// * @typedef {import('../shared/types').Callback} Callback -// */ - // const { identity } = require('lodash'); // const { mapSeries } = require('async'); // const { connectionHelper } = require('../shared/helpers/connectionHelper'); @@ -15,27 +7,24 @@ // const { nameHelper } = require('../shared/helpers/nameHelper'); // const { testConnection } = require('../shared/api/testConnection'); +/** @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. + * Disconnect stub. Nothing is ever connected, so this succeeds without doing any work. * * @param {unknown} _connectionInfo Connection info. * @param {unknown} _appLogger App logger. - * @param {unknown} _callback Callback. - * @returns {Promise} + * @param {Callback} callback Callback. + * @returns {void} Nothing; the result is delivered through the callback. */ -const disconnect = async (_connectionInfo, _appLogger, _callback) => { - // try { - // await connectionHelper.disconnect(); - // callback(); - // } catch (error) { - // const logger = logHelper.createLogger({ - // title: 'Disconnect from database', - // hiddenKeys: connectionInfo.hiddenKeys, - // logger: appLogger, - // }); - // logger.error(error); - // callback(error); - // } +const disconnect = (_connectionInfo, _appLogger, callback) => { + callback(); }; /** @@ -43,24 +32,12 @@ const disconnect = async (_connectionInfo, _appLogger, _callback) => { * * @param {unknown} _connectionInfo Connection info. * @param {unknown} _appLogger App logger. - * @param {unknown} _callback Callback. + * @param {Callback} callback Callback. * @param {unknown} _app App instance. - * @returns {Promise} + * @returns {void} Nothing; the result is delivered through the callback. */ -const getSchemaNames = async (_connectionInfo, _appLogger, _callback, _app) => { - // const logger = logHelper.createLogger({ - // title: 'Retrieve schema names', - // hiddenKeys: connectionInfo.hiddenKeys, - // logger: appLogger, - // }); - // try { - // const connection = await connectionHelper.connect({ connectionInfo, logger }); - // const schemaNames = await instanceHelper.getSchemaNames({ connection }); - // callback(null, schemaNames); - // } catch (error) { - // logger.error(error); - // callback(error); - // } +const getSchemaNames = (_connectionInfo, _appLogger, callback, _app) => { + callback(new Error(NOT_IMPLEMENTED_MESSAGE)); }; /** @@ -68,50 +45,12 @@ const getSchemaNames = async (_connectionInfo, _appLogger, _callback, _app) => { * * @param {unknown} _connectionInfo Connection info. * @param {unknown} _appLogger App logger. - * @param {unknown} _callback Callback. + * @param {Callback} callback Callback. * @param {unknown} _app App instance. - * @returns {Promise} + * @returns {void} Nothing; the result is delivered through the callback. */ -const getDbCollectionsNames = async (_connectionInfo, _appLogger, _callback, _app) => { - // const logger = logHelper.createLogger({ - // title: 'Retrieve table names', - // hiddenKeys: connectionInfo.hiddenKeys, - // logger: appLogger, - // }); - // try { - // const connection = await connectionHelper.connect({ connectionInfo, logger }); - // const dbVersion = await instanceHelper.getDbVersion({ connection }); - // logger.info('Db version: ' + dbVersion); - // logger.info('Get table and schema names'); - // logger.info(connectionInfo); - // const tableNames = await instanceHelper.getDatabasesWithTableNames({ - // connection, - // objectType: OBJECT_TYPE.table, - // includeSystemCollection: connectionInfo.includeSystemCollection, - // tableNameModifier: identity, - // }); - // logger.info('Get views and schema names'); - // const viewNames = await instanceHelper.getDatabasesWithTableNames({ - // connection, - // objectType: OBJECT_TYPE.view, - // includeSystemCollection: connectionInfo.includeSystemCollection, - // tableNameModifier: nameHelper.setViewSign, - // }); - // const allDatabaseNames = [...Object.keys(tableNames), ...Object.keys(viewNames)]; - // const dbCollectionNames = allDatabaseNames.map(dbName => { - // const dbCollections = [...(tableNames[dbName] || []), ...(viewNames[dbName] || [])]; - // return { - // dbName, - // dbCollections, - // isEmpty: !dbCollections.length, - // }; - // }); - // logger.info('Names retrieved successfully'); - // callback(null, dbCollectionNames); - // } catch (error) { - // logger.error(error); - // callback(error); - // } +const getDbCollectionsNames = (_connectionInfo, _appLogger, callback, _app) => { + callback(new Error(NOT_IMPLEMENTED_MESSAGE)); }; /** @@ -119,94 +58,12 @@ const getDbCollectionsNames = async (_connectionInfo, _appLogger, _callback, _ap * * @param {unknown} _connectionInfo Connection info. * @param {unknown} _appLogger App logger. - * @param {unknown} _callback Callback. + * @param {Callback} callback Callback. * @param {unknown} _app App instance. - * @returns {Promise} + * @returns {void} Nothing; the result is delivered through the callback. */ -const getDbCollectionsData = async (_connectionInfo, _appLogger, _callback, _app) => { - // const logger = logHelper.createLogger({ - // title: 'Retrieve table names', - // hiddenKeys: connectionInfo.hiddenKeys, - // logger: appLogger, - // }); - // try { - // const collections = connectionInfo.collectionData.collections; - // const dataBaseNames = connectionInfo.collectionData.dataBaseNames; - // const connection = await connectionHelper.connect({ connectionInfo, logger }); - // const dbVersion = await instanceHelper.getDbVersion({ connection }); - // logger.info('Db version: ' + dbVersion); - // logger.progress('Start reverse engineering ...'); - // const result = await mapSeries(dataBaseNames, async schemaName => { - // const tables = (collections[schemaName] || []).filter(name => !nameHelper.isViewName(name)); - // const views = (collections[schemaName] || []).filter(nameHelper.isViewName).map(nameHelper.getViewName); - // const bucketInfo = await instanceHelper.getSchemaProperties({ connection, schemaName, logger }); - // logger.info(`Parsing schema "${schemaName}"`); - // logger.progress(`Parsing schema "${schemaName}"`, schemaName); - // const result = await mapSeries(tables, async tableName => { - // logger.info(`Get create table statement "${tableName}"`); - // logger.progress(`Get create table statement`, schemaName, tableName); - // const ddl = await instanceHelper.getTableDdl({ - // connection, - // schemaName, - // tableName, - // objectType: OBJECT_TYPE.table, - // logger, - // }); - // return { - // dbName: schemaName, - // collectionName: tableName, - // entityLevel: {}, - // documents: [], - // views: [], - // standardDoc: {}, - // ddl: { - // script: ddl, - // type: 'db2', - // takeAllDdlProperties: true, - // }, - // emptyBucket: false, - // bucketInfo: { - // ...bucketInfo, - // }, - // modelDefinitions: {}, - // }; - // }); - // const viewData = await mapSeries(views, async viewName => { - // logger.info(`Get create view statement "${viewName}"`); - // logger.progress(`Get create view statement`, schemaName, viewName); - // const ddl = await instanceHelper.getTableDdl({ - // connection, - // schemaName, - // tableName: viewName, - // objectType: OBJECT_TYPE.view, - // logger, - // }); - // return { - // name: viewName, - // ddl: { - // script: ddl, - // type: 'db2', - // takeAllDdlProperties: true, - // }, - // }; - // }); - // if (viewData.length) { - // return [ - // ...result, - // { - // dbName: schemaName, - // views: viewData, - // emptyBucket: false, - // }, - // ]; - // } - // return result; - // }); - // callback(null, result.flat(), { dbVersion, database_name: connectionInfo.database }); - // } catch (error) { - // logger.error(error); - // callback(error); - // } +const getDbCollectionsData = (_connectionInfo, _appLogger, callback, _app) => { + callback(new Error(NOT_IMPLEMENTED_MESSAGE)); }; module.exports = { From a1c5b709e62a6c9d35ebafbb2bf3d90eb484348b Mon Sep 17 00:00:00 2001 From: Vitalii Bedletskyi Date: Tue, 11 Aug 2026 12:42:40 +0300 Subject: [PATCH 09/15] fix lodash imports and package --- esbuild.package.js | 15 +- .../alterScriptHelpers/alterEntityHelper.js | 10 +- .../columnHelpers/alterColumnNameHelper.js | 5 +- .../columnHelpers/alterTypeHelper.js | 5 +- .../columnHelpers/commentsHelper.js | 8 +- .../columnHelpers/defaultValueHelper.js | 8 +- .../columnHelpers/nonNullConstraintHelper.js | 15 +- .../createColumnDefinition.js | 12 +- .../entityHelpers/checkConstraintHelper.js | 6 +- .../entityHelpers/indexesHelper.js | 4 +- .../entityHelpers/keyConstraintsHelper.js | 22 +-- .../indexHelpers/addNameToIndexKey.js | 4 +- .../columnDefinition/getColumnConstraints.js | 4 +- .../columnDefinition/getColumnDefault.js | 6 +- .../columnDefinition/getColumnType.js | 23 +-- .../ddlHelpers/comment/commentHelper.js | 4 +- .../ddlHelpers/index/getIndexOptions.js | 14 +- .../ddlProvider/ddlHelpers/key/keyHelper.js | 21 ++- .../ddlHelpers/options/getOptionsByConfigs.js | 9 +- .../ddlHelpers/table/getTableOptions.js | 15 +- .../ddlProvider/ddlProvider.js | 25 ++-- forward_engineering/utils/general.js | 12 +- package-lock.json | 135 ++++++++++++++++++ package.json | 1 + 24 files changed, 259 insertions(+), 124 deletions(-) diff --git a/esbuild.package.js b/esbuild.package.js index 0698d89..e5109bd 100644 --- a/esbuild.package.js +++ b/esbuild.package.js @@ -13,6 +13,9 @@ 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'); @@ -48,13 +51,9 @@ async function packagePlugin() { 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'), + path.resolve(__dirname, 'reverse_engineering', 'api.js'), ].filter(entryPoint => entryPointExists(entryPoint)); - // if (entryPoints.length === 0) { - // throw new Error('No packaging entry points found.'); - // } - await esbuild.build({ entryPoints, bundle: true, @@ -70,6 +69,12 @@ async function packagePlugin() { 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, diff --git a/forward_engineering/alterScript/alterScriptHelpers/alterEntityHelper.js b/forward_engineering/alterScript/alterScriptHelpers/alterEntityHelper.js index 2191146..84c57d9 100644 --- a/forward_engineering/alterScript/alterScriptHelpers/alterEntityHelper.js +++ b/forward_engineering/alterScript/alterScriptHelpers/alterEntityHelper.js @@ -11,7 +11,7 @@ * } from '../../types/ddlProvider' */ -const lodash = require('lodash'); +const toPairs = require('lodash/toPairs'); const { createAlterScriptDto } = require('../dto/alterScriptDto'); const { getModifiedCommentOnColumnScriptDtos } = require('./columnHelpers/commentsHelper'); const { getModifyNonNullColumnsScriptDtos } = require('./columnHelpers/nonNullConstraintHelper'); @@ -81,7 +81,7 @@ const getAddCollectionScriptDto = (ddlProvider, inlineDeltaRelationships) => col const schemaName = getSchemaNameFromCollection({ collection }) ?? ''; const schemaData = { schemaName }; - const columnDefinitions = lodash.toPairs(collectionSchema.properties ?? {}).map(([name, column]) => + const columnDefinitions = toPairs(collectionSchema.properties ?? {}).map(([name, column]) => createColumnDefinitionBySchema({ name, jsonSchema: column, @@ -169,8 +169,7 @@ const getAddColumnScriptDtos = ddlProvider => collection => { const fullTableName = getFullCollectionName({ collectionSchema }); const schemaData = { schemaName: getSchemaNameFromCollection({ collection }) ?? '' }; - return lodash - .toPairs(collection.properties ?? {}) + return toPairs(collection.properties ?? {}) .filter(([, jsonSchema]) => !jsonSchema.compMod) .map(([name, jsonSchema]) => { const columnDefinition = createColumnDefinitionBySchema({ @@ -200,8 +199,7 @@ const getDeleteColumnScriptDtos = ddlProvider => collection => { const collectionSchema = getSchemaOfAlterCollection(collection); const fullTableName = getFullCollectionName({ collectionSchema, preferAlterName: false }); - return lodash - .toPairs(collection.properties ?? {}) + return toPairs(collection.properties ?? {}) .filter(([, jsonSchema]) => !jsonSchema.compMod) .map(([name]) => { const script = ddlProvider.dropColumn({ tableName: fullTableName, columnName: wrapInQuotes(name) }); diff --git a/forward_engineering/alterScript/alterScriptHelpers/columnHelpers/alterColumnNameHelper.js b/forward_engineering/alterScript/alterScriptHelpers/columnHelpers/alterColumnNameHelper.js index ee4a5ee..7adbf1f 100644 --- a/forward_engineering/alterScript/alterScriptHelpers/columnHelpers/alterColumnNameHelper.js +++ b/forward_engineering/alterScript/alterScriptHelpers/columnHelpers/alterColumnNameHelper.js @@ -5,7 +5,7 @@ * } from '../../../types/alterScript' */ -const lodash = require('lodash'); +const toPairs = require('lodash/toPairs'); const { getSchemaOfAlterCollection, getFullCollectionName, @@ -48,8 +48,7 @@ const getRenameColumnScriptDtos = collection => { const isContainerActivated = isParentContainerActivated(collection); const isCollectionActivated = isObjectInDeltaModelActivated(collection); - return lodash - .toPairs(collection.properties ?? {}) + return toPairs(collection.properties ?? {}) .map(([, jsonSchema]) => { const oldName = jsonSchema.compMod?.oldField?.name; const newName = jsonSchema.compMod?.newField?.name; diff --git a/forward_engineering/alterScript/alterScriptHelpers/columnHelpers/alterTypeHelper.js b/forward_engineering/alterScript/alterScriptHelpers/columnHelpers/alterTypeHelper.js index 6954ecc..a0c1d8e 100644 --- a/forward_engineering/alterScript/alterScriptHelpers/columnHelpers/alterTypeHelper.js +++ b/forward_engineering/alterScript/alterScriptHelpers/columnHelpers/alterTypeHelper.js @@ -7,7 +7,7 @@ * @import {DdlProvider} from '../../../types/ddlProvider' */ -const lodash = require('lodash'); +const toPairs = require('lodash/toPairs'); const { createAlterScriptDto } = require('../../dto/alterScriptDto'); const { checkFieldPropertiesChanged, @@ -69,8 +69,7 @@ const getUpdateTypesScriptDtos = ddlProvider => collection => { const isCollectionActivated = isObjectInDeltaModelActivated(collection); const schemaName = getSchemaNameFromCollection({ collection }); - return lodash - .toPairs(collection.properties ?? {}) + return toPairs(collection.properties ?? {}) .filter(([name, jsonSchema]) => { if (!jsonSchema.compMod) { return false; diff --git a/forward_engineering/alterScript/alterScriptHelpers/columnHelpers/commentsHelper.js b/forward_engineering/alterScript/alterScriptHelpers/columnHelpers/commentsHelper.js index bb7c767..51bbc54 100644 --- a/forward_engineering/alterScript/alterScriptHelpers/columnHelpers/commentsHelper.js +++ b/forward_engineering/alterScript/alterScriptHelpers/columnHelpers/commentsHelper.js @@ -5,7 +5,7 @@ * } from '../../../types/alterScript' */ -const lodash = require('lodash'); +const toPairs = require('lodash/toPairs'); const { createAlterScriptDto } = require('../../dto/alterScriptDto'); const { isObjectInDeltaModelActivated, @@ -30,8 +30,7 @@ const getUpdatedCommentOnColumnScriptDtos = collection => { const collectionSchema = getSchemaOfAlterCollection(collection); const tableName = getFullCollectionName({ collectionSchema }); - return lodash - .toPairs(collection.properties ?? {}) + return toPairs(collection.properties ?? {}) .filter(([name, jsonSchema]) => { const newComment = jsonSchema.description; const oldName = jsonSchema.compMod?.oldField?.name ?? name; @@ -63,8 +62,7 @@ const getDeletedCommentOnColumnScriptDtos = collection => { const collectionSchema = getSchemaOfAlterCollection(collection); const tableName = getFullCollectionName({ collectionSchema }); - return lodash - .toPairs(collection.properties ?? {}) + return toPairs(collection.properties ?? {}) .filter(([name, jsonSchema]) => { const oldName = jsonSchema.compMod?.oldField?.name ?? name; const oldComment = collection.role?.properties?.[oldName]?.description; diff --git a/forward_engineering/alterScript/alterScriptHelpers/columnHelpers/defaultValueHelper.js b/forward_engineering/alterScript/alterScriptHelpers/columnHelpers/defaultValueHelper.js index f30a278..701c588 100644 --- a/forward_engineering/alterScript/alterScriptHelpers/columnHelpers/defaultValueHelper.js +++ b/forward_engineering/alterScript/alterScriptHelpers/columnHelpers/defaultValueHelper.js @@ -5,7 +5,7 @@ * } from '../../../types/alterScript' */ -const lodash = require('lodash'); +const toPairs = require('lodash/toPairs'); const { createAlterScriptDto } = require('../../dto/alterScriptDto'); const { getFullCollectionName, @@ -54,8 +54,7 @@ const getUpdatedDefaultColumnValueScriptDtos = ({ collection }) => { const isCollectionActivated = isObjectInDeltaModelActivated(collection); const collectionSchema = getSchemaOfAlterCollection(collection); - return lodash - .toPairs(collection.properties ?? {}) + return toPairs(collection.properties ?? {}) .filter(([name, jsonSchema]) => { const oldName = jsonSchema.compMod?.oldField?.name ?? name; const oldDefault = collection.role?.properties?.[oldName]?.default; @@ -85,8 +84,7 @@ const getDeletedDefaultColumnValueScriptDtos = ({ collection }) => { const isCollectionActivated = isObjectInDeltaModelActivated(collection); const collectionSchema = getSchemaOfAlterCollection(collection); - return lodash - .toPairs(collection.properties ?? {}) + return toPairs(collection.properties ?? {}) .filter(([name, jsonSchema]) => { const oldName = jsonSchema.compMod?.oldField?.name ?? name; const oldDefault = collection.role?.properties?.[oldName]?.default; diff --git a/forward_engineering/alterScript/alterScriptHelpers/columnHelpers/nonNullConstraintHelper.js b/forward_engineering/alterScript/alterScriptHelpers/columnHelpers/nonNullConstraintHelper.js index 28fbfc0..267d1b9 100644 --- a/forward_engineering/alterScript/alterScriptHelpers/columnHelpers/nonNullConstraintHelper.js +++ b/forward_engineering/alterScript/alterScriptHelpers/columnHelpers/nonNullConstraintHelper.js @@ -5,7 +5,8 @@ * } from '../../../types/alterScript' */ -const lodash = require('lodash'); +const difference = require('lodash/difference'); +const toPairs = require('lodash/toPairs'); const { createAlterScriptDto } = require('../../dto/alterScriptDto'); const { getFullCollectionName, @@ -61,16 +62,10 @@ const getModifyNonNullColumnsScriptDtos = collection => { const currentRequiredColumnNames = collection.required ?? []; const previousRequiredColumnNames = collection.role?.required ?? []; - const columnNamesToAddNotNullConstraint = lodash.difference( - currentRequiredColumnNames, - previousRequiredColumnNames, - ); - const columnNamesToRemoveNotNullConstraint = lodash.difference( - previousRequiredColumnNames, - currentRequiredColumnNames, - ); + const columnNamesToAddNotNullConstraint = difference(currentRequiredColumnNames, previousRequiredColumnNames); + const columnNamesToRemoveNotNullConstraint = difference(previousRequiredColumnNames, currentRequiredColumnNames); - const columns = lodash.toPairs(collection.properties ?? {}); + const columns = toPairs(collection.properties ?? {}); const addNotNullConstraintScriptDtos = columns .filter(([name, jsonSchema]) => { diff --git a/forward_engineering/alterScript/alterScriptHelpers/createColumnDefinition.js b/forward_engineering/alterScript/alterScriptHelpers/createColumnDefinition.js index 71ae5ba..59600b4 100644 --- a/forward_engineering/alterScript/alterScriptHelpers/createColumnDefinition.js +++ b/forward_engineering/alterScript/alterScriptHelpers/createColumnDefinition.js @@ -11,7 +11,7 @@ * } from '../../types/ddlProvider' */ -const lodash = require('lodash'); +const isNumber = require('lodash/isNumber'); const { getEntityName } = require('../../utils/general'); /** @@ -50,11 +50,11 @@ const getDefault = jsonSchema => { * @returns {number | undefined} Length. */ const getLength = jsonSchema => { - if (lodash.isNumber(jsonSchema.length)) { + if (isNumber(jsonSchema.length)) { return jsonSchema.length; } - if (lodash.isNumber(jsonSchema.maxLength)) { + if (isNumber(jsonSchema.maxLength)) { return jsonSchema.maxLength; } @@ -68,11 +68,11 @@ const getLength = jsonSchema => { * @returns {number | undefined} Precision. */ const getPrecision = jsonSchema => { - if (lodash.isNumber(jsonSchema.precision)) { + if (isNumber(jsonSchema.precision)) { return jsonSchema.precision; } - if (lodash.isNumber(jsonSchema.fractSecPrecision)) { + if (isNumber(jsonSchema.fractSecPrecision)) { return jsonSchema.fractSecPrecision; } @@ -115,7 +115,7 @@ const createColumnDefinitionBySchema = ({ name, jsonSchema, parentJsonSchema, dd nullable: isNullable(parentJsonSchema, name), default: getDefault(jsonSchema), length: getLength(jsonSchema), - scale: lodash.isNumber(jsonSchema.scale) ? jsonSchema.scale : undefined, + scale: isNumber(jsonSchema.scale) ? jsonSchema.scale : undefined, precision: getPrecision(jsonSchema), isActivated: jsonSchema.isActivated, }; diff --git a/forward_engineering/alterScript/alterScriptHelpers/entityHelpers/checkConstraintHelper.js b/forward_engineering/alterScript/alterScriptHelpers/entityHelpers/checkConstraintHelper.js index 0778acf..ed9acbe 100644 --- a/forward_engineering/alterScript/alterScriptHelpers/entityHelpers/checkConstraintHelper.js +++ b/forward_engineering/alterScript/alterScriptHelpers/entityHelpers/checkConstraintHelper.js @@ -6,7 +6,7 @@ * } from '../../../types/alterScript' */ -const lodash = require('lodash'); +const uniq = require('lodash/uniq'); const { createAlterScriptDto } = require('../../dto/alterScriptDto'); const { getFullCollectionName, @@ -64,9 +64,7 @@ const mapCheckConstraintNamesToChangeHistory = collection => { const newConstraints = checkConstraintHistory.new ?? []; const oldConstraints = checkConstraintHistory.old ?? []; - const constraintNames = lodash.uniq( - [...newConstraints, ...oldConstraints].map(constraint => constraint.chkConstrName), - ); + const constraintNames = uniq([...newConstraints, ...oldConstraints].map(constraint => constraint.chkConstrName)); return constraintNames.map(chkConstrName => ({ old: oldConstraints.find(constraint => constraint.chkConstrName === chkConstrName), diff --git a/forward_engineering/alterScript/alterScriptHelpers/entityHelpers/indexesHelper.js b/forward_engineering/alterScript/alterScriptHelpers/entityHelpers/indexesHelper.js index 2337101..fbd130b 100644 --- a/forward_engineering/alterScript/alterScriptHelpers/entityHelpers/indexesHelper.js +++ b/forward_engineering/alterScript/alterScriptHelpers/entityHelpers/indexesHelper.js @@ -7,7 +7,7 @@ * @import {DdlProvider} from '../../../types/ddlProvider' */ -const lodash = require('lodash'); +const isEqual = require('lodash/isEqual'); const { createAlterScriptDto } = require('../../dto/alterScriptDto'); const { getSchemaNameFromCollection, @@ -62,7 +62,7 @@ const DROP_AND_RECREATE_INDEX_PROPERTIES = [ * @returns {boolean} Whether the index has to be recreated. */ const shouldDropAndRecreateIndex = ({ oldIndex, newIndex }) => - DROP_AND_RECREATE_INDEX_PROPERTIES.some(property => !lodash.isEqual(oldIndex[property], newIndex[property])); + DROP_AND_RECREATE_INDEX_PROPERTIES.some(property => !isEqual(oldIndex[property], newIndex[property])); /** * Check whether two index versions describe the same database index. diff --git a/forward_engineering/alterScript/alterScriptHelpers/entityHelpers/keyConstraintsHelper.js b/forward_engineering/alterScript/alterScriptHelpers/entityHelpers/keyConstraintsHelper.js index 9955375..11d912d 100644 --- a/forward_engineering/alterScript/alterScriptHelpers/entityHelpers/keyConstraintsHelper.js +++ b/forward_engineering/alterScript/alterScriptHelpers/entityHelpers/keyConstraintsHelper.js @@ -20,7 +20,10 @@ * } from '../../../types/ddlProvider' */ -const lodash = require('lodash'); +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 { @@ -99,7 +102,7 @@ const areKeyOptionsEqual = ({ compositeKeys, columnOptions, keyKind }) => return false; } - return lodash.isEqual(extractComparableOptions(compositeKey), columnOptions); + return isEqual(extractComparableOptions(compositeKey), columnOptions); }); /** @@ -165,7 +168,7 @@ const didCompositeKeysChange = ({ oldKeys, newKeys }) => { return true; } - return lodash.differenceWith(oldKeys, newKeys, (oldKey, newKey) => lodash.isEqual(oldKey, newKey)).length > 0; + return differenceWith(oldKeys, newKeys, (oldKey, newKey) => isEqual(oldKey, newKey)).length > 0; }; /** @@ -176,8 +179,7 @@ const didCompositeKeysChange = ({ oldKeys, newKeys }) => { * @returns {KeyConstraintColumn[]} Constraint columns. */ const getCompositeKeyColumns = ({ compositeKey, columns, keyKind }) => - lodash - .toPairs(columns) + toPairs(columns) .filter(([, jsonSchema]) => compositeKey[keyKind.compositeKeyProperty]?.some(keyRef => keyRef.keyId === jsonSchema.GUID), ) @@ -367,7 +369,7 @@ const wasRegularKeyModified = ({ columnJsonSchema, collection, keyKind }) => { return false; } - return !lodash.isEqual( + return !isEqual( getRegularKeyOptions(oldColumnJsonSchema ?? {}, keyKind), getRegularKeyOptions(columnJsonSchema, keyKind), ); @@ -385,8 +387,7 @@ const getAddRegularKeyScriptModifications = ({ collection, keyKind }) => { const entityName = getEntityName(collectionSchema); const isCollectionActivated = isParentContainerActivated(collection) && isObjectInDeltaModelActivated(collection); - return lodash - .toPairs(collection.properties ?? {}) + return toPairs(collection.properties ?? {}) .filter(([, columnJsonSchema]) => { const oldName = columnJsonSchema.compMod?.oldField?.name ?? ''; const oldColumnJsonSchema = collection.role?.properties?.[oldName]; @@ -442,8 +443,7 @@ const getDropRegularKeyScriptModifications = ({ collection, keyKind }) => { const entityName = getEntityName(collectionSchema); const isCollectionActivated = isParentContainerActivated(collection) && isObjectInDeltaModelActivated(collection); - return lodash - .toPairs(collection.properties ?? {}) + return toPairs(collection.properties ?? {}) .filter(([, columnJsonSchema]) => { const oldName = columnJsonSchema.compMod?.oldField?.name ?? ''; const oldColumnJsonSchema = collection.role?.properties?.[oldName]; @@ -490,7 +490,7 @@ const getDropRegularKeyScriptModifications = ({ collection, keyKind }) => { * @returns {KeyScriptModification[]} Ordered key statements. */ const sortKeyScriptModifications = keyScriptModifications => - lodash.orderBy( + orderBy( keyScriptModifications, [modification => modification.fullTableName, modification => !modification.isDropScript], ['asc', 'asc'], diff --git a/forward_engineering/alterScript/alterScriptHelpers/indexHelpers/addNameToIndexKey.js b/forward_engineering/alterScript/alterScriptHelpers/indexHelpers/addNameToIndexKey.js index 7e9d4e6..cb19dd1 100644 --- a/forward_engineering/alterScript/alterScriptHelpers/indexHelpers/addNameToIndexKey.js +++ b/forward_engineering/alterScript/alterScriptHelpers/indexHelpers/addNameToIndexKey.js @@ -6,7 +6,7 @@ * } from '../../../types/alterScript' */ -const lodash = require('lodash'); +const toPairs = require('lodash/toPairs'); const { getSchemaNameFromCollection } = require('../../../utils/general'); /** @@ -18,7 +18,7 @@ const { getSchemaNameFromCollection } = require('../../../utils/general'); */ const getColumnNameById = ({ columnId, collection }) => { const columns = collection.role?.properties ?? collection.properties ?? {}; - const namedColumn = lodash.toPairs(columns).find(([, jsonSchema]) => jsonSchema.GUID === columnId); + const namedColumn = toPairs(columns).find(([, jsonSchema]) => jsonSchema.GUID === columnId); if (namedColumn) { return namedColumn[0]; diff --git a/forward_engineering/ddlProvider/ddlHelpers/columnDefinition/getColumnConstraints.js b/forward_engineering/ddlProvider/ddlHelpers/columnDefinition/getColumnConstraints.js index 950e8e7..ffafc2e 100644 --- a/forward_engineering/ddlProvider/ddlHelpers/columnDefinition/getColumnConstraints.js +++ b/forward_engineering/ddlProvider/ddlHelpers/columnDefinition/getColumnConstraints.js @@ -5,7 +5,7 @@ * } from '../../../types/ddlProvider' */ -const lodash = require('lodash'); +const trim = require('lodash/trim'); const { getOptionsString } = require('../constraint/getOptionsString'); const { getDefaultConstraintName } = require('../key/getDefaultConstraintName'); const { CONSTRAINT_POSTFIX } = require('../../../../shared/constants/constants'); @@ -43,7 +43,7 @@ const getConstraintName = ({ unique, primaryKey, primaryKeyOptions, uniqueKeyOpt const options = getOptions({ primaryKey, unique, primaryKeyOptions, uniqueKeyOptions }); const postfix = primaryKey ? CONSTRAINT_POSTFIX.primaryKey : CONSTRAINT_POSTFIX.uniqueKey; - return lodash.trim(options.constraintName) || getDefaultConstraintName({ entityName, postfix }); + return trim(options.constraintName) || getDefaultConstraintName({ entityName, postfix }); }; /** diff --git a/forward_engineering/ddlProvider/ddlHelpers/columnDefinition/getColumnDefault.js b/forward_engineering/ddlProvider/ddlHelpers/columnDefinition/getColumnDefault.js index 8585019..e19d833 100644 --- a/forward_engineering/ddlProvider/ddlHelpers/columnDefinition/getColumnDefault.js +++ b/forward_engineering/ddlProvider/ddlHelpers/columnDefinition/getColumnDefault.js @@ -5,7 +5,7 @@ * } from '../../../types/ddlProvider' */ -const lodash = require('lodash'); +const toUpper = require('lodash/toUpper'); const { DATA_TYPES_WITH_IDENTITY, DATA_TYPE } = require('../../../../shared/constants/types'); /** @@ -15,7 +15,7 @@ const { DATA_TYPES_WITH_IDENTITY, DATA_TYPE } = require('../../../../shared/cons * @returns {boolean} Whether identity is allowed. */ const canHaveIdentity = ({ type }) => { - return DATA_TYPES_WITH_IDENTITY.includes(lodash.toUpper(type)); + return DATA_TYPES_WITH_IDENTITY.includes(toUpper(type)); }; /** @@ -34,7 +34,7 @@ const isGeneratedAsIdentity = ({ identity, type }) => { * @param {{ type: string }} params Column type. * @returns {boolean} Whether type is ROWID. */ -const isRowid = ({ type }) => lodash.toUpper(type) === DATA_TYPE.rowid; +const isRowid = ({ type }) => toUpper(type) === DATA_TYPE.rowid; /** * Build identity options clause. diff --git a/forward_engineering/ddlProvider/ddlHelpers/columnDefinition/getColumnType.js b/forward_engineering/ddlProvider/ddlHelpers/columnDefinition/getColumnType.js index 1b06bdf..3689ff8 100644 --- a/forward_engineering/ddlProvider/ddlHelpers/columnDefinition/getColumnType.js +++ b/forward_engineering/ddlProvider/ddlHelpers/columnDefinition/getColumnType.js @@ -6,7 +6,8 @@ * } from '../../../types/ddlProvider' */ -const lodash = require('lodash'); +const isNumber = require('lodash/isNumber'); +const toUpper = require('lodash/toUpper'); const { DATA_TYPES_WITH_LENGTH_MULTIPLIER, DATA_TYPES_WITH_LENGTH, @@ -24,7 +25,7 @@ const { * @returns {string} Type clause. */ const addLengthWithMultiplier = ({ type, length, lengthSemantics }) => { - return ` ${type}(${length}${lodash.toUpper(lengthSemantics)})`; + return ` ${type}(${length}${toUpper(lengthSemantics)})`; }; /** @@ -44,11 +45,11 @@ const addLength = ({ type, length }) => { * @returns {string} Type clause. */ const addScalePrecision = ({ type, precision, scale }) => { - if (lodash.isNumber(scale)) { + if (isNumber(scale)) { return ` ${type}(${precision ?? '*'},${scale})`; } - if (lodash.isNumber(precision)) { + if (isNumber(precision)) { return ` ${type}(${precision})`; } @@ -62,7 +63,7 @@ const addScalePrecision = ({ type, precision, scale }) => { * @returns {string} Type clause. */ const addPrecision = ({ type, precision }) => { - if (lodash.isNumber(precision)) { + if (isNumber(precision)) { return ` ${type}(${precision})`; } return ` ${type}`; @@ -75,7 +76,7 @@ const addPrecision = ({ type, precision }) => { * @returns {string} Type clause. */ const getTimestampType = ({ fractSecPrecision, withTimeZone }) => { - const fractSecPrecisionString = lodash.isNumber(fractSecPrecision) ? `(${fractSecPrecision})` : ''; + const fractSecPrecisionString = isNumber(fractSecPrecision) ? `(${fractSecPrecision})` : ''; const timeZoneString = withTimeZone ? ' WITH TIME ZONE' : ''; return ` TIMESTAMP${fractSecPrecisionString}${timeZoneString}`; @@ -92,7 +93,7 @@ const getCharacterSubtypeClause = ({ characterSubtype }) => { return ''; } - return ` FOR ${lodash.toUpper(characterSubtype)} DATA`; + return ` FOR ${toUpper(characterSubtype)} DATA`; }; /** @@ -102,7 +103,7 @@ const getCharacterSubtypeClause = ({ characterSubtype }) => { * @returns {string} CCSID clause. */ const getCcsidClause = ({ ccsid }) => { - if (!lodash.isNumber(ccsid)) { + if (!isNumber(ccsid)) { return ''; } @@ -116,7 +117,7 @@ const getCcsidClause = ({ ccsid }) => { * @returns {string} Inline length clause. */ const getInlineLengthClause = ({ inlineLength }) => { - if (!lodash.isNumber(inlineLength)) { + if (!isNumber(inlineLength)) { return ''; } @@ -215,7 +216,7 @@ const getColumnType = ({ ccsid, inlineLength, }) => { - const hasLength = lodash.isNumber(length); + const hasLength = isNumber(length); let typeStatement = ''; if (isRowid({ type })) { @@ -226,7 +227,7 @@ const getColumnType = ({ typeStatement = addLength({ type, length }); } else if (canHavePrecision({ type }) && canHaveScale({ type })) { typeStatement = addScalePrecision({ type, precision, scale }); - } else if (canHavePrecision({ type }) && lodash.isNumber(precision)) { + } else if (canHavePrecision({ type }) && isNumber(precision)) { typeStatement = addPrecision({ type, precision }); } else if (isTimestamp({ type })) { typeStatement = getTimestampType({ fractSecPrecision, withTimeZone }); diff --git a/forward_engineering/ddlProvider/ddlHelpers/comment/commentHelper.js b/forward_engineering/ddlProvider/ddlHelpers/comment/commentHelper.js index c8e7428..328d691 100644 --- a/forward_engineering/ddlProvider/ddlHelpers/comment/commentHelper.js +++ b/forward_engineering/ddlProvider/ddlHelpers/comment/commentHelper.js @@ -6,7 +6,7 @@ * } from '../../../types/ddlProvider' */ -const lodash = require('lodash'); +const trim = require('lodash/trim'); const templates = require('../../templates'); const { assignTemplates } = require('../../../utils/assignTemplates'); const { wrapInQuotes, commentIfDeactivated, wrapInSingleQuotes } = require('../../../utils/general'); @@ -48,7 +48,7 @@ const getCommentStatement = ({ objectName, objectType, description, mode = COMME template: templates.comment, templateData: { objectType, - objectName: lodash.trim(objectName), + objectName: trim(objectName), comment: wrapInSingleQuotes({ name: escapeSpecialCharacters(description ?? '') }), }, }); diff --git a/forward_engineering/ddlProvider/ddlHelpers/index/getIndexOptions.js b/forward_engineering/ddlProvider/ddlHelpers/index/getIndexOptions.js index 62306c3..04ffc5e 100644 --- a/forward_engineering/ddlProvider/ddlHelpers/index/getIndexOptions.js +++ b/forward_engineering/ddlProvider/ddlHelpers/index/getIndexOptions.js @@ -5,7 +5,9 @@ * } from '../../../types/ddlProvider' */ -const lodash = require('lodash'); +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'); @@ -15,7 +17,7 @@ const { wrapInQuotes, columnMapToStringWithOrder } = require('../../../utils/gen * @param {string} value Value to convert. * @returns {string} Upper-case value. */ -const toUpperCase = value => lodash.toUpper(value); +const toUpperCase = value => toUpper(value); /** * Format a value in upper case, with an optional prefix. @@ -71,10 +73,10 @@ const getIncludeIndexKeys = (keys, index) => { const getUsingClause = (_value, index) => { if (index.indxUsingType === 'STOGROUP' && index.indxStogroup) { const parts = [`USING STOGROUP ${index.indxStogroup}`]; - if (lodash.isNumber(index.indxPriQty)) { + if (isNumber(index.indxPriQty)) { parts.push(`PRIQTY ${index.indxPriQty}`); } - if (lodash.isNumber(index.indxSecQty)) { + if (isNumber(index.indxSecQty)) { parts.push(`SECQTY ${index.indxSecQty}`); } if (index.indxErase) { @@ -116,7 +118,7 @@ const getNullKeysClause = value => { * @returns {string} PIECESIZE clause. */ const getPiecesizeClause = (value, index) => { - if (!lodash.isNumber(value)) { + if (!isNumber(value)) { return ''; } @@ -139,7 +141,7 @@ const getPartitionedClause = value => (value ? 'PARTITIONED' : ''); * @param {string | undefined} value Raw DDL fragment. * @returns {string} Trimmed properties. */ -const getIndexProperties = value => lodash.trim(value); +const getIndexProperties = value => trim(value); /** * Build index options clause for CREATE INDEX. diff --git a/forward_engineering/ddlProvider/ddlHelpers/key/keyHelper.js b/forward_engineering/ddlProvider/ddlHelpers/key/keyHelper.js index b46bada..31301a5 100644 --- a/forward_engineering/ddlProvider/ddlHelpers/key/keyHelper.js +++ b/forward_engineering/ddlProvider/ddlHelpers/key/keyHelper.js @@ -11,7 +11,8 @@ * } from '../../../types/ddlProvider' */ -const lodash = require('lodash'); +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'); @@ -50,7 +51,7 @@ const isUniqueKey = ({ column }) => { * @returns {boolean} Whether inline unique. */ const isInlineUnique = ({ column }) => { - return isUniqueKey({ column }) && !lodash.trim(column.uniqueKeyOptions?.constraintName); + return isUniqueKey({ column }) && !trim(column.uniqueKeyOptions?.constraintName); }; /** @@ -70,7 +71,7 @@ const isPrimaryKey = ({ column }) => { * @returns {boolean} Whether inline primary key. */ const isInlinePrimaryKey = ({ column }) => { - return isPrimaryKey({ column }) && !lodash.trim(column.primaryKeyOptions?.constraintName); + return isPrimaryKey({ column }) && !trim(column.primaryKeyOptions?.constraintName); }; /** @@ -91,7 +92,7 @@ const hydrateKeyOptions = ({ columnName, isActivated, options, keyType, entityNa isActivated: isActivated, }, ], - constraintName: lodash.trim(options?.constraintName) || getDefaultConstraintName({ entityName, postfix }), + constraintName: trim(options?.constraintName) || getDefaultConstraintName({ entityName, postfix }), }; }; @@ -150,7 +151,7 @@ const getCompositePrimaryKeys = ({ jsonSchema, entityName }) => { } return jsonSchema.primaryKey - .filter(primaryKey => !lodash.isEmpty(primaryKey.compositePrimaryKey)) + .filter(primaryKey => !isEmpty(primaryKey.compositePrimaryKey)) .map(primaryKey => Object.assign(hydrateKeyOptions({ options: primaryKey, keyType: KEY_TYPE.primaryKey, entityName }), { columns: getKeys({ keys: primaryKey.compositePrimaryKey, jsonSchema }), @@ -170,7 +171,7 @@ const getCompositeUniqueKeys = ({ jsonSchema, entityName }) => { } return jsonSchema.uniqueKey - .filter(uniqueKey => !lodash.isEmpty(uniqueKey.compositeUniqueKey)) + .filter(uniqueKey => !isEmpty(uniqueKey.compositeUniqueKey)) .map(uniqueKey => Object.assign(hydrateKeyOptions({ options: uniqueKey, keyType: KEY_TYPE.unique, entityName }), { columns: getKeys({ keys: uniqueKey.compositeUniqueKey, jsonSchema }), @@ -231,12 +232,10 @@ const getTableKeyConstraints = ({ jsonSchema, entityName }) => { */ const foreignKeysToString = ({ keys }) => { if (Array.isArray(keys)) { - const activatedKeys = keys - .filter(key => checkIsKeyActivated({ key })) - .map(key => wrapInQuotes(lodash.trim(key.name))); + const activatedKeys = keys.filter(key => checkIsKeyActivated({ key })).map(key => wrapInQuotes(trim(key.name))); const deactivatedKeys = keys .filter(key => !checkIsKeyActivated({ key })) - .map(key => wrapInQuotes(lodash.trim(key.name))); + .map(key => wrapInQuotes(trim(key.name))); const deactivatedKeysAsString = deactivatedKeys.length > 0 ? commentIfDeactivated(deactivatedKeys.join(', '), { isActivated: false, isPartOfLine: true }) @@ -254,7 +253,7 @@ const foreignKeysToString = ({ keys }) => { * @returns {string} Keys string. */ const foreignActiveKeysToString = ({ keys }) => { - return keys.map(key => lodash.trim(key.name)).join(', '); + return keys.map(key => trim(key.name)).join(', '); }; /** diff --git a/forward_engineering/ddlProvider/ddlHelpers/options/getOptionsByConfigs.js b/forward_engineering/ddlProvider/ddlHelpers/options/getOptionsByConfigs.js index 15c8856..d708e89 100644 --- a/forward_engineering/ddlProvider/ddlHelpers/options/getOptionsByConfigs.js +++ b/forward_engineering/ddlProvider/ddlHelpers/options/getOptionsByConfigs.js @@ -5,7 +5,8 @@ * } from '../../../types/ddlProvider' */ -const lodash = require('lodash'); +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 @@ -47,7 +48,7 @@ const getBasicValue = ({ prefix = '', postfix = '', modifier }) => { hasOptionValue(value) ? [prefix, String(resolveModifier(value)), postfix] .filter(Boolean) - .map(part => lodash.trim(part)) + .map(part => trim(part)) .join(' ') : ''; }; @@ -58,8 +59,8 @@ const getBasicValue = ({ prefix = '', postfix = '', modifier }) => { */ const getOptionsByConfigs = ({ configs, data }) => { const statements = configs - .filter(({ key }) => hasOptionValue(lodash.get(data, key))) - .map(({ key, getValue }) => getValue(lodash.get(data, key), data)) + .filter(({ key }) => hasOptionValue(get(data, key))) + .map(({ key, getValue }) => getValue(get(data, key), data)) .filter(Boolean) .join('\n\t'); diff --git a/forward_engineering/ddlProvider/ddlHelpers/table/getTableOptions.js b/forward_engineering/ddlProvider/ddlHelpers/table/getTableOptions.js index a125d5b..5048319 100644 --- a/forward_engineering/ddlProvider/ddlHelpers/table/getTableOptions.js +++ b/forward_engineering/ddlProvider/ddlHelpers/table/getTableOptions.js @@ -9,7 +9,8 @@ * } from '../../../types/ddlProvider' */ -const lodash = require('lodash'); +const isNumber = require('lodash/isNumber'); +const toUpper = require('lodash/toUpper'); const { wrapInQuotes, columnMapToStringWithOrder } = require('../../../utils/general'); const { getOptionsByConfigs, getBasicValue } = require('../options/getOptionsByConfigs'); @@ -86,7 +87,7 @@ const getStructuredTableOptions = ({ tableOptions, inClauseType }) => { * @param {number} value OBID value. * @returns {string} Option clause. */ - getValue: value => (lodash.isNumber(value) ? `OBID ${value}` : ''), + getValue: value => (isNumber(value) ? `OBID ${value}` : ''), }, { key: 'dataCapture', @@ -156,7 +157,7 @@ const getStructuredTableOptions = ({ tableOptions, inClauseType }) => { * @param {number} value Size in G. * @returns {string} Option clause. */ - getValue: value => (isExistingTablespace || !lodash.isNumber(value) ? '' : `DSSIZE ${value} G`), + getValue: value => (isExistingTablespace || !isNumber(value) ? '' : `DSSIZE ${value} G`), }, { key: 'bufferPool', @@ -239,7 +240,7 @@ const getPartitioningClause = ({ partitioning }) => { } if (partitioning.partitionBy === 'SIZE') { - if (!lodash.isNumber(partitioning.everySize)) { + if (!isNumber(partitioning.everySize)) { return ''; } return `PARTITION BY SIZE EVERY ${partitioning.everySize} G`; @@ -257,7 +258,7 @@ const getPartitioningClause = ({ partitioning }) => { } const partitions = (partitioning.partitions ?? []) - .filter(partition => lodash.isNumber(partition.partitionNumber) && partition.endingAt) + .filter(partition => isNumber(partition.partitionNumber) && partition.endingAt) .map(partition => { const inclusive = partition.inclusive ? ' INCLUSIVE' : ''; return `PARTITION ${partition.partitionNumber} ENDING AT (${partition.endingAt})${inclusive}`; @@ -288,7 +289,7 @@ const getTemporalPeriodsClause = ({ periodForSystemTime, periodForBusinessTime } if (periodForBusinessTime?.startColumn && periodForBusinessTime?.endColumn) { const endInclusive = periodForBusinessTime.endInclusive - ? ` ${lodash.toUpper(periodForBusinessTime.endInclusive)}` + ? ` ${toUpper(periodForBusinessTime.endInclusive)}` : ''; clauses.push( `PERIOD FOR BUSINESS_TIME (${wrapInQuotes(periodForBusinessTime.startColumn)}, ${wrapInQuotes(periodForBusinessTime.endColumn)}${endInclusive})`, @@ -326,7 +327,7 @@ const getTableOptions = tableData => { * @param {string} value Append value. * @returns {string} Uppercased value. */ - modifier: value => lodash.toUpper(value), + modifier: value => toUpper(value), }), }, { diff --git a/forward_engineering/ddlProvider/ddlProvider.js b/forward_engineering/ddlProvider/ddlProvider.js index 19cd5ce..c2c2704 100644 --- a/forward_engineering/ddlProvider/ddlProvider.js +++ b/forward_engineering/ddlProvider/ddlProvider.js @@ -25,7 +25,10 @@ * } from '../types/ddlProvider' */ -const lodash = require('lodash'); +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'); @@ -185,9 +188,9 @@ const alterSchema = schemaName => const hydrateColumn = ({ columnDefinition, jsonSchema, schemaData, definitionJsonSchema }) => { const definitionSchema = definitionJsonSchema ?? {}; const isUDTRef = !!jsonSchema.$ref; - const type = isUDTRef ? (columnDefinition.type ?? '') : lodash.toUpper(jsonSchema.mode ?? jsonSchema.type); + const type = isUDTRef ? (columnDefinition.type ?? '') : toUpper(jsonSchema.mode ?? jsonSchema.type); const itemsSchema = Array.isArray(jsonSchema.items) ? jsonSchema.items[0] : jsonSchema.items; - const itemsType = lodash.toUpper(itemsSchema?.mode ?? itemsSchema?.type ?? ''); + const itemsType = toUpper(itemsSchema?.mode ?? itemsSchema?.type ?? ''); return { name: columnDefinition.name, @@ -231,7 +234,7 @@ const hydrateColumn = ({ columnDefinition, jsonSchema, schemaData, definitionJso * @returns {JsonSchemaColumn} Merged schema. */ const hydrateJsonSchemaColumn = (jsonSchema, definitionJsonSchema) => { - if (!jsonSchema.$ref || lodash.isEmpty(definitionJsonSchema)) { + if (!jsonSchema.$ref || isEmpty(definitionJsonSchema)) { return jsonSchema; } const { $ref: _ref, ...jsonSchemaWithoutRef } = jsonSchema; @@ -289,7 +292,7 @@ const createCheckConstraint = ({ name, expression, enforced } = {}) => { template: templates.checkConstraint, templateData: { name: name ? `CONSTRAINT ${wrapInQuotes(name)} ` : '', - expression: lodash.trim(expression).replace(/^\(([\s\S]*)\)$/u, '$1'), + expression: trim(expression).replace(/^\(([\s\S]*)\)$/u, '$1'), enforced: enforced ? ` ${enforced}` : '', }, }); @@ -351,7 +354,7 @@ const createForeignKeyConstraint = (constraint, _dbData, schemaData) => { }); return { - statement: lodash.trim(foreignKeyStatement), + statement: trim(foreignKeyStatement), isActivated, }; }; @@ -419,7 +422,7 @@ const createForeignKey = (constraint, _dbData, schemaData) => { }); return { - statement: lodash.trim(foreignKeyStatement) + '\n', + statement: trim(foreignKeyStatement) + '\n', isActivated, }; }; @@ -599,7 +602,7 @@ const dropView = ({ viewName }) => assignTemplates({ template: templates.dropVie * @returns {IndexData} Hydrated index. */ const hydrateIndex = (indexData, tableData, schemaData) => { - const isParentActivated = lodash.get(tableData, '[0].isActivated', true); + const isParentActivated = get(tableData, '[0].isActivated', true); return { ...indexData, @@ -629,7 +632,7 @@ const createIndex = (tableName, index) => { templateData: { indexType, indexName, indexOptions, indexTableName }, }); const commentStatement = getIndexCommentStatement({ - indexName: lodash.trim(indexName), + indexName: trim(indexName), description: index.indxDescription, }); @@ -726,8 +729,8 @@ const createView = (viewData, _dbData, isActivated = true) => { const viewColumns = columns.length > 0 ? ` (${columnsAsString}\n\t)` : ''; const rawSelectStatement = viewData.selectStatement ?? ''; - const selectStatement = lodash.trim(rawSelectStatement) - ? lodash.trim(setTab({ text: rawSelectStatement })) + const selectStatement = trim(rawSelectStatement) + ? trim(setTab({ text: rawSelectStatement })) : assignTemplates({ template: templates.viewSelectStatement, templateData: { diff --git a/forward_engineering/utils/general.js b/forward_engineering/utils/general.js index 79e5e24..19d39e7 100644 --- a/forward_engineering/utils/general.js +++ b/forward_engineering/utils/general.js @@ -14,7 +14,9 @@ * } from '../types/ddlProvider' */ -const lodash = require('lodash'); +const isEqual = require('lodash/isEqual'); +const omit = require('lodash/omit'); +const toLower = require('lodash/toLower'); const { INLINE_COMMENT } = require('../../shared/constants/constants'); /** @@ -39,8 +41,8 @@ const setTab = ({ text, tab }) => { */ const hasType = ({ descriptors, type }) => { return Object.keys(descriptors) - .map(key => lodash.toLower(key)) - .includes(lodash.toLower(type)); + .map(key => toLower(key)) + .includes(toLower(type)); }; /** @@ -250,7 +252,7 @@ const getFullCollectionName = ({ collectionSchema, preferAlterName = true }) => * @returns {T} Merged schema. */ const getSchemaOfAlterCollection = collection => { - return { ...collection, ...lodash.omit(collection?.role, 'properties') }; + return { ...collection, ...omit(collection?.role, 'properties') }; }; /** @@ -297,7 +299,7 @@ const compareProperties = ({ new: newProperty, old: oldProperty }) => { if (!newProperty && !oldProperty) { return false; } - return !lodash.isEqual(newProperty, oldProperty); + return !isEqual(newProperty, oldProperty); }; /** diff --git a/package-lock.json b/package-lock.json index b8a4dee..64464ce 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17,6 +17,7 @@ "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", @@ -1945,6 +1946,20 @@ "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", @@ -1975,6 +1990,19 @@ "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", @@ -2018,6 +2046,31 @@ "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", @@ -2218,6 +2271,37 @@ "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", @@ -2574,6 +2658,21 @@ "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", @@ -2714,6 +2813,19 @@ "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", @@ -2963,6 +3075,16 @@ "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", @@ -3283,6 +3405,19 @@ ], "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", diff --git a/package.json b/package.json index 36034c2..e166fca 100644 --- a/package.json +++ b/package.json @@ -83,6 +83,7 @@ "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", From 3f689515ec8d4171545c24c27a26a98d3926a93b Mon Sep 17 00:00:00 2001 From: Vitalii Bedletskyi Date: Tue, 11 Aug 2026 13:04:53 +0300 Subject: [PATCH 10/15] add missing FK properties --- .../alterForeignKeyHelper.js | 26 ++++++++++++++++--- .../ddlProvider/ddlHelpers/key/keyHelper.js | 8 +++--- forward_engineering/types/ddlProvider.d.ts | 2 +- .../model_level/modelLevelConfig.json | 17 +++++++++--- 4 files changed, 41 insertions(+), 12 deletions(-) diff --git a/forward_engineering/alterScript/alterScriptHelpers/alterForeignKeyHelper.js b/forward_engineering/alterScript/alterScriptHelpers/alterForeignKeyHelper.js index cc1297d..09d1452 100644 --- a/forward_engineering/alterScript/alterScriptHelpers/alterForeignKeyHelper.js +++ b/forward_engineering/alterScript/alterScriptHelpers/alterForeignKeyHelper.js @@ -10,29 +10,47 @@ const { createAlterScriptDto, createDropAndRecreateAlterScriptDto } = require('. 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. + * 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 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. + * 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 compMod?.code?.old ?? compMod?.name?.old ?? relationship.role.code ?? relationship.role.name ?? ''; + return ( + name || + getDefaultConstraintName({ + entityName: compMod?.child?.collection?.name, + postfix: CONSTRAINT_POSTFIX.foreignKey, + }) + ); }; /** diff --git a/forward_engineering/ddlProvider/ddlHelpers/key/keyHelper.js b/forward_engineering/ddlProvider/ddlHelpers/key/keyHelper.js index 31301a5..7feda21 100644 --- a/forward_engineering/ddlProvider/ddlHelpers/key/keyHelper.js +++ b/forward_engineering/ddlProvider/ddlHelpers/key/keyHelper.js @@ -257,18 +257,18 @@ const foreignActiveKeysToString = ({ keys }) => { }; /** - * Build ON DELETE / ON UPDATE clauses for foreign keys. + * 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, relationshipOnUpdate } = properties; + const { relationshipOnDelete, relationshipEnforced } = properties; const relationshipOnDeleteClause = relationshipOnDelete ? ' ON DELETE ' + relationshipOnDelete : ''; - const relationshipOnUpdateClause = relationshipOnUpdate ? ' ON UPDATE ' + relationshipOnUpdate : ''; + const relationshipEnforcedClause = relationshipEnforced ? ' ' + relationshipEnforced : ''; - return relationshipOnDeleteClause + relationshipOnUpdateClause; + return relationshipOnDeleteClause + relationshipEnforcedClause; }; module.exports = { diff --git a/forward_engineering/types/ddlProvider.d.ts b/forward_engineering/types/ddlProvider.d.ts index d6324d2..c6b7470 100644 --- a/forward_engineering/types/ddlProvider.d.ts +++ b/forward_engineering/types/ddlProvider.d.ts @@ -287,7 +287,7 @@ export type ForeignKeyInput = { foreignTable?: string; customProperties?: { relationshipOnDelete?: string; - relationshipOnUpdate?: string; + relationshipEnforced?: string; }; }; diff --git a/properties_pane/model_level/modelLevelConfig.json b/properties_pane/model_level/modelLevelConfig.json index 5d760b4..7e5fd90 100644 --- a/properties_pane/model_level/modelLevelConfig.json +++ b/properties_pane/model_level/modelLevelConfig.json @@ -193,10 +193,21 @@ making sure that you maintain a proper JSON format. "options": ["", "NO ACTION", "RESTRICT", "CASCADE", "SET NULL"] }, { - "propertyName": "On Update", - "propertyKeyword": "relationshipOnUpdate", + "propertyName": "Enforced", + "propertyKeyword": "relationshipEnforced", + "propertyTooltip": "ENFORCED (default) validates data on insert/update/delete. NOT ENFORCED defines an informational referential constraint.", "propertyType": "select", - "options": ["", "NO ACTION", "RESTRICT"] + "options": [ + "", + { + "name": "ENFORCED", + "value": "ENFORCED" + }, + { + "name": "NOT ENFORCED", + "value": "NOT ENFORCED" + } + ] } ] } From f42d2c1ba4747abadb878529796d14c2ddcde1b3 Mon Sep 17 00:00:00 2001 From: Vitalii Bedletskyi Date: Tue, 11 Aug 2026 14:10:52 +0300 Subject: [PATCH 11/15] add missing table types to config and generation ddl for them --- .../alterScript/alterScriptFromDeltaHelper.js | 65 +++++- .../alterScriptHelpers/alterEntityHelper.js | 11 +- .../alterVersioningHelper.js | 113 +++++++++++ .../ddlHelpers/table/getTableOptions.js | 23 ++- .../ddlHelpers/table/getTableType.js | 8 +- .../table/hydrateAuxiliaryTableData.js | 6 +- .../table/hydrateGlobalTemporaryTableData.js | 39 ++++ .../ddlProvider/ddlProvider.js | 69 ++++++- forward_engineering/ddlProvider/templates.js | 4 + forward_engineering/types/alterScript.d.ts | 6 +- forward_engineering/types/ddlProvider.d.ts | 33 ++- .../entity_level/entityLevelConfig.json | 191 +++++++++++++++--- 12 files changed, 516 insertions(+), 52 deletions(-) create mode 100644 forward_engineering/alterScript/alterScriptHelpers/alterVersioningHelper.js create mode 100644 forward_engineering/ddlProvider/ddlHelpers/table/hydrateGlobalTemporaryTableData.js diff --git a/forward_engineering/alterScript/alterScriptFromDeltaHelper.js b/forward_engineering/alterScript/alterScriptFromDeltaHelper.js index 5657b73..b37c7f1 100644 --- a/forward_engineering/alterScript/alterScriptFromDeltaHelper.js +++ b/forward_engineering/alterScript/alterScriptFromDeltaHelper.js @@ -3,6 +3,7 @@ * AlterRelationship, * AlterScriptData, * AlterScriptDto, + * AlterTable, * DeltaBucket, * DeltaModel, * DeltaSection @@ -18,6 +19,8 @@ const { 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 @@ -71,14 +74,43 @@ const getAlterContainersScriptDtos = ({ collection, app }) => { }; }; +/** + * 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[] }} params Delta model, app - * instance and relationships rendered inline in table definitions. + * @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 }) => { +const getAlterCollectionScriptDtos = ({ collection, app, inlineDeltaRelationships, relatedSchemas }) => { const { added, deleted, modified } = getSectionItems(collection.properties?.entities); const { getAddCollectionScriptDto, @@ -88,7 +120,7 @@ const getAlterCollectionScriptDtos = ({ collection, app, inlineDeltaRelationship getModifyColumnScriptDtos, getAddColumnScriptDtos, getDeleteColumnScriptDtos, - } = getEntitiesScripts(app, inlineDeltaRelationships); + } = getEntitiesScripts(app, inlineDeltaRelationships, relatedSchemas); return [ ...deleted @@ -108,6 +140,27 @@ const getAlterCollectionScriptDtos = ({ collection, app, inlineDeltaRelationship ]; }; +/** + * 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. * @@ -233,6 +286,7 @@ const getAlterScriptDtos = (data, app) => { const ignoreRelationshipIDs = inlineDeltaRelationships .map(relationship => relationship.role?.id) .filter(id => id !== undefined); + const relatedSchemas = buildRelatedSchemas(getSectionItems(collection.properties?.entities)); const { deletedContainersScriptDtos, upsertedContainersScriptDtos } = getAlterContainersScriptDtos({ collection, @@ -241,7 +295,8 @@ const getAlterScriptDtos = (data, app) => { return [ ...upsertedContainersScriptDtos, - ...getAlterCollectionScriptDtos({ collection, app, inlineDeltaRelationships }), + ...getAlterCollectionScriptDtos({ collection, app, inlineDeltaRelationships, relatedSchemas }), + ...getAlterVersioningScriptDtos({ collection, relatedSchemas }), ...getAlterRelationshipsScriptDtos({ collection, ignoreRelationshipIDs }), ...getAlterViewScriptDtos({ collection, app }), ...deletedContainersScriptDtos, diff --git a/forward_engineering/alterScript/alterScriptHelpers/alterEntityHelper.js b/forward_engineering/alterScript/alterScriptHelpers/alterEntityHelper.js index 84c57d9..51f757d 100644 --- a/forward_engineering/alterScript/alterScriptHelpers/alterEntityHelper.js +++ b/forward_engineering/alterScript/alterScriptHelpers/alterEntityHelper.js @@ -74,9 +74,11 @@ const getInlineForeignKeyConstraints = ({ collection, inlineDeltaRelationships, * * @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) => collection => { +const getAddCollectionScriptDto = (ddlProvider, inlineDeltaRelationships, relatedSchemas) => collection => { const collectionSchema = getSchemaOfAlterCollection(collection); const schemaName = getSchemaNameFromCollection({ collection }) ?? ''; const schemaData = { schemaName }; @@ -108,6 +110,7 @@ const getAddCollectionScriptDto = (ddlProvider, inlineDeltaRelationships) => col }), schemaData, columnDefinitions, + relatedSchemas, }, entityData: [collectionSchema], jsonSchema: collectionSchema, @@ -230,6 +233,8 @@ const getModifyColumnScriptDtos = ddlProvider => collection => { * * @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; @@ -241,11 +246,11 @@ const getModifyColumnScriptDtos = ddlProvider => collection => { * }} * Entity script builders. */ -const getEntitiesScripts = (app, inlineDeltaRelationships) => { +const getEntitiesScripts = (app, inlineDeltaRelationships, relatedSchemas) => { const ddlProvider = require('../../ddlProvider/ddlProvider')(null, null, app); return { - getAddCollectionScriptDto: getAddCollectionScriptDto(ddlProvider, inlineDeltaRelationships), + getAddCollectionScriptDto: getAddCollectionScriptDto(ddlProvider, inlineDeltaRelationships, relatedSchemas), getDeleteCollectionScriptDto: getDeleteCollectionScriptDto(ddlProvider), getModifyCollectionScriptDtos, getModifyCollectionKeysScriptDtos: getModifyCollectionKeysScriptDtos(ddlProvider), 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/ddlProvider/ddlHelpers/table/getTableOptions.js b/forward_engineering/ddlProvider/ddlHelpers/table/getTableOptions.js index 5048319..60ea998 100644 --- a/forward_engineering/ddlProvider/ddlHelpers/table/getTableOptions.js +++ b/forward_engineering/ddlProvider/ddlHelpers/table/getTableOptions.js @@ -299,6 +299,25 @@ const getTemporalPeriodsClause = ({ periodForSystemTime, periodForBusinessTime } 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. * @@ -339,6 +358,7 @@ const getTableOptions = tableData => { 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); @@ -351,7 +371,7 @@ const getTableOptions = tableData => { }); const tableProperties = tableData.tableProperties ?? ''; - const statements = [inClause, structuredOptions.trim(), partitioning, temporal, tableProperties] + const statements = [mqtClause, inClause, structuredOptions.trim(), partitioning, temporal, tableProperties] .filter(Boolean) .join('\n\t'); @@ -363,4 +383,5 @@ module.exports = { getInClause, getPartitioningClause, getTemporalPeriodsClause, + getMqtClause, }; diff --git a/forward_engineering/ddlProvider/ddlHelpers/table/getTableType.js b/forward_engineering/ddlProvider/ddlHelpers/table/getTableType.js index deba088..a5d4f75 100644 --- a/forward_engineering/ddlProvider/ddlHelpers/table/getTableType.js +++ b/forward_engineering/ddlProvider/ddlHelpers/table/getTableType.js @@ -1,14 +1,18 @@ /** * Resolve table type clause. * - * @param {{ auxiliary?: boolean }} params Table flags. + * @param {{ auxiliary?: boolean; tableKind?: string }} params Table flags. * @returns {string} Table type clause. */ -const getTableType = ({ auxiliary }) => { +const getTableType = ({ auxiliary, tableKind }) => { if (auxiliary) { return ' AUXILIARY'; } + if (tableKind === 'globalTemporary') { + return ' GLOBAL TEMPORARY'; + } + return ''; }; diff --git a/forward_engineering/ddlProvider/ddlHelpers/table/hydrateAuxiliaryTableData.js b/forward_engineering/ddlProvider/ddlHelpers/table/hydrateAuxiliaryTableData.js index cd97a98..1a1cfe5 100644 --- a/forward_engineering/ddlProvider/ddlHelpers/table/hydrateAuxiliaryTableData.js +++ b/forward_engineering/ddlProvider/ddlHelpers/table/hydrateAuxiliaryTableData.js @@ -15,7 +15,9 @@ const { getName, getIdToNameHashTable } = require('../jsonSchema/jsonSchemaHelpe * @returns {Partial} Auxiliary table data. */ const hydrateAuxiliaryTableData = ({ tableData, detailsTab }) => { - if (!detailsTab.auxiliary) { + const isAuxiliary = detailsTab.tableKind === 'auxiliary'; + + if (!isAuxiliary) { return {}; } @@ -35,7 +37,7 @@ const hydrateAuxiliaryTableData = ({ tableData, detailsTab }) => { const auxiliaryBaseColumn = auxiliaryBaseColumnKey ? idToNameHashTable[auxiliaryBaseColumnKey] : undefined; return { - auxiliary: detailsTab.auxiliary, + auxiliary: isAuxiliary, auxiliaryAppend: detailsTab.auxiliaryAppend, auxiliaryPart: detailsTab.auxiliaryPart, auxiliaryBaseTable, 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/ddlProvider.js b/forward_engineering/ddlProvider/ddlProvider.js index c2c2704..6c6c4e4 100644 --- a/forward_engineering/ddlProvider/ddlProvider.js +++ b/forward_engineering/ddlProvider/ddlProvider.js @@ -56,6 +56,7 @@ 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'); @@ -436,6 +437,7 @@ const createForeignKey = (constraint, _dbData, schemaData) => { 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, @@ -449,6 +451,7 @@ const hydrateTable = ({ tableData, entityData, jsonSchema }) => { return { ...tableData, ...auxiliaryTableData, + ...globalTemporaryTableData, keyConstraints: keyHelper.getTableKeyConstraints({ jsonSchema, entityName: tableData.name }), description: detailsTab.description, tableProperties: detailsTab.tableProperties, @@ -460,6 +463,13 @@ const hydrateTable = ({ tableData, entityData, jsonSchema }) => { 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, }; }; @@ -484,6 +494,14 @@ const createTable = (tableData, isActivated = true) => { auxiliaryBaseColumn, auxiliaryAppend, auxiliaryPart, + tableKind, + likeTable, + gttCcsid, + mqtQuery, + mqtDataOption, + mqtRefresh, + mqtMaintainedBy, + mqtQueryOptimization, inClauseType, databaseName, table_tablespace_name, @@ -495,7 +513,7 @@ const createTable = (tableData, isActivated = true) => { description, tableProperties, } = tableData; - const tableType = getTableType({ auxiliary }); + const tableType = getTableType({ auxiliary, tableKind }); const tableName = getNamePrefixedWithSchemaName({ name, schemaName: schemaData.schemaName }); const comment = getTableCommentStatement({ tableName, description }); @@ -522,14 +540,49 @@ const createTable = (tableData, isActivated = true) => { }); } - const tableProps = getTableProps({ - columns: columns ?? [], - foreignKeyConstraints: foreignKeyConstraints ?? [], - keyConstraints: keyConstraints ?? [], - checkConstraints: checkConstraints ?? [], - 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, diff --git a/forward_engineering/ddlProvider/templates.js b/forward_engineering/ddlProvider/templates.js index be7d435..0cf7749 100644 --- a/forward_engineering/ddlProvider/templates.js +++ b/forward_engineering/ddlProvider/templates.js @@ -15,6 +15,10 @@ module.exports = { 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}', diff --git a/forward_engineering/types/alterScript.d.ts b/forward_engineering/types/alterScript.d.ts index d625a6b..544e01a 100644 --- a/forward_engineering/types/alterScript.d.ts +++ b/forward_engineering/types/alterScript.d.ts @@ -119,6 +119,8 @@ export type AlterCollection = { 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 = { @@ -144,6 +146,8 @@ export type ViewDefinitionRef = { 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. @@ -235,7 +239,7 @@ export type DeltaSection = { export type DeltaModel = { properties?: { containers?: DeltaSection; - entities?: DeltaSection; + entities?: DeltaSection; views?: DeltaSection; relationships?: DeltaSection; }; diff --git a/forward_engineering/types/ddlProvider.d.ts b/forward_engineering/types/ddlProvider.d.ts index c6b7470..f577eb8 100644 --- a/forward_engineering/types/ddlProvider.d.ts +++ b/forward_engineering/types/ddlProvider.d.ts @@ -202,6 +202,7 @@ export type PeriodConfig = { startColumn?: FieldListRef; endColumn?: FieldListRef; endInclusive?: string; + historyTable?: string; }; export type HydratedTemporalPeriod = { @@ -235,11 +236,20 @@ export type EntityDetailsTab = { partitioning?: PartitioningConfig | PartitioningConfig[]; periodForSystemTime?: PeriodConfig | PeriodConfig[]; periodForBusinessTime?: PeriodConfig | PeriodConfig[]; - auxiliary?: boolean; 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; @@ -304,6 +314,14 @@ export type HydratedTable = { 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; @@ -332,6 +350,14 @@ export type CreateTableParams = { 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; @@ -502,6 +528,11 @@ export type HydrateAuxiliaryTableParams = { detailsTab: EntityDetailsTab; }; +export type HydrateGlobalTemporaryTableParams = { + tableData: HydratedTable; + detailsTab: EntityDetailsTab; +}; + export type ConstraintOptionsResult = { constraintString: string; statement: string; diff --git a/properties_pane/entity_level/entityLevelConfig.json b/properties_pane/entity_level/entityLevelConfig.json index df45444..dece027 100644 --- a/properties_pane/entity_level/entityLevelConfig.json +++ b/properties_pane/entity_level/entityLevelConfig.json @@ -129,10 +129,25 @@ making sure that you maintain a proper JSON format. } }, { - "propertyName": "Auxiliary", - "propertyKeyword": "auxiliary", - "propertyTooltip": "CREATE AUXILIARY TABLE used to store LOB column data for a base table.", - "propertyType": "checkbox" + "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", @@ -143,8 +158,8 @@ making sure that you maintain a proper JSON format. "withEmptyOption": true, "excludeCurrent": true, "dependency": { - "key": "auxiliary", - "value": true + "key": "tableKind", + "value": "auxiliary" } }, { @@ -158,8 +173,8 @@ making sure that you maintain a proper JSON format. "maxFields": 1 }, "dependency": { - "key": "auxiliary", - "value": true + "key": "tableKind", + "value": "auxiliary" } }, { @@ -169,8 +184,8 @@ making sure that you maintain a proper JSON format. "propertyType": "select", "options": ["", "yes", "no"], "dependency": { - "key": "auxiliary", - "value": true + "key": "tableKind", + "value": "auxiliary" } }, { @@ -181,7 +196,116 @@ making sure that you maintain a proper JSON format. "valueType": "integer", "allowNegative": false, "dependency": { - "key": "auxiliary", + "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 } }, @@ -194,8 +318,8 @@ making sure that you maintain a proper JSON format. "dependency": { "type": "not", "values": { - "key": "auxiliary", - "value": true + "key": "tableKind", + "value": ["auxiliary", "globalTemporary"] } } }, @@ -210,8 +334,8 @@ making sure that you maintain a proper JSON format. { "type": "not", "values": { - "key": "auxiliary", - "value": true + "key": "tableKind", + "value": ["auxiliary", "globalTemporary"] } }, { @@ -232,8 +356,8 @@ making sure that you maintain a proper JSON format. { "type": "not", "values": { - "key": "auxiliary", - "value": true + "key": "tableKind", + "value": ["auxiliary", "globalTemporary"] } }, { @@ -254,8 +378,8 @@ making sure that you maintain a proper JSON format. { "type": "not", "values": { - "key": "auxiliary", - "value": true + "key": "tableKind", + "value": ["auxiliary", "globalTemporary"] } }, { @@ -273,8 +397,8 @@ making sure that you maintain a proper JSON format. "dependency": { "type": "not", "values": { - "key": "auxiliary", - "value": true + "key": "tableKind", + "value": ["auxiliary", "globalTemporary"] } }, "structure": [ @@ -436,8 +560,8 @@ making sure that you maintain a proper JSON format. "dependency": { "type": "not", "values": { - "key": "auxiliary", - "value": true + "key": "tableKind", + "value": ["auxiliary", "globalTemporary"] } }, "structure": [ @@ -533,8 +657,8 @@ making sure that you maintain a proper JSON format. "dependency": { "type": "not", "values": { - "key": "auxiliary", - "value": true + "key": "tableKind", + "value": ["auxiliary", "globalTemporary", "materializedQuery"] } }, "structure": [ @@ -555,6 +679,15 @@ making sure that you maintain a proper JSON format. "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 } ] }, @@ -568,8 +701,8 @@ making sure that you maintain a proper JSON format. "dependency": { "type": "not", "values": { - "key": "auxiliary", - "value": true + "key": "tableKind", + "value": ["auxiliary", "globalTemporary", "materializedQuery"] } }, "structure": [ @@ -610,8 +743,8 @@ making sure that you maintain a proper JSON format. "dependency": { "type": "not", "values": { - "key": "auxiliary", - "value": true + "key": "tableKind", + "value": ["auxiliary", "globalTemporary"] } } }, From fde571a7fd5bc9889c464008d3c686ec7467c96f Mon Sep 17 00:00:00 2001 From: Vitalii Bedletskyi Date: Tue, 11 Aug 2026 14:59:09 +0300 Subject: [PATCH 12/15] add DDL generation for UDT --- .../alterScript/alterScriptFromDeltaHelper.js | 27 ++ .../alterModelDefinitionHelper.js | 302 ++++++++++++++++++ .../ddlHelpers/comment/commentHelper.js | 33 ++ .../ddlProvider/ddlProvider.js | 27 ++ forward_engineering/ddlProvider/templates.js | 4 + forward_engineering/types/alterScript.d.ts | 40 +++ forward_engineering/types/ddlProvider.d.ts | 1 + 7 files changed, 434 insertions(+) create mode 100644 forward_engineering/alterScript/alterScriptHelpers/alterModelDefinitionHelper.js diff --git a/forward_engineering/alterScript/alterScriptFromDeltaHelper.js b/forward_engineering/alterScript/alterScriptFromDeltaHelper.js index b37c7f1..43ec656 100644 --- a/forward_engineering/alterScript/alterScriptFromDeltaHelper.js +++ b/forward_engineering/alterScript/alterScriptFromDeltaHelper.js @@ -12,6 +12,7 @@ */ const { getContainersScripts } = require('./alterScriptHelpers/alterContainerHelper'); +const { getModelDefinitionsScripts } = require('./alterScriptHelpers/alterModelDefinitionHelper'); const { getEntitiesScripts } = require('./alterScriptHelpers/alterEntityHelper'); const { getDeleteForeignKeyScriptDtos, @@ -74,6 +75,29 @@ const getAlterContainersScriptDtos = ({ collection, app }) => { }; }; +/** + * 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 @@ -292,13 +316,16 @@ const getAlterScriptDtos = (data, app) => { 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)) 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/ddlProvider/ddlHelpers/comment/commentHelper.js b/forward_engineering/ddlProvider/ddlHelpers/comment/commentHelper.js index 328d691..e4e2619 100644 --- a/forward_engineering/ddlProvider/ddlHelpers/comment/commentHelper.js +++ b/forward_engineering/ddlProvider/ddlHelpers/comment/commentHelper.js @@ -17,6 +17,7 @@ const OBJECT_TYPE = { column: 'COLUMN', table: 'TABLE', index: 'INDEX', + type: 'TYPE', }; /** @enum {string} */ @@ -115,6 +116,36 @@ const dropIndexCommentStatement = ({ indexName }) => { }); }; +/** + * 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. * @@ -200,6 +231,8 @@ module.exports = { getTableCommentStatement, getIndexCommentStatement, dropIndexCommentStatement, + getTypeCommentStatement, + dropTypeCommentStatement, getColumnComments, dropSchemaCommentStatement, dropTableCommentStatement, diff --git a/forward_engineering/ddlProvider/ddlProvider.js b/forward_engineering/ddlProvider/ddlProvider.js index 6c6c4e4..45455d0 100644 --- a/forward_engineering/ddlProvider/ddlProvider.js +++ b/forward_engineering/ddlProvider/ddlProvider.js @@ -50,6 +50,7 @@ const { getColumnComments, getSchemaCommentStatement, getIndexCommentStatement, + getTypeCommentStatement, } = require('./ddlHelpers/comment/commentHelper.js'); const { getTableProps } = require('./ddlHelpers/table/getTableProps.js'); const { getTableOptions } = require('./ddlHelpers/table/getTableOptions.js'); @@ -180,6 +181,31 @@ const alterSchema = 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. * @@ -852,6 +878,7 @@ module.exports = (_baseProvider, _options, _app) => ({ createSchema, dropSchema, alterSchema, + createUdt, hydrateColumn, hydrateJsonSchemaColumn, convertColumnDefinition, diff --git a/forward_engineering/ddlProvider/templates.js b/forward_engineering/ddlProvider/templates.js index 0cf7749..e92a4f3 100644 --- a/forward_engineering/ddlProvider/templates.js +++ b/forward_engineering/ddlProvider/templates.js @@ -5,6 +5,10 @@ module.exports = { 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};', diff --git a/forward_engineering/types/alterScript.d.ts b/forward_engineering/types/alterScript.d.ts index 544e01a..1baf19e 100644 --- a/forward_engineering/types/alterScript.d.ts +++ b/forward_engineering/types/alterScript.d.ts @@ -137,6 +137,45 @@ export type AlterContainer = { 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; @@ -242,6 +281,7 @@ export type DeltaModel = { entities?: DeltaSection; views?: DeltaSection; relationships?: DeltaSection; + modelDefinitions?: DeltaSection; }; }; diff --git a/forward_engineering/types/ddlProvider.d.ts b/forward_engineering/types/ddlProvider.d.ts index f577eb8..98741a8 100644 --- a/forward_engineering/types/ddlProvider.d.ts +++ b/forward_engineering/types/ddlProvider.d.ts @@ -722,6 +722,7 @@ export type DdlProvider = { 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; From c75e897c8ea9278cd207d134f5fbbcfe284a7001 Mon Sep 17 00:00:00 2001 From: Vitalii Bedletskyi Date: Tue, 11 Aug 2026 14:59:22 +0300 Subject: [PATCH 13/15] delete commented imports --- reverse_engineering/api.js | 9 --------- 1 file changed, 9 deletions(-) diff --git a/reverse_engineering/api.js b/reverse_engineering/api.js index f7b2189..0ac5ddc 100644 --- a/reverse_engineering/api.js +++ b/reverse_engineering/api.js @@ -1,12 +1,3 @@ -// const { identity } = require('lodash'); -// const { mapSeries } = require('async'); -// const { connectionHelper } = require('../shared/helpers/connectionHelper'); -// const { instanceHelper } = require('../shared/helpers/instanceHelper'); -// const { logHelper } = require('../shared/helpers/logHelper'); -// const { OBJECT_TYPE } = require('../shared/constants/constants'); -// const { nameHelper } = require('../shared/helpers/nameHelper'); -// const { testConnection } = require('../shared/api/testConnection'); - /** @typedef {(error?: unknown, result?: unknown, info?: unknown) => void} Callback */ /** From ce8e5aaf62a03af4e5692bcd212c3102d46fdc33 Mon Sep 17 00:00:00 2001 From: Vitalii Bedletskyi Date: Tue, 11 Aug 2026 15:42:16 +0300 Subject: [PATCH 14/15] delete not supported version --- package.json | 1 - 1 file changed, 1 deletion(-) diff --git a/package.json b/package.json index e166fca..52e01b2 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,6 @@ "applicationTarget": "Db2-zOS", "title": "Db2 for z/OS", "versions": [ - "v12", "v13" ] }, From c050061e3e806870daf02384b645fca7cb8ce437 Mon Sep 17 00:00:00 2001 From: Vitalii Bedletskyi Date: Tue, 11 Aug 2026 15:42:36 +0300 Subject: [PATCH 15/15] fix plugin build where missed type configurations --- buildConstants.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/buildConstants.js b/buildConstants.js index 1126007..c8061cf 100644 --- a/buildConstants.js +++ b/buildConstants.js @@ -3,7 +3,7 @@ const path = require('path'); const DEFAULT_RELEASE_FOLDER_PATH = path.resolve(__dirname, 'release'); -const EXCLUDED_EXTENSIONS = ['.js', '.g4', '.interp', '.tokens']; +const EXCLUDED_EXTENSIONS = ['.js', '.g4', '.interp', '.tokens', '.d.ts']; const EXCLUDED_FILES = [ '.github', '.DS_Store', @@ -18,7 +18,6 @@ const EXCLUDED_FILES = [ '.sonarlint', '.sonarcloud.properties', 'tsconfig.json', - 'types', 'build', 'release', 'node_modules',