diff --git a/CLAUDE.md b/CLAUDE.md index 1ee6890..bacb9e3 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1,106 +1 @@ - Default to using Bun instead of Node.js. - -- Use `bun ` instead of `node ` or `ts-node ` -- Use `bun test` instead of `jest` or `vitest` -- Use `bun build ` instead of `webpack` or `esbuild` -- Use `bun install` instead of `npm install` or `yarn install` or `pnpm install` -- Use `bun run - - -``` - -With the following `frontend.tsx`: - -```tsx#frontend.tsx -import React from "react"; - -// import .css files directly and it works -import './index.css'; - -import { createRoot } from "react-dom/client"; - -const root = createRoot(document.body); - -export default function Frontend() { - return

Hello, world!

; -} - -root.render(); -``` - -Then, run index.ts - -```sh -bun --hot ./index.ts -``` - -For more information, read the Bun API docs in `node_modules/bun-types/docs/**.md`. diff --git a/bun.lock b/bun.lock index 3743c96..6e0a84e 100644 --- a/bun.lock +++ b/bun.lock @@ -1,5 +1,6 @@ { "lockfileVersion": 1, + "configVersion": 0, "workspaces": { "": { "name": "bun-sqlite", @@ -7,13 +8,14 @@ "@inquirer/prompts": "^8.3.0", "chalk": "^5.6.2", "commander": "^14.0.3", - "inquirer": "^13.3.0", + "inquirer": "^13.3.2", "lodash": "^4.17.23", "mysql": "^2.18.1", "sqlite-vec": "^0.1.7-alpha.2", }, "devDependencies": { "@types/bun": "latest", + "@types/inquirer": "^9.0.9", "@types/lodash": "^4.17.24", "@types/mysql": "^2.15.27", "@types/node": "^25.3.3", @@ -24,13 +26,13 @@ }, }, "packages": { - "@inquirer/ansi": ["@inquirer/ansi@2.0.3", "", {}, "sha512-g44zhR3NIKVs0zUesa4iMzExmZpLUdTLRMCStqX3GE5NT6VkPcxQGJ+uC8tDgBUC/vB1rUhUd55cOf++4NZcmw=="], + "@inquirer/ansi": ["@inquirer/ansi@2.0.4", "", {}, "sha512-DpcZrQObd7S0R/U3bFdkcT5ebRwbTTC4D3tCc1vsJizmgPLxNJBo+AAFmrZwe8zk30P2QzgzGWZ3Q9uJwWuhIg=="], "@inquirer/checkbox": ["@inquirer/checkbox@5.1.0", "", { "dependencies": { "@inquirer/ansi": "^2.0.3", "@inquirer/core": "^11.1.5", "@inquirer/figures": "^2.0.3", "@inquirer/type": "^4.0.3" }, "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-/HjF1LN0a1h4/OFsbGKHNDtWICFU/dqXCdym719HFTyJo9IG7Otr+ziGWc9S0iQuohRZllh+WprSgd5UW5Fw0g=="], "@inquirer/confirm": ["@inquirer/confirm@6.0.8", "", { "dependencies": { "@inquirer/core": "^11.1.5", "@inquirer/type": "^4.0.3" }, "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-Di6dgmiZ9xCSUxWUReWTqDtbhXCuG2MQm2xmgSAIruzQzBqNf49b8E07/vbCYY506kDe8BiwJbegXweG8M1klw=="], - "@inquirer/core": ["@inquirer/core@11.1.5", "", { "dependencies": { "@inquirer/ansi": "^2.0.3", "@inquirer/figures": "^2.0.3", "@inquirer/type": "^4.0.3", "cli-width": "^4.1.0", "fast-wrap-ansi": "^0.2.0", "mute-stream": "^3.0.0", "signal-exit": "^4.1.0" }, "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-QQPAX+lka8GyLcZ7u7Nb1h6q72iZ/oy0blilC3IB2nSt1Qqxp7akt94Jqhi/DzARuN3Eo9QwJRvtl4tmVe4T5A=="], + "@inquirer/core": ["@inquirer/core@11.1.7", "", { "dependencies": { "@inquirer/ansi": "^2.0.4", "@inquirer/figures": "^2.0.4", "@inquirer/type": "^4.0.4", "cli-width": "^4.1.0", "fast-wrap-ansi": "^0.2.0", "mute-stream": "^3.0.0", "signal-exit": "^4.1.0" }, "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-1BiBNDk9btIwYIzNZpkikIHXWeNzNncJePPqwDyVMhXhD1ebqbpn1mKGctpoqAbzywZfdG0O4tvmsGIcOevAPQ=="], "@inquirer/editor": ["@inquirer/editor@5.0.8", "", { "dependencies": { "@inquirer/core": "^11.1.5", "@inquirer/external-editor": "^2.0.3", "@inquirer/type": "^4.0.3" }, "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-sLcpbb9B3XqUEGrj1N66KwhDhEckzZ4nI/W6SvLXyBX8Wic3LDLENlWRvkOGpCPoserabe+MxQkpiMoI8irvyA=="], @@ -54,16 +56,20 @@ "@inquirer/select": ["@inquirer/select@5.1.0", "", { "dependencies": { "@inquirer/ansi": "^2.0.3", "@inquirer/core": "^11.1.5", "@inquirer/figures": "^2.0.3", "@inquirer/type": "^4.0.3" }, "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-OyYbKnchS1u+zRe14LpYrN8S0wH1vD0p2yKISvSsJdH2TpI87fh4eZdWnpdbrGauCRWDph3NwxRmM4Pcm/hx1Q=="], - "@inquirer/type": ["@inquirer/type@4.0.3", "", { "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-cKZN7qcXOpj1h+1eTTcGDVLaBIHNMT1Rz9JqJP5MnEJ0JhgVWllx7H/tahUp5YEK1qaByH2Itb8wLG/iScD5kw=="], + "@inquirer/type": ["@inquirer/type@4.0.4", "", { "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-PamArxO3cFJZoOzspzo6cxVlLeIftyBsZw/S9bKY5DzxqJVZgjoj1oP8d0rskKtp7sZxBycsoer1g6UeJV1BBA=="], "@types/bun": ["@types/bun@1.3.9", "", { "dependencies": { "bun-types": "1.3.9" } }, "sha512-KQ571yULOdWJiMH+RIWIOZ7B2RXQGpL1YQrBtLIV3FqDcCu6FsbFUBwhdKUlCKUpS3PJDsHlJ1QKlpxoVR+xtw=="], + "@types/inquirer": ["@types/inquirer@9.0.9", "", { "dependencies": { "@types/through": "*", "rxjs": "^7.2.0" } }, "sha512-/mWx5136gts2Z2e5izdoRCo46lPp5TMs9R15GTSsgg/XnZyxDWVqoVU3R9lWnccKpqwsJLvRoxbCjoJtZB7DSw=="], + "@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.3.3", "", { "dependencies": { "undici-types": "~7.18.0" } }, "sha512-DpzbrH7wIcBaJibpKo9nnSQL0MTRdnWttGyE5haGwK86xgMOkFLp7vEyfQPGLOJh5wNYiJ3V9PmUMDhV9u8kkQ=="], + "@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.9", "", { "dependencies": { "@types/node": "*" } }, "sha512-+UBWWOakIP4Tswh0Bt0QD0alpTY8cb5hvgiYeWCMet9YukHbzuruIEeXC2D7nMJPB12kbh8C7XJykSexEqGKJg=="], @@ -88,7 +94,7 @@ "inherits": ["inherits@2.0.4", "", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="], - "inquirer": ["inquirer@13.3.0", "", { "dependencies": { "@inquirer/ansi": "^2.0.3", "@inquirer/core": "^11.1.5", "@inquirer/prompts": "^8.3.0", "@inquirer/type": "^4.0.3", "mute-stream": "^3.0.0", "run-async": "^4.0.6", "rxjs": "^7.8.2" }, "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-APTrZe9IhrsshL0u2PgmEMLP3CXDBjZ99xh5dR2+sryOt5R+JGL0KNuaTTT2lW54B9eNQDMutPR05UYTL7Xb1Q=="], + "inquirer": ["inquirer@13.3.2", "", { "dependencies": { "@inquirer/ansi": "^2.0.4", "@inquirer/core": "^11.1.7", "@inquirer/prompts": "^8.3.2", "@inquirer/type": "^4.0.4", "mute-stream": "^3.0.0", "run-async": "^4.0.6", "rxjs": "^7.8.2" }, "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-bh/OjBGxNR9qvfQj1n5bxtIF58mbOTp2InN5dKuwUK03dXcDGFsjlDinQRuXMZ4EGiJaFieUWHCAaxH2p7iUBw=="], "isarray": ["isarray@1.0.0", "", {}, "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ=="], @@ -135,5 +141,97 @@ "undici-types": ["undici-types@7.18.2", "", {}, "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w=="], "util-deprecate": ["util-deprecate@1.0.2", "", {}, "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="], + + "@inquirer/checkbox/@inquirer/ansi": ["@inquirer/ansi@2.0.3", "", {}, "sha512-g44zhR3NIKVs0zUesa4iMzExmZpLUdTLRMCStqX3GE5NT6VkPcxQGJ+uC8tDgBUC/vB1rUhUd55cOf++4NZcmw=="], + + "@inquirer/checkbox/@inquirer/core": ["@inquirer/core@11.1.5", "", { "dependencies": { "@inquirer/ansi": "^2.0.3", "@inquirer/figures": "^2.0.3", "@inquirer/type": "^4.0.3", "cli-width": "^4.1.0", "fast-wrap-ansi": "^0.2.0", "mute-stream": "^3.0.0", "signal-exit": "^4.1.0" }, "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-QQPAX+lka8GyLcZ7u7Nb1h6q72iZ/oy0blilC3IB2nSt1Qqxp7akt94Jqhi/DzARuN3Eo9QwJRvtl4tmVe4T5A=="], + + "@inquirer/checkbox/@inquirer/type": ["@inquirer/type@4.0.3", "", { "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-cKZN7qcXOpj1h+1eTTcGDVLaBIHNMT1Rz9JqJP5MnEJ0JhgVWllx7H/tahUp5YEK1qaByH2Itb8wLG/iScD5kw=="], + + "@inquirer/confirm/@inquirer/core": ["@inquirer/core@11.1.5", "", { "dependencies": { "@inquirer/ansi": "^2.0.3", "@inquirer/figures": "^2.0.3", "@inquirer/type": "^4.0.3", "cli-width": "^4.1.0", "fast-wrap-ansi": "^0.2.0", "mute-stream": "^3.0.0", "signal-exit": "^4.1.0" }, "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-QQPAX+lka8GyLcZ7u7Nb1h6q72iZ/oy0blilC3IB2nSt1Qqxp7akt94Jqhi/DzARuN3Eo9QwJRvtl4tmVe4T5A=="], + + "@inquirer/confirm/@inquirer/type": ["@inquirer/type@4.0.3", "", { "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-cKZN7qcXOpj1h+1eTTcGDVLaBIHNMT1Rz9JqJP5MnEJ0JhgVWllx7H/tahUp5YEK1qaByH2Itb8wLG/iScD5kw=="], + + "@inquirer/core/@inquirer/figures": ["@inquirer/figures@2.0.4", "", {}, "sha512-eLBsjlS7rPS3WEhmOmh1znQ5IsQrxWzxWDxO51e4urv+iVrSnIHbq4zqJIOiyNdYLa+BVjwOtdetcQx1lWPpiQ=="], + + "@inquirer/editor/@inquirer/core": ["@inquirer/core@11.1.5", "", { "dependencies": { "@inquirer/ansi": "^2.0.3", "@inquirer/figures": "^2.0.3", "@inquirer/type": "^4.0.3", "cli-width": "^4.1.0", "fast-wrap-ansi": "^0.2.0", "mute-stream": "^3.0.0", "signal-exit": "^4.1.0" }, "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-QQPAX+lka8GyLcZ7u7Nb1h6q72iZ/oy0blilC3IB2nSt1Qqxp7akt94Jqhi/DzARuN3Eo9QwJRvtl4tmVe4T5A=="], + + "@inquirer/editor/@inquirer/type": ["@inquirer/type@4.0.3", "", { "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-cKZN7qcXOpj1h+1eTTcGDVLaBIHNMT1Rz9JqJP5MnEJ0JhgVWllx7H/tahUp5YEK1qaByH2Itb8wLG/iScD5kw=="], + + "@inquirer/expand/@inquirer/core": ["@inquirer/core@11.1.5", "", { "dependencies": { "@inquirer/ansi": "^2.0.3", "@inquirer/figures": "^2.0.3", "@inquirer/type": "^4.0.3", "cli-width": "^4.1.0", "fast-wrap-ansi": "^0.2.0", "mute-stream": "^3.0.0", "signal-exit": "^4.1.0" }, "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-QQPAX+lka8GyLcZ7u7Nb1h6q72iZ/oy0blilC3IB2nSt1Qqxp7akt94Jqhi/DzARuN3Eo9QwJRvtl4tmVe4T5A=="], + + "@inquirer/expand/@inquirer/type": ["@inquirer/type@4.0.3", "", { "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-cKZN7qcXOpj1h+1eTTcGDVLaBIHNMT1Rz9JqJP5MnEJ0JhgVWllx7H/tahUp5YEK1qaByH2Itb8wLG/iScD5kw=="], + + "@inquirer/input/@inquirer/core": ["@inquirer/core@11.1.5", "", { "dependencies": { "@inquirer/ansi": "^2.0.3", "@inquirer/figures": "^2.0.3", "@inquirer/type": "^4.0.3", "cli-width": "^4.1.0", "fast-wrap-ansi": "^0.2.0", "mute-stream": "^3.0.0", "signal-exit": "^4.1.0" }, "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-QQPAX+lka8GyLcZ7u7Nb1h6q72iZ/oy0blilC3IB2nSt1Qqxp7akt94Jqhi/DzARuN3Eo9QwJRvtl4tmVe4T5A=="], + + "@inquirer/input/@inquirer/type": ["@inquirer/type@4.0.3", "", { "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-cKZN7qcXOpj1h+1eTTcGDVLaBIHNMT1Rz9JqJP5MnEJ0JhgVWllx7H/tahUp5YEK1qaByH2Itb8wLG/iScD5kw=="], + + "@inquirer/number/@inquirer/core": ["@inquirer/core@11.1.5", "", { "dependencies": { "@inquirer/ansi": "^2.0.3", "@inquirer/figures": "^2.0.3", "@inquirer/type": "^4.0.3", "cli-width": "^4.1.0", "fast-wrap-ansi": "^0.2.0", "mute-stream": "^3.0.0", "signal-exit": "^4.1.0" }, "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-QQPAX+lka8GyLcZ7u7Nb1h6q72iZ/oy0blilC3IB2nSt1Qqxp7akt94Jqhi/DzARuN3Eo9QwJRvtl4tmVe4T5A=="], + + "@inquirer/number/@inquirer/type": ["@inquirer/type@4.0.3", "", { "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-cKZN7qcXOpj1h+1eTTcGDVLaBIHNMT1Rz9JqJP5MnEJ0JhgVWllx7H/tahUp5YEK1qaByH2Itb8wLG/iScD5kw=="], + + "@inquirer/password/@inquirer/ansi": ["@inquirer/ansi@2.0.3", "", {}, "sha512-g44zhR3NIKVs0zUesa4iMzExmZpLUdTLRMCStqX3GE5NT6VkPcxQGJ+uC8tDgBUC/vB1rUhUd55cOf++4NZcmw=="], + + "@inquirer/password/@inquirer/core": ["@inquirer/core@11.1.5", "", { "dependencies": { "@inquirer/ansi": "^2.0.3", "@inquirer/figures": "^2.0.3", "@inquirer/type": "^4.0.3", "cli-width": "^4.1.0", "fast-wrap-ansi": "^0.2.0", "mute-stream": "^3.0.0", "signal-exit": "^4.1.0" }, "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-QQPAX+lka8GyLcZ7u7Nb1h6q72iZ/oy0blilC3IB2nSt1Qqxp7akt94Jqhi/DzARuN3Eo9QwJRvtl4tmVe4T5A=="], + + "@inquirer/password/@inquirer/type": ["@inquirer/type@4.0.3", "", { "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-cKZN7qcXOpj1h+1eTTcGDVLaBIHNMT1Rz9JqJP5MnEJ0JhgVWllx7H/tahUp5YEK1qaByH2Itb8wLG/iScD5kw=="], + + "@inquirer/rawlist/@inquirer/core": ["@inquirer/core@11.1.5", "", { "dependencies": { "@inquirer/ansi": "^2.0.3", "@inquirer/figures": "^2.0.3", "@inquirer/type": "^4.0.3", "cli-width": "^4.1.0", "fast-wrap-ansi": "^0.2.0", "mute-stream": "^3.0.0", "signal-exit": "^4.1.0" }, "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-QQPAX+lka8GyLcZ7u7Nb1h6q72iZ/oy0blilC3IB2nSt1Qqxp7akt94Jqhi/DzARuN3Eo9QwJRvtl4tmVe4T5A=="], + + "@inquirer/rawlist/@inquirer/type": ["@inquirer/type@4.0.3", "", { "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-cKZN7qcXOpj1h+1eTTcGDVLaBIHNMT1Rz9JqJP5MnEJ0JhgVWllx7H/tahUp5YEK1qaByH2Itb8wLG/iScD5kw=="], + + "@inquirer/search/@inquirer/core": ["@inquirer/core@11.1.5", "", { "dependencies": { "@inquirer/ansi": "^2.0.3", "@inquirer/figures": "^2.0.3", "@inquirer/type": "^4.0.3", "cli-width": "^4.1.0", "fast-wrap-ansi": "^0.2.0", "mute-stream": "^3.0.0", "signal-exit": "^4.1.0" }, "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-QQPAX+lka8GyLcZ7u7Nb1h6q72iZ/oy0blilC3IB2nSt1Qqxp7akt94Jqhi/DzARuN3Eo9QwJRvtl4tmVe4T5A=="], + + "@inquirer/search/@inquirer/type": ["@inquirer/type@4.0.3", "", { "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-cKZN7qcXOpj1h+1eTTcGDVLaBIHNMT1Rz9JqJP5MnEJ0JhgVWllx7H/tahUp5YEK1qaByH2Itb8wLG/iScD5kw=="], + + "@inquirer/select/@inquirer/ansi": ["@inquirer/ansi@2.0.3", "", {}, "sha512-g44zhR3NIKVs0zUesa4iMzExmZpLUdTLRMCStqX3GE5NT6VkPcxQGJ+uC8tDgBUC/vB1rUhUd55cOf++4NZcmw=="], + + "@inquirer/select/@inquirer/core": ["@inquirer/core@11.1.5", "", { "dependencies": { "@inquirer/ansi": "^2.0.3", "@inquirer/figures": "^2.0.3", "@inquirer/type": "^4.0.3", "cli-width": "^4.1.0", "fast-wrap-ansi": "^0.2.0", "mute-stream": "^3.0.0", "signal-exit": "^4.1.0" }, "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-QQPAX+lka8GyLcZ7u7Nb1h6q72iZ/oy0blilC3IB2nSt1Qqxp7akt94Jqhi/DzARuN3Eo9QwJRvtl4tmVe4T5A=="], + + "@inquirer/select/@inquirer/type": ["@inquirer/type@4.0.3", "", { "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-cKZN7qcXOpj1h+1eTTcGDVLaBIHNMT1Rz9JqJP5MnEJ0JhgVWllx7H/tahUp5YEK1qaByH2Itb8wLG/iScD5kw=="], + + "inquirer/@inquirer/prompts": ["@inquirer/prompts@8.3.2", "", { "dependencies": { "@inquirer/checkbox": "^5.1.2", "@inquirer/confirm": "^6.0.10", "@inquirer/editor": "^5.0.10", "@inquirer/expand": "^5.0.10", "@inquirer/input": "^5.0.10", "@inquirer/number": "^4.0.10", "@inquirer/password": "^5.0.10", "@inquirer/rawlist": "^5.2.6", "@inquirer/search": "^4.1.6", "@inquirer/select": "^5.1.2" }, "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-yFroiSj2iiBFlm59amdTvAcQFvWS6ph5oKESls/uqPBect7rTU2GbjyZO2DqxMGuIwVA8z0P4K6ViPcd/cp+0w=="], + + "@inquirer/confirm/@inquirer/core/@inquirer/ansi": ["@inquirer/ansi@2.0.3", "", {}, "sha512-g44zhR3NIKVs0zUesa4iMzExmZpLUdTLRMCStqX3GE5NT6VkPcxQGJ+uC8tDgBUC/vB1rUhUd55cOf++4NZcmw=="], + + "@inquirer/editor/@inquirer/core/@inquirer/ansi": ["@inquirer/ansi@2.0.3", "", {}, "sha512-g44zhR3NIKVs0zUesa4iMzExmZpLUdTLRMCStqX3GE5NT6VkPcxQGJ+uC8tDgBUC/vB1rUhUd55cOf++4NZcmw=="], + + "@inquirer/expand/@inquirer/core/@inquirer/ansi": ["@inquirer/ansi@2.0.3", "", {}, "sha512-g44zhR3NIKVs0zUesa4iMzExmZpLUdTLRMCStqX3GE5NT6VkPcxQGJ+uC8tDgBUC/vB1rUhUd55cOf++4NZcmw=="], + + "@inquirer/input/@inquirer/core/@inquirer/ansi": ["@inquirer/ansi@2.0.3", "", {}, "sha512-g44zhR3NIKVs0zUesa4iMzExmZpLUdTLRMCStqX3GE5NT6VkPcxQGJ+uC8tDgBUC/vB1rUhUd55cOf++4NZcmw=="], + + "@inquirer/number/@inquirer/core/@inquirer/ansi": ["@inquirer/ansi@2.0.3", "", {}, "sha512-g44zhR3NIKVs0zUesa4iMzExmZpLUdTLRMCStqX3GE5NT6VkPcxQGJ+uC8tDgBUC/vB1rUhUd55cOf++4NZcmw=="], + + "@inquirer/rawlist/@inquirer/core/@inquirer/ansi": ["@inquirer/ansi@2.0.3", "", {}, "sha512-g44zhR3NIKVs0zUesa4iMzExmZpLUdTLRMCStqX3GE5NT6VkPcxQGJ+uC8tDgBUC/vB1rUhUd55cOf++4NZcmw=="], + + "@inquirer/search/@inquirer/core/@inquirer/ansi": ["@inquirer/ansi@2.0.3", "", {}, "sha512-g44zhR3NIKVs0zUesa4iMzExmZpLUdTLRMCStqX3GE5NT6VkPcxQGJ+uC8tDgBUC/vB1rUhUd55cOf++4NZcmw=="], + + "inquirer/@inquirer/prompts/@inquirer/checkbox": ["@inquirer/checkbox@5.1.2", "", { "dependencies": { "@inquirer/ansi": "^2.0.4", "@inquirer/core": "^11.1.7", "@inquirer/figures": "^2.0.4", "@inquirer/type": "^4.0.4" }, "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-PubpMPO2nJgMufkoB3P2wwxNXEMUXnBIKi/ACzDUYfaoPuM7gSTmuxJeMscoLVEsR4qqrCMf5p0SiYGWnVJ8kw=="], + + "inquirer/@inquirer/prompts/@inquirer/confirm": ["@inquirer/confirm@6.0.10", "", { "dependencies": { "@inquirer/core": "^11.1.7", "@inquirer/type": "^4.0.4" }, "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-tiNyA73pgpQ0FQ7axqtoLUe4GDYjNCDcVsbgcA5anvwg2z6i+suEngLKKJrWKJolT//GFPZHwN30binDIHgSgQ=="], + + "inquirer/@inquirer/prompts/@inquirer/editor": ["@inquirer/editor@5.0.10", "", { "dependencies": { "@inquirer/core": "^11.1.7", "@inquirer/external-editor": "^2.0.4", "@inquirer/type": "^4.0.4" }, "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-VJx4XyaKea7t8hEApTw5dxeIyMtWXre2OiyJcICCRZI4hkoHsMoCnl/KbUnJJExLbH9csLLHMVR144ZhFE1CwA=="], + + "inquirer/@inquirer/prompts/@inquirer/expand": ["@inquirer/expand@5.0.10", "", { "dependencies": { "@inquirer/core": "^11.1.7", "@inquirer/type": "^4.0.4" }, "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-fC0UHJPXsTRvY2fObiwuQYaAnHrp3aDqfwKUJSdfpgv18QUG054ezGbaRNStk/BKD5IPijeMKWej8VV8O5Q/eQ=="], + + "inquirer/@inquirer/prompts/@inquirer/input": ["@inquirer/input@5.0.10", "", { "dependencies": { "@inquirer/core": "^11.1.7", "@inquirer/type": "^4.0.4" }, "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-nvZ6qEVeX/zVtZ1dY2hTGDQpVGD3R7MYPLODPgKO8Y+RAqxkrP3i/3NwF3fZpLdaMiNuK0z2NaYIx9tPwiSegQ=="], + + "inquirer/@inquirer/prompts/@inquirer/number": ["@inquirer/number@4.0.10", "", { "dependencies": { "@inquirer/core": "^11.1.7", "@inquirer/type": "^4.0.4" }, "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-Ht8OQstxiS3APMGjHV0aYAjRAysidWdwurWEo2i8yI5xbhOBWqizT0+MU1S2GCcuhIBg+3SgWVjEoXgfhY+XaA=="], + + "inquirer/@inquirer/prompts/@inquirer/password": ["@inquirer/password@5.0.10", "", { "dependencies": { "@inquirer/ansi": "^2.0.4", "@inquirer/core": "^11.1.7", "@inquirer/type": "^4.0.4" }, "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-QbNyvIE8q2GTqKLYSsA8ATG+eETo+m31DSR0+AU7x3d2FhaTWzqQek80dj3JGTo743kQc6mhBR0erMjYw5jQ0A=="], + + "inquirer/@inquirer/prompts/@inquirer/rawlist": ["@inquirer/rawlist@5.2.6", "", { "dependencies": { "@inquirer/core": "^11.1.7", "@inquirer/type": "^4.0.4" }, "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-jfw0MLJ5TilNsa9zlJ6nmRM0ZFVZhhTICt4/6CU2Dv1ndY7l3sqqo1gIYZyMMDw0LvE1u1nzJNisfHEhJIxq5w=="], + + "inquirer/@inquirer/prompts/@inquirer/search": ["@inquirer/search@4.1.6", "", { "dependencies": { "@inquirer/core": "^11.1.7", "@inquirer/figures": "^2.0.4", "@inquirer/type": "^4.0.4" }, "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-3/6kTRae98hhDevENScy7cdFEuURnSpM3JbBNg8yfXLw88HgTOl+neUuy/l9W0No5NzGsLVydhBzTIxZP7yChQ=="], + + "inquirer/@inquirer/prompts/@inquirer/select": ["@inquirer/select@5.1.2", "", { "dependencies": { "@inquirer/ansi": "^2.0.4", "@inquirer/core": "^11.1.7", "@inquirer/figures": "^2.0.4", "@inquirer/type": "^4.0.4" }, "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-kTK8YIkHV+f02y7bWCh7E0u2/11lul5WepVTclr3UMBtBr05PgcZNWfMa7FY57ihpQFQH/spLMHTcr0rXy50tA=="], + + "inquirer/@inquirer/prompts/@inquirer/checkbox/@inquirer/figures": ["@inquirer/figures@2.0.4", "", {}, "sha512-eLBsjlS7rPS3WEhmOmh1znQ5IsQrxWzxWDxO51e4urv+iVrSnIHbq4zqJIOiyNdYLa+BVjwOtdetcQx1lWPpiQ=="], + + "inquirer/@inquirer/prompts/@inquirer/editor/@inquirer/external-editor": ["@inquirer/external-editor@2.0.4", "", { "dependencies": { "chardet": "^2.1.1", "iconv-lite": "^0.7.2" }, "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-Prenuv9C1PHj2Itx0BcAOVBTonz02Hc2Nd2DbU67PdGUaqn0nPCnV34oDyyoaZHnmfRxkpuhh/u51ThkrO+RdA=="], + + "inquirer/@inquirer/prompts/@inquirer/search/@inquirer/figures": ["@inquirer/figures@2.0.4", "", {}, "sha512-eLBsjlS7rPS3WEhmOmh1znQ5IsQrxWzxWDxO51e4urv+iVrSnIHbq4zqJIOiyNdYLa+BVjwOtdetcQx1lWPpiQ=="], + + "inquirer/@inquirer/prompts/@inquirer/select/@inquirer/figures": ["@inquirer/figures@2.0.4", "", {}, "sha512-eLBsjlS7rPS3WEhmOmh1znQ5IsQrxWzxWDxO51e4urv+iVrSnIHbq4zqJIOiyNdYLa+BVjwOtdetcQx1lWPpiQ=="], } } diff --git a/dist/commands/admin/index.d.ts b/dist/commands/admin/index.d.ts new file mode 100644 index 0000000..f202e7d --- /dev/null +++ b/dist/commands/admin/index.d.ts @@ -0,0 +1,2 @@ +import { Command } from "commander"; +export default function (): Command; diff --git a/dist/commands/admin/index.js b/dist/commands/admin/index.js new file mode 100644 index 0000000..758c27f --- /dev/null +++ b/dist/commands/admin/index.js @@ -0,0 +1,41 @@ +import { Command } from "commander"; +import init from "../../functions/init"; +import grabDBDir from "../../utils/grab-db-dir"; +import chalk from "chalk"; +import { select } from "@inquirer/prompts"; +import { Database } from "bun:sqlite"; +import listTables from "./list-tables"; +import runSQL from "./run-sql"; +export default function () { + return new Command("admin") + .description("View Tables and Data, Run SQL Queries, Etc.") + .action(async () => { + const { config } = await init(); + const { db_file_path } = grabDBDir({ config }); + const db = new Database(db_file_path); + console.log(chalk.bold(chalk.blue("\nBun SQLite Admin\n"))); + try { + while (true) { + const paradigm = await select({ + message: "Choose an action:", + choices: [ + { name: "List Tables", value: "list_tables" }, + { name: "Run SQL", value: "run_sql" }, + { name: chalk.dim("✕ Exit"), value: "exit" }, + ], + }); + if (paradigm === "exit") + break; + if (paradigm === "list_tables") + await listTables({ db }); + if (paradigm === "run_sql") + await runSQL({ db }); + } + } + catch (error) { + console.error(error.message); + } + db.close(); + process.exit(); + }); +} diff --git a/dist/commands/admin/list-tables.d.ts b/dist/commands/admin/list-tables.d.ts new file mode 100644 index 0000000..72039a3 --- /dev/null +++ b/dist/commands/admin/list-tables.d.ts @@ -0,0 +1,6 @@ +import { Database } from "bun:sqlite"; +type Params = { + db: Database; +}; +export default function listTables({ db }: Params): Promise; +export {}; diff --git a/dist/commands/admin/list-tables.js b/dist/commands/admin/list-tables.js new file mode 100644 index 0000000..2d41494 --- /dev/null +++ b/dist/commands/admin/list-tables.js @@ -0,0 +1,57 @@ +import { Database } from "bun:sqlite"; +import chalk from "chalk"; +import { select } from "@inquirer/prompts"; +import { AppData } from "../../data/app-data"; +import showEntries from "./show-entries"; +export default async function listTables({ db }) { + const tables = db + .query(`SELECT table_name FROM ${AppData["DbSchemaManagerTableName"]}`) + .all(); + if (!tables.length) { + console.log(chalk.yellow("\nNo tables found.\n")); + return; + } + // Level 1: table selection loop + while (true) { + const tableName = await select({ + message: "Select a table:", + choices: [ + ...tables.map((t) => ({ name: t.table_name, value: t.table_name })), + { name: chalk.dim("← Go Back"), value: "__back__" }, + ], + }); + if (tableName === "__back__") + break; + // Level 2: action loop — stays here until "Go Back" + while (true) { + const action = await select({ + message: `"${tableName}" — choose an action:`, + choices: [ + { name: "Show Entries", value: "entries" }, + { name: "Show Schema", value: "schema" }, + { name: chalk.dim("← Go Back"), value: "__back__" }, + ], + }); + if (action === "__back__") + break; + if (action === "entries") { + await showEntries({ db, tableName }); + } + if (action === "schema") { + const columns = db + .query(`PRAGMA table_info("${tableName}")`) + .all(); + console.log(`\n${chalk.bold(`Schema for "${tableName}":`)} \n`); + console.table(columns.map((c) => ({ + "#": c.cid, + Name: c.name, + Type: c.type, + "Not Null": c.notnull ? "YES" : "NO", + Default: c.dflt_value ?? "(none)", + "Primary Key": c.pk ? "YES" : "NO", + }))); + console.log(); + } + } + } +} diff --git a/dist/commands/admin/run-sql.d.ts b/dist/commands/admin/run-sql.d.ts new file mode 100644 index 0000000..ebd4718 --- /dev/null +++ b/dist/commands/admin/run-sql.d.ts @@ -0,0 +1,6 @@ +import { Database } from "bun:sqlite"; +type Params = { + db: Database; +}; +export default function runSQL({ db }: Params): Promise; +export {}; diff --git a/dist/commands/admin/run-sql.js b/dist/commands/admin/run-sql.js new file mode 100644 index 0000000..e78c6ff --- /dev/null +++ b/dist/commands/admin/run-sql.js @@ -0,0 +1,27 @@ +import { Database } from "bun:sqlite"; +import { input } from "@inquirer/prompts"; +import chalk from "chalk"; +export default async function runSQL({ db }) { + const sql = await input({ + message: "Enter SQL query:", + validate: (val) => val.trim().length > 0 || "Query cannot be empty", + }); + try { + const isSelect = /^select/i.test(sql.trim()); + if (isSelect) { + const rows = db.query(sql).all(); + console.log(`\n${chalk.bold(`Result (${rows.length} row${rows.length !== 1 ? "s" : ""}):`)} \n`); + if (rows.length) + console.table(rows); + else + console.log(chalk.yellow("No rows returned.\n")); + } + else { + const result = db.run(sql); + console.log(chalk.green(`\nSuccess! Affected rows: ${result.changes}, Last insert ID: ${result.lastInsertRowid}\n`)); + } + } + catch (error) { + console.error(chalk.red(`\nSQL Error: ${error.message}\n`)); + } +} diff --git a/dist/commands/admin/show-entries.d.ts b/dist/commands/admin/show-entries.d.ts new file mode 100644 index 0000000..d97f56a --- /dev/null +++ b/dist/commands/admin/show-entries.d.ts @@ -0,0 +1,7 @@ +import { Database } from "bun:sqlite"; +type Params = { + db: Database; + tableName: string; +}; +export default function showEntries({ db, tableName }: Params): Promise; +export {}; diff --git a/dist/commands/admin/show-entries.js b/dist/commands/admin/show-entries.js new file mode 100644 index 0000000..d4984cc --- /dev/null +++ b/dist/commands/admin/show-entries.js @@ -0,0 +1,71 @@ +import { Database } from "bun:sqlite"; +import chalk from "chalk"; +import { select, input } from "@inquirer/prompts"; +const LIMIT = 50; +export default async function showEntries({ db, tableName }) { + let page = 0; + let searchField = null; + let searchTerm = null; + while (true) { + const offset = page * LIMIT; + const rows = searchTerm + ? db + .query(`SELECT * FROM "${tableName}" WHERE "${searchField}" LIKE ? LIMIT ${LIMIT} OFFSET ${offset}`) + .all(`%${searchTerm}%`) + : db + .query(`SELECT * FROM "${tableName}" LIMIT ${LIMIT} OFFSET ${offset}`) + .all(); + const countRow = (searchTerm + ? db + .query(`SELECT COUNT(*) as count FROM "${tableName}" WHERE "${searchField}" LIKE ?`) + .get(`%${searchTerm}%`) + : db + .query(`SELECT COUNT(*) as count FROM "${tableName}"`) + .get()); + const total = countRow.count; + const searchInfo = searchTerm + ? chalk.dim(` · searching "${searchField}" = "${searchTerm}"`) + : ""; + console.log(`\n${chalk.bold(tableName)} — Page ${page + 1}${searchInfo} (${rows.length} of ${total}):\n`); + if (rows.length) + console.table(rows); + else + console.log(chalk.yellow("No rows found.")); + console.log(); + const choices = []; + if (page > 0) + choices.push({ name: "← Previous Page", value: "prev" }); + if (offset + rows.length < total) + choices.push({ name: "Next Page →", value: "next" }); + choices.push({ name: "Search by Field", value: "search" }); + if (searchTerm) + choices.push({ name: "Clear Search", value: "clear_search" }); + choices.push({ name: chalk.dim("← Go Back"), value: "__back__" }); + const action = await select({ message: "Navigate:", choices }); + if (action === "__back__") + break; + if (action === "next") + page++; + if (action === "prev") + page--; + if (action === "clear_search") { + searchField = null; + searchTerm = null; + page = 0; + } + if (action === "search") { + const columns = db + .query(`PRAGMA table_info("${tableName}")`) + .all(); + searchField = await select({ + message: "Search by field:", + choices: columns.map((c) => ({ name: c.name, value: c.name })), + }); + searchTerm = await input({ + message: `Search term for "${searchField}":`, + validate: (v) => v.trim().length > 0 || "Cannot be empty", + }); + page = 0; + } + } +} diff --git a/dist/commands/index.js b/dist/commands/index.js index 8c77c48..a00c16a 100644 --- a/dist/commands/index.js +++ b/dist/commands/index.js @@ -4,6 +4,7 @@ import schema from "./schema"; import typedef from "./typedef"; import backup from "./backup"; import restore from "./restore"; +import admin from "./admin"; /** * # Describe Program */ @@ -18,6 +19,7 @@ program.addCommand(schema()); program.addCommand(typedef()); program.addCommand(backup()); program.addCommand(restore()); +program.addCommand(admin()); /** * # Handle Unavailable Commands */ diff --git a/dist/data/app-data.d.ts b/dist/data/app-data.d.ts index c87945c..f3eb051 100644 --- a/dist/data/app-data.d.ts +++ b/dist/data/app-data.d.ts @@ -2,4 +2,5 @@ export declare const AppData: { readonly ConfigFileName: "bun-sqlite.config.ts"; readonly MaxBackups: 10; readonly DefaultBackupDirName: ".backups"; + readonly DbSchemaManagerTableName: "__db_schema_manager__"; }; diff --git a/dist/data/app-data.js b/dist/data/app-data.js index 5045044..439bcb7 100644 --- a/dist/data/app-data.js +++ b/dist/data/app-data.js @@ -2,4 +2,5 @@ export const AppData = { ConfigFileName: "bun-sqlite.config.ts", MaxBackups: 10, DefaultBackupDirName: ".backups", + DbSchemaManagerTableName: "__db_schema_manager__", }; diff --git a/dist/lib/sqlite/db-schema-manager.js b/dist/lib/sqlite/db-schema-manager.js index d928fc9..55036f7 100644 --- a/dist/lib/sqlite/db-schema-manager.js +++ b/dist/lib/sqlite/db-schema-manager.js @@ -2,6 +2,7 @@ import { Database } from "bun:sqlite"; import _ from "lodash"; import DbClient from "."; +import { AppData } from "../../data/app-data"; // Schema Manager Class class SQLiteSchemaManager { db; @@ -10,7 +11,7 @@ class SQLiteSchemaManager { db_schema; constructor({ schema, recreate_vector_table = false, }) { this.db = DbClient; - this.db_manager_table_name = "__db_schema_manager__"; + this.db_manager_table_name = AppData["DbSchemaManagerTableName"]; this.db.run("PRAGMA foreign_keys = ON;"); this.recreate_vector_table = recreate_vector_table; this.createDbManagerTable(); diff --git a/package.json b/package.json index 34e367f..59cffab 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@moduletrace/bun-sqlite", - "version": "1.0.12", + "version": "1.0.13", "description": "SQLite manager for Bun", "author": "Benjamin Toby", "main": "dist/index.js", @@ -13,6 +13,7 @@ }, "devDependencies": { "@types/bun": "latest", + "@types/inquirer": "^9.0.9", "@types/lodash": "^4.17.24", "@types/mysql": "^2.15.27", "@types/node": "^25.3.3" @@ -33,7 +34,7 @@ "@inquirer/prompts": "^8.3.0", "chalk": "^5.6.2", "commander": "^14.0.3", - "inquirer": "^13.3.0", + "inquirer": "^13.3.2", "lodash": "^4.17.23", "mysql": "^2.18.1", "sqlite-vec": "^0.1.7-alpha.2" diff --git a/src/commands/admin/index.ts b/src/commands/admin/index.ts new file mode 100644 index 0000000..cc2a78d --- /dev/null +++ b/src/commands/admin/index.ts @@ -0,0 +1,42 @@ +import { Command } from "commander"; +import init from "../../functions/init"; +import grabDBDir from "../../utils/grab-db-dir"; +import chalk from "chalk"; +import { select } from "@inquirer/prompts"; +import { Database } from "bun:sqlite"; +import listTables from "./list-tables"; +import runSQL from "./run-sql"; + +export default function () { + return new Command("admin") + .description("View Tables and Data, Run SQL Queries, Etc.") + .action(async () => { + const { config } = await init(); + const { db_file_path } = grabDBDir({ config }); + const db = new Database(db_file_path); + + console.log(chalk.bold(chalk.blue("\nBun SQLite Admin\n"))); + + try { + while (true) { + const paradigm = await select({ + message: "Choose an action:", + choices: [ + { name: "List Tables", value: "list_tables" }, + { name: "Run SQL", value: "run_sql" }, + { name: chalk.dim("✕ Exit"), value: "exit" }, + ], + }); + + if (paradigm === "exit") break; + if (paradigm === "list_tables") await listTables({ db }); + if (paradigm === "run_sql") await runSQL({ db }); + } + } catch (error: any) { + console.error(error.message); + } + + db.close(); + process.exit(); + }); +} diff --git a/src/commands/admin/list-tables.ts b/src/commands/admin/list-tables.ts new file mode 100644 index 0000000..d2fecde --- /dev/null +++ b/src/commands/admin/list-tables.ts @@ -0,0 +1,79 @@ +import { Database } from "bun:sqlite"; +import chalk from "chalk"; +import { select } from "@inquirer/prompts"; +import { AppData } from "../../data/app-data"; +import showEntries from "./show-entries"; + +type Params = { db: Database }; + +type ColumnInfo = { + cid: number; + name: string; + type: string; + notnull: number; + dflt_value: string | null; + pk: number; +}; + +export default async function listTables({ db }: Params) { + const tables = db + .query<{ table_name: string }, []>( + `SELECT table_name FROM ${AppData["DbSchemaManagerTableName"]}`, + ) + .all(); + + if (!tables.length) { + console.log(chalk.yellow("\nNo tables found.\n")); + return; + } + + // Level 1: table selection loop + while (true) { + const tableName = await select({ + message: "Select a table:", + choices: [ + ...tables.map((t) => ({ name: t.table_name, value: t.table_name })), + { name: chalk.dim("← Go Back"), value: "__back__" }, + ], + }); + + if (tableName === "__back__") break; + + // Level 2: action loop — stays here until "Go Back" + while (true) { + const action = await select({ + message: `"${tableName}" — choose an action:`, + choices: [ + { name: "Show Entries", value: "entries" }, + { name: "Show Schema", value: "schema" }, + { name: chalk.dim("← Go Back"), value: "__back__" }, + ], + }); + + if (action === "__back__") break; + + if (action === "entries") { + await showEntries({ db, tableName }); + } + + if (action === "schema") { + const columns = db + .query(`PRAGMA table_info("${tableName}")`) + .all(); + + console.log(`\n${chalk.bold(`Schema for "${tableName}":`)} \n`); + console.table( + columns.map((c) => ({ + "#": c.cid, + Name: c.name, + Type: c.type, + "Not Null": c.notnull ? "YES" : "NO", + Default: c.dflt_value ?? "(none)", + "Primary Key": c.pk ? "YES" : "NO", + })), + ); + console.log(); + } + } + } +} diff --git a/src/commands/admin/run-sql.ts b/src/commands/admin/run-sql.ts new file mode 100644 index 0000000..8f2ebef --- /dev/null +++ b/src/commands/admin/run-sql.ts @@ -0,0 +1,34 @@ +import { Database } from "bun:sqlite"; +import { input } from "@inquirer/prompts"; +import chalk from "chalk"; + +type Params = { db: Database }; + +export default async function runSQL({ db }: Params) { + const sql = await input({ + message: "Enter SQL query:", + validate: (val) => val.trim().length > 0 || "Query cannot be empty", + }); + + try { + const isSelect = /^select/i.test(sql.trim()); + + if (isSelect) { + const rows = db.query(sql).all(); + console.log( + `\n${chalk.bold(`Result (${rows.length} row${rows.length !== 1 ? "s" : ""}):`)} \n`, + ); + if (rows.length) console.table(rows); + else console.log(chalk.yellow("No rows returned.\n")); + } else { + const result = db.run(sql); + console.log( + chalk.green( + `\nSuccess! Affected rows: ${result.changes}, Last insert ID: ${result.lastInsertRowid}\n`, + ), + ); + } + } catch (error: any) { + console.error(chalk.red(`\nSQL Error: ${error.message}\n`)); + } +} diff --git a/src/commands/admin/show-entries.ts b/src/commands/admin/show-entries.ts new file mode 100644 index 0000000..5a267ab --- /dev/null +++ b/src/commands/admin/show-entries.ts @@ -0,0 +1,88 @@ +import { Database } from "bun:sqlite"; +import chalk from "chalk"; +import { select, input } from "@inquirer/prompts"; + +type Params = { db: Database; tableName: string }; +type ColumnInfo = { cid: number; name: string }; + +const LIMIT = 50; + +export default async function showEntries({ db, tableName }: Params) { + let page = 0; + let searchField: string | null = null; + let searchTerm: string | null = null; + + while (true) { + const offset = page * LIMIT; + + const rows = searchTerm + ? db + .query( + `SELECT * FROM "${tableName}" WHERE "${searchField}" LIKE ? LIMIT ${LIMIT} OFFSET ${offset}`, + ) + .all(`%${searchTerm}%`) + : db + .query( + `SELECT * FROM "${tableName}" LIMIT ${LIMIT} OFFSET ${offset}`, + ) + .all(); + + const countRow = ( + searchTerm + ? db + .query( + `SELECT COUNT(*) as count FROM "${tableName}" WHERE "${searchField}" LIKE ?`, + ) + .get(`%${searchTerm}%`) + : db + .query(`SELECT COUNT(*) as count FROM "${tableName}"`) + .get() + ) as { count: number }; + + const total = countRow.count; + const searchInfo = searchTerm + ? chalk.dim(` · searching "${searchField}" = "${searchTerm}"`) + : ""; + + console.log( + `\n${chalk.bold(tableName)} — Page ${page + 1}${searchInfo} (${rows.length} of ${total}):\n`, + ); + if (rows.length) console.table(rows); + else console.log(chalk.yellow("No rows found.")); + console.log(); + + const choices: { name: string; value: string }[] = []; + if (page > 0) choices.push({ name: "← Previous Page", value: "prev" }); + if (offset + rows.length < total) + choices.push({ name: "Next Page →", value: "next" }); + choices.push({ name: "Search by Field", value: "search" }); + if (searchTerm) + choices.push({ name: "Clear Search", value: "clear_search" }); + choices.push({ name: chalk.dim("← Go Back"), value: "__back__" }); + + const action = await select({ message: "Navigate:", choices }); + + if (action === "__back__") break; + if (action === "next") page++; + if (action === "prev") page--; + if (action === "clear_search") { + searchField = null; + searchTerm = null; + page = 0; + } + if (action === "search") { + const columns = db + .query(`PRAGMA table_info("${tableName}")`) + .all(); + searchField = await select({ + message: "Search by field:", + choices: columns.map((c) => ({ name: c.name, value: c.name })), + }); + searchTerm = await input({ + message: `Search term for "${searchField}":`, + validate: (v) => v.trim().length > 0 || "Cannot be empty", + }); + page = 0; + } + } +} diff --git a/src/commands/index.ts b/src/commands/index.ts index 9a9424d..b44a4ee 100644 --- a/src/commands/index.ts +++ b/src/commands/index.ts @@ -5,6 +5,7 @@ import schema from "./schema"; import typedef from "./typedef"; import backup from "./backup"; import restore from "./restore"; +import admin from "./admin"; /** * # Declare Global Variables @@ -26,6 +27,7 @@ program.addCommand(schema()); program.addCommand(typedef()); program.addCommand(backup()); program.addCommand(restore()); +program.addCommand(admin()); /** * # Handle Unavailable Commands diff --git a/src/data/app-data.ts b/src/data/app-data.ts index 63108af..c406a89 100644 --- a/src/data/app-data.ts +++ b/src/data/app-data.ts @@ -2,4 +2,5 @@ export const AppData = { ConfigFileName: "bun-sqlite.config.ts", MaxBackups: 10, DefaultBackupDirName: ".backups", + DbSchemaManagerTableName: "__db_schema_manager__", } as const; diff --git a/src/lib/sqlite/db-schema-manager.ts b/src/lib/sqlite/db-schema-manager.ts index a9aec63..7c18c11 100644 --- a/src/lib/sqlite/db-schema-manager.ts +++ b/src/lib/sqlite/db-schema-manager.ts @@ -8,6 +8,7 @@ import type { BUN_SQLITE_FieldSchemaType, BUN_SQLITE_TableSchemaType, } from "../../types"; +import { AppData } from "../../data/app-data"; // Schema Manager Class class SQLiteSchemaManager { @@ -24,7 +25,7 @@ class SQLiteSchemaManager { recreate_vector_table?: boolean; }) { this.db = DbClient; - this.db_manager_table_name = "__db_schema_manager__"; + this.db_manager_table_name = AppData["DbSchemaManagerTableName"]; this.db.run("PRAGMA foreign_keys = ON;"); this.recreate_vector_table = recreate_vector_table; this.createDbManagerTable();