diff --git a/bun.lock b/bun.lock index 2f3856b..20b3cfc 100644 --- a/bun.lock +++ b/bun.lock @@ -12,6 +12,7 @@ "@orama/orama": "^3.1.18", "@typescript/ata": "^0.9.8", "@typescript/vfs": "^1.6.4", + "beautiful-mermaid": "^1.1.3", "class-variance-authority": "^0.7.1", "cnfast": "^0.0.8", "fumadocs-core": "16.12.1", @@ -19,7 +20,8 @@ "fumadocs-twoslash": "^3.3.0", "fumadocs-ui": "npm:@fumadocs/base-ui@16.12.1", "lucide-react": "^1.3.0", - "next": "^16.2.12", + "next": "^16.3.4", + "next-themes": "^0.4.6", "react": "^19.2.8", "react-dom": "^19.2.8", "takumi-js": "^2.4.0", @@ -27,7 +29,6 @@ }, "devDependencies": { "@biomejs/biome": "^2.5.5", - "@duckity/js": "^0.1.4", "@tailwindcss/postcss": "^4.3.3", "@types/mdx": "^2.0.14", "@types/node": "^26.1.1", @@ -68,10 +69,6 @@ "@biomejs/cli-win32-x64": ["@biomejs/cli-win32-x64@2.5.5", "", { "os": "win32", "cpu": "x64" }, "sha512-bIBFo+n6MIxdNcVFy5CrurbKiZQiUciK3bt8+O9I4wjFZNTfXLpi+giq47522eXqW5NBc9ulx7dR1SlZKi2J5g=="], - "@duckity/js": ["@duckity/js@0.1.4", "", { "dependencies": { "@duckity/wasm": "1.1.16" }, "peerDependencies": { "typescript": "^5" } }, "sha512-zjzV2vpKdrFwt954x5thbocTAflrNUVj7yTqMUC8E0ICAbJ9YP30arZvGZqTE98+vemxOIb9hH4SWja7BXH37w=="], - - "@duckity/wasm": ["@duckity/wasm@1.1.16", "", {}, "sha512-P0TDKQ187S68D3ri2ZW9m60gCnUj+/iDLgwWwJLAbfiJhxEFfsCb7tJDgaQQR9mJ2PRETuBHc4TjbLphSuAjGg=="], - "@emnapi/runtime": ["@emnapi/runtime@1.11.3", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-Xz4Tpyki7XyrpbUK1jR1AhdAdaXyhhY4lZ3neLodmhpuWfy2PAQN5B46sAiU4liOXGLkHypn/qU+jvfWSCYYLA=="], "@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.28.1", "", { "os": "aix", "cpu": "ppc64" }, "sha512-Svl7tq8k/08+p6CXPpRjQ1fKX+1odH/BQbb48fV6fj3CWHhsoIOoY87w1oHXm0qEpkIK3ZfVgp0hed3XBXzXMQ=="], @@ -144,53 +141,57 @@ "@img/colour": ["@img/colour@1.1.0", "", {}, "sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ=="], - "@img/sharp-darwin-arm64": ["@img/sharp-darwin-arm64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-darwin-arm64": "1.2.4" }, "os": "darwin", "cpu": "arm64" }, "sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w=="], + "@img/sharp-darwin-arm64": ["@img/sharp-darwin-arm64@0.35.4", "", { "optionalDependencies": { "@img/sharp-libvips-darwin-arm64": "1.3.3" }, "os": "darwin", "cpu": "arm64" }, "sha512-Uhfl4V4lhP2nbUVF9+hyH1+luj86f1gUFeo8ALYxFoULoU+G87D43BfeMP8XHsk9boxAnCY/bf2EHwhA7MuGsA=="], - "@img/sharp-darwin-x64": ["@img/sharp-darwin-x64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-darwin-x64": "1.2.4" }, "os": "darwin", "cpu": "x64" }, "sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw=="], + "@img/sharp-darwin-x64": ["@img/sharp-darwin-x64@0.35.4", "", { "optionalDependencies": { "@img/sharp-libvips-darwin-x64": "1.3.3" }, "os": "darwin", "cpu": "x64" }, "sha512-hWniXY3bG5qKpkKrAwPe4y+VTPmf086YQAnkxWh7uA1YrlRouWGa0M0Mxj3ZjnXFkv7/TD1bTy9lGUK26vRvWw=="], - "@img/sharp-libvips-darwin-arm64": ["@img/sharp-libvips-darwin-arm64@1.2.4", "", { "os": "darwin", "cpu": "arm64" }, "sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g=="], + "@img/sharp-freebsd-wasm32": ["@img/sharp-freebsd-wasm32@0.35.4", "", { "dependencies": { "@img/sharp-wasm32": "0.35.4" }, "os": "freebsd" }, "sha512-lIsKw/BU+kjB4eZjxrYrZmwOJYi3Ajrv66iAlBmUPyKc3HpnloevB1g3wxGD9P/5BbQ1brBGl65VRRrCvQDEqA=="], - "@img/sharp-libvips-darwin-x64": ["@img/sharp-libvips-darwin-x64@1.2.4", "", { "os": "darwin", "cpu": "x64" }, "sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg=="], + "@img/sharp-libvips-darwin-arm64": ["@img/sharp-libvips-darwin-arm64@1.3.3", "", { "os": "darwin", "cpu": "arm64" }, "sha512-suTBPTDGrI9WodccaDdwZItTSaBYASlBk1NSfElSHrUfzu3szG6lvIF58+WiFvnfzuK8ZBFS5zE00PxqxnRiPg=="], - "@img/sharp-libvips-linux-arm": ["@img/sharp-libvips-linux-arm@1.2.4", "", { "os": "linux", "cpu": "arm" }, "sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A=="], + "@img/sharp-libvips-darwin-x64": ["@img/sharp-libvips-darwin-x64@1.3.3", "", { "os": "darwin", "cpu": "x64" }, "sha512-FVJZ5mITMobmXIz/hPDTw0EintTW5H3WfrxwLqEqjiIihlu+hVRyGrFQ60xl0Lxn7Bt3zdpevPaQi0HEzqz9fw=="], - "@img/sharp-libvips-linux-arm64": ["@img/sharp-libvips-linux-arm64@1.2.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw=="], + "@img/sharp-libvips-linux-arm": ["@img/sharp-libvips-linux-arm@1.3.3", "", { "os": "linux", "cpu": "arm" }, "sha512-3rbU4vqXXc3hY/OiXdl52xZvT0F1yEngWfvqudtPJg/KkyiaQw2DRsFrNzpmLvfavbwOq3qXn36GP8obHRULQA=="], - "@img/sharp-libvips-linux-ppc64": ["@img/sharp-libvips-linux-ppc64@1.2.4", "", { "os": "linux", "cpu": "ppc64" }, "sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA=="], + "@img/sharp-libvips-linux-arm64": ["@img/sharp-libvips-linux-arm64@1.3.3", "", { "os": "linux", "cpu": "arm64" }, "sha512-0DaL0A6Xu6sQSQFwe4iVCrKWU2cCTItnRsYsCdxAMm9NF6twAA9BKnoqy4hqz4+azQ0JHuA26qiUKsf1XJ/v5A=="], - "@img/sharp-libvips-linux-riscv64": ["@img/sharp-libvips-linux-riscv64@1.2.4", "", { "os": "linux", "cpu": "none" }, "sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA=="], + "@img/sharp-libvips-linux-ppc64": ["@img/sharp-libvips-linux-ppc64@1.3.3", "", { "os": "linux", "cpu": "ppc64" }, "sha512-cdn1OvUBwsXhbC0zSzJnNzf5MZ/mTrobawDvNXBTxe8VtqKAm0sRuEY2Evzovb/w9JMk4TvRxqt1mekSuJz64w=="], - "@img/sharp-libvips-linux-s390x": ["@img/sharp-libvips-linux-s390x@1.2.4", "", { "os": "linux", "cpu": "s390x" }, "sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ=="], + "@img/sharp-libvips-linux-riscv64": ["@img/sharp-libvips-linux-riscv64@1.3.3", "", { "os": "linux", "cpu": "none" }, "sha512-HjPVx7yKz+0lqdhDlTw1tt90wamBoxhiXpvl1XZpJLiHH4RCJ5yDTqH+VlYPv2fwFs89JFw4c1IexYOcQUi4IQ=="], - "@img/sharp-libvips-linux-x64": ["@img/sharp-libvips-linux-x64@1.2.4", "", { "os": "linux", "cpu": "x64" }, "sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw=="], + "@img/sharp-libvips-linux-s390x": ["@img/sharp-libvips-linux-s390x@1.3.3", "", { "os": "linux", "cpu": "s390x" }, "sha512-neWLh+3yCNThxnfy3c4BbVBeGgt9aftno+XbT56iK28RgeDs3UOFWviLWlUu0bArYVYJaFDK+RRohbicUNCm8Q=="], - "@img/sharp-libvips-linuxmusl-arm64": ["@img/sharp-libvips-linuxmusl-arm64@1.2.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw=="], + "@img/sharp-libvips-linux-x64": ["@img/sharp-libvips-linux-x64@1.3.3", "", { "os": "linux", "cpu": "x64" }, "sha512-4vKmvAst9nrowcqquKFAyZJUDolUaIp8uRiN0mWFguJ1IplC9/pitXtlnnlU4aa/eJw3J7i67V+pwUL+wZGdsA=="], - "@img/sharp-libvips-linuxmusl-x64": ["@img/sharp-libvips-linuxmusl-x64@1.2.4", "", { "os": "linux", "cpu": "x64" }, "sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg=="], + "@img/sharp-libvips-linuxmusl-arm64": ["@img/sharp-libvips-linuxmusl-arm64@1.3.3", "", { "os": "linux", "cpu": "arm64" }, "sha512-Y9kQaLMuNoB0bPYOOdcZMaseNrFpPodIWWMrx+CZyydf2xn68j9WYc6sWWRrDwNkzCQjKYfc68L7jKjGlHMibw=="], - "@img/sharp-linux-arm": ["@img/sharp-linux-arm@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-arm": "1.2.4" }, "os": "linux", "cpu": "arm" }, "sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw=="], + "@img/sharp-libvips-linuxmusl-x64": ["@img/sharp-libvips-linuxmusl-x64@1.3.3", "", { "os": "linux", "cpu": "x64" }, "sha512-fj8Mv0HHfD1Rr+4I68+3agJynxDWtBFgicTbSOb9Bke6pIwzGcJ+RX/yHjmiEGFMCavY/dxvem7MyNaJF+wDiw=="], - "@img/sharp-linux-arm64": ["@img/sharp-linux-arm64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-arm64": "1.2.4" }, "os": "linux", "cpu": "arm64" }, "sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg=="], + "@img/sharp-linux-arm": ["@img/sharp-linux-arm@0.35.4", "", { "optionalDependencies": { "@img/sharp-libvips-linux-arm": "1.3.3" }, "os": "linux", "cpu": "arm" }, "sha512-7OAS8gI0EReKGVN2HssHlM6umJgxF5VI3xN0p9FA91p/YO+ou5hiNghLdZ5BEHztwaaK5+bLKRf8x/o2L2nk9A=="], - "@img/sharp-linux-ppc64": ["@img/sharp-linux-ppc64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-ppc64": "1.2.4" }, "os": "linux", "cpu": "ppc64" }, "sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA=="], + "@img/sharp-linux-arm64": ["@img/sharp-linux-arm64@0.35.4", "", { "optionalDependencies": { "@img/sharp-libvips-linux-arm64": "1.3.3" }, "os": "linux", "cpu": "arm64" }, "sha512-De4jpEnAU8Hd5oT0j1G3uL4ZvTuipVMn7YC6vPaJhy6/7EwEae0SVAoBrUMYQbkLGDm85taVWwuPc1a44LTzCQ=="], - "@img/sharp-linux-riscv64": ["@img/sharp-linux-riscv64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-riscv64": "1.2.4" }, "os": "linux", "cpu": "none" }, "sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw=="], + "@img/sharp-linux-ppc64": ["@img/sharp-linux-ppc64@0.35.4", "", { "optionalDependencies": { "@img/sharp-libvips-linux-ppc64": "1.3.3" }, "os": "linux", "cpu": "ppc64" }, "sha512-2oYZJeIl4kCcMGk4ouZVjnkCtFrpQFlNEtJ6GbxzhHQchwH0NH/qEb9ykmOl29dqwMq+JhFdZn+1ak2FKhI9fQ=="], - "@img/sharp-linux-s390x": ["@img/sharp-linux-s390x@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-s390x": "1.2.4" }, "os": "linux", "cpu": "s390x" }, "sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg=="], + "@img/sharp-linux-riscv64": ["@img/sharp-linux-riscv64@0.35.4", "", { "optionalDependencies": { "@img/sharp-libvips-linux-riscv64": "1.3.3" }, "os": "linux", "cpu": "none" }, "sha512-cPbNChoRURAWdebDIHSenxRpgEdy7JkPydSnUxRm9VvKD7m0/xVaR/8Fzlu81pk5nHEvHH87UZUA7cTtwnbJSA=="], - "@img/sharp-linux-x64": ["@img/sharp-linux-x64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-x64": "1.2.4" }, "os": "linux", "cpu": "x64" }, "sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ=="], + "@img/sharp-linux-s390x": ["@img/sharp-linux-s390x@0.35.4", "", { "optionalDependencies": { "@img/sharp-libvips-linux-s390x": "1.3.3" }, "os": "linux", "cpu": "s390x" }, "sha512-RY0JFY8Fd6RonCBtHz+DvadaPkXDSI1AUn6yWL9TipqkZ1vY8w8evqdgyDFnkm4/K1ve1TvZiaePP5oSd4+WVQ=="], - "@img/sharp-linuxmusl-arm64": ["@img/sharp-linuxmusl-arm64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linuxmusl-arm64": "1.2.4" }, "os": "linux", "cpu": "arm64" }, "sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg=="], + "@img/sharp-linux-x64": ["@img/sharp-linux-x64@0.35.4", "", { "optionalDependencies": { "@img/sharp-libvips-linux-x64": "1.3.3" }, "os": "linux", "cpu": "x64" }, "sha512-9qvvEAuk8k89TfWUoX2htWjbAMX8p+NxCppjpcg5k6xMsjhBQPTsoIh36h9Qde4WRuGpJeYnOjdosDn/cnv+OA=="], - "@img/sharp-linuxmusl-x64": ["@img/sharp-linuxmusl-x64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linuxmusl-x64": "1.2.4" }, "os": "linux", "cpu": "x64" }, "sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q=="], + "@img/sharp-linuxmusl-arm64": ["@img/sharp-linuxmusl-arm64@0.35.4", "", { "optionalDependencies": { "@img/sharp-libvips-linuxmusl-arm64": "1.3.3" }, "os": "linux", "cpu": "arm64" }, "sha512-KB5jxpfWQTr0nc3xdHtWChdbifHrBGsd2SM62Eyxrl8afikm+f5qGBU75SJIZBT/S1MC8XyacdlXBMSWq6OURA=="], - "@img/sharp-wasm32": ["@img/sharp-wasm32@0.34.5", "", { "dependencies": { "@emnapi/runtime": "^1.7.0" }, "cpu": "none" }, "sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw=="], + "@img/sharp-linuxmusl-x64": ["@img/sharp-linuxmusl-x64@0.35.4", "", { "optionalDependencies": { "@img/sharp-libvips-linuxmusl-x64": "1.3.3" }, "os": "linux", "cpu": "x64" }, "sha512-f+eZJZIQNEEd26RPSW+76chwOf1XtA2Y/O+5ocVyLliHkeih3e+jhLVBdNTd2rS3IbNXK8+ug93Vf5ZXtF5Lxg=="], - "@img/sharp-win32-arm64": ["@img/sharp-win32-arm64@0.34.5", "", { "os": "win32", "cpu": "arm64" }, "sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g=="], + "@img/sharp-wasm32": ["@img/sharp-wasm32@0.35.4", "", { "dependencies": { "@emnapi/runtime": "^1.11.3" } }, "sha512-zQnl4Kwp7Q6NHsENtU2T/00Zi+w3AQNwz3+UaTyVBy2FpXrzXzGjndpK61onhZjRtRpQXxCTeqw19bVyXOh7jA=="], - "@img/sharp-win32-ia32": ["@img/sharp-win32-ia32@0.34.5", "", { "os": "win32", "cpu": "ia32" }, "sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg=="], + "@img/sharp-webcontainers-wasm32": ["@img/sharp-webcontainers-wasm32@0.35.4", "", { "dependencies": { "@img/sharp-wasm32": "0.35.4" }, "cpu": "none" }, "sha512-ESfNkywmCfPNyaZjxooddJQiQ+l/nTpGEOGthxiLnIHXC/CmcBixnfwUleX9mCz9ovrUUvKMap/pm8RYbzfwaA=="], - "@img/sharp-win32-x64": ["@img/sharp-win32-x64@0.34.5", "", { "os": "win32", "cpu": "x64" }, "sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw=="], + "@img/sharp-win32-arm64": ["@img/sharp-win32-arm64@0.35.4", "", { "os": "win32", "cpu": "arm64" }, "sha512-iNdlBX9gLVvqe2I3uIJSIKTq6wckP/DYxZtcqxm09x5Gi24DnFBmPAWZmr60ZyYMG0xlzo6goG3670ar+RXvRw=="], + + "@img/sharp-win32-ia32": ["@img/sharp-win32-ia32@0.35.4", "", { "os": "win32", "cpu": "ia32" }, "sha512-kqRsbaa5CS6KHlpxnN7WhE6vAAugXyZButpRdvDWetlv6Qv4N9WTcrWzF7tXfB9T7MsoadqdI8hmwLq6UlLvtw=="], + + "@img/sharp-win32-x64": ["@img/sharp-win32-x64@0.35.4", "", { "os": "win32", "cpu": "x64" }, "sha512-XtmnYhBcrORsJ4XJngyzr/EWP0hRZLAZRFaApdKuviyqF78+ylxh2y06ZmtULAMOnObJ3ucpN0AcwSWnMowTRg=="], "@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.3.13", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA=="], @@ -204,23 +205,23 @@ "@mdx-js/mdx": ["@mdx-js/mdx@3.1.1", "", { "dependencies": { "@types/estree": "^1.0.0", "@types/estree-jsx": "^1.0.0", "@types/hast": "^3.0.0", "@types/mdx": "^2.0.0", "acorn": "^8.0.0", "collapse-white-space": "^2.0.0", "devlop": "^1.0.0", "estree-util-is-identifier-name": "^3.0.0", "estree-util-scope": "^1.0.0", "estree-walker": "^3.0.0", "hast-util-to-jsx-runtime": "^2.0.0", "markdown-extensions": "^2.0.0", "recma-build-jsx": "^1.0.0", "recma-jsx": "^1.0.0", "recma-stringify": "^1.0.0", "rehype-recma": "^1.0.0", "remark-mdx": "^3.0.0", "remark-parse": "^11.0.0", "remark-rehype": "^11.0.0", "source-map": "^0.7.0", "unified": "^11.0.0", "unist-util-position-from-estree": "^2.0.0", "unist-util-stringify-position": "^4.0.0", "unist-util-visit": "^5.0.0", "vfile": "^6.0.0" } }, "sha512-f6ZO2ifpwAQIpzGWaBQT2TXxPv6z3RBzQKpVftEWN78Vl/YweF1uwussDx8ECAXVtr3Rs89fKyG9YlzUs9DyGQ=="], - "@next/env": ["@next/env@16.2.12", "", {}, "sha512-d0Z5Bc13Fa4nR8pFAKx2jay2yhJM16vlfHbTzYnUQAxlNb6B6lmn4hjt69lYNt4kRtyYP6gEM49lPRHNbIyneg=="], + "@next/env": ["@next/env@16.3.4", "", {}, "sha512-cjWZnUUa6jZq2kFaNe/ZyJdZonOZ/QoN0Zka2nz/FLOrfx14pQuM9c5RaSVkWMqgdt4ksgPAMWPyHSs/CyV48Q=="], - "@next/swc-darwin-arm64": ["@next/swc-darwin-arm64@16.2.12", "", { "os": "darwin", "cpu": "arm64" }, "sha512-0W1R0teHWJrqKX0FH20IzzIWAOuGtBxPGuObrxy1lE8hQvCFj49KE8a3WUg0D7sq6rn6zkM4c7YGUnhudBS6oA=="], + "@next/swc-darwin-arm64": ["@next/swc-darwin-arm64@16.3.4", "", { "os": "darwin", "cpu": "arm64" }, "sha512-iBr3I5LZNk5/bgl5//iTgD2tcym14MX0Xo7fD//u9dYAEgGzza1y9oywluPtf74YnOswVdH1908aK9xVz7zQTw=="], - "@next/swc-darwin-x64": ["@next/swc-darwin-x64@16.2.12", "", { "os": "darwin", "cpu": "x64" }, "sha512-Hy5Ls099+aFUmOLmIgPfLqNi6iCwhL3uQCssz5rWk+5Nkc6TUKCE83DY5BbNylfm3+mfwcSFnLRfrZDJhVxdtw=="], + "@next/swc-darwin-x64": ["@next/swc-darwin-x64@16.3.4", "", { "os": "darwin", "cpu": "x64" }, "sha512-2dpiSyl2Jw/NrBPaU2MAKGSa+2MR82pJIn4Sm5Rjr+gxAeuh0z158Su3Z2O8zn7UNNq+ej4bToed6RcRN/Lydg=="], - "@next/swc-linux-arm64-gnu": ["@next/swc-linux-arm64-gnu@16.2.12", "", { "os": "linux", "cpu": "arm64" }, "sha512-+YqU2h1cQkHsGfvjAsrSmst8UIFBibBGm5x3Xgel8NLMiDQtNOM4sM2GOEMvG5YiOBNeN/Ykk8cQC2S0Xrqljg=="], + "@next/swc-linux-arm64-gnu": ["@next/swc-linux-arm64-gnu@16.3.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-+t+U8HZT+fApePCS5h89CSH3datz29MkzyfCn+6fpsZBG/oiEOhINcb9rtkv6sdpToLGFn2e6146NzaKCXkqrA=="], - "@next/swc-linux-arm64-musl": ["@next/swc-linux-arm64-musl@16.2.12", "", { "os": "linux", "cpu": "arm64" }, "sha512-0qjhiYBaKAqF63LA1ZWAAnKTzFUguAaZiRa5etMLGGPj/B6uEVjtIZldIzFEp3wHlB0koK6aTzqPtSdplTCjoA=="], + "@next/swc-linux-arm64-musl": ["@next/swc-linux-arm64-musl@16.3.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-mx03GNs1ocQA5JQ4FxDMmIsNkdrZh8cuezKCrId28e5/gIPU/l7Kcy2+vmCCzdjnnmXJy+iOAu+7K0QppO6Urg=="], - "@next/swc-linux-x64-gnu": ["@next/swc-linux-x64-gnu@16.2.12", "", { "os": "linux", "cpu": "x64" }, "sha512-7A3q26W+h7gnA15uqBToNuDqBEFZZcqh0mW2mn4AJh/G5pdg2RVE3n4slzLEliASZFG3NmsbEzng/x2Sh09mBg=="], + "@next/swc-linux-x64-gnu": ["@next/swc-linux-x64-gnu@16.3.4", "", { "os": "linux", "cpu": "x64" }, "sha512-YIhGY6fSMfha52bnVxnzc9zaVBzJg+cqQTOD8tXIBSx4fuv0pVMxQTE0PaS59YhnMOiYiG09IMwxJAf/CFm/Dw=="], - "@next/swc-linux-x64-musl": ["@next/swc-linux-x64-musl@16.2.12", "", { "os": "linux", "cpu": "x64" }, "sha512-qSjL/uppm+cbh21s72Ss8gkiOhQ4dExWHNGOWy6eZV7STj5WsKehgxT61beSsOj+YYQuTplL376lOCdMQU5T8w=="], + "@next/swc-linux-x64-musl": ["@next/swc-linux-x64-musl@16.3.4", "", { "os": "linux", "cpu": "x64" }, "sha512-+eaaX6axpDb0yF1GCpiERe6njplvdC+nks/fKfcHu3XPGRrald8P3/X7yv7QLdjA51knnxwl9pxdIJsg+w1L+Q=="], - "@next/swc-win32-arm64-msvc": ["@next/swc-win32-arm64-msvc@16.2.12", "", { "os": "win32", "cpu": "arm64" }, "sha512-X6hzsOUJac/e7AWSbn9gQ9nzHld1xWP5iyjHpYWvud8pufB679O1xg4JDyKr8Xd69Jvd+kM2Der6uftiZCmjYA=="], + "@next/swc-win32-arm64-msvc": ["@next/swc-win32-arm64-msvc@16.3.4", "", { "os": "win32", "cpu": "arm64" }, "sha512-0jcXW7Xs/uzICrmgV3MhDYDeRy++1CqnpDIerlPIqYO4bhzB4WNbX/aRnQclustsAyTkFKB0z6rbcjmNg5tR8A=="], - "@next/swc-win32-x64-msvc": ["@next/swc-win32-x64-msvc@16.2.12", "", { "os": "win32", "cpu": "x64" }, "sha512-F6fakeHuFTLOPt0bslQJdf+xtT+WIP9DVn/m4y1w1mRnVPyh3D/cNvzlRkxM444xfm+IvvYNSOrKiA2CDJ0Uxw=="], + "@next/swc-win32-x64-msvc": ["@next/swc-win32-x64-msvc@16.3.4", "", { "os": "win32", "cpu": "x64" }, "sha512-vvBzwu1pYQCp92maZCFCIw/XgOTMR5tur9GjakwIo2cmwRTMKajRZZDS9+e4KsUZWKu1E007WUeAFXRRjZeuzw=="], "@orama/orama": ["@orama/orama@3.1.18", "", {}, "sha512-a61ljmRVVyG5MC/698C8/FfFDw5a8LOIvyOLW5fztgUXqUpc1jOfQzOitSCbge657OgXXThmY3Tk8fpiDb4UcA=="], @@ -244,7 +245,7 @@ "@standard-schema/spec": ["@standard-schema/spec@1.1.0", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="], - "@swc/helpers": ["@swc/helpers@0.5.15", "", { "dependencies": { "tslib": "^2.8.0" } }, "sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g=="], + "@swc/helpers": ["@swc/helpers@0.5.23", "", { "dependencies": { "tslib": "^2.8.0" } }, "sha512-5lSsMOTXURePglDfvuAQUqkGek9Hg2kksOYay2m0+XR++b2NWYL/4sWyuvVBIs8oKnJaxkdi9whaL/sqN13afw=="], "@tailwindcss/node": ["@tailwindcss/node@4.3.3", "", { "dependencies": { "@jridgewell/remapping": "^2.3.5", "enhanced-resolve": "^5.24.1", "jiti": "^2.7.0", "lightningcss": "1.32.0", "magic-string": "^0.30.21", "source-map-js": "^1.2.1", "tailwindcss": "4.3.3" } }, "sha512-/T8IKEsf9VTU6tLjgC7+sv2mOPtQxzE2jMw7u4Tt40Tx+QSZxpzh95/H6cMKoja9XuW7iMdLJYBB0o9G1CaAgg=="], @@ -376,6 +377,8 @@ "baseline-browser-mapping": ["baseline-browser-mapping@2.11.3", "", { "bin": { "baseline-browser-mapping": "dist/cli.cjs" } }, "sha512-sbT0Ui/CZwyAyy7icT1Gw5P1LKRlFaHwaF6tDCW5YHq2X5SeeZFphBuIagopSfwSSZq3sQcbmEL072yphxm7ew=="], + "beautiful-mermaid": ["beautiful-mermaid@1.1.3", "", { "dependencies": { "elkjs": "^0.11.0", "entities": "^7.0.1" } }, "sha512-TItrtrAyHp1vwFfFVYauWGrquouk/6SS21Aq3RsxindSYZODcN4xYrPZD6BiZRU+o5mKJzDPz9MUSMvELdylyg=="], + "boxen": ["boxen@7.0.0", "", { "dependencies": { "ansi-align": "^3.0.1", "camelcase": "^7.0.0", "chalk": "^5.0.1", "cli-boxes": "^3.0.0", "string-width": "^5.1.2", "type-fest": "^2.13.0", "widest-line": "^4.0.1", "wrap-ansi": "^8.0.1" } }, "sha512-j//dBVuyacJbvW+tvZ9HuH03fZ46QcaKvvhZickZqtB271DxJ7SNRSNxrV/dZX0085m7hISRZWbzWlJvx/rHSg=="], "brace-expansion": ["brace-expansion@1.1.16", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-IDw48K2/2kRkg9LdJxurvq3lV3aBgq0REY89duEqFRthjlPdXHKMj7EnQOXVckxzgisinf3nHfrcE2FufFLXMw=="], @@ -452,11 +455,13 @@ "eastasianwidth": ["eastasianwidth@0.2.0", "", {}, "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA=="], + "elkjs": ["elkjs@0.11.1", "", {}, "sha512-zxxR9k+rx5ktMwT/FwyLdPCrq7xN6e4VGGHH8hA01vVYKjTFik7nHOxBnAYtrgYUB1RpAiLvA1/U2YraWxyKKg=="], + "emoji-regex": ["emoji-regex@9.2.2", "", {}, "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg=="], "enhanced-resolve": ["enhanced-resolve@5.24.3", "", { "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.3.3" } }, "sha512-PwKooW9JUzh5chmYfHM3IQl5OkK2u2Nm011MgeZrss3JmFraUx/fqrf78kk8GUMYoibx/14MdwTl/1WKkG7TpQ=="], - "entities": ["entities@6.0.1", "", {}, "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g=="], + "entities": ["entities@7.0.1", "", {}, "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA=="], "esast-util-from-estree": ["esast-util-from-estree@2.0.0", "", { "dependencies": { "@types/estree-jsx": "^1.0.0", "devlop": "^1.0.0", "estree-util-visit": "^2.0.0", "unist-util-position-from-estree": "^2.0.0" } }, "sha512-4CyanoAudUSBAn5K13H4JhsMH6L9ZP7XbLVe/dKybkxMO7eDyLsT8UHl9TRNrU2Gr9nz+FovfSIjuXWJ81uVwQ=="], @@ -724,7 +729,7 @@ "negotiator": ["negotiator@0.6.4", "", {}, "sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w=="], - "next": ["next@16.2.12", "", { "dependencies": { "@next/env": "16.2.12", "@swc/helpers": "0.5.15", "baseline-browser-mapping": "^2.9.19", "caniuse-lite": "^1.0.30001579", "postcss": "8.4.31", "styled-jsx": "5.1.6" }, "optionalDependencies": { "@next/swc-darwin-arm64": "16.2.12", "@next/swc-darwin-x64": "16.2.12", "@next/swc-linux-arm64-gnu": "16.2.12", "@next/swc-linux-arm64-musl": "16.2.12", "@next/swc-linux-x64-gnu": "16.2.12", "@next/swc-linux-x64-musl": "16.2.12", "@next/swc-win32-arm64-msvc": "16.2.12", "@next/swc-win32-x64-msvc": "16.2.12", "sharp": "^0.34.5" }, "peerDependencies": { "@opentelemetry/api": "^1.1.0", "@playwright/test": "^1.51.1", "babel-plugin-react-compiler": "*", "react": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "react-dom": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "sass": "^1.3.0" }, "optionalPeers": ["@opentelemetry/api", "@playwright/test", "babel-plugin-react-compiler", "sass"], "bin": { "next": "dist/bin/next" } }, "sha512-iD59eYQWmbFcEbX7v/acG5DRym9iw1DdaPoD0WTA920naWsE25wShzJW4+UvAs8MK9EC2kBfIH6vtto1H1PHGw=="], + "next": ["next@16.3.4", "", { "dependencies": { "@next/env": "16.3.4", "@swc/helpers": "0.5.23", "baseline-browser-mapping": "^2.9.19", "caniuse-lite": "^1.0.30001579", "postcss": "8.5.23", "styled-jsx": "5.1.6" }, "optionalDependencies": { "@next/swc-darwin-arm64": "16.3.4", "@next/swc-darwin-x64": "16.3.4", "@next/swc-linux-arm64-gnu": "16.3.4", "@next/swc-linux-arm64-musl": "16.3.4", "@next/swc-linux-x64-gnu": "16.3.4", "@next/swc-linux-x64-musl": "16.3.4", "@next/swc-win32-arm64-msvc": "16.3.4", "@next/swc-win32-x64-msvc": "16.3.4", "sharp": "^0.35.4" }, "peerDependencies": { "@opentelemetry/api": "^1.1.0", "@playwright/test": "^1.51.1", "babel-plugin-react-compiler": "*", "react": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "react-dom": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "sass": "^1.3.0" }, "optionalPeers": ["@opentelemetry/api", "@playwright/test", "babel-plugin-react-compiler", "sass"], "bin": { "next": "dist/bin/next" } }, "sha512-/Ztf6CeRH+ejEXUrYtqI4gkS66eFIHuSwqi60RgcpWKodxFZx2/dqVCMKBwILfAHXQ+F1b1vAudgj3mnxqtoIA=="], "next-themes": ["next-themes@0.4.6", "", { "peerDependencies": { "react": "^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc", "react-dom": "^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc" } }, "sha512-pZvgD5L0IEvX5/9GWyHMf3m8BKiVQwsCMHfoFosXtXBMnaS0ZnIJ9ST4b4NqLVKDEm8QBxoNNGNaBv2JNF6XNA=="], @@ -826,7 +831,7 @@ "serve-handler": ["serve-handler@6.1.7", "", { "dependencies": { "bytes": "3.0.0", "content-disposition": "0.5.2", "mime-types": "2.1.18", "minimatch": "3.1.5", "path-is-inside": "1.0.2", "path-to-regexp": "3.3.0", "range-parser": "1.2.0" } }, "sha512-CinAq1xWb0vR3twAv9evEU8cNWkXCb9kd5ePAHUKJBkOsUpR1wt/CvGdeca7vqumL1U5cSaeVQ6zZMxiJ3yWsg=="], - "sharp": ["sharp@0.34.5", "", { "dependencies": { "@img/colour": "^1.0.0", "detect-libc": "^2.1.2", "semver": "^7.7.3" }, "optionalDependencies": { "@img/sharp-darwin-arm64": "0.34.5", "@img/sharp-darwin-x64": "0.34.5", "@img/sharp-libvips-darwin-arm64": "1.2.4", "@img/sharp-libvips-darwin-x64": "1.2.4", "@img/sharp-libvips-linux-arm": "1.2.4", "@img/sharp-libvips-linux-arm64": "1.2.4", "@img/sharp-libvips-linux-ppc64": "1.2.4", "@img/sharp-libvips-linux-riscv64": "1.2.4", "@img/sharp-libvips-linux-s390x": "1.2.4", "@img/sharp-libvips-linux-x64": "1.2.4", "@img/sharp-libvips-linuxmusl-arm64": "1.2.4", "@img/sharp-libvips-linuxmusl-x64": "1.2.4", "@img/sharp-linux-arm": "0.34.5", "@img/sharp-linux-arm64": "0.34.5", "@img/sharp-linux-ppc64": "0.34.5", "@img/sharp-linux-riscv64": "0.34.5", "@img/sharp-linux-s390x": "0.34.5", "@img/sharp-linux-x64": "0.34.5", "@img/sharp-linuxmusl-arm64": "0.34.5", "@img/sharp-linuxmusl-x64": "0.34.5", "@img/sharp-wasm32": "0.34.5", "@img/sharp-win32-arm64": "0.34.5", "@img/sharp-win32-ia32": "0.34.5", "@img/sharp-win32-x64": "0.34.5" } }, "sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg=="], + "sharp": ["sharp@0.35.4", "", { "dependencies": { "@img/colour": "^1.1.0", "detect-libc": "^2.1.2", "semver": "^7.8.5" }, "optionalDependencies": { "@img/sharp-darwin-arm64": "0.35.4", "@img/sharp-darwin-x64": "0.35.4", "@img/sharp-freebsd-wasm32": "0.35.4", "@img/sharp-libvips-darwin-arm64": "1.3.3", "@img/sharp-libvips-darwin-x64": "1.3.3", "@img/sharp-libvips-linux-arm": "1.3.3", "@img/sharp-libvips-linux-arm64": "1.3.3", "@img/sharp-libvips-linux-ppc64": "1.3.3", "@img/sharp-libvips-linux-riscv64": "1.3.3", "@img/sharp-libvips-linux-s390x": "1.3.3", "@img/sharp-libvips-linux-x64": "1.3.3", "@img/sharp-libvips-linuxmusl-arm64": "1.3.3", "@img/sharp-libvips-linuxmusl-x64": "1.3.3", "@img/sharp-linux-arm": "0.35.4", "@img/sharp-linux-arm64": "0.35.4", "@img/sharp-linux-ppc64": "0.35.4", "@img/sharp-linux-riscv64": "0.35.4", "@img/sharp-linux-s390x": "0.35.4", "@img/sharp-linux-x64": "0.35.4", "@img/sharp-linuxmusl-arm64": "0.35.4", "@img/sharp-linuxmusl-x64": "0.35.4", "@img/sharp-webcontainers-wasm32": "0.35.4", "@img/sharp-win32-arm64": "0.35.4", "@img/sharp-win32-ia32": "0.35.4", "@img/sharp-win32-x64": "0.35.4" }, "peerDependencies": { "@types/node": "*" }, "optionalPeers": ["@types/node"] }, "sha512-n++8XWcj+jCOr2IOl7h8LbKnGBDY4aPbmprMONBNFdn0ImXqpGVv5zliDs0V9HbmbCQLpbuo2ej9rAoOQTvMDA=="], "shebang-command": ["shebang-command@2.0.0", "", { "dependencies": { "shebang-regex": "^3.0.0" } }, "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA=="], @@ -960,10 +965,10 @@ "mime-types/mime-db": ["mime-db@1.33.0", "", {}, "sha512-BHJ/EKruNIqJf/QahvxwQZXKygOQ256myeN/Ew+THcAa5q+PjyTTMMeNQC4DZw5AwfvelsUrA6B67NKMqXDbzQ=="], - "next/postcss": ["postcss@8.4.31", "", { "dependencies": { "nanoid": "^3.3.6", "picocolors": "^1.0.0", "source-map-js": "^1.0.2" } }, "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ=="], - "parse-entities/@types/unist": ["@types/unist@2.0.11", "", {}, "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA=="], + "parse5/entities": ["entities@6.0.1", "", {}, "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g=="], + "serve-handler/bytes": ["bytes@3.0.0", "", {}, "sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw=="], "yuku-ast/@yuku-toolchain/types": ["@yuku-toolchain/types@0.6.8", "", {}, "sha512-AbUd1775RVkOxJkh8hkldIWoU6kRMTCsZFSZq8Ny53q7GkbaVe5UCfleNZ3RWCoz/ZKE8qwfeB7Cj0xqhLWsKA=="], diff --git a/content/guides/write-your-own-sdk.mdx b/content/guides/write-your-own-sdk.mdx index 88a81b9..8a62974 100644 --- a/content/guides/write-your-own-sdk.mdx +++ b/content/guides/write-your-own-sdk.mdx @@ -3,10 +3,34 @@ title: Write Your Own SDK description: Learn to write your own Duckity SDK, for when an official one is not yet available. --- +{/* Code blocks with no language have a bug that cause blank lines to have height 0. To work */} +{/* around this issue, we use blank braille characters (⠀) to force the line to display. */} + This guide will guide you through writing your own SDK for Duckity in any language. We'll use examples for JavaScript and Python, taken from the official SDKs, so you can use as reference to make one in any language of your choice. +This guide intentionally explains everything in simple words and with multiple examples. You'll find +it easy to follow this guide as long as you're comfortable in the language you're using. + +The flow is simple: + +```mermaid +sequenceDiagram + participant Duckling@{ "type": "boundary" } as Duckling + participant Server@{ "type": "boundary" } as Server + participant Client + + Client ->> Duckling: Can I have a challenge? + Duckling ->> Client: Sure, have a challenge string + Client --> Client: Solve the challenge received + Client ->> Server: Here, I solved the challenge + Server ->> Duckling: Is this solution token valid? + Duckling ->> Server: Yes/no + Server --> Server: Process the rest of the request if ready, otherwise not. + Server ->> Client: Response +``` + ## Getting a Challenge [step] Duckity serves challenges through the duckling API. It's separate from the management API running on @@ -99,8 +123,8 @@ type Challenge = { These code pieces implement the decoding as a reference. - - + + ```ts lineNumbers interface Challenge { // The challenge's unique ID. @@ -126,20 +150,19 @@ These code pieces implement the decoding as a reference. * @returns The decoded challenge metadata. */ export function decode(challenge: string): Challenge { - // Counts all dots in the challenge string - if (![1, 2].includes((challenge.match(/\./g) || []).length)) { + if (challenge.match(/\./g) || []).length != 1) { throw Error("The challenge string contained too many or not enough sections."); } let [base64, _signature] = challenge.split(".", 2); - let json = atob(base64 as string); + let json = atob(base64.replaceAll("-", "+").replaceAll("_", "/") as string); let data: Challenge = JSON.parse(json as string); return data; } ``` - + ```py lineNumbers import base64, json @@ -177,7 +200,7 @@ These code pieces implement the decoding as a reference. ValueError: The challenge string was not valid. """ - if challenge.count(".") not in [2, 3]: + if challenge.count(".") != 1: raise ValueError("The challenge string contained too many or not enough parts.") raw = challenge.split(".", 1) @@ -221,20 +244,20 @@ The algoritm is simple. Start with `result = 0`, then for every digit in the arr `result = (result << 32) | digit`. The following pseudocode displays it simply: ``` -function getIntegerFromDigits(digits: number[]): +function get_integer_from_digits(digits: number[]) -> integer: result = 0 - +⠀ for digit in digits: result = (result << 32) | digit - +⠀ return result ``` Easy. Then we can use that function on both X and N. The following examples show actual code implementations of such function: - - + + ```ts lineNumbers /** * Converts an array of u32 digits to a bigint. @@ -253,7 +276,7 @@ implementations of such function: } ``` - + ```py lineNumbers # Your life will be brighter if you install `gmpy2-stubs` as a dev dependency, `gmpy2` does # not have typing stubs integrated, therefore no IDE autocompletion nor help. @@ -301,10 +324,10 @@ result of the full computation without needing to build `2^T` nor `X^2^T` nor st This step is also not parallelizable. Each iteration needs to know the result of the previous one, which leaves no advantage to GPUs. In code, this step would be - - + + ```ts lineNumbers - function solve(nDigits: number[], xDigits: number[], tNumber: number): bigint { + function solve(nDigits: number[], xDigits: number[], t: number): bigint { let n = getBigintFromDigits(nDigits); let x = getBigintFromDigits(xDigits); let y = x; @@ -317,7 +340,7 @@ which leaves no advantage to GPUs. In code, this step would be } ``` - + ```py lineNumbers import gmpy2 @@ -330,7 +353,7 @@ which leaves no advantage to GPUs. In code, this step would be for _ in range(t): y = y**2 % n - return n + return y ``` @@ -366,30 +389,30 @@ following parameters (in order): The result of hashing those parameters together will be 32 bytes, which will be our `Z` parameter. For this, we'll need a new function to convert our bigints to digits (bytes). The algorithm is -simple, the inverse of what we used for our `getBigintFromDigits` or `get_mpz_from_digits` to decode -`N` and `X` in the previous step. In pseudocode, it's +simple, the inverse of what we used for our `get_bigint_from_digits()` to decode `N` and `X` in the +previous step. In pseudocode, it's ``` -function getDigitsFromBigint(number: bigint, arrayWidth: number, digitByteWidth: number): - digits = [0] * arrayWidth - +function get_digits_from_bigint(number: bigint, array_width: number, digit_byte_width: number) -> number[]: + digits = [0] * array_width +⠀ mask = 0; - loop digitByteWidth times: + loop digit_byte_width times: mask = mask << 8 mask = mask | 0b1111_1111 - - for i in 0..arrayWidth: # [0; arrayWidth), from 0 to arrayWidth - 1 inclusive. +⠀ + for i in 0..array_width: # [0; array_width), from 0 to array_width - 1 inclusive. digit = number & mask - digits[arrayWidth - 1 - i] = digit - number = number >> (digitByteWidth * 8) - + digits[array_width - 1 - i] = digit + number = number >> (digit_byte_width * 8) +⠀ return digits ``` In TypeScript and Python, that's - - + + ```ts lineNumbers /** * Converts a bigint into a MSF digit array. @@ -409,7 +432,7 @@ In TypeScript and Python, that's mask |= 0b1111_1111n; } - for (let i = digits - 1; i >= 0; i--) { + for (let i = arrayWidth - 1; i >= 0; i--) { let digit = Number(number & mask); digits[i] = digit; number = number >> (digitByteWidth * 8); @@ -419,7 +442,7 @@ In TypeScript and Python, that's } ``` - + ```py lineNumbers import gmpy2 @@ -468,8 +491,8 @@ z = hasher.finish() Easy. The following examples show how to do such thing in TypeScript and Python. - - + + ```ts lineNumbers /** * Hashes the provided parameters to get Z. @@ -495,7 +518,7 @@ Easy. The following examples show how to do such thing in TypeScript and Python. } ``` - + ```py lineNumbers import hashlib @@ -517,10 +540,10 @@ Easy. The following examples show how to do such thing in TypeScript and Python. hash = hashlib.sha256() hash.update(b"duckity") - hash.update(get_digits_from_mpz(n, 512, 1)) - hash.update(get_digits_from_mpz(x, 512, 1)) - hash.update(get_digits_from_mpz(t, 512, 1)) - hash.update(get_digits_from_mpz(y, 512, 1)) + hash.update(bytes(get_digits_from_mpz(n, 512, 1))) + hash.update(bytes(get_digits_from_mpz(x, 512, 1))) + hash.update(bytes(get_digits_from_mpz(t, 512, 1))) + hash.update(bytes(get_digits_from_mpz(y, 512, 1))) z = hash.digest() return z @@ -540,24 +563,24 @@ using the Miller-Rabin algorithm. library instead, make sure you test on exactly 40 bases. -For this, we'll have to update our previous `getBigintFromDigits` or `get_mpz_from_digits` to allow -arbitrary digit sizes. That's simple, instead of hardcoding the digit size we'll pass it as an -argument to the function. Our pseudocode will then look like +For this, we'll have to update our previous `get_bigint_from_digits()` to allow arbitrary digit +sizes. That's simple, instead of hardcoding the digit size we'll pass it as an argument to the +function. Our pseudocode will then look like ``` -function getIntegerFromDigits(digits: number[], digit_byte_width: number): +function get_integer_from_digits(digits: number[], digit_byte_width: number): result = 0 - +⠀ for digit in digits: result = (result << (digit_byte_width * 8)) | digit - +⠀ return result ``` In TypeScript and Python, that's - - + + ```ts lineNumbers /** * Converts an array of digits to a bigint. @@ -577,7 +600,7 @@ In TypeScript and Python, that's } ``` - + ```py lineNumbers import gmpy2 @@ -606,5 +629,492 @@ Now let's interpret `Z` as a bigint. Since `Z` is currently the result of hashin together, each byte will be a single-byte digit. ``` -z = getBigintFromDigits(z, 1) +z = get_bigint_from_digits(z, 1) ``` + +To get `L`, we need to find the next prime to our `Z`. E.g. if `Z = 5`, `L = 7`. Note how 5 is +already a prime, yet we get the next one regardless. + +Getting the next prime is simple. Starting with `Z + 1`, we call 40 Miller-Rabin tests to check +whether it's prime. Many libraries out there already implement Miller-Rabin tests so you don't have +to implement it yourself. In case you don't want to, or cannot, use such libraries, we'll explain +how to implement it manually here. + + + The implementation described below is good enough for the values handled by the API. It is not + meant to be general purpose beyond this SDK's implementation, and edge cases that do not apply + to the calculations needed may be ignored. The Duckling server uses GMP's `next_prime()` + function instead of a manual implementation. + + +Our implementation will be divided into 4 functions: `is_prime(number, amount_of_bases)`, +`is_prime_for_base(number, base)`, `get_next_prime(number, amount_of_bases)`, and +`mod_pow(base, exponent, modulus)`. + +#### `mod_pow` [step] + +This function will return the result of `(A^B) % C` without computing `A^B`. This way, we do not +have to store numbers too big in memory. The algorithm is simple: + +``` +function mod_pow(base: bigint, exponent: bigint, modulus: bigint) -> bigint: + result = 1 +⠀ + while exponent != 0: + if (exponent & 1) == 1: + result = result * base + result = result % modulus +⠀ + base = base * base + base = base % modulus +⠀ + exponent = exponent / 2 +⠀ + return result +``` + +In TypeScript, this equals to + + + + ```ts lineNumbers + /** + * Computes `(base ^ exponent) % modulus` efficiently, without storing `base ^ exponent` in + * memory. + * + * @param base The base of the modular exponentiation. + * @param exponent The exponent of the modular exponentiation. + * @param modulus The modulus of the modular exponentiation. + * + * @returns The result of the modular exponentiation as a bigint. + */ + function modPow(base: bigint, exponent: bigint, modulus: bigint): bigint { + let result = 1n; + + while (exponent !== 0n) { + // (something & 1n) is faster than (something % 2n) + if ((exponent & 1n) === 1n) { + result *= base; + result %= modulus; + } + + base **= 2n; + base %= modulus; + + exponent /= 2n; + } + + return result; + } + ``` + + + + +#### `isPrimeForBase` [step] + +This function will test a number using Miller-Rabin for the specified base. By later testing with +different bases in `is_prime()`, we increase the likelyhood of a number being prime. The test works +by exponentially reducing the chances of a number being a prime until it is extremely unlikely it's +not a prime, without checking whether the number is divisible by other numbers one by one. The +algorithm is simple: + +``` +function is_prime_for_base(number, base) -> boolean: + number_minus_one = number - 1 + odd = number_minus_one + base_times = 0 +⠀ + while odd % 2 == 0: + odd = odd / 2 + base_times = base_times + 1 +⠀ + odd_power = mod_pow(base, odd, number) +⠀ + if odd_power == 1 or odd_power == number_minus_one: + return true +⠀ + loop base_times - 1 times: + odd_power = (odd_power * odd_power) % number +⠀ + if odd_power == number_minus_one: + return true +⠀ + return false +``` + +In TypeScript, this can be implemented as + + + + ```ts lineNumbers + /** + * Runs a single Miller-Rabin test on a number for a specific base. + * + * This function assumes basic checks have been run on the number, like division by two and + * by five. + * + * @param number The number to check if is prime. + * @param base The base to use to check if the number is prime. + * + * @returns If the primality test yielded positive for the specified base. + */ + function isPrimeForBase(number: bigint, base: bigint): boolean { + let numberMinusOne = number - 1n; + + let odd = numberMinusOne; + let baseTimes = 0; + + while (odd % 2n == 0n) { + odd /= 2n; + baseTimes += 1; + } + + let oddPower = modPow(base, odd, number); + + if (oddPower == 1n || oddPower == numberMinusOne) { + return true; + } + + for (let i = 0n; i < baseTimes - 1n; i++) { + oddPower = modPow(oddPower, 2, number); + + if (oddPower == numberMinusOne) { + return true; + } + } + + return false; + } + ``` + + + + +#### `is_prime` [step] + +Checking whether a number is likely a prime is simple. We'll discard numbers that pass basic tests +like division by 2 and by five, then we'll call `is_prime_for_base()` in loop starting in 2 +increasing the loop by 1 after every iteration. All numbers between 2 and `number - 1` inclusive are +valid tests. In essence, + +``` +function is_prime(number, amount_of_tests) -> boolean: + if number in [2, 3, 5, 7]: + return true +⠀ + if number % 2 == 0 or number % 5 == 0: + return false +⠀ + base = 2 +⠀ + loop amount_of_tests times: + if base >= number: + break +⠀ + if not is_prime_for_base(number, base): // Any test returning false means the number is certainly not a prime. + return false +⠀ + return true +``` + +In TypeScript, that is + + + + ```ts lineNumbers + /** + * Runs up to `tests` amount of tests to check whether a number is a prime. + * + * @param number The number to check for primality. + * @param tests The maximum amount of tests to run on the number. Setting this value to a + * higher number means better precision at the cost of speed. + * + * @returns `true` if the number is likely a prime, `false` if the number is not a prime. + */ + function isPrime(number: bigint, tests: number): boolean { + if ([2n, 3n, 5n, 7n].includes(number)) { + return true; + } + + // (something & 1n) is faster than (something % 2n) + if ((number & 1n) === 0n) { + return false; + } + + if (number % 5n === 0n) { + return false; + } + + let base = 2n; + + for (let i = 0n; i < BigInt(tests) && base + i < number; i++) { + let prime = isPrimeForBase(number, base + i); + + if (!prime) { + return false; + } + } + + return true; + } + ``` + + + + +#### `get_next_prime` [step] + +Last but not least, let's call our `is_prime` in a loop starting from `number + 1` until we find a +prime. We'll set the amount of tests to 40. + +This algorithm is the most simple of all the `L`-getting flow: + +``` +loop indefinitely: + number = number + 1 +⠀ + if is_prime(number, 40): + return number +``` + +Note that the `number = number + 1` goes _before_ the `is_prime()` call. We don't want the initial +`number` parameter to be returned if it's prime. + +In TypeScript, that's + + + + ```ts lineNumbers + /** + * Returns the first prime after a number. + * + * Numbers are checked for primality using Miller-Rabin tests on 40 bases each number. + * + * @param number The number to get the following prime for. + * + * @returns The number's following prime. + */ + function getNextPrime(number: bigint): bigint { + while (true) { + number += 1n; + + if (isPrime(number, 40)) { + return number; + } + } + } + ``` + + + + +Now that we can get our next prime, it's finally time to get `L`. + +``` +l = get_next_prime(z) +``` + +That's it! In TypeScript and Python, that's + + + + ```ts lineNumbers + let l = getNextPrime(z); + ``` + + + ```py lineNumbers + import gmpy2 + + l = gmpy2.next_prime(z) + ``` + + + + +### Proving the Solution (PI) [step] + +Once we have `L`, our last step towards getting everything we need to get a solution token is +getting `PI`. We want a parameter `Q` which comes from `2^T = Q * L + R`. Then for `PI`, we use +`PI = X^Q % N`. Not to compute `2^T` directly, we'll use a short algorithm to compute `PI` +progressively: + +``` +r = 1 +pi = 1 +⠀ +loop t times: + r = r * 2 +⠀ + if r >= l: + r = r - l + pi = (pi * pi * x) % n + else: + pi = (pi * pi) % n +``` + +This way, we get our solution `PI` without filling up our memory with `2^T`. In Python and +TypeScript, this is + + + + ```ts lineNumbers + let r = 1n; + let pi = 1n; + + for (let i = 0n; i < t; i++) { + r = r * 2n; + + if (r >= l) { + r = r - l; + pi = (pi * pi * x) % n; + } else { + pi = (pi * pi) % n; + } + } + ``` + + + ```py lineNumbers + import gmpy2 + + r = gmpy2.mpz(1) + pi = gmpy2.mpz(1) + + for i in range(t): + r = r * 2 + + if r >= l: + r = r - l + pi = (pi * pi * x) % n + else: + pi = (pi * pi) % n + ``` + + + + +Once that's done, we'll have our final `PI` value. + +## Encoding the Solution [step] + +Once we have our `Y` and `PI` values, it's time to encode them into a solution token. As stated at +the beginning of this guide, challenge tokens are encoded as + +``` +. +``` + +We'll now encode our solution into a third segment to have a token that looks like follows: + +``` +.. +``` + +Encoding the solution data segment is the inverse of decodind the challenge data segment. We'll +convert our `PI` and `Y` values to MSF u32 digit arrays of 128 digits using our +`get_digits_from_bigint()`, then encode that as JSON and the JSON as URL-safe Base64 with no +padding. The encoded JSON will have the following schema: + +```ts +type Solution = { + pi: number[]; + y: number[]; +}; +``` + +In TypeScript and Python, that's done as follows: + + + + ```ts lineNumbers + export function encode(original: string, y: bigint, pi: bigint): string { + let solution = { + y: getDigitsFromBigint(y, 128, 4), + pi: getDigitsFromBigint(pi, 128, 4), + }; + + let solutionJson = JSON.stringify(solution); + let solutionBase64 = btoa(solutionJson) + .replaceAll("=", "") + .replaceAll("+", "-") + .replaceAll("/", "_"); + + return `${original}.${solutionBase64}`; + } + ``` + + + ```py lineNumbers + import json + + import base64 + + import gmpy2 + + def encode(original: str, y: gmpy2.mpz, pi: gmpy2.mpz) -> str: + y_digits = get_digits_from_mpz(y, 128, 4) + pi_digits = get_digits_from_mpz(pi, 128, 4) + + solution = {"y": y_digits, "pi": pi_digits} + solution = json.dumps(solution) + solution = base64.urlsafe_b64encode(solution.encode("utf-8")).decode() + solution = solution.replace("=", "") + + return f"{original}.{solution}" + ``` + + + + +That's it! Once you have your solution token encoded, you're ready to send it to the server for +validation. + +## Validating the Solution [step] + +Validating the solution is done server-side. Usually, the solution token is posted to the +application's backend server via a REST API and the server validates the challenge. + +Sending the token from the client to the server is application-specific and not covered by SDKs. +Once the server has received the token, the first thing the server should do is to send a request to +the Duckling API to check whether the token is valid. + +The request looks like follows: + +```http +POST /d1/{protection_profile_id}/validate HTTP/1.1 +Host: api.duckity.com +Authorization: Bearer +Accept: application/json +Content-Type: application/json + +{ + "solution": "", + "ip": "123.123.123.123" +} +``` + +The `"solution"` key in the request's body will be the solution submitted by the client. The `"ip"` +will be the IP of the client that submitted the solution token. + + + In case the server for any reason does not have a way of getting the client's IP, it can use the + `"ip"` key in the challenge's encoded data. Note that this is NOT recommended as it will allow + clients to submit solution tokens issued for different clients. The server must use the client's + IP whenever possible. + + +The duckling API's response will look like follows: + +```http +HTTP/1.1 200 OK +Content-Type: application/json + +{ + "is_valid": true +} +``` + +That's it! Your SDK has its core implementation done, now it's time to write documentation for it, +error handling where due, and anything specific to the platform you have developed the SDK for. diff --git a/content/sdks/javascript.mdx b/content/sdks/javascript.mdx index ff6a978..da385ef 100644 --- a/content/sdks/javascript.mdx +++ b/content/sdks/javascript.mdx @@ -45,7 +45,7 @@ Follow these steps depending on your application: Then import it in your code: - ```ts twoslash + ```ts import duckity from "@duckity/js"; ``` @@ -75,7 +75,7 @@ Follow these steps depending on your application: Once you have the SDK installed, you can request a challenge whenever you need it using `duckity.solve()`. -```ts twoslash +```ts const PROTECTION_PROFILE_ID: string = ""; // ---cut--- import duckity from "@duckity/js"; @@ -100,19 +100,12 @@ settings), issue the challenge as soon as possible. Note, however, that the chal To pass threat correlation keys when issuing a challenge, set them in the `options` argument of `duckity.solve()`. -```ts twoslash +```ts const PROTECTION_PROFILE_ID: string = "py83YHkXV6ZpIsJZGVxzS"; // ---cut--- import duckity from "@duckity/js"; -let solution = await duckity.solve( - PROTECTION_PROFILE_ID, - { - keys: { - email: "john@duckity.com", - } - } -); +let solution = await duckity.solve(PROTECTION_PROFILE_ID); ``` ### Using On Self-Hosted Ducklings @@ -120,7 +113,7 @@ let solution = await duckity.solve( Self-hosted ducklings are hosted at a different domain from Duckity-hosted ducklings. To point it to a custom domain, change the following setting: -```ts twoslash +```ts const PROTECTION_PROFILE_ID: string = "py83YHkXV6ZpIsJZGVxzS"; // ---cut--- import duckity from "@duckity/js"; diff --git a/next.config.mjs b/next.config.mjs index 2947366..0682f30 100644 --- a/next.config.mjs +++ b/next.config.mjs @@ -5,11 +5,11 @@ const withMDX = createMDX(); /** @type {import('next').NextConfig} */ const config = { serverExternalPackages: ["@takumi-rs/core", "typescript", "twoslash"], - output: "export", + // output: "export", reactStrictMode: true, allowedDevOrigins: ["duckity.com", "duckity.dev"], basePath: "/docs", - trailingSlash: true, + // trailingSlash: true, images: { unoptimized: true, }, diff --git a/package.json b/package.json index 88f2f01..47e43f0 100644 --- a/package.json +++ b/package.json @@ -19,6 +19,7 @@ "@orama/orama": "^3.1.18", "@typescript/ata": "^0.9.8", "@typescript/vfs": "^1.6.4", + "beautiful-mermaid": "^1.1.3", "class-variance-authority": "^0.7.1", "cnfast": "^0.0.8", "fumadocs-core": "16.12.1", @@ -26,7 +27,8 @@ "fumadocs-twoslash": "^3.3.0", "fumadocs-ui": "npm:@fumadocs/base-ui@16.12.1", "lucide-react": "^1.3.0", - "next": "^16.2.12", + "next": "^16.3.4", + "next-themes": "^0.4.6", "react": "^19.2.8", "react-dom": "^19.2.8", "takumi-js": "^2.4.0", diff --git a/source.config.ts b/source.config.ts index e3b96da..cf83815 100644 --- a/source.config.ts +++ b/source.config.ts @@ -1,11 +1,8 @@ -import ts from "typescript"; -import { setupTypeAcquisition } from "@typescript/ata"; import { defineConfig, defineDocs } from "fumadocs-mdx/config"; import { metaSchema, pageSchema } from "fumadocs-core/source/schema"; import { remarkSteps } from "fumadocs-core/mdx-plugins/remark-steps"; -import { transformerTwoslash } from "fumadocs-twoslash"; +import { remarkMdxMermaid } from 'fumadocs-core/mdx-plugins'; import { rehypeCodeDefaultOptions } from "fumadocs-core/mdx-plugins"; -import { createFileSystemTypesCache } from "fumadocs-twoslash/cache-fs"; // You can customize Zod schemas for frontmatter and `meta.json` here // see https://fumadocs.dev/docs/mdx/collections @@ -22,34 +19,9 @@ export const docs = defineDocs({ }, }); -// Allow importing @duckity/js from twoslash via `import { Duckity } from "@duckity/js"`. -// -// For this, we need to create a virtual file system that includes the @duckity/js package from node_modules. -const sharedFsMap = new Map(); - -const imports = ` - import duckity from "@duckity/js"; -`; - -const ata = setupTypeAcquisition({ - projectName: "twoslash-cdn", - typescript: ts, - logger: console, - delegate: { - receivedFile: (code, path) => { - // ATA paths look like "/node_modules/@types/lodash/index.d.ts" - // Strip the leading slash to make it a valid path for the virtual file system - - sharedFsMap.set(path, code); - }, - }, -}); - -await ata(imports); - export default defineConfig({ mdxOptions: { - remarkPlugins: [remarkSteps], + remarkPlugins: [remarkSteps, remarkMdxMermaid], rehypeCodeOptions: { themes: { light: "github-light", @@ -57,19 +29,6 @@ export default defineConfig({ }, transformers: [ ...(rehypeCodeDefaultOptions.transformers ?? []), - transformerTwoslash({ - typesCache: createFileSystemTypesCache({}), - twoslashOptions: { - fsMap: sharedFsMap, - compilerOptions: { - moduleResolution: ts.ModuleResolutionKind.NodeNext, - target: ts.ScriptTarget.ESNext, - module: ts.ModuleKind.ESNext, - esModuleInterop: true, - allowJs: true, - }, - }, - }), ], // important: Shiki doesn't support lazy loading languages for codeblocks in Twoslash popups diff --git a/src/app/(docs)/[[...slug]]/page.tsx b/src/app/(docs)/[[...slug]]/page.tsx index 9a13436..054ef9a 100644 --- a/src/app/(docs)/[[...slug]]/page.tsx +++ b/src/app/(docs)/[[...slug]]/page.tsx @@ -46,7 +46,7 @@ export default async function Page(props: PageProps<"/[[...slug]]">) { ); } -export async function generateStaticParams() { +export async function generateStaticParams(any?: any) { return source.generateParams(); } diff --git a/src/app/llms.mdx/docs/[[...slug]]/route.ts b/src/app/llms.mdx/docs/[[...slug]]/route.ts index 012e877..2289584 100644 --- a/src/app/llms.mdx/docs/[[...slug]]/route.ts +++ b/src/app/llms.mdx/docs/[[...slug]]/route.ts @@ -16,7 +16,7 @@ export async function GET(_req: Request, { params }: RouteContext<'/llms.mdx/doc }); } -export function generateStaticParams() { +export function generateStaticParams(any?: any) { return source.getPages().map((page) => ({ slug: getPageMarkdownUrl(page).segments, })); diff --git a/src/components/mdx.tsx b/src/components/mdx.tsx index 6f145de..66eac15 100644 --- a/src/components/mdx.tsx +++ b/src/components/mdx.tsx @@ -1,6 +1,7 @@ import defaultMdxComponents from "fumadocs-ui/mdx"; import * as TabsComponents from 'fumadocs-ui/components/tabs'; import Image from "next/image"; +import { Mermaid } from '@/components/mermaid'; import type { MDXComponents } from "mdx/types"; import * as Twoslash from 'fumadocs-twoslash/ui'; @@ -10,6 +11,7 @@ export function getMDXComponents(components?: MDXComponents) { img: (props) => , ...TabsComponents, ...Twoslash, + Mermaid, ...components, } satisfies MDXComponents; } diff --git a/src/components/mermaid.tsx b/src/components/mermaid.tsx new file mode 100644 index 0000000..fae07b9 --- /dev/null +++ b/src/components/mermaid.tsx @@ -0,0 +1,21 @@ +import { CodeBlock, Pre } from 'fumadocs-ui/components/codeblock'; +import { renderMermaidSVG } from 'beautiful-mermaid'; + +export async function Mermaid({ chart }: { chart: string }) { + try { + const svg = renderMermaidSVG(chart, { + bg: 'var(--color-fd-background)', + fg: 'var(--color-fd-foreground)', + interactive: true, + transparent: true, + }); + + return
; + } catch { + return ( + +
{chart}
+
+ ); + } +} \ No newline at end of file