diff --git a/.gitignore b/.gitignore index f616390..e563e6a 100644 --- a/.gitignore +++ b/.gitignore @@ -14,4 +14,4 @@ lib dist .DS_Store -test_static_files/ \ No newline at end of file +test_static_files/ diff --git a/.oxfmtrc.json b/.oxfmtrc.json new file mode 100644 index 0000000..17e56ad --- /dev/null +++ b/.oxfmtrc.json @@ -0,0 +1,7 @@ +{ + "$schema": "./node_modules/oxfmt/configuration_schema.json", + "ignorePatterns": [], + "singleQuote": true, + "semi": false, + "useTabs": true +} diff --git a/.oxlintrc.json b/.oxlintrc.json new file mode 100644 index 0000000..e753306 --- /dev/null +++ b/.oxlintrc.json @@ -0,0 +1,40 @@ +{ + "$schema": "./node_modules/oxlint/configuration_schema.json", + "plugins": null, + "categories": {}, + "rules": {}, + "settings": { + "jsx-a11y": { + "polymorphicPropName": null, + "components": {}, + "attributes": {} + }, + "next": { + "rootDir": [] + }, + "react": { + "formComponents": [], + "linkComponents": [], + "version": null, + "componentWrapperFunctions": [] + }, + "jsdoc": { + "ignorePrivate": false, + "ignoreInternal": false, + "ignoreReplacesDocs": true, + "overrideReplacesDocs": true, + "augmentsExtendsReplacesDocs": false, + "implementsReplacesDocs": false, + "exemptDestructuredRootsFromChecks": false, + "tagNamePreference": {} + }, + "vitest": { + "typecheck": false + } + }, + "env": { + "builtin": true + }, + "globals": {}, + "ignorePatterns": [] +} diff --git a/biome.json b/biome.json deleted file mode 100644 index 8df4b20..0000000 --- a/biome.json +++ /dev/null @@ -1,44 +0,0 @@ -{ - "$schema": "https://biomejs.dev/schemas/2.3.4/schema.json", - "files": { - "includes": [ - "**/*.ts", - "**/*.js", - "!**/dist/**", - "!**/node_modules/**", - "!**/.vitepress/**", - "!**/build/**" - ], - "ignoreUnknown": true - }, - "assist": { "actions": { "source": { "organizeImports": "off" } } }, - "linter": { - "enabled": true, - "rules": { - "recommended": true, - "suspicious": { - "noExplicitAny": "off" - }, - "style": { - "useTemplate": "off", - "useNodejsImportProtocol": "error" - } - } - }, - "json": { - "formatter": { - "indentWidth": 4 - } - }, - "formatter": { - "enabled": true, - "indentWidth": 4, - "indentStyle": "tab" - }, - "javascript": { - "formatter": { - "quoteStyle": "single", - "semicolons": "asNeeded" - } - } -} diff --git a/bun.lock b/bun.lock index 7086a9a..9d34bbd 100644 --- a/bun.lock +++ b/bun.lock @@ -5,11 +5,12 @@ "": { "name": "main", "devDependencies": { - "@biomejs/biome": "2.3.4", "@commitlint/cli": "19.3.0", "@commitlint/config-conventional": "19.2.2", "@types/bun": "1.3.2", "lefthook": "1.6.10", + "oxfmt": "0.28.0", + "oxlint": "1.43.0", "typescript": "5.9.3", }, }, @@ -158,24 +159,6 @@ "@babel/types": ["@babel/types@7.26.9", "", { "dependencies": { "@babel/helper-string-parser": "^7.25.9", "@babel/helper-validator-identifier": "^7.25.9" } }, "sha512-Y3IR1cRnOxOCDvMmNiym7XpXQ93iGDDPHx+Zj+NM+rg0fBaShfQLkg+hKPaZCEvg5N/LeCo4+Rj/i3FuJsIQaw=="], - "@biomejs/biome": ["@biomejs/biome@2.3.4", "", { "optionalDependencies": { "@biomejs/cli-darwin-arm64": "2.3.4", "@biomejs/cli-darwin-x64": "2.3.4", "@biomejs/cli-linux-arm64": "2.3.4", "@biomejs/cli-linux-arm64-musl": "2.3.4", "@biomejs/cli-linux-x64": "2.3.4", "@biomejs/cli-linux-x64-musl": "2.3.4", "@biomejs/cli-win32-arm64": "2.3.4", "@biomejs/cli-win32-x64": "2.3.4" }, "bin": { "biome": "bin/biome" } }, "sha512-TU08LXjBHdy0mEY9APtEtZdNQQijXUDSXR7IK1i45wgoPD5R0muK7s61QcFir6FpOj/RP1+YkPx5QJlycXUU3w=="], - - "@biomejs/cli-darwin-arm64": ["@biomejs/cli-darwin-arm64@2.3.4", "", { "os": "darwin", "cpu": "arm64" }, "sha512-w40GvlNzLaqmuWYiDU6Ys9FNhJiclngKqcGld3iJIiy2bpJ0Q+8n3haiaC81uTPY/NA0d8Q/I3Z9+ajc14102Q=="], - - "@biomejs/cli-darwin-x64": ["@biomejs/cli-darwin-x64@2.3.4", "", { "os": "darwin", "cpu": "x64" }, "sha512-3s7TLVtjJ7ni1xADXsS7x7GMUrLBZXg8SemXc3T0XLslzvqKj/dq1xGeBQ+pOWQzng9MaozfacIHdK2UlJ3jGA=="], - - "@biomejs/cli-linux-arm64": ["@biomejs/cli-linux-arm64@2.3.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-y7efHyyM2gYmHy/AdWEip+VgTMe9973aP7XYKPzu/j8JxnPHuSUXftzmPhkVw0lfm4ECGbdBdGD6+rLmTgNZaA=="], - - "@biomejs/cli-linux-arm64-musl": ["@biomejs/cli-linux-arm64-musl@2.3.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-IruVGQRwMURivWazchiq7gKAqZSFs5so6gi0hJyxk7x6HR+iwZbO2IxNOqyLURBvL06qkIHs7Wffl6Bw30vCbQ=="], - - "@biomejs/cli-linux-x64": ["@biomejs/cli-linux-x64@2.3.4", "", { "os": "linux", "cpu": "x64" }, "sha512-gKfjWR/6/dfIxPJCw8REdEowiXCkIpl9jycpNVHux8aX2yhWPLjydOshkDL6Y/82PcQJHn95VCj7J+BRcE5o1Q=="], - - "@biomejs/cli-linux-x64-musl": ["@biomejs/cli-linux-x64-musl@2.3.4", "", { "os": "linux", "cpu": "x64" }, "sha512-mzKFFv/w66e4/jCobFmD3kymCqG+FuWE7sVa4Yjqd9v7qt2UhXo67MSZKY9Ih18V2IwPzRKQPCw6KwdZs6AXSA=="], - - "@biomejs/cli-win32-arm64": ["@biomejs/cli-win32-arm64@2.3.4", "", { "os": "win32", "cpu": "arm64" }, "sha512-5TJ6JfVez+yyupJ/iGUici2wzKf0RrSAxJhghQXtAEsc67OIpdwSKAQboemILrwKfHDi5s6mu7mX+VTCTUydkw=="], - - "@biomejs/cli-win32-x64": ["@biomejs/cli-win32-x64@2.3.4", "", { "os": "win32", "cpu": "x64" }, "sha512-FGCijXecmC4IedQ0esdYNlMpx0Jxgf4zceCaMu6fkjWyjgn50ZQtMiqZZQ0Q/77yqPxvtkgZAvt5uGw0gAAjig=="], - "@borewit/text-codec": ["@borewit/text-codec@0.1.1", "", {}, "sha512-5L/uBxmjaCIX5h8Z+uu+kA9BQLkc/Wl06UGR5ajNRxu+/XjonB5i8JpgFMrPj3LXTCPA0pv8yxUvbUi+QthGGA=="], "@commitlint/cli": ["@commitlint/cli@19.3.0", "", { "dependencies": { "@commitlint/format": "^19.3.0", "@commitlint/lint": "^19.2.2", "@commitlint/load": "^19.2.0", "@commitlint/read": "^19.2.1", "@commitlint/types": "^19.0.3", "execa": "^8.0.1", "yargs": "^17.0.0" }, "bin": { "commitlint": "cli.js" } }, "sha512-LgYWOwuDR7BSTQ9OLZ12m7F/qhNY+NpAyPBgo4YNMkACE7lGuUnuQq1yi9hz1KA4+3VqpOYl8H1rY/LYK43v7g=="], @@ -296,6 +279,38 @@ "@koa/router": ["@koa/router@15.0.0", "", { "dependencies": { "debug": "^4.4.3", "http-errors": "^2.0.1", "koa-compose": "^4.1.0", "path-to-regexp": "^8.3.0" } }, "sha512-qAoA07CndM5XuBZbTbsnvUj1RNVZtwOvO9xGz7CCE/t4nSopI+xEiFGHyJS1UuSDCt8cJZ9vfCvqbAFga+0y7w=="], + "@oxfmt/darwin-arm64": ["@oxfmt/darwin-arm64@0.28.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-jmUfF7cNJPw57bEK7sMIqrYRgn4LH428tSgtgLTCtjuGuu1ShREyrkeB7y8HtkXRfhBs4lVY+HMLhqElJvZ6ww=="], + + "@oxfmt/darwin-x64": ["@oxfmt/darwin-x64@0.28.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-S6vlV8S7jbjzJOSjfVg2CimUC0r7/aHDLdUm/3+/B/SU/s1jV7ivqWkMv1/8EB43d1BBwT9JQ60ZMTkBqeXSFA=="], + + "@oxfmt/linux-arm64-gnu": ["@oxfmt/linux-arm64-gnu@0.28.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-TfJkMZjePbLiskmxFXVAbGI/OZtD+y+fwS0wyW8O6DWG0ARTf0AipY9zGwGoOdpFuXOJceXvN4SHGLbYNDMY4Q=="], + + "@oxfmt/linux-arm64-musl": ["@oxfmt/linux-arm64-musl@0.28.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-7fyQUdW203v4WWGr1T3jwTz4L7KX9y5DeATryQ6fLT6QQp9GEuct8/k0lYhd+ys42iTV/IkJF20e3YkfSOOILg=="], + + "@oxfmt/linux-x64-gnu": ["@oxfmt/linux-x64-gnu@0.28.0", "", { "os": "linux", "cpu": "x64" }, "sha512-sRKqAvEonuz0qr1X1ncUZceOBJerKzkO2gZIZmosvy/JmqyffpIFL3OE2tqacFkeDhrC+dNYQpusO8zsfHo3pw=="], + + "@oxfmt/linux-x64-musl": ["@oxfmt/linux-x64-musl@0.28.0", "", { "os": "linux", "cpu": "x64" }, "sha512-fW6czbXutX/tdQe8j4nSIgkUox9RXqjyxwyWXUDItpoDkoXllq17qbD7GVc0whrEhYQC6hFE1UEAcDypLJoSzw=="], + + "@oxfmt/win32-arm64": ["@oxfmt/win32-arm64@0.28.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-D/HDeQBAQRjTbD9OLV6kRDcStrIfO+JsUODDCdGmhRfNX8LPCx95GpfyybpZfn3wVF8Jq/yjPXV1xLkQ+s7RcA=="], + + "@oxfmt/win32-x64": ["@oxfmt/win32-x64@0.28.0", "", { "os": "win32", "cpu": "x64" }, "sha512-4+S2j4OxOIyo8dz5osm5dZuL0yVmxXvtmNdHB5xyGwAWVvyWNvf7tCaQD7w2fdSsAXQLOvK7KFQrHFe33nJUCA=="], + + "@oxlint/darwin-arm64": ["@oxlint/darwin-arm64@1.43.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-C/GhObv/pQZg34NOzB6Mk8x0wc9AKj8fXzJF8ZRKTsBPyHusC6AZ6bba0QG0TUufw1KWuD0j++oebQfWeiFXNw=="], + + "@oxlint/darwin-x64": ["@oxlint/darwin-x64@1.43.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-4NjfUtEEH8ewRQ2KlZGmm6DyrvypMdHwBnQT92vD0dLScNOQzr0V9O8Ua4IWXdeCNl/XMVhAV3h4/3YEYern5A=="], + + "@oxlint/linux-arm64-gnu": ["@oxlint/linux-arm64-gnu@1.43.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-75tf1HvwdZ3ebk83yMbSB+moAEWK98mYqpXiaFAi6Zshie7r+Cx5PLXZFUEqkscenoZ+fcNXakHxfn94V6nf1g=="], + + "@oxlint/linux-arm64-musl": ["@oxlint/linux-arm64-musl@1.43.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-BHV4fb36T2p/7bpA9fiJ5ayt7oJbiYX10nklW5arYp4l9/9yG/FQC5J4G1evzbJ/YbipF9UH0vYBAm5xbqGrvw=="], + + "@oxlint/linux-x64-gnu": ["@oxlint/linux-x64-gnu@1.43.0", "", { "os": "linux", "cpu": "x64" }, "sha512-1l3nvnzWWse1YHibzZ4HQXdF/ibfbKZhp9IguElni3bBqEyPEyurzZ0ikWynDxKGXqZa+UNXTFuU1NRVX1RJ3g=="], + + "@oxlint/linux-x64-musl": ["@oxlint/linux-x64-musl@1.43.0", "", { "os": "linux", "cpu": "x64" }, "sha512-+jNYgLGRFTJxJuaSOZJBwlYo5M0TWRw0+3y5MHOL4ArrIdHyCthg6r4RbVWrsR1qUfUE1VSSHQ2bfbC99RXqMg=="], + + "@oxlint/win32-arm64": ["@oxlint/win32-arm64@1.43.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-dvs1C/HCjCyGTURMagiHprsOvVTT3omDiSzi5Qw0D4QFJ1pEaNlfBhVnOUYgUfS6O7Mcmj4+G+sidRsQcWQ/kA=="], + + "@oxlint/win32-x64": ["@oxlint/win32-x64@1.43.0", "", { "os": "win32", "cpu": "x64" }, "sha512-bSuItSU8mTSDsvmmLTepTdCL2FkJI6dwt9tot/k0EmiYF+ArRzmsl4lXVLssJNRV5lJEc5IViyTrh7oiwrjUqA=="], + "@protobufjs/aspromise": ["@protobufjs/aspromise@1.1.2", "", {}, "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ=="], "@protobufjs/base64": ["@protobufjs/base64@1.1.2", "", {}, "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg=="], @@ -784,6 +799,10 @@ "openapi-types": ["openapi-types@12.1.3", "", {}, "sha512-N4YtSYJqghVu4iek2ZUvcN/0aqH1kRDuNqzcycDxhOUpg7GdvLa2F3DgS6yBNhInhv2r/6I0Flkn7CqL8+nIcw=="], + "oxfmt": ["oxfmt@0.28.0", "", { "dependencies": { "tinypool": "2.1.0" }, "optionalDependencies": { "@oxfmt/darwin-arm64": "0.28.0", "@oxfmt/darwin-x64": "0.28.0", "@oxfmt/linux-arm64-gnu": "0.28.0", "@oxfmt/linux-arm64-musl": "0.28.0", "@oxfmt/linux-x64-gnu": "0.28.0", "@oxfmt/linux-x64-musl": "0.28.0", "@oxfmt/win32-arm64": "0.28.0", "@oxfmt/win32-x64": "0.28.0" }, "bin": { "oxfmt": "bin/oxfmt" } }, "sha512-3+hhBqPE6Kp22KfJmnstrZbl+KdOVSEu1V0ABaFIg1rYLtrMgrupx9znnHgHLqKxAVHebjTdiCJDk30CXOt6cw=="], + + "oxlint": ["oxlint@1.43.0", "", { "optionalDependencies": { "@oxlint/darwin-arm64": "1.43.0", "@oxlint/darwin-x64": "1.43.0", "@oxlint/linux-arm64-gnu": "1.43.0", "@oxlint/linux-arm64-musl": "1.43.0", "@oxlint/linux-x64-gnu": "1.43.0", "@oxlint/linux-x64-musl": "1.43.0", "@oxlint/win32-arm64": "1.43.0", "@oxlint/win32-x64": "1.43.0" }, "peerDependencies": { "oxlint-tsgolint": ">=0.11.2" }, "optionalPeers": ["oxlint-tsgolint"], "bin": { "oxlint": "bin/oxlint" } }, "sha512-xiqTCsKZch+R61DPCjyqUVP2MhkQlRRYxLRBeBDi+dtQJ90MOgdcjIktvDCgXz0bgtx94EQzHEndsizZjMX2OA=="], + "p-limit": ["p-limit@4.0.0", "", { "dependencies": { "yocto-queue": "^1.0.0" } }, "sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ=="], "p-locate": ["p-locate@6.0.0", "", { "dependencies": { "p-limit": "^4.0.0" } }, "sha512-wPrq66Llhl7/4AGC6I+cqxT07LhXvWL08LNXz1fENOw0Ap4sRZZ/gZpTTJ5jpurzzzfS2W/Ge9BY3LgLjCShcw=="], @@ -898,6 +917,8 @@ "through": ["through@2.3.8", "", {}, "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg=="], + "tinypool": ["tinypool@2.1.0", "", {}, "sha512-Pugqs6M0m7Lv1I7FtxN4aoyToKg1C4tu+/381vH35y8oENM/Ai7f7C4StcoK4/+BSw9ebcS8jRiVrORFKCALLw=="], + "toidentifier": ["toidentifier@1.0.1", "", {}, "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA=="], "token-types": ["token-types@6.1.1", "", { "dependencies": { "@borewit/text-codec": "^0.1.0", "@tokenizer/token": "^0.3.0", "ieee754": "^1.2.1" } }, "sha512-kh9LVIWH5CnL63Ipf0jhlBIy0UsrMj/NJDfpsy1SqOXlLKEVyXXYrnFxFT1yOOYVGBSApeVnjPw/sBz5BfEjAQ=="], diff --git a/lefthook.yml b/lefthook.yml index 5133bd7..6a6d86b 100644 --- a/lefthook.yml +++ b/lefthook.yml @@ -1,11 +1,11 @@ commit-msg: - commands: - conventional: - run: bun commitlint --edit $1 + commands: + conventional: + run: bun commitlint --edit $1 pre-commit: - parallel: true - commands: - check: - glob: '*.{js,ts,jsx,tsx}' - run: bun biome check --no-errors-on-unmatched --files-ignore-unknown=true {staged_files} + parallel: true + commands: + check: + glob: '*.{js,ts,jsx,tsx}' + run: bun oxlint check {staged_files} diff --git a/package.json b/package.json index c268ceb..3d4addf 100644 --- a/package.json +++ b/package.json @@ -4,20 +4,21 @@ "workspaces": [ "packages/*" ], + "scripts": { + "build:wobe": "bun --filter './packages/wobe' build", + "ci": "bun build:wobe && bun --filter './packages/*' ci", + "format": "bun --filter './packages/*' format && oxfmt --write ./*.json", + "lint": "bun --filter './packages/*' lint", + "pre:commit": "oxlint ./packages && oxfmt --write ./packages", + "squash": "base_branch=${1:-main} && git fetch origin $base_branch && branch=$(git branch --show-current) && git checkout $branch && git reset $(git merge-base origin/$base_branch $branch) && git add -A" + }, "devDependencies": { - "@biomejs/biome": "2.3.4", "@commitlint/cli": "19.3.0", "@commitlint/config-conventional": "19.2.2", "@types/bun": "1.3.2", "lefthook": "1.6.10", + "oxfmt": "0.28.0", + "oxlint": "1.43.0", "typescript": "5.9.3" - }, - "scripts": { - "build:wobe": "bun --filter './packages/wobe' build", - "ci": "bun build:wobe && bun --filter './packages/*' ci", - "format": "bun --filter './packages/*' format && biome format --write ./*.json", - "lint": "bun --filter './packages/*' lint", - "pre:commit": "biome lint ./packages --no-errors-on-unmatched && biome format --write ./packages", - "squash": "base_branch=${1:-main} && git fetch origin $base_branch && branch=$(git branch --show-current) && git checkout $branch && git reset $(git merge-base origin/$base_branch $branch) && git add -A" } } diff --git a/packages/wobe-benchmark/index.ts b/packages/wobe-benchmark/index.ts deleted file mode 100644 index e69de29..0000000 diff --git a/packages/wobe-benchmark/package.json b/packages/wobe-benchmark/package.json index d3afe9e..75c9a5b 100644 --- a/packages/wobe-benchmark/package.json +++ b/packages/wobe-benchmark/package.json @@ -2,25 +2,25 @@ "name": "wobe-benchmark", "version": "0.1.0", "main": "index.ts", + "scripts": { + "bench:startup": "bun run startup/benchmark.ts", + "bench:router": "bun run router/benchmark.ts", + "bench:extracter": "bun run pathExtract/benchmark.ts", + "bench:findHook": "bun run findHook/benchmark.ts", + "lint": "oxlint . ", + "format": "oxfmt --write ." + }, "dependencies": { "wobe": "*" }, "devDependencies": { + "@koa/router": "15.0.0", "@sinclair/typebox": "0.34.41", "elysia": "1.4.18", + "find-my-way": "9.3.0", "get-port": "7.1.0", - "mitata": "1.0.34", "hono": "4.10.7", - "@koa/router": "15.0.0", - "radix3": "1.1.2", - "find-my-way": "9.3.0" - }, - "scripts": { - "bench:startup": "bun run startup/benchmark.ts", - "bench:router": "bun run router/benchmark.ts", - "bench:extracter": "bun run pathExtract/benchmark.ts", - "bench:findHook": "bun run findHook/benchmark.ts", - "lint": "biome lint . --no-errors-on-unmatched", - "format": "biome format --write ." + "mitata": "1.0.34", + "radix3": "1.1.2" } } diff --git a/packages/wobe-benchmark/pathExtract/benchmark.ts b/packages/wobe-benchmark/pathExtract/benchmark.ts index c13c9a7..0c81f73 100644 --- a/packages/wobe-benchmark/pathExtract/benchmark.ts +++ b/packages/wobe-benchmark/pathExtract/benchmark.ts @@ -25,9 +25,7 @@ const routes: Array = [ hasParams: false, }, { - request: new Request( - 'https://localhost:3000/very/deeply/nested/route/hello/there', - ), + request: new Request('https://localhost:3000/very/deeply/nested/route/hello/there'), hasParams: false, }, { diff --git a/packages/wobe-benchmark/router/hono.ts b/packages/wobe-benchmark/router/hono.ts index 2372fb7..e9537de 100644 --- a/packages/wobe-benchmark/router/hono.ts +++ b/packages/wobe-benchmark/router/hono.ts @@ -4,10 +4,7 @@ import { RegExpRouter } from 'hono/router/reg-exp-router' import { TrieRouter } from 'hono/router/trie-router' import { SmartRouter } from 'hono/router/smart-router' -const createHonoRouter = ( - name: string, - router: Router, -): RouterInterface => { +const createHonoRouter = (name: string, router: Router): RouterInterface => { for (const route of routes) { router.add(route.method, route.pathToCompile, handler) } diff --git a/packages/wobe-benchmark/router/wobe.ts b/packages/wobe-benchmark/router/wobe.ts index 83c298e..d31a37c 100644 --- a/packages/wobe-benchmark/router/wobe.ts +++ b/packages/wobe-benchmark/router/wobe.ts @@ -3,9 +3,7 @@ import { UrlPatternRouter, type Router } from 'wobe' const createWobeRouter = (name: string, radixTree: Router) => { for (const route of routes) { - radixTree.addRoute(route.method, route.pathToCompile, () => - Promise.resolve(), - ) + radixTree.addRoute(route.method, route.pathToCompile, () => Promise.resolve()) } radixTree.optimizeTree() @@ -18,7 +16,4 @@ const createWobeRouter = (name: string, radixTree: Router) => { } } -export const wobeRouter = createWobeRouter( - 'UrlPattern router', - new UrlPatternRouter(), -) +export const wobeRouter = createWobeRouter('UrlPattern router', new UrlPatternRouter()) diff --git a/packages/wobe-graphql-apollo/package.json b/packages/wobe-graphql-apollo/package.json index dca0b20..67cf0d4 100644 --- a/packages/wobe-graphql-apollo/package.json +++ b/packages/wobe-graphql-apollo/package.json @@ -2,33 +2,33 @@ "name": "wobe-graphql-apollo", "version": "1.0.11", "description": "Apollo GraphQL server for Wobe (official)", + "keywords": [ + "graphql-apollo", + "wobe" + ], "homepage": "https://wobe.dev", + "license": "MIT", "author": { "name": "coratgerl", "url": "https://github.com/coratgerl" }, - "keywords": [ - "graphql-apollo", - "wobe" - ], "repository": { "type": "git", "url": "https://github.com/palixir/wobe" }, - "license": "MIT", "main": "dist/index.js", - "devDependencies": { - "get-port": "7.0.0", - "wobe": "workspace:*" - }, - "dependencies": { - "@apollo/server": "5.2.0" - }, "scripts": { "build": "bun build --minify --outdir dist $(pwd)/src/index.ts --target=bun --external=* && bun generate:types", "generate:types": "bun tsc --project .", - "format": "biome format --write .", - "lint": "biome lint . --no-errors-on-unmatched ", + "format": "oxfmt --write .", + "lint": "oxlint . ", "ci": "bun lint $(pwd) && bun test src" + }, + "dependencies": { + "@apollo/server": "5.2.0" + }, + "devDependencies": { + "get-port": "7.0.0", + "wobe": "workspace:*" } } diff --git a/packages/wobe-graphql-apollo/src/index.test.ts b/packages/wobe-graphql-apollo/src/index.test.ts index 93a2ff8..cf249a4 100644 --- a/packages/wobe-graphql-apollo/src/index.test.ts +++ b/packages/wobe-graphql-apollo/src/index.test.ts @@ -126,9 +126,7 @@ describe('Wobe GraphQL Apollo plugin', () => { const body = await res.json() expect(res.status).toBe(400) - expect(body.errors?.[0]?.message?.toLowerCase()).toContain( - 'introspection', - ) + expect(body.errors?.[0]?.message?.toLowerCase()).toContain('introspection') wobe.stop() }) @@ -298,9 +296,7 @@ describe('Wobe GraphQL Apollo plugin', () => { const body = await res.json() expect(body.data).toBeUndefined() - expect(body.errors?.[0]?.message).toMatch( - /Multiple operations|Could not determine/i, - ) + expect(body.errors?.[0]?.message).toMatch(/Multiple operations|Could not determine/i) wobe.stop() }) @@ -426,8 +422,7 @@ describe('Wobe GraphQL Apollo plugin', () => { await wobe.usePlugin( await WobeGraphqlApolloPlugin({ - rateLimiter: async () => - new Response('Too Many Requests', { status: 429 }), + rateLimiter: async () => new Response('Too Many Requests', { status: 429 }), options: { typeDefs: `#graphql type Query { hello: String } @@ -728,9 +723,7 @@ describe('Wobe GraphQL Apollo plugin', () => { }) expect(res.status).toBe(200) - expect(res.headers.get('set-cookie')).toBe( - 'before=before;, after=after;', - ) + expect(res.headers.get('set-cookie')).toBe('before=before;, after=after;') expect(await res.json()).toEqual({ data: { hello: 'Hello from Apollo!' }, }) diff --git a/packages/wobe-graphql-apollo/src/index.ts b/packages/wobe-graphql-apollo/src/index.ts index 16dda04..0fbea3e 100644 --- a/packages/wobe-graphql-apollo/src/index.ts +++ b/packages/wobe-graphql-apollo/src/index.ts @@ -1,13 +1,7 @@ import { ApolloServer, type ApolloServerOptions } from '@apollo/server' import { ApolloServerPluginLandingPageLocalDefault } from '@apollo/server/plugin/landingPage/default' import { GraphQLError, type ValidationRule } from 'graphql' -import type { - Wobe, - MaybePromise, - WobePlugin, - WobeResponse, - Context, -} from 'wobe' +import type { Wobe, MaybePromise, WobePlugin, WobeResponse, Context } from 'wobe' export type GraphQLApolloContext = | MaybePromise> @@ -19,28 +13,16 @@ const createDepthLimitRule = (maxDepth: number): ValidationRule => { return (context) => { const checkDepth = (depth: number) => { if (depth > maxDepth) { - context.reportError( - new GraphQLError( - `Query is too deep: ${depth} > max depth ${maxDepth}`, - ), - ) + context.reportError(new GraphQLError(`Query is too deep: ${depth} > max depth ${maxDepth}`)) } } - const traverse = ( - selectionSet: any, - depth: number, - visitedFragments: Set, - ) => { + const traverse = (selectionSet: any, depth: number, visitedFragments: Set) => { checkDepth(depth) for (const selection of selectionSet.selections || []) { if (selection.selectionSet) { - traverse( - selection.selectionSet, - depth + 1, - visitedFragments, - ) + traverse(selection.selectionSet, depth + 1, visitedFragments) continue } @@ -50,11 +32,7 @@ const createDepthLimitRule = (maxDepth: number): ValidationRule => { visitedFragments.add(name) const fragment = context.getFragment(name) if (fragment) { - traverse( - fragment.selectionSet, - depth + 1, - visitedFragments, - ) + traverse(fragment.selectionSet, depth + 1, visitedFragments) } } } @@ -72,18 +50,12 @@ const createCostLimitRule = (maxCost: number): ValidationRule => { return (context) => { let totalCost = 0 - const countSelections = ( - selectionSet: any, - visitedFragments: Set, - ): number => { + const countSelections = (selectionSet: any, visitedFragments: Set): number => { let cost = 0 for (const selection of selectionSet.selections || []) { cost += 1 if (selection.selectionSet) { - cost += countSelections( - selection.selectionSet, - visitedFragments, - ) + cost += countSelections(selection.selectionSet, visitedFragments) continue } if (selection.kind === 'FragmentSpread') { @@ -92,10 +64,7 @@ const createCostLimitRule = (maxCost: number): ValidationRule => { visitedFragments.add(name) const fragment = context.getFragment(name) if (fragment) { - cost += countSelections( - fragment.selectionSet, - visitedFragments, - ) + cost += countSelections(fragment.selectionSet, visitedFragments) } } } @@ -105,18 +74,13 @@ const createCostLimitRule = (maxCost: number): ValidationRule => { return { OperationDefinition(node) { const visitedFragments = new Set() - totalCost += countSelections( - node.selectionSet, - visitedFragments, - ) + totalCost += countSelections(node.selectionSet, visitedFragments) }, Document: { leave() { if (totalCost > maxCost) { context.reportError( - new GraphQLError( - `Query is too expensive: ${totalCost} > max cost ${maxCost}`, - ), + new GraphQLError(`Query is too expensive: ${totalCost} > max cost ${maxCost}`), ) } }, @@ -146,9 +110,7 @@ const createOperationConstraintsRule = ({ !allowedOperationNames.includes(name) ) { context.reportError( - new GraphQLError( - `Operation "${name}" is not allowed in this endpoint.`, - ), + new GraphQLError(`Operation "${name}" is not allowed in this endpoint.`), ) } } @@ -157,9 +119,7 @@ const createOperationConstraintsRule = ({ leave() { if (!allowMultipleOperations && seenOperations.length > 1) { context.reportError( - new GraphQLError( - 'Multiple operations are not allowed in this endpoint.', - ), + new GraphQLError('Multiple operations are not allowed in this endpoint.'), ) } }, @@ -178,10 +138,7 @@ const resolveWithTimeout = async ( const timeoutPromise = new Promise((resolveTimeout) => { timeoutId = setTimeout( - () => - resolveTimeout( - new Response('Request Timeout', { status: 504 }), - ), + () => resolveTimeout(new Response('Request Timeout', { status: 504 })), timeoutMs, ) }) @@ -194,10 +151,7 @@ const resolveWithTimeout = async ( } export interface GraphQLApolloPluginOptions { - graphqlMiddleware?: ( - resolve: () => Promise, - res: WobeResponse, - ) => Promise + graphqlMiddleware?: (resolve: () => Promise, res: WobeResponse) => Promise allowGetRequests?: boolean isProduction?: boolean allowIntrospection?: boolean @@ -239,8 +193,7 @@ export const WobeGraphqlApolloPlugin = async ({ isProduction?: boolean } & GraphQLApolloPluginOptions): Promise => { const introspection = - options.introspection ?? - (allowIntrospection === true ? true : !isProduction) + options.introspection ?? (allowIntrospection === true ? true : !isProduction) const validationRules: ValidationRule[] = [ ...(options.validationRules || []), @@ -281,10 +234,7 @@ export const WobeGraphqlApolloPlugin = async ({ if (maxRequestSizeBytes) { const contentLength = request.headers.get('content-length') - if ( - contentLength && - Number(contentLength) > maxRequestSizeBytes - ) { + if (contentLength && Number(contentLength) > maxRequestSizeBytes) { return new Response('Request Entity Too Large', { status: 413, }) @@ -293,8 +243,7 @@ export const WobeGraphqlApolloPlugin = async ({ if (rateLimiter) { const rateLimitResult = await rateLimiter(context) - if (rateLimitResult instanceof Response) - return rateLimitResult + if (rateLimitResult instanceof Response) return rateLimitResult } const start = performance.now() @@ -306,10 +255,7 @@ export const WobeGraphqlApolloPlugin = async ({ const res = await server.executeHTTPGraphQLRequest({ httpGraphQLRequest: { method: request.method, - body: - request.method === 'GET' - ? request.body - : requestBody, + body: request.method === 'GET' ? request.body : requestBody, // @ts-expect-error headers: request.headers, search: getQueryString(request.url), @@ -318,13 +264,9 @@ export const WobeGraphqlApolloPlugin = async ({ if (!apolloContext) return context if (typeof apolloContext === 'function') { - const apolloContextResult = - await apolloContext(context) + const apolloContextResult = await apolloContext(context) - if ( - apolloContextResult && - typeof apolloContextResult === 'object' - ) + if (apolloContextResult && typeof apolloContextResult === 'object') return { ...context, ...apolloContextResult } return context @@ -356,8 +298,7 @@ export const WobeGraphqlApolloPlugin = async ({ const resolve = async () => fetchEndpoint(context.request) - if (!graphqlMiddleware) - return resolveWithTimeout(resolve, timeoutMs) + if (!graphqlMiddleware) return resolveWithTimeout(resolve, timeoutMs) return graphqlMiddleware(async () => { const response = await resolveWithTimeout(resolve, timeoutMs) diff --git a/packages/wobe-graphql-yoga/package.json b/packages/wobe-graphql-yoga/package.json index ab2ec84..63dd3cb 100644 --- a/packages/wobe-graphql-yoga/package.json +++ b/packages/wobe-graphql-yoga/package.json @@ -2,33 +2,33 @@ "name": "wobe-graphql-yoga", "version": "1.2.9", "description": "GraphQL Yoga server for Wobe (official)", + "keywords": [ + "graphql-yoga", + "wobe" + ], "homepage": "https://wobe.dev", + "license": "MIT", "author": { "name": "coratgerl", "url": "https://github.com/coratgerl" }, - "keywords": [ - "graphql-yoga", - "wobe" - ], "repository": { "type": "git", "url": "https://github.com/palixir/wobe" }, - "license": "MIT", "main": "dist/index.js", - "devDependencies": { - "wobe": "workspace:*", - "get-port": "7.0.0" - }, - "dependencies": { - "graphql-yoga": "5.17.1" - }, "scripts": { "build": "bun build --minify --outdir dist $(pwd)/src/index.ts --target=bun --external=* && bun generate:types", "generate:types": "bun tsc --project .", - "format": "biome format --write .", - "lint": "biome lint . --no-errors-on-unmatched ", + "format": "oxfmt --write .", + "lint": "oxlint . ", "ci": "bun lint $(pwd) && bun test src" + }, + "dependencies": { + "graphql-yoga": "5.17.1" + }, + "devDependencies": { + "get-port": "7.0.0", + "wobe": "workspace:*" } } diff --git a/packages/wobe-graphql-yoga/src/index.test.ts b/packages/wobe-graphql-yoga/src/index.test.ts index 537761a..a468c3e 100644 --- a/packages/wobe-graphql-yoga/src/index.test.ts +++ b/packages/wobe-graphql-yoga/src/index.test.ts @@ -114,9 +114,7 @@ describe('Wobe GraphQL Yoga plugin', () => { const body = await res.json() expect(body.data).toBeUndefined() - expect(body.errors?.[0]?.message?.toLowerCase()).toContain( - 'introspection', - ) + expect(body.errors?.[0]?.message?.toLowerCase()).toContain('introspection') wobe.stop() }) @@ -298,9 +296,7 @@ describe('Wobe GraphQL Yoga plugin', () => { const body = await res.json() expect(body.data).toBeUndefined() - expect(body.errors?.[0]?.message).toMatch( - /Multiple operations|Could not determine/i, - ) + expect(body.errors?.[0]?.message).toMatch(/Multiple operations|Could not determine/i) wobe.stop() }) @@ -426,8 +422,7 @@ describe('Wobe GraphQL Yoga plugin', () => { await wobe.usePlugin( WobeGraphqlYogaPlugin({ - rateLimiter: async () => - new Response('Too Many Requests', { status: 429 }), + rateLimiter: async () => new Response('Too Many Requests', { status: 429 }), typeDefs: ` type Query { hello: String @@ -712,9 +707,7 @@ describe('Wobe GraphQL Yoga plugin', () => { }) expect(res.status).toBe(200) - expect(res.headers.get('set-cookie')).toBe( - 'before=before;, after=after;', - ) + expect(res.headers.get('set-cookie')).toBe('before=before;, after=after;') expect(await res.json()).toEqual({ data: { hello: 'Hello from Yoga!' }, }) diff --git a/packages/wobe-graphql-yoga/src/index.ts b/packages/wobe-graphql-yoga/src/index.ts index e499e37..2efdb55 100644 --- a/packages/wobe-graphql-yoga/src/index.ts +++ b/packages/wobe-graphql-yoga/src/index.ts @@ -5,28 +5,15 @@ import { type Plugin, type YogaServerOptions, } from 'graphql-yoga' -import { - GraphQLError, - NoSchemaIntrospectionCustomRule, - type ValidationRule, -} from 'graphql' -import type { - Context, - MaybePromise, - Wobe, - WobePlugin, - WobeResponse, -} from 'wobe' +import { GraphQLError, NoSchemaIntrospectionCustomRule, type ValidationRule } from 'graphql' +import type { Context, MaybePromise, Wobe, WobePlugin, WobeResponse } from 'wobe' export type GraphqlYogaContext = | MaybePromise> | ((context: any) => MaybePromise) export interface GraphqlYogaPluginOptions { - graphqlMiddleware?: ( - resolve: () => Promise, - res: WobeResponse, - ) => Promise + graphqlMiddleware?: (resolve: () => Promise, res: WobeResponse) => Promise allowGetRequests?: boolean isProduction?: boolean allowIntrospection?: boolean @@ -69,8 +56,7 @@ export const WobeGraphqlYogaPlugin = ({ const graphqlEndpoint = options?.graphqlEndpoint || '/graphql' const plugins: Plugin[] = [...(options.plugins || [])] - const shouldDisableIntrospection = - isProduction && allowIntrospection !== true + const shouldDisableIntrospection = isProduction && allowIntrospection !== true const validationPlugins: Plugin[] = [] @@ -145,10 +131,7 @@ export const WobeGraphqlYogaPlugin = ({ const getResponse = async () => { if (!graphqlMiddleware) return yoga.handle(context.request, context) - return graphqlMiddleware( - async () => yoga.handle(context.request, context), - context.res, - ) + return graphqlMiddleware(async () => yoga.handle(context.request, context), context.res) } const response = await resolveWithTimeout(getResponse, timeoutMs) @@ -174,14 +157,10 @@ export const WobeGraphqlYogaPlugin = ({ return (wobe: Wobe) => { if (allowGetRequests) { - wobe.get(graphqlEndpoint, async (context) => - handleGraphQLRequest(context), - ) + wobe.get(graphqlEndpoint, async (context) => handleGraphQLRequest(context)) } - wobe.post(graphqlEndpoint, async (context) => - handleGraphQLRequest(context), - ) + wobe.post(graphqlEndpoint, async (context) => handleGraphQLRequest(context)) } } @@ -189,28 +168,16 @@ const createDepthLimitRule = (maxDepth: number): ValidationRule => { return (context) => { const checkDepth = (depth: number) => { if (depth > maxDepth) { - context.reportError( - new GraphQLError( - `Query is too deep: ${depth} > max depth ${maxDepth}`, - ), - ) + context.reportError(new GraphQLError(`Query is too deep: ${depth} > max depth ${maxDepth}`)) } } - const traverse = ( - selectionSet: any, - depth: number, - visitedFragments: Set, - ) => { + const traverse = (selectionSet: any, depth: number, visitedFragments: Set) => { checkDepth(depth) for (const selection of selectionSet.selections || []) { if (selection.selectionSet) { - traverse( - selection.selectionSet, - depth + 1, - visitedFragments, - ) + traverse(selection.selectionSet, depth + 1, visitedFragments) continue } @@ -220,11 +187,7 @@ const createDepthLimitRule = (maxDepth: number): ValidationRule => { visitedFragments.add(name) const fragment = context.getFragment(name) if (fragment) { - traverse( - fragment.selectionSet, - depth + 1, - visitedFragments, - ) + traverse(fragment.selectionSet, depth + 1, visitedFragments) } } } @@ -242,18 +205,12 @@ const createCostLimitRule = (maxCost: number): ValidationRule => { return (context) => { let totalCost = 0 - const countSelections = ( - selectionSet: any, - visitedFragments: Set, - ): number => { + const countSelections = (selectionSet: any, visitedFragments: Set): number => { let cost = 0 for (const selection of selectionSet.selections || []) { cost += 1 if (selection.selectionSet) { - cost += countSelections( - selection.selectionSet, - visitedFragments, - ) + cost += countSelections(selection.selectionSet, visitedFragments) continue } if (selection.kind === 'FragmentSpread') { @@ -262,10 +219,7 @@ const createCostLimitRule = (maxCost: number): ValidationRule => { visitedFragments.add(name) const fragment = context.getFragment(name) if (fragment) { - cost += countSelections( - fragment.selectionSet, - visitedFragments, - ) + cost += countSelections(fragment.selectionSet, visitedFragments) } } } @@ -275,18 +229,13 @@ const createCostLimitRule = (maxCost: number): ValidationRule => { return { OperationDefinition(node) { const visitedFragments = new Set() - totalCost += countSelections( - node.selectionSet, - visitedFragments, - ) + totalCost += countSelections(node.selectionSet, visitedFragments) }, Document: { leave() { if (totalCost > maxCost) { context.reportError( - new GraphQLError( - `Query is too expensive: ${totalCost} > max cost ${maxCost}`, - ), + new GraphQLError(`Query is too expensive: ${totalCost} > max cost ${maxCost}`), ) } }, @@ -316,9 +265,7 @@ const createOperationConstraintsRule = ({ !allowedOperationNames.includes(name) ) { context.reportError( - new GraphQLError( - `Operation "${name}" is not allowed in this endpoint.`, - ), + new GraphQLError(`Operation "${name}" is not allowed in this endpoint.`), ) } } @@ -327,9 +274,7 @@ const createOperationConstraintsRule = ({ leave() { if (!allowMultipleOperations && seenOperations.length > 1) { context.reportError( - new GraphQLError( - 'Multiple operations are not allowed in this endpoint.', - ), + new GraphQLError('Multiple operations are not allowed in this endpoint.'), ) } }, @@ -348,10 +293,7 @@ const resolveWithTimeout = async ( const timeoutPromise = new Promise((resolveTimeout) => { timeoutId = setTimeout( - () => - resolveTimeout( - new Response('Request Timeout', { status: 504 }), - ), + () => resolveTimeout(new Response('Request Timeout', { status: 504 })), timeoutMs, ) }) diff --git a/packages/wobe-validator/package.json b/packages/wobe-validator/package.json index 36462da..087bfd7 100644 --- a/packages/wobe-validator/package.json +++ b/packages/wobe-validator/package.json @@ -2,33 +2,33 @@ "name": "wobe-validator", "version": "1.0.1", "description": "Validator plugin for Wobe (official)", + "keywords": [ + "validator", + "wobe" + ], "homepage": "https://wobe.dev", + "license": "MIT", "author": { "name": "coratgerl", "url": "https://github.com/coratgerl" }, - "keywords": [ - "validator", - "wobe" - ], "repository": { "type": "git", "url": "https://github.com/palixir/wobe" }, - "license": "MIT", "main": "dist/index.js", + "scripts": { + "build": "bun build --outdir dist $(pwd)/src/index.ts --target=bun --external=* && bun generate:types", + "generate:types": "bun tsc --project .", + "lint": "oxlint . ", + "ci": "bun lint $(pwd) && bun test src", + "dev": "bun run --watch dev/index.ts" + }, "dependencies": { "@sinclair/typebox": "0.32.27", "wobe": "*" }, "devDependencies": { "get-port": "7.0.0" - }, - "scripts": { - "build": "bun build --outdir dist $(pwd)/src/index.ts --target=bun --external=* && bun generate:types", - "generate:types": "bun tsc --project .", - "lint": "biome lint . --no-errors-on-unmatched ", - "ci": "bun lint $(pwd) && bun test src", - "dev": "bun run --watch dev/index.ts" } } diff --git a/packages/wobe/README.md b/packages/wobe/README.md index 1425159..f9091b8 100644 --- a/packages/wobe/README.md +++ b/packages/wobe/README.md @@ -30,18 +30,16 @@ import { Wobe } from 'wobe' const app = new Wobe() .get('/hello', (context) => context.res.sendText('Hello world')) - .get('/hello/:name', (context) => - context.res.sendText(`Hello ${context.params.name}`), - ) + .get('/hello/:name', (context) => context.res.sendText(`Hello ${context.params.name}`)) .listen(3000) ``` ## Features -- **Simple & Easy to use**: Wobe respects the standard and provides a large ecosystem. -- **Fast & Lightweight**: Wobe is one of the fastest web framework on Bun, and it has 0 dependencies (only 9,76 KB). -- **Multi-runtime**: Wobe supports Node.js and Bun runtime. -- **Easy to extend**: Wobe has an easy-to-use plugin system that allows extending for all your personal use cases. +- **Simple & Easy to use**: Wobe respects the standard and provides a large ecosystem. +- **Fast & Lightweight**: Wobe is one of the fastest web framework on Bun, and it has 0 dependencies (only 9,76 KB). +- **Multi-runtime**: Wobe supports Node.js and Bun runtime. +- **Easy to extend**: Wobe has an easy-to-use plugin system that allows extending for all your personal use cases. ## Benchmarks (on Bun runtime) diff --git a/packages/wobe/fixtures/testFile.html b/packages/wobe/fixtures/testFile.html index b7d4fff..91de8e6 100644 --- a/packages/wobe/fixtures/testFile.html +++ b/packages/wobe/fixtures/testFile.html @@ -1,3 +1,3 @@ -testfile + testfile diff --git a/packages/wobe/package.json b/packages/wobe/package.json index af57835..bc22b44 100644 --- a/packages/wobe/package.json +++ b/packages/wobe/package.json @@ -2,33 +2,33 @@ "name": "wobe", "version": "1.1.14", "description": "A fast, lightweight and simple web framework", + "keywords": [ + "bun", + "server", + "wobe" + ], "homepage": "https://wobe.dev", + "license": "MIT", "author": { "name": "coratgerl", "url": "https://github.com/coratgerl" }, - "license": "MIT", - "keywords": [ - "server", - "bun", - "wobe" - ], "repository": { "type": "git", "url": "git+https://github.com/palixir/wobe" }, "main": "dist/index.js", - "devDependencies": { - "get-port": "7.0.0" - }, "scripts": { "build": "bun build --outdir dist $(pwd)/src/index.ts --target=bun && bun generate:types", "generate:types": "bun tsc --project .", - "lint": "biome lint . --no-errors-on-unmatched ", + "lint": "oxlint .", "ci": "bun lint $(pwd) && bun run test:bun src && bun test:node src", - "format": "biome format --write .", + "format": "oxfmt --write .", "test:bun": "NODE_TLS_REJECT_UNAUTHORIZED=0 bun test", "test:node": "NODE_TLS_REJECT_UNAUTHORIZED=0 NODE_TEST='true' bun test", "dev": "bun run --watch dev/index.ts" + }, + "devDependencies": { + "get-port": "7.0.0" } } diff --git a/packages/wobe/src/Context.test.ts b/packages/wobe/src/Context.test.ts index 3b4335b..e7ae49d 100644 --- a/packages/wobe/src/Context.test.ts +++ b/packages/wobe/src/Context.test.ts @@ -62,9 +62,7 @@ describe('Context', () => { context.redirect('https://example.com/test') - expect(context.res.headers.get('Location')).toEqual( - 'https://example.com/test', - ) + expect(context.res.headers.get('Location')).toEqual('https://example.com/test') expect(context.res.status).toEqual(302) expect(spyContextRes).toHaveBeenCalledTimes(1) @@ -73,9 +71,7 @@ describe('Context', () => { // Redirect permanently context.redirect('https://example.com/test2', 301) - expect(context.res.headers.get('Location')).toEqual( - 'https://example.com/test2', - ) + expect(context.res.headers.get('Location')).toEqual('https://example.com/test2') expect(context.res.status).toEqual(301) }) diff --git a/packages/wobe/src/Context.ts b/packages/wobe/src/Context.ts index fba3d60..3dfd104 100644 --- a/packages/wobe/src/Context.ts +++ b/packages/wobe/src/Context.ts @@ -26,14 +26,9 @@ export class Context { } private _findRoute(router?: Router) { - const { pathName, searchParams } = extractPathnameAndSearchParams( - this.request.url, - ) + const { pathName, searchParams } = extractPathnameAndSearchParams(this.request.url) - const route = router?.findRoute( - this.request.method as HttpMethod, - pathName, - ) + const route = router?.findRoute(this.request.method as HttpMethod, pathName) this.query = searchParams || {} this.pathname = pathName @@ -61,8 +56,7 @@ export class Context { async executeHandler() { this.state = 'beforeHandler' // We need to run hook sequentially - for (const hookBeforeHandler of this.beforeHandlerHook) - await hookBeforeHandler(this) + for (const hookBeforeHandler of this.beforeHandlerHook) await hookBeforeHandler(this) const resultHandler = await this.handler?.(this) @@ -85,13 +79,9 @@ export class Context { if (!cookieHeader) return undefined - const split = cookieHeader - .split(';') - .map((cookie) => cookie.replaceAll(' ', '')) + const split = cookieHeader.split(';').map((cookie) => cookie.replaceAll(' ', '')) - const existingCookie = split.find( - (element) => element.split('=')[0] === name, - ) + const existingCookie = split.find((element) => element.split('=')[0] === name) if (!existingCookie) return undefined diff --git a/packages/wobe/src/Wobe.test.ts b/packages/wobe/src/Wobe.test.ts index e89a51a..2e20add 100644 --- a/packages/wobe/src/Wobe.test.ts +++ b/packages/wobe/src/Wobe.test.ts @@ -1,13 +1,4 @@ -import { - describe, - expect, - it, - beforeAll, - afterAll, - mock, - spyOn, - afterEach, -} from 'bun:test' +import { describe, expect, it, beforeAll, afterAll, mock, spyOn, afterEach } from 'bun:test' import getPort from 'get-port' import { Wobe } from './Wobe' import { HttpException } from './HttpException' @@ -129,9 +120,7 @@ describe('Wobe', () => { throw new Error('Test error') }) .get('/testHttpExceptionError', () => { - throw new HttpException( - new Response('Test error', { status: 400 }), - ) + throw new HttpException(new Response('Test error', { status: 400 })) }) .get('/1', (ctx) => { ctx.res.headers.set('X-Test', 'Test') @@ -147,27 +136,18 @@ describe('Wobe', () => { return ctx.res.send('OK') }) .get('/upload', async () => { - return new Response( - Bun.file(`${__dirname}/../fixtures/testFile.html`), - { - headers: { - 'Content-Type': 'text/html', - }, + return new Response(Bun.file(`${__dirname}/../fixtures/testFile.html`), { + headers: { + 'Content-Type': 'text/html', }, - ) + }) }) .options('/options', async (ctx) => { mockOptions() return ctx.res.send('OK') }) - .beforeHandler( - '/test/', - csrf({ origin: `http://127.0.0.1:${port}` }), - ) - .beforeHandler( - '/test', - csrf({ origin: `http://127.0.0.1:${port}` }), - ) + .beforeHandler('/test/', csrf({ origin: `http://127.0.0.1:${port}` })) + .beforeHandler('/test', csrf({ origin: `http://127.0.0.1:${port}` })) .beforeAndAfterHandler(logger()) .beforeHandler('/testBearer', bearerAuth({ token: '123' })) .beforeHandler('/test/*', mockHookWithWildcardRoute) @@ -216,31 +196,25 @@ describe('Wobe', () => { mockOptions.mockClear() }) - it.skipIf(process.env.NODE_TEST !== 'true')( - 'should call Node runtime adapter', - async () => { - const spyNodeAdapter = spyOn(nodeAdapter, 'NodeAdapter') + it.skipIf(process.env.NODE_TEST !== 'true')('should call Node runtime adapter', async () => { + const spyNodeAdapter = spyOn(nodeAdapter, 'NodeAdapter') - const wobe = new Wobe().listen(5555) + const wobe = new Wobe().listen(5555) - expect(spyNodeAdapter).toHaveBeenCalledTimes(1) + expect(spyNodeAdapter).toHaveBeenCalledTimes(1) - wobe.stop() - }, - ) + wobe.stop() + }) - it.skipIf(process.env.NODE_TEST === 'true')( - 'should call Bun runtime adapter', - async () => { - const spyBunAdapter = spyOn(bunAdapter, 'BunAdapter') + it.skipIf(process.env.NODE_TEST === 'true')('should call Bun runtime adapter', async () => { + const spyBunAdapter = spyOn(bunAdapter, 'BunAdapter') - const wobe = new Wobe().listen(5555) + const wobe = new Wobe().listen(5555) - expect(spyBunAdapter).toHaveBeenCalledTimes(1) + expect(spyBunAdapter).toHaveBeenCalledTimes(1) - wobe.stop() - }, - ) + wobe.stop() + }) // Waiting https://github.com/oven-sh/bun/pull/10266 it.skip('should return the good statusText of the response', async () => { @@ -255,22 +229,16 @@ describe('Wobe', () => { expect(res.statusText).toBe('Test') }) - it.skipIf(process.env.NODE_TEST === 'true')( - 'should upload a file', - async () => { - const res = await fetch(`http://127.0.0.1:${port}/upload`) + it.skipIf(process.env.NODE_TEST === 'true')('should upload a file', async () => { + const res = await fetch(`http://127.0.0.1:${port}/upload`) - expect(await res.text()).toBe('\ntestfile\n\n') - expect(res.headers.get('Content-Type')).toBe('text/html') - expect(res.status).toBe(200) - }, - ) + expect(await res.text()).toBe('\n\ttestfile\n\n') + expect(res.headers.get('Content-Type')).toBe('text/html') + expect(res.status).toBe(200) + }) it('should listen on different hostname', async () => { - const wobeTest = new Wobe({ hostname: '0.0.0.0' }).get( - '/health', - (ctx) => ctx.res.send('ok'), - ) + const wobeTest = new Wobe({ hostname: '0.0.0.0' }).get('/health', (ctx) => ctx.res.send('ok')) wobeTest.listen(5555) @@ -344,12 +312,9 @@ describe('Wobe', () => { expect(listenPort).toBe(localPort) }) - const localWobe = new Wobe().listen( - localPort, - ({ hostname, port: listenPort }) => { - mockCallback(hostname, listenPort) - }, - ) + const localWobe = new Wobe().listen(localPort, ({ hostname, port: listenPort }) => { + mockCallback(hostname, listenPort) + }) expect(mockCallback).toHaveBeenCalledTimes(1) @@ -357,23 +322,17 @@ describe('Wobe', () => { }) it('should works with request cache (same request but different body)', async () => { - const res = await fetch( - `http://127.0.0.1:${port}/testRequestBodyCache`, - { - method: 'POST', - body: '1', - }, - ) + const res = await fetch(`http://127.0.0.1:${port}/testRequestBodyCache`, { + method: 'POST', + body: '1', + }) expect(await res.text()).toBe('1') - const res2 = await fetch( - `http://127.0.0.1:${port}/testRequestBodyCache`, - { - method: 'POST', - body: '2', - }, - ) + const res2 = await fetch(`http://127.0.0.1:${port}/testRequestBodyCache`, { + method: 'POST', + body: '2', + }) expect(await res2.text()).toBe('2') }) @@ -396,9 +355,7 @@ describe('Wobe', () => { expect(await res.text()).toBe('Test error') expect(res.status).toBe(500) - const res2 = await fetch( - `http://127.0.0.1:${port}/testHttpExceptionError`, - ) + const res2 = await fetch(`http://127.0.0.1:${port}/testHttpExceptionError`) expect(await res2.text()).toBe('Test error') expect(res2.status).toBe(400) @@ -412,9 +369,7 @@ describe('Wobe', () => { }) it('should have the correct state if there is afterHandler middleware (with context cache)', async () => { - const res = await fetch( - `http://127.0.0.1:${port}/testAfterHandlerCache`, - ) + const res = await fetch(`http://127.0.0.1:${port}/testAfterHandlerCache`) expect(await res.text()).toBe('afterHandler') @@ -467,9 +422,7 @@ describe('Wobe', () => { expect(res.status).toBe(401) expect(res.statusText).toBe('Unauthorized') - expect(res.headers.get('WWW-Authenticate')).toEqual( - 'Bearer realm="", error="invalid_token"', - ) + expect(res.headers.get('WWW-Authenticate')).toEqual('Bearer realm="", error="invalid_token"') }) it('should not block requests with valid origin', async () => { @@ -524,9 +477,7 @@ describe('Wobe', () => { expect(await res.text()).toEqual('1') - const res2 = await fetch( - `http://127.0.0.1:${port}/route/1/name?test=bun`, - ) + const res2 = await fetch(`http://127.0.0.1:${port}/route/1/name?test=bun`) expect(await res2.text()).toEqual('bun') }) @@ -660,17 +611,13 @@ describe('Wobe', () => { // @ts-expect-error expect(mockHook.mock.calls[0][0].request.method).toBe('GET') // @ts-expect-error - expect(mockHook.mock.calls[0][0].request.url).toBe( - `http://127.0.0.1:${port}/testGet`, - ) + expect(mockHook.mock.calls[0][0].request.url).toBe(`http://127.0.0.1:${port}/testGet`) expect(mockSecondHook).toHaveBeenCalledTimes(1) // @ts-expect-error expect(mockSecondHook.mock.calls[0][0].request.method).toBe('GET') // @ts-expect-error - expect(mockSecondHook.mock.calls[0][0].request.url).toBe( - `http://127.0.0.1:${port}/testGet`, - ) + expect(mockSecondHook.mock.calls[0][0].request.url).toBe(`http://127.0.0.1:${port}/testGet`) expect(mockOnlyOnTestGet).toHaveBeenCalledTimes(1) }) diff --git a/packages/wobe/src/Wobe.ts b/packages/wobe/src/Wobe.ts index b07a51e..3d72da1 100644 --- a/packages/wobe/src/Wobe.ts +++ b/packages/wobe/src/Wobe.ts @@ -38,12 +38,7 @@ export interface WobeOptions { export type HttpMethod = 'POST' | 'GET' | 'DELETE' | 'PUT' | 'ALL' | 'OPTIONS' -export type WobeHandlerOutput = - | void - | Promise - | undefined - | Response - | Promise +export type WobeHandlerOutput = void | Promise | undefined | Response | Promise export type WobeHandler = (ctx: Context & T) => WobeHandlerOutput @@ -78,17 +73,12 @@ export interface WobeWebSocket { beforeWebSocketUpgrade?: Array> onOpen?(ws: ServerWebSocket): void onMessage?(ws: ServerWebSocket, message: string | Buffer): void - onClose?( - ws: ServerWebSocket, - code: number, - message: string | Buffer, - ): void + onClose?(ws: ServerWebSocket, code: number, message: string | Buffer): void onDrain?(ws: ServerWebSocket): void } const factoryOfRuntime = (): RuntimeAdapter => { - if (typeof Bun !== 'undefined' && !process.env.NODE_TEST) - return BunAdapter() + if (typeof Bun !== 'undefined' && !process.env.NODE_TEST) return BunAdapter() return NodeAdapter() } @@ -107,12 +97,7 @@ export class Wobe { }> private router: Router private runtimeAdapter: RuntimeAdapter = factoryOfRuntime() - private httpMethods: Array = [ - 'GET', - 'POST', - 'PUT', - 'DELETE', - ] as const + private httpMethods: Array = ['GET', 'POST', 'PUT', 'DELETE'] as const private webSocket: WobeWebSocket | undefined = undefined @@ -205,9 +190,7 @@ export class Wobe { */ all(path: string, handler: WobeHandler, hook?: WobeHandler) { if (hook) { - this.httpMethods.map((method) => - this._addHook('beforeHandler', method)(path, hook), - ) + this.httpMethods.map((method) => this._addHook('beforeHandler', method)(path, hook)) } this.router.addRoute('ALL', path, handler) @@ -243,10 +226,7 @@ export class Wobe { * @param arg1 The path of the request or the handler * @param handlers The handlers of the request */ - beforeAndAfterHandler( - arg1: string | WobeHandler, - ...handlers: WobeHandler[] - ) { + beforeAndAfterHandler(arg1: string | WobeHandler, ...handlers: WobeHandler[]) { this.httpMethods.map((method) => this._addHook('beforeAndAfterHandler', method)(arg1, ...handlers), ) @@ -259,13 +239,8 @@ export class Wobe { * @param arg1 The path of the request or the handler * @param handlers The handlers of the request */ - beforeHandler( - arg1: string | WobeHandler, - ...handlers: WobeHandler[] - ) { - this.httpMethods.map((method) => - this._addHook('beforeHandler', method)(arg1, ...handlers), - ) + beforeHandler(arg1: string | WobeHandler, ...handlers: WobeHandler[]) { + this.httpMethods.map((method) => this._addHook('beforeHandler', method)(arg1, ...handlers)) return this } @@ -276,9 +251,7 @@ export class Wobe { * @param handlers The handlers of the request */ afterHandler(arg1: string | WobeHandler, ...handlers: WobeHandler[]) { - this.httpMethods.map((method) => - this._addHook('afterHandler', method)(arg1, ...handlers), - ) + this.httpMethods.map((method) => this._addHook('afterHandler', method)(arg1, ...handlers)) return this } @@ -321,19 +294,11 @@ export class Wobe { * @param port The port of the server * @param callback The callback to execute after the server is started */ - listen( - port: number, - callback?: (options: { hostname: string; port: number }) => void, - ) { + listen(port: number, callback?: (options: { hostname: string; port: number }) => void) { // We need to add all hooks after the compilation // because the tree need to be complete for (const hook of this.hooks) { - this.router.addHook( - hook.hook, - hook.pathname, - hook.handler, - hook.method, - ) + this.router.addHook(hook.hook, hook.pathname, hook.handler, hook.method) } this.router.optimizeTree() diff --git a/packages/wobe/src/WobeResponse.test.ts b/packages/wobe/src/WobeResponse.test.ts index 72af6f6..c0e54ec 100644 --- a/packages/wobe/src/WobeResponse.test.ts +++ b/packages/wobe/src/WobeResponse.test.ts @@ -10,9 +10,8 @@ describe('Wobe Response', () => { ) const fileContent = new Uint8Array([ - 71, 73, 70, 56, 57, 97, 1, 0, 1, 0, 128, 255, 0, 192, 192, 192, 0, - 0, 0, 33, 249, 4, 1, 0, 0, 0, 0, 44, 0, 0, 0, 0, 1, 0, 1, 0, 0, 2, - 2, 68, 1, 0, 59, + 71, 73, 70, 56, 57, 97, 1, 0, 1, 0, 128, 255, 0, 192, 192, 192, 0, 0, 0, 33, 249, 4, 1, 0, 0, + 0, 0, 44, 0, 0, 0, 0, 1, 0, 1, 0, 0, 2, 2, 68, 1, 0, 59, ]).buffer const response = wobeResponse.send(fileContent, { @@ -61,9 +60,7 @@ describe('Wobe Response', () => { ) const sharedBuffer = new SharedArrayBuffer(16) const sharedArray = new Uint8Array(sharedBuffer) - sharedArray.set([ - 71, 73, 70, 56, 57, 97, 1, 0, 1, 0, 128, 255, 0, 192, 192, 192, - ]) + sharedArray.set([71, 73, 70, 56, 57, 97, 1, 0, 1, 0, 128, 255, 0, 192, 192, 192]) const response = wobeResponse.send(sharedBuffer, { headers: { 'Content-Type': 'image/gif', @@ -77,9 +74,7 @@ describe('Wobe Response', () => { const responseArrayBuffer = await response.arrayBuffer() expect(new Uint8Array(responseArrayBuffer)).toEqual( - new Uint8Array([ - 71, 73, 70, 56, 57, 97, 1, 0, 1, 0, 128, 255, 0, 192, 192, 192, - ]), + new Uint8Array([71, 73, 70, 56, 57, 97, 1, 0, 1, 0, 128, 255, 0, 192, 192, 192]), ) }) @@ -97,15 +92,11 @@ describe('Wobe Response', () => { }) expect(response.status).toBe(200) - expect(response.headers.get('Content-Type')).toBe( - 'application/octet-stream', - ) + expect(response.headers.get('Content-Type')).toBe('application/octet-stream') const responseArrayBuffer = await response.arrayBuffer() - expect(new Uint8Array(responseArrayBuffer)).toEqual( - new Uint8Array([1, 2, 3, 4, 5]), - ) + expect(new Uint8Array(responseArrayBuffer)).toEqual(new Uint8Array([1, 2, 3, 4, 5])) }) it('should handle empty ArrayBuffer', async () => { @@ -121,9 +112,7 @@ describe('Wobe Response', () => { }, }) expect(response.status).toBe(200) - expect(response.headers.get('Content-Type')).toBe( - 'application/octet-stream', - ) + expect(response.headers.get('Content-Type')).toBe('application/octet-stream') const responseArrayBuffer = await response.arrayBuffer() expect(responseArrayBuffer.byteLength).toBe(0) }) @@ -141,9 +130,7 @@ describe('Wobe Response', () => { }, }) expect(response.status).toBe(200) - expect(response.headers.get('Content-Type')).toBe( - 'application/octet-stream', - ) + expect(response.headers.get('Content-Type')).toBe('application/octet-stream') const responseArrayBuffer = await response.arrayBuffer() expect(responseArrayBuffer.byteLength).toBe(0) }) @@ -163,15 +150,11 @@ describe('Wobe Response', () => { }, }) expect(response.status).toBe(200) - expect(response.headers.get('Content-Type')).toBe( - 'application/octet-stream', - ) + expect(response.headers.get('Content-Type')).toBe('application/octet-stream') const responseArrayBuffer = await response.arrayBuffer() - expect( - new Uint8Array(responseArrayBuffer).every((val) => val === 42), - ).toBeTrue() + expect(new Uint8Array(responseArrayBuffer).every((val) => val === 42)).toBeTrue() }) it('should send null content and handle gracefully', async () => { @@ -192,9 +175,7 @@ describe('Wobe Response', () => { }) it('should clone a Response into a WobeResponse instance', () => { - const wobeResponse = new WobeResponse( - new Request('http://localhost:3000/test'), - ) + const wobeResponse = new WobeResponse(new Request('http://localhost:3000/test')) wobeResponse.headers.set('X-Tata', 'tata') wobeResponse.setCookie('cookieName', 'cookieValue') @@ -206,24 +187,18 @@ describe('Wobe Response', () => { expect(clonedWobeResponse.headers.get('X-Test')).toBe('test') expect(clonedWobeResponse.headers.get('X-Tata')).toBe('tata') - expect(clonedWobeResponse.headers.get('Set-Cookie')).toBe( - 'cookieName=cookieValue;', - ) + expect(clonedWobeResponse.headers.get('Set-Cookie')).toBe('cookieName=cookieValue;') expect(clonedWobeResponse.response?.status).toBe(200) }) it('should set an empty header value', () => { - const wobeResponse = new WobeResponse( - new Request('http://localhost:3000/test'), - ) + const wobeResponse = new WobeResponse(new Request('http://localhost:3000/test')) wobeResponse.headers.set('X-Test', '') expect(wobeResponse.headers.get('X-Test')).toBe('') }) it('should send text with correct headers and add another header', () => { - const wobeResponse = new WobeResponse( - new Request('http://localhost:3000/test'), - ) + const wobeResponse = new WobeResponse(new Request('http://localhost:3000/test')) wobeResponse.headers.set('X-Test', 'test') @@ -235,9 +210,7 @@ describe('Wobe Response', () => { }) it('should set a cookie in a response', () => { - const wobeResponse = new WobeResponse( - new Request('http://localhost:3000/test'), - ) + const wobeResponse = new WobeResponse(new Request('http://localhost:3000/test')) wobeResponse.setCookie('titi', 'test', { httpOnly: true, @@ -257,37 +230,27 @@ describe('Wobe Response', () => { }) it('should reject invalid cookie name', () => { - const wobeResponse = new WobeResponse( - new Request('http://localhost:3000/test'), - ) + const wobeResponse = new WobeResponse(new Request('http://localhost:3000/test')) expect(() => wobeResponse.setCookie('bad name', 'value')).toThrow() }) it('should reject cookie value with CRLF', () => { - const wobeResponse = new WobeResponse( - new Request('http://localhost:3000/test'), - ) + const wobeResponse = new WobeResponse(new Request('http://localhost:3000/test')) expect(() => wobeResponse.setCookie('safe', 'val\r\nue')).toThrow() }) it('should encode dangerous cookie value', () => { - const wobeResponse = new WobeResponse( - new Request('http://localhost:3000/test'), - ) + const wobeResponse = new WobeResponse(new Request('http://localhost:3000/test')) wobeResponse.setCookie('safe', 'value;inject') - expect(wobeResponse.headers?.get('Set-Cookie')).toBe( - 'safe=value%3Binject;', - ) + expect(wobeResponse.headers?.get('Set-Cookie')).toBe('safe=value%3Binject;') }) it('should delete a cookie from a response', () => { - const wobeResponse = new WobeResponse( - new Request('http://localhost:3000/test'), - ) + const wobeResponse = new WobeResponse(new Request('http://localhost:3000/test')) wobeResponse.setCookie('tata', 'tata') @@ -299,9 +262,7 @@ describe('Wobe Response', () => { }) it('should delete two cookies from response', () => { - const wobeResponse = new WobeResponse( - new Request('http://localhost:3000/test'), - ) + const wobeResponse = new WobeResponse(new Request('http://localhost:3000/test')) wobeResponse.setCookie('tata', 'tata') wobeResponse.setCookie('titi', 'titi') @@ -428,10 +389,7 @@ describe('Wobe Response', () => { }), ) - const response = wobeResponse.send( - { a: 1, b: 2 }, - { status: 201, statusText: 'Created' }, - ) + const response = wobeResponse.send({ a: 1, b: 2 }, { status: 201, statusText: 'Created' }) expect(response.status).toBe(201) expect(response.statusText).toBe('Created') diff --git a/packages/wobe/src/WobeResponse.ts b/packages/wobe/src/WobeResponse.ts index 699e135..c08ba9a 100644 --- a/packages/wobe/src/WobeResponse.ts +++ b/packages/wobe/src/WobeResponse.ts @@ -20,8 +20,7 @@ export class WobeResponse { } private static sanitizeCookieValue(value: string) { - if (/[\r\n]/.test(value)) - throw new Error('Invalid cookie value: contains CR/LF') + if (/[\r\n]/.test(value)) throw new Error('Invalid cookie value: contains CR/LF') if (value.includes(';')) return encodeURIComponent(value) // avoid header injection @@ -42,8 +41,7 @@ export class WobeResponse { wobeResponse.headers = new Headers(response.headers) - for (const [key, value] of this.headers.entries()) - wobeResponse.headers.set(key, value) + for (const [key, value] of this.headers.entries()) wobeResponse.headers.set(key, value) wobeResponse.status = response.status wobeResponse.statusText = response.statusText @@ -59,23 +57,14 @@ export class WobeResponse { * @param options The options of the cookie */ setCookie(name: string, value: string, options?: SetCookieOptions) { - if (!WobeResponse.isCookieNameValid(name)) - throw new Error('Invalid cookie name') + if (!WobeResponse.isCookieNameValid(name)) throw new Error('Invalid cookie name') const safeValue = WobeResponse.sanitizeCookieValue(value) let cookie = `${name}=${safeValue};` if (options) { - const { - httpOnly, - path, - domain, - expires, - sameSite, - maxAge, - secure, - } = options + const { httpOnly, path, domain, expires, sameSite, maxAge, secure } = options if (httpOnly) cookie = `${cookie} HttpOnly;` if (path) cookie = `${cookie} Path=${path};` @@ -174,11 +163,7 @@ export class WobeResponse { } else if (content instanceof SharedArrayBuffer) { body = new Uint8Array(content) } else if (content instanceof Buffer) { - body = new Uint8Array( - content.buffer, - content.byteOffset, - content.byteLength, - ) + body = new Uint8Array(content.buffer, content.byteOffset, content.byteLength) } else if (typeof content === 'object') { this.headers.set('content-type', 'application/json') this.headers.set('charset', 'utf-8') diff --git a/packages/wobe/src/adapters/bun/bun.test.ts b/packages/wobe/src/adapters/bun/bun.test.ts index 32607d4..7aeeee7 100644 --- a/packages/wobe/src/adapters/bun/bun.test.ts +++ b/packages/wobe/src/adapters/bun/bun.test.ts @@ -19,9 +19,7 @@ describe.skipIf(process.env.NODE_TEST === 'true')('Bun server', () => { wobe.get('/hi', async (ctx) => { if (ctx.res.status === 201) { - throw new HttpException( - new Response('Status should be equal to 200'), - ) + throw new HttpException(new Response('Status should be equal to 200')) } ctx.res.sendText('Hi') @@ -67,12 +65,8 @@ describe.skipIf(process.env.NODE_TEST === 'true')('Bun server', () => { const port = await getPort() - const key = await Bun.file( - `${import.meta.dirname}/../../../fixtures/key.pem`, - ).text() - const cert = await Bun.file( - `${import.meta.dirname}/../../../fixtures/cert.pem`, - ).text() + const key = await Bun.file(`${import.meta.dirname}/../../../fixtures/key.pem`).text() + const cert = await Bun.file(`${import.meta.dirname}/../../../fixtures/cert.pem`).text() const wobe = new Wobe({ tls: { @@ -139,9 +133,7 @@ describe.skipIf(process.env.NODE_TEST === 'true')('Bun server', () => { const port = await getPort() const wobe = new Wobe({ maxBodySize: 8 }) - wobe.post('/echo', async (ctx) => - ctx.res.sendText(await ctx.request.text()), - ) + wobe.post('/echo', async (ctx) => ctx.res.sendText(await ctx.request.text())) wobe.listen(port) @@ -183,9 +175,7 @@ describe.skipIf(process.env.NODE_TEST === 'true')('Bun server', () => { allowedContentEncodings: ['gzip'], }) - wobe.post('/echo', async (ctx) => - ctx.res.sendText(await ctx.request.text()), - ) + wobe.post('/echo', async (ctx) => ctx.res.sendText(await ctx.request.text())) wobe.listen(port) diff --git a/packages/wobe/src/adapters/bun/bun.ts b/packages/wobe/src/adapters/bun/bun.ts index c637da7..eef92c8 100644 --- a/packages/wobe/src/adapters/bun/bun.ts +++ b/packages/wobe/src/adapters/bun/bun.ts @@ -23,11 +23,7 @@ const parseForwardedIp = (xff?: string | null) => { return first && first.length <= 100 ? first : undefined } -const decompressBody = ( - encoding: string, - buffer: Uint8Array, - maxBodySize: number, -) => { +const decompressBody = (encoding: string, buffer: Uint8Array, maxBodySize: number) => { const lower = encoding.toLowerCase() if (lower === 'identity' || lower === '') return Buffer.from(buffer) @@ -53,12 +49,7 @@ const decompressBody = ( } export const BunAdapter = (): RuntimeAdapter => ({ - createServer: ( - port: number, - router: Router, - options?: WobeOptions, - webSocket?: WobeWebSocket, - ) => + createServer: (port: number, router: Router, options?: WobeOptions, webSocket?: WobeWebSocket) => Bun.serve({ port, tls: options?.tls, @@ -66,23 +57,17 @@ export const BunAdapter = (): RuntimeAdapter => ({ development: process.env.NODE_ENV !== 'production', websocket: bunWebSocket(webSocket), async fetch(req, server) { - const maxBodySize = - options?.maxBodySize ?? DEFAULT_MAX_BODY_SIZE - const allowedContentEncodings = normalizeEncodings( - options?.allowedContentEncodings, - ) + const maxBodySize = options?.maxBodySize ?? DEFAULT_MAX_BODY_SIZE + const allowedContentEncodings = normalizeEncodings(options?.allowedContentEncodings) const hostHeader = req.headers.get('host') - if (!isHostHeaderValid(hostHeader)) - return new Response(null, { status: 400 }) + if (!isHostHeaderValid(hostHeader)) return new Response(null, { status: 400 }) const expectHeader = req.headers.get('expect') if (expectHeader) return new Response(null, { status: 417 }) try { - const contentEncoding = - req.headers.get('content-encoding')?.toLowerCase() || - 'identity' + const contentEncoding = req.headers.get('content-encoding')?.toLowerCase() || 'identity' if (!allowedContentEncodings.includes(contentEncoding)) return new Response('Unsupported Content-Encoding', { @@ -90,43 +75,26 @@ export const BunAdapter = (): RuntimeAdapter => ({ }) // Validate declared content-length before reading - const contentLengthHeader = - req.headers.get('content-length') || '0' + const contentLengthHeader = req.headers.get('content-length') || '0' const parsedLength = Number(contentLengthHeader) - if ( - !Number.isNaN(parsedLength) && - parsedLength > maxBodySize - ) + if (!Number.isNaN(parsedLength) && parsedLength > maxBodySize) return new Response(null, { status: 413 }) let requestForContext = req - if ( - req.method !== 'GET' && - req.method !== 'HEAD' && - req.method !== 'OPTIONS' - ) { + if (req.method !== 'GET' && req.method !== 'HEAD' && req.method !== 'OPTIONS') { const rawBody = new Uint8Array(await req.arrayBuffer()) - if (rawBody.byteLength > maxBodySize) - return new Response(null, { status: 413 }) + if (rawBody.byteLength > maxBodySize) return new Response(null, { status: 413 }) let decodedBody: Buffer try { - decodedBody = decompressBody( - contentEncoding, - rawBody, - maxBodySize, - ) + decodedBody = decompressBody(contentEncoding, rawBody, maxBodySize) } catch (err: any) { if (err?.message === 'UNSUPPORTED_ENCODING') - return new Response( - 'Unsupported Content-Encoding', - { status: 415 }, - ) - if (err?.message === 'PAYLOAD_TOO_LARGE') - return new Response(null, { status: 413 }) + return new Response('Unsupported Content-Encoding', { status: 415 }) + if (err?.message === 'PAYLOAD_TOO_LARGE') return new Response(null, { status: 413 }) return new Response('Invalid compressed body', { status: 400, @@ -144,9 +112,7 @@ export const BunAdapter = (): RuntimeAdapter => ({ context.getIpAdress = () => { if (options?.trustProxy) { - const forwarded = parseForwardedIp( - req.headers.get('x-forwarded-for'), - ) + const forwarded = parseForwardedIp(req.headers.get('x-forwarded-for')) if (forwarded) return forwarded } @@ -155,8 +121,7 @@ export const BunAdapter = (): RuntimeAdapter => ({ if (webSocket && webSocket.path === context.pathname) { // We need to run hook sequentially - for (const hookBeforeSocketUpgrade of webSocket.beforeWebSocketUpgrade || - []) + for (const hookBeforeSocketUpgrade of webSocket.beforeWebSocketUpgrade || []) await hookBeforeSocketUpgrade(context) if (server.upgrade(req)) return diff --git a/packages/wobe/src/adapters/bun/websocket.test.ts b/packages/wobe/src/adapters/bun/websocket.test.ts index a01e2ba..f1cc6af 100644 --- a/packages/wobe/src/adapters/bun/websocket.test.ts +++ b/packages/wobe/src/adapters/bun/websocket.test.ts @@ -1,12 +1,4 @@ -import { - describe, - expect, - it, - beforeAll, - afterAll, - mock, - beforeEach, -} from 'bun:test' +import { describe, expect, it, beforeAll, afterAll, mock, beforeEach } from 'bun:test' import { Wobe } from '../../Wobe' import getPort from 'get-port' import { bunWebSocket } from './websocket' @@ -22,7 +14,6 @@ const waitWebsocketClosed = (ws: WebSocket) => }) describe.skipIf(process.env.NODE_TEST === 'true')('Bun - websocket', () => { - 4 const mockOnOpen = mock(() => {}) const mockOnMessage = mock(() => {}) const mockOnClose = mock(() => {}) @@ -135,10 +126,7 @@ describe.skipIf(process.env.NODE_TEST === 'true')('Bun - websocket', () => { wobe2 .useWebSocket({ path: '/ws', - beforeWebSocketUpgrade: [ - mockBeforeHandler1, - mockBeforeHandler2, - ], + beforeWebSocketUpgrade: [mockBeforeHandler1, mockBeforeHandler2], }) .listen(port2) @@ -167,10 +155,7 @@ describe.skipIf(process.env.NODE_TEST === 'true')('Bun - websocket', () => { wobe2 .useWebSocket({ path: '/ws', - beforeWebSocketUpgrade: [ - mockBeforeHandler1, - mockBeforeHandler2, - ], + beforeWebSocketUpgrade: [mockBeforeHandler1, mockBeforeHandler2], }) .listen(port2) diff --git a/packages/wobe/src/adapters/bun/websocket.ts b/packages/wobe/src/adapters/bun/websocket.ts index 990494f..921252f 100644 --- a/packages/wobe/src/adapters/bun/websocket.ts +++ b/packages/wobe/src/adapters/bun/websocket.ts @@ -1,9 +1,7 @@ import type { WebSocketHandler } from 'bun' import type { WobeWebSocket } from '../../Wobe' -export const bunWebSocket = ( - webSocket?: WobeWebSocket, -): WebSocketHandler => { +export const bunWebSocket = (webSocket?: WobeWebSocket): WebSocketHandler => { return { perMessageDeflate: webSocket?.compression, maxPayloadLength: webSocket?.maxPayloadLength, diff --git a/packages/wobe/src/adapters/node/node.test.ts b/packages/wobe/src/adapters/node/node.test.ts index 9ad1199..12a36a4 100644 --- a/packages/wobe/src/adapters/node/node.test.ts +++ b/packages/wobe/src/adapters/node/node.test.ts @@ -23,9 +23,7 @@ describe.skipIf(process.env.NODE_TEST !== 'true')('Node server', () => { wobe.get('/hi', async (ctx) => { if (ctx.res.status === 201) { - throw new HttpException( - new Response('Status should be equal to 200'), - ) + throw new HttpException(new Response('Status should be equal to 200')) } ctx.res.sendText('Hi') @@ -63,12 +61,8 @@ describe.skipIf(process.env.NODE_TEST !== 'true')('Node server', () => { it('should call create server from node:https if https options is not undefined', async () => { const port = await getPort() - const key = await Bun.file( - `${import.meta.dirname}/../../../fixtures/key.pem`, - ).text() - const cert = await Bun.file( - `${import.meta.dirname}/../../../fixtures/cert.pem`, - ).text() + const key = await Bun.file(`${import.meta.dirname}/../../../fixtures/key.pem`).text() + const cert = await Bun.file(`${import.meta.dirname}/../../../fixtures/cert.pem`).text() const wobe = new Wobe({ tls: { diff --git a/packages/wobe/src/adapters/node/node.ts b/packages/wobe/src/adapters/node/node.ts index 67ced0f..1343099 100644 --- a/packages/wobe/src/adapters/node/node.ts +++ b/packages/wobe/src/adapters/node/node.ts @@ -27,20 +27,14 @@ const parseForwardedIp = (xff?: string | string[]) => { const getClientIp = (req: any, trustProxy?: boolean) => { if (trustProxy) { - const forwarded = parseForwardedIp( - req.headers['x-forwarded-for'] as string, - ) + const forwarded = parseForwardedIp(req.headers['x-forwarded-for'] as string) if (forwarded) return forwarded } return req.socket.remoteAddress || '' } -const decompressBody = ( - encoding: string, - buffer: Uint8Array, - maxBodySize: number, -): Uint8Array => { +const decompressBody = (encoding: string, buffer: Uint8Array, maxBodySize: number): Uint8Array => { const lower = encoding.toLowerCase() if (lower === 'identity' || lower === '') return buffer @@ -74,11 +68,9 @@ const transformResponseInstanceToValidResponse = async (response: Response) => { const contentType = response.headers.get('content-type') - if (contentType === 'appplication/json') - return { headers, body: await response.json() } + if (contentType === 'appplication/json') return { headers, body: await response.json() } - if (contentType === 'text/plain') - return { headers, body: await response.text() } + if (contentType === 'text/plain') return { headers, body: await response.text() } const arrayBuffer = await response.arrayBuffer() return { headers, body: Buffer.from(arrayBuffer) } @@ -92,9 +84,7 @@ export const NodeAdapter = (): RuntimeAdapter => ({ : createHttpServer const certificateObject = options?.tls || {} const maxBodySize = options?.maxBodySize ?? DEFAULT_MAX_BODY_SIZE - const allowedContentEncodings = normalizeEncodings( - options?.allowedContentEncodings, - ) + const allowedContentEncodings = normalizeEncodings(options?.allowedContentEncodings) return createServer(certificateObject, async (req, res) => { const url = `http://${req.headers.host}${req.url}` @@ -113,9 +103,7 @@ export const NodeAdapter = (): RuntimeAdapter => ({ } const contentEncoding = - ( - req.headers['content-encoding'] as string | undefined - )?.toLowerCase() || 'identity' + (req.headers['content-encoding'] as string | undefined)?.toLowerCase() || 'identity' if (!allowedContentEncodings.includes(contentEncoding)) { res.writeHead(415) @@ -148,11 +136,7 @@ export const NodeAdapter = (): RuntimeAdapter => ({ const rawBuffer = Buffer.concat(chunks) const decompressed = decompressBody( contentEncoding, - new Uint8Array( - rawBuffer.buffer, - rawBuffer.byteOffset, - rawBuffer.byteLength, - ), + new Uint8Array(rawBuffer.buffer, rawBuffer.byteOffset, rawBuffer.byteLength), maxBodySize, ) @@ -175,19 +159,14 @@ export const NodeAdapter = (): RuntimeAdapter => ({ return } - context.getIpAdress = () => - getClientIp(req, options?.trustProxy) || '' + context.getIpAdress = () => getClientIp(req, options?.trustProxy) || '' const response = await context.executeHandler() const { headers, body: responseBody } = await transformResponseInstanceToValidResponse(response) - res.writeHead( - response.status || 404, - response.statusText, - headers, - ) + res.writeHead(response.status || 404, response.statusText, headers) res.write(responseBody) } catch (err: any) { @@ -214,10 +193,7 @@ export const NodeAdapter = (): RuntimeAdapter => ({ if (!(err instanceof HttpException)) { const statusCode = Number(err.code) || 500 - const message = - err instanceof Error - ? err.message - : 'Internal Server Error' + const message = err instanceof Error ? err.message : 'Internal Server Error' res.writeHead(statusCode) res.write(message) @@ -226,17 +202,12 @@ export const NodeAdapter = (): RuntimeAdapter => ({ return } - const { headers, body: responseBody } = - await transformResponseInstanceToValidResponse( - err.response, - ) - - res.writeHead( - err.response.status || 500, - err.response.statusText, - headers, + const { headers, body: responseBody } = await transformResponseInstanceToValidResponse( + err.response, ) + res.writeHead(err.response.status || 500, err.response.statusText, headers) + res.write(responseBody) } diff --git a/packages/wobe/src/hooks/bearerAuth.ts b/packages/wobe/src/hooks/bearerAuth.ts index d590501..84b6dec 100644 --- a/packages/wobe/src/hooks/bearerAuth.ts +++ b/packages/wobe/src/hooks/bearerAuth.ts @@ -10,8 +10,7 @@ export interface BearerAuthOptions { const prefix = 'Bearer' -const defaultHash = (token: string) => - createHash('sha256').update(token).digest('base64') +const defaultHash = (token: string) => createHash('sha256').update(token).digest('base64') /** * bearerAuth is a hook that checks if the request has a valid Bearer token diff --git a/packages/wobe/src/hooks/bodyLimit.ts b/packages/wobe/src/hooks/bodyLimit.ts index aacbff2..a6a94bc 100644 --- a/packages/wobe/src/hooks/bodyLimit.ts +++ b/packages/wobe/src/hooks/bodyLimit.ts @@ -12,14 +12,10 @@ export const bodyLimit = (options: BodyLimitOptions): WobeHandler => { return (ctx) => { // The content-length header is not always present if (ctx.request.headers.get('Content-Length')) { - const contentLength = Number( - ctx.request.headers.get('Content-Length') || 0, - ) + const contentLength = Number(ctx.request.headers.get('Content-Length') || 0) if (contentLength > options.maxSize) - throw new HttpException( - new Response('Payload too large', { status: 413 }), - ) + throw new HttpException(new Response('Payload too large', { status: 413 })) } } } diff --git a/packages/wobe/src/hooks/cors.test.ts b/packages/wobe/src/hooks/cors.test.ts index 5655c34..7092f2f 100644 --- a/packages/wobe/src/hooks/cors.test.ts +++ b/packages/wobe/src/hooks/cors.test.ts @@ -15,9 +15,7 @@ describe('Cors hook', () => { handler(context) - expect(context.res.headers?.get('Access-Control-Allow-Origin')).toBe( - '*', - ) + expect(context.res.headers?.get('Access-Control-Allow-Origin')).toBe('*') expect(context.res.headers?.get('Vary')).toBeNull() }) @@ -52,9 +50,7 @@ describe('Cors hook', () => { handler(context) - expect(context.res.headers?.get('Access-Control-Allow-Origin')).toBe( - 'http://localhost:3000', - ) + expect(context.res.headers?.get('Access-Control-Allow-Origin')).toBe('http://localhost:3000') }) it('should correctly allow origin with an array', async () => { @@ -67,9 +63,7 @@ describe('Cors hook', () => { handler(context) - expect(context.res.headers?.get('Access-Control-Allow-Origin')).toBe( - 'http://localhost:3000', - ) + expect(context.res.headers?.get('Access-Control-Allow-Origin')).toBe('http://localhost:3000') const context2 = new Context( new Request('http://localhost:3000/test', { @@ -82,16 +76,13 @@ describe('Cors hook', () => { // With an origin header handler(context2) - expect(context2.res.headers?.get('Access-Control-Allow-Origin')).toBe( - 'http://localhost:3001', - ) + expect(context2.res.headers?.get('Access-Control-Allow-Origin')).toBe('http://localhost:3001') }) it('should correctly allow origin with a function', async () => { const handler = cors({ origin: (origin) => { - if (origin === 'http://localhost:3000') - return 'http://localhost:3000' + if (origin === 'http://localhost:3000') return 'http://localhost:3000' return 'http://localhost:3001' }, @@ -102,9 +93,7 @@ describe('Cors hook', () => { handler(context) - expect(context.res.headers?.get('Access-Control-Allow-Origin')).toBe( - 'http://localhost:3001', - ) + expect(context.res.headers?.get('Access-Control-Allow-Origin')).toBe('http://localhost:3001') const context2 = new Context( new Request('http://localhost:3000/test', { @@ -117,9 +106,7 @@ describe('Cors hook', () => { // With an origin header handler(context2) - expect(context2.res.headers?.get('Access-Control-Allow-Origin')).toBe( - 'http://localhost:3000', - ) + expect(context2.res.headers?.get('Access-Control-Allow-Origin')).toBe('http://localhost:3000') }) it('should allow credentials', async () => { @@ -132,9 +119,7 @@ describe('Cors hook', () => { handler(context) - expect( - context.res.headers?.get('Access-Control-Allow-Credentials'), - ).toBe('true') + expect(context.res.headers?.get('Access-Control-Allow-Credentials')).toBe('true') }) it('should not allow credentials', async () => { @@ -147,9 +132,7 @@ describe('Cors hook', () => { handler(context) - expect( - context.res.headers?.get('Access-Control-Allow-Credentials'), - ).toBeNull() + expect(context.res.headers?.get('Access-Control-Allow-Credentials')).toBeNull() }) it('should control expose headers', async () => { @@ -162,9 +145,7 @@ describe('Cors hook', () => { handlerWithExposeHeaders(context) - expect(context.res.headers?.get('Access-Control-Expose-Headers')).toBe( - 'X-Test', - ) + expect(context.res.headers?.get('Access-Control-Expose-Headers')).toBe('X-Test') }) it('should have expose headers to null when no expose headers is defined', async () => { @@ -176,9 +157,7 @@ describe('Cors hook', () => { handlerWithoutExposeHeaders(context) - expect( - context.res.headers?.get('Access-Control-Expose-Headers'), - ).toBeNull() + expect(context.res.headers?.get('Access-Control-Expose-Headers')).toBeNull() }) it('should not set max age for others request than OPTIONS', async () => { @@ -217,9 +196,7 @@ describe('Cors hook', () => { handlerWithAllowMethods(context) - expect( - context.res.headers?.get('Access-Control-Allow-Methods'), - ).toBeNull() + expect(context.res.headers?.get('Access-Control-Allow-Methods')).toBeNull() }) it('should set allow methods for OPTIONS requests', async () => { @@ -232,9 +209,7 @@ describe('Cors hook', () => { handlerWithAllowMethods(context) - expect(context.res.headers?.get('Access-Control-Allow-Methods')).toBe( - 'GET,POST', - ) + expect(context.res.headers?.get('Access-Control-Allow-Methods')).toBe('GET,POST') }) it('should set allow headers with an allow headers on OPTIONS requests', async () => { @@ -247,12 +222,8 @@ describe('Cors hook', () => { handlerWithAllowMethods(context) - expect(context.res.headers?.get('Access-Control-Allow-Headers')).toBe( - 'X-Test', - ) - expect(context.res.headers?.get('Vary')).toBe( - 'Origin, Access-Control-Request-Headers', - ) + expect(context.res.headers?.get('Access-Control-Allow-Headers')).toBe('X-Test') + expect(context.res.headers?.get('Vary')).toBe('Origin, Access-Control-Request-Headers') }) it('should set allow headers without an allow headers on OPTIONS request', async () => { @@ -271,13 +242,9 @@ describe('Cors hook', () => { handlerWithAllowMethods(context) - expect(context.res.headers?.get('Access-Control-Allow-Headers')).toBe( - 'X-Test', - ) + expect(context.res.headers?.get('Access-Control-Allow-Headers')).toBe('X-Test') - expect(context.res.headers?.get('Vary')).toBe( - 'Origin, Access-Control-Request-Headers', - ) + expect(context.res.headers?.get('Vary')).toBe('Origin, Access-Control-Request-Headers') }) it('should delete Content-Lenght and Content-type on OPTIONS request', async () => { diff --git a/packages/wobe/src/hooks/cors.ts b/packages/wobe/src/hooks/cors.ts index 9be15e5..41d640a 100644 --- a/packages/wobe/src/hooks/cors.ts +++ b/packages/wobe/src/hooks/cors.ts @@ -1,9 +1,6 @@ import type { WobeHandler } from '../Wobe' -type Origin = - | string - | string[] - | ((origin: string) => string | undefined | null) +type Origin = string | string[] | ((origin: string) => string | undefined | null) export interface CorsOptions { origin: Origin @@ -43,49 +40,29 @@ export const cors = (options?: CorsOptions): WobeHandler => { const allowOrigin = getAllowOrigin(opts.origin) - if (allowOrigin) - ctx.res.headers.set('Access-Control-Allow-Origin', allowOrigin) + if (allowOrigin) ctx.res.headers.set('Access-Control-Allow-Origin', allowOrigin) // https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Origin if (opts.origin !== '*') ctx.res.headers.set('Vary', 'Origin') - if (opts.credentials) - ctx.res.headers.set('Access-Control-Allow-Credentials', 'true') + if (opts.credentials) ctx.res.headers.set('Access-Control-Allow-Credentials', 'true') if (opts.exposeHeaders?.length) - ctx.res.headers.set( - 'Access-Control-Expose-Headers', - opts.exposeHeaders.join(','), - ) + ctx.res.headers.set('Access-Control-Expose-Headers', opts.exposeHeaders.join(',')) if (ctx.request.method === 'OPTIONS') { - if (opts.maxAge) - ctx.res.headers.set( - 'Access-Control-Max-Age', - opts.maxAge.toString(), - ) + if (opts.maxAge) ctx.res.headers.set('Access-Control-Max-Age', opts.maxAge.toString()) if (opts.allowMethods?.length) - ctx.res.headers.set( - 'Access-Control-Allow-Methods', - opts.allowMethods.join(','), - ) + ctx.res.headers.set('Access-Control-Allow-Methods', opts.allowMethods.join(',')) const headers = opts.allowHeaders?.length ? opts.allowHeaders - : ctx.request.headers - .get('Access-Control-Request-Headers') - ?.split(/\s*,\s*/) + : ctx.request.headers.get('Access-Control-Request-Headers')?.split(/\s*,\s*/) if (headers?.length) { - ctx.res.headers.set( - 'Access-Control-Allow-Headers', - headers.join(','), - ) - ctx.res.headers?.append( - 'Vary', - 'Access-Control-Request-Headers', - ) + ctx.res.headers.set('Access-Control-Allow-Headers', headers.join(',')) + ctx.res.headers?.append('Vary', 'Access-Control-Request-Headers') } ctx.res.headers?.delete('Content-Length') diff --git a/packages/wobe/src/hooks/csrf.ts b/packages/wobe/src/hooks/csrf.ts index 77038c4..50f9c82 100644 --- a/packages/wobe/src/hooks/csrf.ts +++ b/packages/wobe/src/hooks/csrf.ts @@ -28,13 +28,7 @@ export const csrf = (options: CsrfOptions): WobeHandler => { const method = ctx.request.method?.toUpperCase?.() // Only enforce on non-idempotent methods - if ( - !method || - method === 'GET' || - method === 'HEAD' || - method === 'OPTIONS' - ) - return + if (!method || method === 'GET' || method === 'HEAD' || method === 'OPTIONS') return const requestOrigin = ctx.request.headers.get('origin') const requestReferer = ctx.request.headers.get('referer') diff --git a/packages/wobe/src/hooks/html.test.ts b/packages/wobe/src/hooks/html.test.ts index 5007a84..d165040 100644 --- a/packages/wobe/src/hooks/html.test.ts +++ b/packages/wobe/src/hooks/html.test.ts @@ -34,10 +34,7 @@ describe('html', () => { // Create a subdirectory with index.html await mkdir(join(TEST_DIR, 'subdir'), { recursive: true }) - await writeFile( - join(TEST_DIR, 'subdir', 'index.html'), - 'Subdir', - ) + await writeFile(join(TEST_DIR, 'subdir', 'index.html'), 'Subdir') }) afterEach(async () => { @@ -74,9 +71,7 @@ describe('html', () => { const response = await middleware(ctx) expect(response?.status).toBe(200) - expect(response?.headers.get('Content-Type')).toBe( - 'application/javascript', - ) + expect(response?.headers.get('Content-Type')).toBe('application/javascript') expect(await response?.text()).toContain('console.log("test")') }) @@ -172,14 +167,9 @@ describe('html', () => { it('should handle URL-encoded paths', async () => { // Create a file with spaces in the name - await writeFile( - join(TEST_DIR, 'file with spaces.html'), - 'Spaces', - ) - - const request = new Request( - 'http://localhost:3000/file%20with%20spaces.html', - ) + await writeFile(join(TEST_DIR, 'file with spaces.html'), 'Spaces') + + const request = new Request('http://localhost:3000/file%20with%20spaces.html') const ctx = new MockContext(request) const middleware = html({ rootPath }) @@ -198,16 +188,12 @@ describe('html', () => { const response = await middleware(ctx) expect(response?.status).toBe(200) - expect(response?.headers.get('Content-Type')).toBe( - 'application/octet-stream', - ) + expect(response?.headers.get('Content-Type')).toBe('application/octet-stream') expect(await response?.text()).toContain('No extension content') }) it('should handle query parameters in URL', async () => { - const request = new Request( - 'http://localhost:3000/test.html?param=value', - ) + const request = new Request('http://localhost:3000/test.html?param=value') const ctx = new MockContext(request) const middleware = html({ rootPath }) diff --git a/packages/wobe/src/hooks/html.ts b/packages/wobe/src/hooks/html.ts index 5864afe..5176007 100644 --- a/packages/wobe/src/hooks/html.ts +++ b/packages/wobe/src/hooks/html.ts @@ -107,8 +107,7 @@ const trySendFile = async ( const real = await realpath(filePath) if (!allowSymlinks) { - const insideRoot = - real === resolvedRoot || real.startsWith(resolvedRoot + sep) + const insideRoot = real === resolvedRoot || real.startsWith(resolvedRoot + sep) if (!insideRoot) return ctx.res.send('Forbidden', { status: 403 }) } @@ -121,9 +120,7 @@ const trySendFile = async ( headers: { 'Content-Type': contentType }, }) - const body = isTextType(contentType) - ? await file.text() - : await file.arrayBuffer() + const body = isTextType(contentType) ? await file.text() : await file.arrayBuffer() return ctx.res.send(body, { headers: { 'Content-Type': contentType }, @@ -156,9 +153,7 @@ export const html = (options: HtmlOptions): WobeHandler => { allowSymlinks = false, } = options const resolvedRoot = resolve(rootPath) - const normalizedPrefix = stripPrefix - ? stripPrefix.replace(/\/+$/, '') - : undefined + const normalizedPrefix = stripPrefix ? stripPrefix.replace(/\/+$/, '') : undefined return async (ctx) => { const method = ctx.request.method?.toUpperCase?.() || 'GET' @@ -184,19 +179,15 @@ export const html = (options: HtmlOptions): WobeHandler => { ? decodedPathname.slice(normalizedPrefix.length) || '/' : decodedPathname - const normalizedPathRaw = normalize( - withoutPrefix.replace(/^\/+/, '') || '', - ) - const normalizedPath = - normalizedPathRaw === '.' ? '' : normalizedPathRaw + const normalizedPathRaw = normalize(withoutPrefix.replace(/^\/+/, '') || '') + const normalizedPath = normalizedPathRaw === '.' ? '' : normalizedPathRaw if (!allowDotfiles && isDotPath(normalizedPath)) return ctx.res.send('Not Found', { status: 404 }) const requestedPath = resolve(resolvedRoot, normalizedPath) const isInsideRoot = - requestedPath === resolvedRoot || - requestedPath.startsWith(resolvedRoot + sep) + requestedPath === resolvedRoot || requestedPath.startsWith(resolvedRoot + sep) if (!isInsideRoot) return ctx.res.send('Forbidden', { status: 403 }) @@ -212,8 +203,7 @@ export const html = (options: HtmlOptions): WobeHandler => { if (fallbackFile) { const fallbackPath = resolve(resolvedRoot, fallbackFile) const isFallbackInsideRoot = - fallbackPath === resolvedRoot || - fallbackPath.startsWith(resolvedRoot + sep) + fallbackPath === resolvedRoot || fallbackPath.startsWith(resolvedRoot + sep) if (isFallbackInsideRoot) { const fallbackServed = await trySendFile(ctx, fallbackPath, { diff --git a/packages/wobe/src/hooks/logger.ts b/packages/wobe/src/hooks/logger.ts index 6c7eb21..9d4b169 100644 --- a/packages/wobe/src/hooks/logger.ts +++ b/packages/wobe/src/hooks/logger.ts @@ -23,9 +23,7 @@ const defaultLoggerFunction = ({ `[${ beforeHandler ? 'Before handler' : 'After handler' }] [${method}] ${url}${status ? ' (status:' + status + ')' : ''}${ - requestStartTimeInMs - ? '[' + (Date.now() - requestStartTimeInMs) + 'ms]' - : '' + requestStartTimeInMs ? '[' + (Date.now() - requestStartTimeInMs) + 'ms]' : '' }`, ) } diff --git a/packages/wobe/src/hooks/rateLimit.test.ts b/packages/wobe/src/hooks/rateLimit.test.ts index b446921..17348d7 100644 --- a/packages/wobe/src/hooks/rateLimit.test.ts +++ b/packages/wobe/src/hooks/rateLimit.test.ts @@ -120,12 +120,9 @@ describe('rateLimit', () => { expect(() => handler(context)).toThrow() - const responseFromHttpException = mockHttpExceptionConstructor.mock - .calls[0][0] as Response + const responseFromHttpException = mockHttpExceptionConstructor.mock.calls[0][0] as Response expect(responseFromHttpException.status).toBe(429) - expect(await responseFromHttpException.text()).toBe( - 'Rate limit exceeded', - ) + expect(await responseFromHttpException.text()).toBe('Rate limit exceeded') }) }) diff --git a/packages/wobe/src/hooks/rateLimit.ts b/packages/wobe/src/hooks/rateLimit.ts index f279e4f..a85075c 100644 --- a/packages/wobe/src/hooks/rateLimit.ts +++ b/packages/wobe/src/hooks/rateLimit.ts @@ -10,10 +10,7 @@ export interface RateLimitOptions { /** * rateLimit is a hook that limits the number of requests per interval */ -export const rateLimit = ({ - interval, - numberOfRequests, -}: RateLimitOptions): WobeHandler => { +export const rateLimit = ({ interval, numberOfRequests }: RateLimitOptions): WobeHandler => { const store = new WobeStore({ interval, }) @@ -24,9 +21,7 @@ export const rateLimit = ({ const userRequests = store.get(ipAdress) || 0 if (userRequests >= numberOfRequests) - throw new HttpException( - new Response('Rate limit exceeded', { status: 429 }), - ) + throw new HttpException(new Response('Rate limit exceeded', { status: 429 })) store.set(ipAdress, userRequests + 1) } diff --git a/packages/wobe/src/hooks/secureHeaders.test.ts b/packages/wobe/src/hooks/secureHeaders.test.ts index d2efa5d..488b534 100644 --- a/packages/wobe/src/hooks/secureHeaders.test.ts +++ b/packages/wobe/src/hooks/secureHeaders.test.ts @@ -41,9 +41,7 @@ describe('Secure headers', () => { handler(context) - expect(context.res.headers.get('Cross-Origin-Embedder-Policy')).toEqual( - 'random-value', - ) + expect(context.res.headers.get('Cross-Origin-Embedder-Policy')).toEqual('random-value') }) it('should have a default value for Cross-Origin-Opener-Policy', () => { @@ -59,9 +57,7 @@ describe('Secure headers', () => { handler(context) - expect(context.res.headers.get('Cross-Origin-Opener-Policy')).toEqual( - 'same-origin', - ) + expect(context.res.headers.get('Cross-Origin-Opener-Policy')).toEqual('same-origin') }) it('should set Cross-Origin-Opener-Policy', () => { @@ -79,9 +75,7 @@ describe('Secure headers', () => { handler(context) - expect(context.res.headers.get('Cross-Origin-Opener-Policy')).toEqual( - 'random-value', - ) + expect(context.res.headers.get('Cross-Origin-Opener-Policy')).toEqual('random-value') }) it('should have default value for Cross-Origin-Resource-Policty', () => { @@ -97,9 +91,7 @@ describe('Secure headers', () => { handler(context) - expect(context.res.headers.get('Cross-Origin-Resource-Policy')).toEqual( - 'same-site', - ) + expect(context.res.headers.get('Cross-Origin-Resource-Policy')).toEqual('same-site') }) it('should set Cross-Origin-Resource-Policy', () => { @@ -117,9 +109,7 @@ describe('Secure headers', () => { handler(context) - expect(context.res.headers.get('Cross-Origin-Resource-Policy')).toEqual( - 'random-value', - ) + expect(context.res.headers.get('Cross-Origin-Resource-Policy')).toEqual('random-value') }) it('should have default value for Referer-Policy', () => { @@ -135,9 +125,7 @@ describe('Secure headers', () => { handler(context) - expect(context.res.headers.get('Referrer-Policy')).toEqual( - 'no-referrer', - ) + expect(context.res.headers.get('Referrer-Policy')).toEqual('no-referrer') }) it('should set Referrer-Policy', () => { @@ -155,9 +143,7 @@ describe('Secure headers', () => { handler(context) - expect(context.res.headers.get('Referrer-Policy')).toEqual( - 'random-value', - ) + expect(context.res.headers.get('Referrer-Policy')).toEqual('random-value') }) it('should have default value for Strict-Transport-Security', () => { @@ -211,9 +197,7 @@ describe('Secure headers', () => { handler(context) - expect(context.res.headers.get('X-Content-Type-Options')).toEqual( - 'nosniff', - ) + expect(context.res.headers.get('X-Content-Type-Options')).toEqual('nosniff') }) it('should set X-Content-Type-Options', () => { @@ -231,9 +215,7 @@ describe('Secure headers', () => { handler(context) - expect(context.res.headers.get('X-Content-Type-Options')).toEqual( - 'random-value', - ) + expect(context.res.headers.get('X-Content-Type-Options')).toEqual('random-value') }) it('should have default value for X-Download-Options', () => { @@ -267,9 +249,7 @@ describe('Secure headers', () => { handler(context) - expect(context.res.headers.get('X-Download-Options')).toEqual( - 'random-value', - ) + expect(context.res.headers.get('X-Download-Options')).toEqual('random-value') }) it('should set default X-Frame-Options', () => { diff --git a/packages/wobe/src/hooks/secureHeaders.ts b/packages/wobe/src/hooks/secureHeaders.ts index 98ef674..a114d91 100644 --- a/packages/wobe/src/hooks/secureHeaders.ts +++ b/packages/wobe/src/hooks/secureHeaders.ts @@ -53,55 +53,30 @@ export const secureHeaders = ({ }: SecureHeadersOptions): WobeHandler => { return (ctx) => { if (contentSecurityPolicy) { - const formatContentSecurityPolicy = Object.entries( - contentSecurityPolicy, - ) - .map( - ([key, value]) => - `${key} ${ - Array.isArray(value) ? value.join(' ') : value - }`, - ) + const formatContentSecurityPolicy = Object.entries(contentSecurityPolicy) + .map(([key, value]) => `${key} ${Array.isArray(value) ? value.join(' ') : value}`) .join('; ') - ctx.res.headers.set( - 'Content-Security-Policy', - formatContentSecurityPolicy, - ) + ctx.res.headers.set('Content-Security-Policy', formatContentSecurityPolicy) } if (crossOriginEmbedderPolicy) - ctx.res.headers.set( - 'Cross-Origin-Embedder-Policy', - crossOriginEmbedderPolicy, - ) + ctx.res.headers.set('Cross-Origin-Embedder-Policy', crossOriginEmbedderPolicy) if (crossOriginOpenerPolicy) - ctx.res.headers.set( - 'Cross-Origin-Opener-Policy', - crossOriginOpenerPolicy, - ) + ctx.res.headers.set('Cross-Origin-Opener-Policy', crossOriginOpenerPolicy) if (crossOriginResourcePolicy) - ctx.res.headers.set( - 'Cross-Origin-Resource-Policy', - crossOriginResourcePolicy, - ) + ctx.res.headers.set('Cross-Origin-Resource-Policy', crossOriginResourcePolicy) - if (referrerPolicy) - ctx.res.headers.set('Referrer-Policy', referrerPolicy) + if (referrerPolicy) ctx.res.headers.set('Referrer-Policy', referrerPolicy) if (strictTransportSecurity) - ctx.res.headers.set( - 'Strict-Transport-Security', - strictTransportSecurity.join('; '), - ) + ctx.res.headers.set('Strict-Transport-Security', strictTransportSecurity.join('; ')) - if (xContentTypeOptions) - ctx.res.headers.set('X-Content-Type-Options', xContentTypeOptions) + if (xContentTypeOptions) ctx.res.headers.set('X-Content-Type-Options', xContentTypeOptions) - if (xDownloadOptions) - ctx.res.headers.set('X-Download-Options', xDownloadOptions) + if (xDownloadOptions) ctx.res.headers.set('X-Download-Options', xDownloadOptions) if (xFrameOptions) ctx.res.headers.set('X-Frame-Options', xFrameOptions) } diff --git a/packages/wobe/src/hooks/uploadDirectory.test.ts b/packages/wobe/src/hooks/uploadDirectory.test.ts index 63ca6c9..6e78f00 100644 --- a/packages/wobe/src/hooks/uploadDirectory.test.ts +++ b/packages/wobe/src/hooks/uploadDirectory.test.ts @@ -29,16 +29,11 @@ describe('UploadDirectory Hook', () => { const port = await getPort() const wobe = new Wobe() - wobe.get( - '/bucket/:filename', - uploadDirectory({ directory: testDirectory }), - ) + wobe.get('/bucket/:filename', uploadDirectory({ directory: testDirectory })) wobe.listen(port) - const response = await fetch( - `http://127.0.0.1:${port}/bucket/${fileName}`, - ) + const response = await fetch(`http://127.0.0.1:${port}/bucket/${fileName}`) expect(response.status).toBe(200) expect(response.headers.get('Content-Type')).toBe('text/plain') @@ -53,16 +48,11 @@ describe('UploadDirectory Hook', () => { const port = await getPort() const wobe = new Wobe() - wobe.get( - '/bucket/:filename', - uploadDirectory({ directory: testDirectory }), - ) + wobe.get('/bucket/:filename', uploadDirectory({ directory: testDirectory })) wobe.listen(port) - const response = await fetch( - `http://127.0.0.1:${port}/bucket/non-existent-file.txt`, - ) + const response = await fetch(`http://127.0.0.1:${port}/bucket/non-existent-file.txt`) expect(response.status).toBe(404) expect(await response.text()).toBe('File not found') @@ -86,16 +76,11 @@ describe('UploadDirectory Hook', () => { const port = await getPort() const wobe = new Wobe() - wobe.get( - '/bucket/:filename', - uploadDirectory({ directory: testDirectory }), - ) + wobe.get('/bucket/:filename', uploadDirectory({ directory: testDirectory })) wobe.listen(port) - const response = await fetch( - `http://127.0.0.1:${port}/bucket/${dotFileName}`, - ) + const response = await fetch(`http://127.0.0.1:${port}/bucket/${dotFileName}`) expect(response.status).toBe(404) wobe.stop() @@ -108,10 +93,7 @@ describe('UploadDirectory Hook', () => { const symlinkPath = join(testDirectory, 'link.txt') await symlink(filePath, symlinkPath) - wobe.get( - '/bucket/:filename', - uploadDirectory({ directory: testDirectory }), - ) + wobe.get('/bucket/:filename', uploadDirectory({ directory: testDirectory })) wobe.listen(port) @@ -125,10 +107,7 @@ describe('UploadDirectory Hook', () => { const port = await getPort() const wobe = new Wobe() - wobe.get( - '/bucket/:filename', - uploadDirectory({ directory: testDirectory }), - ) + wobe.get('/bucket/:filename', uploadDirectory({ directory: testDirectory })) wobe.listen(port) diff --git a/packages/wobe/src/hooks/uploadDirectory.ts b/packages/wobe/src/hooks/uploadDirectory.ts index 290cedf..a4057d2 100644 --- a/packages/wobe/src/hooks/uploadDirectory.ts +++ b/packages/wobe/src/hooks/uploadDirectory.ts @@ -60,8 +60,7 @@ export const uploadDirectory = ({ const filePath = resolve(resolvedRoot, fileName) - const isInsideRoot = - filePath === resolvedRoot || filePath.startsWith(resolvedRoot + sep) + const isInsideRoot = filePath === resolvedRoot || filePath.startsWith(resolvedRoot + sep) if (!isInsideRoot) { ctx.res.status = 403 @@ -86,10 +85,7 @@ export const uploadDirectory = ({ const contentType = mimeTypes[ext] || 'application/octet-stream' ctx.res.headers.set('Content-Type', contentType) - ctx.res.headers.set( - 'Content-Length', - fileContent.byteLength.toString(), - ) + ctx.res.headers.set('Content-Length', fileContent.byteLength.toString()) return ctx.res.send(fileContent) } catch { diff --git a/packages/wobe/src/router/RadixTree.test.ts b/packages/wobe/src/router/RadixTree.test.ts index a23e73f..326ed66 100644 --- a/packages/wobe/src/router/RadixTree.test.ts +++ b/packages/wobe/src/router/RadixTree.test.ts @@ -11,430 +11,187 @@ describe('RadixTree', () => { radixTree.optimizeTree() expect(() => { - radixTree.addHook( - 'beforeHandler', - '/test', - () => Promise.resolve(), - 'GET', - ) - }).toThrowError( - 'Cannot add hooks after the tree has been optimized', - ) + radixTree.addHook('beforeHandler', '/test', () => Promise.resolve(), 'GET') + }).toThrowError('Cannot add hooks after the tree has been optimized') }) it('should add a single hook based on the method of the request', () => { const radixTree = new RadixTree() - radixTree.addRoute('GET', '/a/simple/route', () => - Promise.resolve(), - ) - radixTree.addRoute('POST', '/a/simple/route', () => - Promise.resolve(), - ) - radixTree.addHook( - 'beforeHandler', - '/a/simple/route', - () => Promise.resolve(), - 'GET', - ) + radixTree.addRoute('GET', '/a/simple/route', () => Promise.resolve()) + radixTree.addRoute('POST', '/a/simple/route', () => Promise.resolve()) + radixTree.addHook('beforeHandler', '/a/simple/route', () => Promise.resolve(), 'GET') - radixTree.addHook( - 'beforeHandler', - '/a/simple/route', - () => Promise.resolve(), - 'POST', - ) + radixTree.addHook('beforeHandler', '/a/simple/route', () => Promise.resolve(), 'POST') - expect( - radixTree.root.children[0].children[0].children[0].name, - ).toBe('/route') - expect( - radixTree.root.children[0].children[0].children[0].method, - ).toBe('GET') - expect( - radixTree.root.children[0].children[0].children[0].handler, - ).toBeDefined() - expect( - radixTree.root.children[0].children[0].children[0] - .beforeHandlerHook?.length, - ).toBe(1) + expect(radixTree.root.children[0].children[0].children[0].name).toBe('/route') + expect(radixTree.root.children[0].children[0].children[0].method).toBe('GET') + expect(radixTree.root.children[0].children[0].children[0].handler).toBeDefined() + expect(radixTree.root.children[0].children[0].children[0].beforeHandlerHook?.length).toBe(1) - expect( - radixTree.root.children[0].children[0].children[1].name, - ).toBe('/route') - expect( - radixTree.root.children[0].children[0].children[1].method, - ).toBe('POST') - expect( - radixTree.root.children[0].children[0].children[1].handler, - ).toBeDefined() - expect( - radixTree.root.children[0].children[0].children[1] - .beforeHandlerHook?.length, - ).toBe(1) + expect(radixTree.root.children[0].children[0].children[1].name).toBe('/route') + expect(radixTree.root.children[0].children[0].children[1].method).toBe('POST') + expect(radixTree.root.children[0].children[0].children[1].handler).toBeDefined() + expect(radixTree.root.children[0].children[0].children[1].beforeHandlerHook?.length).toBe(1) }) it('should add a hook with path * on beforeHandler with multiple route', () => { const radixTree = new RadixTree() - radixTree.addRoute('GET', '/a/simple/route', () => - Promise.resolve(), - ) + radixTree.addRoute('GET', '/a/simple/route', () => Promise.resolve()) radixTree.addRoute('GET', '/a/simple', () => Promise.resolve()) - radixTree.addHook( - 'beforeHandler', - '*', - () => Promise.resolve(), - 'GET', - ) + radixTree.addHook('beforeHandler', '*', () => Promise.resolve(), 'GET') - expect( - radixTree.root.children[0].children[0].children[0].name, - ).toBe('/route') - expect( - radixTree.root.children[0].children[0].children[0].method, - ).toBe('GET') - expect( - radixTree.root.children[0].children[0].children[0].handler, - ).toBeDefined() + expect(radixTree.root.children[0].children[0].children[0].name).toBe('/route') + expect(radixTree.root.children[0].children[0].children[0].method).toBe('GET') + expect(radixTree.root.children[0].children[0].children[0].handler).toBeDefined() - expect( - radixTree.root.children[0].children[0].beforeHandlerHook - ?.length, - ).toBe(1) + expect(radixTree.root.children[0].children[0].beforeHandlerHook?.length).toBe(1) - expect( - radixTree.root.children[0].children[0].children[0] - .beforeHandlerHook?.length, - ).toBe(1) - expect( - radixTree.root.children[0].children[0].children[0] - .afterHandlerHook, - ).toBeUndefined() + expect(radixTree.root.children[0].children[0].children[0].beforeHandlerHook?.length).toBe(1) + expect(radixTree.root.children[0].children[0].children[0].afterHandlerHook).toBeUndefined() }) it('should add a hook with path * on beforeHandler', () => { const radixTree = new RadixTree() - radixTree.addRoute('GET', '/a/simple/route', () => - Promise.resolve(), - ) + radixTree.addRoute('GET', '/a/simple/route', () => Promise.resolve()) - radixTree.addHook( - 'beforeHandler', - '*', - () => Promise.resolve(), - 'GET', - ) + radixTree.addHook('beforeHandler', '*', () => Promise.resolve(), 'GET') - expect( - radixTree.root.children[0].children[0].children[0].name, - ).toBe('/route') - expect( - radixTree.root.children[0].children[0].children[0].method, - ).toBe('GET') - expect( - radixTree.root.children[0].children[0].children[0].handler, - ).toBeDefined() + expect(radixTree.root.children[0].children[0].children[0].name).toBe('/route') + expect(radixTree.root.children[0].children[0].children[0].method).toBe('GET') + expect(radixTree.root.children[0].children[0].children[0].handler).toBeDefined() - expect( - radixTree.root.children[0].children[0].children[0] - .beforeHandlerHook?.length, - ).toBe(1) - expect( - radixTree.root.children[0].children[0].children[0] - .afterHandlerHook, - ).toBeUndefined() + expect(radixTree.root.children[0].children[0].children[0].beforeHandlerHook?.length).toBe(1) + expect(radixTree.root.children[0].children[0].children[0].afterHandlerHook).toBeUndefined() }) it('should add a hook beforeHandler to the radix tree', () => { const radixTree = new RadixTree() - radixTree.addRoute('GET', '/a/simple/route', () => - Promise.resolve(), - ) - radixTree.addHook( - 'beforeHandler', - '/a/simple/route', - () => Promise.resolve(), - 'GET', - ) + radixTree.addRoute('GET', '/a/simple/route', () => Promise.resolve()) + radixTree.addHook('beforeHandler', '/a/simple/route', () => Promise.resolve(), 'GET') - expect( - radixTree.root.children[0].children[0].children[0].name, - ).toBe('/route') - expect( - radixTree.root.children[0].children[0].children[0].method, - ).toBe('GET') - expect( - radixTree.root.children[0].children[0].children[0].handler, - ).toBeDefined() + expect(radixTree.root.children[0].children[0].children[0].name).toBe('/route') + expect(radixTree.root.children[0].children[0].children[0].method).toBe('GET') + expect(radixTree.root.children[0].children[0].children[0].handler).toBeDefined() - expect( - radixTree.root.children[0].children[0].children[0] - .beforeHandlerHook?.length, - ).toBe(1) - expect( - radixTree.root.children[0].children[0].children[0] - .afterHandlerHook, - ).toBeUndefined() + expect(radixTree.root.children[0].children[0].children[0].beforeHandlerHook?.length).toBe(1) + expect(radixTree.root.children[0].children[0].children[0].afterHandlerHook).toBeUndefined() }) it('should add a hook afterHandler to the radix tree', () => { const radixTree = new RadixTree() - radixTree.addRoute('GET', '/a/simple/route', () => - Promise.resolve(), - ) - radixTree.addHook( - 'afterHandler', - '/a/simple/route', - () => Promise.resolve(), - 'GET', - ) + radixTree.addRoute('GET', '/a/simple/route', () => Promise.resolve()) + radixTree.addHook('afterHandler', '/a/simple/route', () => Promise.resolve(), 'GET') - expect( - radixTree.root.children[0].children[0].children[0].name, - ).toBe('/route') - expect( - radixTree.root.children[0].children[0].children[0].method, - ).toBe('GET') - expect( - radixTree.root.children[0].children[0].children[0].handler, - ).toBeDefined() + expect(radixTree.root.children[0].children[0].children[0].name).toBe('/route') + expect(radixTree.root.children[0].children[0].children[0].method).toBe('GET') + expect(radixTree.root.children[0].children[0].children[0].handler).toBeDefined() - expect( - radixTree.root.children[0].children[0].children[0] - .beforeHandlerHook, - ).toBeUndefined() - expect( - radixTree.root.children[0].children[0].children[0] - .afterHandlerHook?.length, - ).toBe(1) + expect(radixTree.root.children[0].children[0].children[0].beforeHandlerHook).toBeUndefined() + expect(radixTree.root.children[0].children[0].children[0].afterHandlerHook?.length).toBe(1) }) it('should add a hook beforeAndAfterHandler to the radix tree', () => { const radixTree = new RadixTree() - radixTree.addRoute('GET', '/a/simple/route', () => - Promise.resolve(), - ) - radixTree.addHook( - 'beforeAndAfterHandler', - '/a/simple/route', - () => Promise.resolve(), - 'GET', - ) + radixTree.addRoute('GET', '/a/simple/route', () => Promise.resolve()) + radixTree.addHook('beforeAndAfterHandler', '/a/simple/route', () => Promise.resolve(), 'GET') - expect( - radixTree.root.children[0].children[0].children[0].name, - ).toBe('/route') - expect( - radixTree.root.children[0].children[0].children[0].method, - ).toBe('GET') - expect( - radixTree.root.children[0].children[0].children[0].handler, - ).toBeDefined() + expect(radixTree.root.children[0].children[0].children[0].name).toBe('/route') + expect(radixTree.root.children[0].children[0].children[0].method).toBe('GET') + expect(radixTree.root.children[0].children[0].children[0].handler).toBeDefined() - expect( - radixTree.root.children[0].children[0].children[0] - .beforeHandlerHook?.length, - ).toBe(1) - expect( - radixTree.root.children[0].children[0].children[0] - .afterHandlerHook?.length, - ).toBe(1) + expect(radixTree.root.children[0].children[0].children[0].beforeHandlerHook?.length).toBe(1) + expect(radixTree.root.children[0].children[0].children[0].afterHandlerHook?.length).toBe(1) }) it('should add a hook with a wildcard in the middle', () => { const radixTree = new RadixTree() - radixTree.addRoute('GET', '/a/simple/route', () => - Promise.resolve(), - ) - radixTree.addHook( - 'beforeHandler', - '/a/*/route', - () => Promise.resolve(), - 'GET', - ) + radixTree.addRoute('GET', '/a/simple/route', () => Promise.resolve()) + radixTree.addHook('beforeHandler', '/a/*/route', () => Promise.resolve(), 'GET') - expect( - radixTree.root.children[0].children[0].children[0] - .beforeHandlerHook?.length, - ).toBe(1) - expect( - radixTree.root.children[0].children[0].children[0] - .afterHandlerHook, - ).toBeUndefined() + expect(radixTree.root.children[0].children[0].children[0].beforeHandlerHook?.length).toBe(1) + expect(radixTree.root.children[0].children[0].children[0].afterHandlerHook).toBeUndefined() }) it('should add a hook with a wildcard at the end', () => { const radixTree = new RadixTree() - radixTree.addRoute('GET', '/a/simple/route', () => - Promise.resolve(), - ) + radixTree.addRoute('GET', '/a/simple/route', () => Promise.resolve()) - radixTree.addHook( - 'beforeHandler', - '/a/simple/*', - () => Promise.resolve(), - 'GET', - ) + radixTree.addHook('beforeHandler', '/a/simple/*', () => Promise.resolve(), 'GET') - expect( - radixTree.root.children[0].children[0].children[0] - .beforeHandlerHook?.length, - ).toBe(1) - expect( - radixTree.root.children[0].children[0].children[0] - .afterHandlerHook, - ).toBeUndefined() + expect(radixTree.root.children[0].children[0].children[0].beforeHandlerHook?.length).toBe(1) + expect(radixTree.root.children[0].children[0].children[0].afterHandlerHook).toBeUndefined() }) it('should add a hook with a slash at the end', () => { const radixTree = new RadixTree() - radixTree.addRoute('GET', '/a/simple/route', () => - Promise.resolve(), - ) - radixTree.addHook( - 'beforeHandler', - '/a/simple/route/', - () => Promise.resolve(), - 'GET', - ) + radixTree.addRoute('GET', '/a/simple/route', () => Promise.resolve()) + radixTree.addHook('beforeHandler', '/a/simple/route/', () => Promise.resolve(), 'GET') - expect( - radixTree.root.children[0].children[0].children[0] - .beforeHandlerHook?.length, - ).toBe(1) - expect( - radixTree.root.children[0].children[0].children[0] - .afterHandlerHook, - ).toBeUndefined() + expect(radixTree.root.children[0].children[0].children[0].beforeHandlerHook?.length).toBe(1) + expect(radixTree.root.children[0].children[0].children[0].afterHandlerHook).toBeUndefined() }) it('should add two hooks if there are two routes', () => { const radixTree = new RadixTree() - radixTree.addRoute('GET', '/a/simple/route', () => - Promise.resolve(), - ) - radixTree.addRoute('GET', '/a/simple/route2', () => - Promise.resolve(), - ) - radixTree.addHook( - 'beforeHandler', - '/a/simple/route', - () => Promise.resolve(), - 'GET', - ) - radixTree.addHook( - 'afterHandler', - '/a/simple/route2', - () => Promise.resolve(), - 'GET', - ) + radixTree.addRoute('GET', '/a/simple/route', () => Promise.resolve()) + radixTree.addRoute('GET', '/a/simple/route2', () => Promise.resolve()) + radixTree.addHook('beforeHandler', '/a/simple/route', () => Promise.resolve(), 'GET') + radixTree.addHook('afterHandler', '/a/simple/route2', () => Promise.resolve(), 'GET') + expect(radixTree.root.children[0].children[0].children[0].beforeHandlerHook?.length).toBe(1) expect( - radixTree.root.children[0].children[0].children[0] - .beforeHandlerHook?.length, - ).toBe(1) - expect( - radixTree.root.children[0].children[0].children[0] - .beforeHandlerHook?.[0], + radixTree.root.children[0].children[0].children[0].beforeHandlerHook?.[0], ).toBeFunction() - expect( - radixTree.root.children[0].children[0].children[1] - .beforeHandlerHook, - ).toBeUndefined() + expect(radixTree.root.children[0].children[0].children[1].beforeHandlerHook).toBeUndefined() + expect(radixTree.root.children[0].children[0].children[0].afterHandlerHook).toBeUndefined() + expect(radixTree.root.children[0].children[0].children[1].afterHandlerHook?.length).toBe(1) expect( - radixTree.root.children[0].children[0].children[0] - .afterHandlerHook, - ).toBeUndefined() - expect( - radixTree.root.children[0].children[0].children[1] - .afterHandlerHook?.length, - ).toBe(1) - expect( - radixTree.root.children[0].children[0].children[1] - .afterHandlerHook?.[0], + radixTree.root.children[0].children[0].children[1].afterHandlerHook?.[0], ).toBeFunction() }) it('should not add a hook with a wildcard in the middle if the path not match', () => { const radixTree = new RadixTree() - radixTree.addRoute('GET', '/a/simple/route', () => - Promise.resolve(), - ) - radixTree.addHook( - 'beforeHandler', - '/a/simple/route/*/tata', - () => Promise.resolve(), - 'GET', - ) + radixTree.addRoute('GET', '/a/simple/route', () => Promise.resolve()) + radixTree.addHook('beforeHandler', '/a/simple/route/*/tata', () => Promise.resolve(), 'GET') - expect( - radixTree.root.children[0].children[0].children[0] - .beforeHandlerHook, - ).toBeUndefined() - expect( - radixTree.root.children[0].children[0].children[0] - .afterHandlerHook, - ).toBeUndefined() + expect(radixTree.root.children[0].children[0].children[0].beforeHandlerHook).toBeUndefined() + expect(radixTree.root.children[0].children[0].children[0].afterHandlerHook).toBeUndefined() }) it('should not add a hook if the route not match', () => { const radixTree = new RadixTree() - radixTree.addRoute('GET', '/a/simple/route', () => - Promise.resolve(), - ) - radixTree.addHook( - 'beforeHandler', - '/a/simple/route2', - () => Promise.resolve(), - 'GET', - ) + radixTree.addRoute('GET', '/a/simple/route', () => Promise.resolve()) + radixTree.addHook('beforeHandler', '/a/simple/route2', () => Promise.resolve(), 'GET') - expect( - radixTree.root.children[0].children[0].children[0] - .beforeHandlerHook, - ).toBeUndefined() - expect( - radixTree.root.children[0].children[0].children[0] - .afterHandlerHook, - ).toBeUndefined() + expect(radixTree.root.children[0].children[0].children[0].beforeHandlerHook).toBeUndefined() + expect(radixTree.root.children[0].children[0].children[0].afterHandlerHook).toBeUndefined() }) it('should not add a hook if the hook is shorter than the route', () => { const radixTree = new RadixTree() - radixTree.addRoute('GET', '/a/simple/route', () => - Promise.resolve(), - ) - radixTree.addHook( - 'beforeHandler', - '/a/simple', - () => Promise.resolve(), - 'GET', - ) + radixTree.addRoute('GET', '/a/simple/route', () => Promise.resolve()) + radixTree.addHook('beforeHandler', '/a/simple', () => Promise.resolve(), 'GET') - expect( - radixTree.root.children[0].children[0].children[0] - .beforeHandlerHook, - ).toBeUndefined() - expect( - radixTree.root.children[0].children[0].children[0] - .afterHandlerHook, - ).toBeUndefined() + expect(radixTree.root.children[0].children[0].children[0].beforeHandlerHook).toBeUndefined() + expect(radixTree.root.children[0].children[0].children[0].afterHandlerHook).toBeUndefined() }) }) @@ -442,74 +199,46 @@ describe('RadixTree', () => { it('should throw an error if the route already exist with same http method', () => { const radixTree = new RadixTree() radixTree.addRoute('GET', '/route', () => Promise.resolve()) - expect(() => - radixTree.addRoute('GET', '/route', () => Promise.resolve()), - ).toThrow() + expect(() => radixTree.addRoute('GET', '/route', () => Promise.resolve())).toThrow() }) it('should add a route to the radix tree', () => { const radixTree = new RadixTree() - radixTree.addRoute('GET', '/a/simple/route', () => - Promise.resolve(), - ) + radixTree.addRoute('GET', '/a/simple/route', () => Promise.resolve()) expect(radixTree.root.children[0].name).toBe('a') expect(radixTree.root.children[0].method).toBeUndefined() expect(radixTree.root.children[0].handler).toBeUndefined() expect(radixTree.root.children[0].children[0].name).toBe('/simple') - expect( - radixTree.root.children[0].children[0].method, - ).toBeUndefined() - expect( - radixTree.root.children[0].children[0].handler, - ).toBeUndefined() - expect( - radixTree.root.children[0].children[0].children[0].name, - ).toBe('/route') - expect( - radixTree.root.children[0].children[0].children[0].method, - ).toBe('GET') - expect( - radixTree.root.children[0].children[0].children[0].handler, - ).toBeDefined() + expect(radixTree.root.children[0].children[0].method).toBeUndefined() + expect(radixTree.root.children[0].children[0].handler).toBeUndefined() + expect(radixTree.root.children[0].children[0].children[0].name).toBe('/route') + expect(radixTree.root.children[0].children[0].children[0].method).toBe('GET') + expect(radixTree.root.children[0].children[0].children[0].handler).toBeDefined() }) it('should add a route with HTTP method equal to ALL', () => { const radixTree = new RadixTree() - radixTree.addRoute('ALL', '/a/simple/route', () => - Promise.resolve(), - ) + radixTree.addRoute('ALL', '/a/simple/route', () => Promise.resolve()) expect(radixTree.root.children[0].name).toBe('a') expect(radixTree.root.children[0].method).toBeUndefined() expect(radixTree.root.children[0].handler).toBeUndefined() expect(radixTree.root.children[0].children[0].name).toBe('/simple') - expect( - radixTree.root.children[0].children[0].method, - ).toBeUndefined() - expect( - radixTree.root.children[0].children[0].handler, - ).toBeUndefined() - expect( - radixTree.root.children[0].children[0].children[0].name, - ).toBe('/route') - expect( - radixTree.root.children[0].children[0].children[0].method, - ).toBe('ALL') - expect( - radixTree.root.children[0].children[0].children[0].handler, - ).toBeDefined() + expect(radixTree.root.children[0].children[0].method).toBeUndefined() + expect(radixTree.root.children[0].children[0].handler).toBeUndefined() + expect(radixTree.root.children[0].children[0].children[0].name).toBe('/route') + expect(radixTree.root.children[0].children[0].children[0].method).toBe('ALL') + expect(radixTree.root.children[0].children[0].children[0].handler).toBeDefined() }) it('should add a route to the radix tree that is a part of another route', () => { const radixTree = new RadixTree() radixTree.addRoute('GET', '/a/simple', () => Promise.resolve()) - radixTree.addRoute('GET', '/a/simple/route', () => - Promise.resolve(), - ) + radixTree.addRoute('GET', '/a/simple/route', () => Promise.resolve()) expect(radixTree.root.children[0].name).toBe('a') expect(radixTree.root.children[0].method).toBeUndefined() @@ -517,120 +246,69 @@ describe('RadixTree', () => { expect(radixTree.root.children[0].children[0].name).toBe('/simple') expect(radixTree.root.children[0].children[0].method).toBe('GET') expect(radixTree.root.children[0].children[0].handler).toBeDefined() - expect( - radixTree.root.children[0].children[0].children[0].name, - ).toBe('/route') - expect( - radixTree.root.children[0].children[0].children[0].method, - ).toBe('GET') - expect( - radixTree.root.children[0].children[0].children[0].handler, - ).toBeDefined() + expect(radixTree.root.children[0].children[0].children[0].name).toBe('/route') + expect(radixTree.root.children[0].children[0].children[0].method).toBe('GET') + expect(radixTree.root.children[0].children[0].children[0].handler).toBeDefined() }) it('should add a route to the radix tree with no slash at the begining', () => { const radixTree = new RadixTree() - radixTree.addRoute('GET', 'a/simple/route/', () => - Promise.resolve(), - ) + radixTree.addRoute('GET', 'a/simple/route/', () => Promise.resolve()) expect(radixTree.root.children[0].name).toBe('a') expect(radixTree.root.children[0].method).toBeUndefined() expect(radixTree.root.children[0].handler).toBeUndefined() expect(radixTree.root.children[0].children[0].name).toBe('/simple') - expect( - radixTree.root.children[0].children[0].method, - ).toBeUndefined() - expect( - radixTree.root.children[0].children[0].handler, - ).toBeUndefined() - expect( - radixTree.root.children[0].children[0].children[0].name, - ).toBe('/route') - expect( - radixTree.root.children[0].children[0].children[0].method, - ).toBe('GET') - expect( - radixTree.root.children[0].children[0].children[0].handler, - ).toBeDefined() + expect(radixTree.root.children[0].children[0].method).toBeUndefined() + expect(radixTree.root.children[0].children[0].handler).toBeUndefined() + expect(radixTree.root.children[0].children[0].children[0].name).toBe('/route') + expect(radixTree.root.children[0].children[0].children[0].method).toBe('GET') + expect(radixTree.root.children[0].children[0].children[0].handler).toBeDefined() }) it('should add a route to the radix tree with slash at the end', () => { const radixTree = new RadixTree() - radixTree.addRoute('GET', '/a/simple/route/', () => - Promise.resolve(), - ) + radixTree.addRoute('GET', '/a/simple/route/', () => Promise.resolve()) expect(radixTree.root.children[0].name).toBe('a') expect(radixTree.root.children[0].method).toBeUndefined() expect(radixTree.root.children[0].handler).toBeUndefined() expect(radixTree.root.children[0].children[0].name).toBe('/simple') - expect( - radixTree.root.children[0].children[0].method, - ).toBeUndefined() - expect( - radixTree.root.children[0].children[0].handler, - ).toBeUndefined() - expect( - radixTree.root.children[0].children[0].children[0].name, - ).toBe('/route') - expect( - radixTree.root.children[0].children[0].children[0].method, - ).toBe('GET') - expect( - radixTree.root.children[0].children[0].children[0].handler, - ).toBeDefined() + expect(radixTree.root.children[0].children[0].method).toBeUndefined() + expect(radixTree.root.children[0].children[0].handler).toBeUndefined() + expect(radixTree.root.children[0].children[0].children[0].name).toBe('/route') + expect(radixTree.root.children[0].children[0].children[0].method).toBe('GET') + expect(radixTree.root.children[0].children[0].children[0].handler).toBeDefined() }) it('should add a route to the radix tree with param', () => { const radixTree = new RadixTree() - radixTree.addRoute('GET', '/a/simple/route/:id', () => - Promise.resolve(), - ) + radixTree.addRoute('GET', '/a/simple/route/:id', () => Promise.resolve()) expect(radixTree.root.children[0].name).toBe('a') expect(radixTree.root.children[0].method).toBeUndefined() expect(radixTree.root.children[0].handler).toBeUndefined() expect(radixTree.root.children[0].children[0].name).toBe('/simple') - expect( - radixTree.root.children[0].children[0].method, - ).toBeUndefined() - expect( - radixTree.root.children[0].children[0].handler, - ).toBeUndefined() - expect( - radixTree.root.children[0].children[0].children[0].name, - ).toBe('/route') - expect( - radixTree.root.children[0].children[0].children[0].handler, - ).toBeUndefined() - expect( - radixTree.root.children[0].children[0].children[0].children[0] - .name, - ).toBe('/:id') - expect( - radixTree.root.children[0].children[0].children[0].children[0] - .method, - ).toBe('GET') + expect(radixTree.root.children[0].children[0].method).toBeUndefined() + expect(radixTree.root.children[0].children[0].handler).toBeUndefined() + expect(radixTree.root.children[0].children[0].children[0].name).toBe('/route') + expect(radixTree.root.children[0].children[0].children[0].handler).toBeUndefined() + expect(radixTree.root.children[0].children[0].children[0].children[0].name).toBe('/:id') + expect(radixTree.root.children[0].children[0].children[0].children[0].method).toBe('GET') - expect( - radixTree.root.children[0].children[0].children[0].children[0] - .isParameterNode, - ).toBe(true) + expect(radixTree.root.children[0].children[0].children[0].children[0].isParameterNode).toBe( + true, + ) }) it('should add two routes to the radix tree', () => { const radixTree = new RadixTree() - radixTree.addRoute('GET', '/a/simple/route', () => - Promise.resolve(), - ) - radixTree.addRoute('POST', '/a/simple/route', () => - Promise.resolve(), - ) + radixTree.addRoute('GET', '/a/simple/route', () => Promise.resolve()) + radixTree.addRoute('POST', '/a/simple/route', () => Promise.resolve()) expect(radixTree.root.children.length).toBe(1) @@ -638,85 +316,45 @@ describe('RadixTree', () => { expect(radixTree.root.children[0].method).toBeUndefined() expect(radixTree.root.children[0].handler).toBeUndefined() expect(radixTree.root.children[0].children[0].name).toBe('/simple') - expect( - radixTree.root.children[0].children[0].method, - ).toBeUndefined() - expect( - radixTree.root.children[0].children[0].handler, - ).toBeUndefined() - expect( - radixTree.root.children[0].children[0].children[0].name, - ).toBe('/route') - expect( - radixTree.root.children[0].children[0].children[0].method, - ).toBe('GET') - expect( - radixTree.root.children[0].children[0].children[1].method, - ).toBe('POST') + expect(radixTree.root.children[0].children[0].method).toBeUndefined() + expect(radixTree.root.children[0].children[0].handler).toBeUndefined() + expect(radixTree.root.children[0].children[0].children[0].name).toBe('/route') + expect(radixTree.root.children[0].children[0].children[0].method).toBe('GET') + expect(radixTree.root.children[0].children[0].children[1].method).toBe('POST') }) it('should add two routes to the radix tree with param', () => { const radixTree = new RadixTree() - radixTree.addRoute('GET', '/a/simple/route/:id', () => - Promise.resolve(), - ) - radixTree.addRoute('GET', '/a/simple/route/:id/test2/', () => - Promise.resolve(), - ) + radixTree.addRoute('GET', '/a/simple/route/:id', () => Promise.resolve()) + radixTree.addRoute('GET', '/a/simple/route/:id/test2/', () => Promise.resolve()) expect(radixTree.root.children[0].name).toBe('a') expect(radixTree.root.children[0].method).toBeUndefined() expect(radixTree.root.children[0].handler).toBeUndefined() expect(radixTree.root.children[0].children[0].name).toBe('/simple') - expect( - radixTree.root.children[0].children[0].method, - ).toBeUndefined() - expect( - radixTree.root.children[0].children[0].handler, - ).toBeUndefined() - expect( - radixTree.root.children[0].children[0].children[0].name, - ).toBe('/route') - expect( - radixTree.root.children[0].children[0].children[0].handler, - ).toBeUndefined() - expect( - radixTree.root.children[0].children[0].children[0].children[0] - .name, - ).toBe('/:id') - expect( - radixTree.root.children[0].children[0].children[0].children[0] - .method, - ).toBe('GET') - expect( - radixTree.root.children[0].children[0].children[0].children[0] - .isParameterNode, - ).toBe(true) + expect(radixTree.root.children[0].children[0].method).toBeUndefined() + expect(radixTree.root.children[0].children[0].handler).toBeUndefined() + expect(radixTree.root.children[0].children[0].children[0].name).toBe('/route') + expect(radixTree.root.children[0].children[0].children[0].handler).toBeUndefined() + expect(radixTree.root.children[0].children[0].children[0].children[0].name).toBe('/:id') + expect(radixTree.root.children[0].children[0].children[0].children[0].method).toBe('GET') + expect(radixTree.root.children[0].children[0].children[0].children[0].isParameterNode).toBe( + true, + ) - expect( - radixTree.root.children[0].children[0].children[0].children[0] - .name, - ).toBe('/:id') - expect( - radixTree.root.children[0].children[0].children[0].children[0] - .children[0].name, - ).toBe('/test2') - expect( - radixTree.root.children[0].children[0].children[0].children[0] - .method, - ).toBe('GET') + expect(radixTree.root.children[0].children[0].children[0].children[0].name).toBe('/:id') + expect(radixTree.root.children[0].children[0].children[0].children[0].children[0].name).toBe( + '/test2', + ) + expect(radixTree.root.children[0].children[0].children[0].children[0].method).toBe('GET') }) it('should add two routes to the radix tree with diffent radix', () => { const radixTree = new RadixTree() - radixTree.addRoute('GET', '/a/simple/route', () => - Promise.resolve(), - ) - radixTree.addRoute('POST', '/a2/simple/route', () => - Promise.resolve(), - ) + radixTree.addRoute('GET', '/a/simple/route', () => Promise.resolve()) + radixTree.addRoute('POST', '/a2/simple/route', () => Promise.resolve()) expect(radixTree.root.children.length).toBe(2) @@ -724,35 +362,19 @@ describe('RadixTree', () => { expect(radixTree.root.children[0].method).toBeUndefined() expect(radixTree.root.children[0].handler).toBeUndefined() expect(radixTree.root.children[0].children[0].name).toBe('/simple') - expect( - radixTree.root.children[0].children[0].method, - ).toBeUndefined() - expect( - radixTree.root.children[0].children[0].handler, - ).toBeUndefined() - expect( - radixTree.root.children[0].children[0].children[0].name, - ).toBe('/route') - expect( - radixTree.root.children[0].children[0].children[0].method, - ).toBe('GET') + expect(radixTree.root.children[0].children[0].method).toBeUndefined() + expect(radixTree.root.children[0].children[0].handler).toBeUndefined() + expect(radixTree.root.children[0].children[0].children[0].name).toBe('/route') + expect(radixTree.root.children[0].children[0].children[0].method).toBe('GET') expect(radixTree.root.children[1].name).toBe('a2') expect(radixTree.root.children[1].method).toBeUndefined() expect(radixTree.root.children[1].handler).toBeUndefined() expect(radixTree.root.children[1].children[0].name).toBe('/simple') - expect( - radixTree.root.children[1].children[0].method, - ).toBeUndefined() - expect( - radixTree.root.children[1].children[0].handler, - ).toBeUndefined() - expect( - radixTree.root.children[1].children[0].children[0].name, - ).toBe('/route') - expect( - radixTree.root.children[1].children[0].children[0].method, - ).toBe('POST') + expect(radixTree.root.children[1].children[0].method).toBeUndefined() + expect(radixTree.root.children[1].children[0].handler).toBeUndefined() + expect(radixTree.root.children[1].children[0].children[0].name).toBe('/route') + expect(radixTree.root.children[1].children[0].children[0].method).toBe('POST') }) it('should add a route with a wildcard at the end', () => { @@ -765,31 +387,18 @@ describe('RadixTree', () => { expect(radixTree.root.children[0].handler).toBeUndefined() expect(radixTree.root.children[0].children[0].name).toBe('/simple') - expect( - radixTree.root.children[0].children[0].method, - ).toBeUndefined() - expect( - radixTree.root.children[0].children[0].handler, - ).toBeUndefined() + expect(radixTree.root.children[0].children[0].method).toBeUndefined() + expect(radixTree.root.children[0].children[0].handler).toBeUndefined() - expect( - radixTree.root.children[0].children[0].children[0].name, - ).toBe('/*') - expect( - radixTree.root.children[0].children[0].children[0] - .isWildcardNode, - ).toBeTrue() - expect( - radixTree.root.children[0].children[0].children[0].method, - ).toBe('GET') + expect(radixTree.root.children[0].children[0].children[0].name).toBe('/*') + expect(radixTree.root.children[0].children[0].children[0].isWildcardNode).toBeTrue() + expect(radixTree.root.children[0].children[0].children[0].method).toBe('GET') }) it('should add a route with a wildcard at the middle', () => { const radixTree = new RadixTree() - radixTree.addRoute('GET', '/a/simple/*/route', () => - Promise.resolve(), - ) + radixTree.addRoute('GET', '/a/simple/*/route', () => Promise.resolve()) expect(radixTree.root.children[0].name).toBe('a') expect(radixTree.root.children[0].method).toBeUndefined() @@ -797,39 +406,17 @@ describe('RadixTree', () => { expect(radixTree.root.children[0].isWildcardNode).toBeFalse() expect(radixTree.root.children[0].children[0].name).toBe('/simple') - expect( - radixTree.root.children[0].children[0].method, - ).toBeUndefined() - expect( - radixTree.root.children[0].children[0].handler, - ).toBeUndefined() + expect(radixTree.root.children[0].children[0].method).toBeUndefined() + expect(radixTree.root.children[0].children[0].handler).toBeUndefined() - expect( - radixTree.root.children[0].children[0].children[0].name, - ).toBe('/*') - expect( - radixTree.root.children[0].children[0].children[0] - .isWildcardNode, - ).toBeTrue() - expect( - radixTree.root.children[0].children[0].children[0].method, - ).toBeUndefined() - expect( - radixTree.root.children[0].children[0].children[0].handler, - ).toBeUndefined() + expect(radixTree.root.children[0].children[0].children[0].name).toBe('/*') + expect(radixTree.root.children[0].children[0].children[0].isWildcardNode).toBeTrue() + expect(radixTree.root.children[0].children[0].children[0].method).toBeUndefined() + expect(radixTree.root.children[0].children[0].children[0].handler).toBeUndefined() - expect( - radixTree.root.children[0].children[0].children[0].children[0] - .name, - ).toBe('/route') - expect( - radixTree.root.children[0].children[0].children[0].children[0] - .method, - ).toBe('GET') - expect( - radixTree.root.children[0].children[0].children[0].children[0] - .handler, - ).toBeDefined() + expect(radixTree.root.children[0].children[0].children[0].children[0].name).toBe('/route') + expect(radixTree.root.children[0].children[0].children[0].children[0].method).toBe('GET') + expect(radixTree.root.children[0].children[0].children[0].children[0].handler).toBeDefined() }) }) @@ -837,9 +424,7 @@ describe('RadixTree', () => { it('should find a route', () => { const radixTree = new RadixTree() - radixTree.addRoute('GET', '/a/simple/route-2', () => - Promise.resolve(), - ) + radixTree.addRoute('GET', '/a/simple/route-2', () => Promise.resolve()) radixTree.optimizeTree() @@ -853,9 +438,7 @@ describe('RadixTree', () => { it("should find a route with any HTTP method when the route's method is ALL", () => { const radixTree = new RadixTree() - radixTree.addRoute('ALL', '/a/simple/route-2', () => - Promise.resolve(), - ) + radixTree.addRoute('ALL', '/a/simple/route-2', () => Promise.resolve()) radixTree.optimizeTree() @@ -931,9 +514,7 @@ describe('RadixTree', () => { const radixTree = new RadixTree() radixTree.addRoute('GET', '/a/simple', () => Promise.resolve()) - radixTree.addRoute('GET', '/a/simple/route', () => - Promise.resolve(), - ) + radixTree.addRoute('GET', '/a/simple/route', () => Promise.resolve()) radixTree.optimizeTree() @@ -952,9 +533,7 @@ describe('RadixTree', () => { it('should find a route not begining with a slash', () => { const radixTree = new RadixTree() - radixTree.addRoute('GET', '/a/simple/route', () => - Promise.resolve(), - ) + radixTree.addRoute('GET', '/a/simple/route', () => Promise.resolve()) radixTree.optimizeTree() @@ -968,15 +547,9 @@ describe('RadixTree', () => { it('should find a route with same length on multiple children', () => { const radixTree = new RadixTree() - radixTree.addRoute('GET', '/a/simple/route1', () => - Promise.resolve(), - ) - radixTree.addRoute('GET', '/a/simple/route2', () => - Promise.resolve(), - ) - radixTree.addRoute('GET', '/a/simple/route3', () => - Promise.resolve(), - ) + radixTree.addRoute('GET', '/a/simple/route1', () => Promise.resolve()) + radixTree.addRoute('GET', '/a/simple/route2', () => Promise.resolve()) + radixTree.addRoute('GET', '/a/simple/route3', () => Promise.resolve()) radixTree.optimizeTree() @@ -987,34 +560,24 @@ describe('RadixTree', () => { expect(route?.handler).toBeDefined() }) - it.each([true, false])( - 'should not find a route that not exist', - (withOptimizeTree) => { - const radixTree = new RadixTree() + it.each([true, false])('should not find a route that not exist', (withOptimizeTree) => { + const radixTree = new RadixTree() - radixTree.addRoute('GET', '/a/simple/route', () => - Promise.resolve(), - ) + radixTree.addRoute('GET', '/a/simple/route', () => Promise.resolve()) - if (withOptimizeTree) radixTree.optimizeTree() + if (withOptimizeTree) radixTree.optimizeTree() - const route = radixTree.findRoute('GET', '/a/simple') - const route2 = radixTree.findRoute( - 'GET', - '/a/simple/route/bigger', - ) + const route = radixTree.findRoute('GET', '/a/simple') + const route2 = radixTree.findRoute('GET', '/a/simple/route/bigger') - expect(route).toBeNull() - expect(route2).toBeNull() - }, - ) + expect(route).toBeNull() + expect(route2).toBeNull() + }) it('should find a route ending by a slash', () => { const radixTree = new RadixTree() - radixTree.addRoute('GET', '/a/simple/route/', () => - Promise.resolve(), - ) + radixTree.addRoute('GET', '/a/simple/route/', () => Promise.resolve()) radixTree.optimizeTree() @@ -1035,9 +598,7 @@ describe('RadixTree', () => { it('should match a param route when the value is missing but a trailing slash is present', () => { const radixTree = new RadixTree() - radixTree.addRoute('GET', '/bucket/:filename', () => - Promise.resolve(), - ) + radixTree.addRoute('GET', '/bucket/:filename', () => Promise.resolve()) radixTree.optimizeTree() @@ -1051,12 +612,8 @@ describe('RadixTree', () => { it('should find a route by method', () => { const radixTree = new RadixTree() - radixTree.addRoute('GET', '/a/simple/route', () => - Promise.resolve(), - ) - radixTree.addRoute('POST', '/a/simple/route', () => - Promise.resolve(), - ) + radixTree.addRoute('GET', '/a/simple/route', () => Promise.resolve()) + radixTree.addRoute('POST', '/a/simple/route', () => Promise.resolve()) radixTree.optimizeTree() @@ -1068,66 +625,41 @@ describe('RadixTree', () => { expect(route?.handler).toBeDefined() }) - it.each([true, false])( - 'should find a complex route by method', - (withOptimizeTree) => { - const radixTree = new RadixTree() + it.each([true, false])('should find a complex route by method', (withOptimizeTree) => { + const radixTree = new RadixTree() - radixTree.addRoute( - 'GET', - '/there/is/a/very/long/and/complex/route', - () => Promise.resolve(), - ) - radixTree.addRoute( - 'GET', - '/there/is/a/very/long/and/complex/route2', - () => Promise.resolve(), - ) - radixTree.addRoute( - 'POST', - '/there/is/a/very/long/and/complex/addRoute', - () => Promise.resolve(), - ) + radixTree.addRoute('GET', '/there/is/a/very/long/and/complex/route', () => Promise.resolve()) + radixTree.addRoute('GET', '/there/is/a/very/long/and/complex/route2', () => Promise.resolve()) + radixTree.addRoute('POST', '/there/is/a/very/long/and/complex/addRoute', () => + Promise.resolve(), + ) - if (withOptimizeTree) radixTree.optimizeTree() + if (withOptimizeTree) radixTree.optimizeTree() - const route = radixTree.findRoute( - 'GET', - '/there/is/a/very/long/and/complex/route', - ) + const route = radixTree.findRoute('GET', '/there/is/a/very/long/and/complex/route') - const route2 = radixTree.findRoute( - 'GET', - '/there/is/a/very/long/and/complex/route2', - ) + const route2 = radixTree.findRoute('GET', '/there/is/a/very/long/and/complex/route2') - const route3 = radixTree.findRoute( - 'POST', - '/there/is/a/very/long/and/complex/addRoute', - ) + const route3 = radixTree.findRoute('POST', '/there/is/a/very/long/and/complex/addRoute') - const invalidRoute = radixTree.findRoute( - 'POST', - '/there/is/a/very/long/and/complex/route', - ) + const invalidRoute = radixTree.findRoute('POST', '/there/is/a/very/long/and/complex/route') - expect(route).not.toBeNull() - expect(route?.name).toBe('/route') - expect(route?.method).toBe('GET') - expect(route?.handler).toBeDefined() + expect(route).not.toBeNull() + expect(route?.name).toBe('/route') + expect(route?.method).toBe('GET') + expect(route?.handler).toBeDefined() - expect(route2).not.toBeNull() - expect(route2?.name).toBe('/route2') - expect(route2?.method).toBe('GET') - expect(route2?.handler).toBeDefined() + expect(route2).not.toBeNull() + expect(route2?.name).toBe('/route2') + expect(route2?.method).toBe('GET') + expect(route2?.handler).toBeDefined() - expect(route3).not.toBeNull() - expect(route3?.method).toBe('POST') - expect(route3?.handler).toBeDefined() + expect(route3).not.toBeNull() + expect(route3?.method).toBe('POST') + expect(route3?.handler).toBeDefined() - expect(invalidRoute).toBeNull() - }, - ) + expect(invalidRoute).toBeNull() + }) it.each([true, false])( 'should find a route with a parameter directly after the root', @@ -1233,10 +765,7 @@ describe('RadixTree', () => { radixTree.optimizeTree() const route = radixTree.findRoute('GET', '/a/simple/route') - const route2 = radixTree.findRoute( - 'GET', - '/*/another/big/long/route', - ) + const route2 = radixTree.findRoute('GET', '/*/another/big/long/route') expect(route).not.toBeNull() expect(route?.name).toBe('*') @@ -1250,9 +779,7 @@ describe('RadixTree', () => { it('should find a route ending by */', () => { const radixTree = new RadixTree() - radixTree.addRoute('GET', '/a/simple/route/*/', () => - Promise.resolve(), - ) + radixTree.addRoute('GET', '/a/simple/route/*/', () => Promise.resolve()) radixTree.optimizeTree() @@ -1266,16 +793,11 @@ describe('RadixTree', () => { it('should find a complex route with a wildcard', () => { const radixTree = new RadixTree() - radixTree.addRoute('GET', '/a/simple/route/*/', () => - Promise.resolve(), - ) + radixTree.addRoute('GET', '/a/simple/route/*/', () => Promise.resolve()) radixTree.optimizeTree() - const route = radixTree.findRoute( - 'GET', - '/a/simple/route/*/*/*/route', - ) + const route = radixTree.findRoute('GET', '/a/simple/route/*/*/*/route') expect(route).not.toBeNull() expect(route?.name).toBe('/*') @@ -1285,9 +807,7 @@ describe('RadixTree', () => { it('should find a route with many parameters', () => { const radixTree = new RadixTree() - radixTree.addRoute('GET', '/a/:id/:name/:age', () => - Promise.resolve(), - ) + radixTree.addRoute('GET', '/a/:id/:name/:age', () => Promise.resolve()) radixTree.optimizeTree() @@ -1351,18 +871,13 @@ describe('RadixTree', () => { it('should find a route with multiple wildcards', () => { const radixTree = new RadixTree() - radixTree.addRoute('GET', '/a/simple/*/*/*/', () => - Promise.resolve(), - ) + radixTree.addRoute('GET', '/a/simple/*/*/*/', () => Promise.resolve()) radixTree.optimizeTree() const route = radixTree.findRoute('GET', '/a/simple/route') const route2 = radixTree.findRoute('GET', '/a/simple/route/route') - const route3 = radixTree.findRoute( - 'GET', - '/a/simple/route/route/again/another/route', - ) + const route3 = radixTree.findRoute('GET', '/a/simple/route/route/again/another/route') expect(route).not.toBeNull() expect(route?.method).toBe('GET') @@ -1380,9 +895,7 @@ describe('RadixTree', () => { it('should find a route with a wildcard at the middle', () => { const radixTree = new RadixTree() - radixTree.addRoute('GET', '/a/simple/*/route', () => - Promise.resolve(), - ) + radixTree.addRoute('GET', '/a/simple/*/route', () => Promise.resolve()) radixTree.optimizeTree() @@ -1404,9 +917,7 @@ describe('RadixTree', () => { it('should not find a non existing route', () => { const radixTree = new RadixTree() - radixTree.addRoute('GET', '/a/simple/route', () => - Promise.resolve(), - ) + radixTree.addRoute('GET', '/a/simple/route', () => Promise.resolve()) radixTree.optimizeTree() @@ -1431,9 +942,7 @@ describe('RadixTree', () => { it('should extract the parameter when the parameter is at the begin of the route', () => { const radixTree = new RadixTree() - radixTree.addRoute('GET', '/:name/:id/route', () => - Promise.resolve(), - ) + radixTree.addRoute('GET', '/:name/:id/route', () => Promise.resolve()) radixTree.optimizeTree() @@ -1446,9 +955,7 @@ describe('RadixTree', () => { it('should extract the parameter when the parameter is at the end of the route', () => { const radixTree = new RadixTree() - radixTree.addRoute('GET', '/:name/:id/:route', () => - Promise.resolve(), - ) + radixTree.addRoute('GET', '/:name/:id/:route', () => Promise.resolve()) radixTree.optimizeTree() @@ -1465,9 +972,7 @@ describe('RadixTree', () => { it('should extract the parameter when the parameter is at the end of the route with a slash', () => { const radixTree = new RadixTree() - radixTree.addRoute('GET', '/name/id/:route', () => - Promise.resolve(), - ) + radixTree.addRoute('GET', '/name/id/:route', () => Promise.resolve()) radixTree.optimizeTree() @@ -1497,12 +1002,8 @@ describe('RadixTree', () => { it('should optimize a tree by merging all the node with only one child', () => { const radixTree = new RadixTree() - radixTree.addRoute('GET', '/a/simple/route', () => - Promise.resolve(), - ) - radixTree.addRoute('POST', '/a/simple/route', () => - Promise.resolve(), - ) + radixTree.addRoute('GET', '/a/simple/route', () => Promise.resolve()) + radixTree.addRoute('POST', '/a/simple/route', () => Promise.resolve()) radixTree.optimizeTree() @@ -1527,9 +1028,7 @@ describe('RadixTree', () => { const radixTree = new RadixTree() radixTree.addRoute('GET', '/a/simple', () => Promise.resolve()) - radixTree.addRoute('GET', '/a/simple/route', () => - Promise.resolve(), - ) + radixTree.addRoute('GET', '/a/simple/route', () => Promise.resolve()) radixTree.optimizeTree() @@ -1544,9 +1043,7 @@ describe('RadixTree', () => { it('should merge a tree when there is only one route', () => { const radixTree = new RadixTree() - radixTree.addRoute('GET', '/a/simple/route', () => - Promise.resolve(), - ) + radixTree.addRoute('GET', '/a/simple/route', () => Promise.resolve()) radixTree.optimizeTree() @@ -1575,25 +1072,13 @@ describe('RadixTree', () => { expect(radixTree.root.children[0].handler).toBeUndefined() expect(radixTree.root.children[0].children[0].name).toBe('/:id') - expect( - radixTree.root.children[0].children[0].isParameterNode, - ).toBeTrue() - expect( - radixTree.root.children[0].children[0].method, - ).toBeUndefined() - expect( - radixTree.root.children[0].children[0].handler, - ).toBeUndefined() + expect(radixTree.root.children[0].children[0].isParameterNode).toBeTrue() + expect(radixTree.root.children[0].children[0].method).toBeUndefined() + expect(radixTree.root.children[0].children[0].handler).toBeUndefined() - expect( - radixTree.root.children[0].children[0].children[0].name, - ).toBe('/route') - expect( - radixTree.root.children[0].children[0].children[0].method, - ).toBe('GET') - expect( - radixTree.root.children[0].children[0].children[0].handler, - ).toBeDefined() + expect(radixTree.root.children[0].children[0].children[0].name).toBe('/route') + expect(radixTree.root.children[0].children[0].children[0].method).toBe('GET') + expect(radixTree.root.children[0].children[0].children[0].handler).toBeDefined() }) it('should correctly merge a tree with a wildcard', () => { @@ -1612,9 +1097,7 @@ describe('RadixTree', () => { expect(radixTree.root.children[0].handler).toBeUndefined() expect(radixTree.root.children[0].children[0].name).toBe('/*') - expect( - radixTree.root.children[0].children[0].isWildcardNode, - ).toBeTrue() + expect(radixTree.root.children[0].children[0].isWildcardNode).toBeTrue() expect(radixTree.root.children[0].children[0].method).toBe('GET') expect(radixTree.root.children[0].children[0].handler).toBeDefined() }) @@ -1622,15 +1105,9 @@ describe('RadixTree', () => { it('should merge a tree with complex structure', () => { const radixTree = new RadixTree() - radixTree.addRoute('GET', '/there/is/a/complex/route/next2', () => - Promise.resolve(), - ) - radixTree.addRoute('POST', '/there/is/a2/complex/route/next2', () => - Promise.resolve(), - ) - radixTree.addRoute('POST', '/there/is/a2/complex/route/next3', () => - Promise.resolve(), - ) + radixTree.addRoute('GET', '/there/is/a/complex/route/next2', () => Promise.resolve()) + radixTree.addRoute('POST', '/there/is/a2/complex/route/next2', () => Promise.resolve()) + radixTree.addRoute('POST', '/there/is/a2/complex/route/next3', () => Promise.resolve()) radixTree.optimizeTree() @@ -1642,39 +1119,19 @@ describe('RadixTree', () => { expect(radixTree.root.children[0].method).toBeUndefined() expect(radixTree.root.children[0].handler).toBeUndefined() - expect(radixTree.root.children[0].children[0].name).toBe( - '/a/complex/route/next2', - ) + expect(radixTree.root.children[0].children[0].name).toBe('/a/complex/route/next2') expect(radixTree.root.children[0].children[0].method).toBe('GET') expect(radixTree.root.children[0].children[0].handler).toBeDefined() - expect(radixTree.root.children[0].children[1].name).toBe( - '/a2/complex/route', - ) - expect( - radixTree.root.children[0].children[1].method, - ).toBeUndefined() - expect( - radixTree.root.children[0].children[1].handler, - ).toBeUndefined() - expect( - radixTree.root.children[0].children[1].children[0].name, - ).toBe('/next2') - expect( - radixTree.root.children[0].children[1].children[0].method, - ).toBe('POST') - expect( - radixTree.root.children[0].children[1].children[0].handler, - ).toBeDefined() - expect( - radixTree.root.children[0].children[1].children[1].name, - ).toBe('/next3') - expect( - radixTree.root.children[0].children[1].children[1].method, - ).toBe('POST') - expect( - radixTree.root.children[0].children[1].children[1].handler, - ).toBeDefined() + expect(radixTree.root.children[0].children[1].name).toBe('/a2/complex/route') + expect(radixTree.root.children[0].children[1].method).toBeUndefined() + expect(radixTree.root.children[0].children[1].handler).toBeUndefined() + expect(radixTree.root.children[0].children[1].children[0].name).toBe('/next2') + expect(radixTree.root.children[0].children[1].children[0].method).toBe('POST') + expect(radixTree.root.children[0].children[1].children[0].handler).toBeDefined() + expect(radixTree.root.children[0].children[1].children[1].name).toBe('/next3') + expect(radixTree.root.children[0].children[1].children[1].method).toBe('POST') + expect(radixTree.root.children[0].children[1].children[1].handler).toBeDefined() }) }) }) diff --git a/packages/wobe/src/router/RadixTree.ts b/packages/wobe/src/router/RadixTree.ts index 5ace577..7313e5d 100644 --- a/packages/wobe/src/router/RadixTree.ts +++ b/packages/wobe/src/router/RadixTree.ts @@ -17,15 +17,10 @@ export class RadixTree { let foundNode = currentNode.children.find( (node) => - node.name === (i === 0 ? '' : '/') + pathPart && - (node.method === method || !node.method), + node.name === (i === 0 ? '' : '/') + pathPart && (node.method === method || !node.method), ) - if ( - foundNode && - foundNode.method === method && - i === pathParts.length - 1 - ) + if (foundNode && foundNode.method === method && i === pathParts.length - 1) throw new Error(`Route ${method} ${path} already exists`) if (!foundNode) { @@ -75,17 +70,8 @@ export class RadixTree { } } - addHook( - hook: Hook, - path: string, - handler: WobeHandler, - method: HttpMethod, - node?: Node, - ) { - if (this.isOptimized) - throw new Error( - 'Cannot add hooks after the tree has been optimized', - ) + addHook(hook: Hook, path: string, handler: WobeHandler, method: HttpMethod, node?: Node) { + if (this.isOptimized) throw new Error('Cannot add hooks after the tree has been optimized') let currentNode = node || this.root @@ -95,10 +81,7 @@ export class RadixTree { for (let i = 0; i < node.children.length; i++) { const child = node.children[i] - if ( - child.handler && - (method === child.method || method === 'ALL') - ) + if (child.handler && (method === child.method || method === 'ALL')) this._addHookToNode(child, hook, handler) addHookToChildren(child) @@ -108,10 +91,7 @@ export class RadixTree { for (let i = 0; i < currentNode.children.length; i++) { const child = currentNode.children[i] - if ( - child.handler && - (method === child.method || method === 'ALL') - ) { + if (child.handler && (method === child.method || method === 'ALL')) { this._addHookToNode(child, hook, handler) } @@ -140,8 +120,7 @@ export class RadixTree { const foundNode = currentNode.children.find( (node) => - node.name === - (currentNode.name === '/' ? '' : '/') + pathPart && + node.name === (currentNode.name === '/' ? '' : '/') + pathPart && ((node.method && node.method === method) || !node.method), ) @@ -167,19 +146,14 @@ export class RadixTree { let nextIndexToEnd = 0 let params: Record | undefined - const isNodeMatch = ( - node: Node, - indexToBegin: number, - indexToEnd: number, - ): Node | null => { + const isNodeMatch = (node: Node, indexToBegin: number, indexToEnd: number): Node | null => { const nextIndexToBegin = indexToBegin + (indexToEnd - indexToBegin) for (let i = 0; i < node.children.length; i++) { const child = node.children[i] const childName = child.name - const isChildWildcardOrParameterNode = - child.isWildcardNode || child.isParameterNode + const isChildWildcardOrParameterNode = child.isWildcardNode || child.isParameterNode // We get the next end index nextIndexToEnd = localPath.indexOf( @@ -191,8 +165,7 @@ export class RadixTree { if (nextIndexToEnd === -1) nextIndexToEnd = pathLength - if (indexToEnd === nextIndexToEnd && !child.isWildcardNode) - continue + if (indexToEnd === nextIndexToEnd && !child.isWildcardNode) continue // If the child is not a wildcard or parameter node // and the length of the child name is different from the length of the path @@ -207,11 +180,10 @@ export class RadixTree { const indexToAddIfFirstNode = indexToBegin === 0 ? 0 : 1 - params[childName.slice(1 + indexToAddIfFirstNode)] = - localPath.slice( - nextIndexToBegin + indexToAddIfFirstNode, - nextIndexToEnd, - ) + params[childName.slice(1 + indexToAddIfFirstNode)] = localPath.slice( + nextIndexToBegin + indexToAddIfFirstNode, + nextIndexToEnd, + ) } // If the child has no children and the node is a wildcard or parameter node @@ -228,19 +200,12 @@ export class RadixTree { ) { if (isChildWildcardOrParameterNode) return child - const pathToCompute = localPath.slice( - nextIndexToBegin, - nextIndexToEnd, - ) + const pathToCompute = localPath.slice(nextIndexToBegin, nextIndexToEnd) if (pathToCompute === childName) return child } - const foundNode = isNodeMatch( - child, - nextIndexToBegin, - nextIndexToEnd, - ) + const foundNode = isNodeMatch(child, nextIndexToBegin, nextIndexToEnd) if (foundNode) return foundNode } diff --git a/packages/wobe/src/router/UrlPatternRouter.test.ts b/packages/wobe/src/router/UrlPatternRouter.test.ts index 146480d..e8b9d52 100644 --- a/packages/wobe/src/router/UrlPatternRouter.test.ts +++ b/packages/wobe/src/router/UrlPatternRouter.test.ts @@ -11,430 +11,187 @@ describe('UrlPatternRouter', () => { radixTree.optimizeTree() expect(() => { - radixTree.addHook( - 'beforeHandler', - '/test', - () => Promise.resolve(), - 'GET', - ) - }).toThrowError( - 'Cannot add hooks after the tree has been optimized', - ) + radixTree.addHook('beforeHandler', '/test', () => Promise.resolve(), 'GET') + }).toThrowError('Cannot add hooks after the tree has been optimized') }) it('should add a single hook based on the method of the request', () => { const radixTree = new UrlPatternRouter() - radixTree.addRoute('GET', '/a/simple/route', () => - Promise.resolve(), - ) - radixTree.addRoute('POST', '/a/simple/route', () => - Promise.resolve(), - ) - radixTree.addHook( - 'beforeHandler', - '/a/simple/route', - () => Promise.resolve(), - 'GET', - ) + radixTree.addRoute('GET', '/a/simple/route', () => Promise.resolve()) + radixTree.addRoute('POST', '/a/simple/route', () => Promise.resolve()) + radixTree.addHook('beforeHandler', '/a/simple/route', () => Promise.resolve(), 'GET') - radixTree.addHook( - 'beforeHandler', - '/a/simple/route', - () => Promise.resolve(), - 'POST', - ) + radixTree.addHook('beforeHandler', '/a/simple/route', () => Promise.resolve(), 'POST') - expect( - radixTree.root.children[0].children[0].children[0].name, - ).toBe('/route') - expect( - radixTree.root.children[0].children[0].children[0].method, - ).toBe('GET') - expect( - radixTree.root.children[0].children[0].children[0].handler, - ).toBeDefined() - expect( - radixTree.root.children[0].children[0].children[0] - .beforeHandlerHook?.length, - ).toBe(1) + expect(radixTree.root.children[0].children[0].children[0].name).toBe('/route') + expect(radixTree.root.children[0].children[0].children[0].method).toBe('GET') + expect(radixTree.root.children[0].children[0].children[0].handler).toBeDefined() + expect(radixTree.root.children[0].children[0].children[0].beforeHandlerHook?.length).toBe(1) - expect( - radixTree.root.children[0].children[0].children[1].name, - ).toBe('/route') - expect( - radixTree.root.children[0].children[0].children[1].method, - ).toBe('POST') - expect( - radixTree.root.children[0].children[0].children[1].handler, - ).toBeDefined() - expect( - radixTree.root.children[0].children[0].children[1] - .beforeHandlerHook?.length, - ).toBe(1) + expect(radixTree.root.children[0].children[0].children[1].name).toBe('/route') + expect(radixTree.root.children[0].children[0].children[1].method).toBe('POST') + expect(radixTree.root.children[0].children[0].children[1].handler).toBeDefined() + expect(radixTree.root.children[0].children[0].children[1].beforeHandlerHook?.length).toBe(1) }) it('should add a hook with path * on beforeHandler with multiple route', () => { const radixTree = new UrlPatternRouter() - radixTree.addRoute('GET', '/a/simple/route', () => - Promise.resolve(), - ) + radixTree.addRoute('GET', '/a/simple/route', () => Promise.resolve()) radixTree.addRoute('GET', '/a/simple', () => Promise.resolve()) - radixTree.addHook( - 'beforeHandler', - '*', - () => Promise.resolve(), - 'GET', - ) + radixTree.addHook('beforeHandler', '*', () => Promise.resolve(), 'GET') - expect( - radixTree.root.children[0].children[0].children[0].name, - ).toBe('/route') - expect( - radixTree.root.children[0].children[0].children[0].method, - ).toBe('GET') - expect( - radixTree.root.children[0].children[0].children[0].handler, - ).toBeDefined() + expect(radixTree.root.children[0].children[0].children[0].name).toBe('/route') + expect(radixTree.root.children[0].children[0].children[0].method).toBe('GET') + expect(radixTree.root.children[0].children[0].children[0].handler).toBeDefined() - expect( - radixTree.root.children[0].children[0].beforeHandlerHook - ?.length, - ).toBe(1) + expect(radixTree.root.children[0].children[0].beforeHandlerHook?.length).toBe(1) - expect( - radixTree.root.children[0].children[0].children[0] - .beforeHandlerHook?.length, - ).toBe(1) - expect( - radixTree.root.children[0].children[0].children[0] - .afterHandlerHook, - ).toBeUndefined() + expect(radixTree.root.children[0].children[0].children[0].beforeHandlerHook?.length).toBe(1) + expect(radixTree.root.children[0].children[0].children[0].afterHandlerHook).toBeUndefined() }) it('should add a hook with path * on beforeHandler', () => { const radixTree = new UrlPatternRouter() - radixTree.addRoute('GET', '/a/simple/route', () => - Promise.resolve(), - ) + radixTree.addRoute('GET', '/a/simple/route', () => Promise.resolve()) - radixTree.addHook( - 'beforeHandler', - '*', - () => Promise.resolve(), - 'GET', - ) + radixTree.addHook('beforeHandler', '*', () => Promise.resolve(), 'GET') - expect( - radixTree.root.children[0].children[0].children[0].name, - ).toBe('/route') - expect( - radixTree.root.children[0].children[0].children[0].method, - ).toBe('GET') - expect( - radixTree.root.children[0].children[0].children[0].handler, - ).toBeDefined() + expect(radixTree.root.children[0].children[0].children[0].name).toBe('/route') + expect(radixTree.root.children[0].children[0].children[0].method).toBe('GET') + expect(radixTree.root.children[0].children[0].children[0].handler).toBeDefined() - expect( - radixTree.root.children[0].children[0].children[0] - .beforeHandlerHook?.length, - ).toBe(1) - expect( - radixTree.root.children[0].children[0].children[0] - .afterHandlerHook, - ).toBeUndefined() + expect(radixTree.root.children[0].children[0].children[0].beforeHandlerHook?.length).toBe(1) + expect(radixTree.root.children[0].children[0].children[0].afterHandlerHook).toBeUndefined() }) it('should add a hook beforeHandler to the radix tree', () => { const radixTree = new UrlPatternRouter() - radixTree.addRoute('GET', '/a/simple/route', () => - Promise.resolve(), - ) - radixTree.addHook( - 'beforeHandler', - '/a/simple/route', - () => Promise.resolve(), - 'GET', - ) + radixTree.addRoute('GET', '/a/simple/route', () => Promise.resolve()) + radixTree.addHook('beforeHandler', '/a/simple/route', () => Promise.resolve(), 'GET') - expect( - radixTree.root.children[0].children[0].children[0].name, - ).toBe('/route') - expect( - radixTree.root.children[0].children[0].children[0].method, - ).toBe('GET') - expect( - radixTree.root.children[0].children[0].children[0].handler, - ).toBeDefined() + expect(radixTree.root.children[0].children[0].children[0].name).toBe('/route') + expect(radixTree.root.children[0].children[0].children[0].method).toBe('GET') + expect(radixTree.root.children[0].children[0].children[0].handler).toBeDefined() - expect( - radixTree.root.children[0].children[0].children[0] - .beforeHandlerHook?.length, - ).toBe(1) - expect( - radixTree.root.children[0].children[0].children[0] - .afterHandlerHook, - ).toBeUndefined() + expect(radixTree.root.children[0].children[0].children[0].beforeHandlerHook?.length).toBe(1) + expect(radixTree.root.children[0].children[0].children[0].afterHandlerHook).toBeUndefined() }) it('should add a hook afterHandler to the radix tree', () => { const radixTree = new UrlPatternRouter() - radixTree.addRoute('GET', '/a/simple/route', () => - Promise.resolve(), - ) - radixTree.addHook( - 'afterHandler', - '/a/simple/route', - () => Promise.resolve(), - 'GET', - ) + radixTree.addRoute('GET', '/a/simple/route', () => Promise.resolve()) + radixTree.addHook('afterHandler', '/a/simple/route', () => Promise.resolve(), 'GET') - expect( - radixTree.root.children[0].children[0].children[0].name, - ).toBe('/route') - expect( - radixTree.root.children[0].children[0].children[0].method, - ).toBe('GET') - expect( - radixTree.root.children[0].children[0].children[0].handler, - ).toBeDefined() + expect(radixTree.root.children[0].children[0].children[0].name).toBe('/route') + expect(radixTree.root.children[0].children[0].children[0].method).toBe('GET') + expect(radixTree.root.children[0].children[0].children[0].handler).toBeDefined() - expect( - radixTree.root.children[0].children[0].children[0] - .beforeHandlerHook, - ).toBeUndefined() - expect( - radixTree.root.children[0].children[0].children[0] - .afterHandlerHook?.length, - ).toBe(1) + expect(radixTree.root.children[0].children[0].children[0].beforeHandlerHook).toBeUndefined() + expect(radixTree.root.children[0].children[0].children[0].afterHandlerHook?.length).toBe(1) }) it('should add a hook beforeAndAfterHandler to the radix tree', () => { const radixTree = new UrlPatternRouter() - radixTree.addRoute('GET', '/a/simple/route', () => - Promise.resolve(), - ) - radixTree.addHook( - 'beforeAndAfterHandler', - '/a/simple/route', - () => Promise.resolve(), - 'GET', - ) + radixTree.addRoute('GET', '/a/simple/route', () => Promise.resolve()) + radixTree.addHook('beforeAndAfterHandler', '/a/simple/route', () => Promise.resolve(), 'GET') - expect( - radixTree.root.children[0].children[0].children[0].name, - ).toBe('/route') - expect( - radixTree.root.children[0].children[0].children[0].method, - ).toBe('GET') - expect( - radixTree.root.children[0].children[0].children[0].handler, - ).toBeDefined() + expect(radixTree.root.children[0].children[0].children[0].name).toBe('/route') + expect(radixTree.root.children[0].children[0].children[0].method).toBe('GET') + expect(radixTree.root.children[0].children[0].children[0].handler).toBeDefined() - expect( - radixTree.root.children[0].children[0].children[0] - .beforeHandlerHook?.length, - ).toBe(1) - expect( - radixTree.root.children[0].children[0].children[0] - .afterHandlerHook?.length, - ).toBe(1) + expect(radixTree.root.children[0].children[0].children[0].beforeHandlerHook?.length).toBe(1) + expect(radixTree.root.children[0].children[0].children[0].afterHandlerHook?.length).toBe(1) }) it('should add a hook with a wildcard in the middle', () => { const radixTree = new UrlPatternRouter() - radixTree.addRoute('GET', '/a/simple/route', () => - Promise.resolve(), - ) - radixTree.addHook( - 'beforeHandler', - '/a/*/route', - () => Promise.resolve(), - 'GET', - ) + radixTree.addRoute('GET', '/a/simple/route', () => Promise.resolve()) + radixTree.addHook('beforeHandler', '/a/*/route', () => Promise.resolve(), 'GET') - expect( - radixTree.root.children[0].children[0].children[0] - .beforeHandlerHook?.length, - ).toBe(1) - expect( - radixTree.root.children[0].children[0].children[0] - .afterHandlerHook, - ).toBeUndefined() + expect(radixTree.root.children[0].children[0].children[0].beforeHandlerHook?.length).toBe(1) + expect(radixTree.root.children[0].children[0].children[0].afterHandlerHook).toBeUndefined() }) it('should add a hook with a wildcard at the end', () => { const radixTree = new UrlPatternRouter() - radixTree.addRoute('GET', '/a/simple/route', () => - Promise.resolve(), - ) + radixTree.addRoute('GET', '/a/simple/route', () => Promise.resolve()) - radixTree.addHook( - 'beforeHandler', - '/a/simple/*', - () => Promise.resolve(), - 'GET', - ) + radixTree.addHook('beforeHandler', '/a/simple/*', () => Promise.resolve(), 'GET') - expect( - radixTree.root.children[0].children[0].children[0] - .beforeHandlerHook?.length, - ).toBe(1) - expect( - radixTree.root.children[0].children[0].children[0] - .afterHandlerHook, - ).toBeUndefined() + expect(radixTree.root.children[0].children[0].children[0].beforeHandlerHook?.length).toBe(1) + expect(radixTree.root.children[0].children[0].children[0].afterHandlerHook).toBeUndefined() }) it('should add a hook with a slash at the end', () => { const radixTree = new UrlPatternRouter() - radixTree.addRoute('GET', '/a/simple/route', () => - Promise.resolve(), - ) - radixTree.addHook( - 'beforeHandler', - '/a/simple/route/', - () => Promise.resolve(), - 'GET', - ) + radixTree.addRoute('GET', '/a/simple/route', () => Promise.resolve()) + radixTree.addHook('beforeHandler', '/a/simple/route/', () => Promise.resolve(), 'GET') - expect( - radixTree.root.children[0].children[0].children[0] - .beforeHandlerHook?.length, - ).toBe(1) - expect( - radixTree.root.children[0].children[0].children[0] - .afterHandlerHook, - ).toBeUndefined() + expect(radixTree.root.children[0].children[0].children[0].beforeHandlerHook?.length).toBe(1) + expect(radixTree.root.children[0].children[0].children[0].afterHandlerHook).toBeUndefined() }) it('should add two hooks if there are two routes', () => { const radixTree = new UrlPatternRouter() - radixTree.addRoute('GET', '/a/simple/route', () => - Promise.resolve(), - ) - radixTree.addRoute('GET', '/a/simple/route2', () => - Promise.resolve(), - ) - radixTree.addHook( - 'beforeHandler', - '/a/simple/route', - () => Promise.resolve(), - 'GET', - ) - radixTree.addHook( - 'afterHandler', - '/a/simple/route2', - () => Promise.resolve(), - 'GET', - ) + radixTree.addRoute('GET', '/a/simple/route', () => Promise.resolve()) + radixTree.addRoute('GET', '/a/simple/route2', () => Promise.resolve()) + radixTree.addHook('beforeHandler', '/a/simple/route', () => Promise.resolve(), 'GET') + radixTree.addHook('afterHandler', '/a/simple/route2', () => Promise.resolve(), 'GET') + expect(radixTree.root.children[0].children[0].children[0].beforeHandlerHook?.length).toBe(1) expect( - radixTree.root.children[0].children[0].children[0] - .beforeHandlerHook?.length, - ).toBe(1) - expect( - radixTree.root.children[0].children[0].children[0] - .beforeHandlerHook?.[0], + radixTree.root.children[0].children[0].children[0].beforeHandlerHook?.[0], ).toBeFunction() - expect( - radixTree.root.children[0].children[0].children[1] - .beforeHandlerHook, - ).toBeUndefined() + expect(radixTree.root.children[0].children[0].children[1].beforeHandlerHook).toBeUndefined() + expect(radixTree.root.children[0].children[0].children[0].afterHandlerHook).toBeUndefined() + expect(radixTree.root.children[0].children[0].children[1].afterHandlerHook?.length).toBe(1) expect( - radixTree.root.children[0].children[0].children[0] - .afterHandlerHook, - ).toBeUndefined() - expect( - radixTree.root.children[0].children[0].children[1] - .afterHandlerHook?.length, - ).toBe(1) - expect( - radixTree.root.children[0].children[0].children[1] - .afterHandlerHook?.[0], + radixTree.root.children[0].children[0].children[1].afterHandlerHook?.[0], ).toBeFunction() }) it('should not add a hook with a wildcard in the middle if the path not match', () => { const radixTree = new UrlPatternRouter() - radixTree.addRoute('GET', '/a/simple/route', () => - Promise.resolve(), - ) - radixTree.addHook( - 'beforeHandler', - '/a/simple/route/*/tata', - () => Promise.resolve(), - 'GET', - ) + radixTree.addRoute('GET', '/a/simple/route', () => Promise.resolve()) + radixTree.addHook('beforeHandler', '/a/simple/route/*/tata', () => Promise.resolve(), 'GET') - expect( - radixTree.root.children[0].children[0].children[0] - .beforeHandlerHook, - ).toBeUndefined() - expect( - radixTree.root.children[0].children[0].children[0] - .afterHandlerHook, - ).toBeUndefined() + expect(radixTree.root.children[0].children[0].children[0].beforeHandlerHook).toBeUndefined() + expect(radixTree.root.children[0].children[0].children[0].afterHandlerHook).toBeUndefined() }) it('should not add a hook if the route not match', () => { const radixTree = new UrlPatternRouter() - radixTree.addRoute('GET', '/a/simple/route', () => - Promise.resolve(), - ) - radixTree.addHook( - 'beforeHandler', - '/a/simple/route2', - () => Promise.resolve(), - 'GET', - ) + radixTree.addRoute('GET', '/a/simple/route', () => Promise.resolve()) + radixTree.addHook('beforeHandler', '/a/simple/route2', () => Promise.resolve(), 'GET') - expect( - radixTree.root.children[0].children[0].children[0] - .beforeHandlerHook, - ).toBeUndefined() - expect( - radixTree.root.children[0].children[0].children[0] - .afterHandlerHook, - ).toBeUndefined() + expect(radixTree.root.children[0].children[0].children[0].beforeHandlerHook).toBeUndefined() + expect(radixTree.root.children[0].children[0].children[0].afterHandlerHook).toBeUndefined() }) it('should not add a hook if the hook is shorter than the route', () => { const radixTree = new UrlPatternRouter() - radixTree.addRoute('GET', '/a/simple/route', () => - Promise.resolve(), - ) - radixTree.addHook( - 'beforeHandler', - '/a/simple', - () => Promise.resolve(), - 'GET', - ) + radixTree.addRoute('GET', '/a/simple/route', () => Promise.resolve()) + radixTree.addHook('beforeHandler', '/a/simple', () => Promise.resolve(), 'GET') - expect( - radixTree.root.children[0].children[0].children[0] - .beforeHandlerHook, - ).toBeUndefined() - expect( - radixTree.root.children[0].children[0].children[0] - .afterHandlerHook, - ).toBeUndefined() + expect(radixTree.root.children[0].children[0].children[0].beforeHandlerHook).toBeUndefined() + expect(radixTree.root.children[0].children[0].children[0].afterHandlerHook).toBeUndefined() }) }) @@ -442,74 +199,46 @@ describe('UrlPatternRouter', () => { it('should throw an error if the route already exist with same http method', () => { const radixTree = new UrlPatternRouter() radixTree.addRoute('GET', '/route', () => Promise.resolve()) - expect(() => - radixTree.addRoute('GET', '/route', () => Promise.resolve()), - ).toThrow() + expect(() => radixTree.addRoute('GET', '/route', () => Promise.resolve())).toThrow() }) it('should add a route to the radix tree', () => { const radixTree = new UrlPatternRouter() - radixTree.addRoute('GET', '/a/simple/route', () => - Promise.resolve(), - ) + radixTree.addRoute('GET', '/a/simple/route', () => Promise.resolve()) expect(radixTree.root.children[0].name).toBe('a') expect(radixTree.root.children[0].method).toBeUndefined() expect(radixTree.root.children[0].handler).toBeUndefined() expect(radixTree.root.children[0].children[0].name).toBe('/simple') - expect( - radixTree.root.children[0].children[0].method, - ).toBeUndefined() - expect( - radixTree.root.children[0].children[0].handler, - ).toBeUndefined() - expect( - radixTree.root.children[0].children[0].children[0].name, - ).toBe('/route') - expect( - radixTree.root.children[0].children[0].children[0].method, - ).toBe('GET') - expect( - radixTree.root.children[0].children[0].children[0].handler, - ).toBeDefined() + expect(radixTree.root.children[0].children[0].method).toBeUndefined() + expect(radixTree.root.children[0].children[0].handler).toBeUndefined() + expect(radixTree.root.children[0].children[0].children[0].name).toBe('/route') + expect(radixTree.root.children[0].children[0].children[0].method).toBe('GET') + expect(radixTree.root.children[0].children[0].children[0].handler).toBeDefined() }) it('should add a route with HTTP method equal to ALL', () => { const radixTree = new UrlPatternRouter() - radixTree.addRoute('ALL', '/a/simple/route', () => - Promise.resolve(), - ) + radixTree.addRoute('ALL', '/a/simple/route', () => Promise.resolve()) expect(radixTree.root.children[0].name).toBe('a') expect(radixTree.root.children[0].method).toBeUndefined() expect(radixTree.root.children[0].handler).toBeUndefined() expect(radixTree.root.children[0].children[0].name).toBe('/simple') - expect( - radixTree.root.children[0].children[0].method, - ).toBeUndefined() - expect( - radixTree.root.children[0].children[0].handler, - ).toBeUndefined() - expect( - radixTree.root.children[0].children[0].children[0].name, - ).toBe('/route') - expect( - radixTree.root.children[0].children[0].children[0].method, - ).toBe('ALL') - expect( - radixTree.root.children[0].children[0].children[0].handler, - ).toBeDefined() + expect(radixTree.root.children[0].children[0].method).toBeUndefined() + expect(radixTree.root.children[0].children[0].handler).toBeUndefined() + expect(radixTree.root.children[0].children[0].children[0].name).toBe('/route') + expect(radixTree.root.children[0].children[0].children[0].method).toBe('ALL') + expect(radixTree.root.children[0].children[0].children[0].handler).toBeDefined() }) it('should add a route to the radix tree that is a part of another route', () => { const radixTree = new UrlPatternRouter() radixTree.addRoute('GET', '/a/simple', () => Promise.resolve()) - radixTree.addRoute('GET', '/a/simple/route', () => - Promise.resolve(), - ) + radixTree.addRoute('GET', '/a/simple/route', () => Promise.resolve()) expect(radixTree.root.children[0].name).toBe('a') expect(radixTree.root.children[0].method).toBeUndefined() @@ -517,120 +246,69 @@ describe('UrlPatternRouter', () => { expect(radixTree.root.children[0].children[0].name).toBe('/simple') expect(radixTree.root.children[0].children[0].method).toBe('GET') expect(radixTree.root.children[0].children[0].handler).toBeDefined() - expect( - radixTree.root.children[0].children[0].children[0].name, - ).toBe('/route') - expect( - radixTree.root.children[0].children[0].children[0].method, - ).toBe('GET') - expect( - radixTree.root.children[0].children[0].children[0].handler, - ).toBeDefined() + expect(radixTree.root.children[0].children[0].children[0].name).toBe('/route') + expect(radixTree.root.children[0].children[0].children[0].method).toBe('GET') + expect(radixTree.root.children[0].children[0].children[0].handler).toBeDefined() }) it('should add a route to the radix tree with no slash at the begining', () => { const radixTree = new UrlPatternRouter() - radixTree.addRoute('GET', 'a/simple/route/', () => - Promise.resolve(), - ) + radixTree.addRoute('GET', 'a/simple/route/', () => Promise.resolve()) expect(radixTree.root.children[0].name).toBe('a') expect(radixTree.root.children[0].method).toBeUndefined() expect(radixTree.root.children[0].handler).toBeUndefined() expect(radixTree.root.children[0].children[0].name).toBe('/simple') - expect( - radixTree.root.children[0].children[0].method, - ).toBeUndefined() - expect( - radixTree.root.children[0].children[0].handler, - ).toBeUndefined() - expect( - radixTree.root.children[0].children[0].children[0].name, - ).toBe('/route') - expect( - radixTree.root.children[0].children[0].children[0].method, - ).toBe('GET') - expect( - radixTree.root.children[0].children[0].children[0].handler, - ).toBeDefined() + expect(radixTree.root.children[0].children[0].method).toBeUndefined() + expect(radixTree.root.children[0].children[0].handler).toBeUndefined() + expect(radixTree.root.children[0].children[0].children[0].name).toBe('/route') + expect(radixTree.root.children[0].children[0].children[0].method).toBe('GET') + expect(radixTree.root.children[0].children[0].children[0].handler).toBeDefined() }) it('should add a route to the radix tree with slash at the end', () => { const radixTree = new UrlPatternRouter() - radixTree.addRoute('GET', '/a/simple/route/', () => - Promise.resolve(), - ) + radixTree.addRoute('GET', '/a/simple/route/', () => Promise.resolve()) expect(radixTree.root.children[0].name).toBe('a') expect(radixTree.root.children[0].method).toBeUndefined() expect(radixTree.root.children[0].handler).toBeUndefined() expect(radixTree.root.children[0].children[0].name).toBe('/simple') - expect( - radixTree.root.children[0].children[0].method, - ).toBeUndefined() - expect( - radixTree.root.children[0].children[0].handler, - ).toBeUndefined() - expect( - radixTree.root.children[0].children[0].children[0].name, - ).toBe('/route') - expect( - radixTree.root.children[0].children[0].children[0].method, - ).toBe('GET') - expect( - radixTree.root.children[0].children[0].children[0].handler, - ).toBeDefined() + expect(radixTree.root.children[0].children[0].method).toBeUndefined() + expect(radixTree.root.children[0].children[0].handler).toBeUndefined() + expect(radixTree.root.children[0].children[0].children[0].name).toBe('/route') + expect(radixTree.root.children[0].children[0].children[0].method).toBe('GET') + expect(radixTree.root.children[0].children[0].children[0].handler).toBeDefined() }) it('should add a route to the radix tree with param', () => { const radixTree = new UrlPatternRouter() - radixTree.addRoute('GET', '/a/simple/route/:id', () => - Promise.resolve(), - ) + radixTree.addRoute('GET', '/a/simple/route/:id', () => Promise.resolve()) expect(radixTree.root.children[0].name).toBe('a') expect(radixTree.root.children[0].method).toBeUndefined() expect(radixTree.root.children[0].handler).toBeUndefined() expect(radixTree.root.children[0].children[0].name).toBe('/simple') - expect( - radixTree.root.children[0].children[0].method, - ).toBeUndefined() - expect( - radixTree.root.children[0].children[0].handler, - ).toBeUndefined() - expect( - radixTree.root.children[0].children[0].children[0].name, - ).toBe('/route') - expect( - radixTree.root.children[0].children[0].children[0].handler, - ).toBeUndefined() - expect( - radixTree.root.children[0].children[0].children[0].children[0] - .name, - ).toBe('/:id') - expect( - radixTree.root.children[0].children[0].children[0].children[0] - .method, - ).toBe('GET') + expect(radixTree.root.children[0].children[0].method).toBeUndefined() + expect(radixTree.root.children[0].children[0].handler).toBeUndefined() + expect(radixTree.root.children[0].children[0].children[0].name).toBe('/route') + expect(radixTree.root.children[0].children[0].children[0].handler).toBeUndefined() + expect(radixTree.root.children[0].children[0].children[0].children[0].name).toBe('/:id') + expect(radixTree.root.children[0].children[0].children[0].children[0].method).toBe('GET') - expect( - radixTree.root.children[0].children[0].children[0].children[0] - .isParameterNode, - ).toBe(true) + expect(radixTree.root.children[0].children[0].children[0].children[0].isParameterNode).toBe( + true, + ) }) it('should add two routes to the radix tree', () => { const radixTree = new UrlPatternRouter() - radixTree.addRoute('GET', '/a/simple/route', () => - Promise.resolve(), - ) - radixTree.addRoute('POST', '/a/simple/route', () => - Promise.resolve(), - ) + radixTree.addRoute('GET', '/a/simple/route', () => Promise.resolve()) + radixTree.addRoute('POST', '/a/simple/route', () => Promise.resolve()) expect(radixTree.root.children.length).toBe(1) @@ -638,85 +316,45 @@ describe('UrlPatternRouter', () => { expect(radixTree.root.children[0].method).toBeUndefined() expect(radixTree.root.children[0].handler).toBeUndefined() expect(radixTree.root.children[0].children[0].name).toBe('/simple') - expect( - radixTree.root.children[0].children[0].method, - ).toBeUndefined() - expect( - radixTree.root.children[0].children[0].handler, - ).toBeUndefined() - expect( - radixTree.root.children[0].children[0].children[0].name, - ).toBe('/route') - expect( - radixTree.root.children[0].children[0].children[0].method, - ).toBe('GET') - expect( - radixTree.root.children[0].children[0].children[1].method, - ).toBe('POST') + expect(radixTree.root.children[0].children[0].method).toBeUndefined() + expect(radixTree.root.children[0].children[0].handler).toBeUndefined() + expect(radixTree.root.children[0].children[0].children[0].name).toBe('/route') + expect(radixTree.root.children[0].children[0].children[0].method).toBe('GET') + expect(radixTree.root.children[0].children[0].children[1].method).toBe('POST') }) it('should add two routes to the radix tree with param', () => { const radixTree = new UrlPatternRouter() - radixTree.addRoute('GET', '/a/simple/route/:id', () => - Promise.resolve(), - ) - radixTree.addRoute('GET', '/a/simple/route/:id/test2/', () => - Promise.resolve(), - ) + radixTree.addRoute('GET', '/a/simple/route/:id', () => Promise.resolve()) + radixTree.addRoute('GET', '/a/simple/route/:id/test2/', () => Promise.resolve()) expect(radixTree.root.children[0].name).toBe('a') expect(radixTree.root.children[0].method).toBeUndefined() expect(radixTree.root.children[0].handler).toBeUndefined() expect(radixTree.root.children[0].children[0].name).toBe('/simple') - expect( - radixTree.root.children[0].children[0].method, - ).toBeUndefined() - expect( - radixTree.root.children[0].children[0].handler, - ).toBeUndefined() - expect( - radixTree.root.children[0].children[0].children[0].name, - ).toBe('/route') - expect( - radixTree.root.children[0].children[0].children[0].handler, - ).toBeUndefined() - expect( - radixTree.root.children[0].children[0].children[0].children[0] - .name, - ).toBe('/:id') - expect( - radixTree.root.children[0].children[0].children[0].children[0] - .method, - ).toBe('GET') - expect( - radixTree.root.children[0].children[0].children[0].children[0] - .isParameterNode, - ).toBe(true) + expect(radixTree.root.children[0].children[0].method).toBeUndefined() + expect(radixTree.root.children[0].children[0].handler).toBeUndefined() + expect(radixTree.root.children[0].children[0].children[0].name).toBe('/route') + expect(radixTree.root.children[0].children[0].children[0].handler).toBeUndefined() + expect(radixTree.root.children[0].children[0].children[0].children[0].name).toBe('/:id') + expect(radixTree.root.children[0].children[0].children[0].children[0].method).toBe('GET') + expect(radixTree.root.children[0].children[0].children[0].children[0].isParameterNode).toBe( + true, + ) - expect( - radixTree.root.children[0].children[0].children[0].children[0] - .name, - ).toBe('/:id') - expect( - radixTree.root.children[0].children[0].children[0].children[0] - .children[0].name, - ).toBe('/test2') - expect( - radixTree.root.children[0].children[0].children[0].children[0] - .method, - ).toBe('GET') + expect(radixTree.root.children[0].children[0].children[0].children[0].name).toBe('/:id') + expect(radixTree.root.children[0].children[0].children[0].children[0].children[0].name).toBe( + '/test2', + ) + expect(radixTree.root.children[0].children[0].children[0].children[0].method).toBe('GET') }) it('should add two routes to the radix tree with diffent radix', () => { const radixTree = new UrlPatternRouter() - radixTree.addRoute('GET', '/a/simple/route', () => - Promise.resolve(), - ) - radixTree.addRoute('POST', '/a2/simple/route', () => - Promise.resolve(), - ) + radixTree.addRoute('GET', '/a/simple/route', () => Promise.resolve()) + radixTree.addRoute('POST', '/a2/simple/route', () => Promise.resolve()) expect(radixTree.root.children.length).toBe(2) @@ -724,35 +362,19 @@ describe('UrlPatternRouter', () => { expect(radixTree.root.children[0].method).toBeUndefined() expect(radixTree.root.children[0].handler).toBeUndefined() expect(radixTree.root.children[0].children[0].name).toBe('/simple') - expect( - radixTree.root.children[0].children[0].method, - ).toBeUndefined() - expect( - radixTree.root.children[0].children[0].handler, - ).toBeUndefined() - expect( - radixTree.root.children[0].children[0].children[0].name, - ).toBe('/route') - expect( - radixTree.root.children[0].children[0].children[0].method, - ).toBe('GET') + expect(radixTree.root.children[0].children[0].method).toBeUndefined() + expect(radixTree.root.children[0].children[0].handler).toBeUndefined() + expect(radixTree.root.children[0].children[0].children[0].name).toBe('/route') + expect(radixTree.root.children[0].children[0].children[0].method).toBe('GET') expect(radixTree.root.children[1].name).toBe('a2') expect(radixTree.root.children[1].method).toBeUndefined() expect(radixTree.root.children[1].handler).toBeUndefined() expect(radixTree.root.children[1].children[0].name).toBe('/simple') - expect( - radixTree.root.children[1].children[0].method, - ).toBeUndefined() - expect( - radixTree.root.children[1].children[0].handler, - ).toBeUndefined() - expect( - radixTree.root.children[1].children[0].children[0].name, - ).toBe('/route') - expect( - radixTree.root.children[1].children[0].children[0].method, - ).toBe('POST') + expect(radixTree.root.children[1].children[0].method).toBeUndefined() + expect(radixTree.root.children[1].children[0].handler).toBeUndefined() + expect(radixTree.root.children[1].children[0].children[0].name).toBe('/route') + expect(radixTree.root.children[1].children[0].children[0].method).toBe('POST') }) it('should add a route with a wildcard at the end', () => { @@ -765,31 +387,18 @@ describe('UrlPatternRouter', () => { expect(radixTree.root.children[0].handler).toBeUndefined() expect(radixTree.root.children[0].children[0].name).toBe('/simple') - expect( - radixTree.root.children[0].children[0].method, - ).toBeUndefined() - expect( - radixTree.root.children[0].children[0].handler, - ).toBeUndefined() + expect(radixTree.root.children[0].children[0].method).toBeUndefined() + expect(radixTree.root.children[0].children[0].handler).toBeUndefined() - expect( - radixTree.root.children[0].children[0].children[0].name, - ).toBe('/*') - expect( - radixTree.root.children[0].children[0].children[0] - .isWildcardNode, - ).toBeTrue() - expect( - radixTree.root.children[0].children[0].children[0].method, - ).toBe('GET') + expect(radixTree.root.children[0].children[0].children[0].name).toBe('/*') + expect(radixTree.root.children[0].children[0].children[0].isWildcardNode).toBeTrue() + expect(radixTree.root.children[0].children[0].children[0].method).toBe('GET') }) it('should add a route with a wildcard at the middle', () => { const radixTree = new UrlPatternRouter() - radixTree.addRoute('GET', '/a/simple/*/route', () => - Promise.resolve(), - ) + radixTree.addRoute('GET', '/a/simple/*/route', () => Promise.resolve()) expect(radixTree.root.children[0].name).toBe('a') expect(radixTree.root.children[0].method).toBeUndefined() @@ -797,39 +406,17 @@ describe('UrlPatternRouter', () => { expect(radixTree.root.children[0].isWildcardNode).toBeFalse() expect(radixTree.root.children[0].children[0].name).toBe('/simple') - expect( - radixTree.root.children[0].children[0].method, - ).toBeUndefined() - expect( - radixTree.root.children[0].children[0].handler, - ).toBeUndefined() + expect(radixTree.root.children[0].children[0].method).toBeUndefined() + expect(radixTree.root.children[0].children[0].handler).toBeUndefined() - expect( - radixTree.root.children[0].children[0].children[0].name, - ).toBe('/*') - expect( - radixTree.root.children[0].children[0].children[0] - .isWildcardNode, - ).toBeTrue() - expect( - radixTree.root.children[0].children[0].children[0].method, - ).toBeUndefined() - expect( - radixTree.root.children[0].children[0].children[0].handler, - ).toBeUndefined() + expect(radixTree.root.children[0].children[0].children[0].name).toBe('/*') + expect(radixTree.root.children[0].children[0].children[0].isWildcardNode).toBeTrue() + expect(radixTree.root.children[0].children[0].children[0].method).toBeUndefined() + expect(radixTree.root.children[0].children[0].children[0].handler).toBeUndefined() - expect( - radixTree.root.children[0].children[0].children[0].children[0] - .name, - ).toBe('/route') - expect( - radixTree.root.children[0].children[0].children[0].children[0] - .method, - ).toBe('GET') - expect( - radixTree.root.children[0].children[0].children[0].children[0] - .handler, - ).toBeDefined() + expect(radixTree.root.children[0].children[0].children[0].children[0].name).toBe('/route') + expect(radixTree.root.children[0].children[0].children[0].children[0].method).toBe('GET') + expect(radixTree.root.children[0].children[0].children[0].children[0].handler).toBeDefined() }) }) @@ -837,9 +424,7 @@ describe('UrlPatternRouter', () => { it('should find a route', () => { const radixTree = new UrlPatternRouter() - radixTree.addRoute('GET', '/a/simple/route-2', () => - Promise.resolve(), - ) + radixTree.addRoute('GET', '/a/simple/route-2', () => Promise.resolve()) radixTree.optimizeTree() @@ -853,9 +438,7 @@ describe('UrlPatternRouter', () => { it("should find a route with any HTTP method when the route's method is ALL", () => { const radixTree = new UrlPatternRouter() - radixTree.addRoute('ALL', '/a/simple/route-2', () => - Promise.resolve(), - ) + radixTree.addRoute('ALL', '/a/simple/route-2', () => Promise.resolve()) radixTree.optimizeTree() @@ -931,9 +514,7 @@ describe('UrlPatternRouter', () => { const radixTree = new UrlPatternRouter() radixTree.addRoute('GET', '/a/simple', () => Promise.resolve()) - radixTree.addRoute('GET', '/a/simple/route', () => - Promise.resolve(), - ) + radixTree.addRoute('GET', '/a/simple/route', () => Promise.resolve()) radixTree.optimizeTree() @@ -952,9 +533,7 @@ describe('UrlPatternRouter', () => { it('should find a route not begining with a slash', () => { const radixTree = new UrlPatternRouter() - radixTree.addRoute('GET', '/a/simple/route', () => - Promise.resolve(), - ) + radixTree.addRoute('GET', '/a/simple/route', () => Promise.resolve()) radixTree.optimizeTree() @@ -968,15 +547,9 @@ describe('UrlPatternRouter', () => { it('should find a route with same length on multiple children', () => { const radixTree = new UrlPatternRouter() - radixTree.addRoute('GET', '/a/simple/route1', () => - Promise.resolve(), - ) - radixTree.addRoute('GET', '/a/simple/route2', () => - Promise.resolve(), - ) - radixTree.addRoute('GET', '/a/simple/route3', () => - Promise.resolve(), - ) + radixTree.addRoute('GET', '/a/simple/route1', () => Promise.resolve()) + radixTree.addRoute('GET', '/a/simple/route2', () => Promise.resolve()) + radixTree.addRoute('GET', '/a/simple/route3', () => Promise.resolve()) radixTree.optimizeTree() @@ -987,34 +560,24 @@ describe('UrlPatternRouter', () => { expect(route?.handler).toBeDefined() }) - it.each([true, false])( - 'should not find a route that not exist', - (withOptimizeTree) => { - const radixTree = new UrlPatternRouter() + it.each([true, false])('should not find a route that not exist', (withOptimizeTree) => { + const radixTree = new UrlPatternRouter() - radixTree.addRoute('GET', '/a/simple/route', () => - Promise.resolve(), - ) + radixTree.addRoute('GET', '/a/simple/route', () => Promise.resolve()) - if (withOptimizeTree) radixTree.optimizeTree() + if (withOptimizeTree) radixTree.optimizeTree() - const route = radixTree.findRoute('GET', '/a/simple') - const route2 = radixTree.findRoute( - 'GET', - '/a/simple/route/bigger', - ) + const route = radixTree.findRoute('GET', '/a/simple') + const route2 = radixTree.findRoute('GET', '/a/simple/route/bigger') - expect(route).toBeNull() - expect(route2).toBeNull() - }, - ) + expect(route).toBeNull() + expect(route2).toBeNull() + }) it('should find a route ending by a slash', () => { const radixTree = new UrlPatternRouter() - radixTree.addRoute('GET', '/a/simple/route/', () => - Promise.resolve(), - ) + radixTree.addRoute('GET', '/a/simple/route/', () => Promise.resolve()) radixTree.optimizeTree() @@ -1035,9 +598,7 @@ describe('UrlPatternRouter', () => { it('should match a param route when the value is missing but a trailing slash is present', () => { const radixTree = new UrlPatternRouter() - radixTree.addRoute('GET', '/bucket/:filename', () => - Promise.resolve(), - ) + radixTree.addRoute('GET', '/bucket/:filename', () => Promise.resolve()) radixTree.optimizeTree() @@ -1051,12 +612,8 @@ describe('UrlPatternRouter', () => { it('should find a route by method', () => { const radixTree = new UrlPatternRouter() - radixTree.addRoute('GET', '/a/simple/route', () => - Promise.resolve(), - ) - radixTree.addRoute('POST', '/a/simple/route', () => - Promise.resolve(), - ) + radixTree.addRoute('GET', '/a/simple/route', () => Promise.resolve()) + radixTree.addRoute('POST', '/a/simple/route', () => Promise.resolve()) radixTree.optimizeTree() @@ -1068,66 +625,41 @@ describe('UrlPatternRouter', () => { expect(route?.handler).toBeDefined() }) - it.each([true, false])( - 'should find a complex route by method', - (withOptimizeTree) => { - const radixTree = new UrlPatternRouter() + it.each([true, false])('should find a complex route by method', (withOptimizeTree) => { + const radixTree = new UrlPatternRouter() - radixTree.addRoute( - 'GET', - '/there/is/a/very/long/and/complex/route', - () => Promise.resolve(), - ) - radixTree.addRoute( - 'GET', - '/there/is/a/very/long/and/complex/route2', - () => Promise.resolve(), - ) - radixTree.addRoute( - 'POST', - '/there/is/a/very/long/and/complex/addRoute', - () => Promise.resolve(), - ) + radixTree.addRoute('GET', '/there/is/a/very/long/and/complex/route', () => Promise.resolve()) + radixTree.addRoute('GET', '/there/is/a/very/long/and/complex/route2', () => Promise.resolve()) + radixTree.addRoute('POST', '/there/is/a/very/long/and/complex/addRoute', () => + Promise.resolve(), + ) - if (withOptimizeTree) radixTree.optimizeTree() + if (withOptimizeTree) radixTree.optimizeTree() - const route = radixTree.findRoute( - 'GET', - '/there/is/a/very/long/and/complex/route', - ) + const route = radixTree.findRoute('GET', '/there/is/a/very/long/and/complex/route') - const route2 = radixTree.findRoute( - 'GET', - '/there/is/a/very/long/and/complex/route2', - ) + const route2 = radixTree.findRoute('GET', '/there/is/a/very/long/and/complex/route2') - const route3 = radixTree.findRoute( - 'POST', - '/there/is/a/very/long/and/complex/addRoute', - ) + const route3 = radixTree.findRoute('POST', '/there/is/a/very/long/and/complex/addRoute') - const invalidRoute = radixTree.findRoute( - 'POST', - '/there/is/a/very/long/and/complex/route', - ) + const invalidRoute = radixTree.findRoute('POST', '/there/is/a/very/long/and/complex/route') - expect(route).not.toBeNull() - expect(route?.name).toBe('/route') - expect(route?.method).toBe('GET') - expect(route?.handler).toBeDefined() + expect(route).not.toBeNull() + expect(route?.name).toBe('/route') + expect(route?.method).toBe('GET') + expect(route?.handler).toBeDefined() - expect(route2).not.toBeNull() - expect(route2?.name).toBe('/route2') - expect(route2?.method).toBe('GET') - expect(route2?.handler).toBeDefined() + expect(route2).not.toBeNull() + expect(route2?.name).toBe('/route2') + expect(route2?.method).toBe('GET') + expect(route2?.handler).toBeDefined() - expect(route3).not.toBeNull() - expect(route3?.method).toBe('POST') - expect(route3?.handler).toBeDefined() + expect(route3).not.toBeNull() + expect(route3?.method).toBe('POST') + expect(route3?.handler).toBeDefined() - expect(invalidRoute).toBeNull() - }, - ) + expect(invalidRoute).toBeNull() + }) it.each([true, false])( 'should find a route with a parameter directly after the root', @@ -1233,10 +765,7 @@ describe('UrlPatternRouter', () => { radixTree.optimizeTree() const route = radixTree.findRoute('GET', '/a/simple/route') - const route2 = radixTree.findRoute( - 'GET', - '/*/another/big/long/route', - ) + const route2 = radixTree.findRoute('GET', '/*/another/big/long/route') expect(route).not.toBeNull() expect(route?.name).toBe('*') @@ -1250,9 +779,7 @@ describe('UrlPatternRouter', () => { it('should find a route ending by */', () => { const radixTree = new UrlPatternRouter() - radixTree.addRoute('GET', '/a/simple/route/*/', () => - Promise.resolve(), - ) + radixTree.addRoute('GET', '/a/simple/route/*/', () => Promise.resolve()) radixTree.optimizeTree() @@ -1266,16 +793,11 @@ describe('UrlPatternRouter', () => { it('should find a complex route with a wildcard', () => { const radixTree = new UrlPatternRouter() - radixTree.addRoute('GET', '/a/simple/route/*/', () => - Promise.resolve(), - ) + radixTree.addRoute('GET', '/a/simple/route/*/', () => Promise.resolve()) radixTree.optimizeTree() - const route = radixTree.findRoute( - 'GET', - '/a/simple/route/*/*/*/route', - ) + const route = radixTree.findRoute('GET', '/a/simple/route/*/*/*/route') expect(route).not.toBeNull() expect(route?.name).toBe('/*') @@ -1285,9 +807,7 @@ describe('UrlPatternRouter', () => { it('should find a route with many parameters', () => { const radixTree = new UrlPatternRouter() - radixTree.addRoute('GET', '/a/:id/:name/:age', () => - Promise.resolve(), - ) + radixTree.addRoute('GET', '/a/:id/:name/:age', () => Promise.resolve()) radixTree.optimizeTree() @@ -1351,18 +871,13 @@ describe('UrlPatternRouter', () => { it('should find a route with multiple wildcards', () => { const radixTree = new UrlPatternRouter() - radixTree.addRoute('GET', '/a/simple/*/*/*/', () => - Promise.resolve(), - ) + radixTree.addRoute('GET', '/a/simple/*/*/*/', () => Promise.resolve()) radixTree.optimizeTree() const route = radixTree.findRoute('GET', '/a/simple/route') const route2 = radixTree.findRoute('GET', '/a/simple/route/route') - const route3 = radixTree.findRoute( - 'GET', - '/a/simple/route/route/again/another/route', - ) + const route3 = radixTree.findRoute('GET', '/a/simple/route/route/again/another/route') expect(route).not.toBeNull() expect(route?.method).toBe('GET') @@ -1380,9 +895,7 @@ describe('UrlPatternRouter', () => { it('should find a route with a wildcard at the middle', () => { const radixTree = new UrlPatternRouter() - radixTree.addRoute('GET', '/a/simple/*/route', () => - Promise.resolve(), - ) + radixTree.addRoute('GET', '/a/simple/*/route', () => Promise.resolve()) radixTree.optimizeTree() @@ -1404,9 +917,7 @@ describe('UrlPatternRouter', () => { it('should not find a non existing route', () => { const radixTree = new UrlPatternRouter() - radixTree.addRoute('GET', '/a/simple/route', () => - Promise.resolve(), - ) + radixTree.addRoute('GET', '/a/simple/route', () => Promise.resolve()) radixTree.optimizeTree() @@ -1431,9 +942,7 @@ describe('UrlPatternRouter', () => { it('should extract the parameter when the parameter is at the begin of the route', () => { const radixTree = new UrlPatternRouter() - radixTree.addRoute('GET', '/:name/:id/route', () => - Promise.resolve(), - ) + radixTree.addRoute('GET', '/:name/:id/route', () => Promise.resolve()) radixTree.optimizeTree() @@ -1446,9 +955,7 @@ describe('UrlPatternRouter', () => { it('should extract the parameter when the parameter is at the end of the route', () => { const radixTree = new UrlPatternRouter() - radixTree.addRoute('GET', '/:name/:id/:route', () => - Promise.resolve(), - ) + radixTree.addRoute('GET', '/:name/:id/:route', () => Promise.resolve()) radixTree.optimizeTree() @@ -1465,9 +972,7 @@ describe('UrlPatternRouter', () => { it('should extract the parameter when the parameter is at the end of the route with a slash', () => { const radixTree = new UrlPatternRouter() - radixTree.addRoute('GET', '/name/id/:route', () => - Promise.resolve(), - ) + radixTree.addRoute('GET', '/name/id/:route', () => Promise.resolve()) radixTree.optimizeTree() @@ -1497,12 +1002,8 @@ describe('UrlPatternRouter', () => { it('should optimize a tree by merging all the node with only one child', () => { const radixTree = new UrlPatternRouter() - radixTree.addRoute('GET', '/a/simple/route', () => - Promise.resolve(), - ) - radixTree.addRoute('POST', '/a/simple/route', () => - Promise.resolve(), - ) + radixTree.addRoute('GET', '/a/simple/route', () => Promise.resolve()) + radixTree.addRoute('POST', '/a/simple/route', () => Promise.resolve()) radixTree.optimizeTree() @@ -1527,9 +1028,7 @@ describe('UrlPatternRouter', () => { const radixTree = new UrlPatternRouter() radixTree.addRoute('GET', '/a/simple', () => Promise.resolve()) - radixTree.addRoute('GET', '/a/simple/route', () => - Promise.resolve(), - ) + radixTree.addRoute('GET', '/a/simple/route', () => Promise.resolve()) radixTree.optimizeTree() @@ -1544,9 +1043,7 @@ describe('UrlPatternRouter', () => { it('should merge a tree when there is only one route', () => { const radixTree = new UrlPatternRouter() - radixTree.addRoute('GET', '/a/simple/route', () => - Promise.resolve(), - ) + radixTree.addRoute('GET', '/a/simple/route', () => Promise.resolve()) radixTree.optimizeTree() @@ -1575,25 +1072,13 @@ describe('UrlPatternRouter', () => { expect(radixTree.root.children[0].handler).toBeUndefined() expect(radixTree.root.children[0].children[0].name).toBe('/:id') - expect( - radixTree.root.children[0].children[0].isParameterNode, - ).toBeTrue() - expect( - radixTree.root.children[0].children[0].method, - ).toBeUndefined() - expect( - radixTree.root.children[0].children[0].handler, - ).toBeUndefined() + expect(radixTree.root.children[0].children[0].isParameterNode).toBeTrue() + expect(radixTree.root.children[0].children[0].method).toBeUndefined() + expect(radixTree.root.children[0].children[0].handler).toBeUndefined() - expect( - radixTree.root.children[0].children[0].children[0].name, - ).toBe('/route') - expect( - radixTree.root.children[0].children[0].children[0].method, - ).toBe('GET') - expect( - radixTree.root.children[0].children[0].children[0].handler, - ).toBeDefined() + expect(radixTree.root.children[0].children[0].children[0].name).toBe('/route') + expect(radixTree.root.children[0].children[0].children[0].method).toBe('GET') + expect(radixTree.root.children[0].children[0].children[0].handler).toBeDefined() }) it('should correctly merge a tree with a wildcard', () => { @@ -1612,9 +1097,7 @@ describe('UrlPatternRouter', () => { expect(radixTree.root.children[0].handler).toBeUndefined() expect(radixTree.root.children[0].children[0].name).toBe('/*') - expect( - radixTree.root.children[0].children[0].isWildcardNode, - ).toBeTrue() + expect(radixTree.root.children[0].children[0].isWildcardNode).toBeTrue() expect(radixTree.root.children[0].children[0].method).toBe('GET') expect(radixTree.root.children[0].children[0].handler).toBeDefined() }) @@ -1622,15 +1105,9 @@ describe('UrlPatternRouter', () => { it('should merge a tree with complex structure', () => { const radixTree = new UrlPatternRouter() - radixTree.addRoute('GET', '/there/is/a/complex/route/next2', () => - Promise.resolve(), - ) - radixTree.addRoute('POST', '/there/is/a2/complex/route/next2', () => - Promise.resolve(), - ) - radixTree.addRoute('POST', '/there/is/a2/complex/route/next3', () => - Promise.resolve(), - ) + radixTree.addRoute('GET', '/there/is/a/complex/route/next2', () => Promise.resolve()) + radixTree.addRoute('POST', '/there/is/a2/complex/route/next2', () => Promise.resolve()) + radixTree.addRoute('POST', '/there/is/a2/complex/route/next3', () => Promise.resolve()) radixTree.optimizeTree() @@ -1642,39 +1119,19 @@ describe('UrlPatternRouter', () => { expect(radixTree.root.children[0].method).toBeUndefined() expect(radixTree.root.children[0].handler).toBeUndefined() - expect(radixTree.root.children[0].children[0].name).toBe( - '/a/complex/route/next2', - ) + expect(radixTree.root.children[0].children[0].name).toBe('/a/complex/route/next2') expect(radixTree.root.children[0].children[0].method).toBe('GET') expect(radixTree.root.children[0].children[0].handler).toBeDefined() - expect(radixTree.root.children[0].children[1].name).toBe( - '/a2/complex/route', - ) - expect( - radixTree.root.children[0].children[1].method, - ).toBeUndefined() - expect( - radixTree.root.children[0].children[1].handler, - ).toBeUndefined() - expect( - radixTree.root.children[0].children[1].children[0].name, - ).toBe('/next2') - expect( - radixTree.root.children[0].children[1].children[0].method, - ).toBe('POST') - expect( - radixTree.root.children[0].children[1].children[0].handler, - ).toBeDefined() - expect( - radixTree.root.children[0].children[1].children[1].name, - ).toBe('/next3') - expect( - radixTree.root.children[0].children[1].children[1].method, - ).toBe('POST') - expect( - radixTree.root.children[0].children[1].children[1].handler, - ).toBeDefined() + expect(radixTree.root.children[0].children[1].name).toBe('/a2/complex/route') + expect(radixTree.root.children[0].children[1].method).toBeUndefined() + expect(radixTree.root.children[0].children[1].handler).toBeUndefined() + expect(radixTree.root.children[0].children[1].children[0].name).toBe('/next2') + expect(radixTree.root.children[0].children[1].children[0].method).toBe('POST') + expect(radixTree.root.children[0].children[1].children[0].handler).toBeDefined() + expect(radixTree.root.children[0].children[1].children[1].name).toBe('/next3') + expect(radixTree.root.children[0].children[1].children[1].method).toBe('POST') + expect(radixTree.root.children[0].children[1].children[1].handler).toBeDefined() }) }) }) diff --git a/packages/wobe/src/router/UrlPatternRouter.ts b/packages/wobe/src/router/UrlPatternRouter.ts index 936adcc..760f6ac 100644 --- a/packages/wobe/src/router/UrlPatternRouter.ts +++ b/packages/wobe/src/router/UrlPatternRouter.ts @@ -19,9 +19,7 @@ type RouteEntry = { const createURLPattern = (pathname: string): URLPatternLike | null => { const URLPatternConstructor = (globalThis as any).URLPattern as - | (new (init: { - pathname: string - }) => URLPatternLike) + | (new (init: { pathname: string }) => URLPatternLike) | undefined if (!URLPatternConstructor) return null @@ -38,14 +36,8 @@ export class UrlPatternRouter { typeof (globalThis as any).URLPattern === 'object' private routes: Array = [] - private addRouteEntry( - node: Node, - method: HttpMethod, - normalizedPath: string, - ) { - const pattern = this.hasURLPattern - ? createURLPattern(normalizedPath) - : null + private addRouteEntry(node: Node, method: HttpMethod, normalizedPath: string) { + const pattern = this.hasURLPattern ? createURLPattern(normalizedPath) : null this.routePatterns.set(node, pattern) @@ -79,15 +71,10 @@ export class UrlPatternRouter { let foundNode = currentNode.children.find( (node) => - node.name === (i === 0 ? '' : '/') + pathPart && - (node.method === method || !node.method), + node.name === (i === 0 ? '' : '/') + pathPart && (node.method === method || !node.method), ) - if ( - foundNode && - foundNode.method === method && - i === pathParts.length - 1 - ) + if (foundNode && foundNode.method === method && i === pathParts.length - 1) throw new Error(`Route ${method} ${path} already exists`) if (!foundNode) { @@ -140,17 +127,8 @@ export class UrlPatternRouter { } } - addHook( - hook: Hook, - path: string, - handler: WobeHandler, - method: HttpMethod, - node?: Node, - ) { - if (this.isOptimized) - throw new Error( - 'Cannot add hooks after the tree has been optimized', - ) + addHook(hook: Hook, path: string, handler: WobeHandler, method: HttpMethod, node?: Node) { + if (this.isOptimized) throw new Error('Cannot add hooks after the tree has been optimized') let currentNode = node || this.root @@ -161,10 +139,7 @@ export class UrlPatternRouter { while (stack.length > 0) { const child = stack.pop() as Node - if ( - child.handler && - (method === child.method || method === 'ALL') - ) + if (child.handler && (method === child.method || method === 'ALL')) this._addHookToNode(child, hook, handler) if (child.children.length > 0) stack.push(...child.children) @@ -192,8 +167,7 @@ export class UrlPatternRouter { const foundNode = currentNode.children.find( (node) => - node.name === - (currentNode.name === '/' ? '' : '/') + pathPart && + node.name === (currentNode.name === '/' ? '' : '/') + pathPart && ((node.method && node.method === method) || !node.method), ) @@ -230,20 +204,12 @@ export class UrlPatternRouter { matched = true } else if (entry.normalizedPath.endsWith('/*')) { const prefix = entry.normalizedPath.split('/*')[0] - if ( - localPath === prefix || - localPath.startsWith(prefix + '/') - ) - matched = true + if (localPath === prefix || localPath.startsWith(prefix + '/')) matched = true } else { const paramIndex = entry.normalizedPath.indexOf('/:') if (paramIndex !== -1) { const prefix = entry.normalizedPath.slice(0, paramIndex) - if ( - localPath === prefix || - (hadTrailingSlash && localPath === prefix) - ) - matched = true + if (localPath === prefix || (hadTrailingSlash && localPath === prefix)) matched = true } } @@ -262,8 +228,7 @@ export class UrlPatternRouter { if (match?.pathname?.groups) { const groups = match.pathname.groups - if (Object.keys(groups).length > 0) - bestMatch.node.params = groups as Record + if (Object.keys(groups).length > 0) bestMatch.node.params = groups as Record else bestMatch.node.params = undefined } else { bestMatch.node.params = undefined @@ -276,19 +241,14 @@ export class UrlPatternRouter { let nextIndexToEnd = 0 let params: Record | undefined - const isNodeMatch = ( - node: Node, - indexToBegin: number, - indexToEnd: number, - ): Node | null => { + const isNodeMatch = (node: Node, indexToBegin: number, indexToEnd: number): Node | null => { const nextIndexToBegin = indexToBegin + (indexToEnd - indexToBegin) for (let i = 0; i < node.children.length; i++) { const child = node.children[i] const childName = child.name - const isChildWildcardOrParameterNode = - child.isWildcardNode || child.isParameterNode + const isChildWildcardOrParameterNode = child.isWildcardNode || child.isParameterNode nextIndexToEnd = localPath.indexOf( '/', @@ -299,8 +259,7 @@ export class UrlPatternRouter { if (nextIndexToEnd === -1) nextIndexToEnd = pathLength - if (indexToEnd === nextIndexToEnd && !child.isWildcardNode) - continue + if (indexToEnd === nextIndexToEnd && !child.isWildcardNode) continue if ( !isChildWildcardOrParameterNode && @@ -313,11 +272,10 @@ export class UrlPatternRouter { const indexToAddIfFirstNode = indexToBegin === 0 ? 0 : 1 - params[childName.slice(1 + indexToAddIfFirstNode)] = - localPath.slice( - nextIndexToBegin + indexToAddIfFirstNode, - nextIndexToEnd, - ) + params[childName.slice(1 + indexToAddIfFirstNode)] = localPath.slice( + nextIndexToBegin + indexToAddIfFirstNode, + nextIndexToEnd, + ) } if ( @@ -333,19 +291,12 @@ export class UrlPatternRouter { ) { if (isChildWildcardOrParameterNode) return child - const pathToCompute = localPath.slice( - nextIndexToBegin, - nextIndexToEnd, - ) + const pathToCompute = localPath.slice(nextIndexToBegin, nextIndexToEnd) if (pathToCompute === childName) return child } - const foundNode = isNodeMatch( - child, - nextIndexToBegin, - nextIndexToEnd, - ) + const foundNode = isNodeMatch(child, nextIndexToBegin, nextIndexToEnd) if (foundNode) return foundNode } @@ -399,11 +350,7 @@ export class UrlPatternRouter { const rebuild = (node: Node) => { if (node.handler && (node as any).fullPath) { - this.addRouteEntry( - node, - node.method as HttpMethod, - (node as any).fullPath, - ) + this.addRouteEntry(node, node.method as HttpMethod, (node as any).fullPath) } node.children.forEach(rebuild) diff --git a/packages/wobe/src/utils.test.ts b/packages/wobe/src/utils.test.ts index dc26d78..e90cd84 100644 --- a/packages/wobe/src/utils.test.ts +++ b/packages/wobe/src/utils.test.ts @@ -29,8 +29,7 @@ describe('Utils', () => { it('should extract single search param from a route', () => { const route = 'http://localhost:3000/test?name=John' - const { pathName, searchParams } = - extractPathnameAndSearchParams(route) + const { pathName, searchParams } = extractPathnameAndSearchParams(route) expect(pathName).toBe('/test') expect(searchParams).toEqual({ name: 'John' }) @@ -38,8 +37,7 @@ describe('Utils', () => { it('should extract search params from a route', () => { const route = 'http://localhost:3000/test?name=John&age=30' - const { pathName, searchParams } = - extractPathnameAndSearchParams(route) + const { pathName, searchParams } = extractPathnameAndSearchParams(route) expect(pathName).toBe('/test') expect(searchParams).toEqual({ name: 'John', age: '30' }) @@ -48,8 +46,7 @@ describe('Utils', () => { it('should extract search params from a complex route', () => { const route = 'http://localhost:3000/test?name=John&age=30&firstName=Pierre-Jacques&Country=Pays basque' - const { pathName, searchParams } = - extractPathnameAndSearchParams(route) + const { pathName, searchParams } = extractPathnameAndSearchParams(route) expect(pathName).toBe('/test') expect(searchParams).toEqual({ diff --git a/packages/wobe/src/utils.ts b/packages/wobe/src/utils.ts index 288e9b3..c8c2882 100644 --- a/packages/wobe/src/utils.ts +++ b/packages/wobe/src/utils.ts @@ -5,10 +5,7 @@ export const extractPathnameAndSearchParams = (url: string) => { const isQueryContainsSearchParams = queryIndex !== -1 - const path = url.slice( - url.indexOf('/', 8), - !isQueryContainsSearchParams ? urlLength : queryIndex, - ) + const path = url.slice(url.indexOf('/', 8), !isQueryContainsSearchParams ? urlLength : queryIndex) if (isQueryContainsSearchParams) { const searchParams: Record = {} @@ -24,11 +21,10 @@ export const extractPathnameAndSearchParams = (url: string) => { } if (char === '&' || i === urlLength - 1) { - searchParams[url.slice(indexOfLastParam, indexOfLastEqual)] = - url.slice( - indexOfLastEqual + 1, - i === urlLength - 1 ? i + 1 : i, - ) + searchParams[url.slice(indexOfLastParam, indexOfLastEqual)] = url.slice( + indexOfLastEqual + 1, + i === urlLength - 1 ? i + 1 : i, + ) indexOfLastParam = i + 1 } } @@ -66,11 +62,9 @@ export const mimeTypes: MimeType = { '.mpeg': 'video/mpeg', '.zip': 'application/zip', '.doc': 'application/msword', - '.docx': - 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', + '.docx': 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', '.xls': 'application/vnd.ms-excel', - '.xlsx': - 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', + '.xlsx': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', '.bmp': 'image/bmp', '.ico': 'image/x-icon', '.tiff': 'image/tiff', @@ -98,8 +92,7 @@ export const mimeTypes: MimeType = { '.sh': 'application/x-sh', '.php': 'application/x-httpd-php', '.ppt': 'application/vnd.ms-powerpoint', - '.pptx': - 'application/vnd.openxmlformats-officedocument.presentationml.presentation', + '.pptx': 'application/vnd.openxmlformats-officedocument.presentationml.presentation', '.odt': 'application/vnd.oasis.opendocument.text', '.ods': 'application/vnd.oasis.opendocument.spreadsheet', '.odp': 'application/vnd.oasis.opendocument.presentation',