updates
This commit is contained in:
parent
79c5b896fe
commit
56462e6cc2
2
.gitignore
vendored
2
.gitignore
vendored
@ -3,6 +3,7 @@ node_modules
|
|||||||
|
|
||||||
# output
|
# output
|
||||||
out
|
out
|
||||||
|
dist
|
||||||
*.tgz
|
*.tgz
|
||||||
|
|
||||||
# code coverage
|
# code coverage
|
||||||
@ -33,3 +34,4 @@ report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json
|
|||||||
.DS_Store
|
.DS_Store
|
||||||
/test
|
/test
|
||||||
.vscode
|
.vscode
|
||||||
|
.dump
|
||||||
57
bun.lock
57
bun.lock
@ -8,23 +8,22 @@
|
|||||||
"@inquirer/prompts": "^8.3.0",
|
"@inquirer/prompts": "^8.3.0",
|
||||||
"chalk": "^5.6.2",
|
"chalk": "^5.6.2",
|
||||||
"commander": "^14.0.3",
|
"commander": "^14.0.3",
|
||||||
"inquirer": "^13.3.2",
|
|
||||||
"lodash": "^4.17.23",
|
"lodash": "^4.17.23",
|
||||||
"mariadb": "^3.5.3",
|
|
||||||
"mysql": "^2.18.1",
|
|
||||||
"sanitize-html": "^2.17.6",
|
"sanitize-html": "^2.17.6",
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/bun": "latest",
|
"@types/bun": "latest",
|
||||||
"@types/inquirer": "^9.0.9",
|
|
||||||
"@types/lodash": "^4.17.24",
|
"@types/lodash": "^4.17.24",
|
||||||
"@types/mysql": "^2.15.27",
|
|
||||||
"@types/node": "^25.3.3",
|
"@types/node": "^25.3.3",
|
||||||
"@types/sanitize-html": "^2.16.1",
|
"@types/sanitize-html": "^2.16.1",
|
||||||
|
"typescript": "~5.8.0",
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"typescript": "^5",
|
"typescript": ">=5",
|
||||||
},
|
},
|
||||||
|
"optionalPeers": [
|
||||||
|
"typescript",
|
||||||
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"packages": {
|
"packages": {
|
||||||
@ -62,22 +61,12 @@
|
|||||||
|
|
||||||
"@types/bun": ["@types/bun@1.3.14", "", { "dependencies": { "bun-types": "1.3.14" } }, "sha512-h1hFqFVcvAvD9j9K7ZW7vd82aSA+rTdznZa+5bwvCwqSB1jmmfLcbIWhOLx1/+boy/xmjgCs/OMUL8hRJSmnPw=="],
|
"@types/bun": ["@types/bun@1.3.14", "", { "dependencies": { "bun-types": "1.3.14" } }, "sha512-h1hFqFVcvAvD9j9K7ZW7vd82aSA+rTdznZa+5bwvCwqSB1jmmfLcbIWhOLx1/+boy/xmjgCs/OMUL8hRJSmnPw=="],
|
||||||
|
|
||||||
"@types/geojson": ["@types/geojson@7946.0.16", "", {}, "sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg=="],
|
|
||||||
|
|
||||||
"@types/inquirer": ["@types/inquirer@9.0.10", "", { "dependencies": { "@types/through": "*", "rxjs": "^7.2.0" } }, "sha512-vFW2WbXwO9eZpRT5GJGFJ/shgyMNnYozmnjakt9jCQSS1lvqX8pZEQMjJ9RdDPct/YxwciQ8+V8OYn9euIrZDA=="],
|
|
||||||
|
|
||||||
"@types/lodash": ["@types/lodash@4.17.24", "", {}, "sha512-gIW7lQLZbue7lRSWEFql49QJJWThrTFFeIMJdp3eH4tKoxm1OvEPg02rm4wCCSHS0cL3/Fizimb35b7k8atwsQ=="],
|
"@types/lodash": ["@types/lodash@4.17.24", "", {}, "sha512-gIW7lQLZbue7lRSWEFql49QJJWThrTFFeIMJdp3eH4tKoxm1OvEPg02rm4wCCSHS0cL3/Fizimb35b7k8atwsQ=="],
|
||||||
|
|
||||||
"@types/mysql": ["@types/mysql@2.15.27", "", { "dependencies": { "@types/node": "*" } }, "sha512-YfWiV16IY0OeBfBCk8+hXKmdTKrKlwKN1MNKAPBu5JYxLwBEZl7QzeEpGnlZb3VMGJrrGmB84gXiH+ofs/TezA=="],
|
|
||||||
|
|
||||||
"@types/node": ["@types/node@25.9.4", "", { "dependencies": { "undici-types": ">=7.24.0 <7.24.7" } }, "sha512-dszCsrKb5U7ZsVZBWiHFklTloVl0mSEnWH/iZXfZUlI4rzCUnsvGmgqfuVRHL54ugE7/wRuxEIXRa2iMZ+BG6g=="],
|
"@types/node": ["@types/node@25.9.4", "", { "dependencies": { "undici-types": ">=7.24.0 <7.24.7" } }, "sha512-dszCsrKb5U7ZsVZBWiHFklTloVl0mSEnWH/iZXfZUlI4rzCUnsvGmgqfuVRHL54ugE7/wRuxEIXRa2iMZ+BG6g=="],
|
||||||
|
|
||||||
"@types/sanitize-html": ["@types/sanitize-html@2.16.1", "", { "dependencies": { "htmlparser2": "^10.1" } }, "sha512-n9wjs8bCOTyN/ynwD8s/nTcTreIHB1vf31vhLMGqUPNHaweKC4/fAl4Dj+hUlCTKYgm4P3k83fmiFfzkZ6sgMA=="],
|
"@types/sanitize-html": ["@types/sanitize-html@2.16.1", "", { "dependencies": { "htmlparser2": "^10.1" } }, "sha512-n9wjs8bCOTyN/ynwD8s/nTcTreIHB1vf31vhLMGqUPNHaweKC4/fAl4Dj+hUlCTKYgm4P3k83fmiFfzkZ6sgMA=="],
|
||||||
|
|
||||||
"@types/through": ["@types/through@0.0.33", "", { "dependencies": { "@types/node": "*" } }, "sha512-HsJ+z3QuETzP3cswwtzt2vEIiHBk/dCcHGhbmG5X3ecnwFD/lPrMpliGXxSCg03L9AhrdwA4Oz/qfspkDW+xGQ=="],
|
|
||||||
|
|
||||||
"bignumber.js": ["bignumber.js@9.0.0", "", {}, "sha512-t/OYhhJ2SD+YGBQcjY8GzzDHEk9f3nerxjtfa6tlMXfe7frs/WozhvCNoGvpM0P3bNf3Gq5ZRMlGr5f3r4/N8A=="],
|
|
||||||
|
|
||||||
"bun-types": ["bun-types@1.3.14", "", { "dependencies": { "@types/node": "*" } }, "sha512-4N0ig0fEomHt5R0KCFWjovxow98rIoRwKolrYdCcknNwMekCXRnWEUvgu5soYV8QXtVsrUD8B95MBOZGPvr6KQ=="],
|
"bun-types": ["bun-types@1.3.14", "", { "dependencies": { "@types/node": "*" } }, "sha512-4N0ig0fEomHt5R0KCFWjovxow98rIoRwKolrYdCcknNwMekCXRnWEUvgu5soYV8QXtVsrUD8B95MBOZGPvr6KQ=="],
|
||||||
|
|
||||||
"chalk": ["chalk@5.6.2", "", {}, "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA=="],
|
"chalk": ["chalk@5.6.2", "", {}, "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA=="],
|
||||||
@ -88,14 +77,10 @@
|
|||||||
|
|
||||||
"commander": ["commander@14.0.3", "", {}, "sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw=="],
|
"commander": ["commander@14.0.3", "", {}, "sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw=="],
|
||||||
|
|
||||||
"core-util-is": ["core-util-is@1.0.3", "", {}, "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ=="],
|
|
||||||
|
|
||||||
"dayjs": ["dayjs@1.11.21", "", {}, "sha512-98IT+HOahAisibz/yjKbzuOBwYcjJ7BCLPzARyHiyEBmRz4fatF+KPJszEHXsGYjUG234aH/cOjW1wwTbKUZlA=="],
|
"dayjs": ["dayjs@1.11.21", "", {}, "sha512-98IT+HOahAisibz/yjKbzuOBwYcjJ7BCLPzARyHiyEBmRz4fatF+KPJszEHXsGYjUG234aH/cOjW1wwTbKUZlA=="],
|
||||||
|
|
||||||
"deepmerge": ["deepmerge@4.3.1", "", {}, "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A=="],
|
"deepmerge": ["deepmerge@4.3.1", "", {}, "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A=="],
|
||||||
|
|
||||||
"denque": ["denque@2.1.0", "", {}, "sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw=="],
|
|
||||||
|
|
||||||
"dom-serializer": ["dom-serializer@2.0.0", "", { "dependencies": { "domelementtype": "^2.3.0", "domhandler": "^5.0.2", "entities": "^4.2.0" } }, "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg=="],
|
"dom-serializer": ["dom-serializer@2.0.0", "", { "dependencies": { "domelementtype": "^2.3.0", "domhandler": "^5.0.2", "entities": "^4.2.0" } }, "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg=="],
|
||||||
|
|
||||||
"domelementtype": ["domelementtype@2.3.0", "", {}, "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw=="],
|
"domelementtype": ["domelementtype@2.3.0", "", {}, "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw=="],
|
||||||
@ -118,26 +103,14 @@
|
|||||||
|
|
||||||
"iconv-lite": ["iconv-lite@0.7.2", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw=="],
|
"iconv-lite": ["iconv-lite@0.7.2", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw=="],
|
||||||
|
|
||||||
"inherits": ["inherits@2.0.4", "", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="],
|
|
||||||
|
|
||||||
"inquirer": ["inquirer@13.4.3", "", { "dependencies": { "@inquirer/ansi": "^2.0.5", "@inquirer/core": "^11.1.10", "@inquirer/prompts": "^8.4.3", "@inquirer/type": "^4.0.5", "mute-stream": "^3.0.0", "run-async": "^4.0.6", "rxjs": "^7.8.2" }, "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-EPd3IqieHSavSOXh+LZhrIkdQcOELWeRblLT6kslQr+cF9XTh/HxZdSt1YkHH1iq4dvqBnV42uwg2YlorgOy6g=="],
|
|
||||||
|
|
||||||
"is-plain-object": ["is-plain-object@5.0.0", "", {}, "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q=="],
|
"is-plain-object": ["is-plain-object@5.0.0", "", {}, "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q=="],
|
||||||
|
|
||||||
"isarray": ["isarray@1.0.0", "", {}, "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ=="],
|
|
||||||
|
|
||||||
"launder": ["launder@1.7.1", "", { "dependencies": { "dayjs": "^1.11.7" } }, "sha512-mU6WRz5EusL9ZZuiZ5SO4Y6C0P9PAUR9iwdb6bzj4KDihm28DiHFw+/yk9DBH4f+Pv1wuzQ4e2jV3oQ7mkIqvw=="],
|
"launder": ["launder@1.7.1", "", { "dependencies": { "dayjs": "^1.11.7" } }, "sha512-mU6WRz5EusL9ZZuiZ5SO4Y6C0P9PAUR9iwdb6bzj4KDihm28DiHFw+/yk9DBH4f+Pv1wuzQ4e2jV3oQ7mkIqvw=="],
|
||||||
|
|
||||||
"lodash": ["lodash@4.18.1", "", {}, "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q=="],
|
"lodash": ["lodash@4.18.1", "", {}, "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q=="],
|
||||||
|
|
||||||
"lru-cache": ["lru-cache@11.5.1", "", {}, "sha512-RPimw/7aMdv2oqRrxKwvZXcPfwBrn/JZ2xYcY9Hus/6LaS3VOAKVWKWgNLCFSiOm1ESXinjsDlidVU7JlnCN2A=="],
|
|
||||||
|
|
||||||
"mariadb": ["mariadb@3.5.3", "", { "dependencies": { "@types/geojson": "^7946.0.16", "@types/node": ">=20", "denque": "^2.1.0", "iconv-lite": "^0.7.2", "lru-cache": "^11.5.0" } }, "sha512-i053Kc0MgdUv/hu9mCyq67TYfPXFj3/MV8I7ZW5wvJNixIyXC0VztMPUjIVj/449nQo+BsxFD4Fdk/sA/uqKPQ=="],
|
|
||||||
|
|
||||||
"mute-stream": ["mute-stream@3.0.0", "", {}, "sha512-dkEJPVvun4FryqBmZ5KhDo0K9iDXAwn08tMLDinNdRBNPcYEDiWYysLcc6k3mjTMlbP9KyylvRpd4wFtwrT9rw=="],
|
"mute-stream": ["mute-stream@3.0.0", "", {}, "sha512-dkEJPVvun4FryqBmZ5KhDo0K9iDXAwn08tMLDinNdRBNPcYEDiWYysLcc6k3mjTMlbP9KyylvRpd4wFtwrT9rw=="],
|
||||||
|
|
||||||
"mysql": ["mysql@2.18.1", "", { "dependencies": { "bignumber.js": "9.0.0", "readable-stream": "2.3.7", "safe-buffer": "5.1.2", "sqlstring": "2.3.1" } }, "sha512-Bca+gk2YWmqp2Uf6k5NFEurwY/0td0cpebAucFpY/3jhrwrVGuxU2uQFCHjU19SJfje0yQvi+rVWdq78hR5lig=="],
|
|
||||||
|
|
||||||
"nanoid": ["nanoid@3.3.16", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-bzlKTyNJ7+LdGIIwy8ijFpIqEQIvafahV7eYykJ8Cvh42EdJeODoJ6gUJXpQJvej1BddH8OqTXZNE/KfbWAu8Q=="],
|
"nanoid": ["nanoid@3.3.16", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-bzlKTyNJ7+LdGIIwy8ijFpIqEQIvafahV7eYykJ8Cvh42EdJeODoJ6gUJXpQJvej1BddH8OqTXZNE/KfbWAu8Q=="],
|
||||||
|
|
||||||
"parse-srcset": ["parse-srcset@1.0.2", "", {}, "sha512-/2qh0lav6CmI15FzA3i/2Bzk2zCgQhGMkvhOhKNcBVQ1ldgpbfiNTVslmooUmWJcADi1f1kIeynbDRVzNlfR6Q=="],
|
"parse-srcset": ["parse-srcset@1.0.2", "", {}, "sha512-/2qh0lav6CmI15FzA3i/2Bzk2zCgQhGMkvhOhKNcBVQ1ldgpbfiNTVslmooUmWJcADi1f1kIeynbDRVzNlfR6Q=="],
|
||||||
@ -146,16 +119,6 @@
|
|||||||
|
|
||||||
"postcss": ["postcss@8.5.19", "", { "dependencies": { "nanoid": "^3.3.12", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-Mz8SaolMd8nB+G13WkORcxQKHZ/NE4xXevtkJHVuG+guo9/wYKlIMTKAqGdEmYOXR2ijPjTYNHssizdaVSUNdQ=="],
|
"postcss": ["postcss@8.5.19", "", { "dependencies": { "nanoid": "^3.3.12", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-Mz8SaolMd8nB+G13WkORcxQKHZ/NE4xXevtkJHVuG+guo9/wYKlIMTKAqGdEmYOXR2ijPjTYNHssizdaVSUNdQ=="],
|
||||||
|
|
||||||
"process-nextick-args": ["process-nextick-args@2.0.1", "", {}, "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag=="],
|
|
||||||
|
|
||||||
"readable-stream": ["readable-stream@2.3.7", "", { "dependencies": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", "isarray": "~1.0.0", "process-nextick-args": "~2.0.0", "safe-buffer": "~5.1.1", "string_decoder": "~1.1.1", "util-deprecate": "~1.0.1" } }, "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw=="],
|
|
||||||
|
|
||||||
"run-async": ["run-async@4.0.6", "", {}, "sha512-IoDlSLTs3Yq593mb3ZoKWKXMNu3UpObxhgA/Xuid5p4bbfi2jdY1Hj0m1K+0/tEuQTxIGMhQDqGjKb7RuxGpAQ=="],
|
|
||||||
|
|
||||||
"rxjs": ["rxjs@7.8.2", "", { "dependencies": { "tslib": "^2.1.0" } }, "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA=="],
|
|
||||||
|
|
||||||
"safe-buffer": ["safe-buffer@5.1.2", "", {}, "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="],
|
|
||||||
|
|
||||||
"safer-buffer": ["safer-buffer@2.1.2", "", {}, "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="],
|
"safer-buffer": ["safer-buffer@2.1.2", "", {}, "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="],
|
||||||
|
|
||||||
"sanitize-html": ["sanitize-html@2.17.6", "", { "dependencies": { "deepmerge": "^4.2.2", "escape-string-regexp": "^4.0.0", "htmlparser2": "^12.0.0", "is-plain-object": "^5.0.0", "launder": "^1.7.1", "parse-srcset": "^1.0.2", "postcss": "^8.3.11" } }, "sha512-M4bo9tfv1yfhQZZKkc6dL07ALrGJtfvNOuhX3hU9AVPR/uPQ+nKOJBqTYc7LfMQblTW04mtSWDJWEyLvygJsLA=="],
|
"sanitize-html": ["sanitize-html@2.17.6", "", { "dependencies": { "deepmerge": "^4.2.2", "escape-string-regexp": "^4.0.0", "htmlparser2": "^12.0.0", "is-plain-object": "^5.0.0", "launder": "^1.7.1", "parse-srcset": "^1.0.2", "postcss": "^8.3.11" } }, "sha512-M4bo9tfv1yfhQZZKkc6dL07ALrGJtfvNOuhX3hU9AVPR/uPQ+nKOJBqTYc7LfMQblTW04mtSWDJWEyLvygJsLA=="],
|
||||||
@ -164,18 +127,10 @@
|
|||||||
|
|
||||||
"source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="],
|
"source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="],
|
||||||
|
|
||||||
"sqlstring": ["sqlstring@2.3.1", "", {}, "sha512-ooAzh/7dxIG5+uDik1z/Rd1vli0+38izZhGzSa34FwR7IbelPWCCKSNIl8jlL/F7ERvy8CB2jNeM1E9i9mXMAQ=="],
|
"typescript": ["typescript@5.8.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ=="],
|
||||||
|
|
||||||
"string_decoder": ["string_decoder@1.1.1", "", { "dependencies": { "safe-buffer": "~5.1.0" } }, "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg=="],
|
|
||||||
|
|
||||||
"tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
|
|
||||||
|
|
||||||
"typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],
|
|
||||||
|
|
||||||
"undici-types": ["undici-types@7.24.6", "", {}, "sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg=="],
|
"undici-types": ["undici-types@7.24.6", "", {}, "sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg=="],
|
||||||
|
|
||||||
"util-deprecate": ["util-deprecate@1.0.2", "", {}, "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="],
|
|
||||||
|
|
||||||
"dom-serializer/entities": ["entities@4.5.0", "", {}, "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw=="],
|
"dom-serializer/entities": ["entities@4.5.0", "", {}, "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw=="],
|
||||||
|
|
||||||
"sanitize-html/htmlparser2": ["htmlparser2@12.0.0", "", { "dependencies": { "domelementtype": "^3.0.0", "domhandler": "^6.0.0", "domutils": "^4.0.2", "entities": "^8.0.0" } }, "sha512-Tz7u1i95/g2x2jz81+x0FBVhBhY5aRTvD3tXXdFaljuNdzDLJ8UGNRrTcj2cgQvAg3iW/h77Fz15nLW0L0CrZw=="],
|
"sanitize-html/htmlparser2": ["htmlparser2@12.0.0", "", { "dependencies": { "domelementtype": "^3.0.0", "domhandler": "^6.0.0", "domutils": "^4.0.2", "entities": "^8.0.0" } }, "sha512-Tz7u1i95/g2x2jz81+x0FBVhBhY5aRTvD3tXXdFaljuNdzDLJ8UGNRrTcj2cgQvAg3iW/h77Fz15nLW0L0CrZw=="],
|
||||||
|
|||||||
47
package.json
47
package.json
@ -1,26 +1,42 @@
|
|||||||
{
|
{
|
||||||
"name": "@moduletrace/bun-mariadb",
|
"name": "@moduletrace/bun-mariadb",
|
||||||
"version": "1.0.0",
|
"version": "1.0.1",
|
||||||
"description": "Mariadb manager for Bun",
|
"description": "Schema-driven MariaDB manager for Bun",
|
||||||
"author": "Benjamin Toby",
|
"author": "Benjamin Toby",
|
||||||
"main": "dist/index.js",
|
"license": "MIT",
|
||||||
|
"type": "module",
|
||||||
|
"main": "./dist/index.js",
|
||||||
|
"types": "./dist/index.d.ts",
|
||||||
|
"exports": {
|
||||||
|
".": {
|
||||||
|
"types": "./dist/index.d.ts",
|
||||||
|
"import": "./dist/index.js",
|
||||||
|
"default": "./dist/index.js"
|
||||||
|
}
|
||||||
|
},
|
||||||
"bin": {
|
"bin": {
|
||||||
"bun-mariadb": "dist/commands/index.js"
|
"bun-mariadb": "./dist/commands/index.js"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"bun": ">=1.0.0"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "tsc --watch",
|
"dev": "tsc --watch"
|
||||||
"compile": "rm -rf dist && tsc"
|
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/bun": "latest",
|
"@types/bun": "latest",
|
||||||
"@types/inquirer": "^9.0.9",
|
|
||||||
"@types/lodash": "^4.17.24",
|
"@types/lodash": "^4.17.24",
|
||||||
"@types/mysql": "^2.15.27",
|
|
||||||
"@types/node": "^25.3.3",
|
"@types/node": "^25.3.3",
|
||||||
"@types/sanitize-html": "^2.16.1"
|
"@types/sanitize-html": "^2.16.1",
|
||||||
|
"typescript": "~5.8.0"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"typescript": "^5"
|
"typescript": ">=5"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"typescript": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"files": [
|
"files": [
|
||||||
"dist",
|
"dist",
|
||||||
@ -31,14 +47,19 @@
|
|||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "git+https://git.tben.me/Moduletrace/bun-mariadb.git"
|
"url": "git+https://git.tben.me/Moduletrace/bun-mariadb.git"
|
||||||
},
|
},
|
||||||
|
"keywords": [
|
||||||
|
"bun",
|
||||||
|
"mariadb",
|
||||||
|
"schema",
|
||||||
|
"orm",
|
||||||
|
"sql",
|
||||||
|
"vector"
|
||||||
|
],
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@inquirer/prompts": "^8.3.0",
|
"@inquirer/prompts": "^8.3.0",
|
||||||
"chalk": "^5.6.2",
|
"chalk": "^5.6.2",
|
||||||
"commander": "^14.0.3",
|
"commander": "^14.0.3",
|
||||||
"inquirer": "^13.3.2",
|
|
||||||
"lodash": "^4.17.23",
|
"lodash": "^4.17.23",
|
||||||
"mariadb": "^3.5.3",
|
|
||||||
"mysql": "^2.18.1",
|
|
||||||
"sanitize-html": "^2.17.6"
|
"sanitize-html": "^2.17.6"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
13
publish.sh
13
publish.sh
@ -1,8 +1,8 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
set -e
|
set -euo pipefail
|
||||||
|
|
||||||
if [ -z "$1" ]; then
|
if [ -z "${1:-}" ]; then
|
||||||
msg="Updates"
|
msg="Updates"
|
||||||
else
|
else
|
||||||
msg="$1"
|
msg="$1"
|
||||||
@ -11,7 +11,14 @@ fi
|
|||||||
tsc --noEmit
|
tsc --noEmit
|
||||||
rm -rf dist
|
rm -rf dist
|
||||||
tsc
|
tsc
|
||||||
|
|
||||||
|
# Ensure CLI shebang survived compile
|
||||||
|
if ! head -1 dist/commands/index.js | grep -q '#!/usr/bin/env bun'; then
|
||||||
|
echo "ERROR: dist/commands/index.js is missing the bun shebang"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
git add .
|
git add .
|
||||||
git commit -m "$msg"
|
git commit -m "$msg" || true
|
||||||
git push
|
git push
|
||||||
bun publish
|
bun publish
|
||||||
|
|||||||
@ -1,5 +1,4 @@
|
|||||||
import { Command } from "commander";
|
import { Command } from "commander";
|
||||||
import grabDBDir from "../../utils/grab-db-dir";
|
|
||||||
import chalk from "chalk";
|
import chalk from "chalk";
|
||||||
import { select } from "@inquirer/prompts";
|
import { select } from "@inquirer/prompts";
|
||||||
import listTables from "./list-tables";
|
import listTables from "./list-tables";
|
||||||
@ -9,9 +8,6 @@ export default function () {
|
|||||||
return new Command("admin")
|
return new Command("admin")
|
||||||
.description("View Tables and Data, Run SQL Queries, Etc.")
|
.description("View Tables and Data, Run SQL Queries, Etc.")
|
||||||
.action(async () => {
|
.action(async () => {
|
||||||
const config = global.CONFIG;
|
|
||||||
const { db_file_path } = grabDBDir({ config });
|
|
||||||
|
|
||||||
console.log(chalk.bold(chalk.blue("\nBun MariaDB Admin\n")));
|
console.log(chalk.bold(chalk.blue("\nBun MariaDB Admin\n")));
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|||||||
@ -4,24 +4,16 @@ import { AppData } from "../../data/app-data";
|
|||||||
import showEntries from "./show-entries";
|
import showEntries from "./show-entries";
|
||||||
import showFields from "./show-fields";
|
import showFields from "./show-fields";
|
||||||
import dbHandler from "../../lib/db-handler";
|
import dbHandler from "../../lib/db-handler";
|
||||||
|
import MariaDBQuoteGen from "../../lib/schema/mariadb-quote-gen";
|
||||||
|
|
||||||
type Params = {};
|
type Params = {};
|
||||||
|
|
||||||
type ColumnInfo = {
|
|
||||||
cid: number;
|
|
||||||
name: string;
|
|
||||||
type: string;
|
|
||||||
notnull: number;
|
|
||||||
dflt_value: string | null;
|
|
||||||
pk: number;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default async function listTables(
|
export default async function listTables(
|
||||||
params?: Params,
|
params?: Params,
|
||||||
): Promise<"__exit__" | void> {
|
): Promise<"__exit__" | void> {
|
||||||
const tables = (
|
const tables = (
|
||||||
await dbHandler({
|
await dbHandler<{ table_name: string }>({
|
||||||
query: `SELECT table_name FROM ${AppData["DbSchemaManagerTableName"]}`,
|
query: `SELECT table_name FROM ${MariaDBQuoteGen(AppData["DbSchemaManagerTableName"])}`,
|
||||||
})
|
})
|
||||||
).payload;
|
).payload;
|
||||||
|
|
||||||
@ -30,7 +22,6 @@ export default async function listTables(
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Level 1: table selection loop
|
|
||||||
while (true) {
|
while (true) {
|
||||||
const tableName = await select({
|
const tableName = await select({
|
||||||
message: "Select a table:",
|
message: "Select a table:",
|
||||||
@ -47,7 +38,6 @@ export default async function listTables(
|
|||||||
if (tableName === "__back__") break;
|
if (tableName === "__back__") break;
|
||||||
if (tableName === "__exit__") return "__exit__";
|
if (tableName === "__exit__") return "__exit__";
|
||||||
|
|
||||||
// Level 2: action loop — stays here until "Go Back"
|
|
||||||
while (true) {
|
while (true) {
|
||||||
const action = await select({
|
const action = await select({
|
||||||
message: `"${tableName}" — choose an action:`,
|
message: `"${tableName}" — choose an action:`,
|
||||||
@ -73,24 +63,40 @@ export default async function listTables(
|
|||||||
if (result === "__exit__") return "__exit__";
|
if (result === "__exit__") return "__exit__";
|
||||||
}
|
}
|
||||||
|
|
||||||
// if (action === "schema") {
|
if (action === "schema") {
|
||||||
// const columns = db
|
const columns = (
|
||||||
// .query<ColumnInfo, []>(`PRAGMA table_info("${tableName}")`)
|
await dbHandler<{
|
||||||
// .all();
|
ORDINAL_POSITION: number;
|
||||||
|
COLUMN_NAME: string;
|
||||||
|
COLUMN_TYPE: string;
|
||||||
|
IS_NULLABLE: string;
|
||||||
|
COLUMN_DEFAULT: string | null;
|
||||||
|
COLUMN_KEY: string;
|
||||||
|
EXTRA: string;
|
||||||
|
}>({
|
||||||
|
query: `SELECT ORDINAL_POSITION, COLUMN_NAME, COLUMN_TYPE, IS_NULLABLE, COLUMN_DEFAULT, COLUMN_KEY, EXTRA FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = ? ORDER BY ORDINAL_POSITION`,
|
||||||
|
values: [tableName],
|
||||||
|
})
|
||||||
|
).payload;
|
||||||
|
|
||||||
// console.log(`\n${chalk.bold(`Schema for "${tableName}":`)} \n`);
|
console.log(`\n${chalk.bold(`Schema for "${tableName}":`)} \n`);
|
||||||
// console.table(
|
if (columns?.length) {
|
||||||
// columns.map((c) => ({
|
console.table(
|
||||||
// "#": c.cid,
|
columns.map((c) => ({
|
||||||
// Name: c.name,
|
"#": c.ORDINAL_POSITION,
|
||||||
// Type: c.type,
|
Name: c.COLUMN_NAME,
|
||||||
// "Not Null": c.notnull ? "YES" : "NO",
|
Type: c.COLUMN_TYPE,
|
||||||
// Default: c.dflt_value ?? "(none)",
|
Nullable: c.IS_NULLABLE,
|
||||||
// "Primary Key": c.pk ? "YES" : "NO",
|
Default: c.COLUMN_DEFAULT ?? "(none)",
|
||||||
// })),
|
Key: c.COLUMN_KEY || "",
|
||||||
// );
|
Extra: c.EXTRA || "",
|
||||||
// console.log();
|
})),
|
||||||
// }
|
);
|
||||||
|
} else {
|
||||||
|
console.log(chalk.yellow("No columns found."));
|
||||||
|
}
|
||||||
|
console.log();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -13,18 +13,19 @@ export default async function runSQL(params?: Params) {
|
|||||||
try {
|
try {
|
||||||
const res = await dbHandler({ query: sql });
|
const res = await dbHandler({ query: sql });
|
||||||
|
|
||||||
if (res.payload) {
|
if (res.payload?.length) {
|
||||||
console.log(
|
console.log(
|
||||||
`\n${chalk.bold(`Result (${res.payload.length} row${res.payload.length !== 1 ? "s" : ""}):`)} \n`,
|
`\n${chalk.bold(`Result (${res.payload.length} row${res.payload.length !== 1 ? "s" : ""}):`)} \n`,
|
||||||
);
|
);
|
||||||
if (res.payload.length) console.table(res.payload);
|
console.table(res.payload);
|
||||||
else console.log(chalk.yellow("No res returned.\n"));
|
} else if (res.success) {
|
||||||
} else if (res.single_res) {
|
|
||||||
console.log(
|
console.log(
|
||||||
chalk.green(
|
chalk.green(
|
||||||
`\nSuccess! Affected rows: ${res.single_res.changes}, Last insert ID: ${res.single_res.lastInsertRowid}\n`,
|
`\nSuccess! Affected rows: ${res.insert_return?.affected_rows ?? res.count ?? 0}, Last insert ID: ${res.insert_return?.last_insert_id ?? "—"}\n`,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
} else {
|
||||||
|
console.error(chalk.red(`\nSQL Error: ${res.error || res.msg}\n`));
|
||||||
}
|
}
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
console.error(chalk.red(`\nSQL Error: ${error.message}\n`));
|
console.error(chalk.red(`\nSQL Error: ${error.message}\n`));
|
||||||
|
|||||||
@ -1,11 +1,11 @@
|
|||||||
import chalk from "chalk";
|
import chalk from "chalk";
|
||||||
import { select, input } from "@inquirer/prompts";
|
import { select, input } from "@inquirer/prompts";
|
||||||
import dbHandler from "../../lib/db-handler";
|
import dbHandler from "../../lib/db-handler";
|
||||||
|
import MariaDBQuoteGen from "../../lib/schema/mariadb-quote-gen";
|
||||||
|
|
||||||
type Params = {
|
type Params = {
|
||||||
tableName: string;
|
tableName: string;
|
||||||
};
|
};
|
||||||
type ColumnInfo = { cid: number; name: string };
|
|
||||||
|
|
||||||
const LIMIT = 50;
|
const LIMIT = 50;
|
||||||
|
|
||||||
@ -14,36 +14,38 @@ export default async function showEntries({ tableName }: Params) {
|
|||||||
let searchField: string | null = null;
|
let searchField: string | null = null;
|
||||||
let searchTerm: string | null = null;
|
let searchTerm: string | null = null;
|
||||||
|
|
||||||
|
const quotedTable = MariaDBQuoteGen(tableName);
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
const offset = page * LIMIT;
|
const offset = page * LIMIT;
|
||||||
|
|
||||||
const rows = searchTerm
|
const rows = searchTerm
|
||||||
? (
|
? (
|
||||||
await dbHandler({
|
await dbHandler({
|
||||||
query: `SELECT * FROM "${tableName}" WHERE "${searchField}" LIKE ? LIMIT ${LIMIT} OFFSET ${offset}`,
|
query: `SELECT * FROM ${quotedTable} WHERE ${MariaDBQuoteGen(searchField!)} LIKE ? LIMIT ${LIMIT} OFFSET ${offset}`,
|
||||||
values: [`%${searchTerm}%`],
|
values: [`%${searchTerm}%`],
|
||||||
})
|
})
|
||||||
).payload
|
).payload
|
||||||
: (
|
: (
|
||||||
await dbHandler({
|
await dbHandler({
|
||||||
query: `SELECT * FROM "${tableName}" LIMIT ${LIMIT} OFFSET ${offset}`,
|
query: `SELECT * FROM ${quotedTable} LIMIT ${LIMIT} OFFSET ${offset}`,
|
||||||
})
|
})
|
||||||
).payload;
|
).payload;
|
||||||
|
|
||||||
const countRow = searchTerm
|
const countRow = searchTerm
|
||||||
? (
|
? (
|
||||||
await dbHandler({
|
await dbHandler<{ count: number }>({
|
||||||
query: `SELECT COUNT(*) as count FROM "${tableName}" WHERE "${searchField}" LIKE ?`,
|
query: `SELECT COUNT(*) as count FROM ${quotedTable} WHERE ${MariaDBQuoteGen(searchField!)} LIKE ?`,
|
||||||
values: [`%${searchTerm}%`],
|
values: [`%${searchTerm}%`],
|
||||||
})
|
})
|
||||||
).payload
|
).payload
|
||||||
: (
|
: (
|
||||||
await dbHandler({
|
await dbHandler<{ count: number }>({
|
||||||
query: `SELECT COUNT(*) as count FROM "${tableName}"`,
|
query: `SELECT COUNT(*) as count FROM ${quotedTable}`,
|
||||||
})
|
})
|
||||||
).payload;
|
).payload;
|
||||||
|
|
||||||
const total = countRow?.[0]?.count;
|
const total = Number(countRow?.[0]?.count || 0);
|
||||||
const searchInfo = searchTerm
|
const searchInfo = searchTerm
|
||||||
? chalk.dim(` · searching "${searchField}" = "${searchTerm}"`)
|
? chalk.dim(` · searching "${searchField}" = "${searchTerm}"`)
|
||||||
: "";
|
: "";
|
||||||
@ -57,8 +59,7 @@ export default async function showEntries({ tableName }: Params) {
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (rows.length) {
|
if (rows.length) {
|
||||||
console.log(rows);
|
console.table(rows);
|
||||||
// if (rows.length) console.table(rows);
|
|
||||||
} else {
|
} else {
|
||||||
console.log(chalk.yellow("No rows found."));
|
console.log(chalk.yellow("No rows found."));
|
||||||
console.log();
|
console.log();
|
||||||
@ -85,19 +86,31 @@ export default async function showEntries({ tableName }: Params) {
|
|||||||
searchTerm = null;
|
searchTerm = null;
|
||||||
page = 0;
|
page = 0;
|
||||||
}
|
}
|
||||||
// if (action === "search") {
|
if (action === "search") {
|
||||||
// const columns = db
|
const columns = (
|
||||||
// .query<ColumnInfo, []>(`PRAGMA table_info("${tableName}")`)
|
await dbHandler<{ COLUMN_NAME: string }>({
|
||||||
// .all();
|
query: `SELECT COLUMN_NAME FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = ? ORDER BY ORDINAL_POSITION`,
|
||||||
// searchField = await select({
|
values: [tableName],
|
||||||
// message: "Search by field:",
|
})
|
||||||
// choices: columns.map((c) => ({ name: c.name, value: c.name })),
|
).payload;
|
||||||
// });
|
|
||||||
// searchTerm = await input({
|
if (!columns?.length) {
|
||||||
// message: `Search term for "${searchField}":`,
|
console.log(chalk.yellow("No columns found for search."));
|
||||||
// validate: (v) => v.trim().length > 0 || "Cannot be empty",
|
continue;
|
||||||
// });
|
}
|
||||||
// page = 0;
|
|
||||||
// }
|
searchField = await select({
|
||||||
|
message: "Search by field:",
|
||||||
|
choices: columns.map((c) => ({
|
||||||
|
name: c.COLUMN_NAME,
|
||||||
|
value: c.COLUMN_NAME,
|
||||||
|
})),
|
||||||
|
});
|
||||||
|
searchTerm = await input({
|
||||||
|
message: `Search term for "${searchField}":`,
|
||||||
|
validate: (v) => v.trim().length > 0 || "Cannot be empty",
|
||||||
|
});
|
||||||
|
page = 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,92 +1,109 @@
|
|||||||
|
import chalk from "chalk";
|
||||||
|
import { select } from "@inquirer/prompts";
|
||||||
|
import dbHandler from "../../lib/db-handler";
|
||||||
|
|
||||||
type Params = {
|
type Params = {
|
||||||
tableName: string;
|
tableName: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
type ColumnInfo = {
|
|
||||||
cid: number;
|
|
||||||
name: string;
|
|
||||||
type: string;
|
|
||||||
notnull: number;
|
|
||||||
dflt_value: string | null;
|
|
||||||
pk: number;
|
|
||||||
};
|
|
||||||
|
|
||||||
type IndexInfo = { name: string; unique: number; origin: string };
|
|
||||||
type IndexColumn = { name: string };
|
|
||||||
type ForeignKey = {
|
|
||||||
id: number;
|
|
||||||
from: string;
|
|
||||||
table: string;
|
|
||||||
to: string;
|
|
||||||
on_update: string;
|
|
||||||
on_delete: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default async function showFields({
|
export default async function showFields({
|
||||||
tableName,
|
tableName,
|
||||||
}: Params): Promise<"__exit__" | void> {
|
}: Params): Promise<"__exit__" | void> {
|
||||||
// const columns = db
|
const columns = (
|
||||||
// .query<ColumnInfo, []>(`PRAGMA table_info("${tableName}")`)
|
await dbHandler<{
|
||||||
// .all();
|
ORDINAL_POSITION: number;
|
||||||
// const indexes = db
|
COLUMN_NAME: string;
|
||||||
// .query<IndexInfo, []>(`PRAGMA index_list("${tableName}")`)
|
COLUMN_TYPE: string;
|
||||||
// .all();
|
IS_NULLABLE: string;
|
||||||
// const foreignKeys = db
|
COLUMN_DEFAULT: string | null;
|
||||||
// .query<ForeignKey, []>(`PRAGMA foreign_key_list("${tableName}")`)
|
COLUMN_KEY: string;
|
||||||
// .all();
|
EXTRA: string;
|
||||||
// const indexedFields = new Map<string, { unique: boolean }>();
|
COLUMN_COMMENT: string;
|
||||||
// for (const idx of indexes) {
|
}>({
|
||||||
// const cols = db
|
query: `SELECT ORDINAL_POSITION, COLUMN_NAME, COLUMN_TYPE, IS_NULLABLE, COLUMN_DEFAULT, COLUMN_KEY, EXTRA, COLUMN_COMMENT FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = ? ORDER BY ORDINAL_POSITION`,
|
||||||
// .query<IndexColumn, []>(`PRAGMA index_info("${idx.name}")`)
|
values: [tableName],
|
||||||
// .all();
|
})
|
||||||
// for (const col of cols) {
|
).payload;
|
||||||
// indexedFields.set(col.name, { unique: idx.unique === 1 });
|
|
||||||
// }
|
if (!columns?.length) {
|
||||||
// }
|
console.log(chalk.yellow(`\nNo columns found for "${tableName}".\n`));
|
||||||
// while (true) {
|
return;
|
||||||
// const fieldName = await select({
|
}
|
||||||
// message: `"${tableName}" — select a field:`,
|
|
||||||
// choices: [
|
const indexes = (
|
||||||
// ...columns.map((c) => ({ name: c.name, value: c.name })),
|
await dbHandler<{
|
||||||
// { name: chalk.dim("← Go Back"), value: "__back__" },
|
INDEX_NAME: string;
|
||||||
// { name: chalk.dim("✕ Exit"), value: "__exit__" },
|
COLUMN_NAME: string;
|
||||||
// ],
|
NON_UNIQUE: number;
|
||||||
// });
|
INDEX_TYPE: string;
|
||||||
// if (fieldName === "__back__") break;
|
}>({
|
||||||
// if (fieldName === "__exit__") return "__exit__";
|
query: `SELECT INDEX_NAME, COLUMN_NAME, NON_UNIQUE, INDEX_TYPE FROM information_schema.STATISTICS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = ? ORDER BY INDEX_NAME, SEQ_IN_INDEX`,
|
||||||
// const col = columns.find((c) => c.name === fieldName)!;
|
values: [tableName],
|
||||||
// const idx = indexedFields.get(fieldName);
|
})
|
||||||
// const fk = foreignKeys.find((f) => f.from === fieldName);
|
).payload;
|
||||||
// console.log(`\n${chalk.bold(`Field: "${fieldName}"`)}\n`);
|
|
||||||
// console.log(` ${chalk.dim("Table")} ${tableName}`);
|
const foreignKeys = (
|
||||||
// console.log(` ${chalk.dim("Column #")} ${col.cid}`);
|
await dbHandler<{
|
||||||
// console.log(
|
CONSTRAINT_NAME: string;
|
||||||
// ` ${chalk.dim("Type")} ${col.type || chalk.italic("(none)")}`,
|
COLUMN_NAME: string;
|
||||||
// );
|
REFERENCED_TABLE_NAME: string;
|
||||||
// console.log(
|
REFERENCED_COLUMN_NAME: string;
|
||||||
// ` ${chalk.dim("Primary Key")} ${col.pk ? chalk.green("YES") : "NO"}`,
|
}>({
|
||||||
// );
|
query: `SELECT CONSTRAINT_NAME, COLUMN_NAME, REFERENCED_TABLE_NAME, REFERENCED_COLUMN_NAME FROM information_schema.KEY_COLUMN_USAGE WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = ? AND REFERENCED_TABLE_NAME IS NOT NULL`,
|
||||||
// console.log(
|
values: [tableName],
|
||||||
// ` ${chalk.dim("Not Null")} ${col.notnull ? chalk.yellow("YES") : "NO"}`,
|
})
|
||||||
// );
|
).payload;
|
||||||
// console.log(
|
|
||||||
// ` ${chalk.dim("Default")} ${col.dflt_value ?? chalk.italic("(none)")}`,
|
while (true) {
|
||||||
// );
|
const fieldName = await select({
|
||||||
// console.log(
|
message: `"${tableName}" — select a field:`,
|
||||||
// ` ${chalk.dim("Indexed")} ${idx ? chalk.cyan("YES") : "NO"}`,
|
choices: [
|
||||||
// );
|
...columns.map((c) => ({
|
||||||
// console.log(
|
name: c.COLUMN_NAME,
|
||||||
// ` ${chalk.dim("Unique")} ${idx?.unique ? chalk.cyan("YES") : "NO"}`,
|
value: c.COLUMN_NAME,
|
||||||
// );
|
})),
|
||||||
// if (fk) {
|
{ name: chalk.dim("← Go Back"), value: "__back__" },
|
||||||
// console.log(
|
{ name: chalk.dim("✕ Exit"), value: "__exit__" },
|
||||||
// ` ${chalk.dim("Foreign Key")} ${chalk.magenta(`${fk.table}(${fk.to})`)}`,
|
],
|
||||||
// );
|
});
|
||||||
// console.log(` ${chalk.dim("On Update")} ${fk.on_update}`);
|
|
||||||
// console.log(` ${chalk.dim("On Delete")} ${fk.on_delete}`);
|
if (fieldName === "__back__") break;
|
||||||
// } else {
|
if (fieldName === "__exit__") return "__exit__";
|
||||||
// console.log(` ${chalk.dim("Foreign Key")} NO`);
|
|
||||||
// }
|
const col = columns.find((c) => c.COLUMN_NAME === fieldName)!;
|
||||||
// console.log();
|
const colIndexes =
|
||||||
// }
|
indexes?.filter((i) => i.COLUMN_NAME === fieldName) || [];
|
||||||
|
const fk = foreignKeys?.find((f) => f.COLUMN_NAME === fieldName);
|
||||||
|
|
||||||
|
console.log(`\n${chalk.bold(`Field: "${fieldName}"`)}\n`);
|
||||||
|
console.log(` ${chalk.dim("Table")} ${tableName}`);
|
||||||
|
console.log(` ${chalk.dim("Column #")} ${col.ORDINAL_POSITION}`);
|
||||||
|
console.log(` ${chalk.dim("Type")} ${col.COLUMN_TYPE}`);
|
||||||
|
console.log(
|
||||||
|
` ${chalk.dim("Primary Key")} ${col.COLUMN_KEY === "PRI" ? chalk.green("YES") : "NO"}`,
|
||||||
|
);
|
||||||
|
console.log(
|
||||||
|
` ${chalk.dim("Not Null")} ${col.IS_NULLABLE === "NO" ? chalk.yellow("YES") : "NO"}`,
|
||||||
|
);
|
||||||
|
console.log(
|
||||||
|
` ${chalk.dim("Default")} ${col.COLUMN_DEFAULT ?? chalk.italic("(none)")}`,
|
||||||
|
);
|
||||||
|
console.log(
|
||||||
|
` ${chalk.dim("Extra")} ${col.EXTRA || chalk.italic("(none)")}`,
|
||||||
|
);
|
||||||
|
console.log(
|
||||||
|
` ${chalk.dim("Indexed")} ${colIndexes.length ? chalk.cyan(colIndexes.map((i) => i.INDEX_NAME).join(", ")) : "NO"}`,
|
||||||
|
);
|
||||||
|
if (fk) {
|
||||||
|
console.log(
|
||||||
|
` ${chalk.dim("Foreign Key")} ${chalk.magenta(`${fk.REFERENCED_TABLE_NAME}(${fk.REFERENCED_COLUMN_NAME})`)}`,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
console.log(` ${chalk.dim("Foreign Key")} NO`);
|
||||||
|
}
|
||||||
|
if (col.COLUMN_COMMENT) {
|
||||||
|
console.log(` ${chalk.dim("Comment")} ${col.COLUMN_COMMENT}`);
|
||||||
|
}
|
||||||
|
console.log();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,28 +1,98 @@
|
|||||||
import { Command } from "commander";
|
import { Command } from "commander";
|
||||||
import path from "path";
|
import path from "path";
|
||||||
import grabDBDir from "../utils/grab-db-dir";
|
|
||||||
import fs from "fs";
|
import fs from "fs";
|
||||||
|
import grabDBDir from "../utils/grab-db-dir";
|
||||||
import grabDBBackupFileName from "../utils/grab-db-backup-file-name";
|
import grabDBBackupFileName from "../utils/grab-db-backup-file-name";
|
||||||
import chalk from "chalk";
|
import chalk from "chalk";
|
||||||
import trimBackups from "../utils/trim-backups";
|
import trimBackups from "../utils/trim-backups";
|
||||||
|
import mariadbCliEnv, {
|
||||||
|
mariadbCliConnectionArgs,
|
||||||
|
} from "../utils/mariadb-cli-env";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prefer mariadb-dump, fall back to mysqldump.
|
||||||
|
*/
|
||||||
|
function resolveDumpBinary(): string {
|
||||||
|
const candidates = ["mariadb-dump", "mysqldump"];
|
||||||
|
for (const bin of candidates) {
|
||||||
|
try {
|
||||||
|
const result = Bun.spawnSync(["which", bin], {
|
||||||
|
stdout: "pipe",
|
||||||
|
stderr: "pipe",
|
||||||
|
});
|
||||||
|
if (result.exitCode === 0) {
|
||||||
|
return new TextDecoder().decode(result.stdout).trim() || bin;
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// try next
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "mariadb-dump";
|
||||||
|
}
|
||||||
|
|
||||||
export default function () {
|
export default function () {
|
||||||
return new Command("backup")
|
return new Command("backup")
|
||||||
.description("Backup Database")
|
.description("Backup MariaDB database (SQL dump)")
|
||||||
.action(async (opts) => {
|
.action(async () => {
|
||||||
console.log(`Backing up database ...`);
|
console.log(`Backing up database ...`);
|
||||||
|
|
||||||
const config = global.CONFIG;
|
const config = global.CONFIG;
|
||||||
|
const { backup_dir } = grabDBDir({ config });
|
||||||
|
|
||||||
const { backup_dir, db_file_path } = grabDBDir({ config });
|
if (!fs.existsSync(backup_dir)) {
|
||||||
|
fs.mkdirSync(backup_dir, { recursive: true });
|
||||||
|
}
|
||||||
|
|
||||||
const new_db_file_name = grabDBBackupFileName({ config });
|
const backup_file_name = grabDBBackupFileName({ config });
|
||||||
|
const backup_path = path.join(backup_dir, backup_file_name);
|
||||||
|
|
||||||
fs.cpSync(db_file_path, path.join(backup_dir, new_db_file_name));
|
const dumpBin = resolveDumpBinary();
|
||||||
|
const args = [
|
||||||
|
dumpBin,
|
||||||
|
...mariadbCliConnectionArgs(),
|
||||||
|
"--single-transaction",
|
||||||
|
"--routines",
|
||||||
|
"--triggers",
|
||||||
|
"--events",
|
||||||
|
config.db_name,
|
||||||
|
];
|
||||||
|
|
||||||
trimBackups({ config });
|
try {
|
||||||
|
const proc = Bun.spawn(args, {
|
||||||
|
stdout: "pipe",
|
||||||
|
stderr: "pipe",
|
||||||
|
env: mariadbCliEnv(),
|
||||||
|
});
|
||||||
|
|
||||||
console.log(`${chalk.bold(chalk.green(`DB Backup Success!`))}`);
|
const [stdout, stderr, exitCode] = await Promise.all([
|
||||||
process.exit();
|
new Response(proc.stdout).text(),
|
||||||
|
new Response(proc.stderr).text(),
|
||||||
|
proc.exited,
|
||||||
|
]);
|
||||||
|
|
||||||
|
if (exitCode !== 0) {
|
||||||
|
console.error(
|
||||||
|
chalk.red(
|
||||||
|
`Backup failed (exit ${exitCode}): ${stderr || "unknown error"}`,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
fs.writeFileSync(backup_path, stdout, "utf-8");
|
||||||
|
trimBackups({ config });
|
||||||
|
|
||||||
|
console.log(
|
||||||
|
`${chalk.bold(chalk.green(`DB Backup Success!`))} → ${backup_path}`,
|
||||||
|
);
|
||||||
|
process.exit(0);
|
||||||
|
} catch (error: any) {
|
||||||
|
console.error(
|
||||||
|
chalk.red(
|
||||||
|
`Backup ERROR => ${error.message}. Ensure \`mariadb-dump\` or \`mysqldump\` is installed.`,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@ -6,22 +6,47 @@ import grabSortedBackups from "../utils/grab-sorted-backups";
|
|||||||
import { select } from "@inquirer/prompts";
|
import { select } from "@inquirer/prompts";
|
||||||
import grabBackupData from "../utils/grab-backup-data";
|
import grabBackupData from "../utils/grab-backup-data";
|
||||||
import path from "path";
|
import path from "path";
|
||||||
|
import mariadbCliEnv, {
|
||||||
|
mariadbCliConnectionArgs,
|
||||||
|
} from "../utils/mariadb-cli-env";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prefer mariadb client, fall back to mysql.
|
||||||
|
*/
|
||||||
|
function resolveClientBinary(): string {
|
||||||
|
const candidates = ["mariadb", "mysql"];
|
||||||
|
for (const bin of candidates) {
|
||||||
|
try {
|
||||||
|
const result = Bun.spawnSync(["which", bin], {
|
||||||
|
stdout: "pipe",
|
||||||
|
stderr: "pipe",
|
||||||
|
});
|
||||||
|
if (result.exitCode === 0) {
|
||||||
|
return new TextDecoder().decode(result.stdout).trim() || bin;
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// try next
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "mariadb";
|
||||||
|
}
|
||||||
|
|
||||||
export default function () {
|
export default function () {
|
||||||
return new Command("restore")
|
return new Command("restore")
|
||||||
.description("Restore Database")
|
.description("Restore MariaDB database from an SQL dump")
|
||||||
.action(async (opts) => {
|
.action(async () => {
|
||||||
console.log(`Restoring up database ...`);
|
console.log(`Restoring database ...`);
|
||||||
|
|
||||||
const config = global.CONFIG;
|
const config = global.CONFIG;
|
||||||
|
const { backup_dir } = grabDBDir({ config });
|
||||||
|
|
||||||
const { backup_dir, db_file_path } = grabDBDir({ config });
|
const backups = grabSortedBackups({ config }).filter((b) =>
|
||||||
|
b.endsWith(".sql"),
|
||||||
const backups = grabSortedBackups({ config });
|
);
|
||||||
|
|
||||||
if (!backups?.[0]) {
|
if (!backups?.[0]) {
|
||||||
console.error(
|
console.error(
|
||||||
`No Backups to restore. Use the \`backup\` command to create a backup`,
|
`No SQL backups to restore. Use the \`backup\` command to create a backup.`,
|
||||||
);
|
);
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
@ -40,16 +65,49 @@ export default function () {
|
|||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
fs.cpSync(path.join(backup_dir, selected_backup), db_file_path);
|
const backup_path = path.join(backup_dir, selected_backup);
|
||||||
|
if (!fs.existsSync(backup_path)) {
|
||||||
|
console.error(`Backup file not found: ${backup_path}`);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
const clientBin = resolveClientBinary();
|
||||||
|
const sql = fs.readFileSync(backup_path, "utf-8");
|
||||||
|
|
||||||
|
const args = [
|
||||||
|
clientBin,
|
||||||
|
...mariadbCliConnectionArgs(),
|
||||||
|
config.db_name,
|
||||||
|
];
|
||||||
|
|
||||||
|
const proc = Bun.spawn(args, {
|
||||||
|
stdin: new Blob([sql]),
|
||||||
|
stdout: "pipe",
|
||||||
|
stderr: "pipe",
|
||||||
|
env: mariadbCliEnv(),
|
||||||
|
});
|
||||||
|
|
||||||
|
const [stderr, exitCode] = await Promise.all([
|
||||||
|
new Response(proc.stderr).text(),
|
||||||
|
proc.exited,
|
||||||
|
]);
|
||||||
|
|
||||||
|
if (exitCode !== 0) {
|
||||||
|
console.error(
|
||||||
|
chalk.red(
|
||||||
|
`Restore failed (exit ${exitCode}): ${stderr || "unknown error"}`,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
console.log(
|
console.log(
|
||||||
`${chalk.bold(chalk.green(`DB Restore Success!`))}`,
|
`${chalk.bold(chalk.green(`DB Restore Success!`))} ← ${selected_backup}`,
|
||||||
);
|
);
|
||||||
|
process.exit(0);
|
||||||
process.exit();
|
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
console.error(`Backup Restore ERROR => ${error.message}`);
|
console.error(`Backup Restore ERROR => ${error.message}`);
|
||||||
process.exit();
|
process.exit(1);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,9 +1,7 @@
|
|||||||
import { Command } from "commander";
|
import { Command } from "commander";
|
||||||
import { MariaDBSchemaManager } from "../lib/mariadb/db-schema-manager";
|
|
||||||
import grabDirNames from "../data/grab-dir-names";
|
import grabDirNames from "../data/grab-dir-names";
|
||||||
import path from "path";
|
import path from "path";
|
||||||
import dbSchemaToTypeDef from "../lib/mariadb/schema-to-typedef";
|
import dbSchemaToTypeDef from "../lib/mariadb/schema-to-typedef";
|
||||||
import _ from "lodash";
|
|
||||||
import appendDefaultFieldsToDbSchema from "../utils/append-default-fields-to-db-schema";
|
import appendDefaultFieldsToDbSchema from "../utils/append-default-fields-to-db-schema";
|
||||||
import chalk from "chalk";
|
import chalk from "chalk";
|
||||||
import { writeLiveSchema } from "../functions/live-schema";
|
import { writeLiveSchema } from "../functions/live-schema";
|
||||||
@ -12,10 +10,6 @@ import createDBSchema from "../lib/schema/create-db-schema";
|
|||||||
export default function () {
|
export default function () {
|
||||||
return new Command("schema")
|
return new Command("schema")
|
||||||
.description("Build DB From Schema")
|
.description("Build DB From Schema")
|
||||||
.option(
|
|
||||||
"-v, --vector",
|
|
||||||
"Recreate Vector Tables. This will drop and rebuild all vector tables",
|
|
||||||
)
|
|
||||||
.option("-t, --typedef", "Generate typescript type definitions")
|
.option("-t, --typedef", "Generate typescript type definitions")
|
||||||
.action(async (opts) => {
|
.action(async (opts) => {
|
||||||
console.log(`Starting process ...`);
|
console.log(`Starting process ...`);
|
||||||
@ -32,14 +26,10 @@ export default function () {
|
|||||||
dbSchema,
|
dbSchema,
|
||||||
});
|
});
|
||||||
|
|
||||||
// const manager = new MariaDBSchemaManager({
|
await createDBSchema({
|
||||||
// schema: finaldbSchema,
|
db_schema: finaldbSchema,
|
||||||
// });
|
config,
|
||||||
|
});
|
||||||
// await manager.syncSchema();
|
|
||||||
// manager.close();
|
|
||||||
|
|
||||||
await createDBSchema({ db_schema: finaldbSchema });
|
|
||||||
|
|
||||||
if (isTypeDef && config.typedef_file_path) {
|
if (isTypeDef && config.typedef_file_path) {
|
||||||
const out_file = path.resolve(
|
const out_file = path.resolve(
|
||||||
@ -59,9 +49,9 @@ export default function () {
|
|||||||
console.log(
|
console.log(
|
||||||
`${chalk.bold(chalk.green(`DB Schema setup success!`))}`,
|
`${chalk.bold(chalk.green(`DB Schema setup success!`))}`,
|
||||||
);
|
);
|
||||||
process.exit();
|
process.exit(0);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(error);
|
console.error(error);
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@ -7,8 +7,8 @@ import chalk from "chalk";
|
|||||||
|
|
||||||
export default function () {
|
export default function () {
|
||||||
return new Command("typedef")
|
return new Command("typedef")
|
||||||
.description("Build DB From Schema")
|
.description("Generate TypeScript type definitions from the schema")
|
||||||
.action(async (opts) => {
|
.action(async () => {
|
||||||
console.log(`Creating Type Definition From DB Schema ...`);
|
console.log(`Creating Type Definition From DB Schema ...`);
|
||||||
|
|
||||||
const config = global.CONFIG;
|
const config = global.CONFIG;
|
||||||
@ -18,23 +18,21 @@ export default function () {
|
|||||||
|
|
||||||
const finaldbSchema = appendDefaultFieldsToDbSchema({ dbSchema });
|
const finaldbSchema = appendDefaultFieldsToDbSchema({ dbSchema });
|
||||||
|
|
||||||
if (config.typedef_file_path) {
|
if (!config.typedef_file_path) {
|
||||||
const out_file = path.resolve(
|
console.error(
|
||||||
ROOT_DIR,
|
`\`typedef_file_path\` is required in bun-mariadb.config.ts to generate types.`,
|
||||||
config.typedef_file_path,
|
|
||||||
);
|
);
|
||||||
dbSchemaToTypeDef({
|
|
||||||
dbSchema: finaldbSchema,
|
|
||||||
dst_file: out_file,
|
|
||||||
config,
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
console.error(``);
|
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(`${chalk.bold(chalk.green(`Typedef gen success!`))}`);
|
const out_file = path.resolve(ROOT_DIR, config.typedef_file_path);
|
||||||
|
dbSchemaToTypeDef({
|
||||||
|
dbSchema: finaldbSchema,
|
||||||
|
dst_file: out_file,
|
||||||
|
config,
|
||||||
|
});
|
||||||
|
|
||||||
process.exit();
|
console.log(`${chalk.bold(chalk.green(`Typedef gen success!`))}`);
|
||||||
|
process.exit(0);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@ -9,10 +9,6 @@ export default function grabDirNames(params?: Params) {
|
|||||||
const ROOT_DIR = process.cwd();
|
const ROOT_DIR = process.cwd();
|
||||||
const BUN_MARIADB_DIR = path.join(ROOT_DIR, ".bun-mariadb");
|
const BUN_MARIADB_DIR = path.join(ROOT_DIR, ".bun-mariadb");
|
||||||
const BUN_MARIADB_TEMP_DIR = path.join(BUN_MARIADB_DIR, ".tmp");
|
const BUN_MARIADB_TEMP_DIR = path.join(BUN_MARIADB_DIR, ".tmp");
|
||||||
const BUN_MARIADB_TEMP_DB_FILE_PATH = path.join(
|
|
||||||
BUN_MARIADB_TEMP_DIR,
|
|
||||||
"temp.db",
|
|
||||||
);
|
|
||||||
const BUN_MARIADB_LIVE_SCHEMA = path.join(
|
const BUN_MARIADB_LIVE_SCHEMA = path.join(
|
||||||
BUN_MARIADB_DIR,
|
BUN_MARIADB_DIR,
|
||||||
"live-schema.json",
|
"live-schema.json",
|
||||||
@ -23,6 +19,5 @@ export default function grabDirNames(params?: Params) {
|
|||||||
BUN_MARIADB_DIR,
|
BUN_MARIADB_DIR,
|
||||||
BUN_MARIADB_TEMP_DIR,
|
BUN_MARIADB_TEMP_DIR,
|
||||||
BUN_MARIADB_LIVE_SCHEMA,
|
BUN_MARIADB_LIVE_SCHEMA,
|
||||||
BUN_MARIADB_TEMP_DB_FILE_PATH,
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@ -7,12 +7,40 @@ type Params = {
|
|||||||
config: BunMariaDBConfig;
|
config: BunMariaDBConfig;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/** Reuse one client per database name to avoid exhausting MariaDB connections. */
|
||||||
|
const clientCache = new Map<string, Bun.SQL>();
|
||||||
|
|
||||||
export default function grabMariaDBClient({
|
export default function grabMariaDBClient({
|
||||||
config,
|
config,
|
||||||
}: Params): Bun.SQL | undefined {
|
}: Params): Bun.SQL | undefined {
|
||||||
|
const cacheKey = config.db_name || "__default__";
|
||||||
|
|
||||||
|
const cached = clientCache.get(cacheKey);
|
||||||
|
if (cached) {
|
||||||
|
return cached;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prefer the process-wide client when it matches the requested database
|
||||||
|
if (
|
||||||
|
global.MARIADB_CLIENT &&
|
||||||
|
(!global.CONFIG?.db_name || global.CONFIG.db_name === config.db_name)
|
||||||
|
) {
|
||||||
|
clientCache.set(cacheKey, global.MARIADB_CLIENT);
|
||||||
|
return global.MARIADB_CLIENT;
|
||||||
|
}
|
||||||
|
|
||||||
const { ROOT_DIR } = grabDirNames();
|
const { ROOT_DIR } = grabDirNames();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
const tls = config.ssl_ca
|
||||||
|
? {
|
||||||
|
ca: Bun.file(path.resolve(ROOT_DIR, config.ssl_ca)),
|
||||||
|
rejectUnauthorized: false,
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
rejectUnauthorized: false,
|
||||||
|
};
|
||||||
|
|
||||||
const MariaDBClient = new SQL({
|
const MariaDBClient = new SQL({
|
||||||
hostname: process.env.BUN_MARIADB_SERVER_HOST,
|
hostname: process.env.BUN_MARIADB_SERVER_HOST,
|
||||||
username: process.env.BUN_MARIADB_SERVER_USERNAME,
|
username: process.env.BUN_MARIADB_SERVER_USERNAME,
|
||||||
@ -22,17 +50,11 @@ export default function grabMariaDBClient({
|
|||||||
? Number(process.env.BUN_MARIADB_SERVER_PORT)
|
? Number(process.env.BUN_MARIADB_SERVER_PORT)
|
||||||
: undefined,
|
: undefined,
|
||||||
...config.db_config,
|
...config.db_config,
|
||||||
tls: config.ssl_ca
|
tls,
|
||||||
? {
|
|
||||||
ca: Bun.file(path.resolve(ROOT_DIR, config.ssl_ca)),
|
|
||||||
rejectUnauthorized: false,
|
|
||||||
}
|
|
||||||
: {
|
|
||||||
rejectUnauthorized: false,
|
|
||||||
},
|
|
||||||
adapter: "mariadb",
|
adapter: "mariadb",
|
||||||
});
|
} as Bun.SQL.Options);
|
||||||
|
|
||||||
|
clientCache.set(cacheKey, MariaDBClient);
|
||||||
return MariaDBClient;
|
return MariaDBClient;
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
console.error(`Couldn't grab MariaDB Client => ` + error.message);
|
console.error(`Couldn't grab MariaDB Client => ` + error.message);
|
||||||
|
|||||||
@ -7,7 +7,6 @@ import {
|
|||||||
type BUN_MARIADB_DatabaseSchemaType,
|
type BUN_MARIADB_DatabaseSchemaType,
|
||||||
RequiredENVs,
|
RequiredENVs,
|
||||||
} from "../types";
|
} from "../types";
|
||||||
import { SQL } from "bun";
|
|
||||||
import setMariaDBClient from "./set-mariadb-client";
|
import setMariaDBClient from "./set-mariadb-client";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -70,8 +69,8 @@ export default function init(): void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const db_dir = path.resolve(ROOT_DIR, Config.db_dir);
|
const db_dir = path.resolve(ROOT_DIR, Config.db_dir);
|
||||||
if (!fs.existsSync(Config.db_dir)) {
|
if (!fs.existsSync(db_dir)) {
|
||||||
fs.mkdirSync(Config.db_dir, { recursive: true });
|
fs.mkdirSync(db_dir, { recursive: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
const DBSchemaFilePath = path.join(db_dir, AppData["DbSchemaFileName"]);
|
const DBSchemaFilePath = path.join(db_dir, AppData["DbSchemaFileName"]);
|
||||||
|
|||||||
12
src/index.ts
12
src/index.ts
@ -22,3 +22,15 @@ const BunMariaDB = {
|
|||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
export default BunMariaDB;
|
export default BunMariaDB;
|
||||||
|
|
||||||
|
export type {
|
||||||
|
BunMariaDBConfig,
|
||||||
|
BUN_MARIADB_DatabaseSchemaType,
|
||||||
|
BUN_MARIADB_TableSchemaType,
|
||||||
|
BUN_MARIADB_FieldSchemaType,
|
||||||
|
BUN_MARIADB_IndexSchemaType,
|
||||||
|
BUN_MARIADB_UniqueConstraintSchemaType,
|
||||||
|
BUN_MARIADB_ForeignKeyType,
|
||||||
|
DBResponseObject,
|
||||||
|
ServerQueryParam,
|
||||||
|
} from "./types";
|
||||||
|
|||||||
@ -11,17 +11,38 @@ type Param = {
|
|||||||
config?: BunMariaDBConfig;
|
config?: BunMariaDBConfig;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolve a shared MariaDB client. Prefer the global client so schema sync
|
||||||
|
* and CRUD do not open a new connection per query.
|
||||||
|
*/
|
||||||
|
function resolveClient(config?: BunMariaDBConfig): Bun.SQL | undefined {
|
||||||
|
const resolvedConfig = config || global.CONFIG;
|
||||||
|
|
||||||
|
// Same database as the global client → reuse it
|
||||||
|
if (
|
||||||
|
global.MARIADB_CLIENT &&
|
||||||
|
(!resolvedConfig?.db_name ||
|
||||||
|
!global.CONFIG?.db_name ||
|
||||||
|
resolvedConfig.db_name === global.CONFIG.db_name)
|
||||||
|
) {
|
||||||
|
return global.MARIADB_CLIENT;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (resolvedConfig) {
|
||||||
|
return grabMariaDBClient({ config: resolvedConfig });
|
||||||
|
}
|
||||||
|
|
||||||
|
return global.MARIADB_CLIENT;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* # Main DB Handler Function
|
* # Main DB Handler Function
|
||||||
* @requires DSQL_DB_CONN - Gobal Variable for Datasquirel Database
|
|
||||||
*/
|
*/
|
||||||
export default async function dbHandler<
|
export default async function dbHandler<
|
||||||
T extends { [k: string]: any } = { [k: string]: any },
|
T extends { [k: string]: any } = { [k: string]: any },
|
||||||
>({ query, values, config }: Param): Promise<DBResponseObject<T>> {
|
>({ query, values, config }: Param): Promise<DBResponseObject<T>> {
|
||||||
try {
|
try {
|
||||||
const CLIENT = config
|
const CLIENT = resolveClient(config);
|
||||||
? grabMariaDBClient({ config })
|
|
||||||
: global.MARIADB_CLIENT;
|
|
||||||
|
|
||||||
if (!CLIENT) {
|
if (!CLIENT) {
|
||||||
throw new Error(`Couldn't grab MariaDB Client.`);
|
throw new Error(`Couldn't grab MariaDB Client.`);
|
||||||
@ -61,6 +82,5 @@ export default async function dbHandler<
|
|||||||
error: "DB Handler Error => " + error.message,
|
error: "DB Handler Error => " + error.message,
|
||||||
msg: "DB Handler Error => " + error.message,
|
msg: "DB Handler Error => " + error.message,
|
||||||
};
|
};
|
||||||
} finally {
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,29 +0,0 @@
|
|||||||
import fs from "fs";
|
|
||||||
import type { ConnectionConfig } from "mariadb";
|
|
||||||
import path from "path";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* # Grab SSL
|
|
||||||
*/
|
|
||||||
export default function grabDbSSL(): ConnectionConfig["ssl"] {
|
|
||||||
const caProivdedPath =
|
|
||||||
global.CONFIG.ssl_ca || process.env.BUN_MARIADB_SERVER_SSL_KEY_PATH;
|
|
||||||
|
|
||||||
if (!caProivdedPath?.match(/./)) {
|
|
||||||
return {
|
|
||||||
rejectUnauthorized: false,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const ca_file_path = path.resolve(process.cwd(), caProivdedPath);
|
|
||||||
|
|
||||||
if (!fs.existsSync(ca_file_path)) {
|
|
||||||
console.log(`${ca_file_path} does not exist`);
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
ca: fs.readFileSync(ca_file_path),
|
|
||||||
rejectUnauthorized: false,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
File diff suppressed because it is too large
Load Diff
@ -1,49 +0,0 @@
|
|||||||
// import { SQL } from "bun";
|
|
||||||
// import grabDirNames from "../../data/grab-dir-names";
|
|
||||||
// import path from "path";
|
|
||||||
|
|
||||||
// if (!global.CONFIG) {
|
|
||||||
// console.error(`Couldn't grab global Config.`);
|
|
||||||
// process.exit(1);
|
|
||||||
// }
|
|
||||||
|
|
||||||
// if (!global.DB_SCHEMA) {
|
|
||||||
// console.error(`Couldn't grab Database Schema.`);
|
|
||||||
// process.exit(1);
|
|
||||||
// }
|
|
||||||
|
|
||||||
// const config = global.CONFIG;
|
|
||||||
|
|
||||||
// const { ROOT_DIR } = grabDirNames();
|
|
||||||
|
|
||||||
// const MariaDBClient = new SQL({
|
|
||||||
// hostname: process.env.BUN_MARIADB_SERVER_HOST,
|
|
||||||
// username: process.env.BUN_MARIADB_SERVER_USERNAME,
|
|
||||||
// password: process.env.BUN_MARIADB_SERVER_PASSWORD,
|
|
||||||
// database: config.db_name,
|
|
||||||
// port: process.env.BUN_MARIADB_SERVER_PORT
|
|
||||||
// ? Number(process.env.BUN_MARIADB_SERVER_PORT)
|
|
||||||
// : undefined,
|
|
||||||
// ...config.db_config,
|
|
||||||
// tls: config.ssl_ca
|
|
||||||
// ? {
|
|
||||||
// ca: Bun.file(path.resolve(ROOT_DIR, config.ssl_ca)),
|
|
||||||
// rejectUnauthorized: false,
|
|
||||||
// }
|
|
||||||
// : {
|
|
||||||
// rejectUnauthorized: false,
|
|
||||||
// },
|
|
||||||
// adapter: "mariadb",
|
|
||||||
// });
|
|
||||||
|
|
||||||
// global.MARIADB_CLIENT = MariaDBClient;
|
|
||||||
|
|
||||||
// const test = await MariaDBClient.unsafe(`SHOW DATABASES`);
|
|
||||||
|
|
||||||
// if (!test.count) {
|
|
||||||
// console.error(`MariaDBClient Error: Database not ready.`);
|
|
||||||
// console.log(test);
|
|
||||||
// process.exit(1);
|
|
||||||
// }
|
|
||||||
|
|
||||||
// export default MariaDBClient;
|
|
||||||
@ -1,4 +1,5 @@
|
|||||||
import type { BUN_MARIADB_FieldSchemaType } from "../../types";
|
import type { BUN_MARIADB_FieldSchemaType } from "../../types";
|
||||||
|
import isVectorField from "./is-vector-field";
|
||||||
import mapDataType from "./map-data-types";
|
import mapDataType from "./map-data-types";
|
||||||
import MariaDBQuoteGen from "./mariadb-quote-gen";
|
import MariaDBQuoteGen from "./mariadb-quote-gen";
|
||||||
|
|
||||||
@ -16,13 +17,19 @@ export default function buildColumnDefinition(
|
|||||||
parts.push("AUTO_INCREMENT");
|
parts.push("AUTO_INCREMENT");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (field.notNullValue || field.primaryKey || field.isVector) {
|
// Vector columns used in VECTOR INDEX must be NOT NULL
|
||||||
|
if (
|
||||||
|
field.notNullValue ||
|
||||||
|
field.primaryKey ||
|
||||||
|
isVectorField(field)
|
||||||
|
) {
|
||||||
if (!field.primaryKey) {
|
if (!field.primaryKey) {
|
||||||
parts.push("NOT NULL");
|
parts.push("NOT NULL");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (field.unique && !field.primaryKey) {
|
// VECTOR columns cannot be UNIQUE in the usual sense
|
||||||
|
if (field.unique && !field.primaryKey && !isVectorField(field)) {
|
||||||
parts.push("UNIQUE");
|
parts.push("UNIQUE");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -19,26 +19,23 @@ export default async function handleDBSchemaTable({
|
|||||||
}: CreateDBSchemaTableHandlerParams) {
|
}: CreateDBSchemaTableHandlerParams) {
|
||||||
const resolvedTable = resolveTable(table, db_schema);
|
const resolvedTable = resolveTable(table, db_schema);
|
||||||
|
|
||||||
const schemaCond = schemaCondition(config);
|
|
||||||
const liveTables = await querySchemaRows<{ TABLE_NAME: string }>({
|
|
||||||
query: `SELECT TABLE_NAME FROM information_schema.TABLES WHERE ${schemaCond.where} AND TABLE_TYPE = 'BASE TABLE'`,
|
|
||||||
values: schemaCond.values,
|
|
||||||
config,
|
|
||||||
});
|
|
||||||
const liveTableNames = liveTables.map((t) => t.TABLE_NAME);
|
|
||||||
|
|
||||||
let tableExistsTracked = Boolean(db_manager_table_name);
|
let tableExistsTracked = Boolean(db_manager_table_name);
|
||||||
let tableExistsLive = Boolean(
|
let tableExistsLive = Boolean(existing_live_table?.TABLE_NAME);
|
||||||
existing_live_table?.TABLE_NAME ||
|
|
||||||
liveTableNames.includes(resolvedTable.tableName),
|
|
||||||
);
|
|
||||||
let wasRenamed = false;
|
let wasRenamed = false;
|
||||||
|
|
||||||
if (
|
if (
|
||||||
resolvedTable.tableNameOld &&
|
resolvedTable.tableNameOld &&
|
||||||
resolvedTable.tableNameOld !== resolvedTable.tableName
|
resolvedTable.tableNameOld !== resolvedTable.tableName
|
||||||
) {
|
) {
|
||||||
if (liveTableNames.includes(resolvedTable.tableNameOld)) {
|
// Only hit information_schema when a rename is declared
|
||||||
|
const schemaCond = schemaCondition(config);
|
||||||
|
const liveTables = await querySchemaRows<{ TABLE_NAME: string }>({
|
||||||
|
query: `SELECT TABLE_NAME FROM information_schema.TABLES WHERE ${schemaCond.where} AND TABLE_TYPE = 'BASE TABLE' AND TABLE_NAME = ?`,
|
||||||
|
values: [...schemaCond.values, resolvedTable.tableNameOld],
|
||||||
|
config,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (liveTables.length > 0) {
|
||||||
console.log(
|
console.log(
|
||||||
`Renaming table: ${resolvedTable.tableNameOld} -> ${resolvedTable.tableName}`,
|
`Renaming table: ${resolvedTable.tableNameOld} -> ${resolvedTable.tableName}`,
|
||||||
);
|
);
|
||||||
@ -68,7 +65,10 @@ export default async function handleDBSchemaTable({
|
|||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
if (!wasRenamed) {
|
if (!wasRenamed) {
|
||||||
await updateTable({ table: resolvedTable, config });
|
await updateTable({
|
||||||
|
table: resolvedTable,
|
||||||
|
config,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
await upsertDbManagerTable({
|
await upsertDbManagerTable({
|
||||||
tableName: resolvedTable.tableName,
|
tableName: resolvedTable.tableName,
|
||||||
|
|||||||
11
src/lib/schema/is-vector-field.ts
Normal file
11
src/lib/schema/is-vector-field.ts
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
import type { BUN_MARIADB_FieldSchemaType } from "../../types";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* True when a field is a MariaDB native vector column.
|
||||||
|
*/
|
||||||
|
export default function isVectorField(
|
||||||
|
field?: BUN_MARIADB_FieldSchemaType,
|
||||||
|
): boolean {
|
||||||
|
if (!field) return false;
|
||||||
|
return field.isVector === true || field.dataType === "VECTOR";
|
||||||
|
}
|
||||||
@ -6,8 +6,9 @@ export default function mapDataType(
|
|||||||
const dataType = field.dataType?.toUpperCase() || "TEXT";
|
const dataType = field.dataType?.toUpperCase() || "TEXT";
|
||||||
const vectorSize = field.vectorSize || 1536;
|
const vectorSize = field.vectorSize || 1536;
|
||||||
|
|
||||||
if (field.isVector) {
|
// Native MariaDB VECTOR type (11.7+). Prefer this over LONGTEXT storage.
|
||||||
return `LONGTEXT COMMENT 'vector_size=${vectorSize}'`;
|
if (field.isVector || dataType === "VECTOR") {
|
||||||
|
return `VECTOR(${vectorSize})`;
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (dataType) {
|
switch (dataType) {
|
||||||
@ -78,10 +79,6 @@ export default function mapDataType(
|
|||||||
return "JSON";
|
return "JSON";
|
||||||
case "INET6":
|
case "INET6":
|
||||||
return "INET6";
|
return "INET6";
|
||||||
case "VECTOR": {
|
|
||||||
const dimensions = field.vectorSize || 1536;
|
|
||||||
return `VECTOR(${dimensions})`;
|
|
||||||
}
|
|
||||||
case "BOOLEAN":
|
case "BOOLEAN":
|
||||||
return "TINYINT(1)";
|
return "TINYINT(1)";
|
||||||
case "ENUM": {
|
case "ENUM": {
|
||||||
|
|||||||
@ -25,6 +25,10 @@ async function checkIfTableExists({
|
|||||||
return Boolean(rows[0]?.table_exists);
|
return Boolean(rows[0]?.table_exists);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Full table rebuild. For `isVector` tables this drops and recreates in place
|
||||||
|
* (preserving rows when possible). For regular tables it uses a temp-table swap.
|
||||||
|
*/
|
||||||
export default async function recreateTable({
|
export default async function recreateTable({
|
||||||
table,
|
table,
|
||||||
config,
|
config,
|
||||||
@ -42,6 +46,58 @@ export default async function recreateTable({
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Vector tables: drop + recreate + reinsert (MariaDB VECTOR INDEX / dim
|
||||||
|
* changes are not reliably alterable in place).
|
||||||
|
*/
|
||||||
|
if (table.isVector) {
|
||||||
|
console.log(`Recreating vector table: ${table.tableName}`);
|
||||||
|
|
||||||
|
const existingRows = await querySchemaRows<Record<string, any>>({
|
||||||
|
query: `SELECT * FROM ${MariaDBQuoteGen(table.tableName)}`,
|
||||||
|
config,
|
||||||
|
});
|
||||||
|
|
||||||
|
await runSchemaQuery({ query: `SET FOREIGN_KEY_CHECKS = 0`, config });
|
||||||
|
try {
|
||||||
|
await runSchemaQuery({
|
||||||
|
query: `DROP TABLE IF EXISTS ${MariaDBQuoteGen(table.tableName)}`,
|
||||||
|
config,
|
||||||
|
});
|
||||||
|
await createTable({ table, config });
|
||||||
|
} finally {
|
||||||
|
await runSchemaQuery({ query: `SET FOREIGN_KEY_CHECKS = 1`, config });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (existingRows.length > 0) {
|
||||||
|
const schemaFieldNames = new Set(
|
||||||
|
(table.fields || [])
|
||||||
|
.map((f) => f.fieldName)
|
||||||
|
.filter((n): n is string => Boolean(n)),
|
||||||
|
);
|
||||||
|
|
||||||
|
for (const row of existingRows) {
|
||||||
|
const columns = Object.keys(row).filter((c) =>
|
||||||
|
schemaFieldNames.has(c),
|
||||||
|
);
|
||||||
|
if (columns.length === 0) continue;
|
||||||
|
|
||||||
|
const placeholders = columns.map(() => "?").join(", ");
|
||||||
|
const columnList = columns
|
||||||
|
.map((c) => MariaDBQuoteGen(c))
|
||||||
|
.join(", ");
|
||||||
|
|
||||||
|
await runSchemaQuery({
|
||||||
|
query: `INSERT INTO ${MariaDBQuoteGen(table.tableName)} (${columnList}) VALUES (${placeholders})`,
|
||||||
|
values: columns.map((c) => row[c] ?? null),
|
||||||
|
config,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const tempTableName = `${table.tableName}_temp_${Date.now()}`;
|
const tempTableName = `${table.tableName}_temp_${Date.now()}`;
|
||||||
const backupOldTableName = `${table.tableName}_old_${Date.now()}`;
|
const backupOldTableName = `${table.tableName}_old_${Date.now()}`;
|
||||||
const existingColumns = await getTableColumns({
|
const existingColumns = await getTableColumns({
|
||||||
|
|||||||
@ -1,11 +1,33 @@
|
|||||||
import type {
|
import type {
|
||||||
|
BUN_MARIADB_IndexSchemaType,
|
||||||
BUN_MARIADB_TableSchemaType,
|
BUN_MARIADB_TableSchemaType,
|
||||||
BunMariaDBConfig,
|
BunMariaDBConfig,
|
||||||
} from "../../types";
|
} from "../../types";
|
||||||
|
import isVectorField from "./is-vector-field";
|
||||||
import MariaDBQuoteGen from "./mariadb-quote-gen";
|
import MariaDBQuoteGen from "./mariadb-quote-gen";
|
||||||
import runSchemaQuery, { querySchemaRows } from "./run-schema-query";
|
import runSchemaQuery, { querySchemaRows } from "./run-schema-query";
|
||||||
import schemaCondition from "./schema-condition";
|
import schemaCondition from "./schema-condition";
|
||||||
|
|
||||||
|
function isVectorIndexDef(
|
||||||
|
index: BUN_MARIADB_IndexSchemaType,
|
||||||
|
table: BUN_MARIADB_TableSchemaType,
|
||||||
|
): boolean {
|
||||||
|
if (index.indexType === "VECTOR") return true;
|
||||||
|
if (table.isVector) return true;
|
||||||
|
|
||||||
|
const firstFieldName = index.indexTableFields?.[0];
|
||||||
|
if (!firstFieldName) return false;
|
||||||
|
|
||||||
|
const field = table.fields?.find((f) => f.fieldName === firstFieldName);
|
||||||
|
return isVectorField(field);
|
||||||
|
}
|
||||||
|
|
||||||
|
function vectorDistanceMetric(index: BUN_MARIADB_IndexSchemaType): string {
|
||||||
|
if (index.vectorDistanceMetric === "cosine") return "cosine";
|
||||||
|
if (index.vectorDistanceMetric === "euclidean") return "euclidean";
|
||||||
|
return "euclidean";
|
||||||
|
}
|
||||||
|
|
||||||
export default async function syncIndexes({
|
export default async function syncIndexes({
|
||||||
table,
|
table,
|
||||||
config,
|
config,
|
||||||
@ -121,20 +143,10 @@ export default async function syncIndexes({
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!existingIndexesMap.has(index.indexName)) {
|
if (!existingIndexesMap.has(index.indexName)) {
|
||||||
const isVectorIndex =
|
if (isVectorIndexDef(index, table)) {
|
||||||
table.isVector ||
|
|
||||||
table.fields?.some(
|
|
||||||
(f) =>
|
|
||||||
f.fieldName === index.indexTableFields![0] && f.isVector,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (isVectorIndex) {
|
|
||||||
console.log(`Creating Vector index: ${index.indexName}`);
|
console.log(`Creating Vector index: ${index.indexName}`);
|
||||||
const targetField = MariaDBQuoteGen(index.indexTableFields[0]!);
|
const targetField = MariaDBQuoteGen(index.indexTableFields[0]!);
|
||||||
const distanceMetric =
|
const distanceMetric = vectorDistanceMetric(index);
|
||||||
(index.indexType as string)?.toLowerCase() === "cosine"
|
|
||||||
? "cosine"
|
|
||||||
: "euclidean";
|
|
||||||
|
|
||||||
await runSchemaQuery({
|
await runSchemaQuery({
|
||||||
query: `ALTER TABLE ${MariaDBQuoteGen(table.tableName)} ADD VECTOR INDEX ${MariaDBQuoteGen(index.indexName)} (${targetField}) M=8 DISTANCE=${distanceMetric}`,
|
query: `ALTER TABLE ${MariaDBQuoteGen(table.tableName)} ADD VECTOR INDEX ${MariaDBQuoteGen(index.indexName)} (${targetField}) M=8 DISTANCE=${distanceMetric}`,
|
||||||
|
|||||||
@ -6,6 +6,7 @@ import type {
|
|||||||
import buildColumnDefinition from "./build-column-definition";
|
import buildColumnDefinition from "./build-column-definition";
|
||||||
import createTable from "./create-table";
|
import createTable from "./create-table";
|
||||||
import getTableColumns from "./get-table-columns";
|
import getTableColumns from "./get-table-columns";
|
||||||
|
import isVectorField from "./is-vector-field";
|
||||||
import mapDataType from "./map-data-types";
|
import mapDataType from "./map-data-types";
|
||||||
import MariaDBQuoteGen from "./mariadb-quote-gen";
|
import MariaDBQuoteGen from "./mariadb-quote-gen";
|
||||||
import recreateTable from "./recreate-table";
|
import recreateTable from "./recreate-table";
|
||||||
@ -29,6 +30,30 @@ function columnTypesMatch(liveType: string, expectedType: string): boolean {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function vectorTypeDiverged(
|
||||||
|
liveType: string,
|
||||||
|
liveComment: string,
|
||||||
|
field: BUN_MARIADB_FieldSchemaType,
|
||||||
|
): boolean {
|
||||||
|
const dimensions = field.vectorSize || 1536;
|
||||||
|
const expectedNative = `vector(${dimensions})`;
|
||||||
|
const live = liveType.toLowerCase().replace(/\s+/g, "");
|
||||||
|
|
||||||
|
if (live === expectedNative) return false;
|
||||||
|
|
||||||
|
// Legacy LONGTEXT storage with vector_size comment
|
||||||
|
if (live.startsWith("longtext") || live.startsWith("text")) {
|
||||||
|
const match = liveComment.match(/vector_size\s*=\s*(\d+)/i);
|
||||||
|
if (match && Number(match[1]) === dimensions) {
|
||||||
|
// Still legacy storage — treat as diverged so we can migrate to VECTOR
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
async function addColumn({
|
async function addColumn({
|
||||||
tableName,
|
tableName,
|
||||||
field,
|
field,
|
||||||
@ -109,6 +134,7 @@ export default async function updateTable({
|
|||||||
const fieldsToAdd: BUN_MARIADB_FieldSchemaType[] = [];
|
const fieldsToAdd: BUN_MARIADB_FieldSchemaType[] = [];
|
||||||
const fieldsToModify: BUN_MARIADB_FieldSchemaType[] = [];
|
const fieldsToModify: BUN_MARIADB_FieldSchemaType[] = [];
|
||||||
const fieldsToDrop: string[] = [];
|
const fieldsToDrop: string[] = [];
|
||||||
|
let needsVectorRecreate = false;
|
||||||
|
|
||||||
for (const field of table.fields || []) {
|
for (const field of table.fields || []) {
|
||||||
if (!field.fieldName) continue;
|
if (!field.fieldName) continue;
|
||||||
@ -116,6 +142,8 @@ export default async function updateTable({
|
|||||||
const liveField = liveFieldsMap.get(field.fieldName);
|
const liveField = liveFieldsMap.get(field.fieldName);
|
||||||
|
|
||||||
if (!liveField) {
|
if (!liveField) {
|
||||||
|
// Adding a new vector column can require rebuild if VECTOR INDEX
|
||||||
|
// constraints conflict; still try surgical add first.
|
||||||
fieldsToAdd.push(field);
|
fieldsToAdd.push(field);
|
||||||
} else {
|
} else {
|
||||||
let typeDiverged = !columnTypesMatch(
|
let typeDiverged = !columnTypesMatch(
|
||||||
@ -123,10 +151,15 @@ export default async function updateTable({
|
|||||||
mapDataType(field),
|
mapDataType(field),
|
||||||
);
|
);
|
||||||
|
|
||||||
if (field.isVector || field.dataType === "VECTOR") {
|
if (isVectorField(field)) {
|
||||||
const dimensions = field.vectorSize || 1536;
|
typeDiverged = vectorTypeDiverged(
|
||||||
const expectedNativeToken = `vector(${dimensions})`;
|
liveField.type,
|
||||||
typeDiverged = liveField.type.toLowerCase() !== expectedNativeToken;
|
liveField.comment,
|
||||||
|
field,
|
||||||
|
);
|
||||||
|
if (typeDiverged) {
|
||||||
|
needsVectorRecreate = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (typeDiverged) {
|
if (typeDiverged) {
|
||||||
@ -135,6 +168,15 @@ export default async function updateTable({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Vector dimension / storage type changes → full rebuild automatically
|
||||||
|
if (needsVectorRecreate) {
|
||||||
|
console.log(
|
||||||
|
`Vector column change detected on \`${table.tableName}\`; recreating table`,
|
||||||
|
);
|
||||||
|
await recreateTable({ table, config });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
for (const col of existingColumns) {
|
for (const col of existingColumns) {
|
||||||
if (!codeFieldsMap.has(col.name)) {
|
if (!codeFieldsMap.has(col.name)) {
|
||||||
fieldsToDrop.push(col.name);
|
fieldsToDrop.push(col.name);
|
||||||
@ -208,7 +250,7 @@ export default async function updateTable({
|
|||||||
try {
|
try {
|
||||||
await modifyColumn({ tableName: table.tableName, field, config });
|
await modifyColumn({ tableName: table.tableName, field, config });
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
if (field.isVector || field.dataType === "VECTOR") {
|
if (isVectorField(field)) {
|
||||||
console.warn(
|
console.warn(
|
||||||
`[Vector Resize] Re-aligning dimension spaces natively via safe migration schema rebuild.`,
|
`[Vector Resize] Re-aligning dimension spaces natively via safe migration schema rebuild.`,
|
||||||
);
|
);
|
||||||
|
|||||||
@ -1,5 +1,4 @@
|
|||||||
import type { RequestOptions } from "https";
|
import type { RequestOptions } from "https";
|
||||||
import type { ConnectionConfig } from "mariadb";
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fully-qualified database name used when a database needs to be referenced
|
* Fully-qualified database name used when a database needs to be referenced
|
||||||
@ -98,13 +97,9 @@ export interface BUN_MARIADB_TableSchemaType {
|
|||||||
childTableDbId?: string | number;
|
childTableDbId?: string | number;
|
||||||
collation?: (typeof MariaDBCollations)[number];
|
collation?: (typeof MariaDBCollations)[number];
|
||||||
/**
|
/**
|
||||||
* If this is a vector table
|
* If this is a vector-oriented table (native MariaDB VECTOR columns/indexes)
|
||||||
*/
|
*/
|
||||||
isVector?: boolean;
|
isVector?: boolean;
|
||||||
/**
|
|
||||||
* Type of vector. Defaults to `vec0`
|
|
||||||
*/
|
|
||||||
vectorType?: string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -234,6 +229,7 @@ export const MariaDBIndexTypes = [
|
|||||||
"HASH",
|
"HASH",
|
||||||
"FULLTEXT",
|
"FULLTEXT",
|
||||||
"SPATIAL",
|
"SPATIAL",
|
||||||
|
"VECTOR",
|
||||||
] as const;
|
] as const;
|
||||||
|
|
||||||
export type MariaDBIndexType = (typeof MariaDBIndexTypes)[number];
|
export type MariaDBIndexType = (typeof MariaDBIndexTypes)[number];
|
||||||
@ -252,9 +248,13 @@ export interface BUN_MARIADB_IndexSchemaType {
|
|||||||
*/
|
*/
|
||||||
indexTableFields?: string[];
|
indexTableFields?: string[];
|
||||||
/**
|
/**
|
||||||
* Under the hood index type (BTREE, HASH) or modifier (FULLTEXT, SPATIAL)
|
* Under the hood index type (BTREE, HASH) or modifier (FULLTEXT, SPATIAL, VECTOR)
|
||||||
*/
|
*/
|
||||||
indexType?: MariaDBIndexType;
|
indexType?: MariaDBIndexType;
|
||||||
|
/**
|
||||||
|
* Distance metric for VECTOR indexes (`euclidean` | `cosine`). Defaults to euclidean.
|
||||||
|
*/
|
||||||
|
vectorDistanceMetric?: "euclidean" | "cosine";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Optional documentation or tuning note inside the DB metadata
|
* Optional documentation or tuning note inside the DB metadata
|
||||||
@ -1519,7 +1519,7 @@ export type BunMariaDBConfig = {
|
|||||||
db_backup_dir?: string;
|
db_backup_dir?: string;
|
||||||
max_backups?: number;
|
max_backups?: number;
|
||||||
/**
|
/**
|
||||||
* The Root Directory for the DB file and schema
|
* Root directory for schema, types, and local artifacts (relative to project root)
|
||||||
*/
|
*/
|
||||||
db_dir?: string;
|
db_dir?: string;
|
||||||
/**
|
/**
|
||||||
|
|||||||
@ -2,8 +2,12 @@ type Params = {
|
|||||||
backup_name: string;
|
backup_name: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse timestamped backup names: `{db_name}-{timestamp}[.sql]`
|
||||||
|
*/
|
||||||
export default function grabBackupData({ backup_name }: Params) {
|
export default function grabBackupData({ backup_name }: Params) {
|
||||||
const backup_parts = backup_name.split("-");
|
const normalized = backup_name.replace(/\.sql$/, "");
|
||||||
|
const backup_parts = normalized.split("-");
|
||||||
const backup_date_timestamp = Number(backup_parts.pop());
|
const backup_date_timestamp = Number(backup_parts.pop());
|
||||||
const origin_backup_name = backup_parts.join("-");
|
const origin_backup_name = backup_parts.join("-");
|
||||||
|
|
||||||
|
|||||||
@ -5,7 +5,5 @@ type Params = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export default function grabDBBackupFileName({ config }: Params) {
|
export default function grabDBBackupFileName({ config }: Params) {
|
||||||
const new_db_file_name = `${config.db_name}-${Date.now()}`;
|
return `${config.db_name}-${Date.now()}.sql`;
|
||||||
|
|
||||||
return new_db_file_name;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -10,17 +10,14 @@ type Params = {
|
|||||||
export default function grabDBDir({ config }: Params) {
|
export default function grabDBDir({ config }: Params) {
|
||||||
const { ROOT_DIR } = grabDirNames();
|
const { ROOT_DIR } = grabDirNames();
|
||||||
|
|
||||||
let db_dir = ROOT_DIR;
|
const db_dir = config.db_dir
|
||||||
|
? path.resolve(ROOT_DIR, config.db_dir)
|
||||||
if (config.db_dir) {
|
: ROOT_DIR;
|
||||||
db_dir = config.db_dir;
|
|
||||||
}
|
|
||||||
|
|
||||||
const backup_dir_name =
|
const backup_dir_name =
|
||||||
config.db_backup_dir || AppData["DefaultBackupDirName"];
|
config.db_backup_dir || AppData["DefaultBackupDirName"];
|
||||||
|
|
||||||
const backup_dir = path.resolve(db_dir, backup_dir_name);
|
const backup_dir = path.resolve(db_dir, backup_dir_name);
|
||||||
const db_file_path = path.resolve(db_dir, config.db_name);
|
|
||||||
|
|
||||||
return { db_dir, backup_dir, db_file_path };
|
return { db_dir, backup_dir };
|
||||||
}
|
}
|
||||||
|
|||||||
@ -6,24 +6,23 @@ type Params = {
|
|||||||
config: BunMariaDBConfig;
|
config: BunMariaDBConfig;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
function backupTimestamp(name: string): number {
|
||||||
|
const base = name.replace(/\.sql$/, "");
|
||||||
|
const ts = Number(base.split("-").pop());
|
||||||
|
return Number.isFinite(ts) ? ts : 0;
|
||||||
|
}
|
||||||
|
|
||||||
export default function grabSortedBackups({ config }: Params) {
|
export default function grabSortedBackups({ config }: Params) {
|
||||||
const { backup_dir } = grabDBDir({ config });
|
const { backup_dir } = grabDBDir({ config });
|
||||||
|
|
||||||
|
if (!fs.existsSync(backup_dir)) {
|
||||||
|
return [] as string[];
|
||||||
|
}
|
||||||
|
|
||||||
const backups = fs.readdirSync(backup_dir);
|
const backups = fs.readdirSync(backup_dir);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Order Backups. Most recent first.
|
* Order Backups. Most recent first.
|
||||||
*/
|
*/
|
||||||
const ordered_backups = backups.sort((a, b) => {
|
return backups.sort((a, b) => backupTimestamp(b) - backupTimestamp(a));
|
||||||
const a_date = Number(a.split("-").pop());
|
|
||||||
const b_date = Number(b.split("-").pop());
|
|
||||||
|
|
||||||
if (a_date > b_date) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
return 1;
|
|
||||||
});
|
|
||||||
|
|
||||||
return ordered_backups;
|
|
||||||
}
|
}
|
||||||
|
|||||||
28
src/utils/mariadb-cli-env.ts
Normal file
28
src/utils/mariadb-cli-env.ts
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
/**
|
||||||
|
* Build env for mariadb / mariadb-dump child processes without putting
|
||||||
|
* the password on the process argv (visible via `ps`).
|
||||||
|
*/
|
||||||
|
export default function mariadbCliEnv(): NodeJS.ProcessEnv {
|
||||||
|
const env: NodeJS.ProcessEnv = { ...process.env };
|
||||||
|
|
||||||
|
const password = process.env.BUN_MARIADB_SERVER_PASSWORD;
|
||||||
|
if (password) {
|
||||||
|
// Standard MySQL/MariaDB client env vars (prefer not using -p on argv)
|
||||||
|
env.MYSQL_PWD = password;
|
||||||
|
env.MARIADB_PWD = password;
|
||||||
|
}
|
||||||
|
|
||||||
|
return env;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function mariadbCliConnectionArgs(): string[] {
|
||||||
|
const host = process.env.BUN_MARIADB_SERVER_HOST || "127.0.0.1";
|
||||||
|
const user = process.env.BUN_MARIADB_SERVER_USERNAME || "root";
|
||||||
|
const port = process.env.BUN_MARIADB_SERVER_PORT;
|
||||||
|
|
||||||
|
return [
|
||||||
|
`-h${host}`,
|
||||||
|
`-u${user}`,
|
||||||
|
...(port ? [`-P${port}`] : []),
|
||||||
|
];
|
||||||
|
}
|
||||||
@ -25,7 +25,7 @@ type Return = {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* # SQL Gen Operator Gen
|
* # SQL Gen Operator Gen
|
||||||
* @description Generates an SQL operator for node module `mysql` or `serverless-mysql`
|
* @description Maps query equality operators to MariaDB SQL fragments
|
||||||
*/
|
*/
|
||||||
export default function sqlGenOperatorGen({
|
export default function sqlGenOperatorGen({
|
||||||
fieldName,
|
fieldName,
|
||||||
|
|||||||
@ -20,7 +20,7 @@ type Return = {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* # SQL Query Generator
|
* # SQL Query Generator
|
||||||
* @description Generates an SQL Query for node module `mysql` or `serverless-mysql`
|
* @description Builds parameterized SELECT SQL for MariaDB
|
||||||
*/
|
*/
|
||||||
export default function sqlGenerator<
|
export default function sqlGenerator<
|
||||||
T extends { [key: string]: any } = { [key: string]: any },
|
T extends { [key: string]: any } = { [key: string]: any },
|
||||||
|
|||||||
@ -21,6 +21,7 @@
|
|||||||
"maxNodeModuleJsDepth": 10,
|
"maxNodeModuleJsDepth": 10,
|
||||||
"forceConsistentCasingInFileNames": true,
|
"forceConsistentCasingInFileNames": true,
|
||||||
"incremental": true,
|
"incremental": true,
|
||||||
|
"rootDir": "src",
|
||||||
"outDir": "dist"
|
"outDir": "dist"
|
||||||
},
|
},
|
||||||
"include": ["src"],
|
"include": ["src"],
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user