diff --git a/bun.lock b/bun.lock index a18e385..16a9551 100644 --- a/bun.lock +++ b/bun.lock @@ -22,7 +22,7 @@ "@types/bun": "1.2.1", "@types/ini": "4.1.1", "bun-bagel": "1.1.0", - "ronin": "6.0.27", + "ronin": "6.2.16", "tsup": "8.3.6", "typescript": "5.7.3", }, @@ -103,31 +103,31 @@ "@iarna/toml": ["@iarna/toml@2.2.5", "", {}, "sha512-trnsAYxU3xnS1gPHPyU961coFyLkh4gAD/0zQ5mymY4yOZ+CYvsPqUbOFSw0aDM4y0tV7tiFxL/1XfXPNC6IPg=="], - "@inquirer/checkbox": ["@inquirer/checkbox@4.1.1", "", { "dependencies": { "@inquirer/core": "^10.1.6", "@inquirer/figures": "^1.0.10", "@inquirer/type": "^3.0.4", "ansi-escapes": "^4.3.2", "yoctocolors-cjs": "^2.1.2" }, "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-os5kFd/52gZTl/W6xqMfhaKVJHQM8V/U1P8jcSaQJ/C4Qhdrf2jEXdA/HaxfQs9iiUA/0yzYhk5d3oRHTxGDDQ=="], + "@inquirer/checkbox": ["@inquirer/checkbox@4.1.2", "", { "dependencies": { "@inquirer/core": "^10.1.7", "@inquirer/figures": "^1.0.10", "@inquirer/type": "^3.0.4", "ansi-escapes": "^4.3.2", "yoctocolors-cjs": "^2.1.2" }, "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-PL9ixC5YsPXzXhAZFUPmkXGxfgjkdfZdPEPPmt4kFwQ4LBMDG9n/nHXYRGGZSKZJs+d1sGKWgS2GiPzVRKUdtQ=="], - "@inquirer/confirm": ["@inquirer/confirm@5.1.5", "", { "dependencies": { "@inquirer/core": "^10.1.6", "@inquirer/type": "^3.0.4" }, "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-ZB2Cz8KeMINUvoeDi7IrvghaVkYT2RB0Zb31EaLWOE87u276w4wnApv0SH2qWaJ3r0VSUa3BIuz7qAV2ZvsZlg=="], + "@inquirer/confirm": ["@inquirer/confirm@5.1.6", "", { "dependencies": { "@inquirer/core": "^10.1.7", "@inquirer/type": "^3.0.4" }, "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-6ZXYK3M1XmaVBZX6FCfChgtponnL0R6I7k8Nu+kaoNkT828FVZTcca1MqmWQipaW2oNREQl5AaPCUOOCVNdRMw=="], - "@inquirer/core": ["@inquirer/core@10.1.6", "", { "dependencies": { "@inquirer/figures": "^1.0.10", "@inquirer/type": "^3.0.4", "ansi-escapes": "^4.3.2", "cli-width": "^4.1.0", "mute-stream": "^2.0.0", "signal-exit": "^4.1.0", "wrap-ansi": "^6.2.0", "yoctocolors-cjs": "^2.1.2" }, "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-Bwh/Zk6URrHwZnSSzAZAKH7YgGYi0xICIBDFOqBQoXNNAzBHw/bgXgLmChfp+GyR3PnChcTbiCTZGC6YJNJkMA=="], + "@inquirer/core": ["@inquirer/core@10.1.7", "", { "dependencies": { "@inquirer/figures": "^1.0.10", "@inquirer/type": "^3.0.4", "ansi-escapes": "^4.3.2", "cli-width": "^4.1.0", "mute-stream": "^2.0.0", "signal-exit": "^4.1.0", "wrap-ansi": "^6.2.0", "yoctocolors-cjs": "^2.1.2" }, "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-AA9CQhlrt6ZgiSy6qoAigiA1izOa751ugX6ioSjqgJ+/Gd+tEN/TORk5sUYNjXuHWfW0r1n/a6ak4u/NqHHrtA=="], - "@inquirer/editor": ["@inquirer/editor@4.2.6", "", { "dependencies": { "@inquirer/core": "^10.1.6", "@inquirer/type": "^3.0.4", "external-editor": "^3.1.0" }, "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-l0smvr8g/KAVdXx4I92sFxZiaTG4kFc06cFZw+qqwTirwdUHMFLnouXBB9OafWhpO3cfEkEz2CdPoCmor3059A=="], + "@inquirer/editor": ["@inquirer/editor@4.2.7", "", { "dependencies": { "@inquirer/core": "^10.1.7", "@inquirer/type": "^3.0.4", "external-editor": "^3.1.0" }, "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-gktCSQtnSZHaBytkJKMKEuswSk2cDBuXX5rxGFv306mwHfBPjg5UAldw9zWGoEyvA9KpRDkeM4jfrx0rXn0GyA=="], - "@inquirer/expand": ["@inquirer/expand@4.0.8", "", { "dependencies": { "@inquirer/core": "^10.1.6", "@inquirer/type": "^3.0.4", "yoctocolors-cjs": "^2.1.2" }, "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-k0ouAC6L+0Yoj/j0ys2bat0fYcyFVtItDB7h+pDFKaDDSFJey/C/YY1rmIOqkmFVZ5rZySeAQuS8zLcKkKRLmg=="], + "@inquirer/expand": ["@inquirer/expand@4.0.9", "", { "dependencies": { "@inquirer/core": "^10.1.7", "@inquirer/type": "^3.0.4", "yoctocolors-cjs": "^2.1.2" }, "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-Xxt6nhomWTAmuSX61kVgglLjMEFGa+7+F6UUtdEUeg7fg4r9vaFttUUKrtkViYYrQBA5Ia1tkOJj2koP9BuLig=="], "@inquirer/figures": ["@inquirer/figures@1.0.10", "", {}, "sha512-Ey6176gZmeqZuY/W/nZiUyvmb1/qInjcpiZjXWi6nON+nxJpD1bxtSoBxNliGISae32n6OwbY+TSXPZ1CfS4bw=="], - "@inquirer/input": ["@inquirer/input@4.1.5", "", { "dependencies": { "@inquirer/core": "^10.1.6", "@inquirer/type": "^3.0.4" }, "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-bB6wR5wBCz5zbIVBPnhp94BHv/G4eKbUEjlpCw676pI2chcvzTx1MuwZSCZ/fgNOdqDlAxkhQ4wagL8BI1D3Zg=="], + "@inquirer/input": ["@inquirer/input@4.1.6", "", { "dependencies": { "@inquirer/core": "^10.1.7", "@inquirer/type": "^3.0.4" }, "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-1f5AIsZuVjPT4ecA8AwaxDFNHny/tSershP/cTvTDxLdiIGTeILNcKozB0LaYt6mojJLUbOYhpIxicaYf7UKIQ=="], - "@inquirer/number": ["@inquirer/number@3.0.8", "", { "dependencies": { "@inquirer/core": "^10.1.6", "@inquirer/type": "^3.0.4" }, "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-CTKs+dT1gw8dILVWATn8Ugik1OHLkkfY82J+Musb57KpmF6EKyskv8zmMiEJPzOnLTZLo05X/QdMd8VH9oulXw=="], + "@inquirer/number": ["@inquirer/number@3.0.9", "", { "dependencies": { "@inquirer/core": "^10.1.7", "@inquirer/type": "^3.0.4" }, "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-iN2xZvH3tyIYXLXBvlVh0npk1q/aVuKXZo5hj+K3W3D4ngAEq/DkLpofRzx6oebTUhBvOgryZ+rMV0yImKnG3w=="], - "@inquirer/password": ["@inquirer/password@4.0.8", "", { "dependencies": { "@inquirer/core": "^10.1.6", "@inquirer/type": "^3.0.4", "ansi-escapes": "^4.3.2" }, "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-MgA+Z7o3K1df2lGY649fyOBowHGfrKRz64dx3+b6c1w+h2W7AwBoOkHhhF/vfhbs5S4vsKNCuDzS3s9r5DpK1g=="], + "@inquirer/password": ["@inquirer/password@4.0.9", "", { "dependencies": { "@inquirer/core": "^10.1.7", "@inquirer/type": "^3.0.4", "ansi-escapes": "^4.3.2" }, "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-xBEoOw1XKb0rIN208YU7wM7oJEHhIYkfG7LpTJAEW913GZeaoQerzf5U/LSHI45EVvjAdgNXmXgH51cUXKZcJQ=="], "@inquirer/prompts": ["@inquirer/prompts@7.2.3", "", { "dependencies": { "@inquirer/checkbox": "^4.0.6", "@inquirer/confirm": "^5.1.3", "@inquirer/editor": "^4.2.3", "@inquirer/expand": "^4.0.6", "@inquirer/input": "^4.1.3", "@inquirer/number": "^3.0.6", "@inquirer/password": "^4.0.6", "@inquirer/rawlist": "^4.0.6", "@inquirer/search": "^3.0.6", "@inquirer/select": "^4.0.6" }, "peerDependencies": { "@types/node": ">=18" } }, "sha512-hzfnm3uOoDySDXfDNOm9usOuYIaQvTgKp/13l1uJoe6UNY+Zpcn2RYt0jXz3yA+yemGHvDOxVzqWl3S5sQq53Q=="], - "@inquirer/rawlist": ["@inquirer/rawlist@4.0.8", "", { "dependencies": { "@inquirer/core": "^10.1.6", "@inquirer/type": "^3.0.4", "yoctocolors-cjs": "^2.1.2" }, "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-hl7rvYW7Xl4un8uohQRUgO6uc2hpn7PKqfcGkCOWC0AA4waBxAv6MpGOFCEDrUaBCP+pXPVqp4LmnpWmn1E1+g=="], + "@inquirer/rawlist": ["@inquirer/rawlist@4.0.9", "", { "dependencies": { "@inquirer/core": "^10.1.7", "@inquirer/type": "^3.0.4", "yoctocolors-cjs": "^2.1.2" }, "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-+5t6ebehKqgoxV8fXwE49HkSF2Rc9ijNiVGEQZwvbMI61/Q5RcD+jWD6Gs1tKdz5lkI8GRBL31iO0HjGK1bv+A=="], - "@inquirer/search": ["@inquirer/search@3.0.8", "", { "dependencies": { "@inquirer/core": "^10.1.6", "@inquirer/figures": "^1.0.10", "@inquirer/type": "^3.0.4", "yoctocolors-cjs": "^2.1.2" }, "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-ihSE9D3xQAupNg/aGDZaukqoUSXG2KfstWosVmFCG7jbMQPaj2ivxWtsB+CnYY/T4D6LX1GHKixwJLunNCffww=="], + "@inquirer/search": ["@inquirer/search@3.0.9", "", { "dependencies": { "@inquirer/core": "^10.1.7", "@inquirer/figures": "^1.0.10", "@inquirer/type": "^3.0.4", "yoctocolors-cjs": "^2.1.2" }, "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-DWmKztkYo9CvldGBaRMr0ETUHgR86zE6sPDVOHsqz4ISe9o1LuiWfgJk+2r75acFclA93J/lqzhT0dTjCzHuoA=="], - "@inquirer/select": ["@inquirer/select@4.0.8", "", { "dependencies": { "@inquirer/core": "^10.1.6", "@inquirer/figures": "^1.0.10", "@inquirer/type": "^3.0.4", "ansi-escapes": "^4.3.2", "yoctocolors-cjs": "^2.1.2" }, "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-Io2prxFyN2jOCcu4qJbVoilo19caiD3kqkD3WR0q3yDA5HUCo83v4LrRtg55ZwniYACW64z36eV7gyVbOfORjA=="], + "@inquirer/select": ["@inquirer/select@4.0.9", "", { "dependencies": { "@inquirer/core": "^10.1.7", "@inquirer/figures": "^1.0.10", "@inquirer/type": "^3.0.4", "ansi-escapes": "^4.3.2", "yoctocolors-cjs": "^2.1.2" }, "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-BpJyJe7Dkhv2kz7yG7bPSbJLQuu/rqyNlF1CfiiFeFwouegfH+zh13KDyt6+d9DwucKo7hqM3wKLLyJxZMO+Xg=="], "@inquirer/type": ["@inquirer/type@3.0.4", "", { "peerDependencies": { "@types/node": ">=18" }, "optionalPeers": ["@types/node"] }, "sha512-2MNFrDY8jkFYc9Il9DgLsHhMzuHnOYM1+CUYVWbzu9oT0hC7V7EcYvdCKeoll/Fcci04A+ERZ9wcc7cQ8lTkIA=="], @@ -145,51 +145,51 @@ "@pkgjs/parseargs": ["@pkgjs/parseargs@0.11.0", "", {}, "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg=="], - "@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.34.6", "", { "os": "android", "cpu": "arm" }, "sha512-+GcCXtOQoWuC7hhX1P00LqjjIiS/iOouHXhMdiDSnq/1DGTox4SpUvO52Xm+div6+106r+TcvOeo/cxvyEyTgg=="], + "@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.34.8", "", { "os": "android", "cpu": "arm" }, "sha512-q217OSE8DTp8AFHuNHXo0Y86e1wtlfVrXiAlwkIvGRQv9zbc6mE3sjIVfwI8sYUyNxwOg0j/Vm1RKM04JcWLJw=="], - "@rollup/rollup-android-arm64": ["@rollup/rollup-android-arm64@4.34.6", "", { "os": "android", "cpu": "arm64" }, "sha512-E8+2qCIjciYUnCa1AiVF1BkRgqIGW9KzJeesQqVfyRITGQN+dFuoivO0hnro1DjT74wXLRZ7QF8MIbz+luGaJA=="], + "@rollup/rollup-android-arm64": ["@rollup/rollup-android-arm64@4.34.8", "", { "os": "android", "cpu": "arm64" }, "sha512-Gigjz7mNWaOL9wCggvoK3jEIUUbGul656opstjaUSGC3eT0BM7PofdAJaBfPFWWkXNVAXbaQtC99OCg4sJv70Q=="], - "@rollup/rollup-darwin-arm64": ["@rollup/rollup-darwin-arm64@4.34.6", "", { "os": "darwin", "cpu": "arm64" }, "sha512-z9Ib+OzqN3DZEjX7PDQMHEhtF+t6Mi2z/ueChQPLS/qUMKY7Ybn5A2ggFoKRNRh1q1T03YTQfBTQCJZiepESAg=="], + "@rollup/rollup-darwin-arm64": ["@rollup/rollup-darwin-arm64@4.34.8", "", { "os": "darwin", "cpu": "arm64" }, "sha512-02rVdZ5tgdUNRxIUrFdcMBZQoaPMrxtwSb+/hOfBdqkatYHR3lZ2A2EGyHq2sGOd0Owk80oV3snlDASC24He3Q=="], - "@rollup/rollup-darwin-x64": ["@rollup/rollup-darwin-x64@4.34.6", "", { "os": "darwin", "cpu": "x64" }, "sha512-PShKVY4u0FDAR7jskyFIYVyHEPCPnIQY8s5OcXkdU8mz3Y7eXDJPdyM/ZWjkYdR2m0izD9HHWA8sGcXn+Qrsyg=="], + "@rollup/rollup-darwin-x64": ["@rollup/rollup-darwin-x64@4.34.8", "", { "os": "darwin", "cpu": "x64" }, "sha512-qIP/elwR/tq/dYRx3lgwK31jkZvMiD6qUtOycLhTzCvrjbZ3LjQnEM9rNhSGpbLXVJYQ3rq39A6Re0h9tU2ynw=="], - "@rollup/rollup-freebsd-arm64": ["@rollup/rollup-freebsd-arm64@4.34.6", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-YSwyOqlDAdKqs0iKuqvRHLN4SrD2TiswfoLfvYXseKbL47ht1grQpq46MSiQAx6rQEN8o8URtpXARCpqabqxGQ=="], + "@rollup/rollup-freebsd-arm64": ["@rollup/rollup-freebsd-arm64@4.34.8", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-IQNVXL9iY6NniYbTaOKdrlVP3XIqazBgJOVkddzJlqnCpRi/yAeSOa8PLcECFSQochzqApIOE1GHNu3pCz+BDA=="], - "@rollup/rollup-freebsd-x64": ["@rollup/rollup-freebsd-x64@4.34.6", "", { "os": "freebsd", "cpu": "x64" }, "sha512-HEP4CgPAY1RxXwwL5sPFv6BBM3tVeLnshF03HMhJYCNc6kvSqBgTMmsEjb72RkZBAWIqiPUyF1JpEBv5XT9wKQ=="], + "@rollup/rollup-freebsd-x64": ["@rollup/rollup-freebsd-x64@4.34.8", "", { "os": "freebsd", "cpu": "x64" }, "sha512-TYXcHghgnCqYFiE3FT5QwXtOZqDj5GmaFNTNt3jNC+vh22dc/ukG2cG+pi75QO4kACohZzidsq7yKTKwq/Jq7Q=="], - "@rollup/rollup-linux-arm-gnueabihf": ["@rollup/rollup-linux-arm-gnueabihf@4.34.6", "", { "os": "linux", "cpu": "arm" }, "sha512-88fSzjC5xeH9S2Vg3rPgXJULkHcLYMkh8faix8DX4h4TIAL65ekwuQMA/g2CXq8W+NJC43V6fUpYZNjaX3+IIg=="], + "@rollup/rollup-linux-arm-gnueabihf": ["@rollup/rollup-linux-arm-gnueabihf@4.34.8", "", { "os": "linux", "cpu": "arm" }, "sha512-A4iphFGNkWRd+5m3VIGuqHnG3MVnqKe7Al57u9mwgbyZ2/xF9Jio72MaY7xxh+Y87VAHmGQr73qoKL9HPbXj1g=="], - "@rollup/rollup-linux-arm-musleabihf": ["@rollup/rollup-linux-arm-musleabihf@4.34.6", "", { "os": "linux", "cpu": "arm" }, "sha512-wM4ztnutBqYFyvNeR7Av+reWI/enK9tDOTKNF+6Kk2Q96k9bwhDDOlnCUNRPvromlVXo04riSliMBs/Z7RteEg=="], + "@rollup/rollup-linux-arm-musleabihf": ["@rollup/rollup-linux-arm-musleabihf@4.34.8", "", { "os": "linux", "cpu": "arm" }, "sha512-S0lqKLfTm5u+QTxlFiAnb2J/2dgQqRy/XvziPtDd1rKZFXHTyYLoVL58M/XFwDI01AQCDIevGLbQrMAtdyanpA=="], - "@rollup/rollup-linux-arm64-gnu": ["@rollup/rollup-linux-arm64-gnu@4.34.6", "", { "os": "linux", "cpu": "arm64" }, "sha512-9RyprECbRa9zEjXLtvvshhw4CMrRa3K+0wcp3KME0zmBe1ILmvcVHnypZ/aIDXpRyfhSYSuN4EPdCCj5Du8FIA=="], + "@rollup/rollup-linux-arm64-gnu": ["@rollup/rollup-linux-arm64-gnu@4.34.8", "", { "os": "linux", "cpu": "arm64" }, "sha512-jpz9YOuPiSkL4G4pqKrus0pn9aYwpImGkosRKwNi+sJSkz+WU3anZe6hi73StLOQdfXYXC7hUfsQlTnjMd3s1A=="], - "@rollup/rollup-linux-arm64-musl": ["@rollup/rollup-linux-arm64-musl@4.34.6", "", { "os": "linux", "cpu": "arm64" }, "sha512-qTmklhCTyaJSB05S+iSovfo++EwnIEZxHkzv5dep4qoszUMX5Ca4WM4zAVUMbfdviLgCSQOu5oU8YoGk1s6M9Q=="], + "@rollup/rollup-linux-arm64-musl": ["@rollup/rollup-linux-arm64-musl@4.34.8", "", { "os": "linux", "cpu": "arm64" }, "sha512-KdSfaROOUJXgTVxJNAZ3KwkRc5nggDk+06P6lgi1HLv1hskgvxHUKZ4xtwHkVYJ1Rep4GNo+uEfycCRRxht7+Q=="], - "@rollup/rollup-linux-loongarch64-gnu": ["@rollup/rollup-linux-loongarch64-gnu@4.34.6", "", { "os": "linux", "cpu": "none" }, "sha512-4Qmkaps9yqmpjY5pvpkfOerYgKNUGzQpFxV6rnS7c/JfYbDSU0y6WpbbredB5cCpLFGJEqYX40WUmxMkwhWCjw=="], + "@rollup/rollup-linux-loongarch64-gnu": ["@rollup/rollup-linux-loongarch64-gnu@4.34.8", "", { "os": "linux", "cpu": "none" }, "sha512-NyF4gcxwkMFRjgXBM6g2lkT58OWztZvw5KkV2K0qqSnUEqCVcqdh2jN4gQrTn/YUpAcNKyFHfoOZEer9nwo6uQ=="], - "@rollup/rollup-linux-powerpc64le-gnu": ["@rollup/rollup-linux-powerpc64le-gnu@4.34.6", "", { "os": "linux", "cpu": "ppc64" }, "sha512-Zsrtux3PuaxuBTX/zHdLaFmcofWGzaWW1scwLU3ZbW/X+hSsFbz9wDIp6XvnT7pzYRl9MezWqEqKy7ssmDEnuQ=="], + "@rollup/rollup-linux-powerpc64le-gnu": ["@rollup/rollup-linux-powerpc64le-gnu@4.34.8", "", { "os": "linux", "cpu": "ppc64" }, "sha512-LMJc999GkhGvktHU85zNTDImZVUCJ1z/MbAJTnviiWmmjyckP5aQsHtcujMjpNdMZPT2rQEDBlJfubhs3jsMfw=="], - "@rollup/rollup-linux-riscv64-gnu": ["@rollup/rollup-linux-riscv64-gnu@4.34.6", "", { "os": "linux", "cpu": "none" }, "sha512-aK+Zp+CRM55iPrlyKiU3/zyhgzWBxLVrw2mwiQSYJRobCURb781+XstzvA8Gkjg/hbdQFuDw44aUOxVQFycrAg=="], + "@rollup/rollup-linux-riscv64-gnu": ["@rollup/rollup-linux-riscv64-gnu@4.34.8", "", { "os": "linux", "cpu": "none" }, "sha512-xAQCAHPj8nJq1PI3z8CIZzXuXCstquz7cIOL73HHdXiRcKk8Ywwqtx2wrIy23EcTn4aZ2fLJNBB8d0tQENPCmw=="], - "@rollup/rollup-linux-s390x-gnu": ["@rollup/rollup-linux-s390x-gnu@4.34.6", "", { "os": "linux", "cpu": "s390x" }, "sha512-WoKLVrY9ogmaYPXwTH326+ErlCIgMmsoRSx6bO+l68YgJnlOXhygDYSZe/qbUJCSiCiZAQ+tKm88NcWuUXqOzw=="], + "@rollup/rollup-linux-s390x-gnu": ["@rollup/rollup-linux-s390x-gnu@4.34.8", "", { "os": "linux", "cpu": "s390x" }, "sha512-DdePVk1NDEuc3fOe3dPPTb+rjMtuFw89gw6gVWxQFAuEqqSdDKnrwzZHrUYdac7A7dXl9Q2Vflxpme15gUWQFA=="], - "@rollup/rollup-linux-x64-gnu": ["@rollup/rollup-linux-x64-gnu@4.34.6", "", { "os": "linux", "cpu": "x64" }, "sha512-Sht4aFvmA4ToHd2vFzwMFaQCiYm2lDFho5rPcvPBT5pCdC+GwHG6CMch4GQfmWTQ1SwRKS0dhDYb54khSrjDWw=="], + "@rollup/rollup-linux-x64-gnu": ["@rollup/rollup-linux-x64-gnu@4.34.8", "", { "os": "linux", "cpu": "x64" }, "sha512-8y7ED8gjxITUltTUEJLQdgpbPh1sUQ0kMTmufRF/Ns5tI9TNMNlhWtmPKKHCU0SilX+3MJkZ0zERYYGIVBYHIA=="], - "@rollup/rollup-linux-x64-musl": ["@rollup/rollup-linux-x64-musl@4.34.6", "", { "os": "linux", "cpu": "x64" }, "sha512-zmmpOQh8vXc2QITsnCiODCDGXFC8LMi64+/oPpPx5qz3pqv0s6x46ps4xoycfUiVZps5PFn1gksZzo4RGTKT+A=="], + "@rollup/rollup-linux-x64-musl": ["@rollup/rollup-linux-x64-musl@4.34.8", "", { "os": "linux", "cpu": "x64" }, "sha512-SCXcP0ZpGFIe7Ge+McxY5zKxiEI5ra+GT3QRxL0pMMtxPfpyLAKleZODi1zdRHkz5/BhueUrYtYVgubqe9JBNQ=="], - "@rollup/rollup-win32-arm64-msvc": ["@rollup/rollup-win32-arm64-msvc@4.34.6", "", { "os": "win32", "cpu": "arm64" }, "sha512-3/q1qUsO/tLqGBaD4uXsB6coVGB3usxw3qyeVb59aArCgedSF66MPdgRStUd7vbZOsko/CgVaY5fo2vkvPLWiA=="], + "@rollup/rollup-win32-arm64-msvc": ["@rollup/rollup-win32-arm64-msvc@4.34.8", "", { "os": "win32", "cpu": "arm64" }, "sha512-YHYsgzZgFJzTRbth4h7Or0m5O74Yda+hLin0irAIobkLQFRQd1qWmnoVfwmKm9TXIZVAD0nZ+GEb2ICicLyCnQ=="], - "@rollup/rollup-win32-ia32-msvc": ["@rollup/rollup-win32-ia32-msvc@4.34.6", "", { "os": "win32", "cpu": "ia32" }, "sha512-oLHxuyywc6efdKVTxvc0135zPrRdtYVjtVD5GUm55I3ODxhU/PwkQFD97z16Xzxa1Fz0AEe4W/2hzRtd+IfpOA=="], + "@rollup/rollup-win32-ia32-msvc": ["@rollup/rollup-win32-ia32-msvc@4.34.8", "", { "os": "win32", "cpu": "ia32" }, "sha512-r3NRQrXkHr4uWy5TOjTpTYojR9XmF0j/RYgKCef+Ag46FWUTltm5ziticv8LdNsDMehjJ543x/+TJAek/xBA2w=="], - "@rollup/rollup-win32-x64-msvc": ["@rollup/rollup-win32-x64-msvc@4.34.6", "", { "os": "win32", "cpu": "x64" }, "sha512-0PVwmgzZ8+TZ9oGBmdZoQVXflbvuwzN/HRclujpl4N/q3i+y0lqLw8n1bXA8ru3sApDjlmONaNAuYr38y1Kr9w=="], + "@rollup/rollup-win32-x64-msvc": ["@rollup/rollup-win32-x64-msvc@4.34.8", "", { "os": "win32", "cpu": "x64" }, "sha512-U0FaE5O1BCpZSeE6gBl3c5ObhePQSfk9vDRToMmTkbhCOgW4jqvtS5LGyQ76L1fH8sM0keRp4uDTsbjiUyjk0g=="], - "@ronin/cli": ["@ronin/cli@0.2.29", "", { "dependencies": { "@iarna/toml": "2.2.5", "@inquirer/prompts": "7.2.3", "@ronin/engine": "0.0.27", "chalk-template": "1.1.0", "get-port": "7.1.0", "ini": "5.0.0", "json5": "2.2.3", "open": "10.1.0", "ora": "8.1.1", "prettier": "3.4.2", "resolve-from": "5.0.0" } }, "sha512-geSkWFwUhly/a76vAIDXFURb8crbDMudxO5opNy1DomfsQMVWEJTUX+jNOp/ySYifEpA3+LCuYFoXcLoyCwI3A=="], + "@ronin/cli": ["@ronin/cli@0.2.37", "", { "dependencies": { "@dprint/formatter": "0.4.1", "@dprint/typescript": "0.93.3", "@iarna/toml": "2.2.5", "@inquirer/prompts": "7.2.3", "@ronin/engine": "0.0.27", "chalk-template": "1.1.0", "get-port": "7.1.0", "ini": "5.0.0", "json5": "2.2.3", "open": "10.1.0", "ora": "8.1.1", "resolve-from": "5.0.0" } }, "sha512-kBLpbywHT+z32z4xIlEjT8rLrjOBkLUQO/yVeLJkWXPwf/CdKARX156J/qtALascw6eukk/dJAasrnhS2Qcsww=="], - "@ronin/compiler": ["@ronin/compiler@0.14.14", "", {}, "sha512-TqvEdP7p9ub7SeV5wnoO7tT11Z+uKjGTsq648Ye7o3n30t34XhTAqhk6w/VNQ8yMKMa7V19UroSeL2hR0Qclqg=="], + "@ronin/compiler": ["@ronin/compiler@0.17.6", "", {}, "sha512-ob2bPsYkPQK8sN6zYCcbZQ1oBXOJn6suy145yufz3dpjFgdYSvvncsQwJASL15tMVx4AadcXy5WYpNlp/SJerA=="], "@ronin/engine": ["@ronin/engine@0.0.27", "", { "dependencies": { "zod": "3.23.8" } }, "sha512-ArUFnfNH6pVZb3nQStDNZ2p2m4j9QXQTxPM+BrCB6mMYShXrEv9Ke4DiJb+js0IuVhydWWYyA1sql4Nf0IWY5Q=="], - "@ronin/syntax": ["@ronin/syntax@0.2.13", "", {}, "sha512-B12b210E3LDWJkLtHZmmiZjxDPy2vgzC6cUAQHry802O5N2/dEvqapTi8fiowlQvyG43euk/oPtLhlNRiaghvA=="], + "@ronin/syntax": ["@ronin/syntax@0.2.28", "", {}, "sha512-wF7vm+QOdnjJRZIn4uj3Z8n3yc39eTPorzQOqEQVm0RmLeHe5B8SE3bXDZzopmaBf1ff5rQvVbFyLzu3+Qig6A=="], "@types/bun": ["@types/bun@1.2.1", "", { "dependencies": { "bun-types": "1.2.1" } }, "sha512-iiCeMAKMkft8EPQJxSbpVRD0DKqrh91w40zunNajce3nMNNFd/LnAquVisSZC+UpTMjDwtcdyzbWct08IvEqRA=="], @@ -197,7 +197,7 @@ "@types/ini": ["@types/ini@4.1.1", "", {}, "sha512-MIyNUZipBTbyUNnhvuXJTY7B6qNI78meck9Jbv3wk0OgNwRyOOVEKDutAkOs1snB/tx0FafyR6/SN4Ps0hZPeg=="], - "@types/node": ["@types/node@22.13.1", "", { "dependencies": { "undici-types": "~6.20.0" } }, "sha512-jK8uzQlrvXqEU91UxiK5J7pKHyzgnI1Qnl0QDHIgVGuolJhRb9EEl28Cj9b3rGR8B2lhFCtvIm5os8lFnO/1Ew=="], + "@types/node": ["@types/node@22.13.4", "", { "dependencies": { "undici-types": "~6.20.0" } }, "sha512-ywP2X0DYtX3y08eFVx5fNIw7/uIv8hYUKgXoK8oayJlLnKcRfEYCxWMVE1XagUdVtCJlZT1AU4LXEABW+L1Peg=="], "@types/ws": ["@types/ws@8.5.14", "", { "dependencies": { "@types/node": "*" } }, "sha512-bd/YFLW+URhBzMXurx7lWByOu+xzU9+kb3RboOteXYDfW+tr+JZa99OyNmPINEGB/ahzKrEuc8rcv4gnpJmxTw=="], @@ -347,19 +347,17 @@ "postcss-load-config": ["postcss-load-config@6.0.1", "", { "dependencies": { "lilconfig": "^3.1.1" }, "peerDependencies": { "jiti": ">=1.21.0", "postcss": ">=8.0.9", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["jiti", "postcss", "tsx", "yaml"] }, "sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g=="], - "prettier": ["prettier@3.4.2", "", { "bin": { "prettier": "bin/prettier.cjs" } }, "sha512-e9MewbtFo+Fevyuxn/4rrcDAaq0IYxPGLvObpQjiZBMAzB9IGmzlnG9RZy3FFas+eBMu2vA0CszMeduow5dIuQ=="], - "punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="], - "readdirp": ["readdirp@4.1.1", "", {}, "sha512-h80JrZu/MHUZCyHu5ciuoI0+WxsCxzxJTILn6Fs8rxSnFPh+UVHYfeIxK1nVGugMqkfC4vJcBOYbkfkwYK0+gw=="], + "readdirp": ["readdirp@4.1.2", "", {}, "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg=="], "resolve-from": ["resolve-from@5.0.0", "", {}, "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw=="], "restore-cursor": ["restore-cursor@5.1.0", "", { "dependencies": { "onetime": "^7.0.0", "signal-exit": "^4.1.0" } }, "sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA=="], - "rollup": ["rollup@4.34.6", "", { "dependencies": { "@types/estree": "1.0.6" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.34.6", "@rollup/rollup-android-arm64": "4.34.6", "@rollup/rollup-darwin-arm64": "4.34.6", "@rollup/rollup-darwin-x64": "4.34.6", "@rollup/rollup-freebsd-arm64": "4.34.6", "@rollup/rollup-freebsd-x64": "4.34.6", "@rollup/rollup-linux-arm-gnueabihf": "4.34.6", "@rollup/rollup-linux-arm-musleabihf": "4.34.6", "@rollup/rollup-linux-arm64-gnu": "4.34.6", "@rollup/rollup-linux-arm64-musl": "4.34.6", "@rollup/rollup-linux-loongarch64-gnu": "4.34.6", "@rollup/rollup-linux-powerpc64le-gnu": "4.34.6", "@rollup/rollup-linux-riscv64-gnu": "4.34.6", "@rollup/rollup-linux-s390x-gnu": "4.34.6", "@rollup/rollup-linux-x64-gnu": "4.34.6", "@rollup/rollup-linux-x64-musl": "4.34.6", "@rollup/rollup-win32-arm64-msvc": "4.34.6", "@rollup/rollup-win32-ia32-msvc": "4.34.6", "@rollup/rollup-win32-x64-msvc": "4.34.6", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-wc2cBWqJgkU3Iz5oztRkQbfVkbxoz5EhnCGOrnJvnLnQ7O0WhQUYyv18qQI79O8L7DdHrrlJNeCHd4VGpnaXKQ=="], + "rollup": ["rollup@4.34.8", "", { "dependencies": { "@types/estree": "1.0.6" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.34.8", "@rollup/rollup-android-arm64": "4.34.8", "@rollup/rollup-darwin-arm64": "4.34.8", "@rollup/rollup-darwin-x64": "4.34.8", "@rollup/rollup-freebsd-arm64": "4.34.8", "@rollup/rollup-freebsd-x64": "4.34.8", "@rollup/rollup-linux-arm-gnueabihf": "4.34.8", "@rollup/rollup-linux-arm-musleabihf": "4.34.8", "@rollup/rollup-linux-arm64-gnu": "4.34.8", "@rollup/rollup-linux-arm64-musl": "4.34.8", "@rollup/rollup-linux-loongarch64-gnu": "4.34.8", "@rollup/rollup-linux-powerpc64le-gnu": "4.34.8", "@rollup/rollup-linux-riscv64-gnu": "4.34.8", "@rollup/rollup-linux-s390x-gnu": "4.34.8", "@rollup/rollup-linux-x64-gnu": "4.34.8", "@rollup/rollup-linux-x64-musl": "4.34.8", "@rollup/rollup-win32-arm64-msvc": "4.34.8", "@rollup/rollup-win32-ia32-msvc": "4.34.8", "@rollup/rollup-win32-x64-msvc": "4.34.8", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-489gTVMzAYdiZHFVA/ig/iYFllCcWFHMvUHI1rpFmkoUtRlQxqh6/yiNqnYibjMZ2b/+FUQwldG+aLsEt6bglQ=="], - "ronin": ["ronin@6.0.27", "", { "dependencies": { "@ronin/cli": "0.2.29", "@ronin/compiler": "0.14.14", "@ronin/syntax": "0.2.13" }, "bin": { "ronin": "dist/bin/index.js" } }, "sha512-RF/UG474MKxsa2u3GxBOiLam+tW3bpIRoktMrzwlT2xM6PrkyFm+/tWxxR4QU/VBTpsZp+vBBaC0+S+OLSpXhQ=="], + "ronin": ["ronin@6.2.16", "", { "dependencies": { "@ronin/cli": "0.2.37", "@ronin/compiler": "0.17.6", "@ronin/syntax": "0.2.28" }, "bin": { "ronin": "dist/bin/index.js" } }, "sha512-17Br3LgMchxopfa9/ndLegwM7LpMBOkJxvwYoEY8yZaUy84UlKvmrYAVnnHpvoDpXCf4IwCsRsiCUs1cJ0cgHg=="], "run-applescript": ["run-applescript@7.0.0", "", {}, "sha512-9by4Ij99JUr/MCFBUkDKLWK3G9HVXmabKz9U5MlIAIuvuzkiOicRYs8XJLxX+xahD+mLiiCYDqF9dKAgtzKP1A=="], @@ -391,7 +389,7 @@ "tinyexec": ["tinyexec@0.3.2", "", {}, "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA=="], - "tinyglobby": ["tinyglobby@0.2.10", "", { "dependencies": { "fdir": "^6.4.2", "picomatch": "^4.0.2" } }, "sha512-Zc+8eJlFMvgatPZTl6A9L/yht8QqdmUNtURHaKZLmKBE12hNPSrqNkUp2cs3M/UKmNVVAMFQYSjYIVHDjW5zew=="], + "tinyglobby": ["tinyglobby@0.2.11", "", { "dependencies": { "fdir": "^6.4.3", "picomatch": "^4.0.2" } }, "sha512-32TmKeeKUahv0Go8WmQgiEp9Y21NuxjwjqiRC1nrUB51YacfSwuB44xgXD+HdIppmMRgjQNPdrHyA6vIybYZ+g=="], "tmp": ["tmp@0.0.33", "", { "dependencies": { "os-tmpdir": "~1.0.2" } }, "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw=="], diff --git a/package.json b/package.json index 43f615b..8d5edea 100644 --- a/package.json +++ b/package.json @@ -42,7 +42,7 @@ "resolve-from": "5.0.0" }, "devDependencies": { - "ronin": "6.0.27", + "ronin": "6.2.16", "@biomejs/biome": "1.9.4", "@types/bun": "1.2.1", "@types/ini": "4.1.1", diff --git a/src/utils/field.ts b/src/utils/field.ts index ebe4a2c..921eaa0 100644 --- a/src/utils/field.ts +++ b/src/utils/field.ts @@ -1,5 +1,6 @@ import type { MigrationOptions } from '@/src/utils/migration'; import { RONIN_SCHEMA_TEMP_SUFFIX } from '@/src/utils/misc'; +import { convertArrayToObject } from '@/src/utils/model'; import { createFieldQuery, createTempColumnQuery, @@ -118,13 +119,16 @@ export const diffFields = async ( if (field.from.type === 'link') { diff.push( ...createTempModelQuery( - modelSlug, - [ - { ...field.to, slug: field.from.slug }, - ...definedFields.filter((local) => local.slug !== field.to.slug), - ], - indexes, - triggers, + { + slug: modelSlug, + // @ts-expect-error This will work once the types are fixed. + fields: convertArrayToObject([ + { ...field.to, slug: field.from.slug }, + ...definedFields.filter((local) => local.slug !== field.to.slug), + ]), + indexes: convertArrayToObject(indexes), + triggers: convertArrayToObject(triggers), + }, [ renameFieldQuery( `${RONIN_SCHEMA_TEMP_SUFFIX}${modelSlug}`, @@ -176,10 +180,13 @@ export const diffFields = async ( diff.push( ...createTempModelQuery( - modelSlug, - updatedFields || [], - [], - [], + { + slug: modelSlug, + // @ts-expect-error This will work once the types are fixed. + fields: convertArrayToObject(updatedFields || []), + indexes: convertArrayToObject(indexes), + triggers: convertArrayToObject(triggers), + }, queries, existingFields, ), @@ -284,7 +291,14 @@ const adjustFields = ( indexes: Array, triggers: Array, ): Array => { - return createTempModelQuery(modelSlug, fields, indexes, triggers); + return createTempModelQuery({ + slug: modelSlug, + // @ts-expect-error This will work once the types are fixed. + fields: convertArrayToObject(fields), + // @ts-expect-error This will work once the types are fixed. + indexes, + triggers: convertArrayToObject(triggers), + }); }; /** @@ -338,20 +352,22 @@ export const createFields = async ( ); return createTempModelQuery( - modelSlug, - updatedFields || [], - [], - [], + { + slug: modelSlug, + // @ts-expect-error This will work once the types are fixed. + fields: convertArrayToObject(updatedFields || []), + }, queries, existingFields, ); } return createTempModelQuery( - modelSlug, - definedFields || [], - [], - [], + { + slug: modelSlug, + // @ts-expect-error This will work once the types are fixed. + fields: convertArrayToObject(definedFields || []), + }, [], existingFields, ); @@ -419,7 +435,15 @@ const deleteFields = ( const diff: Array = []; for (const fieldToDrop of fieldsToDrop) { if (fieldToDrop.unique) { - return createTempModelQuery(modelSlug, fields, [], [], [], fields); + return createTempModelQuery( + { + slug: modelSlug, + // @ts-expect-error This will work once the types are fixed. + fields: convertArrayToObject(fields), + }, + [], + fields, + ); } diff.push(dropFieldQuery(modelSlug, fieldToDrop.slug)); } diff --git a/src/utils/migration.ts b/src/utils/migration.ts index 704fa32..22e822b 100644 --- a/src/utils/migration.ts +++ b/src/utils/migration.ts @@ -1,6 +1,7 @@ import type { parseArgs } from 'node:util'; import { diffFields, fieldsToAdjust } from '@/src/utils/field'; import { type BaseFlags, areArraysEqual } from '@/src/utils/misc'; +import { convertArrayToObject, convertModelToArrayFields } from '@/src/utils/model'; import { createIndexQuery, createModelQuery, @@ -46,10 +47,17 @@ export const IGNORED_FIELDS = [ * @returns An array of migration steps (as code strings). */ export const diffModels = async ( - definedModels: Array, - existingModels: Array, + definedModelsWithFieldObject: Array, + existingModelsWithFieldObject: Array, options?: MigrationOptions, ): Promise> => { + const definedModels = definedModelsWithFieldObject.map((model) => + convertModelToArrayFields(model), + ); + const existingModels = existingModelsWithFieldObject.map((model) => + convertModelToArrayFields(model), + ); + const diff: Array = []; const adjustModelMetaQueries = adjustModelMeta(definedModels, existingModels); @@ -81,7 +89,16 @@ export const diffModels = async ( diff.push(...adjustModelMetaQueries); diff.push(...dropModels(modelsToBeDropped)); - diff.push(...createModels(modelsToBeAdded)); + + diff.push( + ...createModels( + modelsToBeAdded.map((m) => ({ + ...m, + // @ts-expect-error This will work once the types are fixed. + fields: convertArrayToObject(m.fields), + })), + ), + ); diff.push(...(await adjustModels(definedModels, existingModels, options))); diff.push(...recreateIndexes); diff.push(...recreateTriggers); @@ -112,6 +129,7 @@ const adjustModels = async ( if (remoteModel) { diff.push( ...(await diffFields( + // @ts-expect-error This will work once the types are fixed. localModel.fields || [], remoteModel.fields || [], localModel.slug, @@ -152,11 +170,8 @@ export const dropModels = (models: Array): Array => { */ export const createModels = (models: Array): Array => { const diff: Array = []; - for (const model of models) { - diff.push( - createModelQuery(model.slug, model.fields ? { fields: model.fields } : undefined), - ); + diff.push(createModelQuery(model)); } return diff; @@ -223,7 +238,9 @@ export const modelsToRename = ( // Check if `model.fields` has the same fields as the current model const currentModel = modelsToBeDropped.find((s) => { return areArraysEqual( + // @ts-expect-error This will work once the types are fixed. model.fields?.map((f) => f.slug) || [], + // @ts-expect-error This will work once the types are fixed. s.fields?.map((f) => f.slug) || [], ); }); @@ -287,87 +304,38 @@ export const triggersToRecreate = ( existingModel || ({} as Model), ); - diff.push( - ...(modelRecreated - ? [] - : dropTriggers(definedModel, existingModel || ({} as Model))), - ...createTriggers(definedModel, existingModel || ({} as Model)), - ); - } - - return diff; -}; - -/** - * Generates queries to drop triggers from a model. - * - * @param definedModel - The model defined locally. - * @param existingModel - The model currently defined in the database. - * - * @returns An array of trigger deletion queries as code strings. - */ -export const dropTriggers = ( - definedModel: Model, - existingModel: Model, -): Array => { - const diff: Array = []; - const definedTriggers = definedModel.triggers || []; - const existingTriggers = existingModel.triggers || []; - - // Find every trigger that exists but not in defined - const triggersToDrop = - existingTriggers.filter( - (i) => - !definedTriggers.some( - (d) => - d.fields && - i.fields && - d.fields.length === i.fields.length && - d.fields.every( - (f, idx) => JSON.stringify(f) === JSON.stringify(i.fields?.[idx]), - ), - ), - ) || []; - - for (const trigger of triggersToDrop) { - diff.push(dropTriggerQuery(definedModel.slug, trigger.slug || 'no slug')); - } - - return diff; -}; - -/** - * Generates queries to create triggers for a model. - * - * @param definedModel - The model defined locally. - * @param existingModel - The model currently defined in the database. - * - * @returns An array of trigger creation queries as code strings. - */ -export const createTriggers = ( - definedModel: Model, - existingModel: Model, -): Array => { - const diff: Array = []; - const definedTriggers = definedModel.triggers || []; - const existingTriggers = existingModel.triggers || []; - - // Find every trigger that is defined but not in `existingModel` - const triggersToAdd = definedTriggers.filter( - (i) => - !existingTriggers.some( - (e) => - e?.fields && - i.fields && - e.fields.length === i.fields.length && - e.fields.every( - (f, idx) => JSON.stringify(f) === JSON.stringify(i.fields?.[idx]), - ), - ), - ); + // For each trigger in the defined model, check if a trigger with the same slug exists + // in the database. If it does and its properties differ, drop the existing trigger and + // create a new one with the updated properties. + const needRecreation = Object.entries(definedModel.triggers || {}).reduce< + Array + >((acc, [slug, trigger]) => { + const existingTrigger = existingModel?.triggers?.[slug]; + if ( + existingTrigger && + !(JSON.stringify(trigger) === JSON.stringify(existingTrigger)) + ) { + const createTrigger = createTriggerQuery(definedModel.slug, { + slug, + ...trigger, + }); + const dropTrigger = dropTriggerQuery(definedModel.slug, slug); + acc.push(dropTrigger); + acc.push(createTrigger); + return acc; + } + if (definedModel.triggers?.[slug] && !existingModel?.triggers?.[slug]) { + acc.push( + createTriggerQuery(definedModel.slug, { + slug, + ...trigger, + }), + ); + } + return acc; + }, []); - for (const trigger of triggersToAdd) { - diff.push(createTriggerQuery(definedModel.slug, trigger)); + diff.push(...(modelRecreated ? [] : needRecreation)); } return diff; @@ -387,6 +355,7 @@ export const modelWillBeRecreated = ( ): boolean => { if (!existingModel) return false; return ( + // @ts-expect-error This will work once the types are fixed. (fieldsToAdjust(definedModel.fields || [], existingModel.fields || []) ?? []).length > 0 ); @@ -413,82 +382,35 @@ export const indexesToRecreate = ( existingModel || ({} as Model), ); - diff.push( - ...(modelRecreated - ? [] - : dropIndexes(definedModel, existingModel || ({} as Model))), - ...createIndexes(definedModel, existingModel || ({} as Model)), - ); - } - return diff; -}; - -/** - * Generates queries to drop indexes from a model. - * - * @param definedModel - The model defined locally. - * @param existingModel - The model currently defined in the database. - * - * @returns An array of index deletion queries as code strings. - */ -export const dropIndexes = (definedModel: Model, existingModel: Model): Array => { - const diff: Array = []; - const definedIndexes = definedModel.indexes || []; - - // Find every index that exists but not in defined - const indexesToDrop = - existingModel?.indexes?.filter( - (i) => - !definedIndexes.some( - (d) => - d.fields && - i.fields && - d.fields.length === i.fields.length && - d.unique === i.unique && - d.fields.every( - (f, idx) => JSON.stringify(f) === JSON.stringify(i.fields[idx]), - ), - ), - ) || []; - - for (const index of indexesToDrop) { - diff.push(dropIndexQuery(definedModel.slug, index.slug || 'no slug')); - } - - return diff; -}; - -/** - * Generates queries to create indexes for a model. - * - * @param definedModel - The model defined locally. - * @param existingModel - The model currently defined in the database. - * - * @returns An array of index creation queries as code strings. - */ -export const createIndexes = ( - definedModel: Model, - existingModel: Model, -): Array => { - const diff: Array = []; - const definedIndexes = definedModel.indexes || []; - const existingIndexes = existingModel.indexes || []; - - // Find every index that is defined but not in `existingIndexes` - const indexesToAdd = definedIndexes.filter( - (i) => - !existingIndexes.some( - (e) => - e.fields && - i.fields && - e.fields.length === i.fields.length && - e.unique === i.unique && - e.fields.every((f, idx) => JSON.stringify(f) === JSON.stringify(i.fields[idx])), - ), - ); + // For each index in the defined model, check if an index with the same slug exists + // in the database. If it does and its properties differ, drop the existing index and + // create a new one with the updated properties. + const needRecreation = Object.entries(definedModel.indexes || {}).reduce< + Array + >((acc, [slug, index]) => { + const existingIndex = existingModel?.indexes?.[slug]; + if (existingIndex && !(JSON.stringify(index) === JSON.stringify(existingIndex))) { + const createIndex = createIndexQuery(definedModel.slug, { + slug, + ...index, + }); + const dropIndex = dropIndexQuery(definedModel.slug, slug); + acc.push(dropIndex); + acc.push(createIndex); + return acc; + } + if (definedModel.indexes?.[slug] && !existingModel?.indexes?.[slug]) { + acc.push( + createIndexQuery(definedModel.slug, { + slug, + ...index, + }), + ); + } + return acc; + }, []); - for (const index of indexesToAdd) { - diff.push(createIndexQuery(definedModel.slug, index)); + diff.push(...(modelRecreated ? [] : needRecreation)); } return diff; diff --git a/src/utils/misc.ts b/src/utils/misc.ts index 3c7461f..709637f 100644 --- a/src/utils/misc.ts +++ b/src/utils/misc.ts @@ -3,6 +3,7 @@ import path from 'node:path'; import type { parseArgs } from 'node:util'; import { readConfig, saveConfig } from '@/src/utils/config'; import { fieldsToCreate, fieldsToDrop } from '@/src/utils/field'; +import { convertModelToArrayFields } from '@/src/utils/model'; import { spinner } from '@/src/utils/spinner'; import { input } from '@inquirer/prompts'; import type { Model, Result } from '@ronin/compiler'; @@ -110,18 +111,24 @@ export const logDataTable = (data: Array, tableName: string): void => * @param tableName - Name of the table for logging. */ export const logTableDiff = (tableB: Model, tableA: Model, tableName: string): void => { + const a = convertModelToArrayFields(tableA); + const b = convertModelToArrayFields(tableB); // Get fields that were added and deleted between tables - const fieldsToAdd = fieldsToCreate(tableB.fields ?? [], tableA.fields ?? []); - const fieldsToDelete = fieldsToDrop(tableB.fields ?? [], tableA.fields ?? []); + // @ts-expect-error This will work once the types are fixed. + const fieldsToAdd = fieldsToCreate(b.fields ?? [], a.fields ?? []); + // @ts-expect-error This will work once the types are fixed. + const fieldsToDelete = fieldsToDrop(b.fields ?? [], a.fields ?? []); // Convert fields arrays to maps for easier lookup const fieldsA = Object.fromEntries( - (tableA.fields ?? []).map((field) => [field.slug, field]), + // @ts-expect-error This will work once the types are fixed. + (a.fields ?? []).map((field) => [field.slug, field]), ); // Get all unique property keys from both tables const allKeys = new Set(); - for (const item of [...(tableB.fields ?? []), ...(tableA.fields ?? [])]) { + // @ts-expect-error This will work once the types are fixed. + for (const item of [...(b.fields ?? []), ...(a.fields ?? [])]) { for (const key of Object.keys(item)) { allKeys.add(key); } @@ -130,7 +137,8 @@ export const logTableDiff = (tableB: Model, tableA: Model, tableName: string): v // Create column headers with color formatting const columnHeaders = [ // Headers for current fields (green for new fields) - ...(tableB.fields ?? []).map((field) => { + // @ts-expect-error This will work once the types are fixed. + ...(b.fields ?? []).map((field) => { const isNew = fieldsToAdd.some((f) => f.slug === field.slug); return isNew ? `\x1b[32m${field.slug}\x1b[0m` : field.slug; }), @@ -145,7 +153,8 @@ export const logTableDiff = (tableB: Model, tableA: Model, tableName: string): v let hasValue = false; // Add values for current fields - (tableB.fields ?? []).forEach((field, index) => { + // @ts-expect-error This will work once the types are fixed. + (b.fields ?? []).forEach((field, index) => { const oldValue = fieldsA[field.slug]?.[key as keyof typeof field]; const newValue = field[key as keyof typeof field]; @@ -162,8 +171,8 @@ export const logTableDiff = (tableB: Model, tableA: Model, tableName: string): v fieldsToDelete.forEach((field, i) => { const value = field[key as keyof typeof field]; if (value !== undefined) hasValue = true; - row[columnHeaders[tableB.fields?.length ?? 0 + i]] = - `\x1b[31m\x1b[9m${value}\x1b[0m`; + // @ts-expect-error This will work once the types are fixed. + row[columnHeaders[b.fields?.length ?? 0 + i]] = `\x1b[31m\x1b[9m${value}\x1b[0m`; }); return hasValue ? row : null; @@ -252,7 +261,7 @@ export const sortModels = (models: Array): Array => { // Populate dependencies based on 'target' in fields, // but skip self-links for (const model of models) { - for (const field of model.fields ?? []) { + for (const field of Object.values(model.fields ?? [])) { if (field.type === 'link' && field.target && field.target !== model.slug) { dependencyMap.get(model.slug)?.add(field.target); } diff --git a/src/utils/model.ts b/src/utils/model.ts index 7063c40..5fcac0c 100644 --- a/src/utils/model.ts +++ b/src/utils/model.ts @@ -64,8 +64,74 @@ export const getModels = async ( const results = transaction.formatResults(rawResults, false); const models = 'records' in results[0] ? results[0].records : []; + // @ts-expect-error This will work once the types are fixed. return models.map((model) => ({ ...model, - fields: model.fields?.filter((field) => !IGNORED_FIELDS.includes(field.slug)), + // @ts-expect-error This will work once the types are fixed. + fields: convertObjectToArray(model.fields)?.filter( + (field) => !IGNORED_FIELDS.includes(field.slug), + ), })); }; + +/** + * Converts an object of fields into an array of field objects with slugs. + * + * @param input - Object containing field definitions. + * + * @returns Array of field objects with slugs. + */ +export const convertObjectToArray = >( + input: T, +): Array<{ slug: string } & T[keyof T]> => { + return Object.entries(input).map(([key, value]) => ({ + slug: key, + // @ts-expect-error This will work once the types are fixed. + ...(value as T[keyof T]), + })); +}; + +/** + * Converts an array of field objects with slugs into an object keyed by slug. + * + * @param fields - Array of field objects with slugs. + * + * @returns Object with fields keyed by slug. + */ +export const convertArrayToObject = ( + fields: Array | undefined, +): Record> => { + if (!fields) return {}; + + return fields.reduce>>((obj, field) => { + const { slug, ...rest } = field; + obj[slug] = rest; + return obj; + }, {}); +}; + +/** + * Converts a model's fields from object format to array format. + * + * @param model - Model with fields in object format. + * + * @returns Model with fields converted to array format. + */ +export const convertModelToArrayFields = (model: Model): Model => { + // @ts-expect-error This will work once the types are fixed. + if (JSON.stringify(model) === '{}') return {}; + // @ts-expect-error This will work once the types are fixed. + return { ...model, fields: convertObjectToArray(model.fields) }; +}; + +/** + * Converts a model's fields from array format to object format. + * + * @param model - Model with fields in array format. + * + * @returns Model with fields converted to object format. + */ +export const convertModelToObjectFields = (model: Model): Model => { + // @ts-expect-error This will work once the types are fixed. + return { ...model, fields: convertArrayToObject(model.fields) }; +}; diff --git a/src/utils/protocol.ts b/src/utils/protocol.ts index 1a0f7e5..f1f0a3b 100644 --- a/src/utils/protocol.ts +++ b/src/utils/protocol.ts @@ -3,7 +3,7 @@ import path from 'node:path'; import { formatCode } from '@/src/utils/format'; import { MIGRATIONS_PATH } from '@/src/utils/misc'; import type { LocalPackages } from '@/src/utils/misc'; -import type { Model, Query, Statement } from '@ronin/compiler'; +import { type Model, QUERY_SYMBOLS, type Query, type Statement } from '@ronin/compiler'; /** * Protocol represents a set of database migration queries that can be executed in sequence. @@ -45,10 +45,12 @@ export class Protocol { * @param query - RONIN query string. * * @returns Object containing the Query and options. + * * @private */ private queryToObject = (query: string): Query => { const { getSyntaxProxy } = this._packages.syntax; + const queryTypes = [ 'get', 'set', @@ -59,13 +61,13 @@ export class Protocol { 'alter', 'drop', ]; - const queryProxies = queryTypes.map((type) => - getSyntaxProxy({ rootProperty: type as 'drop' }), - ); + const queryProxies = queryTypes.map((type) => { + return getSyntaxProxy({ root: `${QUERY_SYMBOLS.QUERY}.${type}` }); + }); const func = new Function(...queryTypes, `"use strict"; return ${query}`); - return func(...queryProxies).structure; + return func(...queryProxies)[QUERY_SYMBOLS.QUERY]; }; /** diff --git a/src/utils/queries.ts b/src/utils/queries.ts index fdad6e4..309ffa5 100644 --- a/src/utils/queries.ts +++ b/src/utils/queries.ts @@ -31,20 +31,9 @@ export const dropModelQuery = (modelSlug: string): string => { * createModelQuery('user', { pluralSlug: 'users' }) // Output: create.model({slug:'user',pluralSlug:'users'}) * ``` */ -export const createModelQuery = ( - modelSlug: string, - properties?: Partial, -): string => { - if (properties) { - const propertiesString = Object.entries(properties) - .filter(([_, value]) => value !== undefined) - .map(([key, value]) => { - return `${key}:${serialize(value)}`; - }) - .join(', '); - return `create.model({slug:'${modelSlug}',${propertiesString}})`; - } - return `create.model({slug:'${modelSlug}'})`; +export const createModelQuery = (model: Model): string => { + const { indexes, triggers, ...rest } = model; + return `create.model(${JSON.stringify(rest)})`; }; /** @@ -126,23 +115,21 @@ export const dropFieldQuery = (modelSlug: string, fieldSlug: string): string => * ``` */ export const createTempModelQuery = ( - modelSlug: string, - fields: Array, - _indexes: Array, - triggers: Array, + model: Model, customQueries?: Array, includeFields?: Array, ): Array => { + const { slug, fields, indexes: _indexes, triggers, ...rest } = model; const queries: Array = []; - const tempModelSlug = `${RONIN_SCHEMA_TEMP_SUFFIX}${modelSlug}`; + const tempModelSlug = `${RONIN_SCHEMA_TEMP_SUFFIX}${slug}`; // Create a copy of the model - queries.push(createModelQuery(tempModelSlug, { fields })); + queries.push(createModelQuery({ slug: tempModelSlug, fields, ...rest })); // Move all the data to the copied model queries.push( - `add.${tempModelSlug}.with(() => get.${modelSlug}(${ + `add.${tempModelSlug}.with(() => get.${slug}(${ includeFields ? JSON.stringify({ selecting: includeFields.map((field) => field.slug) }) : '' @@ -154,13 +141,13 @@ export const createTempModelQuery = ( } // Delete the original model - queries.push(dropModelQuery(modelSlug)); + queries.push(dropModelQuery(slug)); // Rename the copied model to the original model - queries.push(`alter.model("${tempModelSlug}").to({slug: "${modelSlug}"})`); + queries.push(`alter.model("${tempModelSlug}").to({slug: "${slug}"})`); - for (const trigger of triggers) { - queries.push(createTriggerQuery(modelSlug, trigger)); + for (const [key, value] of Object.entries(triggers || {})) { + queries.push(createTriggerQuery(slug, { ...value, slug: key })); } return queries; @@ -204,34 +191,6 @@ export const createTempColumnQuery = ( return queries; }; -/** - * Serializes values for use in RONIN queries. - * - * @param value - The value to serialize. - * - * @returns A string representation of the value. - * @internal - */ -const serialize = (value: unknown): string => { - if (typeof value === 'string') { - // Wrap string values in single quotes - return `'${value}'`; - } - if (Array.isArray(value)) { - // Serialize each element in the array - return `[${value.map(serialize).join(', ')}]`; - } - if (typeof value === 'object' && value !== null) { - // Serialize each key-value pair in the object - return `{${Object.entries(value) - .filter(([_, v]) => v !== undefined) - .map(([k, v]) => `${k}:${serialize(v)}`) - .join(', ')}}`; - } - // For numbers, booleans, null, undefined - return String(value); -}; - /** * Generates a RONIN query to rename a model. * diff --git a/tests/fixtures/index.ts b/tests/fixtures/index.ts index 5c9636b..8c56712 100644 --- a/tests/fixtures/index.ts +++ b/tests/fixtures/index.ts @@ -17,6 +17,7 @@ const TestAccount = model({ presets: { over18: { + // @ts-expect-error This will work once the types are fixed. with: { age: { being: { @@ -38,6 +39,7 @@ const TestAccount2 = model({ presets: { over18: { + // @ts-expect-error This will work once the types are fixed. with: { age: { being: { @@ -84,12 +86,12 @@ const TestBlog = model({ hero: blob(), }, - indexes: [ - { + indexes: { + uniqueAuthorNameIndex: { fields: [{ slug: 'author' }, { slug: 'name' }], unique: true, }, - ], + }, }) as unknown as Model; const TestBlog2 = model({ @@ -101,12 +103,12 @@ const TestBlog2 = model({ hero: blob(), }, - indexes: [ - { + indexes: { + uniqueAuthorNewNameIndex: { fields: [{ slug: 'authorNew' }, { slug: 'name' }], unique: true, }, - ], + }, }) as unknown as Model; const TestComments = model({ @@ -204,7 +206,12 @@ export const TestA = model({ age: string({ required: true, unique: true }), active: boolean(), }, - indexes: [{ fields: [{ slug: 'age' }], unique: true }], + indexes: { + uniqueAgeIndex: { + fields: [{ slug: 'age' }], + unique: true, + }, + }, }) as unknown as Model; export const TestB = model({ @@ -212,9 +219,14 @@ export const TestB = model({ fields: { name: string(), age: number({ defaultValue: 18 }), - createdAt: date({ defaultValue: '02-02-2024' }), + createdAt: date({ defaultValue: new Date('02-02-2024') }), + }, + indexes: { + uniqueAgeNameIndex: { + fields: [{ slug: 'age' }, { slug: 'name' }], + unique: true, + }, }, - indexes: [{ fields: [{ slug: 'age' }, { slug: 'name' }], unique: true }], }) as unknown as Model; export const TestC = model({ @@ -225,7 +237,12 @@ export const TestC = model({ age: string({ required: true, unique: true }), active: boolean(), }, - indexes: [{ fields: [{ slug: 'age' }], unique: true }], + indexes: { + uniqueAgeIndex: { + fields: [{ slug: 'age' }], + unique: true, + }, + }, }) as unknown as Model; export const TestD = model({ @@ -233,14 +250,14 @@ export const TestD = model({ fields: { name: string(), }, - triggers: [ - { + triggers: { + filedTrigger: { action: 'INSERT', when: 'BEFORE', // @ts-expect-error Fix in models effects: (): Array => [add.comment.with({ name: 'Test' })], }, - ], + }, }) as unknown as Model; export const TestE = model({ @@ -248,14 +265,14 @@ export const TestE = model({ fields: { name: string(), }, - triggers: [ - { + triggers: { + filedTrigger: { action: 'DELETE', when: 'AFTER', // @ts-expect-error Fix in models - effects: (): Array => [add.comment.with({ name: 'Test' })], + effects: () => [add.comment.with({ name: 'Test' })], }, - ], + }, }) as unknown as Model; export const TestF = model({ @@ -264,7 +281,12 @@ export const TestF = model({ age: string({ required: false, unique: false }), active: boolean(), }, - indexes: [{ fields: [{ slug: 'age' }], unique: true }], + indexes: { + uniqueAgeIndex: { + fields: [{ slug: 'age' }], + unique: true, + }, + }, }) as unknown as Model; export const TestG = model({ @@ -355,6 +377,7 @@ export const TestQ = model({ slug: 'many', fields: { name: string(), + // @ts-expect-error This will work once the types are fixed. test: link({ target: 'test', kind: 'many', actions: { onDelete: 'CASCADE' } }), }, }) as unknown as Model; @@ -366,6 +389,7 @@ export const TestR = model({ test: link({ target: 'test', kind: 'many', + // @ts-expect-error This will work once the types are fixed. actions: { onDelete: 'CASCADE', onUpdate: 'CASCADE' }, }), }, @@ -378,6 +402,7 @@ export const TestS = model({ test: link({ target: 'test', kind: 'many', + // @ts-expect-error This will work once the types are fixed. actions: { onDelete: 'CASCADE', onUpdate: 'CASCADE' }, }), email: string(), diff --git a/tests/fixtures/utils.ts b/tests/fixtures/utils.ts index 82a22c5..e96706f 100644 --- a/tests/fixtures/utils.ts +++ b/tests/fixtures/utils.ts @@ -1,6 +1,6 @@ import { type MigrationOptions, diffModels } from '@/src/utils/migration'; import { type LocalPackages, getLocalPackages } from '@/src/utils/misc'; -import { getModels } from '@/src/utils/model'; +import { convertModelToObjectFields, getModels } from '@/src/utils/model'; import { Protocol } from '@/src/utils/protocol'; import { type Model, type Statement, Transaction } from '@ronin/compiler'; import { type Database, Engine } from '@ronin/engine'; @@ -50,7 +50,7 @@ export const prefillDatabase = async ( const rootModelTransaction = new Transaction([{ create: { model: ROOT_MODEL } }]); const modelTransaction = new Transaction( - models.map((model) => ({ create: { model } })), + models.map((model) => JSON.parse(JSON.stringify({ create: { model } }))), ); // Create the root model and all other models. @@ -93,12 +93,14 @@ export const runMigration = async ( const packages = await getLocalPackages(); const models = await getModels(packages, db); - const modelDiff = await diffModels(definedModels, models, options); + const protocol = new Protocol(packages, modelDiff); await protocol.convertToQueryObjects(); - const statements = protocol.getSQLStatements(models); + const statements = protocol.getSQLStatements( + models.map((model) => convertModelToObjectFields(model)), + ); await db.query(statements); diff --git a/tests/utils/apply.test.ts b/tests/utils/apply.test.ts index 1d5ceaa..eb14a24 100644 --- a/tests/utils/apply.test.ts +++ b/tests/utils/apply.test.ts @@ -47,6 +47,7 @@ describe('apply', () => { rowCounts[model.pluralSlug] = await getRowCount(db, model.pluralSlug); } } + expect(statements).toHaveLength(4); expect(models).toHaveLength(1); expect(models[0].slug).toBe('test'); @@ -273,6 +274,7 @@ describe('apply', () => { rowCounts[model.pluralSlug] = await getRowCount(db, model.pluralSlug); } } + expect(models).toHaveLength(1); // @ts-expect-error This is defined! expect(models[0]?.fields[0]?.unique).toBe(true); @@ -509,9 +511,9 @@ describe('apply', () => { expect(rowCounts).toEqual({ accounts: 1, }); - // TODO: This is not correct. This will be changed in another PR when we bump - // to the latest ronin version. - expect(rows[0].email).toBe('true'); + + // @ts-expect-error This is defined! + expect(rows[0].email).toBe(1); }); }); @@ -570,7 +572,7 @@ describe('apply', () => { } } expect(models).toHaveLength(2); - // @ts-expect-error This is defined! + // @ts-expect-error This is fixed when we stop converting between arrays and objects. expect(models[1]?.fields[0]?.actions?.onDelete).toBe('CASCADE'); expect(rowCounts).toEqual({ comments: 0, @@ -695,8 +697,8 @@ describe('apply', () => { } } expect(models).toHaveLength(1); - expect(models[0]?.triggers?.[0]?.action).toBe('DELETE'); - expect(models[0]?.triggers?.[0]?.when).toBe('AFTER'); + expect(models[0]?.triggers?.filedTrigger?.action).toBe('DELETE'); + expect(models[0]?.triggers?.filedTrigger?.when).toBe('AFTER'); expect(rowCounts).toEqual({ comments: 0, }); @@ -726,7 +728,7 @@ describe('apply', () => { describe('drop', () => { test('simple', async () => { - const { models, db } = await runMigration([TestA], []); + const { models, db } = await runMigration([], [TestA]); const rowCounts: Record = {}; for (const model of models) { @@ -734,10 +736,7 @@ describe('apply', () => { rowCounts[model.pluralSlug] = await getRowCount(db, model.pluralSlug); } } - expect(models).toHaveLength(1); - expect(rowCounts).toEqual({ - tests: 0, - }); + expect(models).toHaveLength(0); }); }); }); diff --git a/tests/utils/field.test.ts b/tests/utils/field.test.ts index 0b37b81..be4df9c 100644 --- a/tests/utils/field.test.ts +++ b/tests/utils/field.test.ts @@ -171,7 +171,7 @@ describe('fields', () => { const diff = await diffFields(localFields, remoteFields, 'account', [], []); expect(diff).toHaveLength(4); expect(diff).toStrictEqual([ - "create.model({slug:'RONIN_TEMP_account',fields:[{type:'number', slug:'id', unique:true}]})", + 'create.model({"slug":"RONIN_TEMP_account","fields":{"id":{"type":"number","unique":true}}})', 'add.RONIN_TEMP_account.with(() => get.account())', 'drop.model("account")', 'alter.model("RONIN_TEMP_account").to({slug: "account"})', @@ -198,7 +198,7 @@ describe('fields', () => { }); expect(diff).toHaveLength(5); expect(diff).toStrictEqual([ - "create.model({slug:'RONIN_TEMP_account',fields:[{type:'link', slug:'profile', target:'profile'}]})", + 'create.model({"slug":"RONIN_TEMP_account","fields":{"profile":{"type":"link","target":"profile"}}})', 'add.RONIN_TEMP_account.with(() => get.account())', 'alter.model("RONIN_TEMP_account").alter.field("profile").to({slug: "newProfile"})', 'drop.model("account")', diff --git a/tests/utils/migration.test.ts b/tests/utils/migration.test.ts index 9d0bc4e..962af34 100644 --- a/tests/utils/migration.test.ts +++ b/tests/utils/migration.test.ts @@ -12,10 +12,8 @@ import { } from '@/fixtures/index'; import { adjustModelMeta, - createIndexes, createModels, diffModels, - dropIndexes, dropModels, indexesToRecreate, triggersToRecreate, @@ -45,7 +43,7 @@ describe('migration', () => { expect(modelDiff).toHaveLength(4); expect(modelDiff).toStrictEqual([ - "create.model({slug:'RONIN_TEMP_account',fields:[{slug:'name', type:'string'}]})", + 'create.model({"slug":"RONIN_TEMP_account","fields":{"name":{"type":"string"}}})', 'add.RONIN_TEMP_account.with(() => get.account())', 'drop.model("account")', 'alter.model("RONIN_TEMP_account").to({slug: "account"})', @@ -57,7 +55,7 @@ describe('migration', () => { const modelDiff = await diffModels([Account, Profile], [Account]); expect(modelDiff).toHaveLength(1); expect(modelDiff).toStrictEqual([ - "create.model({slug:'profile',fields:[{slug:'username', type:'string'}]})", + 'create.model({"slug":"profile","fields":{"username":{"type":"string"}}})', ]); }); @@ -75,7 +73,7 @@ describe('migration', () => { expect(modelDiff).toHaveLength(4); expect(modelDiff).toStrictEqual([ - "create.model({slug:'RONIN_TEMP_account',fields:[{slug:'name', required:true, unique:true, type:'string'}]})", + 'create.model({"slug":"RONIN_TEMP_account","fields":{"name":{"required":true,"unique":true,"type":"string"}}})', 'add.RONIN_TEMP_account.with(() => get.account())', 'drop.model("account")', 'alter.model("RONIN_TEMP_account").to({slug: "account"})', @@ -98,7 +96,7 @@ describe('migration', () => { expect(modelDiff).toHaveLength(4); expect(modelDiff).toStrictEqual([ - "create.model({slug:'RONIN_TEMP_account',fields:[{slug:'name', type:'string'}]})", + 'create.model({"slug":"RONIN_TEMP_account","fields":{"name":{"type":"string"}}})', 'add.RONIN_TEMP_account.with(() => get.account())', 'drop.model("account")', 'alter.model("RONIN_TEMP_account").to({slug: "account"})', @@ -121,7 +119,7 @@ describe('migration', () => { const modelDiff = await diffModels([TestB], [TestA]); expect(modelDiff).toBeDefined(); - expect(modelDiff).toHaveLength(8); + expect(modelDiff).toHaveLength(7); }); }); @@ -133,8 +131,8 @@ describe('migration', () => { expect(modelDiff).toHaveLength(2); expect(modelDiff).toStrictEqual([ - "create.model({slug:'comment',fields:[{slug:'name', type:'string'}]})", - 'alter.model("comment").create.trigger({"action":"INSERT","when":"BEFORE","effects":[{"add":{"comment":{"with":{"name":"Test"}}}}]})', + 'create.model({"slug":"comment","fields":{"name":{"type":"string"}}})', + 'alter.model("comment").create.trigger({"slug":"filedTrigger","action":"INSERT","when":"BEFORE","effects":[{"__RONIN_QUERY":{"add":{"comment":{"with":{"name":"Test"}}}}}]})', ]); }); @@ -151,8 +149,8 @@ describe('migration', () => { expect(modelDiff).toHaveLength(2); expect(modelDiff).toStrictEqual([ - 'alter.model("comment").drop.trigger("no slug")', - 'alter.model("comment").create.trigger({"action":"DELETE","when":"AFTER","effects":[{"add":{"comment":{"with":{"name":"Test"}}}}]})', + 'alter.model("comment").drop.trigger("filedTrigger")', + 'alter.model("comment").create.trigger({"slug":"filedTrigger","action":"DELETE","when":"AFTER","effects":[{"__RONIN_QUERY":{"add":{"comment":{"with":{"name":"Test"}}}}}]})', ]); }); }); @@ -163,11 +161,9 @@ describe('migration', () => { const models = [ { slug: 'test1', - fields: [], }, { slug: 'test2', - fields: [], }, ]; @@ -190,23 +186,21 @@ describe('migration', () => { const models: Array = [ { slug: 'test1', - fields: [ - { - slug: 'field1', + fields: { + field1: { name: 'Field1', type: 'string', }, - ], + }, }, { slug: 'test2', - fields: [ - { - slug: 'field2', + fields: { + field2: { name: 'Field2', type: 'number', }, - ], + }, }, ]; @@ -214,8 +208,8 @@ describe('migration', () => { expect(queries).toHaveLength(2); expect(queries).toStrictEqual([ - "create.model({slug:'test1',fields:[{slug:'field1', name:'Field1', type:'string'}]})", - "create.model({slug:'test2',fields:[{slug:'field2', name:'Field2', type:'number'}]})", + 'create.model({"slug":"test1","fields":{"field1":{"name":"Field1","type":"string"}}})', + 'create.model({"slug":"test2","fields":{"field2":{"name":"Field2","type":"number"}}})', ]); }); @@ -229,7 +223,7 @@ describe('migration', () => { const queries = createModels(models); expect(queries).toHaveLength(1); - expect(queries).toStrictEqual(["create.model({slug:'test1'})"]); + expect(queries).toStrictEqual(['create.model({"slug":"test1"})']); }); test('returns empty array for empty model list', () => { @@ -244,85 +238,34 @@ describe('migration', () => { test('adds new indexes and drops removed ones', () => { const definedModel = { slug: 'test', - indexes: [ - { + indexes: { + index_1: { fields: [{ slug: 'field1' }], unique: true, }, - ], + }, }; const existingModel = { slug: 'test', - indexes: [ - { + indexes: { + index_1: { fields: [{ slug: 'field2' }], unique: false, slug: 'old_index', }, - ], + }, }; - const queries = [ - ...dropIndexes(definedModel, existingModel), - ...createIndexes(definedModel, existingModel), - ]; + // @ts-expect-error This will work once the types are fixed. + const queries = [...indexesToRecreate([definedModel], [existingModel])]; expect(queries).toHaveLength(2); expect(queries).toStrictEqual([ - 'alter.model("test").drop.index("old_index")', - 'alter.model("test").create.index({"fields":[{"slug":"field1"}],"unique":true})', + 'alter.model("test").drop.index("index_1")', + 'alter.model("test").create.index({"slug":"index_1","fields":[{"slug":"field1"}],"unique":true})', ]); }); - - test('handles models with no indexes', () => { - const definedModel = { - slug: 'test', - }; - - const existingModel = { - slug: 'test', - }; - - const queries = [ - ...dropIndexes(definedModel, existingModel), - ...createIndexes(definedModel, existingModel), - ]; - - expect(queries).toHaveLength(0); - expect(queries).toStrictEqual([]); - }); - - test('keeps matching indexes unchanged', () => { - const definedModel = { - slug: 'test', - indexes: [ - { - fields: [{ slug: 'field1' }], - unique: true, - }, - ], - }; - - const existingModel = { - slug: 'test', - indexes: [ - { - fields: [{ slug: 'field1' }], - unique: true, - slug: 'idx', - }, - ], - }; - - const queries = [ - ...dropIndexes(definedModel, existingModel), - ...createIndexes(definedModel, existingModel), - ]; - - expect(queries).toHaveLength(0); - expect(queries).toStrictEqual([]); - }); }); describe('indexesToRecreate', () => { @@ -336,55 +279,56 @@ describe('migration', () => { const definedModels = [ { slug: 'test1', - indexes: [ - { + indexes: { + index_1: { fields: [{ slug: 'field1' }], unique: true, }, - ], + }, }, { slug: 'test2', - indexes: [ - { + indexes: { + index2: { fields: [{ slug: 'field2' }], unique: false, }, - ], + }, }, ]; const existingModels = [ { slug: 'test1', - indexes: [ - { + indexes: { + index_1: { fields: [{ slug: 'field1' }], unique: false, slug: 'old_index1', }, - ], + }, }, { slug: 'test2', - indexes: [ - { + indexes: { + index2: { fields: [{ slug: 'different' }], unique: false, slug: 'old_index2', }, - ], + }, }, ]; + // @ts-expect-error This will work once the types are fixed. const queries = indexesToRecreate(definedModels, existingModels); expect(queries).toHaveLength(4); expect(queries).toStrictEqual([ - 'alter.model("test1").drop.index("old_index1")', - 'alter.model("test1").create.index({"fields":[{"slug":"field1"}],"unique":true})', - 'alter.model("test2").drop.index("old_index2")', - 'alter.model("test2").create.index({"fields":[{"slug":"field2"}],"unique":false})', + 'alter.model("test1").drop.index("index_1")', + 'alter.model("test1").create.index({"slug":"index_1","fields":[{"slug":"field1"}],"unique":true})', + 'alter.model("test2").drop.index("index2")', + 'alter.model("test2").create.index({"slug":"index2","fields":[{"slug":"field2"}],"unique":false})', ]); }); @@ -392,22 +336,23 @@ describe('migration', () => { const definedModels = [ { slug: 'test1', - indexes: [ - { + indexes: { + fieldIndex: { fields: [{ slug: 'field1' }], unique: true, }, - ], + }, }, ]; const existingModels: Array = []; + // @ts-expect-error This will work once the types are fixed. const queries = indexesToRecreate(definedModels, existingModels); expect(queries).toHaveLength(1); expect(queries).toStrictEqual([ - 'alter.model("test1").create.index({"fields":[{"slug":"field1"}],"unique":true})', + 'alter.model("test1").create.index({"slug":"fieldIndex","fields":[{"slug":"field1"}],"unique":true})', ]); }); }); @@ -511,61 +456,61 @@ describe('migration', () => { const definedModels: Array = [ { slug: 'test1', - triggers: [ - { + triggers: { + trigger1: { fields: [{ slug: 'field1' }], action: 'INSERT', when: 'BEFORE', effects: [], }, - ], + }, }, { slug: 'test2', - triggers: [ - { + triggers: { + trigger2: { fields: [{ slug: 'field2' }], action: 'DELETE', when: 'AFTER', effects: [], }, - ], + }, }, ]; const existingModels: Array = [ { slug: 'test1', - triggers: [ - { + triggers: { + trigger1: { fields: [{ slug: 'field1' }], action: 'UPDATE', when: 'AFTER', effects: [], - slug: 'old_trigger1', }, - ], + }, }, { slug: 'test2', - triggers: [ - { + triggers: { + trigger2: { fields: [{ slug: 'field3' }], action: 'INSERT', when: 'BEFORE', effects: [], - slug: 'old_trigger2', }, - ], + }, }, ]; const queries = triggersToRecreate(definedModels, existingModels); - expect(queries).toHaveLength(2); + expect(queries).toHaveLength(4); expect(queries).toStrictEqual([ - 'alter.model("test2").drop.trigger("old_trigger2")', - 'alter.model("test2").create.trigger({"fields":[{"slug":"field2"}],"action":"DELETE","when":"AFTER","effects":[]})', + 'alter.model("test1").drop.trigger("trigger1")', + 'alter.model("test1").create.trigger({"slug":"trigger1","fields":[{"slug":"field1"}],"action":"INSERT","when":"BEFORE","effects":[]})', + 'alter.model("test2").drop.trigger("trigger2")', + 'alter.model("test2").create.trigger({"slug":"trigger2","fields":[{"slug":"field2"}],"action":"DELETE","when":"AFTER","effects":[]})', ]); }); @@ -592,14 +537,14 @@ describe('migration', () => { const definedModels: Array = [ { slug: 'test1', - triggers: [ - { + triggers: { + filedTrigger: { fields: [{ slug: 'field1' }], action: 'INSERT', when: 'BEFORE', effects: [], }, - ], + }, }, ]; @@ -609,7 +554,7 @@ describe('migration', () => { expect(queries).toHaveLength(1); expect(queries).toStrictEqual([ - 'alter.model("test1").create.trigger({"fields":[{"slug":"field1"}],"action":"INSERT","when":"BEFORE","effects":[]})', + 'alter.model("test1").create.trigger({"slug":"filedTrigger","fields":[{"slug":"field1"}],"action":"INSERT","when":"BEFORE","effects":[]})', ]); }); }); diff --git a/tests/utils/misc.test.ts b/tests/utils/misc.test.ts index 6f83bbb..94a9d97 100644 --- a/tests/utils/misc.test.ts +++ b/tests/utils/misc.test.ts @@ -13,6 +13,7 @@ import { } from '@/src/utils/misc'; import { Account, CONSTANTS, TestA, TestB } from '@/fixtures/index'; +import { convertModelToArrayFields } from '@/src/utils/model'; import type { Model } from '@ronin/compiler'; describe('misc', () => { @@ -66,11 +67,21 @@ describe('misc', () => { const consoleTableSpy = spyOn(console, 'table'); const objA: Model = { slug: 'test', - fields: [{ slug: 'name', name: 'Test', type: 'string' }], + fields: { + name: { + name: 'Test', + type: 'string', + }, + }, }; const objB: Model = { slug: 'test', - fields: [{ slug: 'name', name: 'Test', type: 'string' }], + fields: { + name: { + name: 'Test', + type: 'string', + }, + }, }; logTableDiff(objA, objB, 'Identical Objects'); @@ -147,16 +158,20 @@ describe('misc', () => { test('should return models in code definitions - one model', async () => { mock.module('@/src/utils/misc', () => { - return { getModelDefinitions: (): Array => [Account] }; + return { + getModelDefinitions: (): Array => [convertModelToArrayFields(Account)], + }; }); const models = await getModelDefinitions(); + expect(models).toEqual([ { slug: 'account', pluralSlug: 'accounts', fields: [ { + // @ts-expect-error This will work once the types are fixed. slug: 'name', type: 'string', }, @@ -204,29 +219,26 @@ describe('areArraysEqual', () => { test('should sort models with dependencies', () => { const modelA: Model = { slug: 'modelA', - fields: [], }; const modelB: Model = { slug: 'modelB', - fields: [ - { - slug: 'linkToA', + fields: { + linkToA: { type: 'link', target: 'modelA', }, - ], + }, }; const modelC: Model = { slug: 'modelC', - fields: [ - { - slug: 'linkToB', + fields: { + linkToB: { type: 'link', target: 'modelB', }, - ], + }, }; const unsortedModels = [modelC, modelB, modelA]; @@ -240,13 +252,12 @@ describe('areArraysEqual', () => { test('should handle self-referential links', () => { const modelWithSelfLink: Model = { slug: 'employee', - fields: [ - { - slug: 'manager', + fields: { + manager: { type: 'link', target: 'employee', // Self reference }, - ], + }, }; const sortedModels = sortModels([modelWithSelfLink]); @@ -256,24 +267,22 @@ describe('areArraysEqual', () => { test('should throw error on circular dependencies', () => { const modelA: Model = { slug: 'modelA', - fields: [ - { - slug: 'linkToB', + fields: { + linkToB: { type: 'link', target: 'modelB', }, - ], + }, }; const modelB: Model = { slug: 'modelB', - fields: [ - { - slug: 'linkToA', + fields: { + linkToA: { type: 'link', target: 'modelA', }, - ], + }, }; expect(() => sortModels([modelA, modelB])).toThrow( @@ -284,12 +293,10 @@ describe('areArraysEqual', () => { test('should handle models with no dependencies', () => { const modelA: Model = { slug: 'modelA', - fields: [], }; const modelB: Model = { slug: 'modelB', - fields: [], }; const sortedModels = sortModels([modelA, modelB]); @@ -302,13 +309,12 @@ describe('areArraysEqual', () => { test('should throw error if dependency target not found', () => { const modelA: Model = { slug: 'modelA', - fields: [ - { - slug: 'linkToMissing', + fields: { + linkToMissing: { type: 'link', target: 'nonexistentModel', }, - ], + }, }; // The visit function will try to process 'nonexistentModel' but won't find it diff --git a/tests/utils/protocol.test.ts b/tests/utils/protocol.test.ts index c1ff217..e0e686b 100644 --- a/tests/utils/protocol.test.ts +++ b/tests/utils/protocol.test.ts @@ -96,12 +96,11 @@ describe('protocol', () => { const models: Array = [ { slug: 'account', - fields: [ - { - slug: 'handle', + fields: { + handle: { type: 'string', }, - ], + }, }, ]; const protocol = await new Protocol(packages, queries).convertToQueryObjects(); @@ -110,7 +109,7 @@ describe('protocol', () => { expect(statements).toHaveLength(1); expect(statements[0].statement).toStrictEqual( - `SELECT "id", "ronin.locked", "ronin.createdAt", "ronin.createdBy", "ronin.updatedAt", "ronin.updatedBy", "handle" FROM "accounts" WHERE "handle" = 'elaine' LIMIT 1`, + 'SELECT "id", "ronin.createdAt", "ronin.createdBy", "ronin.updatedAt", "ronin.updatedBy", "handle" FROM "accounts" WHERE "handle" = \'elaine\' LIMIT 1', ); }); diff --git a/tests/utils/queries.test.ts b/tests/utils/queries.test.ts index 3dd03c3..da5de10 100644 --- a/tests/utils/queries.test.ts +++ b/tests/utils/queries.test.ts @@ -14,7 +14,7 @@ import { renameModelQuery, setFieldQuery, } from '@/src/utils/queries'; -import type { ModelField, ModelTrigger } from '@ronin/compiler'; +import type { Model, ModelField } from '@ronin/compiler'; describe('queries', () => { test('drop model query', () => { @@ -23,27 +23,31 @@ describe('queries', () => { }); test('create model query without properties', () => { - const result = createModelQuery('user'); - expect(result).toBe("create.model({slug:'user'})"); + const user: Model = { + slug: 'user', + }; + const result = createModelQuery(user); + expect(result).toBe('create.model({"slug":"user"})'); }); test('create model query with properties', () => { - const result = createModelQuery('user', { + const user: Model = { + slug: 'user', pluralSlug: 'users', name: 'User', pluralName: 'Users', - fields: [ - { - slug: 'username', + fields: { + username: { type: 'string', name: 'Username', unique: true, required: true, }, - ], - }); + }, + }; + const result = createModelQuery(user); expect(result).toBe( - "create.model({slug:'user',pluralSlug:'users', name:'User', pluralName:'Users', fields:[{slug:'username', type:'string', name:'Username', unique:true, required:true}]})", + 'create.model({"slug":"user","pluralSlug":"users","name":"User","pluralName":"Users","fields":{"username":{"type":"string","name":"Username","unique":true,"required":true}}})', ); }); @@ -94,18 +98,19 @@ describe('queries', () => { }); test('create temp model query', () => { - const fields: Array = [ - { - slug: 'username', + const fields = { + username: { type: 'string', name: 'Username', unique: true, required: true, }, - ]; - const result = createTempModelQuery('user', fields, [], []); + }; + + // @ts-expect-error TODO: Fix this type. + const result = createTempModelQuery({ slug: 'user', fields }); expect(result).toEqual([ - "create.model({slug:'RONIN_TEMP_user',fields:[{slug:'username', type:'string', name:'Username', unique:true, required:true}]})", + 'create.model({"slug":"RONIN_TEMP_user","fields":{"username":{"type":"string","name":"Username","unique":true,"required":true}}})', 'add.RONIN_TEMP_user.with(() => get.user())', 'drop.model("user")', 'alter.model("RONIN_TEMP_user").to({slug: "user"})', @@ -113,19 +118,21 @@ describe('queries', () => { }); test('create temp model query with custom queries', () => { - const fields: Array = [ - { + const fields = { + username: { slug: 'username', type: 'string', name: 'Username', unique: true, required: true, }, - ]; + }; + const customQueries: Array = ['get.model("user")']; - const result = createTempModelQuery('user', fields, [], [], customQueries); + // @ts-expect-error TODO: Fix this type. + const result = createTempModelQuery({ slug: 'user', fields }, customQueries); expect(result).toEqual([ - "create.model({slug:'RONIN_TEMP_user',fields:[{slug:'username', type:'string', name:'Username', unique:true, required:true}]})", + 'create.model({"slug":"RONIN_TEMP_user","fields":{"username":{"slug":"username","type":"string","name":"Username","unique":true,"required":true}}})', 'add.RONIN_TEMP_user.with(() => get.user())', ...customQueries, 'drop.model("user")', @@ -134,29 +141,31 @@ describe('queries', () => { }); test('create temp model query with triggers', () => { - const fields: Array = [ - { - slug: 'username', + const fields = { + username: { type: 'string', name: 'Username', unique: true, required: true, }, - ]; - const triggers: Array = [ - { + }; + + const triggers = { + test: { action: 'INSERT', when: 'BEFORE', effects: [], }, - ]; - const result = createTempModelQuery('user', fields, [], triggers); + }; + + // @ts-expect-error Todo fix this type. + const result = createTempModelQuery({ slug: 'user', fields, triggers }); expect(result).toEqual([ - "create.model({slug:'RONIN_TEMP_user',fields:[{slug:'username', type:'string', name:'Username', unique:true, required:true}]})", + 'create.model({"slug":"RONIN_TEMP_user","fields":{"username":{"type":"string","name":"Username","unique":true,"required":true}}})', 'add.RONIN_TEMP_user.with(() => get.user())', 'drop.model("user")', 'alter.model("RONIN_TEMP_user").to({slug: "user"})', - 'alter.model("user").create.trigger({"action":"INSERT","when":"BEFORE","effects":[]})', + 'alter.model("user").create.trigger({"action":"INSERT","when":"BEFORE","effects":[],"slug":"test"})', ]); }); @@ -174,12 +183,13 @@ describe('queries', () => { test('add trigger query', () => { const result = createTriggerQuery('user', { + slug: 'validateEmail', action: 'INSERT', when: 'BEFORE', effects: [], }); expect(result).toBe( - 'alter.model("user").create.trigger({"action":"INSERT","when":"BEFORE","effects":[]})', + 'alter.model("user").create.trigger({"slug":"validateEmail","action":"INSERT","when":"BEFORE","effects":[]})', ); }); @@ -190,12 +200,13 @@ describe('queries', () => { test('add index query', () => { const result = createIndexQuery('user', { + slug: 'test', fields: [{ slug: 'email' }], unique: true, }); expect(result).toBe( - 'alter.model("user").create.index({"fields":[{"slug":"email"}],"unique":true})', + 'alter.model("user").create.index({"slug":"test","fields":[{"slug":"email"}],"unique":true})', ); });