feat: migrate to Hono framework
- Add Hono as HTTP framework for Cloudflare Workers - Create app.ts with declarative routing and middleware - Add hono-adapters.ts for auth, rate limit, request ID middleware - Refactor handlers to use Hono Context signature - Maintain all existing business logic unchanged Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
540
package-lock.json
generated
540
package-lock.json
generated
@@ -7,8 +7,12 @@
|
||||
"": {
|
||||
"name": "cloud-instances-api",
|
||||
"version": "1.0.0",
|
||||
"dependencies": {
|
||||
"hono": "^4.11.7"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@cloudflare/workers-types": "^4.20241205.0",
|
||||
"tsx": "^4.7.0",
|
||||
"typescript": "^5.7.2",
|
||||
"vitest": "^2.1.8",
|
||||
"wrangler": "^4.59.3"
|
||||
@@ -1832,6 +1836,28 @@
|
||||
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/get-tsconfig": {
|
||||
"version": "4.13.0",
|
||||
"resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.13.0.tgz",
|
||||
"integrity": "sha512-1VKTZJCwBrvbd+Wn3AOgQP/2Av+TfTCOlE4AcRJE72W1ksZXbAx8PPBR9RzgTeSPzlPMHrbANMH3LbltH73wxQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"resolve-pkg-maps": "^1.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/privatenumber/get-tsconfig?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/hono": {
|
||||
"version": "4.11.7",
|
||||
"resolved": "https://registry.npmjs.org/hono/-/hono-4.11.7.tgz",
|
||||
"integrity": "sha512-l7qMiNee7t82bH3SeyUCt9UF15EVmaBvsppY2zQtrbIhl/yzBTny+YUxsVjSjQ6gaqaeVtZmGocom8TzBlA4Yw==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=16.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/kleur": {
|
||||
"version": "4.1.5",
|
||||
"resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz",
|
||||
@@ -1967,6 +1993,16 @@
|
||||
"node": "^10 || ^12 || >=14"
|
||||
}
|
||||
},
|
||||
"node_modules/resolve-pkg-maps": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz",
|
||||
"integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/rollup": {
|
||||
"version": "4.55.3",
|
||||
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.55.3.tgz",
|
||||
@@ -2166,6 +2202,510 @@
|
||||
"license": "0BSD",
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/tsx": {
|
||||
"version": "4.21.0",
|
||||
"resolved": "https://registry.npmjs.org/tsx/-/tsx-4.21.0.tgz",
|
||||
"integrity": "sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"esbuild": "~0.27.0",
|
||||
"get-tsconfig": "^4.7.5"
|
||||
},
|
||||
"bin": {
|
||||
"tsx": "dist/cli.mjs"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18.0.0"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"fsevents": "~2.3.3"
|
||||
}
|
||||
},
|
||||
"node_modules/tsx/node_modules/@esbuild/aix-ppc64": {
|
||||
"version": "0.27.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.2.tgz",
|
||||
"integrity": "sha512-GZMB+a0mOMZs4MpDbj8RJp4cw+w1WV5NYD6xzgvzUJ5Ek2jerwfO2eADyI6ExDSUED+1X8aMbegahsJi+8mgpw==",
|
||||
"cpu": [
|
||||
"ppc64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"aix"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/tsx/node_modules/@esbuild/android-arm": {
|
||||
"version": "0.27.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.2.tgz",
|
||||
"integrity": "sha512-DVNI8jlPa7Ujbr1yjU2PfUSRtAUZPG9I1RwW4F4xFB1Imiu2on0ADiI/c3td+KmDtVKNbi+nffGDQMfcIMkwIA==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"android"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/tsx/node_modules/@esbuild/android-arm64": {
|
||||
"version": "0.27.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.2.tgz",
|
||||
"integrity": "sha512-pvz8ZZ7ot/RBphf8fv60ljmaoydPU12VuXHImtAs0XhLLw+EXBi2BLe3OYSBslR4rryHvweW5gmkKFwTiFy6KA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"android"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/tsx/node_modules/@esbuild/android-x64": {
|
||||
"version": "0.27.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.2.tgz",
|
||||
"integrity": "sha512-z8Ank4Byh4TJJOh4wpz8g2vDy75zFL0TlZlkUkEwYXuPSgX8yzep596n6mT7905kA9uHZsf/o2OJZubl2l3M7A==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"android"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/tsx/node_modules/@esbuild/darwin-arm64": {
|
||||
"version": "0.27.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.2.tgz",
|
||||
"integrity": "sha512-davCD2Zc80nzDVRwXTcQP/28fiJbcOwvdolL0sOiOsbwBa72kegmVU0Wrh1MYrbuCL98Omp5dVhQFWRKR2ZAlg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/tsx/node_modules/@esbuild/darwin-x64": {
|
||||
"version": "0.27.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.2.tgz",
|
||||
"integrity": "sha512-ZxtijOmlQCBWGwbVmwOF/UCzuGIbUkqB1faQRf5akQmxRJ1ujusWsb3CVfk/9iZKr2L5SMU5wPBi1UWbvL+VQA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/tsx/node_modules/@esbuild/freebsd-arm64": {
|
||||
"version": "0.27.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.2.tgz",
|
||||
"integrity": "sha512-lS/9CN+rgqQ9czogxlMcBMGd+l8Q3Nj1MFQwBZJyoEKI50XGxwuzznYdwcav6lpOGv5BqaZXqvBSiB/kJ5op+g==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"freebsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/tsx/node_modules/@esbuild/freebsd-x64": {
|
||||
"version": "0.27.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.2.tgz",
|
||||
"integrity": "sha512-tAfqtNYb4YgPnJlEFu4c212HYjQWSO/w/h/lQaBK7RbwGIkBOuNKQI9tqWzx7Wtp7bTPaGC6MJvWI608P3wXYA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"freebsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/tsx/node_modules/@esbuild/linux-arm": {
|
||||
"version": "0.27.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.2.tgz",
|
||||
"integrity": "sha512-vWfq4GaIMP9AIe4yj1ZUW18RDhx6EPQKjwe7n8BbIecFtCQG4CfHGaHuh7fdfq+y3LIA2vGS/o9ZBGVxIDi9hw==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/tsx/node_modules/@esbuild/linux-arm64": {
|
||||
"version": "0.27.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.2.tgz",
|
||||
"integrity": "sha512-hYxN8pr66NsCCiRFkHUAsxylNOcAQaxSSkHMMjcpx0si13t1LHFphxJZUiGwojB1a/Hd5OiPIqDdXONia6bhTw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/tsx/node_modules/@esbuild/linux-ia32": {
|
||||
"version": "0.27.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.2.tgz",
|
||||
"integrity": "sha512-MJt5BRRSScPDwG2hLelYhAAKh9imjHK5+NE/tvnRLbIqUWa+0E9N4WNMjmp/kXXPHZGqPLxggwVhz7QP8CTR8w==",
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/tsx/node_modules/@esbuild/linux-loong64": {
|
||||
"version": "0.27.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.2.tgz",
|
||||
"integrity": "sha512-lugyF1atnAT463aO6KPshVCJK5NgRnU4yb3FUumyVz+cGvZbontBgzeGFO1nF+dPueHD367a2ZXe1NtUkAjOtg==",
|
||||
"cpu": [
|
||||
"loong64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/tsx/node_modules/@esbuild/linux-mips64el": {
|
||||
"version": "0.27.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.2.tgz",
|
||||
"integrity": "sha512-nlP2I6ArEBewvJ2gjrrkESEZkB5mIoaTswuqNFRv/WYd+ATtUpe9Y09RnJvgvdag7he0OWgEZWhviS1OTOKixw==",
|
||||
"cpu": [
|
||||
"mips64el"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/tsx/node_modules/@esbuild/linux-ppc64": {
|
||||
"version": "0.27.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.2.tgz",
|
||||
"integrity": "sha512-C92gnpey7tUQONqg1n6dKVbx3vphKtTHJaNG2Ok9lGwbZil6DrfyecMsp9CrmXGQJmZ7iiVXvvZH6Ml5hL6XdQ==",
|
||||
"cpu": [
|
||||
"ppc64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/tsx/node_modules/@esbuild/linux-riscv64": {
|
||||
"version": "0.27.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.2.tgz",
|
||||
"integrity": "sha512-B5BOmojNtUyN8AXlK0QJyvjEZkWwy/FKvakkTDCziX95AowLZKR6aCDhG7LeF7uMCXEJqwa8Bejz5LTPYm8AvA==",
|
||||
"cpu": [
|
||||
"riscv64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/tsx/node_modules/@esbuild/linux-s390x": {
|
||||
"version": "0.27.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.2.tgz",
|
||||
"integrity": "sha512-p4bm9+wsPwup5Z8f4EpfN63qNagQ47Ua2znaqGH6bqLlmJ4bx97Y9JdqxgGZ6Y8xVTixUnEkoKSHcpRlDnNr5w==",
|
||||
"cpu": [
|
||||
"s390x"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/tsx/node_modules/@esbuild/linux-x64": {
|
||||
"version": "0.27.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.2.tgz",
|
||||
"integrity": "sha512-uwp2Tip5aPmH+NRUwTcfLb+W32WXjpFejTIOWZFw/v7/KnpCDKG66u4DLcurQpiYTiYwQ9B7KOeMJvLCu/OvbA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/tsx/node_modules/@esbuild/netbsd-arm64": {
|
||||
"version": "0.27.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.2.tgz",
|
||||
"integrity": "sha512-Kj6DiBlwXrPsCRDeRvGAUb/LNrBASrfqAIok+xB0LxK8CHqxZ037viF13ugfsIpePH93mX7xfJp97cyDuTZ3cw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"netbsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/tsx/node_modules/@esbuild/netbsd-x64": {
|
||||
"version": "0.27.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.2.tgz",
|
||||
"integrity": "sha512-HwGDZ0VLVBY3Y+Nw0JexZy9o/nUAWq9MlV7cahpaXKW6TOzfVno3y3/M8Ga8u8Yr7GldLOov27xiCnqRZf0tCA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"netbsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/tsx/node_modules/@esbuild/openbsd-arm64": {
|
||||
"version": "0.27.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.2.tgz",
|
||||
"integrity": "sha512-DNIHH2BPQ5551A7oSHD0CKbwIA/Ox7+78/AWkbS5QoRzaqlev2uFayfSxq68EkonB+IKjiuxBFoV8ESJy8bOHA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"openbsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/tsx/node_modules/@esbuild/openbsd-x64": {
|
||||
"version": "0.27.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.2.tgz",
|
||||
"integrity": "sha512-/it7w9Nb7+0KFIzjalNJVR5bOzA9Vay+yIPLVHfIQYG/j+j9VTH84aNB8ExGKPU4AzfaEvN9/V4HV+F+vo8OEg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"openbsd"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/tsx/node_modules/@esbuild/openharmony-arm64": {
|
||||
"version": "0.27.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.2.tgz",
|
||||
"integrity": "sha512-LRBbCmiU51IXfeXk59csuX/aSaToeG7w48nMwA6049Y4J4+VbWALAuXcs+qcD04rHDuSCSRKdmY63sruDS5qag==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"openharmony"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/tsx/node_modules/@esbuild/sunos-x64": {
|
||||
"version": "0.27.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.2.tgz",
|
||||
"integrity": "sha512-kMtx1yqJHTmqaqHPAzKCAkDaKsffmXkPHThSfRwZGyuqyIeBvf08KSsYXl+abf5HDAPMJIPnbBfXvP2ZC2TfHg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"sunos"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/tsx/node_modules/@esbuild/win32-arm64": {
|
||||
"version": "0.27.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.2.tgz",
|
||||
"integrity": "sha512-Yaf78O/B3Kkh+nKABUF++bvJv5Ijoy9AN1ww904rOXZFLWVc5OLOfL56W+C8F9xn5JQZa3UX6m+IktJnIb1Jjg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/tsx/node_modules/@esbuild/win32-ia32": {
|
||||
"version": "0.27.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.2.tgz",
|
||||
"integrity": "sha512-Iuws0kxo4yusk7sw70Xa2E2imZU5HoixzxfGCdxwBdhiDgt9vX9VUCBhqcwY7/uh//78A1hMkkROMJq9l27oLQ==",
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/tsx/node_modules/@esbuild/win32-x64": {
|
||||
"version": "0.27.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.2.tgz",
|
||||
"integrity": "sha512-sRdU18mcKf7F+YgheI/zGf5alZatMUTKj/jNS6l744f9u3WFu4v7twcUI9vu4mknF4Y9aDlblIie0IM+5xxaqQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/tsx/node_modules/esbuild": {
|
||||
"version": "0.27.2",
|
||||
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.2.tgz",
|
||||
"integrity": "sha512-HyNQImnsOC7X9PMNaCIeAm4ISCQXs5a5YasTXVliKv4uuBo1dKrG0A+uQS8M5eXjVMnLg3WgXaKvprHlFJQffw==",
|
||||
"dev": true,
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"esbuild": "bin/esbuild"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@esbuild/aix-ppc64": "0.27.2",
|
||||
"@esbuild/android-arm": "0.27.2",
|
||||
"@esbuild/android-arm64": "0.27.2",
|
||||
"@esbuild/android-x64": "0.27.2",
|
||||
"@esbuild/darwin-arm64": "0.27.2",
|
||||
"@esbuild/darwin-x64": "0.27.2",
|
||||
"@esbuild/freebsd-arm64": "0.27.2",
|
||||
"@esbuild/freebsd-x64": "0.27.2",
|
||||
"@esbuild/linux-arm": "0.27.2",
|
||||
"@esbuild/linux-arm64": "0.27.2",
|
||||
"@esbuild/linux-ia32": "0.27.2",
|
||||
"@esbuild/linux-loong64": "0.27.2",
|
||||
"@esbuild/linux-mips64el": "0.27.2",
|
||||
"@esbuild/linux-ppc64": "0.27.2",
|
||||
"@esbuild/linux-riscv64": "0.27.2",
|
||||
"@esbuild/linux-s390x": "0.27.2",
|
||||
"@esbuild/linux-x64": "0.27.2",
|
||||
"@esbuild/netbsd-arm64": "0.27.2",
|
||||
"@esbuild/netbsd-x64": "0.27.2",
|
||||
"@esbuild/openbsd-arm64": "0.27.2",
|
||||
"@esbuild/openbsd-x64": "0.27.2",
|
||||
"@esbuild/openharmony-arm64": "0.27.2",
|
||||
"@esbuild/sunos-x64": "0.27.2",
|
||||
"@esbuild/win32-arm64": "0.27.2",
|
||||
"@esbuild/win32-ia32": "0.27.2",
|
||||
"@esbuild/win32-x64": "0.27.2"
|
||||
}
|
||||
},
|
||||
"node_modules/typescript": {
|
||||
"version": "5.9.3",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
|
||||
|
||||
@@ -33,5 +33,8 @@
|
||||
"typescript": "^5.7.2",
|
||||
"vitest": "^2.1.8",
|
||||
"wrangler": "^4.59.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"hono": "^4.11.7"
|
||||
}
|
||||
}
|
||||
|
||||
191
src/app.ts
Normal file
191
src/app.ts
Normal file
@@ -0,0 +1,191 @@
|
||||
/**
|
||||
* Hono Application Setup
|
||||
*
|
||||
* Configures Hono app with CORS, security headers, and routes.
|
||||
*/
|
||||
|
||||
import { Hono } from 'hono';
|
||||
import type { Context } from 'hono';
|
||||
import type { Env } from './types';
|
||||
import { CORS, HTTP_STATUS } from './constants';
|
||||
import { createLogger } from './utils/logger';
|
||||
import {
|
||||
requestIdMiddleware,
|
||||
authMiddleware,
|
||||
rateLimitMiddleware,
|
||||
optionalAuthMiddleware,
|
||||
} from './middleware/hono-adapters';
|
||||
import { handleHealth } from './routes/health';
|
||||
import { handleInstances } from './routes/instances';
|
||||
import { handleSync } from './routes/sync';
|
||||
|
||||
const logger = createLogger('[App]');
|
||||
|
||||
// Context variables type
|
||||
type Variables = {
|
||||
requestId: string;
|
||||
authenticated?: boolean;
|
||||
};
|
||||
|
||||
// Create Hono app with type-safe bindings
|
||||
const app = new Hono<{ Bindings: Env; Variables: Variables }>();
|
||||
|
||||
/**
|
||||
* Get CORS origin for request
|
||||
* Reused from original index.ts logic
|
||||
*/
|
||||
function getCorsOrigin(c: Context<{ Bindings: Env; Variables: Variables }>): string {
|
||||
const origin = c.req.header('Origin');
|
||||
const env = c.env;
|
||||
|
||||
// Environment variable has explicit origin configured (highest priority)
|
||||
if (env.CORS_ORIGIN && env.CORS_ORIGIN !== '*') {
|
||||
return env.CORS_ORIGIN;
|
||||
}
|
||||
|
||||
// Build allowed origins list based on environment
|
||||
const isDevelopment = env.ENVIRONMENT === 'development';
|
||||
const allowedOrigins = isDevelopment
|
||||
? [...CORS.ALLOWED_ORIGINS, ...CORS.DEVELOPMENT_ORIGINS]
|
||||
: CORS.ALLOWED_ORIGINS;
|
||||
|
||||
// Request origin is in allowed list
|
||||
if (origin && allowedOrigins.includes(origin)) {
|
||||
return origin;
|
||||
}
|
||||
|
||||
// Log unmatched origins for security monitoring
|
||||
if (origin && !allowedOrigins.includes(origin)) {
|
||||
const sanitizedOrigin = origin.replace(/[\r\n\t]/g, '').substring(0, 256);
|
||||
logger.warn('Unmatched origin - using default', {
|
||||
requested_origin: sanitizedOrigin,
|
||||
environment: env.ENVIRONMENT || 'production',
|
||||
default_origin: CORS.DEFAULT_ORIGIN,
|
||||
});
|
||||
}
|
||||
|
||||
// Return explicit default (no wildcard)
|
||||
return CORS.DEFAULT_ORIGIN;
|
||||
}
|
||||
|
||||
/**
|
||||
* CORS middleware
|
||||
* Configured dynamically based on request origin
|
||||
*/
|
||||
app.use('*', async (c, next) => {
|
||||
// Handle OPTIONS preflight - must come before await next()
|
||||
if (c.req.method === 'OPTIONS') {
|
||||
const origin = getCorsOrigin(c);
|
||||
c.res.headers.set('Access-Control-Allow-Origin', origin);
|
||||
c.res.headers.set('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
|
||||
c.res.headers.set('Access-Control-Allow-Headers', 'Content-Type, X-API-Key');
|
||||
c.res.headers.set('Access-Control-Max-Age', CORS.MAX_AGE);
|
||||
return c.body(null, 204);
|
||||
}
|
||||
|
||||
await next();
|
||||
|
||||
// Set CORS headers after processing
|
||||
const origin = getCorsOrigin(c);
|
||||
c.res.headers.set('Access-Control-Allow-Origin', origin);
|
||||
c.res.headers.set('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
|
||||
c.res.headers.set('Access-Control-Allow-Headers', 'Content-Type, X-API-Key');
|
||||
c.res.headers.set('Access-Control-Max-Age', CORS.MAX_AGE);
|
||||
c.res.headers.set(
|
||||
'Access-Control-Expose-Headers',
|
||||
'X-RateLimit-Retry-After, Retry-After, X-Request-ID'
|
||||
);
|
||||
});
|
||||
|
||||
/**
|
||||
* Request ID middleware
|
||||
* Adds unique request ID for tracing
|
||||
*/
|
||||
app.use('*', requestIdMiddleware);
|
||||
|
||||
/**
|
||||
* Security headers middleware
|
||||
* Applied to all responses
|
||||
*/
|
||||
app.use('*', async (c, next) => {
|
||||
await next();
|
||||
|
||||
// Add security headers to response
|
||||
c.res.headers.set('X-Content-Type-Options', 'nosniff');
|
||||
c.res.headers.set('X-Frame-Options', 'DENY');
|
||||
c.res.headers.set('Strict-Transport-Security', 'max-age=31536000');
|
||||
c.res.headers.set('Content-Security-Policy', "default-src 'none'");
|
||||
c.res.headers.set('X-XSS-Protection', '1; mode=block');
|
||||
c.res.headers.set('Referrer-Policy', 'no-referrer');
|
||||
});
|
||||
|
||||
/**
|
||||
* Environment validation middleware
|
||||
* Checks required environment variables before processing
|
||||
*/
|
||||
app.use('*', async (c, next) => {
|
||||
const required = ['API_KEY'];
|
||||
const missing = required.filter((key) => !c.env[key as keyof Env]);
|
||||
|
||||
if (missing.length > 0) {
|
||||
logger.error('Missing required environment variables', {
|
||||
missing,
|
||||
request_id: c.get('requestId'),
|
||||
});
|
||||
|
||||
return c.json(
|
||||
{
|
||||
error: 'Service Unavailable',
|
||||
message: 'Service configuration error',
|
||||
},
|
||||
503
|
||||
);
|
||||
}
|
||||
|
||||
return next();
|
||||
});
|
||||
|
||||
/**
|
||||
* Routes
|
||||
*/
|
||||
|
||||
// Health check (public endpoint with optional authentication)
|
||||
app.get('/health', optionalAuthMiddleware, handleHealth);
|
||||
|
||||
// Query instances (authenticated, rate limited)
|
||||
app.get('/instances', authMiddleware, rateLimitMiddleware, handleInstances);
|
||||
|
||||
// Sync trigger (authenticated, rate limited)
|
||||
app.post('/sync', authMiddleware, rateLimitMiddleware, handleSync);
|
||||
|
||||
/**
|
||||
* 404 handler
|
||||
*/
|
||||
app.notFound((c) => {
|
||||
return c.json(
|
||||
{
|
||||
error: 'Not Found',
|
||||
path: c.req.path,
|
||||
},
|
||||
HTTP_STATUS.NOT_FOUND
|
||||
);
|
||||
});
|
||||
|
||||
/**
|
||||
* Global error handler
|
||||
*/
|
||||
app.onError((err, c) => {
|
||||
logger.error('Request error', {
|
||||
error: err,
|
||||
request_id: c.get('requestId'),
|
||||
});
|
||||
|
||||
return c.json(
|
||||
{
|
||||
error: 'Internal Server Error',
|
||||
},
|
||||
HTTP_STATUS.INTERNAL_ERROR
|
||||
);
|
||||
});
|
||||
|
||||
export default app;
|
||||
190
src/index.ts
190
src/index.ts
@@ -5,199 +5,15 @@
|
||||
*/
|
||||
|
||||
import { Env } from './types';
|
||||
import { handleSync, handleInstances, handleHealth } from './routes';
|
||||
import {
|
||||
authenticateRequest,
|
||||
verifyApiKey,
|
||||
createUnauthorizedResponse,
|
||||
checkRateLimit,
|
||||
createRateLimitResponse,
|
||||
} from './middleware';
|
||||
import { CORS, HTTP_STATUS } from './constants';
|
||||
import app from './app';
|
||||
import { createLogger } from './utils/logger';
|
||||
import { SyncOrchestrator } from './services/sync';
|
||||
|
||||
/**
|
||||
* Validate required environment variables
|
||||
*/
|
||||
function validateEnv(env: Env): { valid: boolean; missing: string[] } {
|
||||
const required = ['API_KEY'];
|
||||
const missing = required.filter(key => !env[key as keyof Env]);
|
||||
return { valid: missing.length === 0, missing };
|
||||
}
|
||||
|
||||
/**
|
||||
* Get CORS origin for request
|
||||
*
|
||||
* Security: No wildcard fallback. Returns explicit allowed origin or default.
|
||||
* Logs unmatched origins for monitoring.
|
||||
*/
|
||||
function getCorsOrigin(request: Request, env: Env): string {
|
||||
const origin = request.headers.get('Origin');
|
||||
const logger = createLogger('[CORS]', env);
|
||||
|
||||
// Environment variable has explicit origin configured (highest priority)
|
||||
if (env.CORS_ORIGIN && env.CORS_ORIGIN !== '*') {
|
||||
return env.CORS_ORIGIN;
|
||||
}
|
||||
|
||||
// Build allowed origins list based on environment
|
||||
const isDevelopment = env.ENVIRONMENT === 'development';
|
||||
const allowedOrigins = isDevelopment
|
||||
? [...CORS.ALLOWED_ORIGINS, ...CORS.DEVELOPMENT_ORIGINS]
|
||||
: CORS.ALLOWED_ORIGINS;
|
||||
|
||||
// Request origin is in allowed list
|
||||
if (origin && allowedOrigins.includes(origin)) {
|
||||
return origin;
|
||||
}
|
||||
|
||||
// Log unmatched origins for security monitoring
|
||||
if (origin && !allowedOrigins.includes(origin)) {
|
||||
// Sanitize origin to prevent log injection (remove control characters)
|
||||
const sanitizedOrigin = origin.replace(/[\r\n\t]/g, '').substring(0, 256);
|
||||
logger.warn('Unmatched origin - using default', {
|
||||
requested_origin: sanitizedOrigin,
|
||||
environment: env.ENVIRONMENT || 'production',
|
||||
default_origin: CORS.DEFAULT_ORIGIN
|
||||
});
|
||||
}
|
||||
|
||||
// Return explicit default (no wildcard)
|
||||
return CORS.DEFAULT_ORIGIN;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add security headers to response
|
||||
* Performance optimization: Reuses response body without cloning to minimize memory allocation
|
||||
*
|
||||
* Benefits:
|
||||
* - Avoids Response.clone() which copies the entire body stream
|
||||
* - Directly references response.body (ReadableStream) without duplication
|
||||
* - Reduces memory allocation and GC pressure per request
|
||||
*
|
||||
* Note: response.body can be null for 204 No Content or empty responses
|
||||
*/
|
||||
function addSecurityHeaders(response: Response, corsOrigin?: string, requestId?: string): Response {
|
||||
const headers = new Headers(response.headers);
|
||||
|
||||
// Basic security headers
|
||||
headers.set('X-Content-Type-Options', 'nosniff');
|
||||
headers.set('X-Frame-Options', 'DENY');
|
||||
headers.set('Strict-Transport-Security', 'max-age=31536000');
|
||||
|
||||
// CORS headers
|
||||
headers.set('Access-Control-Allow-Origin', corsOrigin || CORS.DEFAULT_ORIGIN);
|
||||
headers.set('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
|
||||
headers.set('Access-Control-Allow-Headers', 'Content-Type, X-API-Key');
|
||||
headers.set('Access-Control-Max-Age', CORS.MAX_AGE);
|
||||
headers.set('Access-Control-Expose-Headers', 'X-RateLimit-Retry-After, Retry-After, X-Request-ID');
|
||||
|
||||
// Additional security headers
|
||||
headers.set('Content-Security-Policy', "default-src 'none'");
|
||||
headers.set('X-XSS-Protection', '1; mode=block');
|
||||
headers.set('Referrer-Policy', 'no-referrer');
|
||||
|
||||
// Request ID for audit trail
|
||||
if (requestId) {
|
||||
headers.set('X-Request-ID', requestId);
|
||||
}
|
||||
|
||||
// Create new Response with same body reference (no copy) and updated headers
|
||||
return new Response(response.body, {
|
||||
status: response.status,
|
||||
statusText: response.statusText,
|
||||
headers,
|
||||
});
|
||||
}
|
||||
|
||||
export default {
|
||||
/**
|
||||
* HTTP Request Handler
|
||||
* HTTP Request Handler (delegated to Hono)
|
||||
*/
|
||||
async fetch(request: Request, env: Env, _ctx: ExecutionContext): Promise<Response> {
|
||||
const url = new URL(request.url);
|
||||
const path = url.pathname;
|
||||
|
||||
// Generate request ID for audit trail (use CF-Ray if available, otherwise generate UUID)
|
||||
const requestId = request.headers.get('CF-Ray') || crypto.randomUUID();
|
||||
|
||||
// Get CORS origin based on request and configuration
|
||||
const corsOrigin = getCorsOrigin(request, env);
|
||||
|
||||
try {
|
||||
// Handle OPTIONS preflight requests
|
||||
if (request.method === 'OPTIONS') {
|
||||
return addSecurityHeaders(new Response(null, { status: 204 }), corsOrigin, requestId);
|
||||
}
|
||||
|
||||
// Validate required environment variables
|
||||
const envValidation = validateEnv(env);
|
||||
if (!envValidation.valid) {
|
||||
const logger = createLogger('[Worker]');
|
||||
logger.error('Missing required environment variables', { missing: envValidation.missing, request_id: requestId });
|
||||
return addSecurityHeaders(
|
||||
Response.json(
|
||||
{ error: 'Service Unavailable', message: 'Service configuration error' },
|
||||
{ status: 503 }
|
||||
),
|
||||
corsOrigin,
|
||||
requestId
|
||||
);
|
||||
}
|
||||
|
||||
// Health check (public endpoint with optional authentication)
|
||||
if (path === '/health') {
|
||||
const apiKey = request.headers.get('X-API-Key');
|
||||
const authenticated = apiKey ? verifyApiKey(apiKey, env) : false;
|
||||
return addSecurityHeaders(await handleHealth(env, authenticated), corsOrigin, requestId);
|
||||
}
|
||||
|
||||
// Authentication required for all other endpoints
|
||||
const isAuthenticated = await authenticateRequest(request, env);
|
||||
if (!isAuthenticated) {
|
||||
return addSecurityHeaders(createUnauthorizedResponse(), corsOrigin, requestId);
|
||||
}
|
||||
|
||||
// Rate limiting for authenticated endpoints
|
||||
const rateLimitCheck = await checkRateLimit(request, path, env);
|
||||
if (!rateLimitCheck.allowed) {
|
||||
return addSecurityHeaders(createRateLimitResponse(rateLimitCheck.retryAfter!), corsOrigin, requestId);
|
||||
}
|
||||
|
||||
// Query instances
|
||||
if (path === '/instances' && request.method === 'GET') {
|
||||
return addSecurityHeaders(await handleInstances(request, env), corsOrigin, requestId);
|
||||
}
|
||||
|
||||
// Sync trigger
|
||||
if (path === '/sync' && request.method === 'POST') {
|
||||
return addSecurityHeaders(await handleSync(request, env), corsOrigin, requestId);
|
||||
}
|
||||
|
||||
// 404 Not Found
|
||||
return addSecurityHeaders(
|
||||
Response.json(
|
||||
{ error: 'Not Found', path },
|
||||
{ status: HTTP_STATUS.NOT_FOUND }
|
||||
),
|
||||
corsOrigin,
|
||||
requestId
|
||||
);
|
||||
|
||||
} catch (error) {
|
||||
const logger = createLogger('[Worker]');
|
||||
logger.error('Request error', { error, request_id: requestId });
|
||||
return addSecurityHeaders(
|
||||
Response.json(
|
||||
{ error: 'Internal Server Error' },
|
||||
{ status: HTTP_STATUS.INTERNAL_ERROR }
|
||||
),
|
||||
corsOrigin,
|
||||
requestId
|
||||
);
|
||||
}
|
||||
},
|
||||
fetch: app.fetch,
|
||||
|
||||
/**
|
||||
* Scheduled (Cron) Handler
|
||||
|
||||
110
src/middleware/hono-adapters.ts
Normal file
110
src/middleware/hono-adapters.ts
Normal file
@@ -0,0 +1,110 @@
|
||||
/**
|
||||
* Hono Middleware Adapters
|
||||
*
|
||||
* Adapts existing authentication and rate limiting middleware to Hono's middleware pattern.
|
||||
*/
|
||||
|
||||
import type { Context, Next } from 'hono';
|
||||
import type { Env } from '../types';
|
||||
import {
|
||||
authenticateRequest,
|
||||
verifyApiKey,
|
||||
createUnauthorizedResponse,
|
||||
} from './auth';
|
||||
import { checkRateLimit, createRateLimitResponse } from './rateLimit';
|
||||
import { createLogger } from '../utils/logger';
|
||||
|
||||
const logger = createLogger('[Middleware]');
|
||||
|
||||
// Context variables type
|
||||
type Variables = {
|
||||
requestId: string;
|
||||
authenticated?: boolean;
|
||||
};
|
||||
|
||||
/**
|
||||
* Request ID middleware
|
||||
* Adds unique request ID to context for tracing
|
||||
*/
|
||||
export async function requestIdMiddleware(
|
||||
c: Context<{ Bindings: Env; Variables: Variables }>,
|
||||
next: Next
|
||||
): Promise<void> {
|
||||
// Use CF-Ray if available, otherwise generate UUID
|
||||
const requestId = c.req.header('CF-Ray') || crypto.randomUUID();
|
||||
|
||||
// Store in context for handlers to use
|
||||
c.set('requestId', requestId);
|
||||
|
||||
await next();
|
||||
|
||||
// Add to response headers
|
||||
c.res.headers.set('X-Request-ID', requestId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Authentication middleware
|
||||
* Validates X-API-Key header using existing auth logic
|
||||
*/
|
||||
export async function authMiddleware(
|
||||
c: Context<{ Bindings: Env; Variables: Variables }>,
|
||||
next: Next
|
||||
): Promise<Response | void> {
|
||||
const request = c.req.raw;
|
||||
const env = c.env;
|
||||
|
||||
const isAuthenticated = await authenticateRequest(request, env);
|
||||
|
||||
if (!isAuthenticated) {
|
||||
logger.warn('[Auth] Unauthorized request', {
|
||||
path: c.req.path,
|
||||
requestId: c.get('requestId'),
|
||||
});
|
||||
return createUnauthorizedResponse();
|
||||
}
|
||||
|
||||
await next();
|
||||
}
|
||||
|
||||
/**
|
||||
* Rate limiting middleware
|
||||
* Applies rate limits based on endpoint using existing rate limit logic
|
||||
*/
|
||||
export async function rateLimitMiddleware(
|
||||
c: Context<{ Bindings: Env; Variables: Variables }>,
|
||||
next: Next
|
||||
): Promise<Response | void> {
|
||||
const request = c.req.raw;
|
||||
const path = c.req.path;
|
||||
const env = c.env;
|
||||
|
||||
const rateLimitCheck = await checkRateLimit(request, path, env);
|
||||
|
||||
if (!rateLimitCheck.allowed) {
|
||||
logger.warn('[RateLimit] Rate limit exceeded', {
|
||||
path,
|
||||
retryAfter: rateLimitCheck.retryAfter,
|
||||
requestId: c.get('requestId'),
|
||||
});
|
||||
return createRateLimitResponse(rateLimitCheck.retryAfter!);
|
||||
}
|
||||
|
||||
await next();
|
||||
}
|
||||
|
||||
/**
|
||||
* Optional authentication middleware for health check
|
||||
* Checks if API key is provided and valid, stores result in context
|
||||
*/
|
||||
export async function optionalAuthMiddleware(
|
||||
c: Context<{ Bindings: Env; Variables: Variables }>,
|
||||
next: Next
|
||||
): Promise<void> {
|
||||
const apiKey = c.req.header('X-API-Key');
|
||||
const authenticated = apiKey ? verifyApiKey(apiKey, c.env) : false;
|
||||
|
||||
// Store authentication status in context
|
||||
c.set('authenticated', authenticated);
|
||||
|
||||
await next();
|
||||
}
|
||||
@@ -3,12 +3,19 @@
|
||||
* Comprehensive health monitoring for database and provider sync status
|
||||
*/
|
||||
|
||||
import type { Context } from 'hono';
|
||||
import { Env } from '../types';
|
||||
import { HTTP_STATUS } from '../constants';
|
||||
import { createLogger } from '../utils/logger';
|
||||
|
||||
const logger = createLogger('[Health]');
|
||||
|
||||
// Context variables type
|
||||
type Variables = {
|
||||
requestId: string;
|
||||
authenticated?: boolean;
|
||||
};
|
||||
|
||||
/**
|
||||
* Component health status
|
||||
*/
|
||||
@@ -159,18 +166,17 @@ function sanitizeError(error: string): string {
|
||||
|
||||
/**
|
||||
* Handle health check request
|
||||
* @param env - Cloudflare Worker environment
|
||||
* @param authenticated - Whether the request is authenticated (default: false)
|
||||
* @param c - Hono context
|
||||
*/
|
||||
export async function handleHealth(
|
||||
env: Env,
|
||||
authenticated: boolean = false
|
||||
c: Context<{ Bindings: Env; Variables: Variables }>
|
||||
): Promise<Response> {
|
||||
const timestamp = new Date().toISOString();
|
||||
const authenticated = c.get('authenticated') ?? false;
|
||||
|
||||
try {
|
||||
// Check database health
|
||||
const dbHealth = await checkDatabaseHealth(env.DB);
|
||||
const dbHealth = await checkDatabaseHealth(c.env.DB);
|
||||
|
||||
// If database is unhealthy, return early
|
||||
if (dbHealth.status === 'unhealthy') {
|
||||
@@ -206,7 +212,7 @@ export async function handleHealth(
|
||||
}
|
||||
|
||||
// Get all providers with aggregated counts in a single query
|
||||
const providersWithCounts = await env.DB.prepare(`
|
||||
const providersWithCounts = await c.env.DB.prepare(`
|
||||
SELECT
|
||||
p.id,
|
||||
p.name,
|
||||
|
||||
@@ -5,10 +5,17 @@
|
||||
* Integrates with cache service for performance optimization.
|
||||
*/
|
||||
|
||||
import type { Context } from 'hono';
|
||||
import type { Env, InstanceQueryParams } from '../types';
|
||||
import { QueryService } from '../services/query';
|
||||
import { getGlobalCacheService } from '../services/cache';
|
||||
import { logger } from '../utils/logger';
|
||||
|
||||
// Context variables type
|
||||
type Variables = {
|
||||
requestId: string;
|
||||
authenticated?: boolean;
|
||||
};
|
||||
import {
|
||||
SUPPORTED_PROVIDERS,
|
||||
type SupportedProvider,
|
||||
@@ -311,24 +318,22 @@ function parseQueryParams(url: URL): {
|
||||
/**
|
||||
* Handle GET /instances endpoint
|
||||
*
|
||||
* @param request - HTTP request object
|
||||
* @param env - Cloudflare Worker environment bindings
|
||||
* @param c - Hono context
|
||||
* @returns JSON response with instance query results
|
||||
*
|
||||
* @example
|
||||
* GET /instances?provider=linode&min_vcpu=2&max_price=20&sort_by=price&order=asc&limit=50
|
||||
*/
|
||||
export async function handleInstances(
|
||||
request: Request,
|
||||
env: Env
|
||||
c: Context<{ Bindings: Env; Variables: Variables }>
|
||||
): Promise<Response> {
|
||||
const startTime = Date.now();
|
||||
|
||||
logger.info('[Instances] Request received', { url: request.url });
|
||||
logger.info('[Instances] Request received', { url: c.req.url });
|
||||
|
||||
try {
|
||||
// Parse URL and query parameters
|
||||
const url = new URL(request.url);
|
||||
const url = new URL(c.req.url);
|
||||
const parseResult = parseQueryParams(url);
|
||||
|
||||
// Handle validation errors
|
||||
@@ -347,7 +352,7 @@ export async function handleInstances(
|
||||
logger.info('[Instances] Query params validated', params as unknown as Record<string, unknown>);
|
||||
|
||||
// Get global cache service singleton (shared across all routes)
|
||||
const cacheService = getGlobalCacheService(CACHE_TTL.INSTANCES, env.RATE_LIMIT_KV);
|
||||
const cacheService = getGlobalCacheService(CACHE_TTL.INSTANCES, c.env.RATE_LIMIT_KV);
|
||||
|
||||
// Generate cache key from query parameters
|
||||
const cacheKey = cacheService.generateKey(params as unknown as Record<string, unknown>);
|
||||
@@ -421,7 +426,7 @@ export async function handleInstances(
|
||||
};
|
||||
|
||||
// Get QueryService singleton (reused across requests)
|
||||
const queryService = getQueryService(env.DB, env);
|
||||
const queryService = getQueryService(c.env.DB, c.env);
|
||||
const result = await queryService.queryInstances(queryParams);
|
||||
|
||||
const queryTime = Date.now() - startTime;
|
||||
|
||||
@@ -5,12 +5,19 @@
|
||||
* Validates request parameters and orchestrates sync operations.
|
||||
*/
|
||||
|
||||
import type { Context } from 'hono';
|
||||
import type { Env } from '../types';
|
||||
import { SyncOrchestrator } from '../services/sync';
|
||||
import { logger } from '../utils/logger';
|
||||
import { SUPPORTED_PROVIDERS, HTTP_STATUS } from '../constants';
|
||||
import { parseJsonBody, validateProviders, createErrorResponse } from '../utils/validation';
|
||||
|
||||
// Context variables type
|
||||
type Variables = {
|
||||
requestId: string;
|
||||
authenticated?: boolean;
|
||||
};
|
||||
|
||||
/**
|
||||
* Request body interface for sync endpoint
|
||||
*/
|
||||
@@ -23,8 +30,7 @@ interface SyncRequestBody {
|
||||
/**
|
||||
* Handle POST /sync endpoint
|
||||
*
|
||||
* @param request - HTTP request object
|
||||
* @param env - Cloudflare Worker environment bindings
|
||||
* @param c - Hono context
|
||||
* @returns JSON response with sync results
|
||||
*
|
||||
* @example
|
||||
@@ -35,8 +41,7 @@ interface SyncRequestBody {
|
||||
* }
|
||||
*/
|
||||
export async function handleSync(
|
||||
request: Request,
|
||||
env: Env
|
||||
c: Context<{ Bindings: Env; Variables: Variables }>
|
||||
): Promise<Response> {
|
||||
const startTime = Date.now();
|
||||
const startedAt = new Date().toISOString();
|
||||
@@ -45,7 +50,7 @@ export async function handleSync(
|
||||
|
||||
try {
|
||||
// Validate content-length before parsing body
|
||||
const contentLength = request.headers.get('content-length');
|
||||
const contentLength = c.req.header('content-length');
|
||||
if (contentLength) {
|
||||
const bodySize = parseInt(contentLength, 10);
|
||||
if (isNaN(bodySize) || bodySize > 10240) { // 10KB limit for sync
|
||||
@@ -57,12 +62,12 @@ export async function handleSync(
|
||||
}
|
||||
|
||||
// Parse and validate request body
|
||||
const contentType = request.headers.get('content-type');
|
||||
const contentType = c.req.header('content-type');
|
||||
let body: SyncRequestBody = {};
|
||||
|
||||
// Only parse JSON if content-type is set
|
||||
if (contentType && contentType.includes('application/json')) {
|
||||
const parseResult = await parseJsonBody<SyncRequestBody>(request);
|
||||
const parseResult = await parseJsonBody<SyncRequestBody>(c.req.raw);
|
||||
if (!parseResult.success) {
|
||||
logger.error('[Sync] Invalid JSON in request body', {
|
||||
code: parseResult.error.code,
|
||||
@@ -90,7 +95,7 @@ export async function handleSync(
|
||||
logger.info('[Sync] Validation passed', { providers, force });
|
||||
|
||||
// Initialize SyncOrchestrator
|
||||
const orchestrator = new SyncOrchestrator(env.DB, env);
|
||||
const orchestrator = new SyncOrchestrator(c.env.DB, c.env);
|
||||
|
||||
// Execute synchronization
|
||||
logger.info('[Sync] Starting synchronization', { providers });
|
||||
|
||||
Reference in New Issue
Block a user