add export AI script functionality

This commit is contained in:
johnnyfish
2024-08-21 23:42:16 +03:00
committed by Jonathan Fishner
parent 15404fa113
commit e380200a75
21 changed files with 1241 additions and 65 deletions

View File

@@ -5,6 +5,7 @@
<p align="center"> <p align="center">
<b>Open-source database diagrams editor</b> <br /> <b>Open-source database diagrams editor</b> <br />
<b>No installations • No Database password required.</b> <br />
</p> </p>
<h3 align="center"> <h3 align="center">

643
package-lock.json generated
View File

@@ -8,6 +8,7 @@
"name": "chartdb", "name": "chartdb",
"version": "0.0.0", "version": "0.0.0",
"dependencies": { "dependencies": {
"@ai-sdk/openai": "^0.0.51",
"@radix-ui/react-accordion": "^1.2.0", "@radix-ui/react-accordion": "^1.2.0",
"@radix-ui/react-checkbox": "^1.1.1", "@radix-ui/react-checkbox": "^1.1.1",
"@radix-ui/react-collapsible": "^1.1.0", "@radix-ui/react-collapsible": "^1.1.0",
@@ -27,6 +28,7 @@
"@radix-ui/react-tooltip": "^1.1.2", "@radix-ui/react-tooltip": "^1.1.2",
"@uidotdev/usehooks": "^2.4.1", "@uidotdev/usehooks": "^2.4.1",
"@xyflow/react": "^12.0.4", "@xyflow/react": "^12.0.4",
"ai": "^3.3.14",
"class-variance-authority": "^0.7.0", "class-variance-authority": "^0.7.0",
"clsx": "^2.1.1", "clsx": "^2.1.1",
"cmdk": "^1.0.0", "cmdk": "^1.0.0",
@@ -69,6 +71,181 @@
"vite": "^5.3.4" "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": { "node_modules/@alloc/quick-lru": {
"version": "5.2.0", "version": "5.2.0",
"resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz", "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz",
@@ -85,7 +262,6 @@
"version": "2.3.0", "version": "2.3.0",
"resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz",
"integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==",
"dev": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/gen-mapping": "^0.3.5",
@@ -264,7 +440,6 @@
"version": "7.24.8", "version": "7.24.8",
"resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.24.8.tgz", "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.24.8.tgz",
"integrity": "sha512-pO9KhhRcuUyGnJWwyEgnRJTSIZHiT+vMD0kPeD+so0l7mxkMT19g3pjY9GTnHySck/hDzq+dtW/4VgnMkippsQ==", "integrity": "sha512-pO9KhhRcuUyGnJWwyEgnRJTSIZHiT+vMD0kPeD+so0l7mxkMT19g3pjY9GTnHySck/hDzq+dtW/4VgnMkippsQ==",
"dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">=6.9.0" "node": ">=6.9.0"
@@ -274,7 +449,6 @@
"version": "7.24.7", "version": "7.24.7",
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.7.tgz", "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.7.tgz",
"integrity": "sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w==", "integrity": "sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w==",
"dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">=6.9.0" "node": ">=6.9.0"
@@ -324,7 +498,6 @@
"version": "7.25.3", "version": "7.25.3",
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.25.3.tgz", "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.25.3.tgz",
"integrity": "sha512-iLTJKDbJ4hMvFPgQwwsVoxtHyWpKKPBrxkANrSYewDPaPpT5py5yeVkgPIJ7XYXhndxJpaA3PyALSXQ7u8e/Dw==", "integrity": "sha512-iLTJKDbJ4hMvFPgQwwsVoxtHyWpKKPBrxkANrSYewDPaPpT5py5yeVkgPIJ7XYXhndxJpaA3PyALSXQ7u8e/Dw==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@babel/types": "^7.25.2" "@babel/types": "^7.25.2"
@@ -418,7 +591,6 @@
"version": "7.25.2", "version": "7.25.2",
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.25.2.tgz", "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.25.2.tgz",
"integrity": "sha512-YTnYtra7W9e6/oAZEHj0bJehPRUlLH9/fbpT5LfB0NhQXyALCRkRs3zH9v07IYhkgpqX6Z78FnuccZr/l4Fs4Q==", "integrity": "sha512-YTnYtra7W9e6/oAZEHj0bJehPRUlLH9/fbpT5LfB0NhQXyALCRkRs3zH9v07IYhkgpqX6Z78FnuccZr/l4Fs4Q==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@babel/helper-string-parser": "^7.24.8", "@babel/helper-string-parser": "^7.24.8",
@@ -1156,6 +1328,14 @@
"node": ">= 8" "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": { "node_modules/@pkgjs/parseargs": {
"version": "0.11.0", "version": "0.11.0",
"resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz",
@@ -2511,11 +2691,15 @@
"@types/d3-selection": "*" "@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": { "node_modules/@types/estree": {
"version": "1.0.5", "version": "1.0.5",
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz",
"integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==", "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==",
"dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/@types/hast": { "node_modules/@types/hast": {
@@ -2813,6 +2997,118 @@
"vite": "^4.2.0 || ^5.0.0" "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": { "node_modules/@xobotyi/scrollbar-width": {
"version": "1.9.5", "version": "1.9.5",
"resolved": "https://registry.npmjs.org/@xobotyi/scrollbar-width/-/scrollbar-width-1.9.5.tgz", "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" "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": { "node_modules/ajv": {
"version": "6.12.6", "version": "6.12.6",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
@@ -3449,6 +3810,11 @@
"integrity": "sha512-JhZUT7JFcQy/EzW605k/ktHtncoo9vnyW/2GspNYwFlN1C/WmjuV/xtS04e9SOkL2sTdw0VAZ2UGCcQ9lR6p6w==", "integrity": "sha512-JhZUT7JFcQy/EzW605k/ktHtncoo9vnyW/2GspNYwFlN1C/WmjuV/xtS04e9SOkL2sTdw0VAZ2UGCcQ9lR6p6w==",
"license": "MIT" "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": { "node_modules/clsx": {
"version": "2.1.1", "version": "2.1.1",
"resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", "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": { "node_modules/color-convert": {
"version": "1.9.3", "version": "1.9.3",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
@@ -4227,6 +4606,15 @@
"url": "https://github.com/sponsors/ljharb" "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": { "node_modules/detect-node-es": {
"version": "1.1.0", "version": "1.1.0",
"resolved": "https://registry.npmjs.org/detect-node-es/-/detect-node-es-1.1.0.tgz", "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==", "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==",
"license": "Apache-2.0" "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": { "node_modules/dir-glob": {
"version": "3.0.1", "version": "3.0.1",
"resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz",
@@ -4294,6 +4687,18 @@
"integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==",
"license": "MIT" "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": { "node_modules/error-stack-parser": {
"version": "2.1.4", "version": "2.1.4",
"resolved": "https://registry.npmjs.org/error-stack-parser/-/error-stack-parser-2.1.4.tgz", "resolved": "https://registry.npmjs.org/error-stack-parser/-/error-stack-parser-2.1.4.tgz",
@@ -5207,6 +5612,15 @@
"node": ">=4.0" "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": { "node_modules/esutils": {
"version": "2.0.3", "version": "2.0.3",
"resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
@@ -5216,6 +5630,14 @@
"node": ">=0.10.0" "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": { "node_modules/fast-deep-equal": {
"version": "3.1.3", "version": "3.1.3",
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
@@ -6159,6 +6581,15 @@
"node": ">=8" "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": { "node_modules/is-regex": {
"version": "1.1.4", "version": "1.1.4",
"resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz",
@@ -6383,6 +6814,11 @@
"integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==",
"license": "MIT" "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": { "node_modules/json-schema-traverse": {
"version": "0.4.1", "version": "0.4.1",
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
@@ -6408,6 +6844,33 @@
"node": ">=6" "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": { "node_modules/jsx-ast-utils": {
"version": "3.3.5", "version": "3.3.5",
"resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz", "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz",
@@ -6481,6 +6944,12 @@
"integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==",
"license": "MIT" "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": { "node_modules/locate-path": {
"version": "6.0.0", "version": "6.0.0",
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", "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" "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": { "node_modules/mdn-data": {
"version": "2.0.14", "version": "2.0.14",
"resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.14.tgz", "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.14.tgz",
@@ -6988,6 +7466,17 @@
"node": ">=8" "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": { "node_modules/picocolors": {
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.1.tgz", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.1.tgz",
@@ -7803,6 +8292,11 @@
"url": "https://github.com/sponsors/sindresorhus" "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": { "node_modules/semver": {
"version": "7.6.3", "version": "7.6.3",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz",
@@ -7950,6 +8444,17 @@
"url": "https://github.com/sponsors/wooorm" "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": { "node_modules/stack-generator": {
"version": "2.0.10", "version": "2.0.10",
"resolved": "https://registry.npmjs.org/stack-generator/-/stack-generator-2.0.10.tgz", "resolved": "https://registry.npmjs.org/stack-generator/-/stack-generator-2.0.10.tgz",
@@ -8370,6 +8875,93 @@
"url": "https://github.com/sponsors/ljharb" "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": { "node_modules/synckit": {
"version": "0.9.1", "version": "0.9.1",
"resolved": "https://registry.npmjs.org/synckit/-/synckit-0.9.1.tgz", "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.9.1.tgz",
@@ -8501,7 +9093,6 @@
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz",
"integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==",
"dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">=4" "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": { "node_modules/which": {
"version": "2.0.2", "version": "2.0.2",
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
@@ -9143,6 +9755,23 @@
"url": "https://github.com/sponsors/sindresorhus" "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": { "node_modules/zustand": {
"version": "4.5.4", "version": "4.5.4",
"resolved": "https://registry.npmjs.org/zustand/-/zustand-4.5.4.tgz", "resolved": "https://registry.npmjs.org/zustand/-/zustand-4.5.4.tgz",

View File

@@ -11,6 +11,7 @@
"preview": "vite preview" "preview": "vite preview"
}, },
"dependencies": { "dependencies": {
"@ai-sdk/openai": "^0.0.51",
"@radix-ui/react-accordion": "^1.2.0", "@radix-ui/react-accordion": "^1.2.0",
"@radix-ui/react-checkbox": "^1.1.1", "@radix-ui/react-checkbox": "^1.1.1",
"@radix-ui/react-collapsible": "^1.1.0", "@radix-ui/react-collapsible": "^1.1.0",
@@ -30,6 +31,7 @@
"@radix-ui/react-tooltip": "^1.1.2", "@radix-ui/react-tooltip": "^1.1.2",
"@uidotdev/usehooks": "^2.4.1", "@uidotdev/usehooks": "^2.4.1",
"@xyflow/react": "^12.0.4", "@xyflow/react": "^12.0.4",
"ai": "^3.3.14",
"class-variance-authority": "^0.7.0", "class-variance-authority": "^0.7.0",
"clsx": "^2.1.1", "clsx": "^2.1.1",
"cmdk": "^1.0.0", "cmdk": "^1.0.0",

View File

@@ -1,6 +1,7 @@
import React, { useCallback, useMemo, useState } from 'react'; import React, { useCallback, useMemo, useState } from 'react';
import { DBTable } from '@/lib/domain/db-table'; 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 { ChartDBContext, chartDBContext } from './chartdb-context';
import { DatabaseType } from '@/lib/domain/database-type'; import { DatabaseType } from '@/lib/domain/database-type';
import { DBField } from '@/lib/domain/db-field'; import { DBField } from '@/lib/domain/db-field';
@@ -151,7 +152,7 @@ export const ChartDBProvider: React.FC<React.PropsWithChildren> = ({
}, },
], ],
indexes: [], indexes: [],
color: randomHSLA(), color: randomColor(),
createdAt: Date.now(), createdAt: Date.now(),
isView: false, isView: false,
}; };
@@ -718,11 +719,18 @@ export const ChartDBProvider: React.FC<React.PropsWithChildren> = ({
sourceFieldId, sourceFieldId,
targetFieldId, targetFieldId,
}) => { }) => {
const sourceTableName = getTable(sourceTableId)?.name ?? ''; const sourceTable = getTable(sourceTableId);
const targetTableName = getTable(targetTableId)?.name ?? ''; const sourceTableName = sourceTable?.name ?? '';
const sourceField = sourceTable?.fields.find(
(field: { id: string }) => field.id === sourceFieldId
);
const sourceFieldName = sourceField?.name ?? '';
const relationship: DBRelationship = { const relationship: DBRelationship = {
id: generateId(), id: generateId(),
name: `${sourceTableName}_${targetTableName}_fk`, name: `${sourceTableName}_${sourceFieldName}_fk`,
sourceTableId, sourceTableId,
targetTableId, targetTableId,
sourceFieldId, sourceFieldId,

View File

@@ -1,5 +1,6 @@
import { createContext } from 'react'; import { createContext } from 'react';
import { emptyFn } from '@/lib/utils'; import { emptyFn } from '@/lib/utils';
import { DatabaseType } from '@/lib/domain/database-type';
export interface DialogContext { export interface DialogContext {
// Create diagram dialog // Create diagram dialog
@@ -9,6 +10,10 @@ export interface DialogContext {
// Open diagram dialog // Open diagram dialog
openOpenDiagramDialog: () => void; openOpenDiagramDialog: () => void;
closeOpenDiagramDialog: () => void; closeOpenDiagramDialog: () => void;
// Export SQL dialog
openExportSQLDialog: (params: { targetDatabaseType: DatabaseType }) => void;
closeExportSQLDialog: () => void;
} }
export const dialogContext = createContext<DialogContext>({ export const dialogContext = createContext<DialogContext>({
@@ -16,4 +21,6 @@ export const dialogContext = createContext<DialogContext>({
closeCreateDiagramDialog: emptyFn, closeCreateDiagramDialog: emptyFn,
openOpenDiagramDialog: emptyFn, openOpenDiagramDialog: emptyFn,
closeOpenDiagramDialog: emptyFn, closeOpenDiagramDialog: emptyFn,
openExportSQLDialog: emptyFn,
closeExportSQLDialog: emptyFn,
}); });

View File

@@ -1,13 +1,28 @@
import React, { useState } from 'react'; import React, { useCallback, useState } from 'react';
import { dialogContext } from './dialog-context'; import { DialogContext, dialogContext } from './dialog-context';
import { CreateDiagramDialog } from '@/dialogs/create-diagram-dialog/create-diagram-dialog'; import { CreateDiagramDialog } from '@/dialogs/create-diagram-dialog/create-diagram-dialog';
import { OpenDiagramDialog } from '@/dialogs/open-diagram-dialog/open-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<React.PropsWithChildren> = ({ export const DialogProvider: React.FC<React.PropsWithChildren> = ({
children, children,
}) => { }) => {
const [openNewDiagramDialog, setOpenNewDiagramDialog] = useState(false); const [openNewDiagramDialog, setOpenNewDiagramDialog] = useState(false);
const [openOpenDiagramDialog, setOpenOpenDiagramDialog] = 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 ( return (
<dialogContext.Provider <dialogContext.Provider
@@ -16,11 +31,17 @@ export const DialogProvider: React.FC<React.PropsWithChildren> = ({
closeCreateDiagramDialog: () => setOpenNewDiagramDialog(false), closeCreateDiagramDialog: () => setOpenNewDiagramDialog(false),
openOpenDiagramDialog: () => setOpenOpenDiagramDialog(true), openOpenDiagramDialog: () => setOpenOpenDiagramDialog(true),
closeOpenDiagramDialog: () => setOpenOpenDiagramDialog(false), closeOpenDiagramDialog: () => setOpenOpenDiagramDialog(false),
openExportSQLDialog: openExportSQLDialogHandler,
closeExportSQLDialog: () => setOpenExportSQLDialog(false),
}} }}
> >
{children} {children}
<CreateDiagramDialog dialog={{ open: openNewDiagramDialog }} /> <CreateDiagramDialog dialog={{ open: openNewDiagramDialog }} />
<OpenDiagramDialog dialog={{ open: openOpenDiagramDialog }} /> <OpenDiagramDialog dialog={{ open: openOpenDiagramDialog }} />
<ExportSQLDialog
dialog={{ open: openExportSQLDialog }}
{...openExportSQLDialogParams}
/>
</dialogContext.Provider> </dialogContext.Provider>
); );
}; };

View File

@@ -12,7 +12,10 @@ export interface StorageContext {
// Diagram operations // Diagram operations
addDiagram: (params: { diagram: Diagram }) => Promise<void>; addDiagram: (params: { diagram: Diagram }) => Promise<void>;
listDiagrams: () => Promise<Diagram[]>; listDiagrams: (options?: {
includeTables?: boolean;
includeRelationships?: boolean;
}) => Promise<Diagram[]>;
getDiagram: ( getDiagram: (
id: string, id: string,
options?: { options?: {

View File

@@ -92,10 +92,33 @@ export const StorageProvider: React.FC<React.PropsWithChildren> = ({
await Promise.all(promises); await Promise.all(promises);
}; };
const listDiagrams: StorageContext['listDiagrams'] = async (): Promise< const listDiagrams: StorageContext['listDiagrams'] = async (
Diagram[] options: {
> => { includeTables?: boolean;
return await db.diagrams.toArray(); includeRelationships?: boolean;
} = { includeRelationships: false, includeTables: false }
): Promise<Diagram[]> => {
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 ( const getDiagram: StorageContext['getDiagram'] = async (
@@ -248,10 +271,15 @@ export const StorageProvider: React.FC<React.PropsWithChildren> = ({
const listRelationships: StorageContext['listRelationships'] = async ( const listRelationships: StorageContext['listRelationships'] = async (
diagramId: string diagramId: string
): Promise<DBRelationship[]> => { ): Promise<DBRelationship[]> => {
return await db.db_relationships // Sort relationships alphabetically
.where('diagramId') return await (
.equals(diagramId) await db.db_relationships
.toArray(); .where('diagramId')
.equals(diagramId)
.toArray()
).sort((a, b) => {
return a.name.localeCompare(b.name);
});
}; };
return ( return (

View File

@@ -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<ExportSQLDialogProps> = ({
dialog,
targetDatabaseType,
}) => {
const { closeExportSQLDialog } = useDialog();
const { currentDiagram } = useChartDB();
const [script, setScript] = React.useState<string>();
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(
() => (
<div className="flex flex-col gap-2">
<Spinner />
<div className="flex items-center justify-center gap-1">
<WandSparkles className="h-5" />
<Label>
Generating SQL for{' '}
{databaseTypeToLabelMap[targetDatabaseType]}...
</Label>
</div>
</div>
),
[targetDatabaseType]
);
return (
<Dialog
{...dialog}
onOpenChange={(open) => {
if (!open) {
closeExportSQLDialog();
}
}}
>
<DialogContent
className="flex flex-col min-w-[500px] xl:min-w-[75vw] max-h-[80vh] overflow-y-auto"
showClose
>
<DialogHeader>
<DialogTitle>Export SQL</DialogTitle>
<DialogDescription>
Export the SQL of the current diagram
</DialogDescription>
</DialogHeader>
<div className="flex flex-1 items-center justify-center">
{(script?.length ?? 0) === 0 ? (
renderLoader()
) : (
<CodeSnippet code={script!} />
)}
</div>
<DialogFooter className="flex !justify-between gap-2">
<div />
<DialogClose asChild>
<Button type="button">Close</Button>
</DialogClose>
</DialogFooter>
</DialogContent>
</Dialog>
);
};

View File

@@ -46,7 +46,7 @@ export const OpenDiagramDialog: React.FC<OpenDiagramDialogProps> = ({
useEffect(() => { useEffect(() => {
const fetchDiagrams = async () => { const fetchDiagrams = async () => {
const diagrams = await listDiagrams(); const diagrams = await listDiagrams({ includeTables: true });
setDiagrams( setDiagrams(
diagrams.sort( diagrams.sort(
(a, b) => b.updatedAt.getTime() - a.updatedAt.getTime() (a, b) => b.updatedAt.getTime() - a.updatedAt.getTime()
@@ -87,6 +87,7 @@ export const OpenDiagramDialog: React.FC<OpenDiagramDialogProps> = ({
<TableHead>Name</TableHead> <TableHead>Name</TableHead>
<TableHead>Created at</TableHead> <TableHead>Created at</TableHead>
<TableHead>Last modified</TableHead> <TableHead>Last modified</TableHead>
<TableHead>Tables count</TableHead>
<TableHead>Type</TableHead> <TableHead>Type</TableHead>
</TableRow> </TableRow>
</TableHeader> </TableHeader>
@@ -120,6 +121,9 @@ export const OpenDiagramDialog: React.FC<OpenDiagramDialogProps> = ({
<TableCell> <TableCell>
{diagram.updatedAt.toLocaleString()} {diagram.updatedAt.toLocaleString()}
</TableCell> </TableCell>
<TableCell>
{diagram.tables?.length}
</TableCell>
<TableCell> <TableCell>
{ {
databaseTypeToLabelMap[ databaseTypeToLabelMap[

19
src/lib/colors.ts Normal file
View File

@@ -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.

View File

@@ -295,6 +295,12 @@ export const sqliteDataTypes = [
// Date/Time Types (SQLite uses TEXT, REAL, or INTEGER types for dates and times) // Date/Time Types (SQLite uses TEXT, REAL, or INTEGER types for dates and times)
'date', 'date',
'datetime', 'datetime',
'int',
'float',
'boolean',
'varchar',
'decimal',
] as const; ] as const;
export const dataTypes = [ export const dataTypes = [

View File

@@ -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; const { tables, relationships } = diagram;
if (!tables || tables.length === 0) { if (!tables || tables.length === 0) {
@@ -8,26 +16,48 @@ export const exportSQL = (diagram: Diagram): string => {
} }
// Filter out the tables that are views // 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 // Initialize the SQL script string
let sqlScript = ''; let sqlScript = '';
// Loop through each non-view table to generate the SQL statements // Loop through each non-view table to generate the SQL statements
nonViewTables.forEach(table => { nonViewTables.forEach((table) => {
sqlScript += `CREATE TABLE ${table.name} (\n`; sqlScript += `CREATE TABLE ${table.name} (\n`;
table.fields.forEach((field, index) => { table.fields.forEach((field, index) => {
sqlScript += ` ${field.name} ${field.type}`; sqlScript += ` ${field.name} ${field.type}`;
if (field.primaryKey) { // Add size for character types
sqlScript += ' PRIMARY KEY'; 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) { if (!field.nullable) {
sqlScript += ' NOT NULL'; 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 // Add a comma after each field except the last one
if (index < table.fields.length - 1) { if (index < table.fields.length - 1) {
sqlScript += ',\n'; sqlScript += ',\n';
@@ -35,17 +65,230 @@ export const exportSQL = (diagram: Diagram): string => {
}); });
sqlScript += '\n);\n\n'; 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 // Handle relationships (foreign keys)
relationships?.forEach(relationship => { relationships?.forEach((relationship) => {
const sourceTable = nonViewTables.find(table => table.id === relationship.sourceTableId); const sourceTable = nonViewTables.find(
const targetTable = nonViewTables.find(table => table.id === relationship.targetTableId); (table) => table.id === relationship.sourceTableId
);
const targetTable = nonViewTables.find(
(table) => table.id === relationship.targetTableId
);
if (sourceTable && targetTable) { const sourceTableField = sourceTable?.fields.find(
sqlScript += `ALTER TABLE ${sourceTable.name} ADD CONSTRAINT ${relationship.name} FOREIGN KEY (${relationship.sourceFieldId}) REFERENCES ${targetTable.name} (${relationship.targetFieldId});\n`; (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; return sqlScript;
}; };
export const exportSQL = async (
diagram: Diagram,
databaseType: DatabaseType
): Promise<string> => {
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 PostgreSQLs 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 doesnt 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}
`;
};

View File

@@ -5,5 +5,11 @@ export interface ColumnInfo {
type: string; type: string;
ordinal_position: number; ordinal_position: number;
nullable: boolean; 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;
} }

View File

@@ -11,7 +11,7 @@ export interface DatabaseMetadata {
indexes: IndexInfo[]; indexes: IndexInfo[];
tables: TableInfo[]; tables: TableInfo[];
views: ViewInfo[]; views: ViewInfo[];
server_name: string; database_name: string;
version: string; version: string;
} }

View File

@@ -8,6 +8,11 @@ export interface DBField {
unique: boolean; unique: boolean;
nullable: boolean; nullable: boolean;
createdAt: number; createdAt: number;
characterMaximumLength?: string;
precision?: number;
scale?: number;
default?: string;
collation?: string;
} }
export type FieldType = (typeof dataTypes)[number]; export type FieldType = (typeof dataTypes)[number];

View File

@@ -3,10 +3,11 @@ import { DBField, FieldType } from './db-field';
import { TableInfo } from '../data/import-metadata/metadata-types/table-info'; import { TableInfo } from '../data/import-metadata/metadata-types/table-info';
import { ColumnInfo } from '../data/import-metadata/metadata-types/column-info'; import { ColumnInfo } from '../data/import-metadata/metadata-types/column-info';
import { IndexInfo } from '../data/import-metadata/metadata-types/index-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 { DBRelationship } from './db-relationship';
import { PrimaryKeyInfo } from '../data/import-metadata/metadata-types/primary-key-info'; import { PrimaryKeyInfo } from '../data/import-metadata/metadata-types/primary-key-info';
import { ViewInfo } from '../data/import-metadata/metadata-types/view-info'; import { ViewInfo } from '../data/import-metadata/metadata-types/view-info';
import { generateId } from '../utils';
export interface DBTable { export interface DBTable {
id: string; id: string;
@@ -38,7 +39,11 @@ export const createTablesFromMetadata = ({
// Filter, make unique, and sort columns based on ordinal_position // Filter, make unique, and sort columns based on ordinal_position
const uniqueColumns = new Map<string, ColumnInfo>(); const uniqueColumns = new Map<string, ColumnInfo>();
columns columns
.filter((col) => col.table === tableInfo.table) .filter(
(col) =>
col.schema === tableInfo.schema &&
col.table === tableInfo.table
)
.forEach((col) => { .forEach((col) => {
if (!uniqueColumns.has(col.name)) { if (!uniqueColumns.has(col.name)) {
uniqueColumns.set(col.name, col); uniqueColumns.set(col.name, col);
@@ -46,9 +51,7 @@ export const createTablesFromMetadata = ({
}); });
const sortedColumns = Array.from(uniqueColumns.values()).sort( const sortedColumns = Array.from(uniqueColumns.values()).sort(
(a, b) => { (a, b) => a.ordinal_position - b.ordinal_position
return a.ordinal_position - b.ordinal_position;
}
); );
const tablePrimaryKeys = primaryKeys.filter( const tablePrimaryKeys = primaryKeys.filter(
@@ -71,6 +74,16 @@ export const createTablesFromMetadata = ({
(idx) => idx.column === col.name && idx.unique (idx) => idx.column === col.name && idx.unique
), ),
nullable: col.nullable, 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(), createdAt: Date.now(),
}) })
); );
@@ -102,7 +115,7 @@ export const createTablesFromMetadata = ({
y: Math.random() * 800, // Placeholder Y y: Math.random() * 800, // Placeholder Y
fields, fields,
indexes: dbIndexes, indexes: dbIndexes,
color: isView ? greyColor : randomHSLA(), color: isView ? greyColor : randomColor(),
isView: isView, isView: isView,
createdAt: Date.now(), createdAt: Date.now(),
}; };

View File

@@ -67,7 +67,9 @@ export const loadFromDatabaseMetadata = ({
return { return {
id: generateId(), id: generateId(),
name: databaseMetadata.server_name || `Diagram ${diagramNumber}`, name:
`${databaseMetadata.database_name}-db` ||
`Diagram ${diagramNumber}`,
databaseType: databaseType ?? DatabaseType.GENERIC, databaseType: databaseType ?? DatabaseType.GENERIC,
tables: sortedTables, tables: sortedTables,
relationships, relationships,

1
src/lib/env.ts Normal file
View File

@@ -0,0 +1 @@
export const OPENAI_API_KEY = import.meta.env.VITE_OPENAI_API_KEY;

View File

@@ -2,7 +2,6 @@ import { type ClassValue, clsx } from 'clsx';
import { customAlphabet } from 'nanoid'; import { customAlphabet } from 'nanoid';
const randomId = customAlphabet('0123456789abcdefghijklmnopqrstuvwxyz', 25); const randomId = customAlphabet('0123456789abcdefghijklmnopqrstuvwxyz', 25);
const randonNumber = customAlphabet('1234567890', 18);
import { twMerge } from 'tailwind-merge'; import { twMerge } from 'tailwind-merge';
@@ -10,16 +9,6 @@ export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs)); 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 // eslint-disable-next-line @typescript-eslint/no-explicit-any
export const emptyFn = (): any => undefined; export const emptyFn = (): any => undefined;

View File

@@ -31,12 +31,17 @@ import {
databaseSecondaryLogoMap, databaseSecondaryLogoMap,
databaseTypeToLabelMap, databaseTypeToLabelMap,
} from '@/lib/databases'; } from '@/lib/databases';
import { DatabaseType } from '@/lib/domain/database-type';
export interface TopNavbarProps {} export interface TopNavbarProps {}
export const TopNavbar: React.FC<TopNavbarProps> = () => { export const TopNavbar: React.FC<TopNavbarProps> = () => {
const { diagramName, updateDiagramName, currentDiagram } = useChartDB(); const { diagramName, updateDiagramName, currentDiagram } = useChartDB();
const { openCreateDiagramDialog, openOpenDiagramDialog } = useDialog(); const {
openCreateDiagramDialog,
openOpenDiagramDialog,
openExportSQLDialog,
} = useDialog();
const [editMode, setEditMode] = useState(false); const [editMode, setEditMode] = useState(false);
const { exportImage } = useExportImage(); const { exportImage } = useExportImage();
const [editedDiagramName, setEditedDiagramName] = const [editedDiagramName, setEditedDiagramName] =
@@ -74,11 +79,6 @@ export const TopNavbar: React.FC<TopNavbarProps> = () => {
setEditMode(true); setEditMode(true);
}; };
const exportSql = useCallback(() => {
console.log('Export SQL');
console.log({ currentDiagram });
}, [currentDiagram]);
const exportPNG = useCallback(() => { const exportPNG = useCallback(() => {
exportImage('png'); exportImage('png');
}, [exportImage]); }, [exportImage]);
@@ -95,6 +95,13 @@ export const TopNavbar: React.FC<TopNavbarProps> = () => {
window.open('https://chartdb.io', '_blank'); 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 ( return (
<nav className="flex flex-row items-center justify-between px-4 h-12 border-b"> <nav className="flex flex-row items-center justify-between px-4 h-12 border-b">
<div className="flex flex-1 justify-start gap-x-3"> <div className="flex flex-1 justify-start gap-x-3">
@@ -125,9 +132,81 @@ export const TopNavbar: React.FC<TopNavbarProps> = () => {
Open Open
</MenubarItem> </MenubarItem>
<MenubarSeparator /> <MenubarSeparator />
<MenubarItem onClick={exportSql}> <MenubarSub>
Export <MenubarSubTrigger>
</MenubarItem> Export SQL
</MenubarSubTrigger>
<MenubarSubContent>
<MenubarItem
onClick={() =>
openExportSQLDialog({
targetDatabaseType:
DatabaseType.GENERIC,
})
}
>
{databaseTypeToLabelMap['generic']}
</MenubarItem>
<MenubarItem
onClick={() =>
openExportSQLDialog({
targetDatabaseType:
DatabaseType.POSTGRESQL,
})
}
>
{
databaseTypeToLabelMap[
'postgresql'
]
}
</MenubarItem>
<MenubarItem
onClick={() =>
openExportSQLDialog({
targetDatabaseType:
DatabaseType.MYSQL,
})
}
>
{databaseTypeToLabelMap['mysql']}
</MenubarItem>
<MenubarItem
onClick={() =>
openExportSQLDialog({
targetDatabaseType:
DatabaseType.SQL_SERVER,
})
}
>
{
databaseTypeToLabelMap[
'sql_server'
]
}
</MenubarItem>
<MenubarItem
onClick={() =>
openExportSQLDialog({
targetDatabaseType:
DatabaseType.MARIADB,
})
}
>
{databaseTypeToLabelMap['mariadb']}
</MenubarItem>
<MenubarItem
onClick={() =>
openExportSQLDialog({
targetDatabaseType:
DatabaseType.SQLITE,
})
}
>
{databaseTypeToLabelMap['sqlite']}
</MenubarItem>
</MenubarSubContent>
</MenubarSub>
<MenubarSub> <MenubarSub>
<MenubarSubTrigger> <MenubarSubTrigger>
Export as Export as
@@ -167,6 +246,9 @@ export const TopNavbar: React.FC<TopNavbarProps> = () => {
<MenubarItem onClick={openChartDBIO}> <MenubarItem onClick={openChartDBIO}>
Visit ChartDB Visit ChartDB
</MenubarItem> </MenubarItem>
<MenubarItem onClick={openJoinSlack}>
Join us on Slack
</MenubarItem>
</MenubarContent> </MenubarContent>
</MenubarMenu> </MenubarMenu>
</Menubar> </Menubar>