Added write your own SDK guide

This commit is contained in:
2026-09-02 02:22:21 -03:00
parent 0a7513d986
commit 8baf893a9e
10 changed files with 647 additions and 155 deletions
+50 -45
View File
@@ -12,6 +12,7 @@
"@orama/orama": "^3.1.18", "@orama/orama": "^3.1.18",
"@typescript/ata": "^0.9.8", "@typescript/ata": "^0.9.8",
"@typescript/vfs": "^1.6.4", "@typescript/vfs": "^1.6.4",
"beautiful-mermaid": "^1.1.3",
"class-variance-authority": "^0.7.1", "class-variance-authority": "^0.7.1",
"cnfast": "^0.0.8", "cnfast": "^0.0.8",
"fumadocs-core": "16.12.1", "fumadocs-core": "16.12.1",
@@ -19,7 +20,8 @@
"fumadocs-twoslash": "^3.3.0", "fumadocs-twoslash": "^3.3.0",
"fumadocs-ui": "npm:@fumadocs/[email protected]", "fumadocs-ui": "npm:@fumadocs/[email protected]",
"lucide-react": "^1.3.0", "lucide-react": "^1.3.0",
"next": "^16.2.12", "next": "^16.3.4",
"next-themes": "^0.4.6",
"react": "^19.2.8", "react": "^19.2.8",
"react-dom": "^19.2.8", "react-dom": "^19.2.8",
"takumi-js": "^2.4.0", "takumi-js": "^2.4.0",
@@ -27,7 +29,6 @@
}, },
"devDependencies": { "devDependencies": {
"@biomejs/biome": "^2.5.5", "@biomejs/biome": "^2.5.5",
"@duckity/js": "^0.1.4",
"@tailwindcss/postcss": "^4.3.3", "@tailwindcss/postcss": "^4.3.3",
"@types/mdx": "^2.0.14", "@types/mdx": "^2.0.14",
"@types/node": "^26.1.1", "@types/node": "^26.1.1",
@@ -68,10 +69,6 @@
"@biomejs/cli-win32-x64": ["@biomejs/[email protected]", "", { "os": "win32", "cpu": "x64" }, "sha512-bIBFo+n6MIxdNcVFy5CrurbKiZQiUciK3bt8+O9I4wjFZNTfXLpi+giq47522eXqW5NBc9ulx7dR1SlZKi2J5g=="], "@biomejs/cli-win32-x64": ["@biomejs/[email protected]", "", { "os": "win32", "cpu": "x64" }, "sha512-bIBFo+n6MIxdNcVFy5CrurbKiZQiUciK3bt8+O9I4wjFZNTfXLpi+giq47522eXqW5NBc9ulx7dR1SlZKi2J5g=="],
"@duckity/js": ["@duckity/[email protected]", "", { "dependencies": { "@duckity/wasm": "1.1.16" }, "peerDependencies": { "typescript": "^5" } }, "sha512-zjzV2vpKdrFwt954x5thbocTAflrNUVj7yTqMUC8E0ICAbJ9YP30arZvGZqTE98+vemxOIb9hH4SWja7BXH37w=="],
"@duckity/wasm": ["@duckity/[email protected]", "", {}, "sha512-P0TDKQ187S68D3ri2ZW9m60gCnUj+/iDLgwWwJLAbfiJhxEFfsCb7tJDgaQQR9mJ2PRETuBHc4TjbLphSuAjGg=="],
"@emnapi/runtime": ["@emnapi/[email protected]", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-Xz4Tpyki7XyrpbUK1jR1AhdAdaXyhhY4lZ3neLodmhpuWfy2PAQN5B46sAiU4liOXGLkHypn/qU+jvfWSCYYLA=="], "@emnapi/runtime": ["@emnapi/[email protected]", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-Xz4Tpyki7XyrpbUK1jR1AhdAdaXyhhY4lZ3neLodmhpuWfy2PAQN5B46sAiU4liOXGLkHypn/qU+jvfWSCYYLA=="],
"@esbuild/aix-ppc64": ["@esbuild/[email protected]", "", { "os": "aix", "cpu": "ppc64" }, "sha512-Svl7tq8k/08+p6CXPpRjQ1fKX+1odH/BQbb48fV6fj3CWHhsoIOoY87w1oHXm0qEpkIK3ZfVgp0hed3XBXzXMQ=="], "@esbuild/aix-ppc64": ["@esbuild/[email protected]", "", { "os": "aix", "cpu": "ppc64" }, "sha512-Svl7tq8k/08+p6CXPpRjQ1fKX+1odH/BQbb48fV6fj3CWHhsoIOoY87w1oHXm0qEpkIK3ZfVgp0hed3XBXzXMQ=="],
@@ -144,53 +141,57 @@
"@img/colour": ["@img/[email protected]", "", {}, "sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ=="], "@img/colour": ["@img/[email protected]", "", {}, "sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ=="],
"@img/sharp-darwin-arm64": ["@img/[email protected]4.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/[email protected]5.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/[email protected]4.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/[email protected]5.4", "", { "optionalDependencies": { "@img/sharp-libvips-darwin-x64": "1.3.3" }, "os": "darwin", "cpu": "x64" }, "sha512-hWniXY3bG5qKpkKrAwPe4y+VTPmf086YQAnkxWh7uA1YrlRouWGa0M0Mxj3ZjnXFkv7/TD1bTy9lGUK26vRvWw=="],
"@img/sharp-libvips-darwin-arm64": ["@img/[email protected]", "", { "os": "darwin", "cpu": "arm64" }, "sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g=="], "@img/sharp-freebsd-wasm32": ["@img/[email protected]", "", { "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-[email protected]", "", { "os": "linux", "cpu": "arm" }, "sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A=="], "@img/sharp-libvips-darwin-x64": ["@img/sharp-libvips-[email protected]", "", { "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-[email protected]", "", { "os": "linux", "cpu": "s390x" }, "sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ=="], "@img/sharp-libvips-linux-riscv64": ["@img/sharp-libvips-linux-[email protected]", "", { "os": "linux", "cpu": "none" }, "sha512-HjPVx7yKz+0lqdhDlTw1tt90wamBoxhiXpvl1XZpJLiHH4RCJ5yDTqH+VlYPv2fwFs89JFw4c1IexYOcQUi4IQ=="],
"@img/sharp-libvips-linux-x64": ["@img/sharp-libvips-linux-[email protected]", "", { "os": "linux", "cpu": "x64" }, "sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw=="], "@img/sharp-libvips-linux-s390x": ["@img/sharp-libvips-linux-[email protected]", "", { "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/[email protected]", "", { "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-linux[email protected]", "", { "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/[email protected]5.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-ppc[email protected]4.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-ppc64": "1.2.4" }, "os": "linux", "cpu": "ppc64" }, "sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA=="], "@img/sharp-linux-arm64": ["@img/sharp-linux-arm[email protected]5.4", "", { "optionalDependencies": { "@img/sharp-libvips-linux-arm64": "1.3.3" }, "os": "linux", "cpu": "arm64" }, "sha512-De4jpEnAU8Hd5oT0j1G3uL4ZvTuipVMn7YC6vPaJhy6/7EwEae0SVAoBrUMYQbkLGDm85taVWwuPc1a44LTzCQ=="],
"@img/sharp-linux-riscv64": ["@img/sharp-linux-riscv[email protected]4.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-ppc[email protected]5.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-arm[email protected]4.5", "", { "optionalDependencies": { "@img/sharp-libvips-linuxmusl-arm64": "1.2.4" }, "os": "linux", "cpu": "arm64" }, "sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg=="], "@img/sharp-linux-x64": ["@img/sharp-linux-x[email protected]5.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-x[email protected]4.5", "", { "optionalDependencies": { "@img/sharp-libvips-linuxmusl-x64": "1.2.4" }, "os": "linux", "cpu": "x64" }, "sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q=="], "@img/sharp-linuxmusl-arm64": ["@img/sharp-linuxmusl-arm[email protected]5.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-ia[email protected]4.5", "", { "os": "win32", "cpu": "ia32" }, "sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg=="], "@img/sharp-webcontainers-wasm32": ["@img/sharp-webcontainers-wasm[email protected]5.4", "", { "dependencies": { "@img/sharp-wasm32": "0.35.4" }, "cpu": "none" }, "sha512-ESfNkywmCfPNyaZjxooddJQiQ+l/nTpGEOGthxiLnIHXC/CmcBixnfwUleX9mCz9ovrUUvKMap/pm8RYbzfwaA=="],
"@img/sharp-win32-x64": ["@img/sharp-win32-x[email protected]4.5", "", { "os": "win32", "cpu": "x64" }, "sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw=="], "@img/sharp-win32-arm64": ["@img/sharp-win32-arm[email protected]5.4", "", { "os": "win32", "cpu": "arm64" }, "sha512-iNdlBX9gLVvqe2I3uIJSIKTq6wckP/DYxZtcqxm09x5Gi24DnFBmPAWZmr60ZyYMG0xlzo6goG3670ar+RXvRw=="],
"@img/sharp-win32-ia32": ["@img/[email protected]", "", { "os": "win32", "cpu": "ia32" }, "sha512-kqRsbaa5CS6KHlpxnN7WhE6vAAugXyZButpRdvDWetlv6Qv4N9WTcrWzF7tXfB9T7MsoadqdI8hmwLq6UlLvtw=="],
"@img/sharp-win32-x64": ["@img/[email protected]", "", { "os": "win32", "cpu": "x64" }, "sha512-XtmnYhBcrORsJ4XJngyzr/EWP0hRZLAZRFaApdKuviyqF78+ylxh2y06ZmtULAMOnObJ3ucpN0AcwSWnMowTRg=="],
"@jridgewell/gen-mapping": ["@jridgewell/[email protected]", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA=="], "@jridgewell/gen-mapping": ["@jridgewell/[email protected]", "", { "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/[email protected]", "", { "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=="], "@mdx-js/mdx": ["@mdx-js/[email protected]", "", { "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/[email protected]", "", {}, "sha512-a61ljmRVVyG5MC/698C8/FfFDw5a8LOIvyOLW5fztgUXqUpc1jOfQzOitSCbge657OgXXThmY3Tk8fpiDb4UcA=="], "@orama/orama": ["@orama/[email protected]", "", {}, "sha512-a61ljmRVVyG5MC/698C8/FfFDw5a8LOIvyOLW5fztgUXqUpc1jOfQzOitSCbge657OgXXThmY3Tk8fpiDb4UcA=="],
@@ -244,7 +245,7 @@
"@standard-schema/spec": ["@standard-schema/[email protected]", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="], "@standard-schema/spec": ["@standard-schema/[email protected]", "", {}, "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w=="],
"@swc/helpers": ["@swc/[email protected].15", "", { "dependencies": { "tslib": "^2.8.0" } }, "sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g=="], "@swc/helpers": ["@swc/[email protected].23", "", { "dependencies": { "tslib": "^2.8.0" } }, "sha512-5lSsMOTXURePglDfvuAQUqkGek9Hg2kksOYay2m0+XR++b2NWYL/4sWyuvVBIs8oKnJaxkdi9whaL/sqN13afw=="],
"@tailwindcss/node": ["@tailwindcss/[email protected]", "", { "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=="], "@tailwindcss/node": ["@tailwindcss/[email protected]", "", { "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": ["[email protected]", "", { "bin": { "baseline-browser-mapping": "dist/cli.cjs" } }, "sha512-sbT0Ui/CZwyAyy7icT1Gw5P1LKRlFaHwaF6tDCW5YHq2X5SeeZFphBuIagopSfwSSZq3sQcbmEL072yphxm7ew=="], "baseline-browser-mapping": ["[email protected]", "", { "bin": { "baseline-browser-mapping": "dist/cli.cjs" } }, "sha512-sbT0Ui/CZwyAyy7icT1Gw5P1LKRlFaHwaF6tDCW5YHq2X5SeeZFphBuIagopSfwSSZq3sQcbmEL072yphxm7ew=="],
"beautiful-mermaid": ["[email protected]", "", { "dependencies": { "elkjs": "^0.11.0", "entities": "^7.0.1" } }, "sha512-TItrtrAyHp1vwFfFVYauWGrquouk/6SS21Aq3RsxindSYZODcN4xYrPZD6BiZRU+o5mKJzDPz9MUSMvELdylyg=="],
"boxen": ["[email protected]", "", { "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=="], "boxen": ["[email protected]", "", { "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": ["[email protected]", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-IDw48K2/2kRkg9LdJxurvq3lV3aBgq0REY89duEqFRthjlPdXHKMj7EnQOXVckxzgisinf3nHfrcE2FufFLXMw=="], "brace-expansion": ["[email protected]", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-IDw48K2/2kRkg9LdJxurvq3lV3aBgq0REY89duEqFRthjlPdXHKMj7EnQOXVckxzgisinf3nHfrcE2FufFLXMw=="],
@@ -452,11 +455,13 @@
"eastasianwidth": ["[email protected]", "", {}, "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA=="], "eastasianwidth": ["[email protected]", "", {}, "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA=="],
"elkjs": ["[email protected]", "", {}, "sha512-zxxR9k+rx5ktMwT/FwyLdPCrq7xN6e4VGGHH8hA01vVYKjTFik7nHOxBnAYtrgYUB1RpAiLvA1/U2YraWxyKKg=="],
"emoji-regex": ["[email protected]", "", {}, "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg=="], "emoji-regex": ["[email protected]", "", {}, "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg=="],
"enhanced-resolve": ["[email protected]", "", { "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.3.3" } }, "sha512-PwKooW9JUzh5chmYfHM3IQl5OkK2u2Nm011MgeZrss3JmFraUx/fqrf78kk8GUMYoibx/14MdwTl/1WKkG7TpQ=="], "enhanced-resolve": ["[email protected]", "", { "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": ["[email protected]", "", { "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=="], "esast-util-from-estree": ["[email protected]", "", { "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": ["[email protected]", "", {}, "sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w=="], "negotiator": ["[email protected]", "", {}, "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": ["[email protected]", "", { "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=="], "next-themes": ["[email protected]", "", { "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": ["[email protected]", "", { "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=="], "serve-handler": ["[email protected]", "", { "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": ["[email protected]4.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": ["[email protected]5.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": ["[email protected]", "", { "dependencies": { "shebang-regex": "^3.0.0" } }, "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA=="], "shebang-command": ["[email protected]", "", { "dependencies": { "shebang-regex": "^3.0.0" } }, "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA=="],
@@ -960,10 +965,10 @@
"mime-types/mime-db": ["[email protected]", "", {}, "sha512-BHJ/EKruNIqJf/QahvxwQZXKygOQ256myeN/Ew+THcAa5q+PjyTTMMeNQC4DZw5AwfvelsUrA6B67NKMqXDbzQ=="], "mime-types/mime-db": ["[email protected]", "", {}, "sha512-BHJ/EKruNIqJf/QahvxwQZXKygOQ256myeN/Ew+THcAa5q+PjyTTMMeNQC4DZw5AwfvelsUrA6B67NKMqXDbzQ=="],
"next/postcss": ["[email protected]", "", { "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/[email protected]", "", {}, "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA=="], "parse-entities/@types/unist": ["@types/[email protected]", "", {}, "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA=="],
"parse5/entities": ["[email protected]", "", {}, "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g=="],
"serve-handler/bytes": ["[email protected]", "", {}, "sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw=="], "serve-handler/bytes": ["[email protected]", "", {}, "sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw=="],
"yuku-ast/@yuku-toolchain/types": ["@yuku-toolchain/[email protected]", "", {}, "sha512-AbUd1775RVkOxJkh8hkldIWoU6kRMTCsZFSZq8Ny53q7GkbaVe5UCfleNZ3RWCoz/ZKE8qwfeB7Cj0xqhLWsKA=="], "yuku-ast/@yuku-toolchain/types": ["@yuku-toolchain/[email protected]", "", {}, "sha512-AbUd1775RVkOxJkh8hkldIWoU6kRMTCsZFSZq8Ny53q7GkbaVe5UCfleNZ3RWCoz/ZKE8qwfeB7Cj0xqhLWsKA=="],
+560 -50
View File
@@ -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. 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 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 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. 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] ## Getting a Challenge [step]
Duckity serves challenges through the duckling API. It's separate from the management API running on 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. These code pieces implement the decoding as a reference.
<Tabs items={["TypeScript", "Python"]}> <Tabs items={["TypeScript", "Python"]} groupId="reference-language" persist>
<Tab value="TypeScript"> <Tab value="TypeScript" id="reference-language-ts">
```ts lineNumbers ```ts lineNumbers
interface Challenge { interface Challenge {
// The challenge's unique ID. // The challenge's unique ID.
@@ -126,20 +150,19 @@ These code pieces implement the decoding as a reference.
* @returns The decoded challenge metadata. * @returns The decoded challenge metadata.
*/ */
export function decode(challenge: string): Challenge { export function decode(challenge: string): Challenge {
// Counts all dots in the challenge string if (challenge.match(/\./g) || []).length != 1) {
if (![1, 2].includes((challenge.match(/\./g) || []).length)) {
throw Error("The challenge string contained too many or not enough sections."); throw Error("The challenge string contained too many or not enough sections.");
} }
let [base64, _signature] = challenge.split(".", 2); 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); let data: Challenge = JSON.parse(json as string);
return data; return data;
} }
``` ```
</Tab> </Tab>
<Tab value="Python"> <Tab value="Python" id="reference-language-py">
```py lineNumbers ```py lineNumbers
import base64, json import base64, json
@@ -177,7 +200,7 @@ These code pieces implement the decoding as a reference.
ValueError: The challenge string was not valid. 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.") raise ValueError("The challenge string contained too many or not enough parts.")
raw = challenge.split(".", 1) 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: `result = (result << 32) | digit`. The following pseudocode displays it simply:
``` ```
function getIntegerFromDigits(digits: number[]): function get_integer_from_digits(digits: number[]) -> integer:
result = 0 result = 0
for digit in digits: for digit in digits:
result = (result << 32) | digit result = (result << 32) | digit
return result return result
``` ```
Easy. Then we can use that function on both X and N. The following examples show actual code Easy. Then we can use that function on both X and N. The following examples show actual code
implementations of such function: implementations of such function:
<Tabs items={["TypeScript", "Python"]}> <Tabs items={["TypeScript", "Python"]} groupId="reference-language" persist>
<Tab value="TypeScript"> <Tab value="TypeScript" id="reference-language-ts">
```ts lineNumbers ```ts lineNumbers
/** /**
* Converts an array of u32 digits to a bigint. * Converts an array of u32 digits to a bigint.
@@ -253,7 +276,7 @@ implementations of such function:
} }
``` ```
</Tab> </Tab>
<Tab value="Python"> <Tab value="Python" id="reference-language-py">
```py lineNumbers ```py lineNumbers
# Your life will be brighter if you install `gmpy2-stubs` as a dev dependency, `gmpy2` does # 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. # 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, 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 which leaves no advantage to GPUs. In code, this step would be
<Tabs items={["TypeScript", "Python"]}> <Tabs items={["TypeScript", "Python"]} groupId="reference-language" persist>
<Tab value="TypeScript"> <Tab value="TypeScript" id="reference-language-ts">
```ts lineNumbers ```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 n = getBigintFromDigits(nDigits);
let x = getBigintFromDigits(xDigits); let x = getBigintFromDigits(xDigits);
let y = x; let y = x;
@@ -317,7 +340,7 @@ which leaves no advantage to GPUs. In code, this step would be
} }
``` ```
</Tab> </Tab>
<Tab value="Python"> <Tab value="Python" id="reference-language-py">
```py lineNumbers ```py lineNumbers
import gmpy2 import gmpy2
@@ -330,7 +353,7 @@ which leaves no advantage to GPUs. In code, this step would be
for _ in range(t): for _ in range(t):
y = y**2 % n y = y**2 % n
return n return y
``` ```
</Tab> </Tab>
@@ -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. 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 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 simple, the inverse of what we used for our `get_bigint_from_digits()` to decode `N` and `X` in the
`N` and `X` in the previous step. In pseudocode, it's previous step. In pseudocode, it's
``` ```
function getDigitsFromBigint(number: bigint, arrayWidth: number, digitByteWidth: number): function get_digits_from_bigint(number: bigint, array_width: number, digit_byte_width: number) -> number[]:
digits = [0] * arrayWidth digits = [0] * array_width
mask = 0; mask = 0;
loop digitByteWidth times: loop digit_byte_width times:
mask = mask << 8 mask = mask << 8
mask = mask | 0b1111_1111 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 digit = number & mask
digits[arrayWidth - 1 - i] = digit digits[array_width - 1 - i] = digit
number = number >> (digitByteWidth * 8) number = number >> (digit_byte_width * 8)
return digits return digits
``` ```
In TypeScript and Python, that's In TypeScript and Python, that's
<Tabs items={["TypeScript", "Python"]}> <Tabs items={["TypeScript", "Python"]} groupId="reference-language" persist>
<Tab value="TypeScript"> <Tab value="TypeScript" id="reference-language-ts">
```ts lineNumbers ```ts lineNumbers
/** /**
* Converts a bigint into a MSF digit array. * Converts a bigint into a MSF digit array.
@@ -409,7 +432,7 @@ In TypeScript and Python, that's
mask |= 0b1111_1111n; mask |= 0b1111_1111n;
} }
for (let i = digits - 1; i >= 0; i--) { for (let i = arrayWidth - 1; i >= 0; i--) {
let digit = Number(number & mask); let digit = Number(number & mask);
digits[i] = digit; digits[i] = digit;
number = number >> (digitByteWidth * 8); number = number >> (digitByteWidth * 8);
@@ -419,7 +442,7 @@ In TypeScript and Python, that's
} }
``` ```
</Tab> </Tab>
<Tab value="Python"> <Tab value="Python" id="reference-language-py">
```py lineNumbers ```py lineNumbers
import gmpy2 import gmpy2
@@ -468,8 +491,8 @@ z = hasher.finish()
Easy. The following examples show how to do such thing in TypeScript and Python. Easy. The following examples show how to do such thing in TypeScript and Python.
<Tabs items={["TypeScript", "Python"]}> <Tabs items={["TypeScript", "Python"]} groupId="reference-language" persist>
<Tab value="TypeScript"> <Tab value="TypeScript" id="reference-language-ts">
```ts lineNumbers ```ts lineNumbers
/** /**
* Hashes the provided parameters to get Z. * 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.
} }
``` ```
</Tab> </Tab>
<Tab value="Python"> <Tab value="Python" id="reference-language-py">
```py lineNumbers ```py lineNumbers
import hashlib import hashlib
@@ -517,10 +540,10 @@ Easy. The following examples show how to do such thing in TypeScript and Python.
hash = hashlib.sha256() hash = hashlib.sha256()
hash.update(b"duckity") hash.update(b"duckity")
hash.update(get_digits_from_mpz(n, 512, 1)) hash.update(bytes(get_digits_from_mpz(n, 512, 1)))
hash.update(get_digits_from_mpz(x, 512, 1)) hash.update(bytes(get_digits_from_mpz(x, 512, 1)))
hash.update(get_digits_from_mpz(t, 512, 1)) hash.update(bytes(get_digits_from_mpz(t, 512, 1)))
hash.update(get_digits_from_mpz(y, 512, 1)) hash.update(bytes(get_digits_from_mpz(y, 512, 1)))
z = hash.digest() z = hash.digest()
return z return z
@@ -540,24 +563,24 @@ using the Miller-Rabin algorithm.
library instead, make sure you test on exactly 40 bases. library instead, make sure you test on exactly 40 bases.
</Callout> </Callout>
For this, we'll have to update our previous `getBigintFromDigits` or `get_mpz_from_digits` to allow For this, we'll have to update our previous `get_bigint_from_digits()` to allow arbitrary digit
arbitrary digit sizes. That's simple, instead of hardcoding the digit size we'll pass it as an sizes. That's simple, instead of hardcoding the digit size we'll pass it as an argument to the
argument to the function. Our pseudocode will then look like 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 result = 0
for digit in digits: for digit in digits:
result = (result << (digit_byte_width * 8)) | digit result = (result << (digit_byte_width * 8)) | digit
return result return result
``` ```
In TypeScript and Python, that's In TypeScript and Python, that's
<Tabs items={["TypeScript", "Python"]}> <Tabs items={["TypeScript", "Python"]} groupId="reference-language" persist>
<Tab value="TypeScript"> <Tab value="TypeScript" id="reference-language-ts">
```ts lineNumbers ```ts lineNumbers
/** /**
* Converts an array of digits to a bigint. * Converts an array of digits to a bigint.
@@ -577,7 +600,7 @@ In TypeScript and Python, that's
} }
``` ```
</Tab> </Tab>
<Tab value="Python"> <Tab value="Python" id="reference-language-py">
```py lineNumbers ```py lineNumbers
import gmpy2 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. 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.
<Callout>
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.
</Callout>
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
<Tabs items={["TypeScript"]} groupId="reference-language" persist>
<Tab value="TypeScript" id="reference-language-ts">
```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;
}
```
</Tab>
</Tabs>
#### `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
<Tabs items={["TypeScript"]} groupId="reference-language" persist>
<Tab value="TypeScript" id="reference-language-ts">
```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;
}
```
</Tab>
</Tabs>
#### `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
<Tabs items={["TypeScript"]} groupId="reference-language" persist>
<Tab value="TypeScript" id="reference-language-ts">
```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;
}
```
</Tab>
</Tabs>
#### `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
<Tabs items={["TypeScript"]} groupId="reference-language" persist>
<Tab value="TypeScript" id="reference-language-ts">
```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;
}
}
}
```
</Tab>
</Tabs>
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
<Tabs items={["TypeScript", "Python"]} groupId="reference-language" persist>
<Tab value="TypeScript" id="reference-language-ts">
```ts lineNumbers
let l = getNextPrime(z);
```
</Tab>
<Tab value="Python" id="reference-language-py">
```py lineNumbers
import gmpy2
l = gmpy2.next_prime(z)
```
</Tab>
</Tabs>
### 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
<Tabs items={["TypeScript", "Python"]} groupId="reference-language" persist>
<Tab value="TypeScript" id="reference-language-ts">
```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;
}
}
```
</Tab>
<Tab value="Python" id="reference-language-py">
```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
```
</Tab>
</Tabs>
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
```
<challenge-data>.<challenge-signature>
```
We'll now encode our solution into a third segment to have a token that looks like follows:
```
<challenge-data>.<challenge-signature>.<solution-data>
```
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:
<Tabs items={["TypeScript", "Python"]} groupId="reference-language" persist>
<Tab value="TypeScript" id="reference-language-ts">
```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}`;
}
```
</Tab>
<Tab value="Python" id="reference-language-py">
```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}"
```
</Tab>
</Tabs>
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 <application-id>
Accept: application/json
Content-Type: application/json
{
"solution": "<solution-token>",
"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.
<Callout type="warn">
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.
</Callout>
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.
+5 -12
View File
@@ -45,7 +45,7 @@ Follow these steps depending on your application:
Then import it in your code: Then import it in your code:
```ts twoslash ```ts
import duckity from "@duckity/js"; 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 Once you have the SDK installed, you can request a challenge whenever you need it using
`duckity.solve()`. `duckity.solve()`.
```ts twoslash ```ts
const PROTECTION_PROFILE_ID: string = ""; const PROTECTION_PROFILE_ID: string = "";
// ---cut--- // ---cut---
import duckity from "@duckity/js"; 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 To pass threat correlation keys when issuing a challenge, set them in the `options` argument of
`duckity.solve()`. `duckity.solve()`.
```ts twoslash ```ts
const PROTECTION_PROFILE_ID: string = "py83YHkXV6ZpIsJZGVxzS"; const PROTECTION_PROFILE_ID: string = "py83YHkXV6ZpIsJZGVxzS";
// ---cut--- // ---cut---
import duckity from "@duckity/js"; import duckity from "@duckity/js";
let solution = await duckity.solve( let solution = await duckity.solve(PROTECTION_PROFILE_ID);
PROTECTION_PROFILE_ID,
{
keys: {
email: "[email protected]",
}
}
);
``` ```
### Using On Self-Hosted Ducklings ### 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 Self-hosted ducklings are hosted at a different domain from Duckity-hosted ducklings. To point it
to a custom domain, change the following setting: to a custom domain, change the following setting:
```ts twoslash ```ts
const PROTECTION_PROFILE_ID: string = "py83YHkXV6ZpIsJZGVxzS"; const PROTECTION_PROFILE_ID: string = "py83YHkXV6ZpIsJZGVxzS";
// ---cut--- // ---cut---
import duckity from "@duckity/js"; import duckity from "@duckity/js";
+2 -2
View File
@@ -5,11 +5,11 @@ const withMDX = createMDX();
/** @type {import('next').NextConfig} */ /** @type {import('next').NextConfig} */
const config = { const config = {
serverExternalPackages: ["@takumi-rs/core", "typescript", "twoslash"], serverExternalPackages: ["@takumi-rs/core", "typescript", "twoslash"],
output: "export", // output: "export",
reactStrictMode: true, reactStrictMode: true,
allowedDevOrigins: ["duckity.com", "duckity.dev"], allowedDevOrigins: ["duckity.com", "duckity.dev"],
basePath: "/docs", basePath: "/docs",
trailingSlash: true, // trailingSlash: true,
images: { images: {
unoptimized: true, unoptimized: true,
}, },
+3 -1
View File
@@ -19,6 +19,7 @@
"@orama/orama": "^3.1.18", "@orama/orama": "^3.1.18",
"@typescript/ata": "^0.9.8", "@typescript/ata": "^0.9.8",
"@typescript/vfs": "^1.6.4", "@typescript/vfs": "^1.6.4",
"beautiful-mermaid": "^1.1.3",
"class-variance-authority": "^0.7.1", "class-variance-authority": "^0.7.1",
"cnfast": "^0.0.8", "cnfast": "^0.0.8",
"fumadocs-core": "16.12.1", "fumadocs-core": "16.12.1",
@@ -26,7 +27,8 @@
"fumadocs-twoslash": "^3.3.0", "fumadocs-twoslash": "^3.3.0",
"fumadocs-ui": "npm:@fumadocs/[email protected]", "fumadocs-ui": "npm:@fumadocs/[email protected]",
"lucide-react": "^1.3.0", "lucide-react": "^1.3.0",
"next": "^16.2.12", "next": "^16.3.4",
"next-themes": "^0.4.6",
"react": "^19.2.8", "react": "^19.2.8",
"react-dom": "^19.2.8", "react-dom": "^19.2.8",
"takumi-js": "^2.4.0", "takumi-js": "^2.4.0",
+2 -43
View File
@@ -1,11 +1,8 @@
import ts from "typescript";
import { setupTypeAcquisition } from "@typescript/ata";
import { defineConfig, defineDocs } from "fumadocs-mdx/config"; import { defineConfig, defineDocs } from "fumadocs-mdx/config";
import { metaSchema, pageSchema } from "fumadocs-core/source/schema"; import { metaSchema, pageSchema } from "fumadocs-core/source/schema";
import { remarkSteps } from "fumadocs-core/mdx-plugins/remark-steps"; 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 { rehypeCodeDefaultOptions } from "fumadocs-core/mdx-plugins";
import { createFileSystemTypesCache } from "fumadocs-twoslash/cache-fs";
// You can customize Zod schemas for frontmatter and `meta.json` here // You can customize Zod schemas for frontmatter and `meta.json` here
// see https://fumadocs.dev/docs/mdx/collections // 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<string, string>();
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({ export default defineConfig({
mdxOptions: { mdxOptions: {
remarkPlugins: [remarkSteps], remarkPlugins: [remarkSteps, remarkMdxMermaid],
rehypeCodeOptions: { rehypeCodeOptions: {
themes: { themes: {
light: "github-light", light: "github-light",
@@ -57,19 +29,6 @@ export default defineConfig({
}, },
transformers: [ transformers: [
...(rehypeCodeDefaultOptions.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 // important: Shiki doesn't support lazy loading languages for codeblocks in Twoslash popups
+1 -1
View File
@@ -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(); return source.generateParams();
} }
+1 -1
View File
@@ -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) => ({ return source.getPages().map((page) => ({
slug: getPageMarkdownUrl(page).segments, slug: getPageMarkdownUrl(page).segments,
})); }));
+2
View File
@@ -1,6 +1,7 @@
import defaultMdxComponents from "fumadocs-ui/mdx"; import defaultMdxComponents from "fumadocs-ui/mdx";
import * as TabsComponents from 'fumadocs-ui/components/tabs'; import * as TabsComponents from 'fumadocs-ui/components/tabs';
import Image from "next/image"; import Image from "next/image";
import { Mermaid } from '@/components/mermaid';
import type { MDXComponents } from "mdx/types"; import type { MDXComponents } from "mdx/types";
import * as Twoslash from 'fumadocs-twoslash/ui'; import * as Twoslash from 'fumadocs-twoslash/ui';
@@ -10,6 +11,7 @@ export function getMDXComponents(components?: MDXComponents) {
img: (props) => <Image {...(props as any)} />, img: (props) => <Image {...(props as any)} />,
...TabsComponents, ...TabsComponents,
...Twoslash, ...Twoslash,
Mermaid,
...components, ...components,
} satisfies MDXComponents; } satisfies MDXComponents;
} }
+21
View File
@@ -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 <div dangerouslySetInnerHTML={{ __html: svg }} />;
} catch {
return (
<CodeBlock title="Mermaid">
<Pre>{chart}</Pre>
</CodeBlock>
);
}
}