From 3ab8173fd130e77323793ebcb04b40419978661d Mon Sep 17 00:00:00 2001 From: Will Taylor Date: Wed, 20 Nov 2024 11:04:18 -0500 Subject: [PATCH] Modify router-worker logic to allow for running user-worker ahead of assets - Adds logic to run user-worker ahead of assets - Moves vitest from worker-shared root into asset-worker - Creates new vitest unit suite for router-worker --- .changeset/funny-dingos-guess.md | 5 + .../{ => asset-worker}/vitest.config.mts | 4 +- packages/workers-shared/package.json | 7 +- .../router-worker/__tests__/index.test.ts | 66 +++++++++++ .../router-worker/__tests__/tsconfig.json | 15 +++ .../workers-shared/router-worker/src/index.ts | 13 +++ .../router-worker/vitest.config.mts | 13 +++ .../router-worker/wrangler.toml | 1 + packages/workers-shared/tsconfig.json | 2 +- packages/workers-shared/utils/types.ts | 1 + pnpm-lock.yaml | 109 ++++++++++++++++++ 11 files changed, 231 insertions(+), 5 deletions(-) create mode 100644 .changeset/funny-dingos-guess.md rename packages/workers-shared/{ => asset-worker}/vitest.config.mts (60%) create mode 100644 packages/workers-shared/router-worker/__tests__/index.test.ts create mode 100644 packages/workers-shared/router-worker/__tests__/tsconfig.json create mode 100644 packages/workers-shared/router-worker/vitest.config.mts diff --git a/.changeset/funny-dingos-guess.md b/.changeset/funny-dingos-guess.md new file mode 100644 index 000000000000..aaed92cb88dd --- /dev/null +++ b/.changeset/funny-dingos-guess.md @@ -0,0 +1,5 @@ +--- +"@cloudflare/workers-shared": minor +--- + +Option to invoke user worker ahead of assets diff --git a/packages/workers-shared/vitest.config.mts b/packages/workers-shared/asset-worker/vitest.config.mts similarity index 60% rename from packages/workers-shared/vitest.config.mts rename to packages/workers-shared/asset-worker/vitest.config.mts index 363c185f0a91..19d3d456b85a 100644 --- a/packages/workers-shared/vitest.config.mts +++ b/packages/workers-shared/asset-worker/vitest.config.mts @@ -1,11 +1,11 @@ import { defineProject, mergeConfig } from "vitest/config"; -import configShared from "../../vitest.shared"; +import configShared from "../../../vitest.shared.js"; export default mergeConfig( configShared, defineProject({ test: { - include: ["asset-worker/tests/**.{test,spec}.{ts,js}"], + include: ["tests/**.{test,spec}.{ts,js}"], globals: true, }, }) diff --git a/packages/workers-shared/package.json b/packages/workers-shared/package.json index 608104f3eb8f..3b24b5ead448 100644 --- a/packages/workers-shared/package.json +++ b/packages/workers-shared/package.json @@ -36,8 +36,10 @@ "deploy:asset-worker": "CLOUDFLARE_API_TOKEN=$WORKERS_DEPLOY_AND_CONFIG_CLOUDFLARE_API_TOKEN pnpx wrangler versions upload --experimental-versions -c asset-worker/wrangler.toml", "deploy:router-worker": "CLOUDFLARE_API_TOKEN=$WORKERS_DEPLOY_AND_CONFIG_CLOUDFLARE_API_TOKEN pnpx wrangler versions upload --experimental-versions -c router-worker/wrangler.toml", "dev": "pnpm run clean && concurrently -n bundle:asset-worker,bundle:router-worker -c blue,magenta \"pnpm run bundle:asset-worker --watch\" \"pnpm run bundle:router-worker --watch\"", - "test": "vitest", - "test:ci": "vitest run", + "test": "CI=true concurrently --group -n router-worker,asset-worker \"pnpm run test:router-worker\" \"pnpm run test:asset-worker\"", + "test:asset-worker": "vitest -c asset-worker/vitest.config.mts --dir asset-worker", + "test:ci": "pnpm run test", + "test:router-worker": "vitest -c router-worker/vitest.config.mts --dir router-worker", "types:emit": "tsc index.ts --declaration --emitDeclarationOnly --declarationDir ./dist" }, "dependencies": { @@ -46,6 +48,7 @@ }, "devDependencies": { "@cloudflare/eslint-config-worker": "workspace:*", + "@cloudflare/vitest-pool-workers": "latest", "@cloudflare/workers-tsconfig": "workspace:*", "@cloudflare/workers-types": "^4.20241106.0", "@types/mime": "^3.0.4", diff --git a/packages/workers-shared/router-worker/__tests__/index.test.ts b/packages/workers-shared/router-worker/__tests__/index.test.ts new file mode 100644 index 000000000000..157c2fe4138a --- /dev/null +++ b/packages/workers-shared/router-worker/__tests__/index.test.ts @@ -0,0 +1,66 @@ +import { createExecutionContext } from "cloudflare:test"; +import { describe, expect, it } from "vitest"; +import { default as worker } from "../src/index"; + +describe("unit tests", async () => { + it("fails if specify running user worker ahead of assets, without user worker", async () => { + const request = new Request("https://example.com"); + const ctx = createExecutionContext(); + + const env = { + CONFIG: { + invoke_user_worker_ahead_of_assets: true, + has_user_worker: false, + }, + } as typeof env; + + void expect( + async () => await worker.fetch(request, env, ctx) + ).rejects.toThrowError( + "Fetch for user worker without having a user worker binding" + ); + }); + + it("it returns fetch from user worker when invoke_user_worker_ahead_of_assets true", async () => { + const request = new Request("https://example.com"); + const ctx = createExecutionContext(); + + const env = { + CONFIG: { + invoke_user_worker_ahead_of_assets: true, + has_user_worker: true, + }, + USER_WORKER: { + async fetch(_: Request): Promise { + return new Response("hello from user worker"); + }, + }, + } as typeof env; + + const response = await worker.fetch(request, env, ctx); + expect(await response.text()).toEqual("hello from user worker"); + }); + + it("it returns fetch from asset worker when matching existing asset path", async () => { + const request = new Request("https://example.com"); + const ctx = createExecutionContext(); + + const env = { + CONFIG: { + invoke_user_worker_ahead_of_assets: false, + has_user_worker: false, + }, + ASSET_WORKER: { + async fetch(_: Request): Promise { + return new Response("hello from asset worker"); + }, + async unstable_canFetch(_: Request): Promise { + return true; + }, + }, + } as typeof env; + + const response = await worker.fetch(request, env, ctx); + expect(await response.text()).toEqual("hello from asset worker"); + }); +}); diff --git a/packages/workers-shared/router-worker/__tests__/tsconfig.json b/packages/workers-shared/router-worker/__tests__/tsconfig.json new file mode 100644 index 000000000000..48f509607b68 --- /dev/null +++ b/packages/workers-shared/router-worker/__tests__/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "ES2020", + "lib": ["ES2020"], + "types": [ + "@cloudflare/workers-types/experimental", + "@cloudflare/vitest-pool-workers" + ], + "moduleResolution": "bundler", + "noEmit": true, + "skipLibCheck": true + }, + "include": ["**/*.ts"] +} diff --git a/packages/workers-shared/router-worker/src/index.ts b/packages/workers-shared/router-worker/src/index.ts index 9528a7dc7c7e..ccb412adbb0a 100644 --- a/packages/workers-shared/router-worker/src/index.ts +++ b/packages/workers-shared/router-worker/src/index.ts @@ -56,6 +56,19 @@ export default { } const maybeSecondRequest = request.clone(); + + // User's configuration indicates they want user-Worker to run ahead of any + // assets. Do not provide any fallback logic. + if (env.CONFIG.invoke_user_worker_ahead_of_assets) { + if (!env.CONFIG.has_user_worker) { + throw new Error( + "Fetch for user worker without having a user worker binding" + ); + } + return env.USER_WORKER.fetch(maybeSecondRequest); + } + + // Otherwise, we try to first fetch assets, falling back to user-Worker. if (env.CONFIG.has_user_worker) { if (await env.ASSET_WORKER.unstable_canFetch(request)) { analytics.setData({ dispatchtype: DISPATCH_TYPE.ASSETS }); diff --git a/packages/workers-shared/router-worker/vitest.config.mts b/packages/workers-shared/router-worker/vitest.config.mts new file mode 100644 index 000000000000..4f94cd4886ed --- /dev/null +++ b/packages/workers-shared/router-worker/vitest.config.mts @@ -0,0 +1,13 @@ +import { defineWorkersConfig } from "@cloudflare/vitest-pool-workers/config"; + +export default defineWorkersConfig({ + test: { + poolOptions: { + workers: { + wrangler: { + configPath: "./wrangler.toml", + }, + }, + }, + }, +}); diff --git a/packages/workers-shared/router-worker/wrangler.toml b/packages/workers-shared/router-worker/wrangler.toml index d96e0418f1e0..b1c7ce0a453e 100644 --- a/packages/workers-shared/router-worker/wrangler.toml +++ b/packages/workers-shared/router-worker/wrangler.toml @@ -12,6 +12,7 @@ account_id = "0f1b8aa119a907021f659042f95ea9ba" workers_dev = false main = "src/index.ts" compatibility_date = "2024-07-31" +compatibility_flags = ["nodejs_compat"] [version_metadata] binding = "VERSION_METADATA" diff --git a/packages/workers-shared/tsconfig.json b/packages/workers-shared/tsconfig.json index e112be387120..9ee2befe1743 100644 --- a/packages/workers-shared/tsconfig.json +++ b/packages/workers-shared/tsconfig.json @@ -5,7 +5,7 @@ "sourceMap": true, "forceConsistentCasingInFileNames": true, "useUnknownInCatchVariables": false, - "types": ["@cloudflare/workers-types/experimental"] + "types": ["@cloudflare/workers-types/experimental", "@types/node"] }, "include": ["**/*.ts", "vitest.config.mts"], "exclude": ["node_modules", "dist", "**/tests", "**/*.test.ts"] diff --git a/packages/workers-shared/utils/types.ts b/packages/workers-shared/utils/types.ts index 42422e9b8e1b..c415d13c0b7c 100644 --- a/packages/workers-shared/utils/types.ts +++ b/packages/workers-shared/utils/types.ts @@ -2,6 +2,7 @@ import { z } from "zod"; export const RoutingConfigSchema = z.object({ has_user_worker: z.boolean().optional(), + invoke_user_worker_ahead_of_assets: z.boolean().optional(), }); export const AssetConfigSchema = z.object({ diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 73acc46b8003..f6da52a37279 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1576,6 +1576,9 @@ importers: '@cloudflare/eslint-config-worker': specifier: workspace:* version: link:../eslint-config-worker + '@cloudflare/vitest-pool-workers': + specifier: latest + version: 0.5.29(@cloudflare/workers-types@4.20241106.0)(@vitest/runner@2.1.3)(@vitest/snapshot@2.1.3)(vitest@2.1.3) '@cloudflare/workers-tsconfig': specifier: workspace:* version: link:../workers-tsconfig @@ -2414,6 +2417,10 @@ packages: peerDependencies: react: ^15.0.0-0 || ^16.0.0-0 || ^17.0.0-0 + '@cloudflare/kv-asset-handler@0.3.4': + resolution: {integrity: sha512-YLPHc8yASwjNkmcDMQMY35yiWjoKAKnhUbPRszBRS0YgH+IXtsMp61j+yTcnCE3oO2DgP0U3iejLC8FTtKDC8Q==} + engines: {node: '>=16.13'} + '@cloudflare/style-const@5.7.3': resolution: {integrity: sha512-N9Y8bcFXoO7htm+sSVsBmQOVbjLeEY2hy1CBmvt0AoH1zWvs3izwJrnlL0ee4kJ6DkyjaY6SIAkUGUtTOApF3Q==} peerDependencies: @@ -2453,6 +2460,13 @@ packages: '@cloudflare/util-markdown@1.2.15': resolution: {integrity: sha512-H8q/Msk+9Fga6iqqmff7i4mi+kraBCQWFbMEaKIRq3+HBNN5gkpizk05DSG6iIHVxCG1M3WR1FkN9CQ0ZtK4Cw==} + '@cloudflare/vitest-pool-workers@0.5.29': + resolution: {integrity: sha512-UFRT/pUWj8O7FDrdKowjucaiMukMg6mwaDhbXU9FgZvnwhS958Y6TxjMZoUY/5UMp9vNY6wF1V1WeUfkBiB74Q==} + peerDependencies: + '@vitest/runner': 2.0.x - 2.1.x + '@vitest/snapshot': 2.0.x - 2.1.x + vitest: 2.0.x - 2.1.x + '@cloudflare/workerd-darwin-64@1.20241106.1': resolution: {integrity: sha512-zxvaToi1m0qzAScrxFt7UvFVqU8DxrCO2CinM1yQkv5no7pA1HolpIrwZ0xOhR3ny64Is2s/J6BrRjpO5dM9Zw==} engines: {node: '>=16'} @@ -2483,6 +2497,10 @@ packages: cpu: [x64] os: [win32] + '@cloudflare/workers-shared@0.7.1': + resolution: {integrity: sha512-46cP5FCrl3TrvHeoHLb5SRuiDMKH5kc9Yvo36SAfzt8dqJI/qJRoY1GP3ioHn/gP7v2QIoUOTAzIl7Ml7MnfrA==} + engines: {node: '>=16.7.0'} + '@cloudflare/workers-types@4.20241106.0': resolution: {integrity: sha512-pI4ivacmp+vgNO/siHDsZ6BdITR0LC4Mh/1+yzVLcl9U75pt5DUDCOWOiqIRFXRq6H65DPnJbEPFo3x9UfgofQ==} @@ -6304,6 +6322,11 @@ packages: resolution: {integrity: sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==} engines: {node: '>=4'} + miniflare@3.20241106.0: + resolution: {integrity: sha512-PjOoJKjUUofCueQskfhXlGvvHxZj36UAJAp1DnquMK88MFF50zCULblh0KXMSNM+bXeQYA94Gj06a7kfmBGxPw==} + engines: {node: '>=16.13'} + hasBin: true + minimatch@10.0.1: resolution: {integrity: sha512-ethXTt3SGGR+95gudmqJ1eNhRO7eGEGIgYA9vnPatK4/etz2MEVDno5GMCibdMTuBMyElzIlgxMna3K94XDIDQ==} engines: {node: 20 || >=22} @@ -8314,6 +8337,16 @@ packages: engines: {node: '>=16'} hasBin: true + wrangler@3.88.0: + resolution: {integrity: sha512-1yM5cgerjKkIBL9GOzIWhl8MsyEGNTsN4emJCiLLHJFz52TSNjJJlMbd1x0hX351mfy2MsGgUqKcx8iRL51PcQ==} + engines: {node: '>=16.17.0'} + hasBin: true + peerDependencies: + '@cloudflare/workers-types': ^4.20241106.0 + peerDependenciesMeta: + '@cloudflare/workers-types': + optional: true + wrap-ansi@6.2.0: resolution: {integrity: sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==} engines: {node: '>=8'} @@ -9181,6 +9214,10 @@ snapshots: dependencies: react: 18.3.1 + '@cloudflare/kv-asset-handler@0.3.4': + dependencies: + mime: 3.0.0 + '@cloudflare/style-const@5.7.3(react@18.3.1)': dependencies: '@cloudflare/types': 6.23.6(react@18.3.1) @@ -9245,6 +9282,25 @@ snapshots: lodash.memoize: 4.1.2 marked: 0.3.19 + '@cloudflare/vitest-pool-workers@0.5.29(@cloudflare/workers-types@4.20241106.0)(@vitest/runner@2.1.3)(@vitest/snapshot@2.1.3)(vitest@2.1.3)': + dependencies: + '@vitest/runner': 2.1.3 + '@vitest/snapshot': 2.1.3 + birpc: 0.2.14 + cjs-module-lexer: 1.2.3 + devalue: 4.3.2 + esbuild: 0.17.19 + miniflare: 3.20241106.0 + semver: 7.5.4 + vitest: 2.1.3(@types/node@18.19.59)(@vitest/ui@2.1.3)(msw@2.4.3(typescript@5.6.3))(supports-color@9.2.2) + wrangler: 3.88.0(@cloudflare/workers-types@4.20241106.0) + zod: 3.22.3 + transitivePeerDependencies: + - '@cloudflare/workers-types' + - bufferutil + - supports-color + - utf-8-validate + '@cloudflare/workerd-darwin-64@1.20241106.1': optional: true @@ -9260,6 +9316,11 @@ snapshots: '@cloudflare/workerd-windows-64@1.20241106.1': optional: true + '@cloudflare/workers-shared@0.7.1': + dependencies: + mime: 3.0.0 + zod: 3.22.3 + '@cloudflare/workers-types@4.20241106.0': {} '@colors/colors@1.5.0': @@ -13407,6 +13468,25 @@ snapshots: min-indent@1.0.1: {} + miniflare@3.20241106.0: + dependencies: + '@cspotcode/source-map-support': 0.8.1 + acorn: 8.11.3 + acorn-walk: 8.3.2 + capnp-ts: 0.7.0(patch_hash=l4yimnxyvkiyj6alnps2ec3sii) + exit-hook: 2.2.1 + glob-to-regexp: 0.4.1 + stoppable: 1.1.0 + undici: 5.28.4 + workerd: 1.20241106.1 + ws: 8.18.0 + youch: 3.2.3 + zod: 3.22.3 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + minimatch@10.0.1: dependencies: brace-expansion: 2.0.1 @@ -15521,6 +15601,35 @@ snapshots: '@cloudflare/workerd-linux-arm64': 1.20241106.1 '@cloudflare/workerd-windows-64': 1.20241106.1 + wrangler@3.88.0(@cloudflare/workers-types@4.20241106.0): + dependencies: + '@cloudflare/kv-asset-handler': 0.3.4 + '@cloudflare/workers-shared': 0.7.1 + '@esbuild-plugins/node-globals-polyfill': 0.2.3(esbuild@0.17.19) + '@esbuild-plugins/node-modules-polyfill': 0.2.2(esbuild@0.17.19) + blake3-wasm: 2.1.5 + chokidar: 4.0.1 + date-fns: 4.1.0 + esbuild: 0.17.19 + itty-time: 1.0.6 + miniflare: 3.20241106.0 + nanoid: 3.3.7 + path-to-regexp: 6.3.0 + resolve: 1.22.8 + resolve.exports: 2.0.2 + selfsigned: 2.1.1 + source-map: 0.6.1 + unenv: unenv-nightly@2.0.0-20241111-080453-894aa31 + workerd: 1.20241106.1 + xxhash-wasm: 1.0.1 + optionalDependencies: + '@cloudflare/workers-types': 4.20241106.0 + fsevents: 2.3.3 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + wrap-ansi@6.2.0: dependencies: ansi-styles: 4.3.0