├── .dockerignore ├── .gitignore ├── .vscode ├── cspell.json └── settings.json ├── LICENSE ├── README.md ├── package.json ├── pnpm-lock.yaml ├── pnpm-workspace.yaml ├── publisher ├── .env.example ├── Dockerfile ├── api.http ├── config.template.capnp ├── fly.toml ├── package.json ├── src │ ├── calls │ │ ├── getConfig.ts │ │ ├── index.ts │ │ ├── models.ts │ │ └── publish.ts │ ├── global.d.ts │ └── index.ts ├── tsconfig.build.json └── tsconfig.json ├── tsconfig.base.json └── worker ├── Dockerfile ├── entrypoint.sh ├── fly.toml ├── package.json ├── src └── router.ts ├── tasks └── build.js └── tsconfig.json /.dockerignore: -------------------------------------------------------------------------------- 1 | **/.tsbuildinfo 2 | **/Dockerfile -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # local config 2 | *.local.* 3 | *.local 4 | 5 | #node 6 | node_modules/ 7 | .env 8 | .env.* 9 | .pnpm-debug.log 10 | .tsbuildinfo 11 | .build 12 | 13 | # artifacts 14 | .expo/ 15 | .astro/ 16 | 17 | *.local 18 | 19 | # testing 20 | coverage/ 21 | 22 | # Gitignore 23 | *.swp 24 | *.*~ 25 | project.lock.json 26 | .DS_Store 27 | *.pyc 28 | nupkg/ 29 | 30 | # Rider 31 | .idea 32 | 33 | # User-specific files 34 | *.suo 35 | *.user 36 | *.userosscache 37 | *.sln.docstates 38 | 39 | # Build results 40 | dist/ 41 | dist-ssr/ 42 | [Dd]ebug/ 43 | [Dd]ebugPublic/ 44 | [Rr]elease/ 45 | [Rr]eleases/ 46 | x64/ 47 | x86/ 48 | build/ 49 | bld/ 50 | [Bb]in/ 51 | [Oo]bj/ 52 | [Oo]ut/ 53 | msbuild.log 54 | msbuild.err 55 | msbuild.wrn 56 | 57 | # Visual Studio 2015 58 | .vs/ 59 | 60 | # Windows thumbnail cache files 61 | Thumbs.db 62 | Thumbs.db:encryptable 63 | 64 | # Dump file 65 | *.stackdump 66 | 67 | # Folder config file 68 | [Dd]esktop.ini 69 | 70 | # Recycle Bin used on file shares 71 | $RECYCLE.BIN/ 72 | 73 | # Windows shortcuts 74 | *.lnk 75 | 76 | # General 77 | .DS_Store 78 | .AppleDouble 79 | .LSOverride 80 | ._* 81 | .DocumentRevisions-V100 82 | .fseventsd 83 | .Spotlight-V100 84 | .TemporaryItems 85 | .Trashes 86 | .VolumeIcon.icns 87 | .com.apple.timemachine.donotpresent 88 | 89 | # Linux 90 | *~ 91 | .fuse_hidden* 92 | .directory 93 | .Trash-* 94 | .nfs* 95 | -------------------------------------------------------------------------------- /.vscode/cspell.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2", 3 | "language": "en", 4 | "overrides": [ 5 | { 6 | "filename": "**/*.astro", 7 | "languageId": "html" 8 | } 9 | ], 10 | "ignorePaths": [ 11 | "package.json", 12 | "package-lock.json", 13 | "node_modules", 14 | ".git", 15 | ".vscode", 16 | ".vscode-insiders" 17 | ], 18 | "words": [ 19 | "capnp", 20 | "fflate", 21 | "httpc", 22 | "outfile", 23 | "tsyringe", 24 | "workerd" 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "files.exclude": { 3 | "**/dist": true, 4 | }, 5 | "typescript.tsdk": "node_modules/typescript/lib" 6 | } 7 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 Giuseppe La Torre 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # self-workerd 2 | 3 | This is a proof-of-concept on how you can build and self-host a complete FaaS architecture. 4 | 5 | This project is based on [workerd](https://github.com/cloudflare/workerd), the V8-based JavaScript runtime that powers Cloudflare Workers. 6 | 7 | And, for a real scenario, it uses [Fly](https://fly.io) as cloud provider, where the FaaS is hosted. It relies on container-based deployment. 8 | 9 | ## Step by step Tutorial 10 | A complete guide can be found on 11 | [https://www.breakp.dev/blog/build-your-own-faas](https://www.breakp.dev/blog/build-your-own-faas/?from=github-readme) 12 | 13 | It includes everything - from setup, to test and deploy. 14 | 15 | 16 | ## Local run 17 | This is a pnpm managed repository. So, it all starts with 18 | ``` 19 | pnpm install 20 | ``` 21 | Then you can start the publisher 22 | ``` 23 | cd publisher 24 | pnpm dev 25 | ``` 26 | and the worker 27 | ``` 28 | cd worker 29 | pnpm build 30 | pnpm start:worker 31 | ``` 32 | 33 | 34 | ## Follow updates 35 | - Star the repository 36 | - Twitter: [@giuseppelt](https://twitter.com/@giuseppelt) 37 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "self-workerd-mono", 3 | "version": "0.0.1", 4 | "type": "module", 5 | "private": true 6 | } 7 | -------------------------------------------------------------------------------- /pnpm-lock.yaml: -------------------------------------------------------------------------------- 1 | lockfileVersion: '6.0' 2 | 3 | settings: 4 | autoInstallPeers: true 5 | excludeLinksFromLockfile: false 6 | 7 | importers: 8 | 9 | .: {} 10 | 11 | publisher: 12 | dependencies: 13 | '@httpc/kit': 14 | specifier: 0.2.0-pre20230629031731 15 | version: 0.2.0-pre20230629031731(reflect-metadata@0.1.13)(tsyringe@4.8.0)(zod@3.21.4) 16 | dotenv: 17 | specifier: ^16.3.1 18 | version: 16.3.1 19 | fflate: 20 | specifier: ^0.8.0 21 | version: 0.8.0 22 | reflect-metadata: 23 | specifier: ^0.1.13 24 | version: 0.1.13 25 | tsyringe: 26 | specifier: ^4.8.0 27 | version: 4.8.0 28 | zod: 29 | specifier: ^3.21.4 30 | version: 3.21.4 31 | devDependencies: 32 | '@types/node': 33 | specifier: ^18 34 | version: 18.0.0 35 | '@types/node-fetch': 36 | specifier: ^2.6.4 37 | version: 2.6.4 38 | ts-node-dev: 39 | specifier: ^2.0.0 40 | version: 2.0.0(@types/node@18.0.0)(typescript@5.1.3) 41 | typescript: 42 | specifier: ^5.1.3 43 | version: 5.1.3 44 | 45 | worker: 46 | dependencies: 47 | workerd: 48 | specifier: ^1.20230518.0 49 | version: 1.20230518.0 50 | devDependencies: 51 | '@cloudflare/workers-types': 52 | specifier: ^4.20230518.0 53 | version: 4.20230518.0 54 | esbuild: 55 | specifier: ^0.18.6 56 | version: 0.18.6 57 | 58 | packages: 59 | 60 | /@cloudflare/workerd-darwin-64@1.20230518.0: 61 | resolution: {integrity: sha512-reApIf2/do6GjLlajU6LbRYh8gm/XcaRtzGbF8jo5IzyDSsdStmfNuvq7qssZXG92219Yp1kuTgR9+D1GGZGbg==} 62 | engines: {node: '>=16'} 63 | cpu: [x64] 64 | os: [darwin] 65 | requiresBuild: true 66 | dev: false 67 | optional: true 68 | 69 | /@cloudflare/workerd-darwin-arm64@1.20230518.0: 70 | resolution: {integrity: sha512-1l+xdbmPddqb2YIHd1YJ3YG/Fl1nhayzcxfL30xfNS89zJn9Xn3JomM0XMD4mk0d5GruBP3q8BQZ1Uo4rRLF3A==} 71 | engines: {node: '>=16'} 72 | cpu: [arm64] 73 | os: [darwin] 74 | requiresBuild: true 75 | dev: false 76 | optional: true 77 | 78 | /@cloudflare/workerd-linux-64@1.20230518.0: 79 | resolution: {integrity: sha512-/pfR+YBpMOPr2cAlwjtInil0hRZjD8KX9LqK9JkfkEiaBH8CYhnJQcOdNHZI+3OjcY09JnQtEVC5xC4nbW7Bvw==} 80 | engines: {node: '>=16'} 81 | cpu: [x64] 82 | os: [linux] 83 | requiresBuild: true 84 | dev: false 85 | optional: true 86 | 87 | /@cloudflare/workerd-linux-arm64@1.20230518.0: 88 | resolution: {integrity: sha512-q3HQvn3J4uEkE0cfDAGG8zqzSZrD47cavB/Tzv4mNutqwg6B4wL3ifjtGeB55tnP2K2KL0GVmX4tObcvpUF4BA==} 89 | engines: {node: '>=16'} 90 | cpu: [arm64] 91 | os: [linux] 92 | requiresBuild: true 93 | dev: false 94 | optional: true 95 | 96 | /@cloudflare/workerd-windows-64@1.20230518.0: 97 | resolution: {integrity: sha512-vNEHKS5gKKduNOBYtQjcBopAmFT1iScuPWMZa2nJboSjOB9I/5oiVsUpSyk5Y2ARyrohXNz0y8D7p87YzTASWw==} 98 | engines: {node: '>=16'} 99 | cpu: [x64] 100 | os: [win32] 101 | requiresBuild: true 102 | dev: false 103 | optional: true 104 | 105 | /@cloudflare/workers-types@4.20230518.0: 106 | resolution: {integrity: sha512-A0w1V+5SUawGaaPRlhFhSC/SCDT9oQG8TMoWOKFLA4qbqagELqEAFD4KySBIkeVOvCBLT1DZSYBMCxbXddl0kw==} 107 | dev: true 108 | 109 | /@cspotcode/source-map-support@0.8.1: 110 | resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==} 111 | engines: {node: '>=12'} 112 | dependencies: 113 | '@jridgewell/trace-mapping': 0.3.9 114 | dev: true 115 | 116 | /@esbuild/android-arm64@0.18.6: 117 | resolution: {integrity: sha512-pL0Ci8P9q1sWbtPx8CXbc8JvPvvYdJJQ+LO09PLFsbz3aYNdFBGWJjiHU+CaObO4Ames+GOFpXRAJZS2L3ZK/A==} 118 | engines: {node: '>=12'} 119 | cpu: [arm64] 120 | os: [android] 121 | requiresBuild: true 122 | dev: true 123 | optional: true 124 | 125 | /@esbuild/android-arm@0.18.6: 126 | resolution: {integrity: sha512-J3lwhDSXBBppSzm/LC1uZ8yKSIpExc+5T8MxrYD9KNVZG81FOAu2VF2gXi/6A/LwDDQQ+b6DpQbYlo3VwxFepQ==} 127 | engines: {node: '>=12'} 128 | cpu: [arm] 129 | os: [android] 130 | requiresBuild: true 131 | dev: true 132 | optional: true 133 | 134 | /@esbuild/android-x64@0.18.6: 135 | resolution: {integrity: sha512-hE2vZxOlJ05aY28lUpB0y0RokngtZtcUB+TVl9vnLEnY0z/8BicSvrkThg5/iI1rbf8TwXrbr2heEjl9fLf+EA==} 136 | engines: {node: '>=12'} 137 | cpu: [x64] 138 | os: [android] 139 | requiresBuild: true 140 | dev: true 141 | optional: true 142 | 143 | /@esbuild/darwin-arm64@0.18.6: 144 | resolution: {integrity: sha512-/tuyl4R+QhhoROQtuQj9E/yfJtZNdv2HKaHwYhhHGQDN1Teziem2Kh7BWQMumfiY7Lu9g5rO7scWdGE4OsQ6MQ==} 145 | engines: {node: '>=12'} 146 | cpu: [arm64] 147 | os: [darwin] 148 | requiresBuild: true 149 | dev: true 150 | optional: true 151 | 152 | /@esbuild/darwin-x64@0.18.6: 153 | resolution: {integrity: sha512-L7IQga2pDT+14Ti8HZwsVfbCjuKP4U213T3tuPggOzyK/p4KaUJxQFXJgfUFHKzU0zOXx8QcYRYZf0hSQtppkw==} 154 | engines: {node: '>=12'} 155 | cpu: [x64] 156 | os: [darwin] 157 | requiresBuild: true 158 | dev: true 159 | optional: true 160 | 161 | /@esbuild/freebsd-arm64@0.18.6: 162 | resolution: {integrity: sha512-bq10jFv42V20Kk77NvmO+WEZaLHBKuXcvEowixnBOMkaBgS7kQaqTc77ZJDbsUpXU3KKNLQFZctfaeINmeTsZA==} 163 | engines: {node: '>=12'} 164 | cpu: [arm64] 165 | os: [freebsd] 166 | requiresBuild: true 167 | dev: true 168 | optional: true 169 | 170 | /@esbuild/freebsd-x64@0.18.6: 171 | resolution: {integrity: sha512-HbDLlkDZqUMBQaiday0pJzB6/8Xx/10dI3xRebJBReOEeDSeS+7GzTtW9h8ZnfB7/wBCqvtAjGtWQLTNPbR2+g==} 172 | engines: {node: '>=12'} 173 | cpu: [x64] 174 | os: [freebsd] 175 | requiresBuild: true 176 | dev: true 177 | optional: true 178 | 179 | /@esbuild/linux-arm64@0.18.6: 180 | resolution: {integrity: sha512-NMY9yg/88MskEZH2s4i6biz/3av+M8xY5ua4HE7CCz5DBz542cr7REe317+v7oKjnYBCijHpkzo5vU85bkXQmQ==} 181 | engines: {node: '>=12'} 182 | cpu: [arm64] 183 | os: [linux] 184 | requiresBuild: true 185 | dev: true 186 | optional: true 187 | 188 | /@esbuild/linux-arm@0.18.6: 189 | resolution: {integrity: sha512-C+5kb6rgsGMmvIdUI7v1PPgC98A6BMv233e97aXZ5AE03iMdlILFD/20HlHrOi0x2CzbspXn9HOnlE4/Ijn5Kw==} 190 | engines: {node: '>=12'} 191 | cpu: [arm] 192 | os: [linux] 193 | requiresBuild: true 194 | dev: true 195 | optional: true 196 | 197 | /@esbuild/linux-ia32@0.18.6: 198 | resolution: {integrity: sha512-AXazA0ljvQEp7cA9jscABNXsjodKbEcqPcAE3rDzKN82Vb3lYOq6INd+HOCA7hk8IegEyHW4T72Z7QGIhyCQEA==} 199 | engines: {node: '>=12'} 200 | cpu: [ia32] 201 | os: [linux] 202 | requiresBuild: true 203 | dev: true 204 | optional: true 205 | 206 | /@esbuild/linux-loong64@0.18.6: 207 | resolution: {integrity: sha512-JjBf7TwY7ldcPgHYt9UcrjZB03+WZqg/jSwMAfzOzM5ZG+tu5umUqzy5ugH/crGI4eoDIhSOTDp1NL3Uo/05Fw==} 208 | engines: {node: '>=12'} 209 | cpu: [loong64] 210 | os: [linux] 211 | requiresBuild: true 212 | dev: true 213 | optional: true 214 | 215 | /@esbuild/linux-mips64el@0.18.6: 216 | resolution: {integrity: sha512-kATNsslryVxcH1sO3KP2nnyUWtZZVkgyhAUnyTVVa0OQQ9pmDRjTpHaE+2EQHoCM5wt/uav2edrAUqbwn3tkKQ==} 217 | engines: {node: '>=12'} 218 | cpu: [mips64el] 219 | os: [linux] 220 | requiresBuild: true 221 | dev: true 222 | optional: true 223 | 224 | /@esbuild/linux-ppc64@0.18.6: 225 | resolution: {integrity: sha512-B+wTKz+8pi7mcWXFQV0LA79dJ+qhiut5uK9q0omoKnq8yRIwQJwfg3/vclXoqqcX89Ri5Y5538V0Se2v5qlcLA==} 226 | engines: {node: '>=12'} 227 | cpu: [ppc64] 228 | os: [linux] 229 | requiresBuild: true 230 | dev: true 231 | optional: true 232 | 233 | /@esbuild/linux-riscv64@0.18.6: 234 | resolution: {integrity: sha512-h44RBLVXFUSjvhOfseE+5UxQ/r9LVeqK2S8JziJKOm9W7SePYRPDyn7MhzhNCCFPkcjIy+soCxfhlJXHXXCR0A==} 235 | engines: {node: '>=12'} 236 | cpu: [riscv64] 237 | os: [linux] 238 | requiresBuild: true 239 | dev: true 240 | optional: true 241 | 242 | /@esbuild/linux-s390x@0.18.6: 243 | resolution: {integrity: sha512-FlYpyr2Xc2AUePoAbc84NRV+mj7xpsISeQ36HGf9etrY5rTBEA+IU9HzWVmw5mDFtC62EQxzkLRj8h5Hq85yOQ==} 244 | engines: {node: '>=12'} 245 | cpu: [s390x] 246 | os: [linux] 247 | requiresBuild: true 248 | dev: true 249 | optional: true 250 | 251 | /@esbuild/linux-x64@0.18.6: 252 | resolution: {integrity: sha512-Mc4EUSYwzLci77u0Kao6ajB2WbTe5fNc7+lHwS3a+vJISC/oprwURezUYu1SdWAYoczbsyOvKAJwuNftoAdjjg==} 253 | engines: {node: '>=12'} 254 | cpu: [x64] 255 | os: [linux] 256 | requiresBuild: true 257 | dev: true 258 | optional: true 259 | 260 | /@esbuild/netbsd-x64@0.18.6: 261 | resolution: {integrity: sha512-3hgZlp7NqIM5lNG3fpdhBI5rUnPmdahraSmwAi+YX/bp7iZ7mpTv2NkypGs/XngdMtpzljICxnUG3uPfqLFd3w==} 262 | engines: {node: '>=12'} 263 | cpu: [x64] 264 | os: [netbsd] 265 | requiresBuild: true 266 | dev: true 267 | optional: true 268 | 269 | /@esbuild/openbsd-x64@0.18.6: 270 | resolution: {integrity: sha512-aEWTdZQHtSRROlDYn7ygB8yAqtnall/UnmoVIJVqccKitkAWVVSYocQUWrBOxLEFk8XdlRouVrLZe6WXszyviA==} 271 | engines: {node: '>=12'} 272 | cpu: [x64] 273 | os: [openbsd] 274 | requiresBuild: true 275 | dev: true 276 | optional: true 277 | 278 | /@esbuild/sunos-x64@0.18.6: 279 | resolution: {integrity: sha512-uxk/5yAGpjKZUHOECtI9W+9IcLjKj+2m0qf+RG7f7eRBHr8wP6wsr3XbNbgtOD1qSpPapd6R2ZfSeXTkCcAo5g==} 280 | engines: {node: '>=12'} 281 | cpu: [x64] 282 | os: [sunos] 283 | requiresBuild: true 284 | dev: true 285 | optional: true 286 | 287 | /@esbuild/win32-arm64@0.18.6: 288 | resolution: {integrity: sha512-oXlXGS9zvNCGoAT/tLHAsFKrIKye1JaIIP0anCdpaI+Dc10ftaNZcqfLzEwyhdzFAYInXYH4V7kEdH4hPyo9GA==} 289 | engines: {node: '>=12'} 290 | cpu: [arm64] 291 | os: [win32] 292 | requiresBuild: true 293 | dev: true 294 | optional: true 295 | 296 | /@esbuild/win32-ia32@0.18.6: 297 | resolution: {integrity: sha512-qh7IcAHUvvmMBmoIG+V+BbE9ZWSR0ohF51e5g8JZvU08kZF58uDFL5tHs0eoYz31H6Finv17te3W3QB042GqVA==} 298 | engines: {node: '>=12'} 299 | cpu: [ia32] 300 | os: [win32] 301 | requiresBuild: true 302 | dev: true 303 | optional: true 304 | 305 | /@esbuild/win32-x64@0.18.6: 306 | resolution: {integrity: sha512-9UDwkz7Wlm4N9jnv+4NL7F8vxLhSZfEkRArz2gD33HesAFfMLGIGNVXRoIHtWNw8feKsnGly9Hq1EUuRkWl0zA==} 307 | engines: {node: '>=12'} 308 | cpu: [x64] 309 | os: [win32] 310 | requiresBuild: true 311 | dev: true 312 | optional: true 313 | 314 | /@httpc/kit@0.2.0-pre20230629031731(reflect-metadata@0.1.13)(tsyringe@4.8.0)(zod@3.21.4): 315 | resolution: {integrity: sha512-ZfsAvGbEF7aJeyzmmwFINzMe5g4GlUJ+xjWYm99PXHUigaNxI7IhlPPCEyY8RfFEv+mmEI9HdFD2D70K3eKNYw==} 316 | engines: {node: '>=16.13.0'} 317 | peerDependencies: 318 | '@redis/client': ^1.3.1 319 | class-transformer: ^0.5.1 320 | class-validator: ^0.14.0 321 | lru-cache: ^9 322 | maxmind: ^4.3.11 323 | reflect-metadata: ^0.1.13 324 | tsyringe: ^4.7.0 325 | winston: ^3.8.2 326 | zod: ^3.21 327 | peerDependenciesMeta: 328 | '@redis/client': 329 | optional: true 330 | class-transformer: 331 | optional: true 332 | class-validator: 333 | optional: true 334 | lru-cache: 335 | optional: true 336 | maxmind: 337 | optional: true 338 | winston: 339 | optional: true 340 | zod: 341 | optional: true 342 | dependencies: 343 | '@httpc/server': 0.2.0-pre20230627005920 344 | jws: 4.0.0 345 | reflect-metadata: 0.1.13 346 | tsyringe: 4.8.0 347 | zod: 3.21.4 348 | dev: false 349 | 350 | /@httpc/server@0.2.0-pre20230627005920: 351 | resolution: {integrity: sha512-dK0XeleU8NBrJMIEICfClTBIRSZH3T2nALK5Q4zgRKhDgPD9C4s9Bh+zFWtLvWrzU2LXmNenW4J9Uw29fQBC5g==} 352 | engines: {node: '>=16.13.0'} 353 | dependencies: 354 | path-to-regexp: 6.2.1 355 | dev: false 356 | 357 | /@jridgewell/resolve-uri@3.1.1: 358 | resolution: {integrity: sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==} 359 | engines: {node: '>=6.0.0'} 360 | dev: true 361 | 362 | /@jridgewell/sourcemap-codec@1.4.15: 363 | resolution: {integrity: sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==} 364 | dev: true 365 | 366 | /@jridgewell/trace-mapping@0.3.9: 367 | resolution: {integrity: sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==} 368 | dependencies: 369 | '@jridgewell/resolve-uri': 3.1.1 370 | '@jridgewell/sourcemap-codec': 1.4.15 371 | dev: true 372 | 373 | /@tsconfig/node10@1.0.9: 374 | resolution: {integrity: sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==} 375 | dev: true 376 | 377 | /@tsconfig/node12@1.0.11: 378 | resolution: {integrity: sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==} 379 | dev: true 380 | 381 | /@tsconfig/node14@1.0.3: 382 | resolution: {integrity: sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==} 383 | dev: true 384 | 385 | /@tsconfig/node16@1.0.4: 386 | resolution: {integrity: sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==} 387 | dev: true 388 | 389 | /@types/node-fetch@2.6.4: 390 | resolution: {integrity: sha512-1ZX9fcN4Rvkvgv4E6PAY5WXUFWFcRWxZa3EW83UjycOB9ljJCedb2CupIP4RZMEwF/M3eTcCihbBRgwtGbg5Rg==} 391 | dependencies: 392 | '@types/node': 18.0.0 393 | form-data: 3.0.1 394 | dev: true 395 | 396 | /@types/node@18.0.0: 397 | resolution: {integrity: sha512-cHlGmko4gWLVI27cGJntjs/Sj8th9aYwplmZFwmmgYQQvL5NUsgVJG7OddLvNfLqYS31KFN0s3qlaD9qCaxACA==} 398 | dev: true 399 | 400 | /@types/strip-bom@3.0.0: 401 | resolution: {integrity: sha512-xevGOReSYGM7g/kUBZzPqCrR/KYAo+F0yiPc85WFTJa0MSLtyFTVTU6cJu/aV4mid7IffDIWqo69THF2o4JiEQ==} 402 | dev: true 403 | 404 | /@types/strip-json-comments@0.0.30: 405 | resolution: {integrity: sha512-7NQmHra/JILCd1QqpSzl8+mJRc8ZHz3uDm8YV1Ks9IhK0epEiTw8aIErbvH9PI+6XbqhyIQy3462nEsn7UVzjQ==} 406 | dev: true 407 | 408 | /acorn-walk@8.2.0: 409 | resolution: {integrity: sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==} 410 | engines: {node: '>=0.4.0'} 411 | dev: true 412 | 413 | /acorn@8.9.0: 414 | resolution: {integrity: sha512-jaVNAFBHNLXspO543WnNNPZFRtavh3skAkITqD0/2aeMkKZTN+254PyhwxFYrk3vQ1xfY+2wbesJMs/JC8/PwQ==} 415 | engines: {node: '>=0.4.0'} 416 | hasBin: true 417 | dev: true 418 | 419 | /anymatch@3.1.3: 420 | resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} 421 | engines: {node: '>= 8'} 422 | dependencies: 423 | normalize-path: 3.0.0 424 | picomatch: 2.3.1 425 | dev: true 426 | 427 | /arg@4.1.3: 428 | resolution: {integrity: sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==} 429 | dev: true 430 | 431 | /asynckit@0.4.0: 432 | resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} 433 | dev: true 434 | 435 | /balanced-match@1.0.2: 436 | resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} 437 | dev: true 438 | 439 | /binary-extensions@2.2.0: 440 | resolution: {integrity: sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==} 441 | engines: {node: '>=8'} 442 | dev: true 443 | 444 | /brace-expansion@1.1.11: 445 | resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} 446 | dependencies: 447 | balanced-match: 1.0.2 448 | concat-map: 0.0.1 449 | dev: true 450 | 451 | /braces@3.0.2: 452 | resolution: {integrity: sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==} 453 | engines: {node: '>=8'} 454 | dependencies: 455 | fill-range: 7.0.1 456 | dev: true 457 | 458 | /buffer-equal-constant-time@1.0.1: 459 | resolution: {integrity: sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==} 460 | dev: false 461 | 462 | /buffer-from@1.1.2: 463 | resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} 464 | dev: true 465 | 466 | /chokidar@3.5.3: 467 | resolution: {integrity: sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==} 468 | engines: {node: '>= 8.10.0'} 469 | dependencies: 470 | anymatch: 3.1.3 471 | braces: 3.0.2 472 | glob-parent: 5.1.2 473 | is-binary-path: 2.1.0 474 | is-glob: 4.0.3 475 | normalize-path: 3.0.0 476 | readdirp: 3.6.0 477 | optionalDependencies: 478 | fsevents: 2.3.2 479 | dev: true 480 | 481 | /combined-stream@1.0.8: 482 | resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} 483 | engines: {node: '>= 0.8'} 484 | dependencies: 485 | delayed-stream: 1.0.0 486 | dev: true 487 | 488 | /concat-map@0.0.1: 489 | resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} 490 | dev: true 491 | 492 | /create-require@1.1.1: 493 | resolution: {integrity: sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==} 494 | dev: true 495 | 496 | /delayed-stream@1.0.0: 497 | resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} 498 | engines: {node: '>=0.4.0'} 499 | dev: true 500 | 501 | /diff@4.0.2: 502 | resolution: {integrity: sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==} 503 | engines: {node: '>=0.3.1'} 504 | dev: true 505 | 506 | /dotenv@16.3.1: 507 | resolution: {integrity: sha512-IPzF4w4/Rd94bA9imS68tZBaYyBWSCE47V1RGuMrB94iyTOIEwRmVL2x/4An+6mETpLrKJ5hQkB8W4kFAadeIQ==} 508 | engines: {node: '>=12'} 509 | dev: false 510 | 511 | /dynamic-dedupe@0.3.0: 512 | resolution: {integrity: sha512-ssuANeD+z97meYOqd50e04Ze5qp4bPqo8cCkI4TRjZkzAUgIDTrXV1R8QCdINpiI+hw14+rYazvTRdQrz0/rFQ==} 513 | dependencies: 514 | xtend: 4.0.2 515 | dev: true 516 | 517 | /ecdsa-sig-formatter@1.0.11: 518 | resolution: {integrity: sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==} 519 | dependencies: 520 | safe-buffer: 5.2.1 521 | dev: false 522 | 523 | /esbuild@0.18.6: 524 | resolution: {integrity: sha512-5QgxWaAhU/tPBpvkxUmnFv2YINHuZzjbk0LeUUnC2i3aJHjfi5yR49lgKgF7cb98bclOp/kans8M5TGbGFfJlQ==} 525 | engines: {node: '>=12'} 526 | hasBin: true 527 | requiresBuild: true 528 | optionalDependencies: 529 | '@esbuild/android-arm': 0.18.6 530 | '@esbuild/android-arm64': 0.18.6 531 | '@esbuild/android-x64': 0.18.6 532 | '@esbuild/darwin-arm64': 0.18.6 533 | '@esbuild/darwin-x64': 0.18.6 534 | '@esbuild/freebsd-arm64': 0.18.6 535 | '@esbuild/freebsd-x64': 0.18.6 536 | '@esbuild/linux-arm': 0.18.6 537 | '@esbuild/linux-arm64': 0.18.6 538 | '@esbuild/linux-ia32': 0.18.6 539 | '@esbuild/linux-loong64': 0.18.6 540 | '@esbuild/linux-mips64el': 0.18.6 541 | '@esbuild/linux-ppc64': 0.18.6 542 | '@esbuild/linux-riscv64': 0.18.6 543 | '@esbuild/linux-s390x': 0.18.6 544 | '@esbuild/linux-x64': 0.18.6 545 | '@esbuild/netbsd-x64': 0.18.6 546 | '@esbuild/openbsd-x64': 0.18.6 547 | '@esbuild/sunos-x64': 0.18.6 548 | '@esbuild/win32-arm64': 0.18.6 549 | '@esbuild/win32-ia32': 0.18.6 550 | '@esbuild/win32-x64': 0.18.6 551 | dev: true 552 | 553 | /fflate@0.8.0: 554 | resolution: {integrity: sha512-FAdS4qMuFjsJj6XHbBaZeXOgaypXp8iw/Tpyuq/w3XA41jjLHT8NPA+n7czH/DDhdncq0nAyDZmPeWXh2qmdIg==} 555 | dev: false 556 | 557 | /fill-range@7.0.1: 558 | resolution: {integrity: sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==} 559 | engines: {node: '>=8'} 560 | dependencies: 561 | to-regex-range: 5.0.1 562 | dev: true 563 | 564 | /form-data@3.0.1: 565 | resolution: {integrity: sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg==} 566 | engines: {node: '>= 6'} 567 | dependencies: 568 | asynckit: 0.4.0 569 | combined-stream: 1.0.8 570 | mime-types: 2.1.35 571 | dev: true 572 | 573 | /fs.realpath@1.0.0: 574 | resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} 575 | dev: true 576 | 577 | /fsevents@2.3.2: 578 | resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==} 579 | engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} 580 | os: [darwin] 581 | requiresBuild: true 582 | dev: true 583 | optional: true 584 | 585 | /function-bind@1.1.1: 586 | resolution: {integrity: sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==} 587 | dev: true 588 | 589 | /glob-parent@5.1.2: 590 | resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} 591 | engines: {node: '>= 6'} 592 | dependencies: 593 | is-glob: 4.0.3 594 | dev: true 595 | 596 | /glob@7.2.3: 597 | resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} 598 | dependencies: 599 | fs.realpath: 1.0.0 600 | inflight: 1.0.6 601 | inherits: 2.0.4 602 | minimatch: 3.1.2 603 | once: 1.4.0 604 | path-is-absolute: 1.0.1 605 | dev: true 606 | 607 | /has@1.0.3: 608 | resolution: {integrity: sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==} 609 | engines: {node: '>= 0.4.0'} 610 | dependencies: 611 | function-bind: 1.1.1 612 | dev: true 613 | 614 | /inflight@1.0.6: 615 | resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} 616 | dependencies: 617 | once: 1.4.0 618 | wrappy: 1.0.2 619 | dev: true 620 | 621 | /inherits@2.0.4: 622 | resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} 623 | dev: true 624 | 625 | /is-binary-path@2.1.0: 626 | resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} 627 | engines: {node: '>=8'} 628 | dependencies: 629 | binary-extensions: 2.2.0 630 | dev: true 631 | 632 | /is-core-module@2.12.1: 633 | resolution: {integrity: sha512-Q4ZuBAe2FUsKtyQJoQHlvP8OvBERxO3jEmy1I7hcRXcJBGGHFh/aJBswbXuS9sgrDH2QUO8ilkwNPHvHMd8clg==} 634 | dependencies: 635 | has: 1.0.3 636 | dev: true 637 | 638 | /is-extglob@2.1.1: 639 | resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} 640 | engines: {node: '>=0.10.0'} 641 | dev: true 642 | 643 | /is-glob@4.0.3: 644 | resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} 645 | engines: {node: '>=0.10.0'} 646 | dependencies: 647 | is-extglob: 2.1.1 648 | dev: true 649 | 650 | /is-number@7.0.0: 651 | resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} 652 | engines: {node: '>=0.12.0'} 653 | dev: true 654 | 655 | /jwa@2.0.0: 656 | resolution: {integrity: sha512-jrZ2Qx916EA+fq9cEAeCROWPTfCwi1IVHqT2tapuqLEVVDKFDENFw1oL+MwrTvH6msKxsd1YTDVw6uKEcsrLEA==} 657 | dependencies: 658 | buffer-equal-constant-time: 1.0.1 659 | ecdsa-sig-formatter: 1.0.11 660 | safe-buffer: 5.2.1 661 | dev: false 662 | 663 | /jws@4.0.0: 664 | resolution: {integrity: sha512-KDncfTmOZoOMTFG4mBlG0qUIOlc03fmzH+ru6RgYVZhPkyiy/92Owlt/8UEN+a4TXR1FQetfIpJE8ApdvdVxTg==} 665 | dependencies: 666 | jwa: 2.0.0 667 | safe-buffer: 5.2.1 668 | dev: false 669 | 670 | /make-error@1.3.6: 671 | resolution: {integrity: sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==} 672 | dev: true 673 | 674 | /mime-db@1.52.0: 675 | resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} 676 | engines: {node: '>= 0.6'} 677 | dev: true 678 | 679 | /mime-types@2.1.35: 680 | resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} 681 | engines: {node: '>= 0.6'} 682 | dependencies: 683 | mime-db: 1.52.0 684 | dev: true 685 | 686 | /minimatch@3.1.2: 687 | resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} 688 | dependencies: 689 | brace-expansion: 1.1.11 690 | dev: true 691 | 692 | /minimist@1.2.8: 693 | resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} 694 | dev: true 695 | 696 | /mkdirp@1.0.4: 697 | resolution: {integrity: sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==} 698 | engines: {node: '>=10'} 699 | hasBin: true 700 | dev: true 701 | 702 | /normalize-path@3.0.0: 703 | resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} 704 | engines: {node: '>=0.10.0'} 705 | dev: true 706 | 707 | /once@1.4.0: 708 | resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} 709 | dependencies: 710 | wrappy: 1.0.2 711 | dev: true 712 | 713 | /path-is-absolute@1.0.1: 714 | resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} 715 | engines: {node: '>=0.10.0'} 716 | dev: true 717 | 718 | /path-parse@1.0.7: 719 | resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} 720 | dev: true 721 | 722 | /path-to-regexp@6.2.1: 723 | resolution: {integrity: sha512-JLyh7xT1kizaEvcaXOQwOc2/Yhw6KZOvPf1S8401UyLk86CU79LN3vl7ztXGm/pZ+YjoyAJ4rxmHwbkBXJX+yw==} 724 | dev: false 725 | 726 | /picomatch@2.3.1: 727 | resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} 728 | engines: {node: '>=8.6'} 729 | dev: true 730 | 731 | /readdirp@3.6.0: 732 | resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} 733 | engines: {node: '>=8.10.0'} 734 | dependencies: 735 | picomatch: 2.3.1 736 | dev: true 737 | 738 | /reflect-metadata@0.1.13: 739 | resolution: {integrity: sha512-Ts1Y/anZELhSsjMcU605fU9RE4Oi3p5ORujwbIKXfWa+0Zxs510Qrmrce5/Jowq3cHSZSJqBjypxmHarc+vEWg==} 740 | dev: false 741 | 742 | /resolve@1.22.2: 743 | resolution: {integrity: sha512-Sb+mjNHOULsBv818T40qSPeRiuWLyaGMa5ewydRLFimneixmVy2zdivRl+AF6jaYPC8ERxGDmFSiqui6SfPd+g==} 744 | hasBin: true 745 | dependencies: 746 | is-core-module: 2.12.1 747 | path-parse: 1.0.7 748 | supports-preserve-symlinks-flag: 1.0.0 749 | dev: true 750 | 751 | /rimraf@2.7.1: 752 | resolution: {integrity: sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==} 753 | hasBin: true 754 | dependencies: 755 | glob: 7.2.3 756 | dev: true 757 | 758 | /safe-buffer@5.2.1: 759 | resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} 760 | dev: false 761 | 762 | /source-map-support@0.5.21: 763 | resolution: {integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==} 764 | dependencies: 765 | buffer-from: 1.1.2 766 | source-map: 0.6.1 767 | dev: true 768 | 769 | /source-map@0.6.1: 770 | resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} 771 | engines: {node: '>=0.10.0'} 772 | dev: true 773 | 774 | /strip-bom@3.0.0: 775 | resolution: {integrity: sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==} 776 | engines: {node: '>=4'} 777 | dev: true 778 | 779 | /strip-json-comments@2.0.1: 780 | resolution: {integrity: sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==} 781 | engines: {node: '>=0.10.0'} 782 | dev: true 783 | 784 | /supports-preserve-symlinks-flag@1.0.0: 785 | resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} 786 | engines: {node: '>= 0.4'} 787 | dev: true 788 | 789 | /to-regex-range@5.0.1: 790 | resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} 791 | engines: {node: '>=8.0'} 792 | dependencies: 793 | is-number: 7.0.0 794 | dev: true 795 | 796 | /tree-kill@1.2.2: 797 | resolution: {integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==} 798 | hasBin: true 799 | dev: true 800 | 801 | /ts-node-dev@2.0.0(@types/node@18.0.0)(typescript@5.1.3): 802 | resolution: {integrity: sha512-ywMrhCfH6M75yftYvrvNarLEY+SUXtUvU8/0Z6llrHQVBx12GiFk5sStF8UdfE/yfzk9IAq7O5EEbTQsxlBI8w==} 803 | engines: {node: '>=0.8.0'} 804 | hasBin: true 805 | peerDependencies: 806 | node-notifier: '*' 807 | typescript: '*' 808 | peerDependenciesMeta: 809 | node-notifier: 810 | optional: true 811 | dependencies: 812 | chokidar: 3.5.3 813 | dynamic-dedupe: 0.3.0 814 | minimist: 1.2.8 815 | mkdirp: 1.0.4 816 | resolve: 1.22.2 817 | rimraf: 2.7.1 818 | source-map-support: 0.5.21 819 | tree-kill: 1.2.2 820 | ts-node: 10.9.1(@types/node@18.0.0)(typescript@5.1.3) 821 | tsconfig: 7.0.0 822 | typescript: 5.1.3 823 | transitivePeerDependencies: 824 | - '@swc/core' 825 | - '@swc/wasm' 826 | - '@types/node' 827 | dev: true 828 | 829 | /ts-node@10.9.1(@types/node@18.0.0)(typescript@5.1.3): 830 | resolution: {integrity: sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==} 831 | hasBin: true 832 | peerDependencies: 833 | '@swc/core': '>=1.2.50' 834 | '@swc/wasm': '>=1.2.50' 835 | '@types/node': '*' 836 | typescript: '>=2.7' 837 | peerDependenciesMeta: 838 | '@swc/core': 839 | optional: true 840 | '@swc/wasm': 841 | optional: true 842 | dependencies: 843 | '@cspotcode/source-map-support': 0.8.1 844 | '@tsconfig/node10': 1.0.9 845 | '@tsconfig/node12': 1.0.11 846 | '@tsconfig/node14': 1.0.3 847 | '@tsconfig/node16': 1.0.4 848 | '@types/node': 18.0.0 849 | acorn: 8.9.0 850 | acorn-walk: 8.2.0 851 | arg: 4.1.3 852 | create-require: 1.1.1 853 | diff: 4.0.2 854 | make-error: 1.3.6 855 | typescript: 5.1.3 856 | v8-compile-cache-lib: 3.0.1 857 | yn: 3.1.1 858 | dev: true 859 | 860 | /tsconfig@7.0.0: 861 | resolution: {integrity: sha512-vZXmzPrL+EmC4T/4rVlT2jNVMWCi/O4DIiSj3UHg1OE5kCKbk4mfrXc6dZksLgRM/TZlKnousKH9bbTazUWRRw==} 862 | dependencies: 863 | '@types/strip-bom': 3.0.0 864 | '@types/strip-json-comments': 0.0.30 865 | strip-bom: 3.0.0 866 | strip-json-comments: 2.0.1 867 | dev: true 868 | 869 | /tslib@1.14.1: 870 | resolution: {integrity: sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==} 871 | dev: false 872 | 873 | /tsyringe@4.8.0: 874 | resolution: {integrity: sha512-YB1FG+axdxADa3ncEtRnQCFq/M0lALGLxSZeVNbTU8NqhOVc51nnv2CISTcvc1kyv6EGPtXVr0v6lWeDxiijOA==} 875 | engines: {node: '>= 6.0.0'} 876 | dependencies: 877 | tslib: 1.14.1 878 | dev: false 879 | 880 | /typescript@5.1.3: 881 | resolution: {integrity: sha512-XH627E9vkeqhlZFQuL+UsyAXEnibT0kWR2FWONlr4sTjvxyJYnyefgrkyECLzM5NenmKzRAy2rR/OlYLA1HkZw==} 882 | engines: {node: '>=14.17'} 883 | hasBin: true 884 | dev: true 885 | 886 | /v8-compile-cache-lib@3.0.1: 887 | resolution: {integrity: sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==} 888 | dev: true 889 | 890 | /workerd@1.20230518.0: 891 | resolution: {integrity: sha512-VNmK0zoNZXrwEEx77O/oQDVUzzyDjf5kKKK8bty+FmKCd5EQJCpqi8NlRKWLGMyyYrKm86MFz0kAsreTEs7HHA==} 892 | engines: {node: '>=16'} 893 | hasBin: true 894 | requiresBuild: true 895 | optionalDependencies: 896 | '@cloudflare/workerd-darwin-64': 1.20230518.0 897 | '@cloudflare/workerd-darwin-arm64': 1.20230518.0 898 | '@cloudflare/workerd-linux-64': 1.20230518.0 899 | '@cloudflare/workerd-linux-arm64': 1.20230518.0 900 | '@cloudflare/workerd-windows-64': 1.20230518.0 901 | dev: false 902 | 903 | /wrappy@1.0.2: 904 | resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} 905 | dev: true 906 | 907 | /xtend@4.0.2: 908 | resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==} 909 | engines: {node: '>=0.4'} 910 | dev: true 911 | 912 | /yn@3.1.1: 913 | resolution: {integrity: sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==} 914 | engines: {node: '>=6'} 915 | dev: true 916 | 917 | /zod@3.21.4: 918 | resolution: {integrity: sha512-m46AKbrzKVzOzs/DZgVnG5H55N1sv1M8qZU3A8RIKbs3mrACDNeIOeilDymVb2HdmP8uwshOCF4uJ8uM9rCqJw==} 919 | dev: false 920 | -------------------------------------------------------------------------------- /pnpm-workspace.yaml: -------------------------------------------------------------------------------- 1 | workspaces: 2 | - publisher/ 3 | - worker/ 4 | -------------------------------------------------------------------------------- /publisher/.env.example: -------------------------------------------------------------------------------- 1 | PORT=3000 2 | FLY_AUTH_TOKEN=- 3 | FLY_WORKER_APP=self-faas-worker 4 | DATA_PATH=/data 5 | -------------------------------------------------------------------------------- /publisher/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:18-alpine 2 | 3 | 4 | # 5 | # Build 6 | # 7 | 8 | RUN npm install -g pnpm@8.6 9 | 10 | WORKDIR /app 11 | 12 | COPY package.json . 13 | COPY pnpm-*.yaml . 14 | RUN pnpm fetch --ignore-scripts 15 | 16 | COPY publisher/package.json publisher/ 17 | RUN pnpm install --offline --frozen-lockfile 18 | 19 | COPY tsconfig* . 20 | COPY publisher/tsconfig* publisher/ 21 | COPY publisher/src/ publisher/src 22 | 23 | RUN pnpm run --filter @self-workerd/publisher build 24 | 25 | # 26 | # Build final image 27 | # 28 | 29 | FROM node:18-alpine 30 | ENV NODE_ENV=production 31 | RUN npm install -g pnpm@8.6 32 | 33 | WORKDIR /app 34 | 35 | COPY --from=0 /app/package.json . 36 | COPY --from=0 /app/pnpm-*.yaml . 37 | COPY --from=0 /app/publisher/package.json publisher/ 38 | 39 | RUN pnpm install --frozen-lockfile --prod 40 | COPY publisher/config.template.capnp publisher/ 41 | COPY --from=0 /app/publisher/dist/ publisher/dist 42 | 43 | WORKDIR /app/publisher 44 | 45 | EXPOSE 3000 46 | ENTRYPOINT [ "pnpm", "start" ] 47 | -------------------------------------------------------------------------------- /publisher/api.http: -------------------------------------------------------------------------------- 1 | @host = http://localhost:{{$dotenv PORT}} 2 | 3 | ### publish call 4 | POST {{host}}/publish 5 | Content-Type: application/json 6 | 7 | { 8 | "name": "test", 9 | "module": "export default { fetch(){ return new Response(JSON.stringify({test:true})); }}" 10 | } 11 | 12 | ### publish call - 2 13 | POST {{host}}/publish 14 | Content-Type: application/json 15 | 16 | { 17 | "name": "test2", 18 | "module": "export default { fetch(){ return new Response(JSON.stringify({test2:false})); }}" 19 | } 20 | 21 | 22 | ### getConfig call 23 | GET {{host}}/getConfig 24 | -------------------------------------------------------------------------------- /publisher/config.template.capnp: -------------------------------------------------------------------------------- 1 | using Workerd = import "/workerd/workerd.capnp"; 2 | 3 | const config :Workerd.Config = ( 4 | services = [ 5 | (name = "router", worker = .router), 6 | ], 7 | 8 | sockets = [ ( name = "http", address = "*:8080", http = (), service = "router" ) ], 9 | ); 10 | 11 | const router :Workerd.Worker = ( 12 | compatibilityDate = "2023-02-28", 13 | modules = [(name = "router.js", esModule = embed "_router.js")], 14 | bindings = [ 15 | (name = "_config", json = embed "_meta.json"), 16 | ], 17 | ); 18 | -------------------------------------------------------------------------------- /publisher/fly.toml: -------------------------------------------------------------------------------- 1 | # fly.toml app configuration file generated for self-workerd-publisher on 2023-06-27T17:07:42+02:00 2 | # 3 | # See https://fly.io/docs/reference/configuration/ for information about how to use this file. 4 | # 5 | 6 | app = "self-workerd-publisher" 7 | kill_signal = "SIGINT" 8 | kill_timeout = "5s" 9 | primary_region = "ams" 10 | 11 | [build] 12 | image = "self-workerd-publisher" 13 | 14 | [env] 15 | DATA_PATH = "/data" 16 | FLY_WORKER_APP = "self-workerd-worker" 17 | PORT = "3000" 18 | 19 | [[mounts]] 20 | destination = "/data" 21 | source = "self_workerd_data" 22 | 23 | [[services]] 24 | internal_port = 3000 25 | processes = ["app"] 26 | protocol = "tcp" 27 | 28 | [[services.ports]] 29 | force_https = true 30 | handlers = ["http"] 31 | port = 80 32 | 33 | [[services.ports]] 34 | handlers = ["tls", "http"] 35 | port = 443 36 | [services.concurrency] 37 | hard_limit = 250 38 | soft_limit = 200 39 | type = "connections" 40 | 41 | [[services.tcp_checks]] 42 | grace_period = "1s" 43 | interval = "15s" 44 | restart_limit = 0 45 | timeout = "2s" 46 | -------------------------------------------------------------------------------- /publisher/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@self-workerd/publisher", 3 | "version": "0.0.1", 4 | "scripts": { 5 | "start": "node dist/index.js", 6 | "build": "tsc -p tsconfig.build.json", 7 | "dev": "ts-node-dev -T src/index.ts", 8 | "dev:debug": "ts-node-dev --inspect -T src/index.ts" 9 | }, 10 | "dependencies": { 11 | "@httpc/kit": "0.2.0-pre20230629031731", 12 | "dotenv": "^16.3.1", 13 | "fflate": "^0.8.0", 14 | "reflect-metadata": "^0.1.13", 15 | "tsyringe": "^4.8.0", 16 | "zod": "^3.21.4" 17 | }, 18 | "devDependencies": { 19 | "@types/node": "^18", 20 | "@types/node-fetch": "^2.6.4", 21 | "ts-node-dev": "^2.0.0", 22 | "typescript": "^5.1.3" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /publisher/src/calls/getConfig.ts: -------------------------------------------------------------------------------- 1 | import fs from "fs/promises"; 2 | import path from "path"; 3 | import { Authenticated, httpCall, useInjected } from "@httpc/kit"; 4 | import { strToU8, zipSync } from "fflate"; 5 | import { WorkerConfig, WorkerDefinition } from "./models"; 6 | 7 | 8 | export const getConfig = httpCall( 9 | // Authenticated("role:system"), // disabled for now :) 10 | async () => { 11 | 12 | // 1- read all worker definitions 13 | // 2- create workerd config.capnp file and js 14 | // 3- zip all and return 15 | 16 | const dataPath = useInjected("ENV:DATA_PATH"); 17 | let capnp = await fs.readFile("config.template.capnp", "utf8"); 18 | 19 | const modules = new Map(); 20 | const config: WorkerConfig = { 21 | routes: [] 22 | }; 23 | 24 | for (const file of await fs.readdir(dataPath)) { 25 | if (!file.endsWith(".json")) continue; 26 | 27 | const definition: WorkerDefinition = JSON.parse(await fs.readFile(path.join(dataPath, file), "utf8")); 28 | 29 | config.routes.push(definition.name); 30 | modules.set(`${definition.name}.js`, strToU8(definition.module)); 31 | 32 | 33 | // generate capnp workerd config 34 | // 1- append workerd config 35 | // 2- define service 36 | // 3- add a binding to the router service 37 | capnp += ` 38 | const ${definition.name} :Workerd.Worker = ( 39 | compatibilityDate = "${definition.compatibilityDate || "2023-02-28"}", 40 | modules = [(name = "${definition.name}.js", esModule = embed "${definition.name}.js")], 41 | );`; 42 | 43 | const serviceStr = "services = ["; 44 | const serviceIdx = capnp.indexOf(serviceStr); 45 | capnp = capnp.substring(0, serviceIdx + serviceStr.length) + 46 | `\n (name = "${definition.name}", worker = .${definition.name}),` + 47 | capnp.substring(serviceIdx + serviceStr.length); 48 | 49 | const bindingStr = "bindings = ["; 50 | const bindingIdx = capnp.indexOf(bindingStr); 51 | capnp = capnp.substring(0, bindingIdx + bindingStr.length) + 52 | `\n (name = "${definition.name}", service = "${definition.name}"),` + 53 | capnp.substring(bindingIdx + bindingStr.length); 54 | } 55 | 56 | return zipSync({ 57 | ...Object.fromEntries(modules), 58 | "_meta.json": strToU8(JSON.stringify(config)), 59 | "config.capnp": strToU8(capnp) 60 | }, { 61 | level: 9 62 | }); 63 | } 64 | ); 65 | -------------------------------------------------------------------------------- /publisher/src/calls/index.ts: -------------------------------------------------------------------------------- 1 | import { publish } from "./publish"; 2 | import { getConfig } from "./getConfig"; 3 | 4 | 5 | /** 6 | * The two operations for the Publisher 7 | * - publish -> add a new worker 8 | * - getConfig -> get a zip for all worker defs 9 | */ 10 | 11 | export default { 12 | publish, 13 | getConfig, 14 | } 15 | -------------------------------------------------------------------------------- /publisher/src/calls/models.ts: -------------------------------------------------------------------------------- 1 | import { z } from "zod"; 2 | 3 | 4 | export const WorkerDefinitionSchema = z.object({ 5 | name: z.string().min(4).max(20).regex(/^[a-zA-Z0-9-_]+$/), 6 | module: z.string().min(20), 7 | flags: z.array(z.string().min(2).max(30)).optional(), 8 | compatibilityDate: z.string().regex(/^\d{4}-\d{2}-\d{2}$/).optional(), 9 | }) 10 | 11 | export type WorkerDefinition = z.infer; 12 | 13 | 14 | export type WorkerConfig = { 15 | routes: string[] 16 | } 17 | -------------------------------------------------------------------------------- /publisher/src/calls/publish.ts: -------------------------------------------------------------------------------- 1 | import fs from "fs/promises"; 2 | import path from "path"; 3 | import { httpCall, useInjected, useLogger, Authenticated, Validate } from "@httpc/kit"; 4 | import { WorkerDefinition, WorkerDefinitionSchema } from "./models"; 5 | 6 | 7 | export const publish = httpCall( 8 | // Authenticated("role:user"), // disabled for now :) 9 | Validate(WorkerDefinitionSchema), 10 | async (definition: WorkerDefinition) => { 11 | 12 | // steps 13 | // 1- write the definition 14 | // 2- restart all running workers 15 | 16 | const logger = useLogger(); 17 | 18 | const dataPath = useInjected("ENV:DATA_PATH"); 19 | await fs.mkdir(dataPath, { recursive: true }); 20 | await fs.writeFile(path.join(dataPath, definition.name + ".json"), JSON.stringify(definition), "utf-8"); 21 | 22 | logger.info("Published worker %s", definition.name); 23 | 24 | // run restart in the bg 25 | // we don't need to wait to be completed 26 | Promise.resolve().then(async () => { 27 | const workerApp = useInjected("ENV:FLY_WORKER_APP"); 28 | logger.verbose("Restarting app %s", workerApp); 29 | 30 | const machines = await flyApi(`/v1/apps/${workerApp}/machines`); 31 | 32 | for (const machine of machines) { 33 | // skip non started machine 34 | if (machine.state !== "started") continue; 35 | 36 | logger.verbose("Machine %s: restarting", machine.id); 37 | 38 | await flyApi(`/v1/apps/${workerApp}/machines/${machine.id}/stop`, null); 39 | await new Promise(r => setTimeout(r, 2000)); 40 | await flyApi(`/v1/apps/${workerApp}/machines/${machine.id}/start`, null); 41 | 42 | logger.verbose("Machine %s: restarted", machine.id); 43 | } 44 | }).catch(err => { 45 | logger.error(err); 46 | }); 47 | 48 | return { success: true }; 49 | } 50 | ); 51 | 52 | 53 | async function flyApi(path: string, data?: any) { 54 | const endpoint = "http://_api.internal:4280"; 55 | const flyToken = useInjected("ENV:FLY_AUTH_TOKEN"); 56 | 57 | const response = await fetch(`${endpoint}${path}`, { 58 | method: arguments.length === 1 ? "GET" : "POST", 59 | headers: { 60 | authorization: `Bearer ${flyToken}`, 61 | ...data !== undefined ? { "content-type": "application/json" } : undefined, 62 | }, 63 | body: data !== undefined && data !== null ? JSON.stringify(data) : undefined 64 | }); 65 | 66 | return await response.json(); 67 | } 68 | -------------------------------------------------------------------------------- /publisher/src/global.d.ts: -------------------------------------------------------------------------------- 1 | import type _fetch from "node-fetch"; 2 | 3 | declare global { 4 | export const fetch: typeof _fetch; 5 | export const { 6 | Headers, 7 | Request, 8 | Response, 9 | }: typeof import("node-fetch"); 10 | 11 | 12 | 13 | interface EnvVariableTypes { 14 | DATA_PATH: string; 15 | FLY_WORKER_APP: string; 16 | FLY_AUTH_TOKEN: string; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /publisher/src/index.ts: -------------------------------------------------------------------------------- 1 | import "reflect-metadata"; 2 | import "dotenv/config"; 3 | import { Application } from "@httpc/kit"; 4 | import "@httpc/kit/validation-zod"; 5 | import calls from "./calls"; 6 | 7 | 8 | const app = new Application({ 9 | port: Number(process.env.PORT) || 3000, 10 | cors: true, 11 | calls, 12 | }); 13 | 14 | app.registerEnv({ 15 | DATA_PATH: "required", 16 | FLY_AUTH_TOKEN: "required", 17 | FLY_WORKER_APP: "required", 18 | }); 19 | 20 | app.initialize().then(() => { 21 | app.start(); 22 | }); 23 | -------------------------------------------------------------------------------- /publisher/tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "./dist", 5 | "noEmit": false, 6 | "sourceMap": false, 7 | "declaration": false 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /publisher/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.base.json", 3 | "compilerOptions": { 4 | "lib": [ 5 | "esnext" 6 | ], 7 | "target": "esnext", 8 | "module": "commonjs", 9 | "moduleResolution": "node" 10 | }, 11 | "include": [ 12 | "src" 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /tsconfig.base.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2020", 4 | "allowJs": false, 5 | "skipLibCheck": true, 6 | "strict": true, 7 | "noImplicitAny": true, 8 | "noImplicitThis": true, 9 | "noFallthroughCasesInSwitch": false, 10 | "isolatedModules": true, 11 | "emitDecoratorMetadata": true, 12 | "experimentalDecorators": true, 13 | "allowSyntheticDefaultImports": true, 14 | "forceConsistentCasingInFileNames": true, 15 | "useUnknownInCatchVariables": false, 16 | "esModuleInterop": true, 17 | "resolveJsonModule": true, 18 | "module": "ESNext", 19 | "moduleResolution": "node", 20 | "sourceMap": false, 21 | "noEmit": true 22 | }, 23 | "watchOptions": { 24 | "watchFile": "useFsEvents", 25 | "watchDirectory": "useFsEvents" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /worker/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:18-slim 2 | 3 | 4 | # 5 | # Build 6 | # 7 | 8 | RUN apt-get update && \ 9 | DEBIAN_FRONTEND=noninteractive apt-get install -qy libc++1 && \ 10 | apt-get clean 11 | 12 | RUN npm install -g pnpm@8.6 13 | 14 | WORKDIR /app 15 | 16 | COPY package.json . 17 | COPY pnpm-*.yaml . 18 | RUN pnpm fetch 19 | 20 | # COPY workerd to root 21 | RUN find node_modules/ -wholename "*cloudflare/workerd*/bin/workerd*" -type f | xargs cp -t . 22 | 23 | COPY worker/package.json worker/ 24 | RUN pnpm install --offline --frozen-lockfile 25 | 26 | COPY tsconfig* . 27 | COPY worker/tsconfig* worker/ 28 | COPY worker/src/ worker/src 29 | COPY worker/tasks/ worker/tasks 30 | 31 | RUN pnpm run --filter @self-workerd/worker build 32 | 33 | 34 | # 35 | # Build final image 36 | # 37 | FROM debian:12-slim 38 | RUN apt-get update && \ 39 | DEBIAN_FRONTEND=noninteractive apt-get install -qy libc++1 curl unzip && \ 40 | apt-get clean 41 | 42 | WORKDIR /app 43 | 44 | COPY worker/entrypoint.sh . 45 | COPY --from=0 /app/worker/dist config 46 | COPY --from=0 /app/workerd . 47 | 48 | EXPOSE 3000 49 | 50 | ENV PUBLISHER_ENDPOINT http://host.docker.internal:3001 51 | ENTRYPOINT [ "./entrypoint.sh" ] 52 | -------------------------------------------------------------------------------- /worker/entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | 4 | if [[ $# -gt 0 ]]; then 5 | # If we pass a command, run it 6 | exec "$@" 7 | exit 0 8 | fi 9 | 10 | 11 | curl -o config.zip $PUBLISHER_ENDPOINT/getConfig 12 | unzip config.zip -d ./config 13 | 14 | ./workerd serve ./config/config.capnp 15 | -------------------------------------------------------------------------------- /worker/fly.toml: -------------------------------------------------------------------------------- 1 | # fly.toml app configuration file generated for self-workerd-worker on 2023-06-27T17:22:02+02:00 2 | # 3 | # See https://fly.io/docs/reference/configuration/ for information about how to use this file. 4 | # 5 | 6 | app = "self-workerd-worker" 7 | kill_signal = "SIGTERM" 8 | kill_timeout = "5s" 9 | primary_region = "ams" 10 | 11 | [build] 12 | image = "self-workerd-worker" 13 | 14 | [env] 15 | PUBLISHER_ENDPOINT = "http://self-workerd-publisher.internal:3000" 16 | 17 | [[services]] 18 | auto_start_machines = true 19 | auto_stop_machines = true 20 | internal_port = 8080 21 | min_machines_running = 1 22 | processes = ["app"] 23 | protocol = "tcp" 24 | 25 | [[services.ports]] 26 | force_https = true 27 | handlers = ["http"] 28 | port = 80 29 | 30 | [[services.ports]] 31 | handlers = ["tls", "http"] 32 | port = 443 33 | [services.concurrency] 34 | hard_limit = 250 35 | soft_limit = 200 36 | type = "connections" 37 | 38 | [[services.tcp_checks]] 39 | grace_period = "1s" 40 | interval = "15s" 41 | restart_limit = 0 42 | timeout = "2s" 43 | -------------------------------------------------------------------------------- /worker/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@self-workerd/worker", 3 | "version": "0.0.1", 4 | "type": "module", 5 | "scripts": { 6 | "start:worker": "workerd serve ./dist/config.capnp --verbose", 7 | "build": "node tasks/build.js" 8 | }, 9 | "engines": { 10 | "node": ">=18" 11 | }, 12 | "devDependencies": { 13 | "@cloudflare/workers-types": "^4.20230518.0", 14 | "esbuild": "^0.18.6" 15 | }, 16 | "dependencies": { 17 | "workerd": "^1.20230518.0" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /worker/src/router.ts: -------------------------------------------------------------------------------- 1 | 2 | 3 | type RouterConfig = { 4 | routes: string[] 5 | } 6 | 7 | type Env = { 8 | // own binding 9 | _config: RouterConfig 10 | } & { 11 | // the actual workers 12 | [key: string]: Fetcher 13 | } 14 | 15 | export default { 16 | fetch(request, env) { 17 | const config = env._config; 18 | 19 | // extract the function path /$function-name/...splat 20 | // function name is case insensitive 21 | const [, route] = new URL(request.url).pathname.toLowerCase().split("/"); 22 | 23 | // function not specified or not found 24 | if (!route || !config.routes.includes(route)) { 25 | return new Response(null, { status: 404 }); 26 | } 27 | 28 | // worker not loaded 29 | // something wrong internally 30 | const worker = env[route]; 31 | if (!worker || typeof worker.fetch !== "function") { 32 | return new Response(null, { status: 500 }); 33 | } 34 | 35 | // forward the request to the worker 36 | return worker.fetch(request); 37 | } 38 | } satisfies ExportedHandler 39 | -------------------------------------------------------------------------------- /worker/tasks/build.js: -------------------------------------------------------------------------------- 1 | import fs from "fs/promises"; 2 | import esbuild from "esbuild"; 3 | 4 | 5 | await fs.rm("./dist", { force: true, recursive: true }); 6 | 7 | await Promise.all([ 8 | esbuild.build({ 9 | entryPoints: ["./src/router.ts"], 10 | outfile: "./dist/_router.js", 11 | format: "esm", 12 | target: "esnext", 13 | bundle: true, 14 | minify: true, 15 | }), 16 | ]); 17 | -------------------------------------------------------------------------------- /worker/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.base.json", 3 | "compilerOptions": { 4 | "outDir": "./dist", 5 | "lib": [ 6 | "esnext" 7 | ], 8 | "target": "esnext", 9 | "module": "esnext", 10 | "moduleResolution": "bundler", 11 | "types": [ 12 | "@cloudflare/workers-types" 13 | ] 14 | }, 15 | "include": [ 16 | "src" 17 | ] 18 | } 19 | --------------------------------------------------------------------------------