diff --git a/data/bin/duckdb b/data/bin/duckdb new file mode 100644 index 0000000..c2b4b5b --- /dev/null +++ b/data/bin/duckdb @@ -0,0 +1 @@ +DuckDB In-Memory SQL Database \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 390e337..6e6719c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,19 +8,22 @@ "name": "akash-portfolio", "version": "0.1.0", "dependencies": { + "@duckdb/duckdb-wasm": "^1.29.0", + "@tailwindcss/postcss": "^4.0.14", "@vercel/analytics": "^1.5.0", "@vercel/speed-insights": "^1.2.0", "js-argparse": "^0.3.4", "next": "15.5.2", + "postcss": "^8.5.3", "react": "19.0.0", "react-dom": "19.0.0", "react-innertext": "^1.1.5", "table": "^6.9.0", + "tailwindcss": "^4.0.14", "zustand": "^5.0.3" }, "devDependencies": { "@eslint/js": "^9.22.0", - "@tailwindcss/postcss": "^4.0.14", "@types/emscripten": "^1.40.0", "@types/figlet": "^1.7.0", "@types/node": "22.13.10", @@ -28,8 +31,6 @@ "@types/react-dom": "19.0.4", "eslint": "9.22.0", "eslint-config-next": "15.2.2", - "postcss": "^8.5.3", - "tailwindcss": "^4.0.14", "typescript": "5.8.2", "typescript-eslint": "^8.26.1" } @@ -38,7 +39,6 @@ "version": "5.2.0", "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz", "integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==", - "dev": true, "license": "MIT", "engines": { "node": ">=10" @@ -47,16 +47,12 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@emnapi/core": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.3.1.tgz", - "integrity": "sha512-pVGjBIt1Y6gg3EJN8jTcfpP/+uuRksIo055oE/OBkDNcjZqVbfkWCksG1Jp4yZnj3iKWyWX8fdG/j6UDYPbFog==", - "dev": true, - "license": "MIT", - "optional": true, + "node_modules/@duckdb/duckdb-wasm": { + "version": "1.30.0", + "resolved": "https://registry.npmjs.org/@duckdb/duckdb-wasm/-/duckdb-wasm-1.30.0.tgz", + "integrity": "sha512-9aWrm+4ayl4sTlvGtl/b+LxrUyXaac3yyVqkoJ3F7Vkd62PoS8PcQIRJ/KjXBW36LP1CnPY5jjvFyIcTFLtcXA==", "dependencies": { - "@emnapi/wasi-threads": "1.0.1", - "tslib": "^2.4.0" + "apache-arrow": "^17.0.0" } }, "node_modules/@emnapi/runtime": { @@ -69,17 +65,6 @@ "tslib": "^2.4.0" } }, - "node_modules/@emnapi/wasi-threads": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.0.1.tgz", - "integrity": "sha512-iIBu7mwkq4UQGeMEM8bLwNK962nXdhodeScX4slfQnRhEMMzvYivHhutCIk8uojvmASXXPC2WNEjwxFWk72Oqw==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, "node_modules/@eslint-community/eslint-utils": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", @@ -275,7 +260,6 @@ "cpu": [ "arm64" ], - "license": "Apache-2.0", "optional": true, "os": [ "darwin" @@ -297,7 +281,6 @@ "cpu": [ "x64" ], - "license": "Apache-2.0", "optional": true, "os": [ "darwin" @@ -319,7 +302,6 @@ "cpu": [ "arm64" ], - "license": "LGPL-3.0-or-later", "optional": true, "os": [ "darwin" @@ -335,7 +317,6 @@ "cpu": [ "x64" ], - "license": "LGPL-3.0-or-later", "optional": true, "os": [ "darwin" @@ -351,7 +332,6 @@ "cpu": [ "arm" ], - "license": "LGPL-3.0-or-later", "optional": true, "os": [ "linux" @@ -367,7 +347,6 @@ "cpu": [ "arm64" ], - "license": "LGPL-3.0-or-later", "optional": true, "os": [ "linux" @@ -399,7 +378,6 @@ "cpu": [ "s390x" ], - "license": "LGPL-3.0-or-later", "optional": true, "os": [ "linux" @@ -415,7 +393,6 @@ "cpu": [ "x64" ], - "license": "LGPL-3.0-or-later", "optional": true, "os": [ "linux" @@ -431,7 +408,6 @@ "cpu": [ "arm64" ], - "license": "LGPL-3.0-or-later", "optional": true, "os": [ "linux" @@ -447,7 +423,6 @@ "cpu": [ "x64" ], - "license": "LGPL-3.0-or-later", "optional": true, "os": [ "linux" @@ -463,7 +438,6 @@ "cpu": [ "arm" ], - "license": "Apache-2.0", "optional": true, "os": [ "linux" @@ -485,7 +459,6 @@ "cpu": [ "arm64" ], - "license": "Apache-2.0", "optional": true, "os": [ "linux" @@ -529,7 +502,6 @@ "cpu": [ "s390x" ], - "license": "Apache-2.0", "optional": true, "os": [ "linux" @@ -551,7 +523,6 @@ "cpu": [ "x64" ], - "license": "Apache-2.0", "optional": true, "os": [ "linux" @@ -573,7 +544,6 @@ "cpu": [ "arm64" ], - "license": "Apache-2.0", "optional": true, "os": [ "linux" @@ -595,7 +565,6 @@ "cpu": [ "x64" ], - "license": "Apache-2.0", "optional": true, "os": [ "linux" @@ -617,7 +586,6 @@ "cpu": [ "wasm32" ], - "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT", "optional": true, "dependencies": { "@emnapi/runtime": "^1.4.4" @@ -655,7 +623,6 @@ "cpu": [ "ia32" ], - "license": "Apache-2.0 AND LGPL-3.0-or-later", "optional": true, "os": [ "win32" @@ -674,7 +641,6 @@ "cpu": [ "x64" ], - "license": "Apache-2.0 AND LGPL-3.0-or-later", "optional": true, "os": [ "win32" @@ -686,24 +652,10 @@ "url": "https://opencollective.com/libvips" } }, - "node_modules/@napi-rs/wasm-runtime": { - "version": "0.2.7", - "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.7.tgz", - "integrity": "sha512-5yximcFK5FNompXfJFoWanu5l8v1hNGqNHh9du1xETp9HWk/B/PzvchX55WYOPaIeNglG8++68AAiauBAtbnzw==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@emnapi/core": "^1.3.1", - "@emnapi/runtime": "^1.3.1", - "@tybys/wasm-util": "^0.9.0" - } - }, "node_modules/@next/env": { "version": "15.5.2", "resolved": "https://registry.npmjs.org/@next/env/-/env-15.5.2.tgz", - "integrity": "sha512-Qe06ew4zt12LeO6N7j8/nULSOe3fMXE4dM6xgpBQNvdzyK1sv5y4oAP3bq4LamrvGCZtmRYnW8URFCeX5nFgGg==", - "license": "MIT" + "integrity": "sha512-Qe06ew4zt12LeO6N7j8/nULSOe3fMXE4dM6xgpBQNvdzyK1sv5y4oAP3bq4LamrvGCZtmRYnW8URFCeX5nFgGg==" }, "node_modules/@next/eslint-plugin-next": { "version": "15.2.2", @@ -722,7 +674,6 @@ "cpu": [ "arm64" ], - "license": "MIT", "optional": true, "os": [ "darwin" @@ -738,7 +689,6 @@ "cpu": [ "x64" ], - "license": "MIT", "optional": true, "os": [ "darwin" @@ -754,7 +704,6 @@ "cpu": [ "arm64" ], - "license": "MIT", "optional": true, "os": [ "linux" @@ -770,7 +719,6 @@ "cpu": [ "arm64" ], - "license": "MIT", "optional": true, "os": [ "linux" @@ -786,7 +734,6 @@ "cpu": [ "x64" ], - "license": "MIT", "optional": true, "os": [ "linux" @@ -802,7 +749,6 @@ "cpu": [ "x64" ], - "license": "MIT", "optional": true, "os": [ "linux" @@ -818,7 +764,6 @@ "cpu": [ "arm64" ], - "license": "MIT", "optional": true, "os": [ "win32" @@ -834,7 +779,6 @@ "cpu": [ "x64" ], - "license": "MIT", "optional": true, "os": [ "win32" @@ -918,7 +862,6 @@ "version": "4.0.14", "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.0.14.tgz", "integrity": "sha512-Ux9NbFkKWYE4rfUFz6M5JFLs/GEYP6ysxT8uSyPn6aTbh2K3xDE1zz++eVK4Vwx799fzMF8CID9sdHn4j/Ab8w==", - "dev": true, "license": "MIT", "dependencies": { "enhanced-resolve": "^5.18.1", @@ -930,7 +873,6 @@ "version": "2.4.2", "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.4.2.tgz", "integrity": "sha512-rg9zJN+G4n2nfJl5MW3BMygZX56zKPNVEYYqq7adpmMh4Jn2QNEwhvQlFy6jPVdcod7txZtKHWnyZiA3a0zP7A==", - "dev": true, "license": "MIT", "bin": { "jiti": "lib/jiti-cli.mjs" @@ -940,7 +882,6 @@ "version": "4.0.14", "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.0.14.tgz", "integrity": "sha512-M8VCNyO/NBi5vJ2cRcI9u8w7Si+i76a7o1vveoGtbbjpEYJZYiyc7f2VGps/DqawO56l3tImIbq2OT/533jcrA==", - "dev": true, "license": "MIT", "engines": { "node": ">= 10" @@ -959,23 +900,6 @@ "@tailwindcss/oxide-win32-x64-msvc": "4.0.14" } }, - "node_modules/@tailwindcss/oxide-android-arm64": { - "version": "4.0.14", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.0.14.tgz", - "integrity": "sha512-VBFKC2rFyfJ5J8lRwjy6ub3rgpY186kAcYgiUr8ArR8BAZzMruyeKJ6mlsD22Zp5ZLcPW/FXMasJiJBx0WsdQg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">= 10" - } - }, "node_modules/@tailwindcss/oxide-darwin-arm64": { "version": "4.0.14", "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.0.14.tgz", @@ -983,7 +907,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -993,164 +916,10 @@ "node": ">= 10" } }, - "node_modules/@tailwindcss/oxide-darwin-x64": { - "version": "4.0.14", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.0.14.tgz", - "integrity": "sha512-V5AjFuc3ndWGnOi1d379UsODb0TzAS2DYIP/lwEbfvafUaD2aNZIcbwJtYu2DQqO2+s/XBvDVA+w4yUyaewRwg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-freebsd-x64": { - "version": "4.0.14", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.0.14.tgz", - "integrity": "sha512-tXvtxbaZfcPfqBwW3f53lTcyH6EDT+1eT7yabwcfcxTs+8yTPqxsDUhrqe9MrnEzpNkd+R/QAjJapfd4tjWdLg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": { - "version": "4.0.14", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.0.14.tgz", - "integrity": "sha512-cSeLNWWqIWeSTmBntQvyY2/2gcLX8rkPFfDDTQVF8qbRcRMVPLxBvFVJyfSAYRNch6ZyVH2GI6dtgALOBDpdNA==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-linux-arm64-gnu": { - "version": "4.0.14", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.0.14.tgz", - "integrity": "sha512-bwDWLBalXFMDItcSXzFk6y7QKvj6oFlaY9vM+agTlwFL1n1OhDHYLZkSjaYsh6KCeG0VB0r7H8PUJVOM1LRZyg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-linux-arm64-musl": { - "version": "4.0.14", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.0.14.tgz", - "integrity": "sha512-gVkJdnR/L6iIcGYXx64HGJRmlme2FGr/aZH0W6u4A3RgPMAb+6ELRLi+UBiH83RXBm9vwCfkIC/q8T51h8vUJQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-linux-x64-gnu": { - "version": "4.0.14", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.0.14.tgz", - "integrity": "sha512-EE+EQ+c6tTpzsg+LGO1uuusjXxYx0Q00JE5ubcIGfsogSKth8n8i2BcS2wYTQe4jXGs+BQs35l78BIPzgwLddw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-linux-x64-musl": { - "version": "4.0.14", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.0.14.tgz", - "integrity": "sha512-KCCOzo+L6XPT0oUp2Jwh233ETRQ/F6cwUnMnR0FvMUCbkDAzHbcyOgpfuAtRa5HD0WbTbH4pVD+S0pn1EhNfbw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-win32-arm64-msvc": { - "version": "4.0.14", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.0.14.tgz", - "integrity": "sha512-AHObFiFL9lNYcm3tZSPqa/cHGpM5wOrNmM2uOMoKppp+0Hom5uuyRh0QkOp7jftsHZdrZUpmoz0Mp6vhh2XtUg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10" - } - }, - "node_modules/@tailwindcss/oxide-win32-x64-msvc": { - "version": "4.0.14", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.0.14.tgz", - "integrity": "sha512-rNXXMDJfCJLw/ZaFTOLOHoGULxyXfh2iXTGiChFiYTSgKBKQHIGEpV0yn5N25WGzJJ+VBnRjHzlmDqRV+d//oQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 10" - } - }, "node_modules/@tailwindcss/postcss": { "version": "4.0.14", "resolved": "https://registry.npmjs.org/@tailwindcss/postcss/-/postcss-4.0.14.tgz", "integrity": "sha512-+uIR6KtKhla1XeIanF27KtrfYy+PX+R679v5LxbkmEZlhQe3g8rk+wKj7Xgt++rWGRuFLGMXY80Ek8JNn+kN/g==", - "dev": true, "license": "MIT", "dependencies": { "@alloc/quick-lru": "^5.2.0", @@ -1161,16 +930,15 @@ "tailwindcss": "4.0.14" } }, - "node_modules/@tybys/wasm-util": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.9.0.tgz", - "integrity": "sha512-6+7nlbMVX/PVDCwaIQ8nTOPveOcFLSt8GcXdx8hD0bt39uWxYT88uXzqTd4fTvqta7oeUJqudepapKNt2DYJFw==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } + "node_modules/@types/command-line-args": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/@types/command-line-args/-/command-line-args-5.2.3.tgz", + "integrity": "sha512-uv0aG6R0Y8WHZLTamZwtfsDLVRnOa+n+n5rEvFWL5Na5gZ8V2Teab/duDPFzIIIhs9qizDpcavCusCLJZu62Kw==" + }, + "node_modules/@types/command-line-usage": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/@types/command-line-usage/-/command-line-usage-5.0.4.tgz", + "integrity": "sha512-BwR5KP3Es/CSht0xqBcUXS3qCAUVXwpRKsV2+arxeb65atasuXG9LykC9Ab10Cw3s2raH92ZqOeILaQbsB2ACg==" }, "node_modules/@types/emscripten": { "version": "1.40.0", @@ -1483,163 +1251,20 @@ }, "funding": { "url": "https://opencollective.com/eslint" - } - }, - "node_modules/@unrs/rspack-resolver-binding-darwin-arm64": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@unrs/rspack-resolver-binding-darwin-arm64/-/rspack-resolver-binding-darwin-arm64-1.1.2.tgz", - "integrity": "sha512-bQx2L40UF5XxsXwkD26PzuspqUbUswWVbmclmUC+c83Cv/EFrFJ1JaZj5Q5jyYglKGOtyIWY/hXTCdWRN9vT0Q==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@unrs/rspack-resolver-binding-darwin-x64": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@unrs/rspack-resolver-binding-darwin-x64/-/rspack-resolver-binding-darwin-x64-1.1.2.tgz", - "integrity": "sha512-dMi9a7//BsuPTnhWEDxmdKZ6wxQlPnAob8VSjefGbKX/a+pHfTaX1pm/jv2VPdarP96IIjCKPatJS/TtLQeGQA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@unrs/rspack-resolver-binding-freebsd-x64": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@unrs/rspack-resolver-binding-freebsd-x64/-/rspack-resolver-binding-freebsd-x64-1.1.2.tgz", - "integrity": "sha512-RiBZQ+LSORQObfhV1yH7jGz+4sN3SDYtV53jgc8tUVvqdqVDaUm1KA3zHLffmoiYNGrYkE3sSreGC+FVpsB4Vg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ] - }, - "node_modules/@unrs/rspack-resolver-binding-linux-arm-gnueabihf": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@unrs/rspack-resolver-binding-linux-arm-gnueabihf/-/rspack-resolver-binding-linux-arm-gnueabihf-1.1.2.tgz", - "integrity": "sha512-IyKIFBtOvuPCJt1WPx9e9ovTGhZzrIbW11vWzw4aPmx3VShE+YcMpAldqQubdCep0UVKZyFt+2hQDQZwFiJ4jg==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@unrs/rspack-resolver-binding-linux-arm64-gnu": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@unrs/rspack-resolver-binding-linux-arm64-gnu/-/rspack-resolver-binding-linux-arm64-gnu-1.1.2.tgz", - "integrity": "sha512-RfYtlCtJrv5i6TO4dSlpbyOJX9Zbhmkqrr9hjDfr6YyE5KD0ywLRzw8UjXsohxG1XWgRpb2tvPuRYtURJwbqWg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@unrs/rspack-resolver-binding-linux-arm64-musl": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@unrs/rspack-resolver-binding-linux-arm64-musl/-/rspack-resolver-binding-linux-arm64-musl-1.1.2.tgz", - "integrity": "sha512-MaITzkoqsn1Rm3+YnplubgAQEfOt+2jHfFvuFhXseUfcfbxe8Zyc3TM7LKwgv7mRVjIl+/yYN5JqL0cjbnhAnQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@unrs/rspack-resolver-binding-linux-x64-gnu": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@unrs/rspack-resolver-binding-linux-x64-gnu/-/rspack-resolver-binding-linux-x64-gnu-1.1.2.tgz", - "integrity": "sha512-Nu981XmzQqis/uB3j4Gi3p5BYCd/zReU5zbJmjMrEH7IIRH0dxZpdOmS/+KwEk6ao7Xd8P2D2gDHpHD/QTp0aQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@unrs/rspack-resolver-binding-linux-x64-musl": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@unrs/rspack-resolver-binding-linux-x64-musl/-/rspack-resolver-binding-linux-x64-musl-1.1.2.tgz", - "integrity": "sha512-xJupeDvaRpV0ADMuG1dY9jkOjhUzTqtykvchiU2NldSD+nafSUcMWnoqzNUx7HGiqbTMOw9d9xT8ZiFs+6ZFyQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@unrs/rspack-resolver-binding-wasm32-wasi": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@unrs/rspack-resolver-binding-wasm32-wasi/-/rspack-resolver-binding-wasm32-wasi-1.1.2.tgz", - "integrity": "sha512-un6X/xInks+KEgGpIHFV8BdoODHRohaDRvOwtjq+FXuoI4Ga0P6sLRvf4rPSZDvoMnqUhZtVNG0jG9oxOnrrLQ==", - "cpu": [ - "wasm32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@napi-rs/wasm-runtime": "^0.2.7" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@unrs/rspack-resolver-binding-win32-arm64-msvc": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@unrs/rspack-resolver-binding-win32-arm64-msvc/-/rspack-resolver-binding-win32-arm64-msvc-1.1.2.tgz", - "integrity": "sha512-2lCFkeT1HYUb/OOStBS1m67aZOf9BQxRA+Wf/xs94CGgzmoQt7H4V/BrkB/GSGKsudXjkiwt2oHNkHiowAS90A==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] + } }, - "node_modules/@unrs/rspack-resolver-binding-win32-x64-msvc": { + "node_modules/@unrs/rspack-resolver-binding-darwin-arm64": { "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@unrs/rspack-resolver-binding-win32-x64-msvc/-/rspack-resolver-binding-win32-x64-msvc-1.1.2.tgz", - "integrity": "sha512-EYfya5HCQ/8Yfy7rvAAX2rGytu81+d/CIhNCbZfNKLQ690/qFsdEeTXRsMQW1afHoluMM50PsjPYu8ndy8fSQg==", + "resolved": "https://registry.npmjs.org/@unrs/rspack-resolver-binding-darwin-arm64/-/rspack-resolver-binding-darwin-arm64-1.1.2.tgz", + "integrity": "sha512-bQx2L40UF5XxsXwkD26PzuspqUbUswWVbmclmUC+c83Cv/EFrFJ1JaZj5Q5jyYglKGOtyIWY/hXTCdWRN9vT0Q==", "cpu": [ - "x64" + "arm64" ], "dev": true, "license": "MIT", "optional": true, "os": [ - "win32" + "darwin" ] }, "node_modules/@vercel/analytics": { @@ -1779,6 +1404,38 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/apache-arrow": { + "version": "17.0.0", + "resolved": "https://registry.npmjs.org/apache-arrow/-/apache-arrow-17.0.0.tgz", + "integrity": "sha512-X0p7auzdnGuhYMVKYINdQssS4EcKec9TCXyez/qtJt32DrIMGbzqiaMiQ0X6fQlQpw8Fl0Qygcv4dfRAr5Gu9Q==", + "dependencies": { + "@swc/helpers": "^0.5.11", + "@types/command-line-args": "^5.2.3", + "@types/command-line-usage": "^5.0.4", + "@types/node": "^20.13.0", + "command-line-args": "^5.2.1", + "command-line-usage": "^7.0.1", + "flatbuffers": "^24.3.25", + "json-bignum": "^0.0.3", + "tslib": "^2.6.2" + }, + "bin": { + "arrow2csv": "bin/arrow2csv.cjs" + } + }, + "node_modules/apache-arrow/node_modules/@types/node": { + "version": "20.19.24", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.24.tgz", + "integrity": "sha512-FE5u0ezmi6y9OZEzlJfg37mqqf6ZDSF2V/NLjUyGrR9uTZ7Sb9F7bLNZ03S4XVUNRWGA7Ck4c1kK+YnuWjl+DA==", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/apache-arrow/node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==" + }, "node_modules/argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", @@ -1796,6 +1453,14 @@ "node": ">= 0.4" } }, + "node_modules/array-back": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/array-back/-/array-back-3.1.0.tgz", + "integrity": "sha512-TkuxA4UCOvxuDK6NZYXCalszEzj+TLszyASooky+i742l9TqsOdYCMJJupxRic61hwquNtppB3hgcuq9SVSH1Q==", + "engines": { + "node": ">=6" + } + }, "node_modules/array-buffer-byte-length": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.2.tgz", @@ -2131,7 +1796,6 @@ "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, "license": "MIT", "dependencies": { "ansi-styles": "^4.1.0", @@ -2144,6 +1808,20 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, + "node_modules/chalk-template": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/chalk-template/-/chalk-template-0.4.0.tgz", + "integrity": "sha512-/ghrgmhfY8RaSdeo43hNXxpoHAtxdbskUHjPpfqUWGttFgycUhYPGx3YZBCnUCvOa7Doivn1IZec3DEGFoMgLg==", + "dependencies": { + "chalk": "^4.1.2" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/chalk-template?sponsor=1" + } + }, "node_modules/client-only": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz", @@ -2154,7 +1832,6 @@ "version": "4.2.3", "resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz", "integrity": "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==", - "license": "MIT", "optional": true, "dependencies": { "color-convert": "^2.0.1", @@ -2186,13 +1863,56 @@ "version": "1.9.1", "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz", "integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==", - "license": "MIT", "optional": true, "dependencies": { "color-name": "^1.0.0", "simple-swizzle": "^0.2.2" } }, + "node_modules/command-line-args": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/command-line-args/-/command-line-args-5.2.1.tgz", + "integrity": "sha512-H4UfQhZyakIjC74I9d34fGYDwk3XpSr17QhEd0Q3I9Xq1CETHo4Hcuo87WyWHpAF1aSLjLRf5lD9ZGX2qStUvg==", + "dependencies": { + "array-back": "^3.1.0", + "find-replace": "^3.0.0", + "lodash.camelcase": "^4.3.0", + "typical": "^4.0.0" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/command-line-usage": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/command-line-usage/-/command-line-usage-7.0.3.tgz", + "integrity": "sha512-PqMLy5+YGwhMh1wS04mVG44oqDsgyLRSKJBdOo1bnYhMKBW65gZF1dRp2OZRhiTjgUHljy99qkO7bsctLaw35Q==", + "dependencies": { + "array-back": "^6.2.2", + "chalk-template": "^0.4.0", + "table-layout": "^4.1.0", + "typical": "^7.1.1" + }, + "engines": { + "node": ">=12.20.0" + } + }, + "node_modules/command-line-usage/node_modules/array-back": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/array-back/-/array-back-6.2.2.tgz", + "integrity": "sha512-gUAZ7HPyb4SJczXAMUXMGAvI976JoK3qEx9v1FTmeYuJj0IBiaKttG1ydtGKdkfqWkIkouke7nG8ufGy77+Cvw==", + "engines": { + "node": ">=12.17" + } + }, + "node_modules/command-line-usage/node_modules/typical": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/typical/-/typical-7.3.0.tgz", + "integrity": "sha512-ya4mg/30vm+DOWfBg4YK3j2WD6TWtRkCbasOJr40CseYENzCUby/7rIvXA99JGsQHeNxLbnXdyLLxKSv3tauFw==", + "engines": { + "node": ">=12.17" + } + }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -2346,7 +2066,6 @@ "version": "2.0.4", "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.4.tgz", "integrity": "sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA==", - "devOptional": true, "license": "Apache-2.0", "engines": { "node": ">=8" @@ -2391,7 +2110,6 @@ "version": "5.18.1", "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.1.tgz", "integrity": "sha512-ZSW3ma5GkcQBIpwZTSRAI8N71Uuwgs93IezB7mf7R60tC8ZbJideoDNKjHn2O9KIlx6rkGTTEk1xUCK2E1Y2Yg==", - "dev": true, "license": "MIT", "dependencies": { "graceful-fs": "^4.2.4", @@ -3157,6 +2875,17 @@ "node": ">=8" } }, + "node_modules/find-replace": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-replace/-/find-replace-3.0.0.tgz", + "integrity": "sha512-6Tb2myMioCAgv5kfvP5/PkZZ/ntTpVK39fHY7WkWBgvbeE+VHd/tZuZ4mrC+bxh4cfOZeYKVPaJIZtZXV7GNCQ==", + "dependencies": { + "array-back": "^3.0.1" + }, + "engines": { + "node": ">=4.0.0" + } + }, "node_modules/find-up": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", @@ -3188,6 +2917,11 @@ "node": ">=16" } }, + "node_modules/flatbuffers": { + "version": "24.12.23", + "resolved": "https://registry.npmjs.org/flatbuffers/-/flatbuffers-24.12.23.tgz", + "integrity": "sha512-dLVCAISd5mhls514keQzmEG6QHmUUsNuWsb4tFafIUwvvgDjXhtfAYSKOzt5SWOy+qByV5pbsDZ+Vb7HUOBEdA==" + }, "node_modules/flatted": { "version": "3.3.3", "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", @@ -3382,7 +3116,6 @@ "version": "4.2.11", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", - "dev": true, "license": "ISC" }, "node_modules/graphemer": { @@ -3409,7 +3142,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -3557,10 +3289,9 @@ } }, "node_modules/is-arrayish": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz", - "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==", - "license": "MIT", + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.4.tgz", + "integrity": "sha512-m6UrgzFVUYawGBh1dUsWR5M2Clqic9RVXC/9f8ceNlv2IcO9j9J/z8UoCLPqtsPBFNzEpfR3xftohbfqDx8EQA==", "optional": true }, "node_modules/is-async-function": { @@ -4000,6 +3731,14 @@ "js-yaml": "bin/js-yaml.js" } }, + "node_modules/json-bignum": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/json-bignum/-/json-bignum-0.0.3.tgz", + "integrity": "sha512-2WHyXj3OfHSgNyuzDbSxI1w2jgw5gkWSWhS7Qg4bWXx1nLk3jnbwfUeS0PSba3IzpTUWdHxBieELUzXRjQB2zg==", + "engines": { + "node": ">=0.8" + } + }, "node_modules/json-buffer": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", @@ -4098,7 +3837,6 @@ "version": "1.29.2", "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.29.2.tgz", "integrity": "sha512-6b6gd/RUXKaw5keVdSEtqFVdzWnU5jMxTUjA2bVcMNPLwSQ08Sv/UodBVtETLCn7k4S1Ibxwh7k68IwLZPgKaA==", - "dev": true, "license": "MPL-2.0", "dependencies": { "detect-libc": "^2.0.3" @@ -4130,28 +3868,6 @@ "cpu": [ "arm64" ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-darwin-x64": { - "version": "1.29.2", - "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.29.2.tgz", - "integrity": "sha512-j5qYxamyQw4kDXX5hnnCKMf3mLlHvG44f24Qyi2965/Ycz829MYqjrVg2H8BidybHBp9kom4D7DR5VqCKDXS0w==", - "cpu": [ - "x64" - ], - "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -4165,174 +3881,6 @@ "url": "https://opencollective.com/parcel" } }, - "node_modules/lightningcss-freebsd-x64": { - "version": "1.29.2", - "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.29.2.tgz", - "integrity": "sha512-wDk7M2tM78Ii8ek9YjnY8MjV5f5JN2qNVO+/0BAGZRvXKtQrBC4/cn4ssQIpKIPP44YXw6gFdpUF+Ps+RGsCwg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-arm-gnueabihf": { - "version": "1.29.2", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.29.2.tgz", - "integrity": "sha512-IRUrOrAF2Z+KExdExe3Rz7NSTuuJ2HvCGlMKoquK5pjvo2JY4Rybr+NrKnq0U0hZnx5AnGsuFHjGnNT14w26sg==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-arm64-gnu": { - "version": "1.29.2", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.29.2.tgz", - "integrity": "sha512-KKCpOlmhdjvUTX/mBuaKemp0oeDIBBLFiU5Fnqxh1/DZ4JPZi4evEH7TKoSBFOSOV3J7iEmmBaw/8dpiUvRKlQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-arm64-musl": { - "version": "1.29.2", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.29.2.tgz", - "integrity": "sha512-Q64eM1bPlOOUgxFmoPUefqzY1yV3ctFPE6d/Vt7WzLW4rKTv7MyYNky+FWxRpLkNASTnKQUaiMJ87zNODIrrKQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-x64-gnu": { - "version": "1.29.2", - "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.29.2.tgz", - "integrity": "sha512-0v6idDCPG6epLXtBH/RPkHvYx74CVziHo6TMYga8O2EiQApnUPZsbR9nFNrg2cgBzk1AYqEd95TlrsL7nYABQg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-x64-musl": { - "version": "1.29.2", - "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.29.2.tgz", - "integrity": "sha512-rMpz2yawkgGT8RULc5S4WiZopVMOFWjiItBT7aSfDX4NQav6M44rhn5hjtkKzB+wMTRlLLqxkeYEtQ3dd9696w==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-win32-arm64-msvc": { - "version": "1.29.2", - "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.29.2.tgz", - "integrity": "sha512-nL7zRW6evGQqYVu/bKGK+zShyz8OVzsCotFgc7judbt6wnB2KbiKKJwBE4SGoDBQ1O94RjW4asrCjQL4i8Fhbw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-win32-x64-msvc": { - "version": "1.29.2", - "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.29.2.tgz", - "integrity": "sha512-EdIUW3B2vLuHmv7urfzMI/h2fmlnOQBk1xlsDxkN1tCWKjNFjfLhGxYk8C8mzpSfr+A6jFFIi8fU6LbQGsRWjA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, "node_modules/locate-path": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", @@ -4349,6 +3897,11 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/lodash.camelcase": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", + "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==" + }, "node_modules/lodash.merge": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", @@ -4467,7 +4020,6 @@ "version": "15.5.2", "resolved": "https://registry.npmjs.org/next/-/next-15.5.2.tgz", "integrity": "sha512-H8Otr7abj1glFhbGnvUt3gz++0AF1+QoCXEBmd/6aKbfdFwrn0LpA836Ed5+00va/7HQSDD+mOoVhn3tNy3e/Q==", - "license": "MIT", "dependencies": { "@next/env": "15.5.2", "@swc/helpers": "0.5.15", @@ -4807,7 +4359,6 @@ "version": "8.5.3", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.3.tgz", "integrity": "sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==", - "dev": true, "funding": [ { "type": "opencollective", @@ -5200,7 +4751,6 @@ "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.34.3.tgz", "integrity": "sha512-eX2IQ6nFohW4DbvHIOLRB3MHFpYqaqvXd3Tp5e/T/dSH83fxaNJQRvDMhASmkNTsNTVF2/OOopzRCt7xokgPfg==", "hasInstallScript": true, - "license": "Apache-2.0", "optional": true, "dependencies": { "color": "^4.2.3", @@ -5338,10 +4888,9 @@ } }, "node_modules/simple-swizzle": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz", - "integrity": "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==", - "license": "MIT", + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.4.tgz", + "integrity": "sha512-nAu1WFPQSMNr2Zn9PGSZK9AGn4t/y97lEm+MXTtUDwfP0ksAIX4nO+6ruD9Jwut4C49SB1Ws+fbXsm/yScWOHw==", "optional": true, "dependencies": { "is-arrayish": "^0.3.1" @@ -5555,7 +5104,6 @@ "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, "license": "MIT", "dependencies": { "has-flag": "^4.0.0" @@ -5593,6 +5141,26 @@ "node": ">=10.0.0" } }, + "node_modules/table-layout": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/table-layout/-/table-layout-4.1.1.tgz", + "integrity": "sha512-iK5/YhZxq5GO5z8wb0bY1317uDF3Zjpha0QFFLA8/trAoiLbQD0HUbMesEaxyzUgDxi2QlcbM8IvqOlEjgoXBA==", + "dependencies": { + "array-back": "^6.2.2", + "wordwrapjs": "^5.1.0" + }, + "engines": { + "node": ">=12.17" + } + }, + "node_modules/table-layout/node_modules/array-back": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/array-back/-/array-back-6.2.2.tgz", + "integrity": "sha512-gUAZ7HPyb4SJczXAMUXMGAvI976JoK3qEx9v1FTmeYuJj0IBiaKttG1ydtGKdkfqWkIkouke7nG8ufGy77+Cvw==", + "engines": { + "node": ">=12.17" + } + }, "node_modules/table/node_modules/ajv": { "version": "8.17.1", "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", @@ -5639,14 +5207,12 @@ "version": "4.0.14", "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.0.14.tgz", "integrity": "sha512-92YT2dpt671tFiHH/e1ok9D987N9fHD5VWoly1CdPD/Cd1HMglvZwP3nx2yTj2lbXDAHt8QssZkxTLCCTNL+xw==", - "dev": true, "license": "MIT" }, "node_modules/tapable": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==", - "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -5870,6 +5436,14 @@ "typescript": ">=4.8.4 <5.9.0" } }, + "node_modules/typical": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/typical/-/typical-4.0.0.tgz", + "integrity": "sha512-VAH4IvQ7BDFYglMd7BPRDfLgxZZX4O4TFcRDA6EN5X7erNJJq+McIEp8np9aVtxrCJ6qx4GTYVfOWNjcqwZgRw==", + "engines": { + "node": ">=8" + } + }, "node_modules/unbox-primitive": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.1.0.tgz", @@ -6021,6 +5595,14 @@ "node": ">=0.10.0" } }, + "node_modules/wordwrapjs": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/wordwrapjs/-/wordwrapjs-5.1.1.tgz", + "integrity": "sha512-0yweIbkINJodk27gX9LBGMzyQdBDan3s/dEAiwBOj+Mf0PPyWL6/rikalkv8EeD0E8jm4o5RXEOrFTP3NXbhJg==", + "engines": { + "node": ">=12.17" + } + }, "node_modules/yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", diff --git a/package.json b/package.json index 3d48a17..81a9048 100644 --- a/package.json +++ b/package.json @@ -11,19 +11,22 @@ "clean": "make clean" }, "dependencies": { + "@duckdb/duckdb-wasm": "^1.29.0", + "@tailwindcss/postcss": "^4.0.14", "@vercel/analytics": "^1.5.0", "@vercel/speed-insights": "^1.2.0", "js-argparse": "^0.3.4", "next": "15.5.2", + "postcss": "^8.5.3", "react": "19.0.0", "react-dom": "19.0.0", "react-innertext": "^1.1.5", "table": "^6.9.0", + "tailwindcss": "^4.0.14", "zustand": "^5.0.3" }, "devDependencies": { "@eslint/js": "^9.22.0", - "@tailwindcss/postcss": "^4.0.14", "@types/emscripten": "^1.40.0", "@types/figlet": "^1.7.0", "@types/node": "22.13.10", @@ -31,8 +34,6 @@ "@types/react-dom": "19.0.4", "eslint": "9.22.0", "eslint-config-next": "15.2.2", - "postcss": "^8.5.3", - "tailwindcss": "^4.0.14", "typescript": "5.8.2", "typescript-eslint": "^8.26.1" } diff --git a/public/wasm/fs/fs.data b/public/wasm/fs/fs.data index 0c0e8d8..30832f5 100644 --- a/public/wasm/fs/fs.data +++ b/public/wasm/fs/fs.data @@ -1,4 +1,4 @@ -Command to read fileA talking catCommand to change directory Command to clear terminal screenCommand to get helpCommand to list directoryCommand to make directory+---------------------------+--------------------------------+----------------------------------------------------------------------+ +Command to read fileA talking catCommand to change directory Command to clear terminal screenDuckDB In-Memory SQL DatabaseCommand to get helpCommand to list directoryCommand to make directory+---------------------------+--------------------------------+----------------------------------------------------------------------+ | Project | Technologies | Link | |---------------------------|--------------------------------|----------------------------------------------------------------------| | Bash Portfolio | WebAssembly, Next.js | https://github.com/skywalker212/bash-portfolio | diff --git a/public/wasm/fs/fs.js b/public/wasm/fs/fs.js index aa510c6..423c52d 100644 --- a/public/wasm/fs/fs.js +++ b/public/wasm/fs/fs.js @@ -11,10 +11,10 @@ var l=moduleArg,aa,ba,ca=new Promise((a,b)=>{aa=a;ba=b});l.expectedDataFileDownl k)}function e(k){function n(t,y,E){this.start=t;this.end=y;this.audio=E}function r(t){if(!t)throw"Loading data file failed."+Error().stack;if(t.constructor.name!==ArrayBuffer.name)throw"bad input to processPackageData"+Error().stack;t=new Uint8Array(t);n.prototype.ac=t;t=b.files;for(var y=0;y{h?(h(k),h=null):m=k},d);l.calledRun?e(l):(l.preRun??(l.preRun= -[])).push(e)}({files:[{filename:"/bin/cat",start:0,end:20},{filename:"/bin/catsay",start:20,end:33},{filename:"/bin/cd",start:33,end:61},{filename:"/bin/clear",start:61,end:93},{filename:"/bin/help",start:93,end:112},{filename:"/bin/ls",start:112,end:137},{filename:"/bin/mkdir",start:137,end:162},{filename:"/bin/projects",start:162,end:2472},{filename:"/bin/skills",start:2472,end:3495},{filename:"/bin/whoami",start:3495,end:4965}],remote_package_size:4965})})();var ea=Object.assign({},l),p="",fa; -"undefined"!=typeof document&&document.currentScript&&(p=document.currentScript.src);_scriptName&&(p=_scriptName);p.startsWith("blob:")?p="":p=p.slice(0,p.replace(/[?#].*/,"").lastIndexOf("/")+1);fa=async a=>{a=await fetch(a,{credentials:"same-origin"});if(a.ok)return a.arrayBuffer();throw Error(a.status+" : "+a.url);};var ha=l.print||console.log.bind(console),ia=l.printErr||console.error.bind(console);Object.assign(l,ea);ea=null;var ja=l.wasmBinary,ka,la=!1,ma,u,w,x,na,z,A,oa,B,pa,ra,sa=!1; -function ta(){var a=ka.buffer;l.HEAP8=u=new Int8Array(a);l.HEAP16=x=new Int16Array(a);l.HEAPU8=w=new Uint8Array(a);l.HEAPU16=na=new Uint16Array(a);l.HEAP32=z=new Int32Array(a);l.HEAPU32=A=new Uint32Array(a);l.HEAPF32=oa=new Float32Array(a);l.HEAPF64=ra=new Float64Array(a);l.HEAP64=B=new BigInt64Array(a);l.HEAPU64=pa=new BigUint64Array(a)}var ua=0,va=null;function wa(){ua++;l.monitorRunDependencies?.(ua)}function xa(){ua--;l.monitorRunDependencies?.(ua);if(0==ua&&va){var a=va;va=null;a()}} -function ya(a){l.onAbort?.(a);a="Aborted("+a+")";ia(a);la=!0;a+=". Build with -sASSERTIONS for more info.";sa&&za();a=new WebAssembly.RuntimeError(a);ba(a);throw a;}var Aa;async function Ba(a){if(!ja)try{var b=await fa(a);return new Uint8Array(b)}catch{}if(a==Aa&&ja)a=new Uint8Array(ja);else throw"both async and sync fetching of the wasm failed";return a} +[])).push(e)}({files:[{filename:"/bin/cat",start:0,end:20},{filename:"/bin/catsay",start:20,end:33},{filename:"/bin/cd",start:33,end:61},{filename:"/bin/clear",start:61,end:93},{filename:"/bin/duckdb",start:93,end:122},{filename:"/bin/help",start:122,end:141},{filename:"/bin/ls",start:141,end:166},{filename:"/bin/mkdir",start:166,end:191},{filename:"/bin/projects",start:191,end:2501},{filename:"/bin/skills",start:2501,end:3524},{filename:"/bin/whoami",start:3524,end:4994}],remote_package_size:4994})})(); +var ea=Object.assign({},l),p="",fa;"undefined"!=typeof document&&document.currentScript&&(p=document.currentScript.src);_scriptName&&(p=_scriptName);p.startsWith("blob:")?p="":p=p.slice(0,p.replace(/[?#].*/,"").lastIndexOf("/")+1);fa=async a=>{a=await fetch(a,{credentials:"same-origin"});if(a.ok)return a.arrayBuffer();throw Error(a.status+" : "+a.url);};var ha=l.print||console.log.bind(console),ia=l.printErr||console.error.bind(console);Object.assign(l,ea);ea=null; +var ja=l.wasmBinary,ka,la=!1,ma,u,w,x,na,z,A,oa,B,pa,ra,sa=!1;function ta(){var a=ka.buffer;l.HEAP8=u=new Int8Array(a);l.HEAP16=x=new Int16Array(a);l.HEAPU8=w=new Uint8Array(a);l.HEAPU16=na=new Uint16Array(a);l.HEAP32=z=new Int32Array(a);l.HEAPU32=A=new Uint32Array(a);l.HEAPF32=oa=new Float32Array(a);l.HEAPF64=ra=new Float64Array(a);l.HEAP64=B=new BigInt64Array(a);l.HEAPU64=pa=new BigUint64Array(a)}var ua=0,va=null;function wa(){ua++;l.monitorRunDependencies?.(ua)} +function xa(){ua--;l.monitorRunDependencies?.(ua);if(0==ua&&va){var a=va;va=null;a()}}function ya(a){l.onAbort?.(a);a="Aborted("+a+")";ia(a);la=!0;a+=". Build with -sASSERTIONS for more info.";sa&&za();a=new WebAssembly.RuntimeError(a);ba(a);throw a;}var Aa;async function Ba(a){if(!ja)try{var b=await fa(a);return new Uint8Array(b)}catch{}if(a==Aa&&ja)a=new Uint8Array(ja);else throw"both async and sync fetching of the wasm failed";return a} async function Ca(a,b){try{var c=await Ba(a);return await WebAssembly.instantiate(c,b)}catch(d){ia(`failed to asynchronously prepare wasm: ${d}`),ya(d)}}async function Da(a){var b=Aa;if(!ja&&"function"==typeof WebAssembly.instantiateStreaming)try{var c=fetch(b,{credentials:"same-origin"});return await WebAssembly.instantiateStreaming(c,a)}catch(d){ia(`wasm streaming compile failed: ${d}`),ia("falling back to ArrayBuffer instantiation")}return Ca(b,a)} class Ea{name="ExitStatus";constructor(a){this.message=`Program terminated with exit(${a})`;this.status=a}} var Fa=a=>{for(;0{var a=l.preRun.shift();Ha.unshift(a)},Ja=l.noExitRuntime||!0,Ka=(a,b)=>{for(var c=0,d=a.length-1;0<=d;d--){var e=a[d];"."===e?a.splice(d,1):".."===e?(a.splice(d,1),c++):c&&(a.splice(d,1),c--)}if(b)for(;c;c--)a.unshift("..");return a},La=a=>{var b="/"===a.charAt(0),c="/"===a.slice(-1);(a=Ka(a.split("/").filter(d=>!!d),!b).join("/"))||b||(a=".");a&&c&&(a+="/");return(b?"/":"")+a},Ma=a=>{var b=/^(\/?|)([\s\S]*?)((?:\.{1,2}|[^\/]+?|)(\.[^.\/]*|))(?:[\/]*)$/.exec(a).slice(1); diff --git a/src/commands/duckdb.ts b/src/commands/duckdb.ts new file mode 100644 index 0000000..dc69a07 --- /dev/null +++ b/src/commands/duckdb.ts @@ -0,0 +1,181 @@ +import { Command, CommandResultType } from '@/types'; +import { ArgumentParser } from 'js-argparse'; +import * as duckdb from '@duckdb/duckdb-wasm'; +import { createInMemoryDuckDBConnection, createPersistentDuckDBConnection, closeConnection } from '@/lib/duckdb/connection'; +import { executeSqlStatement } from '@/lib/duckdb/queryExecutor'; +import { DuckDBReplState, DEFAULT_DB_NAME, MULTILINE_PROMPT } from '@/lib/duckdb/types'; +import { isMetaCommand, executeMetaCommand } from '@/lib/duckdb/metaCommands'; +import { isCompleteSqlStatement, appendToMultilineBuffer } from '@/lib/duckdb/sqlParser'; + +const name = "duckdb"; +const description = "In-memory SQL database using DuckDB"; + +type Args = { + sql: string[]; + persistent?: boolean; + database?: string; +} + +type DuckDBCommand = Command; + +const duckdbArgs = new ArgumentParser(name, description); + +duckdbArgs.addArgument(['sql'], { + required: false, + metavar: "SQL", + help: "SQL query to execute", + nargs: "*", + default: [] +}); + +duckdbArgs.addArgument(['-p', '--persistent'], { + type: "boolean", + default: false, + help: "Use persistent database (stored in IndexedDB)" +}); + +duckdbArgs.addArgument(['-d', '--database'], { + help: "Database name (for persistent mode)", + default: DEFAULT_DB_NAME +}); + +function createInitialReplState( + db: duckdb.AsyncDuckDB, + connection: duckdb.AsyncDuckDBConnection, + isPersistent: boolean, + dbName: string +): DuckDBReplState { + return { + connection, + db, + sessionId: crypto.randomUUID(), + dbName, + isPersistent, + queryHistory: [], + outputFormat: 'table', + multilineBuffer: '', + loadedFiles: [] + }; +} + +const helpMessage = `DuckDB In-Memory SQL Database + +Usage: + duckdb Enter interactive REPL mode + duckdb "SQL query" Execute a single SQL query + +REPL Commands: + .exit Exit REPL mode + .help Show this help message + +Examples: + duckdb + > CREATE TABLE users (id INT, name VARCHAR, age INT) + > INSERT INTO users VALUES (1, 'Alice', 30), (2, 'Bob', 25) + > SELECT * FROM users + > .exit + +Or single command: + duckdb "SELECT 1 + 1 as result" + +Note: In REPL mode, database persists across queries. Exiting REPL clears the database.`; + +export const duckdbCommand: DuckDBCommand = { + name, + args: duckdbArgs, + description, + execute: async ({ terminalStore, fileSystem }, args) => { + try { + const sqlInput = args.sql.join(" ").trim(); + const isInRepl = terminalStore.replMode === 'duckdb'; + + if (isInRepl) { + const state = terminalStore.replData as DuckDBReplState; + + if (isMetaCommand(sqlInput)) { + const result = await executeMetaCommand(sqlInput, state, { terminalStore, fileSystem }); + + if (result.metadata?.shouldExit) { + await closeConnection(state.connection); + terminalStore.setReplMode(null); + return { + content: "Exited DuckDB REPL", + type: CommandResultType.INFO + }; + } + + return result; + } + + if (!sqlInput) { + return { + content: helpMessage, + type: CommandResultType.TEXT + }; + } + + state.multilineBuffer = appendToMultilineBuffer(state.multilineBuffer, sqlInput); + + if (!isCompleteSqlStatement(state.multilineBuffer)) { + return { + content: MULTILINE_PROMPT, + type: CommandResultType.INFO + }; + } + + const completeStatement = state.multilineBuffer; + state.multilineBuffer = ''; + + const result = await executeSqlStatement(state.connection, completeStatement, state.outputFormat); + + if (result.type !== CommandResultType.ERROR) { + state.queryHistory.push(completeStatement); + } + + return result; + } + + if (!sqlInput) { + let db: duckdb.AsyncDuckDB; + let connection: duckdb.AsyncDuckDBConnection; + let wasRestored = false; + + if (args.persistent) { + const result = await createPersistentDuckDBConnection(args.database!); + db = result.db; + connection = result.connection; + wasRestored = result.wasRestored; + } else { + const result = await createInMemoryDuckDBConnection(); + db = result.db; + connection = result.connection; + } + + const state = createInitialReplState(db, connection, args.persistent || false, args.database!); + terminalStore.setReplMode('duckdb', state); + + const modeMsg = args.persistent + ? ` (persistent: ${args.database}${wasRestored ? ', restored from save' : ''})` + : ' (in-memory)'; + + return { + content: `Entered DuckDB REPL${modeMsg}. Type .exit to quit, .help for help.`, + type: CommandResultType.INFO + }; + } + + const { connection } = await createInMemoryDuckDBConnection(); + const result = await executeSqlStatement(connection, sqlInput, 'table'); + await closeConnection(connection); + + return result; + + } catch (error: unknown) { + const errorMessage = error instanceof Error ? error.message : String(error); + return { + content: `DuckDB Error: ${errorMessage}`, + type: CommandResultType.ERROR + }; + } + } +}; diff --git a/src/commands/index.ts b/src/commands/index.ts index c434218..fc79659 100644 --- a/src/commands/index.ts +++ b/src/commands/index.ts @@ -10,6 +10,7 @@ import { mkdirCommand } from './mkdir'; import { catCommand } from './cat'; import { rmCommand } from './rm'; import { catsayCommand } from './catsay'; +import { duckdbCommand } from './duckdb'; export const commands = [ helpCommand, @@ -17,6 +18,7 @@ export const commands = [ skillsCommand, projectsCommand, catsayCommand, + duckdbCommand, clearCommand, lsCommand, cdCommand, diff --git a/src/components/Terminal.tsx b/src/components/Terminal.tsx index c7f6a59..edc5490 100644 --- a/src/components/Terminal.tsx +++ b/src/components/Terminal.tsx @@ -13,7 +13,7 @@ export const FileSystemContext = createContext(null) const Terminal: React.FC = () => { const [input, setInput] = useState(''); const terminalStore = useTerminalStore(); - const { currentDirectory, addCommandToHistory } = terminalStore; + const { currentDirectory, addCommandToHistory, addDuckdbCommandToHistory, replMode } = terminalStore; const { output, setOutput, executeCommand, clearTerminal } = useTerminal(initialRender, terminalStore); const inputRef = useAutoFocus(); const terminalRef = useRef(null); @@ -23,17 +23,23 @@ const Terminal: React.FC = () => { const trimmedInput = input.trim(); if (!trimmedInput) return; - addCommandToHistory(trimmedInput); + // Add to appropriate history based on REPL mode + if (replMode === 'duckdb') { + addDuckdbCommandToHistory(trimmedInput); + } else { + addCommandToHistory(trimmedInput); + } + if (trimmedInput === 'clear') { clearTerminal(); } else if (fileSystem) { const result = await executeCommand(trimmedInput, fileSystem); - const inputResult = { content: `${getPrompt(currentDirectory)}${input}`, type: CommandResultType.INPUT }; + const inputResult = { content: `${getPrompt(currentDirectory, replMode)}${input}`, type: CommandResultType.INPUT }; setOutput(prev => [...prev, inputResult, ...result]); } setInput(''); // eslint-disable-next-line react-hooks/exhaustive-deps - }, [input, fileSystem, currentDirectory, addCommandToHistory, clearTerminal, executeCommand, setOutput]); + }, [input, fileSystem, currentDirectory, replMode, addCommandToHistory, addDuckdbCommandToHistory, clearTerminal, executeCommand, setOutput]); useKeyboardNavigation(inputRef, fileSystem as WASMFileSystem, getPreviousCommand, getNextCommand, setInput, clearTerminal, handleSubmit); @@ -81,7 +87,7 @@ const Terminal: React.FC = () => { ref={inputRef} value={input} onChange={(e) => setInput(e.target.value)} - prompt={getPrompt(currentDirectory)} + prompt={getPrompt(currentDirectory, replMode)} />} diff --git a/src/lib/duckdb/connection.ts b/src/lib/duckdb/connection.ts new file mode 100644 index 0000000..d3d8a04 --- /dev/null +++ b/src/lib/duckdb/connection.ts @@ -0,0 +1,40 @@ +import * as duckdb from '@duckdb/duckdb-wasm'; +import { loadDatabaseFromIndexedDB } from './persistence'; + +export async function createInMemoryDuckDBConnection(): Promise<{ + db: duckdb.AsyncDuckDB; + connection: duckdb.AsyncDuckDBConnection; +}> { + const JSDELIVR_BUNDLES = duckdb.getJsDelivrBundles(); + const bundle = await duckdb.selectBundle(JSDELIVR_BUNDLES); + + const worker_url = URL.createObjectURL( + new Blob([`importScripts("${bundle.mainWorker}");`], { type: 'text/javascript' }) + ); + + const worker = new Worker(worker_url); + const logger = new duckdb.ConsoleLogger(); + + const db = new duckdb.AsyncDuckDB(logger, worker); + await db.instantiate(bundle.mainModule); + + const connection = await db.connect(); + + return { db, connection }; +} + +export async function createPersistentDuckDBConnection(dbName: string): Promise<{ + db: duckdb.AsyncDuckDB; + connection: duckdb.AsyncDuckDBConnection; + wasRestored: boolean; +}> { + const { db, connection } = await createInMemoryDuckDBConnection(); + + const wasRestored = await loadDatabaseFromIndexedDB(connection, dbName); + + return { db, connection, wasRestored }; +} + +export async function closeConnection(connection: duckdb.AsyncDuckDBConnection): Promise { + await connection.close(); +} diff --git a/src/lib/duckdb/fileLoader.ts b/src/lib/duckdb/fileLoader.ts new file mode 100644 index 0000000..59f5010 --- /dev/null +++ b/src/lib/duckdb/fileLoader.ts @@ -0,0 +1,48 @@ +import * as duckdb from '@duckdb/duckdb-wasm'; +import { WASMFileSystem } from '@/utils/fileSystemUtils'; + +export async function loadDatabaseFromWasmFilesystem( + db: duckdb.AsyncDuckDB, + wasmFs: WASMFileSystem, + filePath: string +): Promise { + const fileContent = wasmFs.readFile(filePath); + const encoder = new TextEncoder(); + const binaryData = encoder.encode(fileContent); + + await db.registerFileBuffer(filePath, binaryData); +} + +export async function loadDatabaseFromLocalFile( + db: duckdb.AsyncDuckDB, + file: File +): Promise { + const arrayBuffer = await file.arrayBuffer(); + const uint8Array = new Uint8Array(arrayBuffer); + + const virtualPath = `/uploads/${file.name}`; + await db.registerFileBuffer(virtualPath, uint8Array); +} + +export function promptForFileUpload(): Promise { + return new Promise((resolve) => { + const input = document.createElement('input'); + input.type = 'file'; + input.accept = '.duckdb,.db'; + input.style.display = 'none'; + + input.onchange = () => { + const file = input.files?.[0] || null; + document.body.removeChild(input); + resolve(file); + }; + + input.oncancel = () => { + document.body.removeChild(input); + resolve(null); + }; + + document.body.appendChild(input); + input.click(); + }); +} diff --git a/src/lib/duckdb/formatters.ts b/src/lib/duckdb/formatters.ts new file mode 100644 index 0000000..6438968 --- /dev/null +++ b/src/lib/duckdb/formatters.ts @@ -0,0 +1,120 @@ +import * as arrow from 'apache-arrow'; +import { CommandResult, CommandResultType, TableCommandResult, TableType } from '@/types'; +import { OutputFormat } from './types'; + +export function formatValue(value: unknown): string { + if (value === null || value === undefined) return 'NULL'; + if (typeof value === 'string') return value; + if (typeof value === 'number') return value.toString(); + if (typeof value === 'boolean') return value ? 'true' : 'false'; + if (value instanceof Date) return value.toISOString(); + return String(value); +} + +export function formatAsTable(result: arrow.Table): TableCommandResult { + const rows: string[][] = []; + const headers = result.schema.fields.map((field: arrow.Field) => field.name); + rows.push(headers); + + for (let i = 0; i < result.numRows; i++) { + const row: string[] = []; + for (let j = 0; j < result.numCols; j++) { + const column = result.getChildAt(j); + const value = column?.get(i); + row.push(formatValue(value)); + } + rows.push(row); + } + + return { + content: rows, + type: CommandResultType.TABLE, + tableType: TableType.NORMAL + }; +} + +function escapeCsvField(field: string): string { + if (field.includes(',') || field.includes('"') || field.includes('\n')) { + return `"${field.replace(/"/g, '""')}"`; + } + return field; +} + +export function formatAsCSV(result: arrow.Table): CommandResult { + const lines: string[] = []; + const headers = result.schema.fields.map((f: arrow.Field) => f.name); + lines.push(headers.map(escapeCsvField).join(',')); + + for (let i = 0; i < result.numRows; i++) { + const row: string[] = []; + for (let j = 0; j < result.numCols; j++) { + const column = result.getChildAt(j); + const value = column?.get(i); + row.push(escapeCsvField(formatValue(value))); + } + lines.push(row.join(',')); + } + + return { + content: lines.join('\n'), + type: CommandResultType.TEXT + }; +} + +export function formatAsJSON(result: arrow.Table): CommandResult { + const rows: Record[] = []; + const headers = result.schema.fields.map((f: arrow.Field) => f.name); + + for (let i = 0; i < result.numRows; i++) { + const row: Record = {}; + for (let j = 0; j < result.numCols; j++) { + const column = result.getChildAt(j); + row[headers[j]] = column?.get(i); + } + rows.push(row); + } + + return { + content: JSON.stringify(rows, null, 2), + type: CommandResultType.TEXT + }; +} + +export function formatAsMarkdown(result: arrow.Table): CommandResult { + const lines: string[] = []; + const headers = result.schema.fields.map((f: arrow.Field) => f.name); + + lines.push('| ' + headers.join(' | ') + ' |'); + lines.push('| ' + headers.map(() => '---').join(' | ') + ' |'); + + for (let i = 0; i < result.numRows; i++) { + const row: string[] = []; + for (let j = 0; j < result.numCols; j++) { + const column = result.getChildAt(j); + const value = column?.get(i); + row.push(formatValue(value)); + } + lines.push('| ' + row.join(' | ') + ' |'); + } + + return { + content: lines.join('\n'), + type: CommandResultType.TEXT + }; +} + +export function formatQueryResult( + result: arrow.Table, + format: OutputFormat +): CommandResult { + switch (format) { + case 'table': + return formatAsTable(result); + case 'csv': + return formatAsCSV(result); + case 'json': + return formatAsJSON(result); + case 'markdown': + return formatAsMarkdown(result); + } +} diff --git a/src/lib/duckdb/metaCommands.ts b/src/lib/duckdb/metaCommands.ts new file mode 100644 index 0000000..afaa2b3 --- /dev/null +++ b/src/lib/duckdb/metaCommands.ts @@ -0,0 +1,348 @@ +import { CommandResult, CommandResultType } from '@/types'; +import { MetaCommand, DuckDBReplState, CommandContext, OutputFormat } from './types'; +import { executeSqlStatement } from './queryExecutor'; +import { exportDatabaseToIndexedDB, listSavedDatabases, deleteSavedDatabase } from './persistence'; +import { loadDatabaseFromWasmFilesystem, loadDatabaseFromLocalFile, promptForFileUpload } from './fileLoader'; +import { formatAsCSV, formatAsJSON } from './formatters'; + +const META_COMMANDS: MetaCommand[] = [ + { + name: '.tables', + description: 'List all tables', + usage: '.tables [PATTERN]', + handler: async (args, state) => { + const pattern = args[0] ? `WHERE name LIKE '${args[0]}'` : ''; + return executeSqlStatement( + state.connection, + `SELECT name, estimated_size FROM duckdb_tables() ${pattern}`, + state.outputFormat + ); + } + }, + { + name: '.schema', + description: 'Show table schema', + usage: '.schema [TABLE_NAME]', + handler: async (args, state) => { + if (args[0]) { + return executeSqlStatement(state.connection, `DESCRIBE ${args[0]}`, state.outputFormat); + } + return executeSqlStatement( + state.connection, + `SELECT sql FROM duckdb_tables()`, + state.outputFormat + ); + } + }, + { + name: '.mode', + description: 'Set output format', + usage: '.mode table|csv|json|markdown', + handler: async (args, state) => { + const format = args[0] as OutputFormat; + if (!['table', 'csv', 'json', 'markdown'].includes(format)) { + throw new Error('Invalid format. Use: table, csv, json, or markdown'); + } + state.outputFormat = format; + return { + content: `Output format set to: ${format}`, + type: CommandResultType.INFO + }; + } + }, + { + name: '.history', + description: 'Show query history', + usage: '.history [N]', + handler: async (args, state) => { + const count = args[0] ? parseInt(args[0]) : 20; + const recent = state.queryHistory.slice(-count); + if (recent.length === 0) { + return { + content: 'No query history', + type: CommandResultType.INFO + }; + } + return { + content: recent.map((q, i) => `${i + 1}. ${q}`).join('\n'), + type: CommandResultType.TEXT + }; + } + }, + { + name: '.load', + description: 'Load .duckdb file', + usage: '.load local|PATH', + handler: async (args, state, context) => { + if (!args[0]) { + throw new Error('Usage: .load local|PATH'); + } + + if (args[0] === 'local') { + const file = await promptForFileUpload(); + if (!file) { + return { + content: 'File upload cancelled', + type: CommandResultType.INFO + }; + } + + try { + await loadDatabaseFromLocalFile(state.db, file); + state.loadedFiles.push(file.name); + return { + content: `Loaded database file: ${file.name}`, + type: CommandResultType.SUCCESS + }; + } catch (error) { + throw new Error(`Failed to load file: ${(error as Error).message}`); + } + } else { + const filePath = args[0]; + try { + await loadDatabaseFromWasmFilesystem(state.db, context.fileSystem, filePath); + state.loadedFiles.push(filePath); + return { + content: `Loaded database file: ${filePath}`, + type: CommandResultType.SUCCESS + }; + } catch (error) { + throw new Error(`Failed to load file: ${(error as Error).message}`); + } + } + } + }, + { + name: '.import', + description: 'Import CSV/JSON file', + usage: '.import FILE_PATH TABLE_NAME', + handler: async (args, state) => { + if (args.length < 2) { + throw new Error('Usage: .import FILE_PATH TABLE_NAME'); + } + + const [filePath, tableName] = args; + const extension = filePath.toLowerCase().split('.').pop(); + + try { + if (extension === 'csv') { + await state.connection.query( + `CREATE TABLE ${tableName} AS SELECT * FROM read_csv_auto('${filePath}')` + ); + } else if (extension === 'json') { + await state.connection.query( + `CREATE TABLE ${tableName} AS SELECT * FROM read_json_auto('${filePath}')` + ); + } else { + throw new Error('Unsupported file format. Use .csv or .json'); + } + + return { + content: `Imported ${filePath} into table ${tableName}`, + type: CommandResultType.SUCCESS + }; + } catch (error) { + throw new Error(`Failed to import file: ${(error as Error).message}`); + } + } + }, + { + name: '.export', + description: 'Export table to file', + usage: '.export TABLE_NAME FILE_PATH [csv|json]', + handler: async (args, state, context) => { + if (args.length < 2) { + throw new Error('Usage: .export TABLE_NAME FILE_PATH [csv|json]'); + } + + const [tableName, filePath] = args; + const format = args[2] || filePath.toLowerCase().split('.').pop() || 'csv'; + + try { + const result = await state.connection.query(`SELECT * FROM ${tableName}`); + + let fileContent: string; + if (format === 'csv') { + const csvResult = formatAsCSV(result); + fileContent = csvResult.content as string; + } else if (format === 'json') { + const jsonResult = formatAsJSON(result); + fileContent = jsonResult.content as string; + } else { + throw new Error('Unsupported format. Use csv or json'); + } + + context.fileSystem.writeFile(filePath, fileContent); + + return { + content: `Exported ${tableName} to ${filePath}`, + type: CommandResultType.SUCCESS + }; + } catch (error) { + throw new Error(`Failed to export table: ${(error as Error).message}`); + } + } + }, + { + name: '.save', + description: 'Save database to IndexedDB', + usage: '.save [DB_NAME]', + handler: async (args, state) => { + const dbName = args[0] || state.dbName; + try { + await exportDatabaseToIndexedDB(state.connection, dbName); + state.isPersistent = true; + state.dbName = dbName; + return { + content: `Database saved as: ${dbName}`, + type: CommandResultType.SUCCESS + }; + } catch (error) { + throw new Error(`Failed to save database: ${(error as Error).message}`); + } + } + }, + { + name: '.databases', + description: 'List saved databases', + usage: '.databases', + handler: async () => { + const databases = await listSavedDatabases(); + if (databases.length === 0) { + return { + content: 'No saved databases', + type: CommandResultType.INFO + }; + } + + const rows: string[][] = [['Name', 'Saved At']]; + for (const db of databases) { + const date = new Date(db.timestamp).toLocaleString(); + rows.push([db.name, date]); + } + + return { + content: rows, + type: CommandResultType.TABLE + }; + } + }, + { + name: '.drop', + description: 'Delete a saved database', + usage: '.drop DB_NAME', + handler: async (args) => { + if (!args[0]) { + throw new Error('Database name required. Usage: .drop DB_NAME'); + } + + try { + await deleteSavedDatabase(args[0]); + return { + content: `Database '${args[0]}' deleted`, + type: CommandResultType.SUCCESS + }; + } catch (error) { + throw new Error(`Failed to delete database: ${(error as Error).message}`); + } + } + }, + { + name: '.help', + description: 'Show help', + usage: '.help [COMMAND]', + handler: async (args) => { + if (args[0]) { + const cmd = findMetaCommand(args[0]); + if (cmd) { + return { + content: `${cmd.name} - ${cmd.description}\nUsage: ${cmd.usage}`, + type: CommandResultType.INFO + }; + } + return { + content: `Unknown command: ${args[0]}`, + type: CommandResultType.ERROR + }; + } + + const helpText = ` +DuckDB REPL Commands: + +Meta Commands: +${META_COMMANDS.map(cmd => ` ${cmd.name.padEnd(20)} ${cmd.description}`).join('\n')} + +SQL Queries: + Type any SQL query and press Enter + Multi-line queries are supported (query continues until semicolon or complete statement) + +Output Formats: + table - Table format (default) + csv - Comma-separated values + json - JSON array of objects + markdown - Markdown table + +Examples: + CREATE TABLE users (id INT, name VARCHAR) + INSERT INTO users VALUES (1, 'Alice'), (2, 'Bob') + SELECT * FROM users + .mode json + SELECT * FROM users + .exit + `.trim(); + + return { + content: helpText, + type: CommandResultType.TEXT + }; + } + }, + { + name: '.exit', + aliases: ['.quit'], + description: 'Exit REPL', + usage: '.exit', + handler: async (args, state) => { + if (state.isPersistent) { + try { + await exportDatabaseToIndexedDB(state.connection, state.dbName); + } catch (error) { + console.error('Failed to auto-save on exit:', error); + } + } + return { + content: 'EXIT_REPL', + type: CommandResultType.INFO, + metadata: { shouldExit: true } + }; + } + } +]; + +export function isMetaCommand(input: string): boolean { + return input.trim().startsWith('.'); +} + +export function findMetaCommand(input: string): MetaCommand | null { + const cmd = input.split(/\s+/)[0].toLowerCase(); + return META_COMMANDS.find( + mc => mc.name === cmd || mc.aliases?.includes(cmd) + ) || null; +} + +export async function executeMetaCommand( + input: string, + state: DuckDBReplState, + context: CommandContext +): Promise { + const parts = input.trim().split(/\s+/); + const commandName = parts[0]; + const args = parts.slice(1); + + const metaCmd = findMetaCommand(commandName); + if (!metaCmd) { + throw new Error(`Unknown meta command: ${commandName}. Type .help for available commands.`); + } + + return await metaCmd.handler(args, state, context); +} diff --git a/src/lib/duckdb/persistence.ts b/src/lib/duckdb/persistence.ts new file mode 100644 index 0000000..73f6084 --- /dev/null +++ b/src/lib/duckdb/persistence.ts @@ -0,0 +1,183 @@ +import * as duckdb from '@duckdb/duckdb-wasm'; +import { SavedDatabase, IDB_NAME, IDB_VERSION, DB_STORE_NAME } from './types'; +import * as arrow from 'apache-arrow'; + +export async function openIndexedDB(): Promise { + return new Promise((resolve, reject) => { + const request = indexedDB.open(IDB_NAME, IDB_VERSION); + + request.onerror = () => reject(request.error); + request.onsuccess = () => resolve(request.result); + + request.onupgradeneeded = (event) => { + const db = (event.target as IDBOpenDBRequest).result; + if (!db.objectStoreNames.contains(DB_STORE_NAME)) { + db.createObjectStore(DB_STORE_NAME, { keyPath: 'name' }); + } + }; + }); +} + +async function getAllTableNames(connection: duckdb.AsyncDuckDBConnection): Promise { + const result = await connection.query(`SELECT name FROM duckdb_tables() WHERE schema = 'main'`); + const names: string[] = []; + + for (let i = 0; i < result.numRows; i++) { + const nameColumn = result.getChildAt(0); + const name = nameColumn?.get(i); + if (name) names.push(String(name)); + } + + return names; +} + +async function getSchemaStatements(connection: duckdb.AsyncDuckDBConnection): Promise { + const queries = [ + `SELECT sql FROM duckdb_tables() WHERE schema = 'main'`, + `SELECT sql FROM duckdb_views() WHERE schema = 'main'`, + `SELECT sql FROM duckdb_indexes() WHERE schema = 'main'` + ]; + + const statements: string[] = []; + + for (const query of queries) { + try { + const result = await connection.query(query); + for (let i = 0; i < result.numRows; i++) { + const sqlColumn = result.getChildAt(0); + const sql = sqlColumn?.get(i); + if (sql) statements.push(String(sql)); + } + } catch { + // Some queries might fail if no views/indexes exist, that's okay + continue; + } + } + + return statements; +} + +export async function exportDatabaseToIndexedDB( + connection: duckdb.AsyncDuckDBConnection, + dbName: string +): Promise { + const tables = await getAllTableNames(connection); + const schema = await getSchemaStatements(connection); + + const tablesData: Record = {}; + + for (const tableName of tables) { + const result = await connection.query(`SELECT * FROM ${tableName}`); + + const writer = arrow.RecordBatchStreamWriter.writeAll(result); + const chunks: Uint8Array[] = []; + + for await (const chunk of writer) { + chunks.push(chunk); + } + + const totalLength = chunks.reduce((acc, chunk) => acc + chunk.length, 0); + const combined = new Uint8Array(totalLength); + let offset = 0; + for (const chunk of chunks) { + combined.set(chunk, offset); + offset += chunk.length; + } + + tablesData[tableName] = combined.buffer; + } + + const savedDb: SavedDatabase = { + name: dbName, + timestamp: Date.now(), + schema, + tables: tablesData + }; + + const db = await openIndexedDB(); + const tx = db.transaction(DB_STORE_NAME, 'readwrite'); + const store = tx.objectStore(DB_STORE_NAME); + + await new Promise((resolve, reject) => { + const request = store.put(savedDb); + request.onsuccess = () => resolve(); + request.onerror = () => reject(request.error); + }); + + db.close(); +} + +export async function loadDatabaseFromIndexedDB( + connection: duckdb.AsyncDuckDBConnection, + dbName: string +): Promise { + const db = await openIndexedDB(); + const tx = db.transaction(DB_STORE_NAME, 'readonly'); + const store = tx.objectStore(DB_STORE_NAME); + + const savedDb = await new Promise((resolve, reject) => { + const request = store.get(dbName); + request.onsuccess = () => resolve(request.result || null); + request.onerror = () => reject(request.error); + }); + + db.close(); + + if (!savedDb) { + return false; + } + + for (const ddl of savedDb.schema) { + try { + await connection.query(ddl); + } catch (error) { + console.error(`Failed to execute DDL: ${ddl}`, error); + throw error; + } + } + + for (const [tableName, arrowBuffer] of Object.entries(savedDb.tables)) { + const uint8Array = new Uint8Array(arrowBuffer); + await connection.insertArrowFromIPCStream(uint8Array, { + name: tableName + }); + } + + return true; +} + +export async function listSavedDatabases(): Promise> { + const db = await openIndexedDB(); + const tx = db.transaction(DB_STORE_NAME, 'readonly'); + const store = tx.objectStore(DB_STORE_NAME); + + const databases = await new Promise>((resolve, reject) => { + const request = store.getAll(); + request.onsuccess = () => { + const results = request.result.map((db: SavedDatabase) => ({ + name: db.name, + timestamp: db.timestamp + })); + resolve(results); + }; + request.onerror = () => reject(request.error); + }); + + db.close(); + + return databases; +} + +export async function deleteSavedDatabase(dbName: string): Promise { + const db = await openIndexedDB(); + const tx = db.transaction(DB_STORE_NAME, 'readwrite'); + const store = tx.objectStore(DB_STORE_NAME); + + await new Promise((resolve, reject) => { + const request = store.delete(dbName); + request.onsuccess = () => resolve(); + request.onerror = () => reject(request.error); + }); + + db.close(); +} diff --git a/src/lib/duckdb/queryExecutor.ts b/src/lib/duckdb/queryExecutor.ts new file mode 100644 index 0000000..605b12f --- /dev/null +++ b/src/lib/duckdb/queryExecutor.ts @@ -0,0 +1,42 @@ +import * as duckdb from '@duckdb/duckdb-wasm'; +import { CommandResult, CommandResultType } from '@/types'; +import { OutputFormat } from './types'; +import { formatQueryResult } from './formatters'; + +export type QueryType = 'SELECT' | 'DML' | 'DDL'; + +export function detectQueryType(sql: string): QueryType { + const trimmed = sql.trim().toUpperCase(); + if (/^(SELECT|SHOW|DESCRIBE|EXPLAIN|PRAGMA)/.test(trimmed)) { + return 'SELECT'; + } + if (/^(INSERT|UPDATE|DELETE)/.test(trimmed)) { + return 'DML'; + } + return 'DDL'; +} + +export async function executeSqlStatement( + connection: duckdb.AsyncDuckDBConnection, + sql: string, + format: OutputFormat = 'table' +): Promise { + const queryType = detectQueryType(sql); + + if (queryType === 'SELECT') { + const result = await connection.query(sql); + + if (result.numRows === 0) { + return { + type: CommandResultType.SUCCESS + }; + } + + return formatQueryResult(result, format); + } else { + await connection.query(sql); + return { + type: CommandResultType.SUCCESS + }; + } +} diff --git a/src/lib/duckdb/sqlParser.ts b/src/lib/duckdb/sqlParser.ts new file mode 100644 index 0000000..02eb458 --- /dev/null +++ b/src/lib/duckdb/sqlParser.ts @@ -0,0 +1,47 @@ +export function isCompleteSqlStatement(sql: string): boolean { + const trimmed = sql.trim(); + + if (!trimmed) return true; + if (trimmed.startsWith('.')) return true; + + if (trimmed.endsWith(';')) return true; + + let parenDepth = 0; + let inString = false; + let stringChar = ''; + + for (let i = 0; i < trimmed.length; i++) { + const char = trimmed[i]; + const prevChar = i > 0 ? trimmed[i - 1] : ''; + + if ((char === "'" || char === '"') && prevChar !== '\\') { + if (!inString) { + inString = true; + stringChar = char; + } else if (char === stringChar) { + inString = false; + } + } + + if (!inString) { + if (char === '(') parenDepth++; + if (char === ')') parenDepth--; + } + } + + if (inString || parenDepth !== 0) { + return false; + } + + const endsWithMultilineKeyword = /\b(SELECT|FROM|WHERE|JOIN|INNER|LEFT|RIGHT|OUTER|ON|GROUP|ORDER|HAVING|UNION|INTERSECT|EXCEPT|WITH|AS|AND|OR|WHEN|THEN|ELSE|CASE|VALUES|SET)\s*$/i.test(trimmed); + + if (endsWithMultilineKeyword) { + return false; + } + + return true; +} + +export function appendToMultilineBuffer(buffer: string, newLine: string): string { + return buffer ? `${buffer}\n${newLine}` : newLine; +} diff --git a/src/lib/duckdb/types.ts b/src/lib/duckdb/types.ts new file mode 100644 index 0000000..892ff58 --- /dev/null +++ b/src/lib/duckdb/types.ts @@ -0,0 +1,52 @@ +import * as duckdb from '@duckdb/duckdb-wasm'; +import { CommandResult } from '@/types'; +import { TerminalStore } from '@/store'; +import { WASMFileSystem } from '@/utils'; + +export type OutputFormat = 'table' | 'csv' | 'json' | 'markdown'; + +export interface DuckDBReplState { + connection: duckdb.AsyncDuckDBConnection; + db: duckdb.AsyncDuckDB; + sessionId: string; + dbName: string; + isPersistent: boolean; + queryHistory: string[]; + outputFormat: OutputFormat; + multilineBuffer: string; + loadedFiles: string[]; +} + +export interface CommandContext { + terminalStore: TerminalStore; + fileSystem: WASMFileSystem; +} + +export interface MetaCommand { + name: string; + aliases?: string[]; + description: string; + usage: string; + handler: MetaCommandHandler; +} + +export type MetaCommandHandler = ( + args: string[], + state: DuckDBReplState, + context: CommandContext +) => Promise; + +export interface SavedDatabase { + name: string; + timestamp: number; + schema: string[]; + tables: Record; +} + +export const DEFAULT_DB_NAME = 'terminal_database'; +export const MAX_QUERY_HISTORY = 100; +export const REPL_PROMPT = 'duckdb> '; +export const MULTILINE_PROMPT = ' -> '; +export const IDB_NAME = 'terminal_duckdb_storage'; +export const IDB_VERSION = 1; +export const DB_STORE_NAME = 'duckdb_databases'; diff --git a/src/store/terminalStore.ts b/src/store/terminalStore.ts index db917b0..b990dd9 100644 --- a/src/store/terminalStore.ts +++ b/src/store/terminalStore.ts @@ -4,30 +4,48 @@ import { terminalConfig } from '@/config' export interface TerminalStore extends TerminalState { addCommandToHistory: (command: string) => void + addDuckdbCommandToHistory: (command: string) => void setHistoryIndex: (index: number) => void + setDuckdbHistoryIndex: (index: number) => void setOutputStream: (stream: TerminalOutputStream, streamInfo?: FileOutputStream) => void changeDirectory: (newDirectory: string) => void + setReplMode: (mode: string | null, data?: unknown) => void } export const useTerminalStore = create((set) => ({ commandHistory: [], + duckdbCommandHistory: [], user: terminalConfig.user, host: terminalConfig.host, outputStream: TerminalOutputStream.STDOUT, currentDirectory: terminalConfig.initialDirectory, historyIndex: 0, + duckdbHistoryIndex: 0, + replMode: null, + replData: null, addCommandToHistory: (command) => set((state) => ({ commandHistory: [...state.commandHistory, command], historyIndex: state.commandHistory.length + 1 })), + addDuckdbCommandToHistory: (command) => set((state) => ({ + duckdbCommandHistory: [...state.duckdbCommandHistory, command], + duckdbHistoryIndex: state.duckdbCommandHistory.length + 1 + })), setHistoryIndex: (index) => set(() => ({ historyIndex: index })), + setDuckdbHistoryIndex: (index) => set(() => ({ + duckdbHistoryIndex: index + })), setOutputStream: (stream, streamInfo) => set(() => ({ outputStream: stream, ...streamInfo ? { streamInfo } : {} })), changeDirectory: (newDirectory) => set({ currentDirectory: newDirectory }), + setReplMode: (mode, data) => set(() => ({ + replMode: mode, + replData: data + })), })); export const getOutputStream = () => { @@ -40,10 +58,13 @@ export const getOutputStream = () => { export const getPreviousCommand = () => { const state = useTerminalStore.getState(); - const commandHistory = state.commandHistory; - const historyIndex = state.historyIndex; + const isDuckdbMode = state.replMode === 'duckdb'; + const commandHistory = isDuckdbMode ? state.duckdbCommandHistory : state.commandHistory; + const historyIndex = isDuckdbMode ? state.duckdbHistoryIndex : state.historyIndex; + const setIndex = isDuckdbMode ? state.setDuckdbHistoryIndex : state.setHistoryIndex; + if (historyIndex > 0) { - state.setHistoryIndex(historyIndex - 1); + setIndex(historyIndex - 1); return commandHistory[historyIndex - 1]; } else { return null; @@ -52,13 +73,16 @@ export const getPreviousCommand = () => { export const getNextCommand = () => { const state = useTerminalStore.getState(); - const commandHistory = state.commandHistory; - const historyIndex = state.historyIndex; + const isDuckdbMode = state.replMode === 'duckdb'; + const commandHistory = isDuckdbMode ? state.duckdbCommandHistory : state.commandHistory; + const historyIndex = isDuckdbMode ? state.duckdbHistoryIndex : state.historyIndex; + const setIndex = isDuckdbMode ? state.setDuckdbHistoryIndex : state.setHistoryIndex; + if (historyIndex < commandHistory.length - 1) { - state.setHistoryIndex(historyIndex + 1); + setIndex(historyIndex + 1); return commandHistory[historyIndex + 1]; } else if (historyIndex === commandHistory.length - 1) { - state.setHistoryIndex(commandHistory.length); + setIndex(commandHistory.length); return ''; } return null; diff --git a/src/types/terminal.ts b/src/types/terminal.ts index d270d3b..6b9f097 100644 --- a/src/types/terminal.ts +++ b/src/types/terminal.ts @@ -21,6 +21,10 @@ export interface TerminalState { currentDirectory: string; commandHistory: string[]; historyIndex: number; + duckdbCommandHistory: string[]; + duckdbHistoryIndex: number; + replMode: string | null; + replData: unknown; } export type CommandHandler = (command: string) => string; @@ -39,6 +43,7 @@ export enum CommandResultType { export interface CommandResult { content?: string[][] | ReactNode; type: CommandResultType; + metadata?: Record; } export enum TableType { diff --git a/src/utils/commandHandler.ts b/src/utils/commandHandler.ts index 89af83d..8a3ea03 100644 --- a/src/utils/commandHandler.ts +++ b/src/utils/commandHandler.ts @@ -6,6 +6,26 @@ import { WASMFileSystem } from './fileSystemUtils'; import { ArgumentParserError, ArgumentTypeError, InvalidChoiceError, InvalidNargsError, MissingRequiredArgumentError, UnknownArgumentError } from 'js-argparse'; export const handleCommand = async (input: string, terminalStore: TerminalStore, fileSystem: WASMFileSystem): Promise => { + // Check if we're in REPL mode + if (terminalStore.replMode) { + // Route input to the REPL command + const replCommand = commands.find(cmd => cmd.name === terminalStore.replMode); + if (replCommand) { + try { + // In REPL mode, treat the entire input as a single argument to the REPL command + const parsedArgs = replCommand.args.parseArgs(input); + const result = await replCommand.execute({ terminalStore, fileSystem }, parsedArgs); + return Array.isArray(result) ? result : [result]; + } catch (error: unknown) { + console.error(`Error in REPL mode:`, error); + return [{ + content: `REPL Error: ${(error as Error).message}`, + type: CommandResultType.ERROR + }]; + } + } + } + const { command: commandName, args, file } = parseCommand(input); if (file) { terminalStore.setOutputStream(TerminalOutputStream.FILE, { name: file }); diff --git a/src/utils/terminalUtils.ts b/src/utils/terminalUtils.ts index 8f34783..93d8e45 100644 --- a/src/utils/terminalUtils.ts +++ b/src/utils/terminalUtils.ts @@ -2,9 +2,10 @@ import { ParsedCommand } from '@/types'; import { terminalConfig } from '@/config'; import { HOME_DIR } from '@/config/terminalConfig'; -export const getPrompt = (currentDirectory: string): string => { +export const getPrompt = (currentDirectory: string, replMode?: string | null): string => { const homeDirectoryRegex = new RegExp(`^${HOME_DIR}`); - return `${terminalConfig.user}@${terminalConfig.host}:${currentDirectory.replace(homeDirectoryRegex,'~')}$ `; + const basePrompt = `${terminalConfig.user}@${terminalConfig.host}:${currentDirectory.replace(homeDirectoryRegex, '~')}`; + return replMode ? `${replMode}> ` : `${basePrompt}$ `; }; export const parseCommand = (input: string): ParsedCommand => {