diff --git a/README.md b/README.md
index e5860dc2..432d7e96 100644
--- a/README.md
+++ b/README.md
@@ -5,6 +5,7 @@
Open-source database diagrams editor
+ No installations • No Database password required.
diff --git a/package-lock.json b/package-lock.json
index 88bf0616..00af81a5 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -8,6 +8,7 @@
"name": "chartdb",
"version": "0.0.0",
"dependencies": {
+ "@ai-sdk/openai": "^0.0.51",
"@radix-ui/react-accordion": "^1.2.0",
"@radix-ui/react-checkbox": "^1.1.1",
"@radix-ui/react-collapsible": "^1.1.0",
@@ -27,6 +28,7 @@
"@radix-ui/react-tooltip": "^1.1.2",
"@uidotdev/usehooks": "^2.4.1",
"@xyflow/react": "^12.0.4",
+ "ai": "^3.3.14",
"class-variance-authority": "^0.7.0",
"clsx": "^2.1.1",
"cmdk": "^1.0.0",
@@ -69,6 +71,181 @@
"vite": "^5.3.4"
}
},
+ "node_modules/@ai-sdk/openai": {
+ "version": "0.0.51",
+ "resolved": "https://registry.npmjs.org/@ai-sdk/openai/-/openai-0.0.51.tgz",
+ "integrity": "sha512-e+badQnVzAuY0CXThjXZM4IdbztGnntz0Oo44jyklVsWjhJxnpr5m47ALR+0C/Wdakl5oHFGy4CZfiJ9K6ZyVw==",
+ "dependencies": {
+ "@ai-sdk/provider": "0.0.21",
+ "@ai-sdk/provider-utils": "1.0.15"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "peerDependencies": {
+ "zod": "^3.0.0"
+ }
+ },
+ "node_modules/@ai-sdk/provider": {
+ "version": "0.0.21",
+ "resolved": "https://registry.npmjs.org/@ai-sdk/provider/-/provider-0.0.21.tgz",
+ "integrity": "sha512-9j95uaPRxwYkzQdkl4XO/MmWWW5c5vcVSXtqvALpD9SMB9fzH46dO3UN4VbOJR2J3Z84CZAqgZu5tNlkptT9qQ==",
+ "dependencies": {
+ "json-schema": "0.4.0"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@ai-sdk/provider-utils": {
+ "version": "1.0.15",
+ "resolved": "https://registry.npmjs.org/@ai-sdk/provider-utils/-/provider-utils-1.0.15.tgz",
+ "integrity": "sha512-icZqf2kpV8XdSViei4pX9ylYcVn+pk9AnVquJJGjGQGnwZ/5OgShqnFcLYrMjQfQcSVkz0PxdQVsIhZHzlT9Og==",
+ "dependencies": {
+ "@ai-sdk/provider": "0.0.21",
+ "eventsource-parser": "1.1.2",
+ "nanoid": "3.3.6",
+ "secure-json-parse": "2.7.0"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "peerDependencies": {
+ "zod": "^3.0.0"
+ },
+ "peerDependenciesMeta": {
+ "zod": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@ai-sdk/provider-utils/node_modules/nanoid": {
+ "version": "3.3.6",
+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.6.tgz",
+ "integrity": "sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "bin": {
+ "nanoid": "bin/nanoid.cjs"
+ },
+ "engines": {
+ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
+ }
+ },
+ "node_modules/@ai-sdk/react": {
+ "version": "0.0.48",
+ "resolved": "https://registry.npmjs.org/@ai-sdk/react/-/react-0.0.48.tgz",
+ "integrity": "sha512-KfW33Gj5/qDA6RWfJ42al3QsgIA2UO+x0gX1M6Kk6LY4bTFgy7+F4GLmo4eflM/9o2M7fUZrNddoOuJ15vbgZg==",
+ "dependencies": {
+ "@ai-sdk/provider-utils": "1.0.15",
+ "@ai-sdk/ui-utils": "0.0.35",
+ "swr": "2.2.5"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "peerDependencies": {
+ "react": "^18 || ^19",
+ "zod": "^3.0.0"
+ },
+ "peerDependenciesMeta": {
+ "react": {
+ "optional": true
+ },
+ "zod": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@ai-sdk/solid": {
+ "version": "0.0.38",
+ "resolved": "https://registry.npmjs.org/@ai-sdk/solid/-/solid-0.0.38.tgz",
+ "integrity": "sha512-7pMW6leig8Y05UIL8jy/1dEDTjtfA2WG9qkVMWjnKSKiucT/Z5uOO3zWNHYq8EVwdJJnv+RR8gUASXcZLTh7og==",
+ "dependencies": {
+ "@ai-sdk/provider-utils": "1.0.15",
+ "@ai-sdk/ui-utils": "0.0.35"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "peerDependencies": {
+ "solid-js": "^1.7.7"
+ },
+ "peerDependenciesMeta": {
+ "solid-js": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@ai-sdk/svelte": {
+ "version": "0.0.40",
+ "resolved": "https://registry.npmjs.org/@ai-sdk/svelte/-/svelte-0.0.40.tgz",
+ "integrity": "sha512-S62aB2aT7gjrVY2uDhxwZFBg9hl4wNwu+kd31zsowByC/yyZp9MRIMXkDCkj0qQLFXvfUzaUuzk8v9gvuPOFCQ==",
+ "dependencies": {
+ "@ai-sdk/provider-utils": "1.0.15",
+ "@ai-sdk/ui-utils": "0.0.35",
+ "sswr": "2.1.0"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "peerDependencies": {
+ "svelte": "^3.0.0 || ^4.0.0"
+ },
+ "peerDependenciesMeta": {
+ "svelte": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@ai-sdk/ui-utils": {
+ "version": "0.0.35",
+ "resolved": "https://registry.npmjs.org/@ai-sdk/ui-utils/-/ui-utils-0.0.35.tgz",
+ "integrity": "sha512-JZWp5gbH9K0/qmmqv0JFrH97JNMB9dU1xtrR2a8uzRE0wYtNmd3KsM9x3KW/f9OGjxUHzAkrboMvxKv/3uz24w==",
+ "dependencies": {
+ "@ai-sdk/provider": "0.0.21",
+ "@ai-sdk/provider-utils": "1.0.15",
+ "json-schema": "0.4.0",
+ "secure-json-parse": "2.7.0",
+ "zod-to-json-schema": "3.22.5"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "peerDependencies": {
+ "zod": "^3.0.0"
+ },
+ "peerDependenciesMeta": {
+ "zod": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@ai-sdk/vue": {
+ "version": "0.0.40",
+ "resolved": "https://registry.npmjs.org/@ai-sdk/vue/-/vue-0.0.40.tgz",
+ "integrity": "sha512-01LuQT+Cx2e19fYB4nlMlQhmpJ826S1HfGcB4BY30+/XOJebdHRPPOZ3WV9BytBD7kha/tnngBruiYzegGR+Ug==",
+ "dependencies": {
+ "@ai-sdk/provider-utils": "1.0.15",
+ "@ai-sdk/ui-utils": "0.0.35",
+ "swrv": "1.0.4"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "peerDependencies": {
+ "vue": "^3.3.4"
+ },
+ "peerDependenciesMeta": {
+ "vue": {
+ "optional": true
+ }
+ }
+ },
"node_modules/@alloc/quick-lru": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz",
@@ -85,7 +262,6 @@
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz",
"integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==",
- "dev": true,
"license": "Apache-2.0",
"dependencies": {
"@jridgewell/gen-mapping": "^0.3.5",
@@ -264,7 +440,6 @@
"version": "7.24.8",
"resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.24.8.tgz",
"integrity": "sha512-pO9KhhRcuUyGnJWwyEgnRJTSIZHiT+vMD0kPeD+so0l7mxkMT19g3pjY9GTnHySck/hDzq+dtW/4VgnMkippsQ==",
- "dev": true,
"license": "MIT",
"engines": {
"node": ">=6.9.0"
@@ -274,7 +449,6 @@
"version": "7.24.7",
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.7.tgz",
"integrity": "sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w==",
- "dev": true,
"license": "MIT",
"engines": {
"node": ">=6.9.0"
@@ -324,7 +498,6 @@
"version": "7.25.3",
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.25.3.tgz",
"integrity": "sha512-iLTJKDbJ4hMvFPgQwwsVoxtHyWpKKPBrxkANrSYewDPaPpT5py5yeVkgPIJ7XYXhndxJpaA3PyALSXQ7u8e/Dw==",
- "dev": true,
"license": "MIT",
"dependencies": {
"@babel/types": "^7.25.2"
@@ -418,7 +591,6 @@
"version": "7.25.2",
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.25.2.tgz",
"integrity": "sha512-YTnYtra7W9e6/oAZEHj0bJehPRUlLH9/fbpT5LfB0NhQXyALCRkRs3zH9v07IYhkgpqX6Z78FnuccZr/l4Fs4Q==",
- "dev": true,
"license": "MIT",
"dependencies": {
"@babel/helper-string-parser": "^7.24.8",
@@ -1156,6 +1328,14 @@
"node": ">= 8"
}
},
+ "node_modules/@opentelemetry/api": {
+ "version": "1.9.0",
+ "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.0.tgz",
+ "integrity": "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==",
+ "engines": {
+ "node": ">=8.0.0"
+ }
+ },
"node_modules/@pkgjs/parseargs": {
"version": "0.11.0",
"resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz",
@@ -2511,11 +2691,15 @@
"@types/d3-selection": "*"
}
},
+ "node_modules/@types/diff-match-patch": {
+ "version": "1.0.36",
+ "resolved": "https://registry.npmjs.org/@types/diff-match-patch/-/diff-match-patch-1.0.36.tgz",
+ "integrity": "sha512-xFdR6tkm0MWvBfO8xXCSsinYxHcqkQUlcHeSpMC2ukzOb6lwQAfDmW+Qt0AvlGd8HpsS28qKsB+oPeJn9I39jg=="
+ },
"node_modules/@types/estree": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz",
"integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==",
- "dev": true,
"license": "MIT"
},
"node_modules/@types/hast": {
@@ -2813,6 +2997,118 @@
"vite": "^4.2.0 || ^5.0.0"
}
},
+ "node_modules/@vue/compiler-core": {
+ "version": "3.4.38",
+ "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.4.38.tgz",
+ "integrity": "sha512-8IQOTCWnLFqfHzOGm9+P8OPSEDukgg3Huc92qSG49if/xI2SAwLHQO2qaPQbjCWPBcQoO1WYfXfTACUrWV3c5A==",
+ "peer": true,
+ "dependencies": {
+ "@babel/parser": "^7.24.7",
+ "@vue/shared": "3.4.38",
+ "entities": "^4.5.0",
+ "estree-walker": "^2.0.2",
+ "source-map-js": "^1.2.0"
+ }
+ },
+ "node_modules/@vue/compiler-core/node_modules/estree-walker": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz",
+ "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==",
+ "peer": true
+ },
+ "node_modules/@vue/compiler-dom": {
+ "version": "3.4.38",
+ "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.4.38.tgz",
+ "integrity": "sha512-Osc/c7ABsHXTsETLgykcOwIxFktHfGSUDkb05V61rocEfsFDcjDLH/IHJSNJP+/Sv9KeN2Lx1V6McZzlSb9EhQ==",
+ "peer": true,
+ "dependencies": {
+ "@vue/compiler-core": "3.4.38",
+ "@vue/shared": "3.4.38"
+ }
+ },
+ "node_modules/@vue/compiler-sfc": {
+ "version": "3.4.38",
+ "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.4.38.tgz",
+ "integrity": "sha512-s5QfZ+9PzPh3T5H4hsQDJtI8x7zdJaew/dCGgqZ2630XdzaZ3AD8xGZfBqpT8oaD/p2eedd+pL8tD5vvt5ZYJQ==",
+ "peer": true,
+ "dependencies": {
+ "@babel/parser": "^7.24.7",
+ "@vue/compiler-core": "3.4.38",
+ "@vue/compiler-dom": "3.4.38",
+ "@vue/compiler-ssr": "3.4.38",
+ "@vue/shared": "3.4.38",
+ "estree-walker": "^2.0.2",
+ "magic-string": "^0.30.10",
+ "postcss": "^8.4.40",
+ "source-map-js": "^1.2.0"
+ }
+ },
+ "node_modules/@vue/compiler-sfc/node_modules/estree-walker": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz",
+ "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==",
+ "peer": true
+ },
+ "node_modules/@vue/compiler-ssr": {
+ "version": "3.4.38",
+ "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.4.38.tgz",
+ "integrity": "sha512-YXznKFQ8dxYpAz9zLuVvfcXhc31FSPFDcqr0kyujbOwNhlmaNvL2QfIy+RZeJgSn5Fk54CWoEUeW+NVBAogGaw==",
+ "peer": true,
+ "dependencies": {
+ "@vue/compiler-dom": "3.4.38",
+ "@vue/shared": "3.4.38"
+ }
+ },
+ "node_modules/@vue/reactivity": {
+ "version": "3.4.38",
+ "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.4.38.tgz",
+ "integrity": "sha512-4vl4wMMVniLsSYYeldAKzbk72+D3hUnkw9z8lDeJacTxAkXeDAP1uE9xr2+aKIN0ipOL8EG2GPouVTH6yF7Gnw==",
+ "peer": true,
+ "dependencies": {
+ "@vue/shared": "3.4.38"
+ }
+ },
+ "node_modules/@vue/runtime-core": {
+ "version": "3.4.38",
+ "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.4.38.tgz",
+ "integrity": "sha512-21z3wA99EABtuf+O3IhdxP0iHgkBs1vuoCAsCKLVJPEjpVqvblwBnTj42vzHRlWDCyxu9ptDm7sI2ZMcWrQqlA==",
+ "peer": true,
+ "dependencies": {
+ "@vue/reactivity": "3.4.38",
+ "@vue/shared": "3.4.38"
+ }
+ },
+ "node_modules/@vue/runtime-dom": {
+ "version": "3.4.38",
+ "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.4.38.tgz",
+ "integrity": "sha512-afZzmUreU7vKwKsV17H1NDThEEmdYI+GCAK/KY1U957Ig2NATPVjCROv61R19fjZNzMmiU03n79OMnXyJVN0UA==",
+ "peer": true,
+ "dependencies": {
+ "@vue/reactivity": "3.4.38",
+ "@vue/runtime-core": "3.4.38",
+ "@vue/shared": "3.4.38",
+ "csstype": "^3.1.3"
+ }
+ },
+ "node_modules/@vue/server-renderer": {
+ "version": "3.4.38",
+ "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.4.38.tgz",
+ "integrity": "sha512-NggOTr82FbPEkkUvBm4fTGcwUY8UuTsnWC/L2YZBmvaQ4C4Jl/Ao4HHTB+l7WnFCt5M/dN3l0XLuyjzswGYVCA==",
+ "peer": true,
+ "dependencies": {
+ "@vue/compiler-ssr": "3.4.38",
+ "@vue/shared": "3.4.38"
+ },
+ "peerDependencies": {
+ "vue": "3.4.38"
+ }
+ },
+ "node_modules/@vue/shared": {
+ "version": "3.4.38",
+ "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.4.38.tgz",
+ "integrity": "sha512-q0xCiLkuWWQLzVrecPb0RMsNWyxICOjPrcrwxTUEHb1fsnvni4dcuyG7RT/Ie7VPTvnjzIaWzRMUBsrqNj/hhw==",
+ "peer": true
+ },
"node_modules/@xobotyi/scrollbar-width": {
"version": "1.9.5",
"resolved": "https://registry.npmjs.org/@xobotyi/scrollbar-width/-/scrollbar-width-1.9.5.tgz",
@@ -2870,6 +3166,71 @@
"acorn": "^6.0.0 || ^7.0.0 || ^8.0.0"
}
},
+ "node_modules/ai": {
+ "version": "3.3.14",
+ "resolved": "https://registry.npmjs.org/ai/-/ai-3.3.14.tgz",
+ "integrity": "sha512-GF3CVS1rnOtgN68OQGlT/2quhg/D3sMFwak48OGXeqv4VRcDgGJx3UqSwT7ipFa9BncRqo7TIqDHHji3Doamaw==",
+ "dependencies": {
+ "@ai-sdk/provider": "0.0.21",
+ "@ai-sdk/provider-utils": "1.0.15",
+ "@ai-sdk/react": "0.0.48",
+ "@ai-sdk/solid": "0.0.38",
+ "@ai-sdk/svelte": "0.0.40",
+ "@ai-sdk/ui-utils": "0.0.35",
+ "@ai-sdk/vue": "0.0.40",
+ "@opentelemetry/api": "1.9.0",
+ "eventsource-parser": "1.1.2",
+ "json-schema": "0.4.0",
+ "jsondiffpatch": "0.6.0",
+ "nanoid": "3.3.6",
+ "secure-json-parse": "2.7.0",
+ "zod-to-json-schema": "3.22.5"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "peerDependencies": {
+ "openai": "^4.42.0",
+ "react": "^18 || ^19",
+ "sswr": "^2.1.0",
+ "svelte": "^3.0.0 || ^4.0.0",
+ "zod": "^3.0.0"
+ },
+ "peerDependenciesMeta": {
+ "openai": {
+ "optional": true
+ },
+ "react": {
+ "optional": true
+ },
+ "sswr": {
+ "optional": true
+ },
+ "svelte": {
+ "optional": true
+ },
+ "zod": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/ai/node_modules/nanoid": {
+ "version": "3.3.6",
+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.6.tgz",
+ "integrity": "sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "bin": {
+ "nanoid": "bin/nanoid.cjs"
+ },
+ "engines": {
+ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
+ }
+ },
"node_modules/ajv": {
"version": "6.12.6",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
@@ -3449,6 +3810,11 @@
"integrity": "sha512-JhZUT7JFcQy/EzW605k/ktHtncoo9vnyW/2GspNYwFlN1C/WmjuV/xtS04e9SOkL2sTdw0VAZ2UGCcQ9lR6p6w==",
"license": "MIT"
},
+ "node_modules/client-only": {
+ "version": "0.0.1",
+ "resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz",
+ "integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA=="
+ },
"node_modules/clsx": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz",
@@ -3836,6 +4202,19 @@
}
}
},
+ "node_modules/code-red": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/code-red/-/code-red-1.0.4.tgz",
+ "integrity": "sha512-7qJWqItLA8/VPVlKJlFXU+NBlo/qyfs39aJcuMT/2ere32ZqvF5OSxgdM5xOfJJ7O429gg2HM47y8v9P+9wrNw==",
+ "peer": true,
+ "dependencies": {
+ "@jridgewell/sourcemap-codec": "^1.4.15",
+ "@types/estree": "^1.0.1",
+ "acorn": "^8.10.0",
+ "estree-walker": "^3.0.3",
+ "periscopic": "^3.1.0"
+ }
+ },
"node_modules/color-convert": {
"version": "1.9.3",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
@@ -4227,6 +4606,15 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/dequal": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz",
+ "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==",
+ "peer": true,
+ "engines": {
+ "node": ">=6"
+ }
+ },
"node_modules/detect-node-es": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/detect-node-es/-/detect-node-es-1.1.0.tgz",
@@ -4245,6 +4633,11 @@
"integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==",
"license": "Apache-2.0"
},
+ "node_modules/diff-match-patch": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/diff-match-patch/-/diff-match-patch-1.0.5.tgz",
+ "integrity": "sha512-IayShXAgj/QMXgB0IWmKx+rOPuGMhqm5w6jvFxmVenXKIzRqTAAsbBPT3kWQeGANj3jGgvcvv4yK6SxqYmikgw=="
+ },
"node_modules/dir-glob": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz",
@@ -4294,6 +4687,18 @@
"integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==",
"license": "MIT"
},
+ "node_modules/entities": {
+ "version": "4.5.0",
+ "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz",
+ "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==",
+ "peer": true,
+ "engines": {
+ "node": ">=0.12"
+ },
+ "funding": {
+ "url": "https://github.com/fb55/entities?sponsor=1"
+ }
+ },
"node_modules/error-stack-parser": {
"version": "2.1.4",
"resolved": "https://registry.npmjs.org/error-stack-parser/-/error-stack-parser-2.1.4.tgz",
@@ -5207,6 +5612,15 @@
"node": ">=4.0"
}
},
+ "node_modules/estree-walker": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz",
+ "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==",
+ "peer": true,
+ "dependencies": {
+ "@types/estree": "^1.0.0"
+ }
+ },
"node_modules/esutils": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
@@ -5216,6 +5630,14 @@
"node": ">=0.10.0"
}
},
+ "node_modules/eventsource-parser": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-1.1.2.tgz",
+ "integrity": "sha512-v0eOBUbiaFojBu2s2NPBfYUoRR9GjcDNvCXVaqEf5vVfpIAh9f8RCo4vXTP8c63QRKCFwoLpMpTdPwwhEKVgzA==",
+ "engines": {
+ "node": ">=14.18"
+ }
+ },
"node_modules/fast-deep-equal": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
@@ -6159,6 +6581,15 @@
"node": ">=8"
}
},
+ "node_modules/is-reference": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-3.0.2.tgz",
+ "integrity": "sha512-v3rht/LgVcsdZa3O2Nqs+NMowLOxeOm7Ay9+/ARQ2F+qEoANRcqrjAZKGN0v8ymUetZGgkp26LTnGT7H0Qo9Pg==",
+ "peer": true,
+ "dependencies": {
+ "@types/estree": "*"
+ }
+ },
"node_modules/is-regex": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz",
@@ -6383,6 +6814,11 @@
"integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==",
"license": "MIT"
},
+ "node_modules/json-schema": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz",
+ "integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA=="
+ },
"node_modules/json-schema-traverse": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
@@ -6408,6 +6844,33 @@
"node": ">=6"
}
},
+ "node_modules/jsondiffpatch": {
+ "version": "0.6.0",
+ "resolved": "https://registry.npmjs.org/jsondiffpatch/-/jsondiffpatch-0.6.0.tgz",
+ "integrity": "sha512-3QItJOXp2AP1uv7waBkao5nCvhEv+QmJAd38Ybq7wNI74Q+BBmnLn4EDKz6yI9xGAIQoUF87qHt+kc1IVxB4zQ==",
+ "dependencies": {
+ "@types/diff-match-patch": "^1.0.36",
+ "chalk": "^5.3.0",
+ "diff-match-patch": "^1.0.5"
+ },
+ "bin": {
+ "jsondiffpatch": "bin/jsondiffpatch.js"
+ },
+ "engines": {
+ "node": "^18.0.0 || >=20.0.0"
+ }
+ },
+ "node_modules/jsondiffpatch/node_modules/chalk": {
+ "version": "5.3.0",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz",
+ "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==",
+ "engines": {
+ "node": "^12.17.0 || ^14.13 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/chalk?sponsor=1"
+ }
+ },
"node_modules/jsx-ast-utils": {
"version": "3.3.5",
"resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz",
@@ -6481,6 +6944,12 @@
"integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==",
"license": "MIT"
},
+ "node_modules/locate-character": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/locate-character/-/locate-character-3.0.0.tgz",
+ "integrity": "sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA==",
+ "peer": true
+ },
"node_modules/locate-path": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz",
@@ -6547,6 +7016,15 @@
"react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0-rc"
}
},
+ "node_modules/magic-string": {
+ "version": "0.30.11",
+ "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.11.tgz",
+ "integrity": "sha512-+Wri9p0QHMy+545hKww7YAu5NyzF8iomPL/RQazugQ9+Ez4Ic3mERMd8ZTX5rfK944j+560ZJi8iAwgak1Ac7A==",
+ "peer": true,
+ "dependencies": {
+ "@jridgewell/sourcemap-codec": "^1.5.0"
+ }
+ },
"node_modules/mdn-data": {
"version": "2.0.14",
"resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.14.tgz",
@@ -6988,6 +7466,17 @@
"node": ">=8"
}
},
+ "node_modules/periscopic": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/periscopic/-/periscopic-3.1.0.tgz",
+ "integrity": "sha512-vKiQ8RRtkl9P+r/+oefh25C3fhybptkHKCZSPlcXiJux2tJF55GnEj3BVn4A5gKfq9NWWXXrxkHBwVPUfH0opw==",
+ "peer": true,
+ "dependencies": {
+ "@types/estree": "^1.0.0",
+ "estree-walker": "^3.0.0",
+ "is-reference": "^3.0.0"
+ }
+ },
"node_modules/picocolors": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.1.tgz",
@@ -7803,6 +8292,11 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/secure-json-parse": {
+ "version": "2.7.0",
+ "resolved": "https://registry.npmjs.org/secure-json-parse/-/secure-json-parse-2.7.0.tgz",
+ "integrity": "sha512-6aU+Rwsezw7VR8/nyvKTx8QpWH9FrcYiXXlqC4z5d5XQBDRqtbfsRjnwGyqbi3gddNtWHuEk9OANUotL26qKUw=="
+ },
"node_modules/semver": {
"version": "7.6.3",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz",
@@ -7950,6 +8444,17 @@
"url": "https://github.com/sponsors/wooorm"
}
},
+ "node_modules/sswr": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/sswr/-/sswr-2.1.0.tgz",
+ "integrity": "sha512-Cqc355SYlTAaUt8iDPaC/4DPPXK925PePLMxyBKuWd5kKc5mwsG3nT9+Mq2tyguL5s7b4Jg+IRMpTRsNTAfpSQ==",
+ "dependencies": {
+ "swrev": "^4.0.0"
+ },
+ "peerDependencies": {
+ "svelte": "^4.0.0 || ^5.0.0-next.0"
+ }
+ },
"node_modules/stack-generator": {
"version": "2.0.10",
"resolved": "https://registry.npmjs.org/stack-generator/-/stack-generator-2.0.10.tgz",
@@ -8370,6 +8875,93 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/svelte": {
+ "version": "4.2.18",
+ "resolved": "https://registry.npmjs.org/svelte/-/svelte-4.2.18.tgz",
+ "integrity": "sha512-d0FdzYIiAePqRJEb90WlJDkjUEx42xhivxN8muUBmfZnP+tzUgz12DJ2hRJi8sIHCME7jeK1PTMgKPSfTd8JrA==",
+ "peer": true,
+ "dependencies": {
+ "@ampproject/remapping": "^2.2.1",
+ "@jridgewell/sourcemap-codec": "^1.4.15",
+ "@jridgewell/trace-mapping": "^0.3.18",
+ "@types/estree": "^1.0.1",
+ "acorn": "^8.9.0",
+ "aria-query": "^5.3.0",
+ "axobject-query": "^4.0.0",
+ "code-red": "^1.0.3",
+ "css-tree": "^2.3.1",
+ "estree-walker": "^3.0.3",
+ "is-reference": "^3.0.1",
+ "locate-character": "^3.0.0",
+ "magic-string": "^0.30.4",
+ "periscopic": "^3.1.0"
+ },
+ "engines": {
+ "node": ">=16"
+ }
+ },
+ "node_modules/svelte/node_modules/aria-query": {
+ "version": "5.3.0",
+ "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.0.tgz",
+ "integrity": "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==",
+ "peer": true,
+ "dependencies": {
+ "dequal": "^2.0.3"
+ }
+ },
+ "node_modules/svelte/node_modules/axobject-query": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz",
+ "integrity": "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==",
+ "peer": true,
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/svelte/node_modules/css-tree": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.3.1.tgz",
+ "integrity": "sha512-6Fv1DV/TYw//QF5IzQdqsNDjx/wc8TrMBZsqjL9eW01tWb7R7k/mq+/VXfJCl7SoD5emsJop9cOByJZfs8hYIw==",
+ "peer": true,
+ "dependencies": {
+ "mdn-data": "2.0.30",
+ "source-map-js": "^1.0.1"
+ },
+ "engines": {
+ "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0"
+ }
+ },
+ "node_modules/svelte/node_modules/mdn-data": {
+ "version": "2.0.30",
+ "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.30.tgz",
+ "integrity": "sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==",
+ "peer": true
+ },
+ "node_modules/swr": {
+ "version": "2.2.5",
+ "resolved": "https://registry.npmjs.org/swr/-/swr-2.2.5.tgz",
+ "integrity": "sha512-QtxqyclFeAsxEUeZIYmsaQ0UjimSq1RZ9Un7I68/0ClKK/U3LoyQunwkQfJZr2fc22DfIXLNDc2wFyTEikCUpg==",
+ "dependencies": {
+ "client-only": "^0.0.1",
+ "use-sync-external-store": "^1.2.0"
+ },
+ "peerDependencies": {
+ "react": "^16.11.0 || ^17.0.0 || ^18.0.0"
+ }
+ },
+ "node_modules/swrev": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/swrev/-/swrev-4.0.0.tgz",
+ "integrity": "sha512-LqVcOHSB4cPGgitD1riJ1Hh4vdmITOp+BkmfmXRh4hSF/t7EnS4iD+SOTmq7w5pPm/SiPeto4ADbKS6dHUDWFA=="
+ },
+ "node_modules/swrv": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/swrv/-/swrv-1.0.4.tgz",
+ "integrity": "sha512-zjEkcP8Ywmj+xOJW3lIT65ciY/4AL4e/Or7Gj0MzU3zBJNMdJiT8geVZhINavnlHRMMCcJLHhraLTAiDOTmQ9g==",
+ "peerDependencies": {
+ "vue": ">=3.2.26 < 4"
+ }
+ },
"node_modules/synckit": {
"version": "0.9.1",
"resolved": "https://registry.npmjs.org/synckit/-/synckit-0.9.1.tgz",
@@ -8501,7 +9093,6 @@
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz",
"integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==",
- "dev": true,
"license": "MIT",
"engines": {
"node": ">=4"
@@ -8865,6 +9456,27 @@
}
}
},
+ "node_modules/vue": {
+ "version": "3.4.38",
+ "resolved": "https://registry.npmjs.org/vue/-/vue-3.4.38.tgz",
+ "integrity": "sha512-f0ZgN+mZ5KFgVv9wz0f4OgVKukoXtS3nwET4c2vLBGQR50aI8G0cqbFtLlX9Yiyg3LFGBitruPHt2PxwTduJEw==",
+ "peer": true,
+ "dependencies": {
+ "@vue/compiler-dom": "3.4.38",
+ "@vue/compiler-sfc": "3.4.38",
+ "@vue/runtime-dom": "3.4.38",
+ "@vue/server-renderer": "3.4.38",
+ "@vue/shared": "3.4.38"
+ },
+ "peerDependencies": {
+ "typescript": "*"
+ },
+ "peerDependenciesMeta": {
+ "typescript": {
+ "optional": true
+ }
+ }
+ },
"node_modules/which": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
@@ -9143,6 +9755,23 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/zod": {
+ "version": "3.23.8",
+ "resolved": "https://registry.npmjs.org/zod/-/zod-3.23.8.tgz",
+ "integrity": "sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==",
+ "peer": true,
+ "funding": {
+ "url": "https://github.com/sponsors/colinhacks"
+ }
+ },
+ "node_modules/zod-to-json-schema": {
+ "version": "3.22.5",
+ "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.22.5.tgz",
+ "integrity": "sha512-+akaPo6a0zpVCCseDed504KBJUQpEW5QZw7RMneNmKw+fGaML1Z9tUNLnHHAC8x6dzVRO1eB2oEMyZRnuBZg7Q==",
+ "peerDependencies": {
+ "zod": "^3.22.4"
+ }
+ },
"node_modules/zustand": {
"version": "4.5.4",
"resolved": "https://registry.npmjs.org/zustand/-/zustand-4.5.4.tgz",
diff --git a/package.json b/package.json
index 5c5ca1ab..5db83dc3 100644
--- a/package.json
+++ b/package.json
@@ -11,6 +11,7 @@
"preview": "vite preview"
},
"dependencies": {
+ "@ai-sdk/openai": "^0.0.51",
"@radix-ui/react-accordion": "^1.2.0",
"@radix-ui/react-checkbox": "^1.1.1",
"@radix-ui/react-collapsible": "^1.1.0",
@@ -30,6 +31,7 @@
"@radix-ui/react-tooltip": "^1.1.2",
"@uidotdev/usehooks": "^2.4.1",
"@xyflow/react": "^12.0.4",
+ "ai": "^3.3.14",
"class-variance-authority": "^0.7.0",
"clsx": "^2.1.1",
"cmdk": "^1.0.0",
diff --git a/src/context/chartdb-context/chartdb-provider.tsx b/src/context/chartdb-context/chartdb-provider.tsx
index 8c5045b1..2c3a0559 100644
--- a/src/context/chartdb-context/chartdb-provider.tsx
+++ b/src/context/chartdb-context/chartdb-provider.tsx
@@ -1,6 +1,7 @@
import React, { useCallback, useMemo, useState } from 'react';
import { DBTable } from '@/lib/domain/db-table';
-import { generateId, randomHSLA } from '@/lib/utils';
+import { generateId } from '@/lib/utils';
+import { randomColor } from '@/lib/colors';
import { ChartDBContext, chartDBContext } from './chartdb-context';
import { DatabaseType } from '@/lib/domain/database-type';
import { DBField } from '@/lib/domain/db-field';
@@ -151,7 +152,7 @@ export const ChartDBProvider: React.FC = ({
},
],
indexes: [],
- color: randomHSLA(),
+ color: randomColor(),
createdAt: Date.now(),
isView: false,
};
@@ -718,11 +719,18 @@ export const ChartDBProvider: React.FC = ({
sourceFieldId,
targetFieldId,
}) => {
- const sourceTableName = getTable(sourceTableId)?.name ?? '';
- const targetTableName = getTable(targetTableId)?.name ?? '';
+ const sourceTable = getTable(sourceTableId);
+ const sourceTableName = sourceTable?.name ?? '';
+
+ const sourceField = sourceTable?.fields.find(
+ (field: { id: string }) => field.id === sourceFieldId
+ );
+
+ const sourceFieldName = sourceField?.name ?? '';
+
const relationship: DBRelationship = {
id: generateId(),
- name: `${sourceTableName}_${targetTableName}_fk`,
+ name: `${sourceTableName}_${sourceFieldName}_fk`,
sourceTableId,
targetTableId,
sourceFieldId,
diff --git a/src/context/dialog-context/dialog-context.tsx b/src/context/dialog-context/dialog-context.tsx
index 7c7ce646..c6c2fe3a 100644
--- a/src/context/dialog-context/dialog-context.tsx
+++ b/src/context/dialog-context/dialog-context.tsx
@@ -1,5 +1,6 @@
import { createContext } from 'react';
import { emptyFn } from '@/lib/utils';
+import { DatabaseType } from '@/lib/domain/database-type';
export interface DialogContext {
// Create diagram dialog
@@ -9,6 +10,10 @@ export interface DialogContext {
// Open diagram dialog
openOpenDiagramDialog: () => void;
closeOpenDiagramDialog: () => void;
+
+ // Export SQL dialog
+ openExportSQLDialog: (params: { targetDatabaseType: DatabaseType }) => void;
+ closeExportSQLDialog: () => void;
}
export const dialogContext = createContext({
@@ -16,4 +21,6 @@ export const dialogContext = createContext({
closeCreateDiagramDialog: emptyFn,
openOpenDiagramDialog: emptyFn,
closeOpenDiagramDialog: emptyFn,
+ openExportSQLDialog: emptyFn,
+ closeExportSQLDialog: emptyFn,
});
diff --git a/src/context/dialog-context/dialog-provider.tsx b/src/context/dialog-context/dialog-provider.tsx
index deb597ad..f059b260 100644
--- a/src/context/dialog-context/dialog-provider.tsx
+++ b/src/context/dialog-context/dialog-provider.tsx
@@ -1,13 +1,28 @@
-import React, { useState } from 'react';
-import { dialogContext } from './dialog-context';
+import React, { useCallback, useState } from 'react';
+import { DialogContext, dialogContext } from './dialog-context';
import { CreateDiagramDialog } from '@/dialogs/create-diagram-dialog/create-diagram-dialog';
import { OpenDiagramDialog } from '@/dialogs/open-diagram-dialog/open-diagram-dialog';
+import { ExportSQLDialog } from '@/dialogs/export-sql-dialog/export-sql-dialog';
+import { DatabaseType } from '@/lib/domain/database-type';
export const DialogProvider: React.FC = ({
children,
}) => {
const [openNewDiagramDialog, setOpenNewDiagramDialog] = useState(false);
const [openOpenDiagramDialog, setOpenOpenDiagramDialog] = useState(false);
+ const [openExportSQLDialog, setOpenExportSQLDialog] = useState(false);
+ const [openExportSQLDialogParams, setOpenExportSQLDialogParams] = useState<{
+ targetDatabaseType: DatabaseType;
+ }>({ targetDatabaseType: DatabaseType.GENERIC });
+
+ const openExportSQLDialogHandler: DialogContext['openExportSQLDialog'] =
+ useCallback(
+ ({ targetDatabaseType }) => {
+ setOpenExportSQLDialog(true);
+ setOpenExportSQLDialogParams({ targetDatabaseType });
+ },
+ [setOpenExportSQLDialog]
+ );
return (
= ({
closeCreateDiagramDialog: () => setOpenNewDiagramDialog(false),
openOpenDiagramDialog: () => setOpenOpenDiagramDialog(true),
closeOpenDiagramDialog: () => setOpenOpenDiagramDialog(false),
+ openExportSQLDialog: openExportSQLDialogHandler,
+ closeExportSQLDialog: () => setOpenExportSQLDialog(false),
}}
>
{children}
+
);
};
diff --git a/src/context/storage-context/storage-context.tsx b/src/context/storage-context/storage-context.tsx
index 807fef57..96e8236d 100644
--- a/src/context/storage-context/storage-context.tsx
+++ b/src/context/storage-context/storage-context.tsx
@@ -12,7 +12,10 @@ export interface StorageContext {
// Diagram operations
addDiagram: (params: { diagram: Diagram }) => Promise;
- listDiagrams: () => Promise;
+ listDiagrams: (options?: {
+ includeTables?: boolean;
+ includeRelationships?: boolean;
+ }) => Promise;
getDiagram: (
id: string,
options?: {
diff --git a/src/context/storage-context/storage-provider.tsx b/src/context/storage-context/storage-provider.tsx
index e323f9d5..7e3efea4 100644
--- a/src/context/storage-context/storage-provider.tsx
+++ b/src/context/storage-context/storage-provider.tsx
@@ -92,10 +92,33 @@ export const StorageProvider: React.FC = ({
await Promise.all(promises);
};
- const listDiagrams: StorageContext['listDiagrams'] = async (): Promise<
- Diagram[]
- > => {
- return await db.diagrams.toArray();
+ const listDiagrams: StorageContext['listDiagrams'] = async (
+ options: {
+ includeTables?: boolean;
+ includeRelationships?: boolean;
+ } = { includeRelationships: false, includeTables: false }
+ ): Promise => {
+ let diagrams = await db.diagrams.toArray();
+
+ if (options.includeTables) {
+ diagrams = await Promise.all(
+ diagrams.map(async (diagram) => {
+ diagram.tables = await listTables(diagram.id);
+ return diagram;
+ })
+ );
+ }
+
+ if (options.includeRelationships) {
+ diagrams = await Promise.all(
+ diagrams.map(async (diagram) => {
+ diagram.relationships = await listRelationships(diagram.id);
+ return diagram;
+ })
+ );
+ }
+
+ return diagrams;
};
const getDiagram: StorageContext['getDiagram'] = async (
@@ -248,10 +271,15 @@ export const StorageProvider: React.FC = ({
const listRelationships: StorageContext['listRelationships'] = async (
diagramId: string
): Promise => {
- return await db.db_relationships
- .where('diagramId')
- .equals(diagramId)
- .toArray();
+ // Sort relationships alphabetically
+ return await (
+ await db.db_relationships
+ .where('diagramId')
+ .equals(diagramId)
+ .toArray()
+ ).sort((a, b) => {
+ return a.name.localeCompare(b.name);
+ });
};
return (
diff --git a/src/dialogs/export-sql-dialog/export-sql-dialog.tsx b/src/dialogs/export-sql-dialog/export-sql-dialog.tsx
new file mode 100644
index 00000000..ac0f3c35
--- /dev/null
+++ b/src/dialogs/export-sql-dialog/export-sql-dialog.tsx
@@ -0,0 +1,107 @@
+import { Button } from '@/components/button/button';
+import { CodeSnippet } from '@/components/code-snippet/code-snippet';
+import {
+ Dialog,
+ DialogClose,
+ DialogContent,
+ DialogDescription,
+ DialogFooter,
+ DialogHeader,
+ DialogTitle,
+} from '@/components/dialog/dialog';
+import { Label } from '@/components/label/label';
+import { Spinner } from '@/components/spinner/spinner';
+import { useChartDB } from '@/hooks/use-chartdb';
+import { useDialog } from '@/hooks/use-dialog';
+import {
+ exportBaseSQL,
+ exportSQL,
+} from '@/lib/data/export-metadata/export-sql-script';
+import { databaseTypeToLabelMap } from '@/lib/databases';
+import { DatabaseType } from '@/lib/domain/database-type';
+import { DialogProps } from '@radix-ui/react-dialog';
+import { WandSparkles } from 'lucide-react';
+import React, { useCallback, useEffect } from 'react';
+
+export interface ExportSQLDialogProps {
+ dialog: DialogProps;
+ targetDatabaseType: DatabaseType;
+}
+
+export const ExportSQLDialog: React.FC = ({
+ dialog,
+ targetDatabaseType,
+}) => {
+ const { closeExportSQLDialog } = useDialog();
+ const { currentDiagram } = useChartDB();
+ const [script, setScript] = React.useState();
+
+ const exportSQLScript = useCallback(async () => {
+ if (targetDatabaseType === DatabaseType.GENERIC) {
+ return Promise.resolve(exportBaseSQL(currentDiagram));
+ } else {
+ return exportSQL(currentDiagram, targetDatabaseType);
+ }
+ }, [targetDatabaseType, currentDiagram]);
+
+ useEffect(() => {
+ setScript(undefined);
+ const fetchScript = async () => {
+ const script = await exportSQLScript();
+ setScript(script);
+ };
+ fetchScript();
+ }, [dialog.open, setScript, exportSQLScript]);
+
+ const renderLoader = useCallback(
+ () => (
+
+
+
+
+
+
+
+ ),
+ [targetDatabaseType]
+ );
+ return (
+
+ );
+};
diff --git a/src/dialogs/open-diagram-dialog/open-diagram-dialog.tsx b/src/dialogs/open-diagram-dialog/open-diagram-dialog.tsx
index 3adbb759..889e2b38 100644
--- a/src/dialogs/open-diagram-dialog/open-diagram-dialog.tsx
+++ b/src/dialogs/open-diagram-dialog/open-diagram-dialog.tsx
@@ -46,7 +46,7 @@ export const OpenDiagramDialog: React.FC = ({
useEffect(() => {
const fetchDiagrams = async () => {
- const diagrams = await listDiagrams();
+ const diagrams = await listDiagrams({ includeTables: true });
setDiagrams(
diagrams.sort(
(a, b) => b.updatedAt.getTime() - a.updatedAt.getTime()
@@ -87,6 +87,7 @@ export const OpenDiagramDialog: React.FC = ({
Name
Created at
Last modified
+ Tables count
Type
@@ -120,6 +121,9 @@ export const OpenDiagramDialog: React.FC = ({
{diagram.updatedAt.toLocaleString()}
+
+ {diagram.tables?.length}
+
{
databaseTypeToLabelMap[
diff --git a/src/lib/colors.ts b/src/lib/colors.ts
new file mode 100644
index 00000000..7d42e4d3
--- /dev/null
+++ b/src/lib/colors.ts
@@ -0,0 +1,19 @@
+export const randomColor = () => {
+ const colors = [
+ '#ff6363', // A brighter red.
+ '#ff6b8a', // A vibrant pink.
+ '#c05dcf', // A rich purple.
+ '#b067e9', // A lighter purple.
+ '#8a61f5', // A bold indigo.
+ '#7175fa', // A lighter indigo.
+ '#8eb7ff', // A sky blue.
+ '#42e0c0', // A fresh aqua.
+ '#4dee8a', // A mint green.
+ '#9ef07a', // A lime green.
+ '#ffe374', // A warm yellow.
+ '#ff9f74', // A peachy orange.
+ ];
+ return colors[Math.floor(Math.random() * colors.length)];
+};
+
+export const greyColor = '#b0b0b0'; // A Cloudy Grey.
diff --git a/src/lib/data/data-types.ts b/src/lib/data/data-types.ts
index 04fc3d4b..7da70fb8 100644
--- a/src/lib/data/data-types.ts
+++ b/src/lib/data/data-types.ts
@@ -295,6 +295,12 @@ export const sqliteDataTypes = [
// Date/Time Types (SQLite uses TEXT, REAL, or INTEGER types for dates and times)
'date',
'datetime',
+
+ 'int',
+ 'float',
+ 'boolean',
+ 'varchar',
+ 'decimal',
] as const;
export const dataTypes = [
diff --git a/src/lib/data/export-metadata/export-sql-script.ts b/src/lib/data/export-metadata/export-sql-script.ts
index f1f21ff5..999ce3dd 100644
--- a/src/lib/data/export-metadata/export-sql-script.ts
+++ b/src/lib/data/export-metadata/export-sql-script.ts
@@ -1,6 +1,14 @@
-import { Diagram } from "../domain/diagram";
+import { generateText } from 'ai';
+import { createOpenAI } from '@ai-sdk/openai';
+import { Diagram } from '../../domain/diagram';
+import { OPENAI_API_KEY } from '@/lib/env';
+import { DatabaseType } from '@/lib/domain/database-type';
-export const exportSQL = (diagram: Diagram): string => {
+const openai = createOpenAI({
+ apiKey: OPENAI_API_KEY,
+});
+
+export const exportBaseSQL = (diagram: Diagram): string => {
const { tables, relationships } = diagram;
if (!tables || tables.length === 0) {
@@ -8,26 +16,48 @@ export const exportSQL = (diagram: Diagram): string => {
}
// Filter out the tables that are views
- const nonViewTables = tables.filter(table => !table.isView);
+ const nonViewTables = tables.filter((table) => !table.isView);
+
+ // Align the data types based on foreign key relationships
+ alignForeignKeyDataTypes(diagram);
// Initialize the SQL script string
let sqlScript = '';
// Loop through each non-view table to generate the SQL statements
- nonViewTables.forEach(table => {
+ nonViewTables.forEach((table) => {
sqlScript += `CREATE TABLE ${table.name} (\n`;
table.fields.forEach((field, index) => {
sqlScript += ` ${field.name} ${field.type}`;
- if (field.primaryKey) {
- sqlScript += ' PRIMARY KEY';
+ // Add size for character types
+ if (field.characterMaximumLength) {
+ sqlScript += `(${field.characterMaximumLength})`;
}
+ // Add precision and scale for numeric types
+ if (field.precision !== null && field.scale !== null) {
+ sqlScript += `(${field.precision}, ${field.scale})`;
+ } else if (field.precision !== null) {
+ sqlScript += `(${field.precision})`;
+ }
+
+ // Handle NOT NULL constraint
if (!field.nullable) {
sqlScript += ' NOT NULL';
}
+ // Handle DEFAULT value
+ if (field.default) {
+ sqlScript += ` DEFAULT ${field.default}`;
+ }
+
+ // Handle PRIMARY KEY constraint
+ if (field.primaryKey) {
+ sqlScript += ' PRIMARY KEY';
+ }
+
// Add a comma after each field except the last one
if (index < table.fields.length - 1) {
sqlScript += ',\n';
@@ -35,17 +65,230 @@ export const exportSQL = (diagram: Diagram): string => {
});
sqlScript += '\n);\n\n';
+
+ // Generate SQL for indexes
+ table.indexes.forEach((index) => {
+ const fieldNames = index.fieldIds
+ .map(
+ (fieldId) =>
+ table.fields.find((field) => field.id === fieldId)?.name
+ )
+ .filter(Boolean)
+ .join(', ');
+
+ if (fieldNames) {
+ sqlScript += `CREATE ${index.unique ? 'UNIQUE ' : ''}INDEX ${index.name} ON ${table.name} (${fieldNames});\n`;
+ }
+ });
+
+ sqlScript += '\n';
});
- // Handle relationships (foreign keys) if needed
- relationships?.forEach(relationship => {
- const sourceTable = nonViewTables.find(table => table.id === relationship.sourceTableId);
- const targetTable = nonViewTables.find(table => table.id === relationship.targetTableId);
+ // Handle relationships (foreign keys)
+ relationships?.forEach((relationship) => {
+ const sourceTable = nonViewTables.find(
+ (table) => table.id === relationship.sourceTableId
+ );
+ const targetTable = nonViewTables.find(
+ (table) => table.id === relationship.targetTableId
+ );
- if (sourceTable && targetTable) {
- sqlScript += `ALTER TABLE ${sourceTable.name} ADD CONSTRAINT ${relationship.name} FOREIGN KEY (${relationship.sourceFieldId}) REFERENCES ${targetTable.name} (${relationship.targetFieldId});\n`;
+ const sourceTableField = sourceTable?.fields.find(
+ (field) => field.id === relationship.sourceFieldId
+ );
+ const targetTableField = targetTable?.fields.find(
+ (field) => field.id === relationship.targetFieldId
+ );
+
+ if (
+ sourceTable &&
+ targetTable &&
+ sourceTableField &&
+ targetTableField
+ ) {
+ sqlScript += `ALTER TABLE ${sourceTable.name} ADD CONSTRAINT ${relationship.name} FOREIGN KEY (${sourceTableField.name}) REFERENCES ${targetTable.name} (${targetTableField.name});\n`;
}
});
return sqlScript;
};
+
+export const exportSQL = async (
+ diagram: Diagram,
+ databaseType: DatabaseType
+): Promise => {
+ const sqlScript = exportBaseSQL(diagram);
+ const prompt = generateSQLPrompt(databaseType, sqlScript);
+
+ const { text } = await generateText({
+ model: openai('gpt-4o-mini-2024-07-18'),
+ prompt: prompt,
+ });
+
+ return text;
+};
+
+function getMySQLDataTypeSize(type: string) {
+ return (
+ {
+ tinyint: 1,
+ smallint: 2,
+ mediumint: 3,
+ integer: 4,
+ bigint: 8,
+ float: 4,
+ double: 8,
+ decimal: 16,
+ numeric: 16,
+ // Add other relevant data types if needed
+ }[type.toLowerCase()] || 0
+ );
+}
+
+function alignForeignKeyDataTypes(diagram: Diagram) {
+ const { tables, relationships } = diagram;
+
+ if (
+ !tables ||
+ tables.length === 0 ||
+ !relationships ||
+ relationships.length === 0
+ ) {
+ return;
+ }
+
+ // Convert tables to a map for quick lookup
+ const tableMap = new Map();
+ tables.forEach((table) => {
+ tableMap.set(table.id, table);
+ });
+
+ // Iterate through each relationship to update the child table column data types
+ relationships.forEach((relationship) => {
+ const { sourceTableId, sourceFieldId, targetTableId, targetFieldId } =
+ relationship;
+
+ const sourceTable = tableMap.get(sourceTableId);
+ const targetTable = tableMap.get(targetTableId);
+
+ if (sourceTable && targetTable) {
+ const sourceField = sourceTable.fields.find(
+ (field: { id: string }) => field.id === sourceFieldId
+ );
+ const targetField = targetTable.fields.find(
+ (field: { id: string }) => field.id === targetFieldId
+ );
+
+ if (sourceField && targetField) {
+ const sourceSize = getMySQLDataTypeSize(sourceField.type);
+ const targetSize = getMySQLDataTypeSize(targetField.type);
+
+ if (sourceSize > targetSize) {
+ // Print a message indicating that the data type is being adjusted
+ console.log(
+ `Adjusting data type of '${targetTable.name}.${targetField.name}' from '${targetField.type}' to '${sourceField.type}' to match '${sourceTable.name}.${sourceField.name}'`
+ );
+
+ // Adjust the child field data type to the larger data type
+ targetField.type = sourceField.type;
+ } else if (targetSize > sourceSize) {
+ // Print a message indicating that the data type is being adjusted
+ console.log(
+ `Adjusting data type of '${targetTable.name}.${sourceField.name}' from '${sourceField.type}' to '${targetField.type}' to match '${targetTable.name}.${targetField.name}' ('${targetField.type}')`
+ );
+
+ // Adjust the child field data type to the larger data type
+ sourceField.type = targetField.type;
+ } else {
+ console.log(
+ `same data type '${targetField.type}' = '${sourceField.type}'.`
+ );
+ }
+ }
+ }
+ });
+}
+
+const generateSQLPrompt = (databaseType: string, sqlScript: string) => {
+ const basePrompt = `
+ You are generating SQL scripts for creating database tables and sequences, handling primary keys, indices, and other table attributes.
+ The following instructions will guide you in optimizing the scripts for the ${databaseType} dialect:
+ - **Column Names**: Do **not** modify the names of columns. Ensure that all column names in the generated SQL script are exactly as provided in the input schema. If the input specifies a column name, it must appear in the output script unchanged.
+ - **Column Name Conflicts**: When a column name conflicts with a data type or reserved keyword (e.g., fulltext), escape the column name by enclosing it.
+ `;
+
+ const dialectInstructions =
+ {
+ POSTGRESQL: `
+ - **Sequence Creation**: Use \`CREATE SEQUENCE IF NOT EXISTS\` for sequence creation.
+ - **Table and Index Creation**: Use \`CREATE TABLE IF NOT EXISTS\` and \`CREATE INDEX IF NOT EXISTS\` to avoid errors if the object already exists.
+ - **Serial and Identity Columns**: For auto-increment columns, use \`SERIAL\` or \`GENERATED BY DEFAULT AS IDENTITY\`.
+ - **Conditional Statements**: Utilize PostgreSQL’s support for \`IF NOT EXISTS\` in relevant \`CREATE\` statements.
+ `,
+ MYSQL: `
+ - **Table Creation**: Use \`CREATE TABLE IF NOT EXISTS\` for creating tables. While creating the table structure, ensure that all foreign key columns use the correct data types as determined in the foreign key review.
+ - **Auto-Increment**: Use \`AUTO_INCREMENT\` for auto-incrementing primary key columns.
+ - **Index Creation**: Place all \`CREATE INDEX\` statements separately after the \`CREATE TABLE\` statement. Avoid using \`IF NOT EXISTS\` in \`CREATE INDEX\` statements.
+ - **Indexing TEXT/BLOB Columns**: Do **not** create regular indexes on \`TEXT\` or \`BLOB\` columns. If indexing these types is required, use \`FULLTEXT\` indexes specifically for \`TEXT\` columns where appropriate, or consider alternative strategies.
+ - **Date Column Defaults**: Avoid using \`CURRENT_DATE\` as a default for \`DATE\` columns. Instead, consider using \`DEFAULT NULL\` or handle default values programmatically.
+ - **Timestamp Default Value**: Use \`DEFAULT CURRENT_TIMESTAMP\` for \`TIMESTAMP\` columns. Only one \`TIMESTAMP\` column can have \`CURRENT_TIMESTAMP\` as the default without specifying \`ON UPDATE\`.
+ - **Boolean Columns**: Use \`TINYINT(1)\` instead of \`BOOLEAN\` for better compatibility with MySQL/MariaDB versions that might not fully support the \`BOOLEAN\` data type.
+ - **TEXT and BLOB Constraints**: Do not use \`NOT NULL\` with \`TEXT\` or \`BLOB\` columns, as these types do not support the \`NOT NULL\` constraint in MariaDB.
+ - **ENUM Data Type**: Ensure that default values are compatible and that the \`ENUM\` declaration adheres to MariaDB's syntax requirements.
+ - **Default Values**: Ensure that default values for columns, especially \`DECIMAL\` and \`ENUM\`, are correctly formatted and comply with MariaDB's SQL syntax.
+ - **Sequences**: Recognize that MySQL does not natively support sequences. Use \`AUTO_INCREMENT\` instead.
+
+ **Reminder**: Ensure all column names that conflict with reserved keywords or data types (like \`fulltext\`) are escaped using backticks (\`).
+ `,
+ SQL_SERVER: `
+ - **Sequence Creation**: Use \`CREATE SEQUENCE\` without \`IF NOT EXISTS\`, and employ conditional logic (\`IF NOT EXISTS\`) to check for sequence existence before creation.
+ - **Identity Columns**: Always prefer using the \`IDENTITY\` keyword (e.g., \`INT IDENTITY(1,1)\`) for auto-incrementing primary key columns when possible.
+ - **Conditional Logic**: Use a conditional block like \`IF NOT EXISTS (SELECT * FROM sys.objects WHERE ...)\` since SQL Server doesn’t support \`IF NOT EXISTS\` directly in \`CREATE\` statements.
+ - **Avoid Unsupported Syntax**: Ensure the script does not include unsupported statements like \`CREATE TABLE IF NOT EXISTS\`.
+ `,
+ MARIADB: `
+ - **Table Creation**: Use \`CREATE TABLE IF NOT EXISTS\` for creating tables. While creating the table structure, ensure that all foreign key columns use the correct data types as determined in the foreign key review.
+ - **Auto-Increment**: Use \`AUTO_INCREMENT\` for auto-incrementing primary key columns.
+ - **Index Creation**: Place all \`CREATE INDEX\` statements separately after the \`CREATE TABLE\` statement. Avoid using \`IF NOT EXISTS\` in \`CREATE INDEX\` statements.
+ - **Indexing TEXT/BLOB Columns**: Do **not** create regular indexes on \`TEXT\` or \`BLOB\` columns. If indexing these types is required, use \`FULLTEXT\` indexes specifically for \`TEXT\` columns where appropriate, or consider alternative strategies.
+ - **Date Column Defaults**: Avoid using \`CURRENT_DATE\` as a default for \`DATE\` columns. Instead, consider using \`DEFAULT NULL\` or handle default values programmatically.
+ - **Timestamp Default Value**: Use \`DEFAULT CURRENT_TIMESTAMP\` for \`TIMESTAMP\` columns. Only one \`TIMESTAMP\` column can have \`CURRENT_TIMESTAMP\` as the default without specifying \`ON UPDATE\`.
+ - **Boolean Columns**: Use \`TINYINT(1)\` instead of \`BOOLEAN\` for better compatibility with MySQL/MariaDB versions that might not fully support the \`BOOLEAN\` data type.
+ - **TEXT and BLOB Constraints**: Do not use \`NOT NULL\` with \`TEXT\` or \`BLOB\` columns, as these types do not support the \`NOT NULL\` constraint in MariaDB.
+ - **ENUM Data Type**: Ensure that default values are compatible and that the \`ENUM\` declaration adheres to MariaDB's syntax requirements.
+ - **Default Values**: Ensure that default values for columns, especially \`DECIMAL\` and \`ENUM\`, are correctly formatted and comply with MariaDB's SQL syntax.
+ - **Sequences**: Recognize that MySQL does not natively support sequences. Use \`AUTO_INCREMENT\` instead.
+
+ **Reminder**: Ensure all column names that conflict with reserved keywords or data types (like \`fulltext\`) are escaped using backticks (\`).
+ `,
+ SQLITE: `
+ - **Table Creation**: Use \`CREATE TABLE IF NOT EXISTS\`.
+ - **Auto-Increment**: Use \`AUTOINCREMENT\` with \`INTEGER PRIMARY KEY\` for auto-increment functionality.
+ - **No Sequence Support**: SQLite does not support sequences; rely solely on \`AUTOINCREMENT\` for similar functionality.
+ - **Foreign Key Constraints**: Do not use \`ALTER TABLE\` to add foreign key constraints. SQLite does not support adding foreign keys to an existing table after it has been created. Always define foreign key constraints during the \`CREATE TABLE\` statement. Avoid using named constraints in foreign key definitions.
+ - **Adding Foreign Keys to Existing Tables**: If adding a foreign key to an existing table is required, suggest creating a new table with the foreign key constraint, migrating the data, and renaming the new table to the original name.
+ - **General SQLite Constraints**: Remember, \`ALTER TABLE\` in SQLite is limited and cannot add constraints after the table is created.
+ - **Conditional Logic**: Ensure the script uses SQLite-compatible syntax and does not include unsupported features.
+ `,
+ }[databaseType] || '';
+
+ const additionalInstructions = `
+ Just answer with the script with no additional details. give the commands flat without markdown.
+
+ No images are allowed. Do not try to generate or link images, including base64 data URLs.
+
+ Feel free to suggest corrections for suspected typos.
+ `;
+
+ return `${basePrompt}\n${dialectInstructions}\n
+ - **Validation**: After generating the script, validate it against the respective SQL dialect by attempting to execute it in a corresponding database environment.
+ - **Syntax Checking**: Use SQL linting tools specific to each dialect to ensure the script is free from syntax errors.
+ - **Manual Review**: Include a step where a knowledgeable developer reviews the generated script to ensure it meets the required specifications and adheres to best practices.
+
+ Here is the SQL script that needs to be optimized or generated according to the instructions above:
+
+ ${sqlScript}
+
+ ${additionalInstructions}
+ `;
+};
diff --git a/src/lib/data/import-metadata/metadata-types/column-info.ts b/src/lib/data/import-metadata/metadata-types/column-info.ts
index 55f1ff14..d8962605 100644
--- a/src/lib/data/import-metadata/metadata-types/column-info.ts
+++ b/src/lib/data/import-metadata/metadata-types/column-info.ts
@@ -5,5 +5,11 @@ export interface ColumnInfo {
type: string;
ordinal_position: number;
nullable: boolean;
- collation: string;
+ character_maximum_length?: string | null; // The maximum length of the column (if applicable), nullable
+ precision?: {
+ precision: number | null; // The precision for numeric types
+ scale: number | null; // The scale for numeric types
+ } | null; // Nullable, not all types have precision
+ default?: string | null; // Default value for the column, nullable
+ collation?: string | null;
}
diff --git a/src/lib/data/import-metadata/metadata-types/database-metadata.ts b/src/lib/data/import-metadata/metadata-types/database-metadata.ts
index 0f670b5f..8a4307ff 100644
--- a/src/lib/data/import-metadata/metadata-types/database-metadata.ts
+++ b/src/lib/data/import-metadata/metadata-types/database-metadata.ts
@@ -11,7 +11,7 @@ export interface DatabaseMetadata {
indexes: IndexInfo[];
tables: TableInfo[];
views: ViewInfo[];
- server_name: string;
+ database_name: string;
version: string;
}
diff --git a/src/lib/domain/db-field.ts b/src/lib/domain/db-field.ts
index 996d3a97..d575b5e9 100644
--- a/src/lib/domain/db-field.ts
+++ b/src/lib/domain/db-field.ts
@@ -8,6 +8,11 @@ export interface DBField {
unique: boolean;
nullable: boolean;
createdAt: number;
+ characterMaximumLength?: string;
+ precision?: number;
+ scale?: number;
+ default?: string;
+ collation?: string;
}
export type FieldType = (typeof dataTypes)[number];
diff --git a/src/lib/domain/db-table.ts b/src/lib/domain/db-table.ts
index 39fe98c4..aef17171 100644
--- a/src/lib/domain/db-table.ts
+++ b/src/lib/domain/db-table.ts
@@ -3,10 +3,11 @@ import { DBField, FieldType } from './db-field';
import { TableInfo } from '../data/import-metadata/metadata-types/table-info';
import { ColumnInfo } from '../data/import-metadata/metadata-types/column-info';
import { IndexInfo } from '../data/import-metadata/metadata-types/index-info';
-import { generateId, greyColor, randomHSLA } from '@/lib/utils';
+import { greyColor, randomColor } from '@/lib/colors';
import { DBRelationship } from './db-relationship';
import { PrimaryKeyInfo } from '../data/import-metadata/metadata-types/primary-key-info';
import { ViewInfo } from '../data/import-metadata/metadata-types/view-info';
+import { generateId } from '../utils';
export interface DBTable {
id: string;
@@ -38,7 +39,11 @@ export const createTablesFromMetadata = ({
// Filter, make unique, and sort columns based on ordinal_position
const uniqueColumns = new Map();
columns
- .filter((col) => col.table === tableInfo.table)
+ .filter(
+ (col) =>
+ col.schema === tableInfo.schema &&
+ col.table === tableInfo.table
+ )
.forEach((col) => {
if (!uniqueColumns.has(col.name)) {
uniqueColumns.set(col.name, col);
@@ -46,9 +51,7 @@ export const createTablesFromMetadata = ({
});
const sortedColumns = Array.from(uniqueColumns.values()).sort(
- (a, b) => {
- return a.ordinal_position - b.ordinal_position;
- }
+ (a, b) => a.ordinal_position - b.ordinal_position
);
const tablePrimaryKeys = primaryKeys.filter(
@@ -71,6 +74,16 @@ export const createTablesFromMetadata = ({
(idx) => idx.column === col.name && idx.unique
),
nullable: col.nullable,
+ ...(col.character_maximum_length &&
+ col.character_maximum_length !== 'null'
+ ? { character_maximum_length: col.character_maximum_length }
+ : {}),
+ ...(col.precision?.precision
+ ? { precision: col.precision.precision }
+ : {}),
+ ...(col.precision?.scale ? { scale: col.precision.scale } : {}),
+ ...(col.default ? { default: col.default } : {}),
+ ...(col.collation ? { collation: col.collation } : {}),
createdAt: Date.now(),
})
);
@@ -102,7 +115,7 @@ export const createTablesFromMetadata = ({
y: Math.random() * 800, // Placeholder Y
fields,
indexes: dbIndexes,
- color: isView ? greyColor : randomHSLA(),
+ color: isView ? greyColor : randomColor(),
isView: isView,
createdAt: Date.now(),
};
diff --git a/src/lib/domain/diagram.ts b/src/lib/domain/diagram.ts
index 8b664d61..75d0088f 100644
--- a/src/lib/domain/diagram.ts
+++ b/src/lib/domain/diagram.ts
@@ -67,7 +67,9 @@ export const loadFromDatabaseMetadata = ({
return {
id: generateId(),
- name: databaseMetadata.server_name || `Diagram ${diagramNumber}`,
+ name:
+ `${databaseMetadata.database_name}-db` ||
+ `Diagram ${diagramNumber}`,
databaseType: databaseType ?? DatabaseType.GENERIC,
tables: sortedTables,
relationships,
diff --git a/src/lib/env.ts b/src/lib/env.ts
new file mode 100644
index 00000000..c5267248
--- /dev/null
+++ b/src/lib/env.ts
@@ -0,0 +1 @@
+export const OPENAI_API_KEY = import.meta.env.VITE_OPENAI_API_KEY;
diff --git a/src/lib/utils.ts b/src/lib/utils.ts
index 598960d2..5c9a660e 100644
--- a/src/lib/utils.ts
+++ b/src/lib/utils.ts
@@ -2,7 +2,6 @@ import { type ClassValue, clsx } from 'clsx';
import { customAlphabet } from 'nanoid';
const randomId = customAlphabet('0123456789abcdefghijklmnopqrstuvwxyz', 25);
-const randonNumber = customAlphabet('1234567890', 18);
import { twMerge } from 'tailwind-merge';
@@ -10,16 +9,6 @@ export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
}
-export const convertToDecimal = (number: number) => {
- const digits = number.toString().length; // Get the number of digits
- return number / Math.pow(10, digits); // Divide the number by 10^digits
-};
-
-export const randomHSLA = () =>
- `hsla(${~~(360 * convertToDecimal(parseInt(randonNumber())))}, 70%, 72%, 0.8)`;
-
-export const greyColor = 'hsla(0, 0%, 65%, 1)';
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export const emptyFn = (): any => undefined;
diff --git a/src/pages/editor-page/top-navbar/top-navbar.tsx b/src/pages/editor-page/top-navbar/top-navbar.tsx
index b5b2bfe7..8858d42f 100644
--- a/src/pages/editor-page/top-navbar/top-navbar.tsx
+++ b/src/pages/editor-page/top-navbar/top-navbar.tsx
@@ -31,12 +31,17 @@ import {
databaseSecondaryLogoMap,
databaseTypeToLabelMap,
} from '@/lib/databases';
+import { DatabaseType } from '@/lib/domain/database-type';
export interface TopNavbarProps {}
export const TopNavbar: React.FC = () => {
const { diagramName, updateDiagramName, currentDiagram } = useChartDB();
- const { openCreateDiagramDialog, openOpenDiagramDialog } = useDialog();
+ const {
+ openCreateDiagramDialog,
+ openOpenDiagramDialog,
+ openExportSQLDialog,
+ } = useDialog();
const [editMode, setEditMode] = useState(false);
const { exportImage } = useExportImage();
const [editedDiagramName, setEditedDiagramName] =
@@ -74,11 +79,6 @@ export const TopNavbar: React.FC = () => {
setEditMode(true);
};
- const exportSql = useCallback(() => {
- console.log('Export SQL');
- console.log({ currentDiagram });
- }, [currentDiagram]);
-
const exportPNG = useCallback(() => {
exportImage('png');
}, [exportImage]);
@@ -95,6 +95,13 @@ export const TopNavbar: React.FC = () => {
window.open('https://chartdb.io', '_blank');
}, []);
+ const openJoinSlack = useCallback(() => {
+ window.open(
+ 'https://join.slack.com/t/chartdb/shared_invite/zt-2ourrlh5e-mKIHCRML3_~m_gHjD5EcUg',
+ '_blank'
+ );
+ }, []);
+
return (