{"id":26795960,"url":"https://github.com/markjaquith/workerbee","last_synced_at":"2026-03-02T10:31:05.718Z","repository":{"id":57196285,"uuid":"316824715","full_name":"markjaquith/workerbee","owner":"markjaquith","description":"A friendly tool for composing Cloudflare Workers","archived":false,"fork":false,"pushed_at":"2023-05-26T21:25:55.000Z","size":233,"stargazers_count":9,"open_issues_count":5,"forks_count":0,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-10-02T14:57:14.138Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/markjaquith.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2020-11-28T21:25:44.000Z","updated_at":"2022-10-07T08:02:56.000Z","dependencies_parsed_at":"2025-04-22T19:56:32.563Z","dependency_job_id":"a5d11a79-925d-431a-b889-fb85662bde0a","html_url":"https://github.com/markjaquith/workerbee","commit_stats":null,"previous_names":["markjaquith/cf-worker-utils"],"tags_count":30,"template":false,"template_full_name":null,"purl":"pkg:github/markjaquith/workerbee","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/markjaquith%2Fworkerbee","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/markjaquith%2Fworkerbee/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/markjaquith%2Fworkerbee/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/markjaquith%2Fworkerbee/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/markjaquith","download_url":"https://codeload.github.com/markjaquith/workerbee/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/markjaquith%2Fworkerbee/sbom","scorecard":{"id":620107,"data":{"date":"2025-08-11","repo":{"name":"github.com/markjaquith/workerbee","commit":"336d8a35440a84d0bb6c9446f6f9ddd3eb570d50"},"scorecard":{"version":"v5.2.1-40-gf6ed084d","commit":"f6ed084d17c9236477efd66e5b258b9d4cc7b389"},"score":3.4,"checks":[{"name":"Dangerous-Workflow","score":10,"reason":"no dangerous workflow patterns detected","details":null,"documentation":{"short":"Determines if the project's GitHub Action workflows avoid dangerous patterns.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#dangerous-workflow"}},{"name":"Token-Permissions","score":0,"reason":"detected GitHub workflow tokens with excessive permissions","details":["Warn: no topLevel permission defined: .github/workflows/tests.yml:1","Warn: no topLevel permission defined: .github/workflows/typescript.yml:1","Info: no jobLevel write permissions found"],"documentation":{"short":"Determines if the project's workflows follow the principle of least privilege.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#token-permissions"}},{"name":"SAST","score":0,"reason":"no SAST tool detected","details":["Warn: no pull requests merged into dev branch"],"documentation":{"short":"Determines if the project uses static code analysis.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#sast"}},{"name":"Code-Review","score":0,"reason":"Found 0/30 approved changesets -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project requires human code review before pull requests (aka merge requests) are merged.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#code-review"}},{"name":"Packaging","score":-1,"reason":"packaging workflow not detected","details":["Warn: no GitHub/GitLab publishing workflow detected."],"documentation":{"short":"Determines if the project is published as a package that others can easily download, install, easily update, and uninstall.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#packaging"}},{"name":"Maintained","score":0,"reason":"0 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project is \"actively maintained\".","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#maintained"}},{"name":"Binary-Artifacts","score":10,"reason":"no binaries found in the repo","details":null,"documentation":{"short":"Determines if the project has generated executable (binary) artifacts in the source repository.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#binary-artifacts"}},{"name":"Pinned-Dependencies","score":0,"reason":"dependency not pinned by hash detected -- score normalized to 0","details":["Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/tests.yml:8: update your workflow using https://app.stepsecurity.io/secureworkflow/markjaquith/workerbee/tests.yml/main?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/tests.yml:11: update your workflow using https://app.stepsecurity.io/secureworkflow/markjaquith/workerbee/tests.yml/main?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/typescript.yml:8: update your workflow using https://app.stepsecurity.io/secureworkflow/markjaquith/workerbee/typescript.yml/main?enable=pin","Warn: npmCommand not pinned by hash: .github/workflows/tests.yml:17","Warn: npmCommand not pinned by hash: .github/workflows/typescript.yml:12","Info:   0 out of   3 GitHub-owned GitHubAction dependencies pinned","Info:   0 out of   2 npmCommand dependencies pinned"],"documentation":{"short":"Determines if the project has declared and pinned the dependencies of its build process.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#pinned-dependencies"}},{"name":"CII-Best-Practices","score":0,"reason":"no effort to earn an OpenSSF best practices badge detected","details":null,"documentation":{"short":"Determines if the project has an OpenSSF (formerly CII) Best Practices Badge.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#cii-best-practices"}},{"name":"Security-Policy","score":0,"reason":"security policy file not detected","details":["Warn: no security policy file detected","Warn: no security file to analyze","Warn: no security file to analyze","Warn: no security file to analyze"],"documentation":{"short":"Determines if the project has published a security policy.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#security-policy"}},{"name":"Vulnerabilities","score":10,"reason":"0 existing vulnerabilities detected","details":null,"documentation":{"short":"Determines if the project has open, known unfixed vulnerabilities.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#vulnerabilities"}},{"name":"License","score":10,"reason":"license file detected","details":["Info: project has a license file: LICENSE:0","Info: FSF or OSI recognized license: MIT License: LICENSE:0"],"documentation":{"short":"Determines if the project has defined a license.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#license"}},{"name":"Signed-Releases","score":-1,"reason":"no releases found","details":null,"documentation":{"short":"Determines if the project cryptographically signs release artifacts.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#signed-releases"}},{"name":"Branch-Protection","score":0,"reason":"branch protection not enabled on development/release branches","details":["Warn: branch protection not enabled for branch 'main'"],"documentation":{"short":"Determines if the default and release branches are protected with GitHub's branch protection settings.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#branch-protection"}},{"name":"Fuzzing","score":0,"reason":"project is not fuzzed","details":["Warn: no fuzzer integrations found"],"documentation":{"short":"Determines if the project uses fuzzing.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#fuzzing"}}]},"last_synced_at":"2025-08-21T05:00:43.516Z","repository_id":57196285,"created_at":"2025-08-21T05:00:43.516Z","updated_at":"2025-08-21T05:00:43.516Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":29998512,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-03-02T09:59:02.300Z","status":"ssl_error","status_checked_at":"2026-03-02T09:59:02.001Z","response_time":60,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2025-03-29T18:16:39.917Z","updated_at":"2026-03-02T10:31:05.669Z","avatar_url":"https://github.com/markjaquith.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Worker Bee: 🐝 Cloudflare Worker Composer ☁️\n\n![minified and zipped\nsize](https://img.shields.io/bundlephobia/minzip/workerbee)\n![Tests](https://github.com/markjaquith/workerbee/actions/workflows/tests.yml/badge.svg)\n\nToolkit for composing Cloudflare Workers, focused on the use\ncase of having an upstream server, and wanting to conditionally manipulate\nrequests and responses.\n\n## Example Uses\n\n- All requests to `/landing-page/` should strip that subdirectory and proxy from\n  Netlify instead of your normal server.\n- Requests from the `googleweblight` user agent should have `Cache-Control: no-transform`\n  set on the response.\n- Cookies should be stripped for requests to the `/shop/` section of your site.\n- UTM parameters and Facebook click IDs should be removed from requests to your\n  server to increase cacheability.\n- WordPress users should not be logged in on the front of the site unless they’re\n  previewing a post.\n- Make your entire site HTTPS except for one section.\n- Make all images use browser-native lazy loading.\n\nIf you'd like, jump straight to [the examples](docs/examples.md).\n\n## Table of Contents\n\n- [Concepts](#concepts)\n- [Usage](#usage)\n- [Lifecycle](#lifecycle)\n- [Routing](#routing)\n- [Handlers](#handlers)\n- [Bundled Handlers](#bundled-handlers)\n- [Logic](#logic)\n- [Conditions](#conditions)\n- [Best Practices](#best-practices)\n\n## Concepts\n\nCloudflare Worker Utilities is based around three main concepts:\n\n- **Handlers** — Functions that are run when a request is being received,\n  and/or a response from the server/cache is coming back. They can change the\n  request/response, deliver a new request/response altogether, or conditionally\n  add other handlers.\n- **Routes** — Host/route request path patterns with handlers thare are only added only for\n  requests that match the pattern.\n- **Conditions** — Functions that determine whether a handler should be applied.\n\n## Usage\n\n1. Bootstrap your Cloudflare Worker, [using Wrangler][wrangler]. Make sure\n   you’re using Webpack.\n2. `npm i workerbee` from your Worker directory.\n3. In your Worker, import `handleFetch` and provide an array of request/response\n   handlers, and/or route-limited request/response handlers.\n\nExample:\n\n```js\nimport handleFetch from 'workerbee'\n\nhandleFetch({\n\trequest: requestHandlers, // Run on every request.\n\tresponse: responseHandler, // Run on every response.\n\troutes: (router) =\u003e {\n\t\trouter.get('/test', {\n\t\t\trequest: requestHandlers, // Run on matching requests.\n\t\t\tresponse: responseHanders, // Run on responses from matching requests.\n\t\t})\n\n\t\trouter.get('/posts/:id', {\n\t\t\trequest: requestHandlers, // Run on matching requests.\n\t\t\tresponse: responseHandlers, // Run on responses from matching requests.\n\t\t})\n\t},\n})\n```\n\nTop level request and response handlers will be run on every route, _before_ any\nroute-specific handlers.\n\nFor all places where you specify handlers, you can provide one handler, an array\nof handlers, or no handlers (null, or empty array). Routes can also accept\nvariadic handlers, which will be assumed to be request handlers.\n\n## Lifecycle\n\nIt goes like this:\n\n1. `Request` is received.\n2. The `Request` loops through all request handlers (global, and then route).\n3. If early `Response` wasn’t received, the resulting `Request` object is\n   fetched (from the cache or the server).\n4. The resulting `Response` object is passed through the response handlers\n   (global, and then route).\n5. The response is returned to the client.\n\n```\n  ┌──────────────────┐\n  │ Incoming Request │\n  │  to your Worker  │\n  └──────────────────┘\n            │\n            ▼\n    .───────────────.\n   (  Matches route? )───Yes─┐\n    `───────────────'        │\n            │                ▼\n            │    ┌───────────────────────┐\n           No    │ Append route handlers │\n            │    │  to global handlers   │\n            │    └───────────────────────┘\n            │                │\n            └───────┬────────┘\n                    │\n                    ▼\n           ┌─────────────────┐\n           │    Run next     │\n ┌────────▶│ request handler │\n │         └─────────────────┘\n │                  │\n │                  ▼\n │   .─────────────────────────────.\n │  ( Handler returned a Response?  )───┐\n │   `─────────────────────────────'    │\n │                  │                  Yes\n │                 No                   │\nYes                 │                   │\n │                  ▼                   │\n │          .───────────────.           │\n └─────────(  More handlers? )          │\n            `───────────────'           │\n                    │                   │\n                   No                   │\n                    │                   │\n                    ▼                   │\n         .─────────────────────.        │\n  ┌─────(  Request in CF cache? )────┐  │\n  │      `─────────────────────'     │  │\n Yes                                No  │\n  │  ┌────────────┐  ┌────────────┐  │  │\n  │  │ Fetch from │  │ Fetch from │  │  │\n  └─▶│   cache    │  │   server   │◀─┘  │\n     └────────────┘  └────────────┘     │\n            │               │           │\n            └───────┬───────┘           │\n                    │                   │\n                    ▼                   │\n              ┌──────────┐              │\n              │ Response │◀─────────────┘\n              └──────────┘\n                    │\n                    ▼\n          ┌──────────────────┐\n          │     Run next     │\n      ┌──▶│ response handler │\n      │   └──────────────────┘\n      │             │\n     Yes            ▼\n      │     .───────────────.\n      └────(  More handlers? )\n            `───────────────'\n                    │\n                   No\n                    │\n                    ▼\n           ┌────────────────┐\n           │ Final Response │\n           └────────────────┘\n```\n\n## Routing\n\nThe router has functions for all HTTP methods, plus `router.all()` which matches\nany method. e.g. `router.get(path, handlers)`, `router.post(path, handlers)`.\n\nThe path argument uses the [path-to-regexp][path-to-regexp] library,\nwhich has good support for positional path parameters. Here’s what various\nroutes would yield for a given request:\n\n| Pattern                    | 🆗  | URL                                | Params                               |\n| -------------------------- | --- | ---------------------------------- | ------------------------------------ |\n| `/posts/:id`               |     |                                    |                                      |\n|                            | ✅  | `/posts/123`                       | `{id: \"123\"}`                        |\n|                            | ✅  | `/posts/hello`                     | `{id: \"hello\"}`                      |\n|                            | ❌  | `/posts`                           |                                      |\n| `/posts/:id?`              |     |                                    |                                      |\n|                            | ✅  | `/posts/123`                       | `{id: \"123\"}`                        |\n|                            | ✅  | `/posts/hello`                     | `{id: \"hello\"}`                      |\n|                            | ✅  | `/posts`                           | `{}`                                 |\n|                            | ❌  | `/posts/hello/another`             |                                      |\n| `/posts/:id(\\\\d+)/:action` |     |                                    |                                      |\n|                            | ✅  | `/posts/123/edit`                  | `{id: \"123\", action: \"edit\"}`        |\n|                            | ❌  | `/posts/hello/edit`                |                                      |\n| `/posts/:id+`              |     |                                    |                                      |\n|                            | ✅  | `/posts/123`                       | `{id: [\"123\"]}`                      |\n|                            | ✅  | `/posts/123/hello/there`           | `{id: [\"123\", \"hello\", \"there\"]}`    |\n| `/posts/:id*`              |     |                                    |                                      |\n|                            | ✅  | `/posts`                           | `{}`                                 |\n|                            | ✅  | `/posts/123`                       | `{id: [\"123\"]}`                      |\n|                            | ✅  | `/posts/123/hello`                 | `{id: [\"123\", \"hello\"]}`             |\n| `/bread/:meat+/bread`      |     |                                    |                                      |\n|                            | ✅  | `/bread/turkey/bread`              | `{meat: [\"turkey\"]}`                 |\n|                            | ✅  | `/bread/peanut-butter/jelly/bread` | `{meat: [\"peanut-butter\", \"jelly\"]}` |\n|                            | ❌  | `/bread/bread`                     |                                      |\n| `/mother{-:type}?`         |     |                                    |                                      |\n|                            | ✅  | `/mother`                          | `{}`                                 |\n|                            | ✅  | `/mother-in-law`                   | `{type: \"in-law\"}`                   |\n|                            | ❌  | `/mothers`                         |                                      |\n\nIf you want to match a path prefix and everything after it, just use a wildcard\nmatcher like `/prefix/:any*` (and then just ignore what gets matched by `:any*`).\n\nNote that a trailing slash will match, so `/posts/` will match `/posts`.\n\nGo read the [path-to-regex documentation][path-to-regexp] for more information.\n\n[path-to-regexp]: https://github.com/pillarjs/path-to-regexp#readme\n\nYou can also limit your routes to a specific host, like so:\n\n```js\nimport handleFetch, { forbidden, setRequestHeaders } from 'workerbee'\n\nhandleFetch({\n\troutes: (router) =\u003e {\n\t\trouter.host('example.com', (router) =\u003e {\n\t\t\trouter.get('/', setRequestHeaders({ 'x-foo': 'bar' }))\n\t\t})\n\t\trouter.host('*.blogs.example.com', (router) =\u003e {\n\t\t\trouter.all('/xmlrpc.php', forbidden())\n\t\t})\n\t},\n})\n```\n\nThis makes it trivial to set up a Worker that services multiple subdomains and\nroutes, instead of having to maintain a bunch of independent Workers.\n\n## Handlers\n\nHandlers are functions (preferably `async` functions). They are passed an object\nthat contains:\n\n```js\n{\n  addRequestHandler(),\n  addResponseHandler(),\n  addCfPropertiesHandler(),\n\tsetRedirectMode(),\n  originalRequest,\n  request,\n  response,\n  current,\n  params,\n  phase,\n}\n```\n\n- `addRequestHandler(handler, options)` — dynamically adds another request\n  handler (pass `{immediate: true}` to add it as the first or next handler).\n- `addResponseHandler(handler, options)` — dynamically adds another response\n  handler (pass `{immediate: true}` to add it as the first or next handler).\n- `addCfPropertiesHandler(handler)` — adds a callback that receives and returns\n  new properties to pass to `fetch()` on the `cf` key (see [Cloudflare\n  documentation][cfpropertieshandlerdocs]).\n- `setRedirectMode(mode)` — sets the redirect mode for the main fetch. Default is 'manual', but you can set 'follow' or 'error'.\n- `request` — A `Request` object representing the current state of the request.\n- `originalRequest` — The original `Request` object (might be different if other\n  handlers returned a new request).\n- `response` — A `Response` object with the current state of the response. —\n  `current` — During the request phase, this will equal `request`. During the\n  response phrase, this will equal `response`. This is mostly used for\n  conditions. For instance, the `header` condition works on either requests or\n  responses, as both have headers. Thus it looks at `{ current: { headers } }`.\n- `params` — An object containing any param matches from the route.\n- `phase` — One of `\"request\"` or `\"response\"`.\n\n[cfpropertieshandlerdocs]: https://developers.cloudflare.com/workers/runtime-apis/request#requestinitcfproperties\n\nRequest handlers can return three things:\n\n1. Nothing — the current request will be passed on to the rest of the request\n   handlers.\n2. A new `Request` object — this will get passed on to the rest of the request\n   handlers.\n3. A `Response` object — this will skip the rest of the request handlers and get\n   passed through the response handlers.\n\nResponse handlers can return two things:\n\n1. Nothing — the current response will be passed on to the rest of the repsonse\n   handlers.\n2. A new `Response` object — this will get passed on to the rest of the request\n   handlers.\n\n## Bundled Handlers\n\nThe following handlers are included:\n\n- `setUrl(url: string)`\n- `setHost(host: string)`\n- `setPath(path: string)`\n- `setProtocol(protocol: string)`\n- `setHttps()`\n- `setHttp()`\n- `forbidden()`\n- `setRequestHeaders([header: string, value: string][] | {[header: string]: string})`\n- `appendRequestHeaders([header: string, value: string][] | {[header: string]: string})`\n- `removeRequestHeaders(headers: string[])`\n- `setResponseHeaders([header: string, value: string][] | {[header: string]: string})`\n- `appendResponseHeaders([header: string, value: string][] | {[header: string]: string})`\n- `removeResponseHeaders(headers: string[])`\n- `copyResponseHeader(from: string, to: string)`\n- `lazyLoadImages()`\n- `prependPath(pathPrefix: string)`\n- `removePathPrefix(pathPrefix: string)`\n- `redirect(status: number)`\n- `redirectHttps()`\n- `redirectHttp()`\n- `requireCookieOrParam(param: string, forbiddenMessage: string)`\n\n## Logic\n\nInstead of bundling logic into custom handlers, you can also use\n`addHandlerIf(condition, ...handlers)` together with the `any()`, `all()` and\n`none()` gates to specify the logic outside of the handler. Here’s an example:\n\n```js\nimport {\n\thandleFetch,\n\taddHandlerIf,\n\tcontains,\n\theader,\n\tforbidden,\n} from 'workerbee'\n\nhandleFetch({\n\trequest: [\n\t\taddHandlerIf(\n\t\t\tany(\n\t\t\t\theader('user-agent', contains('Googlebot')),\n\t\t\t\theader('user-agent', contains('Yahoo! Slurp')),\n\t\t\t),\n\t\t\tforbidden(),\n\t\t\tsomeCustomHandler(),\n\t\t),\n\t],\n})\n```\n\n`addHandlerIf()` takes a single condition as its first argument, but you can\nnest `any()`, `all()` and `none()` as much as you like to compose a more complex\ncondition.\n\n## Conditions\n\nAs hinted above, there are several built-in conditions for you to use:\n\n- `header(headerName: string, matcher: ValueMatcher)`\n- `contentType(matcher: ValueMatcher)`\n- `isHtml()`\n- `hasParam(paramName: string)`\n- `hasRouteParam(paramName: string)`\n- `param(paramName: string, matcher: ValueMatcher)`\n- `routeParam(paramName: string, matcher: ValueMatcher)`\n- `isHttps()`\n- `isHttps()`\n\nThe ones that take a string (or nothing) are straightforward, but what’s up with\n`ValueMatcher`?\n\nA `ValueMatcher` is flexible. It can be:\n\n- `string` — will match if the string `===` the value.\n- `string[]` — will match if any of the strings `===` the value.\n- `ValueMatchingFunction` — a function that takes the value and returns a\n  boolean that decides the match.\n- `ValueMatchingFunction[]` — an array of functions that take the value, any of\n  which can return true and decide the match.\n\nThe following `ValueMatchingFunction`s are available:\n\n- `contains(value: string | NegatedString | CaseInsensitiveString | NegatedCaseInsensitiveString)`\n- `startsWith(value: string | NegatedString | CaseInsensitiveString | NegatedCaseInsensitiveString)`\n- `endsWith(value: string | NegatedString | CaseInsensitiveString | NegatedCaseInsensitiveString)`\n\nThese functions can also accept insensitive strings and negated strings with the\n`text('yourtext').i` and `text('yourtext).not` helpers.\n\n```js\naddHandlerIf(\n\theader('User-Agent', startsWith(text('WordPress').not.i)),\n\tforbidden(),\n)\n```\n\nNote that you can use logic functions to compose value matchers! So the example\nfrom the Logic section could be rewritten like this:\n\n```js\nimport {\n\thandleFetch,\n\taddHandlerIf,\n\tcontains,\n\theader,\n\tforbidden,\n} from 'workerbee'\n\nhandleFetch({\n\trequest: [\n\t\taddHandlerIf(\n\t\t\theader(\n\t\t\t\t'user-agent',\n\t\t\t\tany(contains('Googlebot'), contains('Yahoo! Slurp')),\n\t\t\t),\n\t\t\tforbidden(),\n\t\t\tsomeCustomHandler(),\n\t\t),\n\t],\n})\n```\n\nTwo more points:\n\n1. The built-in conditionals support partial application. So you can do this:\n\n```js\nconst userAgent = header('user-agent')\n```\n\nNow, `userAgent` is a **function** that accepts a `ValueMatcher`.\n\nYou could take this further and do:\n\n```js\nconst isGoogle = userAgent(startsWith('Googlebot'))\n```\n\nNow you could just add a handler like:\n\n```js\nhandleFetch({\n\trequest: [addHandlerIf(isGoogle, forbiddden)],\n})\n```\n\n2. The built-in conditionals automatically apply to `current`. So if you run\n   them as a request handler, header inspection will look at the request. As a\n   response handler, it’ll look at response. But you can also use the raw\n   conditionals while creating your own handlers. For instance, in a response\n   handler you might want to look at the request that went to the server, or the\n   originalRequest that came to Cloudflare.\n\n```js\nimport forbidden from 'workerbee'\nimport { hasParam } from 'workerbee/conditions'\n\nexport default async function forbiddenIfFooParam({ request }) {\n\tif (hasParam('foo', request)) {\n\t\treturn forbidden()\n\t}\n}\n```\n\nIn **most cases** you will not be reaching into the request from the response. A\nbetter way to handle this is to have a request handler that conditionally adds a\nresponse handler. But if you want to, you can, and you can use those \"raw\"\nconditions to help. Note that the raw conditions will not be curried, and you'll\nhave to pass a request or response to them as their last argument.\n\n## Best Practices\n\n1. Always return a new Request or Response object if you want to change things.\n2. Don’t return anything if your handler is declining to act.\n3. If you have a response handler that is only needed based on what a request\n   handler does, conditionally add that response handler on the fly in the\n   request handler.\n4. Use partial application of built-in conditionals to make your code easier to\n   read.\n\n[wrangler]: https://developers.cloudflare.com/workers/learning/getting-started\n\n## License\n\nMIT License\n\nCopyright \u0026copy; 2020–2021 Mark Jaquith\n\n---\n\nThis software incorporates work covered by the following copyright and\npermission notices:\n\n[tsndr/cloudflare-worker-router](https://github.com/tsndr/cloudflare-worker-router)\\\nCopyright \u0026copy; 2021 Tobias Schneider\\\n(MIT License)\n\n[pillarjs/path-to-regexp](https://github.com/pillarjs/path-to-regexp#readme)\\\nCopyright \u0026copy; 2014 Blake Embrey\\\n(MIT LICENSE)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmarkjaquith%2Fworkerbee","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmarkjaquith%2Fworkerbee","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmarkjaquith%2Fworkerbee/lists"}