Repository: elysiajs/elysia Branch: main Commit: 56310be9617b Files: 270 Total size: 2.0 MB Directory structure: gitextract_5rm6upbg/ ├── .eslintrc.json ├── .github/ │ ├── FUNDING.yaml │ ├── ISSUE_TEMPLATE/ │ │ ├── 2-bug-report.yml │ │ ├── 3-feature-request.yml │ │ └── config.yml │ ├── dependabot.yml │ └── workflows/ │ ├── ci.yml │ └── publish.yml ├── .gitignore ├── .npmignore ├── .npmrc ├── .prettierrc ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── build.ts ├── example/ │ ├── a.ts │ ├── async-recursive.ts │ ├── body.ts │ ├── cookie.ts │ ├── counter.ts │ ├── custom-response.ts │ ├── derive.ts │ ├── error.ts │ ├── extension.ts │ ├── file.ts │ ├── guard.ts │ ├── headers.ts │ ├── hook.ts │ ├── html-import.ts │ ├── http.ts │ ├── index.html │ ├── lazy/ │ │ └── index.ts │ ├── lazy-module.ts │ ├── native.ts │ ├── nested-multipart-files.ts │ ├── nested-schema.ts │ ├── newFile.ts │ ├── openapi.ts │ ├── params.ts │ ├── proxy.ts │ ├── redirect.ts │ ├── rename.ts │ ├── response.ts │ ├── router.ts │ ├── schema.ts │ ├── simple.ts │ ├── sleep.ts │ ├── spawn.ts │ ├── store.ts │ ├── stress/ │ │ ├── a.ts │ │ ├── decorate.ts │ │ ├── instance.ts │ │ ├── memoir.ts │ │ ├── multiple-routes.ts │ │ └── sucrose.ts │ ├── type-inference.ts │ ├── uint8array.ts │ ├── upload.ts │ ├── video.ts │ └── websocket.ts ├── knip.json ├── package.json ├── src/ │ ├── adapter/ │ │ ├── bun/ │ │ │ ├── compose.ts │ │ │ ├── handler-native.ts │ │ │ ├── handler.ts │ │ │ └── index.ts │ │ ├── cloudflare-worker/ │ │ │ └── index.ts │ │ ├── index.ts │ │ ├── types.ts │ │ ├── utils.ts │ │ └── web-standard/ │ │ ├── handler.ts │ │ └── index.ts │ ├── compose.ts │ ├── context.ts │ ├── cookies.ts │ ├── dynamic-handle.ts │ ├── error.ts │ ├── formats.ts │ ├── index.ts │ ├── manifest.ts │ ├── parse-query.ts │ ├── replace-schema.ts │ ├── schema.ts │ ├── sucrose.ts │ ├── trace.ts │ ├── type-system/ │ │ ├── format.ts │ │ ├── index.ts │ │ ├── types.ts │ │ └── utils.ts │ ├── types.ts │ ├── universal/ │ │ ├── env.ts │ │ ├── file.ts │ │ ├── index.ts │ │ ├── request.ts │ │ ├── server.ts │ │ ├── types.ts │ │ └── utils.ts │ ├── utils.ts │ └── ws/ │ ├── bun.ts │ ├── index.ts │ └── types.ts ├── test/ │ ├── adapter/ │ │ ├── bun/ │ │ │ └── index.test.ts │ │ └── web-standard/ │ │ ├── cookie-to-header.test.ts │ │ ├── map-compact-response.test.ts │ │ ├── map-early-response.test.ts │ │ ├── map-response.test.ts │ │ ├── set-cookie.test.ts │ │ └── utils.ts │ ├── aot/ │ │ ├── analysis.test.ts │ │ ├── generation.test.ts │ │ ├── has-transform.test.ts │ │ ├── has-type.test.ts │ │ └── response.test.ts │ ├── bun/ │ │ ├── router.test.ts │ │ └── sql.test.ts │ ├── cloudflare/ │ │ ├── .gitignore │ │ ├── package.json │ │ ├── script/ │ │ │ └── test.ts │ │ ├── src/ │ │ │ └── index.ts │ │ ├── tsconfig.json │ │ ├── worker-configuration.d.ts │ │ └── wrangler.jsonc │ ├── cookie/ │ │ ├── explicit.test.ts │ │ ├── implicit.test.ts │ │ ├── response.test.ts │ │ ├── signature.test.ts │ │ └── unchanged.test.ts │ ├── core/ │ │ ├── aot-strictpath.test.ts │ │ ├── as.test.ts │ │ ├── before-handle-arrow.test.ts │ │ ├── compose.test.ts │ │ ├── config.test.ts │ │ ├── context.test.ts │ │ ├── dynamic.test.ts │ │ ├── elysia.test.ts │ │ ├── formdata.test.ts │ │ ├── handle-error.test.ts │ │ ├── macro-lifecycle.test.ts │ │ ├── modules.test.ts │ │ ├── mount.test.ts │ │ ├── native-static.test.ts │ │ ├── normalize.test.ts │ │ ├── path.test.ts │ │ ├── redirect.test.ts │ │ ├── sanitize.test.ts │ │ ├── status.test.ts │ │ └── stop.test.ts │ ├── extends/ │ │ ├── decorators.test.ts │ │ ├── error.test.ts │ │ ├── models.test.ts │ │ └── store.test.ts │ ├── hoc/ │ │ └── index.test.ts │ ├── lifecycle/ │ │ ├── after-handle.test.ts │ │ ├── after-response.test.ts │ │ ├── before-handle.test.ts │ │ ├── derive.test.ts │ │ ├── error.test.ts │ │ ├── hook-types.test.ts │ │ ├── map-derive.test.ts │ │ ├── map-resolve.test.ts │ │ ├── map-response.test.ts │ │ ├── parser.test.ts │ │ ├── request.test.ts │ │ ├── resolve.test.ts │ │ ├── response.test.ts │ │ └── transform.test.ts │ ├── macro/ │ │ └── macro.test.ts │ ├── modules.ts │ ├── node/ │ │ ├── .gitignore │ │ ├── cjs/ │ │ │ ├── bun.lockb │ │ │ ├── index.js │ │ │ └── package.json │ │ └── esm/ │ │ ├── bun.lockb │ │ ├── index.js │ │ └── package.json │ ├── path/ │ │ ├── group.test.ts │ │ ├── guard.test.ts │ │ └── path.test.ts │ ├── plugins/ │ │ ├── affix.test.ts │ │ ├── checksum.test.ts │ │ ├── error-propagation.test.ts │ │ └── plugin.test.ts │ ├── production/ │ │ └── index.test.ts │ ├── response/ │ │ ├── custom-response.test.ts │ │ ├── headers.test.ts │ │ ├── range.test.ts │ │ ├── redirect.test.ts │ │ ├── sse-double-wrap.test.ts │ │ ├── static.test.ts │ │ └── stream.test.ts │ ├── schema/ │ │ └── schema-utils.test.ts │ ├── standard-schema/ │ │ ├── reference.test.ts │ │ ├── standalone.test.ts │ │ └── validate.test.ts │ ├── sucrose/ │ │ ├── bracket-pair-range-reverse.test.ts │ │ ├── bracket-pair-range.test.ts │ │ ├── extract-main-parameter.test.ts │ │ ├── find-alias.test.ts │ │ ├── infer-body-reference.test.ts │ │ ├── integration.test.ts │ │ ├── query.test.ts │ │ ├── remove-colon-alias.test.ts │ │ ├── remove-default-parameter.test.ts │ │ ├── retrieve-root-parameters.test.ts │ │ ├── separate-function.test.ts │ │ └── sucrose.test.ts │ ├── timeout.ts │ ├── tracer/ │ │ ├── aot.test.ts │ │ ├── detail.test.ts │ │ ├── timing.test.ts │ │ └── trace.test.ts │ ├── type-system/ │ │ ├── array-buffer.test.ts │ │ ├── array-string.test.ts │ │ ├── boolean-string.test.ts │ │ ├── coercion-number.test.ts │ │ ├── date.test.ts │ │ ├── files.test.ts │ │ ├── form.test.ts │ │ ├── formdata.test.ts │ │ ├── import.ts │ │ ├── object-string.test.ts │ │ ├── string-format.test.ts │ │ ├── uint8array.test.ts │ │ └── union-enum.test.ts │ ├── types/ │ │ ├── async-modules.ts │ │ ├── documentation.ts │ │ ├── index.ts │ │ ├── lifecycle/ │ │ │ ├── derive.ts │ │ │ ├── resolve.ts │ │ │ └── soundness.ts │ │ ├── macro.ts │ │ ├── plugins.ts │ │ ├── schema-standalone.ts │ │ ├── standard-schema/ │ │ │ └── index.ts │ │ ├── type-system.ts │ │ └── utils.ts │ ├── units/ │ │ ├── class-to-object.test.ts │ │ ├── deduplicate-checksum.test.ts │ │ ├── get-schema-validator.test.ts │ │ ├── has-ref.test.ts │ │ ├── has-transform.test.ts │ │ ├── merge-deep.test.ts │ │ ├── merge-object-schemas.test.ts │ │ ├── numeric.test.ts │ │ └── replace-schema-type.test.ts │ ├── utils.d.ts │ ├── utils.ts │ ├── validator/ │ │ ├── body.test.ts │ │ ├── cookie.test.ts │ │ ├── encode.test.ts │ │ ├── exact-mirror.test.ts │ │ ├── header.test.ts │ │ ├── novalidate.test.ts │ │ ├── params.test.ts │ │ ├── query.test.ts │ │ ├── response-validation-nested.test.ts │ │ ├── response.test.ts │ │ ├── standalone.test.ts │ │ └── validator.test.ts │ └── ws/ │ ├── aot.test.ts │ ├── connection.test.ts │ ├── destructuring.test.ts │ ├── message.test.ts │ └── utils.ts ├── tsconfig.dts.json ├── tsconfig.json └── tsconfig.test.json ================================================ FILE CONTENTS ================================================ ================================================ FILE: .eslintrc.json ================================================ { "env": { "browser": true, "es2021": true }, "extends": [ "eslint:recommended", "plugin:@typescript-eslint/recommended", "plugin:sonarjs/recommended" ], "parser": "@typescript-eslint/parser", "parserOptions": { "ecmaVersion": "latest", "sourceType": "module" }, "plugins": ["@typescript-eslint", "sonarjs"], "rules": { "@typescript-eslint/ban-types": "off", "@typescript-eslint/no-explicit-any": "off", "no-mixed-spaces-and-tabs": "off", "@typescript-eslint/no-non-null-assertion": "off", "@typescript-eslint/no-extra-semi": "off", "@typescript-eslint/ban-ts-comment": "off", "@typescript-eslint/no-namespace": "off", "no-case-declarations": "off", "no-extra-semi": "off", "sonarjs/cognitive-complexity": "off", "sonarjs/no-all-duplicated-branches": "off" }, "ignorePatterns": ["example/*", "test/**/*"] } ================================================ FILE: .github/FUNDING.yaml ================================================ github: SaltyAom ================================================ FILE: .github/ISSUE_TEMPLATE/2-bug-report.yml ================================================ name: 🐛 Bug Report description: Report an issue that should be fixed labels: [bug] body: - type: markdown attributes: value: | Thank you for submitting a bug report. It helps make Elysia.JS better. If you need help or support using Elysia.JS, and are not reporting a bug, please head over to Q&A discussions [Discussions](https://github.com/elysiajs/elysia/discussions/categories/q-a), where you can ask questions in the Q&A forum. Make sure you are running the version of Elysia.JS and Bun.Sh The bug you are experiencing may already have been fixed. Please try to include as much information as possible. - type: input attributes: label: What version of Elysia is running? description: Copy the row with `elysia` dependency from output `bun pm ls` command - type: input attributes: label: What platform is your computer? description: | For MacOS and Linux: copy the output of `uname -mprs` For Windows: copy the output of `"$([Environment]::OSVersion | ForEach-Object VersionString) $(if ([Environment]::Is64BitOperatingSystem) { "x64" } else { "x86" })"` in the PowerShell console - type: input attributes: label: What environment are you using description: | Answer with runtime and version you are using. - Bun version: `bun --version` - Node version: `node --version` - Deno version: `deno --version` - Cloudflare Workers (compatability date if applicable) - type: input attributes: label: Are you using dynamic mode? description: Is your code using `new Elysia({ aot:false })` - type: textarea attributes: label: What steps can reproduce the bug? description: Explain the bug and provide a code snippet that can reproduce it. validations: required: true - type: textarea attributes: label: What is the expected behavior? description: If possible, please provide text instead of a screenshot. - type: textarea attributes: label: What do you see instead? description: If possible, please provide text instead of a screenshot. - type: textarea attributes: label: Additional information description: Is there anything else you think we should know? - type: input attributes: label: Have you try removing the `node_modules` and `bun.lockb` and try again yet? description: rm -rf node_modules && bun.lockb ================================================ FILE: .github/ISSUE_TEMPLATE/3-feature-request.yml ================================================ name: 🚀 Feature Request description: Suggest an idea, feature, or enhancement labels: [enhancement] body: - type: markdown attributes: value: | Thank you for submitting an idea. It helps make Elysia.JS better. If you want to discuss Elysia.JS, or learn how others are using Elysia.JS, please head to our [Discord](https://discord.com/invite/y7kH46ZE) server, where you can chat among the community. - type: textarea attributes: label: What is the problem this feature would solve? validations: required: true - type: textarea attributes: label: What is the feature you are proposing to solve the problem? validations: required: true - type: textarea attributes: label: What alternatives have you considered? ================================================ FILE: .github/ISSUE_TEMPLATE/config.yml ================================================ blank_issues_enabled: true contact_links: - name: 📗 Documentation Issue url: https://github.com/elysiajs/documentation/issues/new/choose about: Head over to our Documentation repository! - name: 💬 Ask a Question url: https://discord.gg/eaFJ2KDJck about: Head over to our Discord! ================================================ FILE: .github/dependabot.yml ================================================ version: 2 updates: - package-ecosystem: 'npm' directory: './' schedule: interval: 'daily' - package-ecosystem: 'github-actions' directory: './' schedule: interval: 'daily' ================================================ FILE: .github/workflows/ci.yml ================================================ name: Build and Test on: push: pull_request: jobs: build: name: Build and test code runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v4 - name: Setup bun uses: oven-sh/setup-bun@v1 with: bun-version: latest - name: Install packages run: bun install - name: Build code run: bun run build - name: Test run: bun run test - name: Test run: bun run test:cf - name: Publish Preview if: github.event_name == 'pull_request' run: bunx pkg-pr-new publish ================================================ FILE: .github/workflows/publish.yml ================================================ name: Publish on: release: types: [published] defaults: run: shell: bash permissions: id-token: write env: # Enable debug logging for actions ACTIONS_RUNNER_DEBUG: true jobs: publish-npm: name: 'Publish: npm Registry' runs-on: ubuntu-latest steps: - name: 'Checkout' uses: actions/checkout@v4 - name: 'Setup Bun' uses: oven-sh/setup-bun@v1 with: bun-version: latest registry-url: "https://registry.npmjs.org" - uses: actions/setup-node@v5 with: node-version: '24.x' registry-url: 'https://registry.npmjs.org' - name: Install packages run: bun install - name: Build code run: bun run build - name: Test run: bun run test - name: Test run: bun run test:cf - name: 'Publish' run: | NODE_AUTH_TOKEN="" npm publish --provenance --access=public ================================================ FILE: .gitignore ================================================ .DS_Store node_modules build dump.rdb dist trace .scannerwork *.tsbuildinfo .wrangler .elysia heap.json server ================================================ FILE: .npmignore ================================================ .git .gitignore .github .prettierrc .eslintrc.js .eslint.json .swc.cjs.swcrc .swc.esm.swcrc .swcrc .husky .eslintrc.json bun.lock bun.lockb node_modules tsconfig.json CHANGELOG.md heap.json example tests test docs src .DS_Store test tsconfig.cjs.json tsconfig.dts.json tsconfig.esm.json tsconfig.test.json tsconfig.test.tsbuildinfo CONTRIBUTING.md CODE_OF_CONDUCT.md trace build.ts .scannerwork src server ================================================ FILE: .npmrc ================================================ enable-pre-post-scripts=true registry=https://registry.npmjs.org always-auth=true ================================================ FILE: .prettierrc ================================================ { "useTabs": true, "tabWidth": 4, "semi": false, "singleQuote": true, "trailingComma": "none" } ================================================ FILE: CHANGELOG.md ================================================ # 1.4.28 - 17 Mar 2025 Feature: - [#1803](https://github.com/elysiajs/elysia/pull/1803) stream response with pull based backpressure - [#1802](https://github.com/elysiajs/elysia/pull/1802) handle range header for file/blob response - [#1722](https://github.com/elysiajs/elysia/pull/1772), [#1741](https://github.com/elysiajs/elysia/issues/1741) direct ReadableStream perf blow-up Bug fix: - [#1805](https://github.com/elysiajs/elysia/pull/1805) dynamic imports inside .guard not registering routes - [#1771](https://github.com/elysiajs/elysia/issues/1771) breaks Bun HTML imports - [#1797](https://github.com/elysiajs/elysia/pull/1797) await mapped error response promise - [#1794](https://github.com/elysiajs/elysia/pull/1794) merge app cookie config into route cookie validator config - [#1796](https://github.com/elysiajs/elysia/pull/1796) check custom parser by full name - [#1795](https://github.com/elysiajs/elysia/pull/1795) write transformed cookie value to cookie entry directly - [#1793](https://github.com/elysiajs/elysia/pull/1793) use cookie schema for cookie noValidate check - [#1792](https://github.com/elysiajs/elysia/pull/1792) throw ValidationError instead of boolean in response encode path - detect HTML bundle when inline response is Promise Change: - [#1613](https://github.com/elysiajs/elysia/pull/1613) export `ElysiaTypeCustomErrors` - remove Bun specific built - export `AnySchema`, `UnwrapSchema`, `ModelsToTypes` from root - conditional set headers of String and Object when no set.headers is set # 1.4.27 - 1 Mar 2026 Bug fix: - getSchemaValidator: handle TypeBox as sub type - handle cookie prototype pollution when parsing cookie Improvement: - conditional async on getSchemaValidator when schema is Standard Schema - use Response.json on Bun # 1.4.26 - 25 Feb 2026 Bug fix: - [#1755](https://github.com/elysiajs/elysia/issues/1755) deduplicate local handler from global event - [#1752](https://github.com/elysiajs/elysia/issues/1752) system router with trailing path doesn't match with non-trailing - url format redos - [#1747](https://github.com/elysiajs/elysia/issues/1747) parsing request from mount hang # 1.4.25 - 12 Feb 2026 Feature: - export ElysiaStatus Bug fix: - macro with conflict literal value per status - recursive macro with conflict value per status # 1.4.24 - 11 Feb 2026 Feature: - graceful unsigned cookie transition Bug fix: - [#1733](https://github.com/elysiajs/elysia/pull/1733) preserve multiple set-cookie headers in mounted handlers - object cookie with secret doesn't deserialized after parsed # 1.4.23 - 9 Feb 2026 Feature: - [#1719](https://github.com/elysiajs/elysia/pull/1719) add t.Union/t.Intersection handling in property enumerations/checks - [#1697](https://github.com/elysiajs/elysia/pull/1697) extend complex formdata support to StandardSchema - [#1656](https://github.com/elysiajs/elysia/pull/1675) serialize custom array-like custom class with array sub class Bug fix: - [#1721](https://github.com/elysiajs/elysia/issues/1721) Promise with response schema - [#1700](https://github.com/elysiajs/elysia/issues/1700) distinct union object - [#1683](https://github.com/elysiajs/elysia/pull/1683) response validation returns 500 instead of 422 for nested schemas in dynamic mode - [#1679](https://github.com/elysiajs/elysia/pull/1679) preserve headers when throwing from AsyncGenerator - [#1595](https://github.com/elysiajs/elysia/pull/1595) stream reference should point to teed value - fix can't modify immutable headers error Change: - update exact-mirror to 0.2.7 # 1.4.22 - 14 Jan 2026 Improvement: - use imperative check for `replaceURLPath` instead of allocating `new URL` - reduce ts-ignore/ts-expect-error on promise map handler - [#1655](https://github.com/elysiajs/elysia/pull/1655) decode single values in ArrayQuery Bug fix: - [#1671](https://github.com/elysiajs/elysia/issues/1671) mount produces incorrect URL path when Elysia instance has prefix option - [https://github.com/elysiajs/elysia/issues/1617](https://github.com/elysiajs/elysia/issues/1617), [#1623](https://github.com/elysiajs/elysia/pull/1623) AOT compilation removes beforeHandle when using arrow function expression - [#1667](https://github.com/elysiajs/elysia/issues/1667) skip body parsing on mount with dynamic mode - [#1662](https://github.com/elysiajs/elysia/pull/1662) custom thenable fallback in `mapCompactResponse` causing runtime crash with undefined variable - [#1661](https://github.com/elysiajs/elysia/pull/1661), [#1663](https://github.com/elysiajs/elysia/pull/1663) fix SSE double-wrapping bug when returning pre-formatted Response - ValueError with summary missing types - Elysia not using Bun.routes by default # 1.4.21 - 4 Jan 2026 Improvement: - [#1654](https://github.com/elysiajs/elysia/pull/1654) encode t.Date() to iso string - [#1624](https://github.com/elysiajs/elysia/pull/1624) apply macros before merging hooks in group method Bug fix: - call Sucrose GC unref when possible (browser fallback) - add allowUnsafeValidationDetails to Standard Validator # 1.4.20 - 3 Jan 2026 Improvement: - add `ModelValidator.schema` for accessing raw schema - [#1636](https://github.com/elysiajs/elysia/issues/1636) add `subscriptions` to `Elysia.ws` context - [#1638](https://github.com/elysiajs/elysia/pull/1638) unref Sucrose gc Bug fix: - [#1649](https://github.com/elysiajs/elysia/pull/1649) add null check for object properties (t.Record) - [#1646](https://github.com/elysiajs/elysia/pull/1646) use constant-time comparison for signed cookie verification - [#1639](https://github.com/elysiajs/elysia/issues/1639) compose: ReferenceError: "_r_r is not defined" when onError returns plain object & mapResponse exists - [#1631](https://github.com/elysiajs/elysia/issues/1631) update Exact Mirror to 0.2.6 - [#1630](https://github.com/elysiajs/elysia/issues/1630) enforce fetch to return MaybePromise Bug fix: - `Elysia.models` broke when referencing non typebox model # 1.4.19 - 13 Dec 2025 Security: - reject invalid cookie signature when using cookie rotation Improvement - [#1609](https://github.com/elysiajs/elysia/issues/1609) calling afterResponse with aot: false - [#1607](https://github.com/elysiajs/elysia/issues/1607), [#1606](https://github.com/elysiajs/elysia/issues/1606), [#1139](https://github.com/elysiajs/elysia/issues/1138) data coercion on nested form data - [#1604](https://github.com/elysiajs/elysia/issues/1604) save lazy compilation of `Elysia.fetch` for up to 45x performance improvement - [#1588](https://github.com/elysiajs/elysia/pull/1588), [#1587](https://github.com/elysiajs/elysia/pull/1587) add seen weakset during mergeDeep Bug fix: - [#1614](https://github.com/elysiajs/elysia/issues/1614) Cloudflare Worker with dynamic path - add set immediate fallback - [#1597](https://github.com/elysiajs/elysia/issues/1597) safeParse, parse to static type - [#1596](https://github.com/elysiajs/elysia/issues/1596) handle context pass to function with sub context - [#1591](https://github.com/elysiajs/elysia/pull/1591), [#1590](https://github.com/elysiajs/elysia/pull/1591) merge set.headers without duplicating Response - [#1546](https://github.com/elysiajs/elysia/issues/1546) override group prefix type - [#1526](https://github.com/elysiajs/elysia/issues/1526) default error handler doesn't work in Cloudflare Workers - handle group with empty prefix in type-level # 1.4.18 - 4 Dec 2025 Security: - use `JSON.stringify` over custom escape implementation # 1.4.17 - 2 Dec 2025 Improvement: - [#1573](https://github.com/elysiajs/elysia/pull/1573) `Server` is always resolved to `any` when `@types/bun` is missing - [#1568](https://github.com/elysiajs/elysia/pull/1568) optimize cookie value comparison using FNV-1a hash - [#1549](https://github.com/elysiajs/elysia/pull/1549) Set-Cookie headers not sent when errors are thrown - [#1579](https://github.com/elysiajs/elysia/pull/1579) HEAD to handle Promise value Security: - cookie injection, prototype pollution, and RCE Bug fix: - [#1550](https://github.com/elysiajs/elysia/pull/1550) excess property checking - allow cookie.sign to be string Change: - [#1584](https://github.com/elysiajs/elysia/pull/1584) change customError property to unknown - [#1556](https://github.com/elysiajs/elysia/issues/1556) prevent sending set-cookie header when cookie value is not modified - [#1563](https://github.com/elysiajs/elysia/issues/1563) standard schema on websocket - conditional parseQuery parameter - conditional pass `c.request` to handler for streaming response - fix missing `contentType` type on `parser` # 1.4.16 - 13 Nov 2025 Improvement: - ValidationError: add `messageValue` as an alias of `errorValue` - ValidationError.detail now accept optional 2nd parameter `allowUnsafeValidatorDetails` - macro: add `introspect` - prevent redundant route compilation - merge multiple macro resolve response Bug fix: - [#1543](https://github.com/elysiajs/elysia/pull/1524) respect toResponse() method on Error classes - [#1537](https://github.com/elysiajs/elysia/issues/1537) websocket: ping/pong not being called - [#1536](https://github.com/elysiajs/elysia/pull/1536) export ExtractErrorFromHandle - [#1535](https://github.com/elysiajs/elysia/pull/1535) skip response validation for generators and streams - [#1531](https://github.com/elysiajs/elysia/pull/1531) typo in ElysiaTypeCustomErrorCallback: valdation to validation - [#1528](https://github.com/elysiajs/elysia/issues/1528) error in parsing request body validation errors with Zod - [#1527](https://github.com/elysiajs/elysia/issues/1527) bracket handling in exact mirror - [#1524](https://github.com/elysiajs/elysia/pull/1524) head request handler not working # 1.4.15 - 3 Nov 2025 Bug fix: - 1.4.14 regression with Eden Treaty, and OpenAPI type gen # 1.4.14 - 3 Nov 2025 Feature: - [#1520](https://github.com/elysiajs/elysia/issues/1520), [#1522](https://github.com/elysiajs/elysia/issues/1522) async handlers crash on HEAD request - [#1510](https://github.com/elysiajs/elysia/issues/1510) strict status response - [#1503](https://github.com/elysiajs/elysia/pull/1503) add t.NumericEnum() for numeric enum query handling - [#1450](https://github.com/elysiajs/elysia/pull/1450) call onAfterResponse when onError returns a value - remove redundant `Prettify` - prettify response for OpenAPI type gen - conditional apply macro context in type level - improve type performance in general Change: - remove `Prettify2`, `Partial2` # 1.4.13 - 23 Oct 2025 Feature: - [#1453](https://github.com/elysiajs/elysia/issues/1453) add `allowUnsafeValidationDetails` for disabling unsafe validation details in production mode - allow rapid stream in non browser mode or `ELYSIA_RAPID_STREAM` is set - `afterResponse` now wait for generator stream to finish - trace of `handle`, and `afterResponse` now wait for generator stream to finish Bug fix: - [#1502](https://github.com/elysiajs/elysia/issues/1502), [#1501](https://github.com/elysiajs/elysia/issues/1501) afterHandle doesn't update status - [#1500](https://github.com/elysiajs/elysia/issues/1500) export `InvalidFileType` from root - [#1495](https://github.com/elysiajs/elysia/pull/1495) request server hook parameters are typed as any (Bun 1.3.0) - [#1483](https://github.com/elysiajs/elysia/pull/1483) handle standard schema validators in ValidationError.all - [#1459](https://github.com/elysiajs/elysia/pull/1459) fix strictPath behavior when aot: false is set - [#1455](https://github.com/elysiajs/elysia/pull/1455) graceful shutdown not awaiting server.stop - [#1499](https://github.com/elysiajs/elysia/pull/1449) fails when merging with t.Optional schema - remove encoding chunk from native static response in Bun adapter Change: - make `@types/bun` an optional dependency # 1.4.12 - 14 Oct 2025 Improvement: - named macro function callback - adjust build script # 1.4.11 - 12 Oct 2025 Bug fix: - [#1469](https://github.com/elysiajs/elysia/issues/1469) incorrect ping, pong type signature - [#1467](https://github.com/elysiajs/elysia/issues/1467) better error union handling in `onError` - [#1463](https://github.com/elysiajs/elysia/issues/1463) responseValue is undefined in afterHandle when beforeHandle returns status - [#1460](https://github.com/elysiajs/elysia/issues/1460) compressed response in mapResponse is corrupted if status !== 200 - [#1456](https://github.com/elysiajs/elysia/issues/1456) add response type check for stream - [#1451](https://github.com/elysiajs/elysia/issues/1451) cookie validation is not running when there's no body schema - make `file-type` non optional dependency to fix default build problem # 1.4.10 - 9 Oct 2025 Bug fix: - [#1406](https://github.com/elysiajs/elysia/issues/1406) enforce return type in OptionalHandler - static handlers in Bun 1.3 - conditional use `crypto` randomUUID if not available (eg. iOS Safari) Change: - `Elysia.file` readstream value is now IIFE to re-read # 1.4.9 - 29 Sep 2025 Improvement: - add Cloudflare Worker test - add `Sucrose.Settings` - [#1443](https://github.com/elysiajs/elysia/pull/1443) add knip for detecting deadcode # 1.4.8 - 27 Sep 2025 Improvement: - automatically clear up sucrose cache when not used - remove `Bun.hash` from checksum calculation Change: - make `file-type` optional to reduce bundle size - [#1432](https://github.com/elysiajs/elysia/pull/1432) missing context (set) in mapResponse for ElysiaFile - [#1435](https://github.com/elysiajs/elysia/pull/1435) missing cookies in SSE # 1.4.7 - 22 Sep 2025 Feature: - experimental `adapter/cloudflare-worker` - add `ElysiaAdapter.beforeCompile` Change: - do not prettify routes when using `guard`, `group` - use `process.getBuiltinModule` instead of dynamic import for file - `Elysia.file.value` on Web Standard Adapter now is not a promise # 1.4.6 - 18 Sep 2025 Improvement: - [#1406](https://github.com/elysiajs/elysia/issues/1406) strictly check for 200 inline status code - coerce union status value and return type - add `BunHTMLBundleLike` to Elysia inline handler - [#1405](https://github.com/elysiajs/elysia/issues/1405) prevent Elysia from being a dependency of itself - [#1416](https://github.com/elysiajs/elysia/issues/1416) check if object is frozen before merging, add try-catch to prevent crash - [#1419](https://github.com/elysiajs/elysia/issues/1419) guard doesn't apply scoped/global schema to object macro - [#1425](https://github.com/elysiajs/elysia/issues/1425) DELETE doesn't inherit derive/resolve type Change: - [#1409](https://github.com/elysiajs/elysia/issues/1409) onTransform now doesn't include type as it isn't validated yet, creating confusion Bug fix: - [#1410](https://github.com/elysiajs/elysia/issues/1410) handle union derive/resolve property - probably fix mergeDeep attempted to assign to readonly property - derive/resolve inherit type in inline handler # 1.4.5 - 15 Sep 2025 Improvement: - soundness for guard, group - overwrite primitive value if collide (intersectIfObject) Bug fix: - standard schema incorrectly validate cookie and coercion - merge nested guard, group standalone schema properly Breaking Change: - no longer coerce type for .model with `t.Ref` by default # 1.4.4 - 13 Sep 2025 Bug fix: - merge schema in GET method - remove accidental console.log # 1.4.3 - 13 Sep 2025 Bug fix: - `mapValueError` should return all possible value both in dev environment and production environment - inline lifecycle get method doesn't inherit Singleton # 1.4.2 - 13 Sep 2025 Bug fix: - remove debug `q` from inline handler # 1.4.1 - 13 Sep 2025 Bug fix: - type error for inline Elysia.file # 1.4.0 - 13 Sep 2025 Feature: - standard validator - macro schema, macro extension, macro detail - lifecycle type soundness - type inference reduced by ~11% - [#861](https://github.com/elysiajs/elysia/issues/861) missing HEAD method when register route with GET Improvement - [#861](https://github.com/elysiajs/elysia/issues/861) automatically add HEAD method when defining GET route Change - ObjectString/ArrayString no longer produce default value by default due to security reasons - Cookie now dynamically parse when format is likely JSON - export `fileType` for external file type validation for accurate response Breaking Change - remove macro v1 due to non type soundness - remove `error` function, use `status` instead - deprecation notice for `response` in `mapResponse`, `afterResponse`, use `responseValue` instead - remove reference array model by string eg. `user[]`, use `t.Array(t.Ref('user'))` instead # 1.3.22 - 6 Sep 2025 Bug fix: - safely unwrap t.Record # 1.3.21 - 31 Aug 2025 Bug fix: - [#1356](https://github.com/elysiajs/elysia/pull/1356) webSocket validation error handling in BunAdapter - [#1358](https://github.com/elysiajs/elysia/pull/1358) allow overriding websocket handler with listen options - [#1365](https://github.com/elysiajs/elysia/pull/1365) check if the plugin.constructor (fix import module in Bun 1.2.21) - [#1367](https://github.com/elysiajs/elysia/pull/1367) .trace hooks (onAfterResponse, etc...) not being called - [#1369](https://github.com/elysiajs/elysia/issues/1369) don't block microtask queue in SSE # 1.3.20 - 24 Aug 2025 Change: - mime is undefined when using `Elysia.file` in Web Standard Adapter # 1.3.19 - 24 Aug 2025 Change: - [#1357](https://github.com/elysiajs/elysia/issues/1357) return `Response` proxy as-is Bug fix: - [elysiajs/node#45](https://github.com/elysiajs/node/issues/45) detect Response polyfill on Node # 1.3.18 - 23 Aug 2025 Bug fix: - `ReadableStream` is not pass to `handleStream` in `mapCompactResponse`, and `mapEarlyResponse` # 1.3.17 - 23 Aug 2025 Bug fix: - [#1353](https://github.com/elysiajs/elysia/issues/1353) normalize encodeSchema with Transform # 1.3.16 - 23 Aug 2025 Improvement: - `sse` now infer type - `sse` now accepts `ReadableStream` to return stream as `text/event-stream` - refactor SSE handler - support returning `ReadableStream` from generator or async generator Change: - sse no longer include generated id by default Bug fix: - static response now use callback clone instead of bind # 1.3.15 - 21 Aug 2025 Bug fix: - ValidationError.detail only handle custom error # 1.3.14 - 21 Aug 2025 Improvement: - custom error on production mode - add `ValidationError.withDetail` - add `withDetail` for additional error information # 1.3.13 - 18 Aug 2025 Bug fix: - important performance degration, exact mirror normalize doesn't apply correctly - normalize optional property with special character Change: - update `exact-mirror` to `0.1.6` # 1.3.12 - 19 Aug 2025 Bug fix: - [#1348](https://github.com/elysiajs/elysia/issues/1348) onAfterResponse runs twice if NotFoundError thrown and onError provided # 1.3.11 - 18 Aug 2025 Bug fix: - [#1346](https://github.com/elysiajs/elysia/issues/1346) cannot declare 'mep' twice # 1.3.10 - 18 Aug 2025 Bug fix: - [#1028](https://github.com/elysiajs/elysia/issues/1028) query array nuqs format in dynamic mode - unwrap t.Import in dynamic mode # 1.3.9 - 18 Aug 2025 Feature: - [#932](https://github.com/elysiajs/elysia/issues/932) add `t.ArrayBuffer`, `t.Uint8Array` Bug fix: - [#459](https://github.com/elysiajs/elysia/issues/459) route prefix should give type error when prefix is not start with '/' - [#669](https://github.com/elysiajs/elysia/issues/669) add nullable field to t.Nullable for OpenAPI 3.0 spec - [#711](https://github.com/elysiajs/elysia/issues/711) set default headers for non-aot - [#713](https://github.com/elysiajs/elysia/issues/713) NotFoundError doesn't call onAfterResponse hook - [#771](https://github.com/elysiajs/elysia/issues/771) skip body parsing if Content-Type is present but body is not - [#747](https://github.com/elysiajs/elysia/issues/747) mapResponse inside mapError override error value - [#812](https://github.com/elysiajs/elysia/issues/812) check for minItems length before array validation - [#833](https://github.com/elysiajs/elysia/issues/833) cookie signing doesn't work in dynamic mode - [#859](https://github.com/elysiajs/elysia/issues/859) clean non-root additionalProperties - [#924](https://github.com/elysiajs/elysia/issues/924) decode path param - [#985](https://github.com/elysiajs/elysia/issues/924) Nullable accept options - [#1028](https://github.com/elysiajs/elysia/issues/1028) string | string[] query parameter, reference array - [#1120](https://github.com/elysiajs/elysia/issues/1120) cannot set multiple cookies when response is a file - [#1124](https://github.com/elysiajs/elysia/issues/1124) validate url encoded query - [#1158](https://github.com/elysiajs/elysia/issues/1158) prevent side-effect from guard merge - [#1162](https://github.com/elysiajs/elysia/issues/1162) handle encoded space in array query string - [#1267](https://github.com/elysiajs/elysia/issues/1267) parse without contentType headers throw Bad Request - [#1274](https://github.com/elysiajs/elysia/issues/1274) support .use(undefined | false) for conditional plugin - [#1276](https://github.com/elysiajs/elysia/issues/1276) mapResponse with set inference produce invalid instruction - [#1268](https://github.com/elysiajs/elysia/issues/1268) using number instead of stringifed value for reporting validation error - [#1288](https://github.com/elysiajs/elysia/issues/1288) handle array query string in dynamic mode - [#1294](https://github.com/elysiajs/elysia/issues/1294) return status from `derive` and `resolve` shouldn't call `onError` - [#1297](https://github.com/elysiajs/elysia/issues/1297), [#1325](https://github.com/elysiajs/elysia/pull/1325) fix HTML imported pages in compiled apps - [#1319](https://github.com/elysiajs/elysia/pull/1319) fix array of plugin usage causes incorrect path aggregation - [#1323](https://github.com/elysiajs/elysia/issues/1323) don't duplicate error from plugin - [#1327](https://github.com/elysiajs/elysia/pull/1327) ensure that t.Date value is Date in Encode - dynamic handle should handle named parser - instanceof ElysiaCustomStatusResponse should return true when import from root Elysia module Improvement: - remove `finally` from compose - `NotFoundError` should parse query if inferred - [#853](https://github.com/elysiajs/elysia/issues/853) Bun Static response now handle pre-compute `onRequest`, and `onError` - prettify ElysiaWS type - export `ElysiaCustomStatusResponse` - handle type-level status check in after response Change: - status no longer make value as readonly - afterResponse now call after response by scheduling setImmediate - update memoirist to 0.4.0 - update exact-mirror to 0.1.5 # 1.3.8 - 31 Jul 2025 Improvement: - ElysiaFile doesn't inherits `set.headers` eg. cors - [Web Standard] automatically set `Content-Type`, `Content-Range` of ElysiaFile Bug fix: - [#1316](https://github.com/elysiajs/elysia/pull/1316) fix context type when multiple macros are selected - [#1306](https://github.com/elysiajs/elysia/pull/1306) preserve type narrowing in getSchemaValidator - add `set` to `handleFile` when file is `ElysiaFile` - [Web Standard] inherit set.status for `ElysiaFile` - make `ElysiaAdapter.stop` optional # 1.3.7 - 31 Jul 2025 Bug fix: - [#1314](https://github.com/elysiajs/elysia/issues/1314) coerce TransformDecodeError to ValidationError - [#1313](https://github.com/elysiajs/elysia/pull/1313) onRequest not firing - [#1311](https://github.com/elysiajs/elysia/issues/1311) [Exact Mirror] handle property starts with a number - [#1310](https://github.com/elysiajs/elysia/issues/1310) webSocket fails to connect when inside group and guard - [#1309](https://github.com/elysiajs/elysia/issues/1309) encode is not called when using dynamic handler - [#1304](https://github.com/elysiajs/elysia/issues/1304) remove response body from HTTP 101, 204, 205, 304, 307, 308 Change: - update exact mirror to 0.1.3 - warn when stop is called instead of throwing an error # 1.3.6 - 24 Jul 2025 Improvement: - [#1263](https://github.com/elysiajs/elysia/pull/1263) bun adapter add qi to routes that need query from guard - [#1270](https://github.com/elysiajs/elysia/pull/1270) add Symbol.dispose - [#1089](https://github.com/elysiajs/elysia/pull/1089) add stop function to ElysiaAdapter type Bug fix: - [#1126](https://github.com/elysiajs/elysia/pull/1126) websocket errors not catching - [#1281](https://github.com/elysiajs/elysia/issues/1281) automatically enforce additional properties in nested schema (eg. array) - Dynamic handle decode signed cookie secret instead of accidental hardcoded value # 1.3.5 - 16 Jun 2025 Bug fix: - [#1255](https://github.com/elysiajs/elysia/issues/1255) regression in serving an imported HTML file - [#1251](https://github.com/elysiajs/elysia/issues/1251) property 'status' does not exist onError function - [#1247](https://github.com/elysiajs/elysia/pull/1247) ensure WebSockets get routed properly without AoT compilation - [#1246](https://github.com/elysiajs/elysia/issues/1246) property 'timeout' does not exist on type 'Server' - [#1245](https://github.com/elysiajs/elysia/issues/1245) error on onAfterHandle (no property 'response') - [#1239](https://github.com/elysiajs/elysia/issues/1239) t.Files validator breaks for response schema - [#1187](https://github.com/elysiajs/elysia/pull/1187), [#1169](https://github.com/elysiajs/elysia/issues/1169) websocket beforeLoad not being executed # 1.3.4 - 3 Jun 2025 Feature: - sse helper Bug fix: - [#1237](https://github.com/elysiajs/elysia/issues/1237) ws in a group merge error - [#1235](https://github.com/elysiajs/elysia/issues/1235) errors not handled correctly in resolve hook on dynamic mode - [#1234](https://github.com/elysiajs/elysia/issues/1234) optional path parameters can't follow required ones - [#1232](https://github.com/elysiajs/elysia/issues/1232) t.Files fails with array of files Change: - When yield is not sse, content-type is set to either `text/plain` or `application/json` based on the response type # 1.3.3 - 27 May 2025 Bug fix: - mapResponseContext is not passed to compose - await `ElysiaFile` when not using Bun - export `adapter/utils` # 1.3.2 - 27 May 2025 Feature: - Support Bun native static response per method for Bun >= 1.2.14 - [#1213](https://github.com/elysiajs/elysia/issues/1213) trace.time is undefined in .trace() callback Improvement: - implement all universal type - offload `AsyncGenerator`, `ReplaceFile` from Eden Treaty to `CreateEden` - [#1223](https://github.com/elysiajs/elysia/issues/1223) infer `status(200)` response from handler if not specified - [#1185](https://github.com/elysiajs/elysia/issues/1185) use non-greedy match for `isContextPassToFunction` to prevent false positive # 1.3.1 - 8 May 2025 Bug fix: - [#1200](https://github.com/elysiajs/elysia/issues/1200) limited Bun Router to supported method - [#1199](https://github.com/elysiajs/elysia/issues/1199) object are not normalized when t.Transform is provided - [#1198](https://github.com/elysiajs/elysia/issues/1198), [#1188](https://github.com/elysiajs/elysia/issues/1188), [#1186](https://github.com/elysiajs/elysia/issues/1186) exclude wildcard route from Bun router - [#1197](https://github.com/elysiajs/elysia/issues/1197) leave incorrect union field as-is - [#1195](https://github.com/elysiajs/elysia/issues/1195) invalid onAfterHandle typing - [#1194](https://github.com/elysiajs/elysia/issues/1194) normalize array response - [#1193](https://github.com/elysiajs/elysia/issues/1193) undefine value.schema.noValidate - [#1192](https://github.com/elysiajs/elysia/issues/1192) using a macro inside a group does not call the handler when using the `precompile` option - [#1190](https://github.com/elysiajs/elysia/issues/1190) derive and resolve handlers not being executed on WS context - [#1189](https://github.com/elysiajs/elysia/issues/1189) Type Inference Issue with Eden Treaty Group Endpoints - [#1185](https://github.com/elysiajs/elysia/issues/1185) path is missing from Context when Bun System Router is used - [#1184](https://github.com/elysiajs/elysia/issues/1184) Missing `mapEarlyResponse` on Bun System Router Change: - update `exact-mirror` to `0.1.2` # 1.3.0 - 5 May 2025 Feature: - add `exactMirror` - add `systemRouter` config - `standalone Validator` - add `Elysia.Ref` for referencing schema with autocompletion instead of `t.Ref` - support Ref inside inline schema - add sucrose cache - new validation `t.Form`, `t.NoValidate` - use `file-type` to check file type - add `INVALID_FILE_TYPE` error - add `sanitize` options Improvement: - `encodeSchema` now stable and enabled by default - optimize types - reduce redundant type check when using Encode - optimize isAsync - unwrap Definition['typebox'] by default to prevent unnecessary UnwrapTypeModule call - Elysia.form can now be type check - refactor type-system - refactor `_types` into `~Types` - using aot compilation to check for custom Elysia type, eg. Numeric - refactor `app.router.static`, and move static router code generation to compile phase - optimize memory usage on `add`, `_use`, and some utility functions - improve start up time on multiple route - dynamically create cookie validator as needed in compilation process - reduce object cloning - optimize start index for finding delimiter of a content type header - Promise can now be a static response - `ParseError` now keeps stack trace - refactor `parseQuery` and `parseQueryFromURL` - add `config` options to `mount` - recompile automatically after async modules is mounted - support macro on when hook has function - support resolve macro on ws - [#1146](https://github.com/elysiajs/elysia/pull/1146) add support to return web API's File from handler - [#1165](https://github.com/elysiajs/elysia/pull/1165) skip non-numeric status codes in response schema validation - [#1177](https://github.com/elysiajs/elysia/issues/1177) cookie does not sign when an error is thrown Bug fix: - `Response` returned from `onError` is using octet stream - unintentional memory allocation when using `mergeObjectArray` - handle empty space on Date query Change: - only provide `c.request` to mapResponse when `maybeStream` is true - use plain object for `routeTree` instead of `Map` - remove `compressHistoryHook` and `decompressHistoryHook` - webstandard handler now return `text/plain` if not on Bun - use non const value for `decorate` unless explicitly specified - `Elysia.mount` now set `detail.hide = true` by default Breaking Change: - remove `as('plugin')` in favor of `as('scoped')` - remove root `index` for Eden Treaty - remove `websocket` from `ElysiaAdapter` - remove `inference.request` # 1.2.25 - 6 Mar 2025 Bug fix: - [#1108](https://github.com/elysiajs/elysia/issues/1108) use validation response instead of return type when schema is provided - [#1105](https://github.com/elysiajs/elysia/pull/1105), [#1003](https://github.com/elysiajs/elysia/issues/1003) invalid parsing body with missed fields if used object model # 1.2.24 - 2 Mar 2025 Bug fix: - 200 object response is not inferring type in type-level - [#1091](https://github.com/elysiajs/elysia/issues/1091) route is not defined when using trace # 1.2.23 - 25 Feb 2025 Bug fix: - [#1087](https://github.com/elysiajs/elysia/pull/1087) websocket to parse string array - [#1088](https://github.com/elysiajs/elysia/pull/1088) infinite loop when inference body is empty # 1.2.22 - 24 Feb 2025 Bug fix: - [#1074](https://github.com/elysiajs/elysia/pull/1074) hasTransform doesn't detect Transform on root - [#873](https://github.com/elysiajs/elysia/issues/873#issuecomment-2676628776) encode before type check # 1.2.21 - 22 Feb 2025 Bug fix: - [#671](https://github.com/elysiajs/elysia/issues/671#issuecomment-2676263442) Transform inside t.Intersect isn't detected # 1.2.20 - 22 Feb 2025 Bug fix: - [#671](https://github.com/elysiajs/elysia/issues/671#issuecomment-2675777040) Transform query schema check fails - model type # 1.2.19 - 22 Feb 2025 Bug fix: - [#1078](https://github.com/elysiajs/elysia/issues/1078) array string default to '[]' instead of undefined # 1.2.18 - 22 Feb 2025 Bug fix: - duplicated static route may cause index conflict resulting in incorrect route # 1.2.17 - 21 Feb 2025 Bug fix: - `.mount` doesn't return pass entire request # 1.2.16 - 21 Feb 2025 Improvement: - `AfterHandler` infer response type Change: - [#1068](https://github.com/elysiajs/elysia/issues/1068) update `@sinclair/typebox` to `0.34.27` Bug fix: - [#1075](https://github.com/elysiajs/elysia/issues/1075) nested async plugins mismatch routes to handlers - [#1073](https://github.com/elysiajs/elysia/issues/1073) file type validation not working - [#1070](https://github.com/elysiajs/elysia/issues/1070) .mount is mutating the incoming request method - mount path is incorrect when using prefix with trailing `*` - [#873](https://github.com/elysiajs/elysia/issues/873) add `experimental.encodeSchema` for custom `Transform` Encode type # 1.2.15 - 19 Feb 2025 Bug fix: - [#1067](https://github.com/elysiajs/elysia/issues/1067) recompile async plugin once registered - [#1052](https://github.com/elysiajs/elysia/issues/1052) webSocket errors not getting catched by error handler - [#1038](https://github.com/elysiajs/elysia/issues/1038) incorrect type inference with deferred modules leads to TypeErrors in runtime - [#1015](https://github.com/elysiajs/elysia/issues/1015) using a model by name in route query leads to type mispatch, yet validation succeeds if doesn't use Ref - [#1047](https://github.com/elysiajs/elysia/issues/1047) ampersand in URL search params is discarded - detect `Transform` inside `t.Array` in `hasTransform` Improvement: - add test cases for `hasTransform` - `hasTransform` now supports Union, Intersect - remove redundant `decodeURIComponent` in nested query # 1.2.14 - 17 Feb 2025 Feature: - parse nuqs string array format if query is specified as `t.Array(t.String())` Improvement: - handle recursive nested async plugin - Response now handle proxy streaming - [#971](https://github.com/elysiajs/elysia/issues/971) wrap import("fs/promises") AND wrap import("fs") in try-catch to avoid error (silly me, tee-hee~) - handle nested array property swap for `replaceSchemaType` Breaking Change: - [Internal] `Elysia.modules` now return void # 1.2.13 - 16 Feb 2025 Improvement: - [#977](https://github.com/elysiajs/elysia/pull/977) use Registry instead of TypeSystem - remove redundant switch-case for path mapping when strictPath is disabled and path is overlapped - remove redundant allocation for nativeStaticHanlder when strictPath path is overlapped Bug fix: - [#1062](https://github.com/elysiajs/elysia/pull/1062) correctly set t.Date() defaults - [#1050](https://github.com/elysiajs/elysia/issues/1050) app.onRequest(ctx => {ctx.server}): Can't find variable: getServer - [#1040](https://github.com/elysiajs/elysia/pull/1040) undefined route context on aot=false - [#1017](https://github.com/elysiajs/elysia/pull/1017) replace t.Number() for Type.Integer() - [#976](https://github.com/elysiajs/elysia/pull/976) error responses with aot: false - [#975](https://github.com/elysiajs/elysia/pull/975) await nested async plugins - [#971](https://github.com/elysiajs/elysia/issues/971) wrap import("fs/promises") in try-catch to avoid error - [discord](https://discord.com/channels/1044804142461362206/1289400305506844672/1289400305506844672) file format doesn't check for '*' format # 1.2.12 - 4 Feb 2025 Bug fix: - warn when non-existing macro is used - parser doesn't generate optimize instruction # 1.2.11 - 1 Feb 2025 Feature: - Reduce memory usage: - Compressed lifecycle event - Avoid unnecessary declaration in compose.ts - Lazily build radix tree for dynamic router Change: - Update TypeBox to 0.34.15 Bug fix: - [#1039](vhttps://github.com/elysiajs/elysia/issues/1039) Elysia fails to start with an error inside its own code when using decorate twice with Object.create(null) - [#1005](https://github.com/elysiajs/elysia/issues/1005) Parsing malformed body with NODE_ENV 'production' results in UNKNOWN error - [#1037](https://github.com/elysiajs/elysia/issues/1037) Validation errors in production throw undefined is not an object (evaluating 'error2.schema') - [#1036](https://github.com/elysiajs/elysia/issues/1036) Support Bun HTML import # 1.2.10 - 5 Jan 2025 Feature: - add shorthand property for macro function Improvement: - use `deuri` instead of `fast-decode-uri-component` - [#985](https://github.com/elysiajs/elysia/issues/985) MaybeEmpty and Nullable should have options args Bug fix: - Macro function doesn't inherits local/scoped derive and resolve in type-level # 1.2.9 - 28 Dec 2024 Bug fix: - Resolve macro unintentionally return instead of assign new context # 1.2.8 - 27 Dec 2024 Bug fix: - [#966](https://github.com/elysiajs/elysia/issues/966) generic error somehow return 200 # 1.2.7 - 27 Dec 2024 Bug fix: - macro doesn't work with guard - [#981](https://github.com/elysiajs/elysia/issues/981) unable to deference schema, create default, and coerce value - [#966](https://github.com/elysiajs/elysia/issues/966) `error`'s value return as-if when thrown - [#964](https://github.com/elysiajs/elysia/issues/964) InvalidCookieSignature errors are not caught by onError - [#952](https://github.com/elysiajs/elysia/issues/952) onAfterResponse does not provide mapped response value unless aot is disabled - `mapResponse.response` is `{}` if no response schema is provided - Response doesn't reconcile when handler return `Response` is used with `mapResponse` - `onError` now accepts `error` as `number` when `Elysia.error` is thrown (but not returned) # 1.2.6 - 25 Dec 2024 Bug fix: - mapResponse with onError caused compilation error # 1.2.5 - 25 Dec 2024 Bug fix: - define universal/file in package export # 1.2.4 - 25 Dec 2024 Bug fix: - performance regression from eager access abortSignal # 1.2.3 - 25 Dec 2024 Bug fix: - [#973](https://github.com/elysiajs/elysia/issues/973) Parsing malformed body results in `UNKNOWN`-Error instead of `ParseError` - [#971](https://github.com/elysiajs/elysia/issues/971) remove top level import, use dynamic import instead - [#969](https://github.com/elysiajs/elysia/issues/969) Invalid context on `.onStart`, `.onStop` - [#965](https://github.com/elysiajs/elysia/issues/965) [Composer] failed to generate optimized handler. Unexpected identifier 'mapCompactResponse' - [#962](https://github.com/elysiajs/elysia/pull/962) fix schema default value when AOT is of - decorator name with space is not working # 1.2.2 - 24 Dec 2024 Bug fix: - conditional import and require # 1.2.1 - 23 Dec 2024 Bug fix: - conditional import for fs - object macro parameter maybe array - optional return for macro # 1.2.0 - 23 Dec 2024 Feature: - Commitment to Universal Runtime Support - Node Adapter - Web Standard Adapter - Bun Adapter - Universal Utilities - Name parser - Add resolve support to Macro - Improve WebSocket - Support ping, pong and latest Bun feature - Match type declaration with Bun - Support for return, yield - Match Context type - Performance Improvement - Entire rewrite - bind over getter return - Infer 422 validation - Compilation minification - Validation Stuff - t.MaybeArray - Typebox Module & Nested model - Inline module Improvement: - Memory Usage - [Internal] Register loosePath in compilation process to reduce memory usage and reduce registration time from O(2n) to O(n) - Try to accept and coerce different version of Elysia plugin - Event Listener now infers path parameter automatically based on scope - Add ‘scoped’ to bulk `as` for casting type to ‘scoped’ similar to ‘plugin’ Change: - Update `cookie` to 1.0.1 - Update TypeBox to 0.33 - `content-length` now accept number Breaking Change: - [Internal] Remove router internal property static.http.staticHandlers - [Internal] Router history compile now link with history composed # 1.1.27 - 23 Dec 2024 Bug fix: - [#963](https://github.com/elysiajs/elysia/pull/963) array parser on query string when AOT is off - [#961](https://github.com/elysiajs/elysia/pull/961) literal handler when AOT is off # 1.1.26 - 4 Dev 2024 Bug fix: - [#907](https://github.com/elysiajs/elysia/issues/907), [#872](https://github.com/elysiajs/elysia/issues/872), [#926](https://github.com/elysiajs/elysia/issues/926) BooleanString is not behave correctly if property is not provided - [#929](https://github.com/elysiajs/elysia/issues/929) Non-ASCII characters cause querystring index to be incorrectly slice - [#912](https://github.com/elysiajs/elysia/issues/912) handle JavaScript date numeric offset # 1.1.25 - 14 Nov 2024 Bug fix: - [#908](https://github.com/elysiajs/elysia/pull/908) boolean-string converted to string - [#905](https://github.com/elysiajs/elysia/pull/905) avoid response normailization side effects Change: - don't minify identifiers in bun bundle # 1.1.24 - 31 Oct 2024 Security: - [#891](https://github.com/elysiajs/elysia/pull/891) Upgrade Cookie to 0.7.x to fix CVE-2024-47764 Bug fix: - [#885](https://github.com/elysiajs/elysia/pull/885) unwrap transform errors - [#903](https://github.com/elysiajs/elysia/pull/903) typebox object schemas without properties key # 1.1.23 - 22 Oct 2024 Bug fix: - Handle object with `.then` even if it's not promise (looking at you, Drizzle) # 1.1.22 - 13 Oct 2024 Bug fix: - Fix `set-cookie` to resent if value is accessed even without set # 1.1.21 - 13 Oct 2024 Improvement: - infer 200 response from handle if not specified # 1.1.20 - 10 Oct 2024 Bug fix: - merge guard and not specified hook responses status # 1.1.19 - 7 Oct 2024 Bug fix: - unable to return `error` from derive/resolve # 1.1.18 - 4 Oct 2024 Breaking change: - remove automatic conversion of 1-level deep object with file field to formdata - migration: wrap a response with `formdata` - (internal): remove `ELYSIA_RESPONSE` symbol - (internal) `error` now use `class ElysiaCustomStatusResponse` instead of plain object Improvement: - Optimize `object type` response mapping performance # 1.1.17 - 29 Sep 2024 Change: - Coerce number to numeric on body root automatically - Coerce boolean to booleanString on body root automatically Bug fix: - [#838](https://github.com/elysiajs/elysia/issues/838) invalid `onAfterResponse` typing - [#855](https://github.com/elysiajs/elysia/issues/855) Validation with Numeric & Number options doesn't work - [#843](https://github.com/elysiajs/elysia/issues/843) Resolve does not work with aot: false # 1.1.16 - 23 Sep 2024 Bug fix: - separate between `createStaticHandler` and `createNativeStaticHandler` for maintainability - performance degradation using inline fetch on text static response and file # 1.1.15 - 23 Sep 2024 Bug fix: - `createStaticResponse` unintentionally mutate `set.headers` # 1.1.14 - 23 Sep 2024 Feature: - add auto-completion to `Content-Type` headers Bug fix: - exclude file from Bun native static response until Bun support - set 'text/plain' for string if no content-type is set for native static response # 1.1.13 - 18 Sep 2024 Feature: - [#813](https://github.com/elysiajs/elysia/pull/813) allow UnionEnum to get readonly array by @BleedingDev Bug fix: - [#830](https://github.com/elysiajs/elysia/issues/830) Incorrect type for ws.publish - [#827](https://github.com/elysiajs/elysia/issues/827) returning a response is forcing application/json content-type - [#821](https://github.com/elysiajs/elysia/issues/821) handle "+" in query with validation - [#820](https://github.com/elysiajs/elysia/issues/820) params in hooks inside prefixed groups are incorrectly typed never - [#819](https://github.com/elysiajs/elysia/issues/819) setting cookie attribute before value cause cookie attribute to not be set - [#810](https://github.com/elysiajs/elysia/issues/810) wrong inference of response in afterResponse, includes status code # 1.1.12 - 4 Sep 2024 Feature: - setup provenance publish - [#808](https://github.com/elysiajs/elysia/pull/808) add UnionEnum type with JSON schema enum usage - [#807](https://github.com/elysiajs/elysia/pull/807) add closeActiveConnections to Elysia.stop() Bug fix: - [#808](https://github.com/elysiajs/elysia/pull/808) ArrayString type cast as Object instead of Array - config.nativeStaticResponse is not defined # 1.1.11 - 1 Sep 2024 Feature: - native Bun static response - can be disabled by setting `app.config.nativeStaticResponse = false` - [#93](https://github.com/elysiajs/elysia/issues/93) export TypeSystemPolicy - [#752](https://github.com/elysiajs/elysia/issues/752) tye coercion on dynamic mode Bug fix: - [#332](https://github.com/elysiajs/elysia/issues/332) mount() does not preserve body when fetching through http server - Using as('plugin') cast cause derive key to be unknown # 1.1.10 30 Aug 2024 Bug fix: - incorrect named export 'fasti-querystring' to 'fast-querystring' # 1.1.9 - 28 Aug 2024 Change: - getter fields no longer stringified to JSON by default on returning response Bug fix: - [#796](https://github.com/elysiajs/elysia/issues/796) ValueClone: Unable to clone value after 1.1.8 update - [#795](https://github.com/elysiajs/elysia/issues/795) Broken Dates after 1.1.8 update - [#793](https://github.com/elysiajs/elysia/issues/793) Unable to delete property. t.File() # 1.1.8 - 27 Aug 2024 Feature: - [#748](https://github.com/elysiajs/elysia/issues/748) add standardHostname config Bug fix: - [#787](https://github.com/elysiajs/elysia/issues/787) [#789](https://github.com/elysiajs/elysia/issues/789) [#737](https://github.com/elysiajs/elysia/issues/737) Unexpected TypeError on NODE_ENV=production in mapValueError - [#793](https://github.com/elysiajs/elysia/issues/793) unable to delete property t.File() - [#780](https://github.com/elysiajs/elysia/issues/780) error from sending empty body multipart/form-data - [#779](https://github.com/elysiajs/elysia/issues/779) custom errors thrown in onRequest are not usable when caught in onError - [#771](https://github.com/elysiajs/elysia/issues/771) error from body-parser when sent Content-Type header without body - [#679](https://github.com/elysiajs/elysia/issues/679) plugin registered by async inline function don't work - [#670](https://github.com/elysiajs/elysia/issues/670) support for classes and getter fields # 1.1.7 - 19 Aug 2024 Bug fix: - `parseQuery` is not parsing array on body Change: - rename `parseQuery` to `parseQueryFromURL` - export fast-querystring.js path # 1.1.6 - 12 Aug 2024 Feature: - [#763](https://github.com/elysiajs/elysia/pull/763) add hide in detail to hide route from OpenAPI/swagger - add streaming support for fetch proxy Bug fix: - [#776](https://github.com/elysiajs/elysia/issues/776) custom errors throw in onRequest do not get proper code set in onError # 1.1.5 - 2 Aug 2024 Feature: - refactor fastQuerystring using switch and bitwise flag Bug fix: - sucrose: invalid separateFunction on minified async function - [#758](https://github.com/elysiajs/elysia/issues/758) guard doesn't apply cookie schema # 1.1.4 - 23 Jul 2024 Feature: - [#718](https://github.com/elysiajs/elysia/pull/718) implement normalization support for class instances with getter functions Bug fix: - removeColonAlias accidentally slice -2 end index for last parameter - [#726](https://github.com/elysiajs/elysia/pull/726) lazy instantiation of `stringToStructureCoercions` - [#750](https://github.com/elysiajs/elysia/issues/750) Cookie: Right side of assignment cannot be destructured - [#749](https://github.com/elysiajs/elysia/issues/749) Query params following an array query are parsed as array items - [#751](https://github.com/elysiajs/elysia/issues/751) Dynamic mode response failed if null or undefined value is returned - [#741](https://github.com/elysiajs/elysia/issues/741) stream stringify object # 1.1.3 - 17 Jul 2024 Change: - sucrose: exact inference name - use `mapResponse` instead of `mapCompactResponse` for stream - [#727](https://github.com/elysiajs/elysia/issues/727) - defers first stream execution before returning response - [#729](https://github.com/elysiajs/elysia/issues/729) - [#722](https://github.com/elysiajs/elysia/issues/722) derive context is not passed to onError Bug fix: - `onError` with scope not being able to infer context type # 1.1.2 - 16 Jul 2024 Bug fix: - [#724](https://github.com/elysiajs/elysia/issues/724), [bun#12594](https://github.com/oven-sh/bun/issues/12594) sucrose: possibly fix `bun build --compile` not being able to infer first, and last context parameter - derive is being override by resolve in certain function - [#722](https://github.com/elysiajs/elysia/issues/722) Type error with global `app.derive` followed by onError - params on `onError` is now `{ [key in string]: string }` instead of `never` - [#721](https://github.com/elysiajs/elysia/issues/721) unexpected isContextPassToFunction: minified whitespace of arrow function causing inaccurate separateFunction # 1.1.1 - 16 Jul 2024 Breaking Change: - parse query as `string` instead of `string | string[]` unless specified # 1.1.0 - 16 Jul 2024 Feature: - Trace v2 - Normalization is on by default - Data type coercion - Guard as, bulk as cast - Response status coercion - Optional path parameter - Generator response stream Breaking Change: - Parse value as string for all validators unless explicitly specified. - See [50a5d92](https://github.com/elysiajs/elysia/commit/50a5d92ea3212c5f95f94552e4cb7d31b2c253ad), [44bf279](https://github.com/elysiajs/elysia/commit/44bf279c3752c6909533d19c83b24413d19d27fa). - Remove objects auto-parsing in query unless explicitly specified via query - Except query string as defined in RFC 3986, TLDR; query string could be either string or array of string. - Rename `onResponse` to `onAfterResponse` - [Internal] Remove $passthrough in favor of toResponse - [Internal] UnwrapRoute type now always resolve with status code Improvement: - Add auto-complete for `set.headers` - Add `server` property - `onError` supports array function - Parse query object with and without schema - Sucrose: improve isContextPassToFunction, and extractMainParameter stability - Add `replaceSchemaType` - Add `route` to `context` - Optimize recursive MacroToProperty type - Parse query array and object - Optimize code path for `composeGeneralHandler` - Add debug report on compiler panic - Reduce memory usage of route registration ~36% on large codebase - Reduce compilation code path - Remove trace inference - Reduce router compilation code path - removing route handler compilation cache (st${index}, stc${index}) - Add undefined union to cookie in case if cookie is not present - Optimize response status resolve type inference Change: - Deprecated `ObjectString` for parsing array - Using `Cookie` instead of `Cookie` if schema is not defined - Remove prototype poluation from hook - remove static analysis for query name - remove query replace '+' in favor removing static query analysis - mapResponse is now called in error event - reconcilation decorator in type level Bug fix: - Normalize headers accidentally use query validator check instead - `onError` missing trace symbol - Headers validator compilation is not cached - Deduplicate macro propagation - Websocket in nested group now work - Error response is not check unless successful status code is provided # 1.0.27 - 2 Jul 2024 Bug fix: - [#640](https://github.com/elysiajs/elysia/issues/640) Unable to access root level macros in plugins - [#606](https://github.com/elysiajs/elysia/issues/606) Object encoding in query parameters # 1.0.26 - 30 Jun 2024 Bug fix: - mapResponse is not called on beforeHandle, and afterHandle # 1.0.25 - 21 Jun 2024 Bug fix: - type is resolved as `File` if `@types/bun` is not installed when using with Eden Treaty # 1.0.24 - 18 Jun 2024 Bug fix: - `derive`, `resolve` support void return - [#677](https://github.com/elysiajs/elysia/issues/677) Query params validation for array of string fail # 1.0.23 - 9 Jun 2024 Feature: - add `toResponse` for mapping custom response - [#606](https://github.com/elysiajs/elysia/issues/606) Object encoding in query parameters Bug fix: - [#654](https://github.com/elysiajs/elysia/pull/654) set correct normalization behavior for addtional properties - [#649](https://github.com/elysiajs/elysia/pull/649) cookie decode value might be null - [#664](https://github.com/elysiajs/elysia/issues/664) "default" option is not being applied on validation - [#656](https://github.com/elysiajs/elysia/issues/656) ctx.query doesn't work in some case - set forceDynamicQuery to true by default - [#658](https://github.com/elysiajs/elysia/issues/658) aot does not recognize the use of ctx.body within a try catch - [#630](https://github.com/elysiajs/elysia/issues/630) accessing ctx.query directly breaks the object # 1.0.22 - 23 May 2024 Breaking Change: - set default cookie path to `/` Feature: - add `form` utility for returning explicit formdata - add object with image to return as `formdata` Bug fix: - return `Bun.file` by specifying `t.File()` and `t.Object({ any: t.File() })` as a response # 1.0.21 - 21 May 2024 Breaking Change: - `t.type({ error })` now accepts `(error: ({ type, validator, value, errors }) => unknown)` instead of `(error: (type, validator, value) => unknown)` Improvement: - `t.type({ error })` accepts `string | number | boolean | Object` instead of `string` - `t.type({ error })` return `string | number | boolean | Object | void` instead of `string` - add `errors: ValueError[]` to `t.type({ error({ errors }) {} })` Bug fix: - [#644](https://github.com/elysiajs/elysia/issues/644) redirect doesn't work with `aot: false` - [#641](https://github.com/elysiajs/elysia/issues/641) cookie schema validation doesn't work with `aot: true` - [#615](https://github.com/elysiajs/elysia/issues/615) highlight derive and resolve when using `onError` # 1.0.20 - 13 May 2024 Bug fix: - macro is not inherits inside group # 1.0.19 - 13 May 2024 Bug fix: - remove set.clone spread operator for mapping Response # 1.0.18 - 11 May 2024 Feature: - add support for partitioned cookie Bug fix: - recursive MacroToProperty type on unknown macro # 1.0.17 - 9 May 2024 Improvement: - add context.url to get full URL string (including query) - reduce query parsing instruction # 1.0.16 - 2 May 2024 Bug fix: - [ratelimit#28](https://github.com/rayriffy/elysia-rate-limit/issues/28) trace hang when using server-timing with rate-limit plugin # 1.0.15 - 27 Apr 2024 Feature: - add `redirect` function to `Context` Improvement: - sucrose: remove unreachable query bracket check, reduce bracket instruction - sucrose: query accessor keyword check at initialization instead of in loop - sucrose: remove accessor check - sucrose: skip query check for immediate return Change: - sucrose: add `isArrowReturn` to `separateFunction` - sucrose: skip inference queries check if `query` is not found Change: - allow custom parser when `type` is specified - add `contentType` to context - soft deprecate `contentType` as 2nd `parse` parameter Bug fix: - [#622](https://github.com/elysiajs/elysia/issues/622) sucrose: mistake cookie for query - duplicate format found - using `parse`, `type`, `body` generate invalid syntax # 1.0.14 - 22 Apr 2024 Improvement: - [#596](https://github.com/elysiajs/elysia/pull/596) account for 20x response status schemas for type safety Bug fix: - [#615](https://github.com/elysiajs/elysia/issues/615) - [588](https://github.com/elysiajs/elysia/issues/588) separate async derive/resolve function doesn't get await - primitive thrown result in invalid type # 1.0.12 - 5 Apr 2024 Improvement: - export `InferContext` and `InferHandler` Bug fix: - remove accidental `console.log` in `compile` # 1.0.12 - 5 Apr 2024 Feature: - add `InferContext` Bug fix: - returning null with response validation cause error # 1.0.11 - 2 Apr 2024 Bug fix: - possibly fix for "Duplicate type kind 'Files' detected" - add ajv-formats - [#562](https://github.com/elysiajs/elysia/pull/575) %26 (&) to be interpreted as & (query separator) # 1.0.10 - 30 Mar 2024 Bug fix: - [ServerTiming#1](https://github.com/elysiajs/elysia-server-timing/issues/1) late beforeHandle on set trace inference doesn't produce exit instruction # 1.0.9 - 25 Mar 2024 Feature: - `Elysia.config.detail` constructor - shorthand `Elysia.tags` to constructor, `LocalHook` - guard inherits detail Bug fix: - inference link on `precompile: false` creating unnecessary instruction # 1.0.8 - 25 Mar 2024 Feature: - [#562](https://github.com/elysiajs/elysia/pull/562) add `normalize` config Improvement: - Scope cookie instruction to route level instead of global config - [#557](https://github.com/elysiajs/elysia/pull/557) cache tsc buildinfo for running faster - [#551](https://github.com/elysiajs/elysia/pull/551) use AnyElysia instead of inline Elysia Bug fix: - [#564](https://github.com/elysiajs/elysia/pull/564) Fixing "ReadableStream is locked" - [#552](https://github.com/elysiajs/elysia/pull/552) fix: shift promise in PromiseGroup even when rejected # 1.0.7 - 20 Mar 2024 Feature: - add Elysia.propagate to propagate hook type from 'local' to 'scoped' Improvement: - remove function.$elysia - remove function extension Bug fix: - duplicate macro call - [#548](https://github.com/elysiajs/elysia/issues/548) additional case for "accessing all query params using property name (ctx.query) doesn't work anymore" - [#599](https://github.com/elysiajs/elysia/issues/559) plugin with scoped settings not functioning correctly # 1.0.6 - 20 Mar 2024 Bug fix: - inline function doesn't propagate correctly on type level # 1.0.5 - 19 Mar 2024 Improvement: - using regex for date pattern matching before using new Date validation - using tsc to emit declaration file instead of tsup - add `mapResponse` to MacroManager Bug fix: - Ephemeral and Volatile type isn't recognize by MacroManager - inline guard cookie doesn't apply to local instance # 1.0.4 - 18 Mar 2024 Improvement: - resolve, derive soundness # 1.0.3 - 18 Mar 2024 Improvement: - Reduce instruction for static resource Bug fix: - Fix returning mulitple status code using `error` doesn't accept the response # 1.0.2 - 18 Mar 2024 Feature: - add `scoped` support for `derive` and `resolve` Improvement: - Type soundness - type inference performance improvement # 1.0.1 - 18 Mar 2024 Improvement: - `mapHandler` now check passthrough once instead of twice - exclude return type of`ELYSIA_RESPONSE` type from `derive` and `resolve` - throw error if `error` is return in `derive` and `resolve` - handle `return error` on `transform` - [#502](https://github.com/elysiajs/elysia/pull/502) merge response schema from parent scope Bug fix: - explicit `type: 'json'` with body schema throw unexpected `body.Check` is not a function - [#549](https://github.com/elysiajs/elysia/pull/549) await the .modules of nested Elysia instances - [#548](https://github.com/elysiajs/elysia/issues/548) Accessing all query params using property name (ctx.query) doesn't work anymore # 1.0.0 - 16 Mar 2024 Improvement: - fine-grained reactive cookie - using single source of truth for cookie - macro support for websocket - add `mapResolve` - add `{ as: 'global' | 'scoped' | 'local' }` to lifecycle event - add ephemeral type - inline `error` to handler - inline `error` has auto-completion and type checking based on status code - handler now check return type of `error` based on status code - utility `Elysia._types` for types inference - [#495](https://github.com/elysiajs/elysia/issues/495) Provide user friendly error for failed parse - handler now infers return type for error status for Treaty - `t.Date` now allow stringified date - improves type test case - add test case for all life-cycle - resolve, mapResolve, derive, mapDerive use ephemeral type to scope down accurately - inference query dynamic variable Breaking Change: - [#513](https://github.com/elysiajs/elysia/issues/513) lifecycle is now local first Change: - group private API property - move `Elysia.routes` to `Elysia.router.history` - detect possible json before return - unknown response now return as-is instead of JSON.stringify() - change Elysia validation error to JSON instead of string - static content evalute hook JIT instead of AOT Bug fix: - [#466](https://github.com/elysiajs/elysia/issues/466) Async Derive leaks request context to other requests if `aot: true` - [#505](https://github.com/elysiajs/elysia/issues/505) Empty ObjectString missing validation inside query schema - [#503](https://github.com/elysiajs/elysia/issues/503) Beta: undefined class when using decorate and derive - onStop callback called twice when calling .stop - mapDerive now resolve to `Singleton['derive']` instead of `Singleton['store']` - `ValidationError` doesn't return `content-type` as `application/json` - validate `error(status, value)` validate per status - derive/resolve always scoped to Global - duplicated onError call if not handled - [#516](https://github.com/elysiajs/elysia/issues/516) server timing breaks beforeHandle guards - cookie.remove() doesn't set correct cookie path # 0.8.17 - 12 Feb 2024 Feature: - [#474](https://github.com/elysiajs/elysia/pull/474) Numeric Cookie with length >= 16 cant be parsed to number - [#476](https://github.com/elysiajs/elysia/pull/476) Using a query key that contains a hyphen or a dot raises a SyntaxError - [#460](https://github.com/elysiajs/elysia/pull/460) - [#458](https://github.com/elysiajs/elysia/pull/458) Multiple scoped plugins do not register routes - [#457](https://github.com/elysiajs/elysia/pull/457) Elysia arguments scoped and prefix do not work at the same time Change: - [#472](https://github.com/elysiajs/elysia/pull/472) Move documentation issue template to documentation repository # 0.8.16 - 6 Feb 2024 Feature: - [#448](https://github.com/elysiajs/elysia/pull/448) BooleanString - @bogeychan Bug fix: - [#451](https://github.com/elysiajs/elysia/pull/464) handle spread operator use on possible null or undefined - [#460](https://github.com/elysiajs/elysia/pull/460) - [#457](https://github.com/elysiajs/elysia/pull/457) scoped plugin instances now respect the prefix property - [#458](https://github.com/elysiajs/elysia/pull/458) adding a second scoped plugin does not unmount the route handler of a previously added scoped instance anymore. # 0.8.15 - 30 Jan 2024 Bug fix: - [#451](https://github.com/elysiajs/elysia/issues/451) macro does not run when it should (macro deduplication) - [#450](https://github.com/elysiajs/elysia/issues/450) Local hook parse doesn't get executed with `aot: false` # 0.8.14 - 26 Jan 2024 Bug fix: - types are missing in `exports.*` - [#441](https://github.com/elysiajs/elysia/issues/441) Vite doesn't get bundle without main # 0.8.13 - 26 Jan 2024 Bug fix: - types is not import - bun build regression on export * from '@sinclair/typebox/system' - update memoirist to use mjs # 0.8.12 - 26 Jan 2024 Change: - using .mjs for es module # 0.8.11 - 26 Jan 2024 Change: - using tsup instead of swc - [#441](https://github.com/elysiajs/elysia/issues/441) remove nanoid, using web crypto randomInt instead Bug fix: - [#446](https://github.com/elysiajs/elysia/pull/446) numeric string check to use Number instead of parseInt - [#445](https://github.com/elysiajs/elysia/pull/445) empty body custom response when set.headers is empty # 0.8.10 - 24 Jan 2024 Bug fix: - [#440](https://github.com/elysiajs/elysia/pull/440) query params with + sign did not get converted - [#433](https://github.com/elysiajs/elysia/pull/433) remove crypto, unblock vite bundling, cloudflare worker support - [#422](https://github.com/elysiajs/elysia/pull/422) add check for instanceof if constructor.name doesn't match # 0.8.9 - 11 Jan 2024 Bug fix: - macro panic # 0.8.8. - 2 Jan 2024 Bug fix: - Add TypeBox back to Bun bundle # 0.8.7 - 1 Jan 2024 Improvement: - [#385](https://github.com/elysiajs/elysia/issues/385) If error is instanceof Response, respond with it Bug fix: - onRequest doesn't early return - handle thrown error function - [#373](https://github.com/elysiajs/elysia/issues/373) cookie is not set when File is return - [#379](https://github.com/elysiajs/elysia/issues/379) WebSocket: Sending a space character ' ' receives 0 - [#317](https://github.com/elysiajs/elysia/issues/317) Exclude TypeBox from bundling # 0.8.6. - 29 Dec 2023 Bug fix: - body without default value thrown Object.assign error # 0.8.5. - 27 Dec 2023 Bug fix: - Bun entry point # 0.8.4. - 27 Dec 2023 Bug fix: - macro caused an Object.entries cannot be undefined - `mapResponse` and `afterHandle` missing decorators # 0.8.3. - 23 Dec 2023 Bug fix: - add early return on `isContextPassToFunction` for static content to prevent invalid regex # 0.8.2 - 23 Dec 2023 Bug fix: - `ctx.path` and `ctx.qi` is missing when using `onRequest` # 0.8.1 - 23 Dec 2023 Bug fix: - `be` is undefined when using `afterResponse` with `mapResponse` # 0.8.0 - 23 Dec 2023 Feature: - `headers` initialization function - macro - static content - default property - error function - add stack trace to plugin checksum configurable by `config.analytic` (default to false) - new life-cycle - `resolve`: derive after validation - `mapResponse`: custom response mapping Improvement: - lazy query reference - add content-range header to `File` and `Blob` by default if etag is not used - update TypeBox to 0.32 - override lifecycle response of `be` and `af` Breaking Change: - `afterHandle` no longer early return Change: - change validation response to JSON - differentiate derive from `decorator['request']` as `decorator['derive']` - `derive` now don't show infer type in onRequest Bug fix: - remove `headers`, `path` from `PreContext` - remove `derive` from `PreContext` - Elysia type doesn't output custom `error` - `onStart` doesn't reflect server # 0.7.31 - 9 Dec 2023 Improvement: - [#345](https://github.com/elysiajs/elysia/pull/345) add font to `SchemaOptions` - Update `@types/cookie` to `^0.6.0` Bug fix: - [#338](https://github.com/elysiajs/elysia/pull/338) guard sandbox did not inherit global config. - [#330](https://github.com/elysiajs/elysia/pull/330) preserve query params for mounted handler - [#332](https://github.com/elysiajs/elysia/pull/332) reexport TSchema from typebox - [#319](https://github.com/elysiajs/elysia/pull/319) TypeBox Ref error when using Elysia.group() # 0.7.30 - 5 Dec 2023 Bug fix: - Emergency release override latest beta # 0.7.29 - 19 Nov 2023 Bug fix: - WebSocket params conflict with defined type - Inherits status code on custom error # 0.7.28 - 16 Nov 2023 Chore: - Update `cookie` to `0.6.0` Bug fix: - [#314](https://github.com/elysiajs/elysia/pull/314) Unable to dereference schema with 'undefined' when using t.Ref # 0.7.27 - 16 Nov 2023 Bug fix: - [#312](https://github.com/elysiajs/elysia/issues/312) default params type suggestion for WebSocket - [#310](https://github.com/elysiajs/elysia/issues/310) Preserve original hostname when using `.mount()` - [#309](https://github.com/elysiajs/elysia/issues/309) t.RegExp doesn't work due to requiring default value - [#308](https://github.com/elysiajs/elysia/issues/308) t.Numeric should not convert empty string to 0 - [#305](https://github.com/elysiajs/elysia/issues/305) Elysia({ scoped: true }) should still expose defined routes on type level - [#304](https://github.com/elysiajs/elysia/issues/304) Using a hook/guard/schema with a handler function and request without body results in a "Unexpected end of JSON input"-error - [#299](https://github.com/elysiajs/elysia/issues/299) Missing request.path parameter in .onRequest - [#289](https://github.com/elysiajs/elysia/issues/289) Ability to localize TypeBox errors - [#272](https://github.com/elysiajs/elysia/issues/272) onError handler has error property as undefined on Cloudflare Workers - [#210](https://github.com/elysiajs/elysia/issues/210) t.Numeric not validating properly - [#188](https://github.com/elysiajs/elysia/issues/188) Status codes of the error classes don't match the response through onError - [#140](https://github.com/elysiajs/elysia/issues/140) plugin hierarchy messes up derive function in child plugin - [#27](https://github.com/elysiajs/elysia/issues/27) Websocket definition in groups # 0.7.26 - 15 Nov 2023 Bug fix: - duplicated lifecycle event if using function plugin async # 0.7.25 - 14 Nov 2023 Bug fix: - Leaked type from `guard` callback and `group guard` # 0.7.24 - 8 Nov 2023 Bug fix: - add `ReadableStream` to response mapping to `mapResponse` # 0.7.23 - 8 Nov 2023 Bug fix: - Send `exit` status on early return with trace set # 0.7.22 - 8 Nov 2023 Change: - Rewrite `trace` Bug fix: - trace not awaiting multiple trace process - trace hang on early `beforeHandle` return - `afterHandle` with `trace.afterHandle` AoT cause duplicate value header # 0.7.21 - 27 Oct 2023 Bug fix: - [#281](https://github.com/elysiajs/elysia/pull/281) add cookie.remove options - add `await traceDone` to early return # 0.7.20 - 26 Oct 2023 Bug fix: - `trace` is stuck when inherits to plugin Improvement: - add unit test for `mapCompactResponse`, `Passthrough` # 0.7.19 - 25 Oct 2023 Bug fix: - add `$passthrough` for `mapCompactResponse` # 0.7.18 - 24 Oct 2023 Feature: - add map handler for `ReadableStream` - add `$passthrough` for custom property for response mapping Bug fix: - `.route` accept `string[]` instead of `string` Change: - remove `ElyEden` # 0.7.17 - 15 Oct 2023 Feature: - add `ElyEden` - re-add `id` to websocket Bug fix: - [#255](https://github.com/elysiajs/elysia/issues/255) removeCookie sends HTTP-Header that is ignored by the Browser - [#263](https://github.com/elysiajs/elysia/issues/263) http and websocket on same route - [#269](https://github.com/elysiajs/elysia/pull/269) Correct handling of Buffer object # 0.7.16 - 10 Oct 2023 Improvement: - `t.Cookie` cookie option type - [#253](https://github.com/elysiajs/elysia/pull/253) platform agnostic cookie - Decorator like `state`, `decorate` and `derive`, doesn't apply to WebSocket `data` - re-export `Static` from # 0.7.15 - 26 Sep 2023 Change: - Update TypeBox to 0.31.17 - [#218](https://github.com/elysiajs/elysia/pull/218) Fix [#213](https://github.com/elysiajs/elysia/pull/213) prepend async redefined routes (partial fix) - Using set `onRequest` doesn't set headers and status on empty error handler # 0.7.14 - 26 Sep 2023 Bug fix: - Make `t.Files` parameter optional - model remap now using `TSchema` instead of literal type for creating type abstraction # 0.7.13 - 25 Sep 2023 Improvement: - Using listener instead of microtick to handle `trace.set` - Set default cookie path to '/' Bug fix: - Duplicate group path when hook is provided # 0.7.12 - 23 Sep 2023 Bug fix: - Handle cookie expire time - Set default value of config.cookie.path to '/' # 0.7.11 - 23 Sep 2023 Improvement: - Skip cookie validation if schema is empty object Bug fix: - Accept cookie property from constructor when schema is not defined # 0.7.10 - 23 Sep 2023 Bug fix: - handle FFI object in deepMerge, fix Prisma # 0.7.9 - 23 Sep 2023 Bug fix: - async instance cause config to be undefined # 0.7.8 - 23 Sep 2023 Bug fix: - async instance cause type conflict # 0.7.7 - 22 Sep 2023 Bug fix: - [#210](https://github.com/elysiajs/elysia/issues/210) `t.Numeric` allowing plain `String` - `t.ObjectString` allowing plain `String` - [#209](https://github.com/elysiajs/elysia/issues/209) `t.MaybeEmpty` tolerate `null` and `undefined` - [#205](https://github.com/elysiajs/elysia/issues/205) WebSocket routes not working in plugins - [#195](https://github.com/elysiajs/elysia/pull/195), [#201](https://github.com/elysiajs/elysia/pull/201) allow WebSocket destructuring # 0.7.6 - 21 Sep 2023 Bug fix: - Separate return type by status # 0.7.5 - 21 Sep 2023 Bug fix: - inject derive to `GraceHandler` # 0.7.4 - 21 Sep 2023 Bug fix: - check for class-like object - add `GraceHandler` to access both `app` and `context` # 0.7.3 - 21 Sep 2023 Bug fix: - resolve 200 by default when type is not provided # 0.7.2 - 20 Sep 2023 Bug fix: - decorator and store is resolved as `undefined` in `onError` hook - deepMerge with Module object - Retain comment in `.d.ts` # 0.7.1 - 20 Sep 2023 Bug Fix: - Class property is removed when calling deepMerge # 0.7.0 - 20 Sep 2023 Feature: - rewrite type - rewrite Web Socket - add mapper method - add affix, prefix, suffix - trace - typeBox.Transfom - rewrite Type.ElysiaMeta to use TypeBox.Transform - new type: - t.Cookie - t.ObjectString - t.MaybeEmpty - t.Nullable - add `Context` to `onError` - lifecycle hook now accept array function - true encapsulation scope Improvement: - static Code Analysis now support rest parameter - breakdown dynamic router into single pipeline instead of inlining to static router to reduce memory usage - set `t.File` and `t.Files` to `File` instead of `Blob` - skip class instance merging - handle `UnknownContextPassToFunction` - [#157](https://github.com/elysiajs/elysia/pull/179) WebSocket - added unit tests and fixed example & api by @bogeychan - [#179](https://github.com/elysiajs/elysia/pull/179) add github action to run bun test by @arthurfiorette Breaking Change: - remove `ws` plugin, migrate to core - rename `addError` to `error` Change: - using single findDynamicRoute instead of inlining to static map - remove `mergician` - remove array routes due to problem with TypeScript Bug fix: - strictly validate response by default - `t.Numeric` not working on headers / query / params - `t.Optional(t.Object({ [name]: t.Numeric }))` causing error - add null check before converting `Numeric` - inherits store to instance plugin - handle class overlapping - [#187](https://github.com/elysiajs/elysia/pull/187) InternalServerError message fixed to "INTERNAL_SERVER_ERROR" instead of "NOT_FOUND" by @bogeychan - [#167](https://github.com/elysiajs/elysia/pull/167) mapEarlyResponse with aot on after handle # 0.6.24 - 18 Sep 2023 Feature: - [#149](https://github.com/elysiajs/elysia/pulls/149) support additional status codes in redirects Improvement: - [#157](https://github.com/elysiajs/elysia/pulls/157) added unit tests and fixed example & api Bug fix: - [#167](https://github.com/elysiajs/elysia/pulls/167) mapEarlyResponse with aot on after handle - [#160](https://github.com/elysiajs/elysia/pulls/160) typo in test suite name - [#152](https://github.com/elysiajs/elysia/pulls/152) bad code in Internal server error class # 0.6.23 - 12 Sep 2023 Bug fix: - Maximum callstack for duplicated deep class / object - [#121](https://github.com/elysiajs/elysia/issues/121) Cannot use PrismaClient in .decorate or .state # 0.6.22 - 11 Sep 2023 Bug fix: - Remove `const` and `RemoveDeepWritable` from decorate to allow function call # 0.6.21 - 10 Sep 2023 Feature: - [#112](https://github.com/elysiajs/elysia/issues/112) Route arrays # 0.6.20 - 9 Sep 2023 Bug fix: - [#107](https://github.com/elysiajs/elysia/issues/107) Elysia handler local hooks not recognizing registered errors on app instance # 0.6.19 - 7 Sep 2023 Bug fix: - Inherits state and error from plugin instance # 0.6.18 - 5 Sep 2023 Improvement: - Automatically parse File to `Files` if set # 0.6.17 - 4 Sep 2023 Bug fix: - [#98](https://github.com/elysiajs/elysia/issues/98) Add context.set.cookie to accept array of string - [#92](https://github.com/elysiajs/elysia/pull/92) WebSocket beforeHandle unable to access plugins / state / derive's # 0.6.16 - 1 Sep 2023 Bug fix: - inherits `onError` lifecycle from plugin instance # 0.6.15 - 31 Aug 2023 Bug fix: - inherits `set` if `Response` is returned # 0.6.14 - 28 Aug 2023 Bug fix: - deduplicate plugin via global model - duplicated life-cycle - top-down plugin deduplication - plugin life-cycle leak on new model - add `Elysia.scope` to contain lifecycle, store, and decorators # 0.6.13 - 28 Aug 2023 Bug fix: - make this.server.reload optional to make Node compatability work - duplicate path name when using prefix config with group - don't filter local event inside new plugin model group - Remove post.handler in return # 0.6.12 - 26 Aug 2023 Bug fix: - Make this.server.reload optional to make Node compatability work # 0.6.11 - 16 Aug 2023 Bug fix: - [#86](https://github.com/elysiajs/elysia/issues/86) Group prefix repeating on inline function callback - [#88](https://github.com/elysiajs/elysia/issues/88), onResponse hooks validation return non 400 # 0.6.10 - 13 Aug 2023 Bug fix: - Query is set to pathname when ? not presented in dynamic mode # 0.6.9 - 11 Aug 2023 Bug fix: - Derive not working on dynamic mode # 0.6.8 - 11 Aug 2023 Bug fix: - append routes on dynamic mode # 0.6.7 - 11 Aug 2023 Bug fix: - use: Plugin type inference # 0.6.6 - 11 Aug 2023 Bug fix: - Collide Elysia.prefix on other methods # 0.6.5 - 11 Aug 2023 Bug fix: - Collide Elysia.prefix type # 0.6.4 - 11 Aug 2023 Bug fix: - Collide Elysia.prefix type - Add skip group with prefix instance see [#85](https://github.com/elysiajs/elysia/pull/85) # 0.6.3 - 8 Aug 2023 Bug fix: - resolve .code and [ERROR_CODE] # 0.6.2 - 8 Aug 2023 Change: - Add **ErrorCode** symbol Bug fix: - Inline guard hook - Error code not handled - Set default query to {} when presented # 0.6.1 - 6 Aug 2023 Improvement: - Drop usage of `node:process` to support Cloudflare Worker # 0.6.0 - 6 Aug 2023 Feature: - Introducing Dynamic Mode (aot: false) - Introducing `.mount` - Introducing `.error` for handling Strict Error Type - Plugin checksum for plugin deduplication - Add `.onResponse` Improvement: - TypeBox 0.30 - AfterHandle now automatically maps the value - Using Bun Build for targeting Bun - Support Cloudflare worker with Dynamic Mode (and ENV) Change: - Moved registerSchemaPath to @elysiajs/swagger # 0.6.0-alpha.4 Feature: - Add `addError` to declaratively add Error to scope - Add `afterHandle` now can return a literal value instead of limited to only `Response` # 0.6.0-alpha.3 - 29 Jul 2023 Feature: - Introduce `.mount` - Add dynamic mode for TypeBox - Add $elysiaChecksum to deduplicate lifecycle event - Add $elysiaHookType to differentiate between global and local hook in `use` Fix: - Deduplication of plugin's lifecycle (see $elysiaHookType) Change: - Using Bun Build for target Bun Breaking Change: - [Internal] refactored `getSchemaValidator`, `getResponseSchemaValidator` to named parameters - [Internal] moved `registerSchemaPath` to `@elysiajs/swagger` # 0.6.0-alpha.2 Feature: - [Internal] Add qi (queryIndex) to context - Add `error` field to Elysia type system for adding custom error message # 0.6.0-alpha.1 Feature: - [Internal] Add support for accessing composedHandler via routes # 0.6.0-alpha.0 Feature: - Dynamic mode for Cloudflare Worker - Support for registering custom error code - Using `loosePath` (by default), and add `config.strictPath - Support for setting basePath - Recursive path typing Improvement: - Slighty improve type checking speed Bug Fix: - recursive schema collision causing infinite type Breaking Change: - Remove Elysia Symbol (Internal) # 0.5.25 - 25 Jul 2023 Bug fix: - ws resolve type to undefined instead of unknown cause unexpected type mismatched when not provided # 0.5.24 - 22 Jul 2023 Bug fix: - [#68](https://github.com/elysiajs/elysia/issues/68) invalid path params when using numeric # 0.5.23 - 20 Jul 2023 Bug fix: - [#68](https://github.com/elysiajs/elysia/issues/68) invalid optional query params when using numeric # 0.5.22 - 9 Jul 2023 Bug fix: - update onAfterHandle to be Response only # 0.5.20 - 23 Jun 2023 Bug fix: - async fn on Static Code Analysis # 0.5.19 - 19 Jun 2023 Bug fix: - optimize `ws` plugin type # 0.5.18 - 11 Jun 2023 Bug fix: - `mapEarlyResponse` is missing on request # 0.5.17 - 7 Jun 2023 Improvement: - Respect explicit body type first - `mapCompactResponse` on `null` or `undefined` type Bug fix: - Mapped unioned type on Static Code Analysis - `form` is `undefined` when using parsing `formData` # 0.5.16 - 5 Jun 2023 Improvement: - Respect inner scope of lifecycle first - Add type support for local `afterHandle` Bug fix: - `onAfterHandler` cause response to mutate on void # 0.5.15 - 4 Jun 2023 Improvement: - Map CommonJS module in package.json # 0.5.14 - 4 June 2023 Improvement: - Using tsc to compile CommonJS instead of SWC to support `module.exports` syntax # 0.5.13 - 4 June 2023 Bug fix: - Add loosen type for onError's code for defying custom error status # 0.5.12 - 3 June 2023 Bug fix: - Multiple onRequest cause error # 0.5.11 - 31 May 2023 Improvement: - Experimental basic support for Static Code Analysis in Nodejs # 0.5.10 - 31 May 2023 Bug fix: - h is undefined when using headers in Node environment - Update Memoirist to 0.1.4 to support full CommonJS # 0.5.9 - 30 May 2023 Improvement: - Add content-type support for 'none', 'arrayBuffer' / 'application/octet-stream' - Add type support type registration of wildcard params - Add support for 'config.basePath' # 0.5.8 - 27 May 2023 Improvement: - Add support for returning a class instance # 0.5.7 - 25 May 2023 Bug fix: - Bun is undefined on other runtime # 0.5.6 - 25 May 2023 Improvement: - Using `new Response` instead of factory `Response.json` # 0.5.5 - 25 May 2023 Improvement: - Using request.json() to handle application/json body instead of JSON.parse(await c.text()) # 0.5.4 - 25 May 2023 Improvement: - Add Static Code Analysis for conditional try-catch - Reduce usage of method to accessor # 0.5.3 - 22 May 2023 Improvement: - Add `mapCompactResponse` for static code analysis - Using `constructor.name` to inline object mapping - Using single assignment for URL destructuring - Using default map for dynamic route to remove static map label and break Bug fix: - Web Socket context.headers is empty [Elysia#46](https://github.com/elysiajs/elysia/issues/46) # 0.5.2 - 16 May 2023 Improvement: - Static Code Analysis for fallback route Bug fix: - Remove constant generic from `state` to be mutable # 0.5.1 - 16 May 2023 Bug fix: - Syntax error if multiple numeric type is set - Prevent fallthrough behavior of switch map # 0.5.0 - 15 May 2023 Improvement: - Add CommonJS support for running Elysia with Node adapter - Remove manual fragment mapping to speed up path extraction - Inline validator in `composeHandler` to improve performance - Use one time context assignment - Add support for lazy context injection via Static Code Analysis - Ensure response non nullability - Add unioned body validator check - Set default object handler to inherits - Using `constructor.name` mapping instead of `instanceof` to improve speed - Add dedicated error constructor to improve performance - Conditional literal fn for checking onRequest iteration - improve WebSocket type Bug fix: - Possible Breaking Change: - Rename `innerHandle` to `fetch` - to migrate: rename `.innerHandle` to `fetch` - Rename `.setModel` to `.model` - to migrate: rename `setModel` to `model` - Remove `hook.schema` to `hook` - to migrate: remove schema and curly brace `schema.type`: ```ts // from app.post('/', ({ body }) => body, { schema: { body: t.Object({ username: t.String() }) } }) // to app.post('/', ({ body }) => body, { body: t.Object({ username: t.String() }) }) ``` - remove `mapPathnameRegex` (internal) # 0.5.0-beta.8 - 15 May 2023 Bug fix: - it recompile on async # 0.5.0-beta.7 15 May 2023 Bug fix: - detect promise on parse - using swc to compile to commonjs # 0.5.0-beta.6 - 15 May 2023 Improvement: - Improve merge schema type # 0.5.0-beta.5 - 15 May 2023 Bug fix: - Add support for ALL method for dynamic path - Add support for parser in pre-compiled body # 0.5.0-beta.4 - 15 May 2023 Bug fix: - Use Memoirist instead of Raikiri in ws # 0.5.0-beta.3 - 15 May 2023 Improvement: - Static Code Analysis on derive # 0.5.0-beta.2 - 14 May 2023 Improvement: - Re-compile on lazy modules # 0.5.0-beta.1 - 14 May 2023 Improvement: - Merge nested schema type # 0.4.14 - 2 May 2023 Fix: - set default object handler to inherits # 0.4.13 - 28 Apr 2023 Fix: - emergency override experimental version # 0.4.12 - 26 Apr 2023 Fix: - CatchResponse to return 200 status code by default when using Eden Treaty # 0.4.11 - 26 Apr 2023 Fix: - response schema doesn't unwrap response type # 0.4.10 - 25 Apr 2023 Fix: - Update Raikiri stability # 0.4.9 - 21 Apr 2023 Improvement: - Add support for `parse` in websocket [#33](https://github.com/elysiajs/elysia/pull/33) Fix: - Inherits out-of-order `onError` life cycle in nested group - Update Raikiri to 0.1.2 to handle mangled part # 0.4.8 - 18 Apr 2023 Fix: - Fix LocalHandler doesn't check single type response # 0.4.7 - 18 Apr 2023 Improvement: - Update Raikiri to ^1.1.0 # 0.4.6 - 10 Apr 2023 Improvement: - perf: add static route main class - perf: reduce `ComposedHandler` to function instead of nested object Fix: - `group` and `guard` shouldn't decorate a request on type-level (acceptable on run-time level for shared memory) # 0.4.5 - 6 Apr 2023 Fix: - Using default value check for `set.status` instead truthy value # 0.4.4 - 6 Apr 2023 Improvement: - using `isNotEmpty` for `mapResponse` - pre check if `set.headers['Set-Cookie']` is array before converting to headers - using `mapPathnameAndQueryRegEx.exec(request.url)` instead of `request.url.match(mapPathnameAndQueryRegEx)` # 0.4.3 - 31 Mar 2023 Fix: - Scoped decorators # 0.4.2 - 31 Mar 2023 Improvement: - Use constructor name for faster handler matching - Map Promise # 0.4.1 - 31 Mar 2023 Fix: - remove type module from package.json # 0.4.0 - 30 Mar 2023 Feature: - Ahead of Time compilation - TypeBox 0.26 - Validate response per status instead of union - Add `if` for conditional route - Custom Validation Error Improvement: - Update TypeBox to 0.26.8 - Inline a declaration for response type - Refactor some type for faster response - Use Typebox `Error().First()` instead of iteration - Add `innerHandle` for returning an actual response (for benchmark) Breaking Change: - Separate `.fn` to `@elysiajs/fn` # 0.3.2 - 26 Mar 2023 Fix: - child to inhertis WebSocket plugin (https://github.com/elysiajs/elysia/issues/27) - multiple status response does not work with the group (https://github.com/elysiajs/elysia/issues/28) # 0.3.1 - 17 Mar 2023 Fix: - Wildcard fallback of Raikiri # 0.3.0 - 17 Mar 2023 Feature: - Elysia Fn - Suport `multipart/form-data` - `t.File` and `t.Files` for file validation - `schema.content` for specifying content type Improvement: - Add string format: 'email', 'uuid', 'date', 'date-time' - Update @sinclair/typebox to 0.25.24 - Update Raikiri to 0.2.0-beta.0 (ei) - Add file upload test thanks to #21 (@amirrezamahyari) - Pre compile lowercase method for Eden - Reduce complex instruction for most Elysia types - Change store type to `unknown` - Compile `ElysiaRoute` type to literal - Optimize type compliation, type inference and auto-completion - Improve type compilation speed - Improve TypeScript inference between plugin registration - Optimize TypeScript inference size - Context creation optimization - Use Raikiri router by default - Remove unused function - Refactor `registerSchemaPath` to support OpenAPI 3.0.3 - Add `error` inference for Eden - Mark `@sinclair/typebox` as optional `peerDenpendencies` Fix: - Raikiri 0.2 thrown error on not found - Union response with `t.File` is not working - Definitions isn't defined on Swagger - details are missing on group plugin - group plugin, isn't unable to compile schema - group is not exportable because EXPOSED is a private property - Multiple cookies doesn't set `content-type` to `application/json` - `EXPOSED` is not export when using `fn.permission` - Missing merged return type for `.ws` - Missing nanoid - context side-effects - `t.Files` in swagger is referring to single file - Eden response type is unknown - Unable to type `setModel` inference definition via Eden - Handle error thrown in non permission function - Exported variable has or is using name 'SCHEMA' from external module - Exported variable has or is using name 'DEFS' from external module - Possible errors for building Elysia app with `declaration: true` in `tsconfig.json` Breaking Change: - Rename `inject` to `derive` - Depreacate `ElysiaRoute`, changed to inline - Remove `derive` - Update from OpenAPI 2.x to OpenAPI 3.0.3 - Move context.store[SYMBOL] to meta[SYMBOL] # 0.3.0-rc.9 - 16 Mar 2023 Improvement: - Add string format: 'email', 'uuid', 'date', 'date-time' # 0.3.0-rc.8 - 16 Mar 2023 Fix: - Raikiri 0.2 thrown error on not found # 0.3.0-rc.7 - 16 Mar 2023 Improvement: - Update @sinclair/typebox to 0.25.24 - Update Raikiri to 0.2.0-beta.0 (ei) - Add file upload test thanks to #21 (@amirrezamahyari) # 0.3.0-rc.6 - 10 Mar 2023 Fix: - Union response with `t.File` is not working # 0.3.0-rc.5 - 10 Mar 2023 Fix: - Definitions isn't defined on Swagger - details are missing on group plugin - group plugin, isn't unable to compile schema - group is not exportable because EXPOSED is a private property # 0.3.0-rc.4 - 9 Mar 2023 Fix: - console.log while using cookie # 0.3.0-rc.3 - 9 Mar 2023 Breaking Change: - Rename `inject` to `derive` Fix: - Multiple cookies doesn't set `content-type` to `application/json` - `EXPOSED` is not export when using `fn.permission` # 0.3.0-rc.2 - 7 Mar 2023 Fix: - Missing merged return type for `.ws` # 0.3.0-rc.1 - 7 Mar 2023 Fix: - Missing nanoid # 0.3.0-beta.6 - 4 Mar 2023 Fix: - context side-effects # 0.3.0-beta.5 - 1 Mar 2023 Improvement: - Pre compile lowercase method for Eden # 0.3.0-beta.3 - 27 Feb 2023 Improvement: - ~33% faster for compiling type inference - Reduce complex instruction for most Elysia types - Change store type to `unknown` Fix: - `t.Files` in swagger is referring to single file - Eden response type is unknown # 0.3.0-beta.2 - 27 Feb 2023 Improvement: - Compile `ElysiaRoute` type to literal - Optimize type compliation, type inference and auto-completion - Improve type compilation speed by ~3x Fix: - Unable to type `setModel` inference definition via Eden Breaking Change: - Depreacate `ElysiaRoute`, changed to inline # 0.3.0-beta.1 - 25 Feb 2023 Fix: - Handle error thrown in non permission function # 0.3.0-beta.0 - 25 Feb 2023 Feature: - Elysia Fn - Suport `multipart/form-data` - `t.File` and `t.Files` for file validation - `schema.content` for specifying content type Improvement: - Improve TypeScript inference between plugin registration - Optimize TypeScript inference size - Context creation optimization - Use Raikiri router by default - Remove unused function - Refactor `registerSchemaPath` to support OpenAPI 3.0.3 - Add `error` inference for Eden - Mark `@sinclair/typebox` as optional `peerDenpendencies` Fix: - Exported variable has or is using name 'SCHEMA' from external module - Exported variable has or is using name 'DEFS' from external module - Possible errors for building Elysia app with `declaration: true` in `tsconfig.json` Breaking Change: - Remove `derive` - Update from OpenAPI 2.x to OpenAPI 3.0.3 - Move context.store[SYMBOL] to meta[SYMBOL] # 0.2.9 - 20 Feb 2023 Bug fix: - `group` doesn't inherits `onError` # 0.2.8 - 20 Feb 2023 Bug fix: - `group` doesn't inherits `onError` # 0.2.7 - 15 Feb 2023 Improvement: - Remove `bind(this)` # 0.2.6 - 10 Feb 2023 Feature: - Add supports for multiple cookie # 0.2.5 - 1 Feb 2023 Improvement: - Minor optimization # 0.2.4 - 1 Feb 2023 Improvement: - Using SWC to bundle and minification - Minor optimization # 0.2.3 - 30 Jan 2023 Improvement: - Update Raikiri to 0.0.0-beta.4 Change: - Remove strictPath option and enabled by default # 0.2.2 - 30 Jan 2023 Improvement: - Migrate from @medley/router to Raikiri - Minor optimization # 0.2.0-rc.1 - 24 Jan 2023 Improvement: - Map OpenAPI's schema detail on response - Fix Type instantiation is excessively deep and possibly infinite - Improve TypeScript inference time by removing recursive type in generic - Inferred body is never instead of unknown # 0.2.0-rc.0 - 23 Jan 2023 Feature: - Add support for reference model via `.model` - Add support for OpenAPI's `definitions` field # 0.2.0-beta.2 - 22 Jan 2023 Feature: - Add support for custom openapi field using `schema.detail` - Add support for custom code for `response` Improvement: - Unioned status type for response - Optimize TypeScript inference performance # 0.2.0-beta.1 - 22 Jan 2023 Breaking Change: - `onParse` now accepts `(context: PreContext, contentType: string)` instead of `(request: Request, contentType: string)` - To migrate, add `.request` to context to access `Request` Feature: - `onRequest` and `onParse` now can access `PreContext` - Support `application/x-www-form-urlencoded` by default Improvement: - body parser now parse `content-type` with extra attribute eg. `application/json;charset=utf-8` # 0.2.0-beta.0 - 17 Jan 2023 Feature: - Support for Async / lazy-load plugin Improvement: - Decode URI parameter path parameter - Handle union type correctly # 0.1.3 - 12 Jan 2023 Improvement: - Validate `Response` object - Union type inference on response # 0.1.2 - 31 Dec 2022 Bug fix: - onRequest doesn't run in `group` and `guard` # 0.1.1 - 28 Dec 2022 Improvement: - Parse encoded URI on querystring - Exclude URI fragment from querystring - Blasphemy hack for updating Elysia server using `--hot` - Exclude fragment on `getPath` # 0.1.0 - 24 Dec 2022 [[Reburn](https://youtu.be/xVPDszGmTgg?t=1139)] is the first *stable* beta release for Elysia. Happy Christmas, wishing you happy tonight as we release the first stable release of Elysia. With this API is now stabilized, and Elysia will focus on growing its ecosystem and plugins for common patterns. ## Eden Introducing [Eden](https://elysiajs.com/plugins/eden.html), a fully type-safe client for Elysia server like tRPC. A 600 bytes client for Elysia server, no code generation need, creating a fully type-safe, and auto-complete for both client and server. See Eden in action [on Twitter](https://twitter.com/saltyAom/status/1602362204799438848?s=20&t=yqyxaNx_W0MNK9u3wnaK3g) ## The fastest With a lot effort put into micro-optimization and re-architecture, Elysia is the fastest Bun web framework benchmarked on 24 December 2022, outperformed 2/3 category put into test. See benchmark results at [Bun http benchmark](https://github.com/SaltyAom/bun-http-framework-benchmark) ## Improved Documentation Elysia now have an improved documentation at [elysiajs.com](https://elysiajs.com). Now with a proper landing page, searchable content, and revised content put into. ## Afterward Merry Christmas, and happy new year. As 0.1 released, we recommended to give Elysia a try and build stuff with it. With the wonderful tools, we are happy to looking forward to see what wonderful software will you build. --- > Fly away, let me fly away > Never hide in dark > Head on, start a riot > Fly away, defying fate in my way > Crush it > Make it! > Feel > My > Heart! # 0.1.0.rc.10 - 21 Dec 2022 Change: - Remove cjs format as Bun can import ESM from CJS - Remove comment on build file, rely on .t.ds instead # 0.1.0.rc.9 - 19 Dec 2022 Change: - Support plugins which use `getPath`, and `mapQuery` on 0.1.0-rc.6 # 0.1.0.rc.8 - 16 Dec 2022 Improvement: - Infers type from `group`, and `guard` Change: - `Elysia.handle` now only accept valid `URL` # 0.1.0.rc.7 - 15 Dec 2022 Improvement: - Minor optimization - `Router.register` now returns type - Inline default bodyParser # 0.1.0.rc.6 - 13 Dec 2022 Fix: - `.listen` object is now optional # 0.1.0.rc.5 - 13 Dec 2022 Breaking Change: - `onError` change its type: ```typescript // Previously onError: (error: Error, code: ErrorCode) // Now onError: (params: { error: Error code: ErrorCode set: Context['set'] }) => any ``` To migrate, add curly brace to `onError` parameters. - `onRequest` change its type: ```typescript // Previously onRequest: (request: Request, store: Instance['Store']) => any // Now onRequest: (params: { request: Request, store: Instance['store'] set: Context['set'] }) ``` To migrate, add curly brace to `onRequest` parameters. Feature: - Manual patch for [bun#1435](https://github.com/oven-sh/bun/issues/1435), and unblock test suite for error handler. # 0.1.0.rc.4 - 12 Dec 2022 Fix: - Remove `console.log` for '*' # 0.1.0.rc.3 - 12 Dec 2022 Feature: - Strict type for `SCHEMA` - Infered type parameters for `SCHEMA` Fix: - Auto prefix path with `/` for non - Fallback catch all route for registered parameter # 0.1.0.rc.2 - 8 Dec 2022 Fix: - skip body parsing for 'HEAD' - missing response status on some error - compatability for cjs - add main fields for Bundlephobia supports - add declaration file for both esm and cjs - ship `src` for TypeScript support with `declare global` # 0.1.0.rc.1 - 6 Dec 2022 Stabilized API Feature: - add header access to context via `context.header` Breaking Change: - rename `schema.header` to `schema.headers` # 0.0.0-experimental.55 - 1 Dec 2022 Bug fix: - `inject` now accept `Context` # 0.0.0-experimental.54 - 1 Dec 2022 Feature: - `derive` to creating derive state - `inject` to decorate method based on context # 0.0.0-experimental.53 - 24 Nov 2022 Feature: - `.all` for registering path with any method Improvement: - `getSchemaValidator` now infer output type to be reusable with `@kingworldjs/trpc` Bug fix: - `handler.hooks` is undefined on 404 # 0.0.0-experimental.52 - 23 Nov 2022 Improvement: - Decorators is now lazily allocate - `.serve` now accept numberic string as port for convenient with `process.env` # 0.0.0-experimental.51 - 22 Nov 2022 [[Just Right Slow]](https://youtu.be/z7nN7ryqU28) introduce breaking major changes of KingWorld, specific on a plugin system. Previously, we define plugin by accepting 2 parameters, `KingWorld` and `Config` like this: ```typescript const plugin = (app: KingWorld, config) => app new KingWorld().use(plugin, { // Provide some config here }) ``` However, this has flaw by the design because: - No support for async plugin - No generic for type inference - Not possible to accept 3...n parameters (if need) - Hard/heavy work to get type inference To fix all of the problem above, KingWorld now accept only one parameter. A callback which return KingWorld Instance, but accept anything before that. ```typescript const plugin = (config) => (app: KingWorld) => app new KingWorld().use(plugin({ // provide some config here })) ``` This is a workaround just like the way to register async plugin before exp.51, we accept any parameters in a function which return callback of a KingWorld instance. This open a new possibility, plugin can now be async, generic type is now possible. More over that, decorate can now accept any parameters as it doesn't really affect any performance or any real restriction. Which means that something like this is now possible. ```typescript const a = (name: Name) => (app: KingWorld) => app.decorate(name, { hi: () => 'hi' }) new KingWorld() .use(a('customName')) // Retrieve generic from plugin, not possible before exp.51 .get({ customName } => customName.hi()) ``` This lead to even more safe with type safety, as you can now use any generic as you would like. The first plugin to leverage this feature is [jwt](https://github.com/saltyaom/kingworld-jwt) which can introduce jwt function with custom namespace which is type safe. Change: - new `decorators` property for assigning fast `Context` # 0.0.0-experimental.50 - 21 Nov 2022 Improvement: - Faster router.find performance - Faster query map performance - Early return on not found - Better type for `router` Change: - Remove `storeFactory` from router # 0.0.0-experimental.49 - 19 Nov 2022 Bug fix: - Conditionally return header in response # 0.0.0-experimental.48 - 18 Nov 2022 Bug fix: - Import Context as non-default - TypeScript's type not infering Context # 0.0.0-experimental.47 - 18 Nov 2022 Bug fix: - Remove `export default Context` as it's a type - Import Context as non-default # 0.0.0-experimental.46 - 18 Nov 2022 Bug fix: - Add custom response to `Blob` # 0.0.0-experimental.45 - 18 Nov 2022 Bug fix: - Set default HTTP status to 200 (https://github.com/oven-sh/bun/issues/1523) # 0.0.0-experimental.44 - 18 Nov 2022 Improvement: - Faster object iteration for setting headers - `KingWorld` config now accept `Serve` including `SSL` Change: - Use direct comparison for falsey value # 0.0.0-experimental.42 - 13 Nov 2022 Bug fix: - Router doesn't handle part which start with the same letter # 0.0.0-experimental.41 - 9 Nov 2022 Change: - Internal schema now use correct OpenAPI type (KingWorld need CORRECTION 💢💢) # 0.0.0-experimental.40 - 9 Nov 2022 Breaking Change: - `Context` is now `interface` (non-constructable) - `responseHeaders`, `status`, `redirect` is now replaced with `set` - To migrate: ```typescript // From app.get('/', ({ responseHeaders, status, redirect }) => { responseHeaders['server'] = 'KingWorld' status(401) redirect('/') }) // To app.get('/', ({ set }) => { set.headers['server'] = 'KingWorld' set.status = 401 set.redirect = '/' }) ``` Improvement: - Global `.schema` now infer type for handler - Add JSDocs for main method with example - `.listen` now accept `Bun.Server` as a callback function - Response support for `FileBlob` # 0.0.0-experimental.39 - 8 Nov 2022 Breaking Change: - `method` is changed to `route` Improvement: - `LocalHook` now prefers the nearest type instead of the merge - Merge the nearest schema first - add `contentType` as a second parameter for `BodyParser` Bug fix: - Correct type for `after handle` - Fix infinite cycling infer type for `Handler` # 0.0.0-experimental.38 - 7 Nov 2022 Bug fix: - Correct type for `afterHandle` # 0.0.0-experimental.37 - 6 Nov 2022 [[Sage]](https://youtu.be/rgM5VGYToQQ) is one of the major experimental releases and breaking changes of KingWorld. The major improvement of Sage is that it provides almost (if not) full support for TypeScript and type inference. ## Type Inference KingWorld has a complex type of system. It's built with the DRY principle in mind, to reduce the developer's workload. That's why KingWorld tries to type everything at its best, inferring type from your code into TypeScript's type. For example, writing schema with nested `guard` is instructed with type and validation. This ensures that your type will always be valid no matter what, and inferring type to your IDE automatically. ![FgqOZUYVUAAVv6a](https://user-images.githubusercontent.com/35027979/200132497-63d68331-cf96-4e12-9f4d-b6a8d142eb69.jpg) You can even type `response` to make your that you didn't leak any important data by forgetting to update the response when you're doing a migration. ## Validator KingWorld's validator now replaced `zod`, and `ajv` with `@sinclair/typebox`. With the new validator, validation is now faster than the previous version by 188x if you're using zod, and 4.1x if you're using ajv adapter. With Edge Computing in mind, refactoring to new validate dropped the unused packages and reduced size by 181.2KB. To give you an idea, KingWorld without a validator is around 10KB (non-gzipped). Memory usage is also reduced by almost half by changing the validator. ###### According to M1 Max running `example/simple.ts`, running exp.36 uses 24MB of memory while exp.37 use 12MB of memory This greatly improves the performance of KingWorld in a long run. ## Changelog Breaking Change: - Replace `zod`, `zod-to-json-schema`, `ajv`, with `@sinclair/typebox` Improvement: - `use` now accept any non `KingWorld<{}, any>` - `use` now combine typed between current instance and plugin - `use` now auto infer type if function is inline - `LocalHook` can now infer `params` type from path string Change: - `TypedSchema` is now replaced with `Instance['schema']` # 0.0.0-experimental.36 - 4 Nov 2022 Breaking Change: - `AfterRequestHandle` now accept (`Context`, `Response`) instead of `(Response, Context)` Improvement: - `.guard` now combine global and local recursively - `.use` now inherits schema # 0.0.0-experimental.35 - 3 Nov 2022 Bug fix: - Remove `console.log` on failed validation # 0.0.0-experimental.34 - 3 Nov 2022 Improvement: - Add Ajv 8.11.0 - Error log for validation is updated to `instancePath` # 0.0.0-experimental.33 - 3 Nov 2022 Feature: - `.schema` for global schema validation - `.start`, `.stop` and accept `KingWorld` as first parameter Improvement: - `.guard` now auto infer type from schema to `Handler` - scoped `.guard` now inherits hook - `NewInstance` now inherits `InheritSchema` Bug fix: - Rename `afterHandle` to `onAfterHandle` to match naming convention - Make `afterHandle` in `RegisterHook` optional - Internal type conversion between `Hook`, `LocalHook` # 0.0.0-experimental.32 - 2 Nov 2022 Feature: - add `afterHandle` hook Improvement: - Using `WithArray` to reduce redundant type Bug fix: - `beforeHandle` hook doesn't accept array # 0.0.0-experimental.31 - 2 Nov 2022 Bug fix: - Add `zod` by default # 0.0.0-experimental.30 - 2 Nov 2022 Bug fix: - Add `zod-to-json-schema` by default # 0.0.0-experimental.29 - 2 Nov 2022 [Regulus] This version introduces rework for internal architecture. Refine, and unify the structure of how KingWorld works internally. Although many refactoring might require, I can assure you that this is for the greater good, as the API refinement lay down a solid structure for the future of KingWorld. Thanks to API refinement, this version also introduced a lot of new interesting features, and many APIs simplified. Notable improvements and new features: - Define Schema, auto-infer type, and validation - Simplifying Handler's generic - Unifying Life Cycle into one property - Custom Error handler, and body-parser - Before start/stop and clean up effect # 0.0.0-experimental.28 - 30 Oct 2022 Happy halloween. This version named [GHOST FOOD] is one of the big improvement for KingWorld, I have been working on lately. It has a lot of feature change for better performance, and introduce lots of deprecation. Be sure to follow the migration section in `Breaking Change`. Feature: - Auto infer type from `plugin` after merging with `use` - `decorate` to extends `Context` method - add `addParser`, for custom handler for parsing body Breaking Change: - Moved `store` into `context.store` - To migrate: ```typescript // From app.get(({}, store) => store.a) // To app.get(({ store }) => store.a) ``` - `ref`, and `refFn` is now removed - Remove `Plugin` type, simplified Plugin type declaration - To migrate: ```typescript // From import type { Plugin } from 'kingworld' const a: Plugin = (app) => app // To import type { KingWorld } from 'kingworld' const a = (app: KingWorld) => app ``` - Migrate `Header` to `Record` - To migrate: ```typescript app.get("/", ({ responseHeader }) => { // From responseHeader.append('X-Powered-By', 'KingWorld') // To responseHeader['X-Powered-By', 'KingWorld'] return "KingWorld" }) ``` Change: - Store is now globally mutable Improvement: - Faster header initialization - Faster hook initialization # 0.0.0-experimental.27 - 23 Sep 2022 Feature: - Add `config.strictPath` for handling strict path # 0.0.0-experimental.26 - 10 Sep 2022 Improvement: - Improve `clone` performance - Inline `ref` value - Using object to store internal route Bug fix: - 404 on absolute path # 0.0.0-experimental.25 - 9 Sep 2022 Feature: - Auto infer typed for `params`, `state`, `ref` - `onRequest` now accept async function - `refFn` syntax sugar for adding fn as reference instead of `() => () => value` Improvement: - Switch from `@saltyaom/trek-router` to `@medley/router` - Using `clone` instead of flatten object - Refactor path fn for inline cache - Refactor `Context` to class Bug fix: - `.ref()` throw error when accept function # 0.0.0-experimental.24 - 21 Aug 2022 Change: - optimized for `await` # 0.0.0-experimental.23 - 21 Aug 2022 Feature: - Initialial config is now available, starting with `bodyLimit` config for limiting body size Breaking Change: - `ctx.body` is now a literal value instead of `Promise` - To migrate, simply remove `await` Change: - `default` now accept `Handler` instead of `EmptyHandler` Bug fix: - Default Error response now return `responseHeaders` - Possibly fixed parsing body error benchmark # 0.0.0-experimental.22 - 19 Aug 2022 Breaking Change: - context.body is now deprecated, use request.text() or request.json() instead Improvement: - Using reference header to increase json response speed - Remove `body` getter, setter Change: - Using `instanceof` to early return `Response` # 0.0.0-experimental.21 - 14 Aug 2022 Breaking Change: - `context.headers` now return `Header` instead of `Record` Feature: - Add status function to `Context` - `handle` now accept `number | Serve` - Remove `querystring` to support native Cloudflare Worker - Using raw headers check to parse `body` type # 0.0.0-experimental.20 - 13 Aug 2022 Feature: - Handle error as response # 0.0.0-experimental.19 - 13 Aug 2022 Change: - Use Array Spread instead of concat as it's faster by 475% - Update to @saltyaom/trek-router 0.0.7 as it's faster by 10% - Use array.length instead of array[0] as it's faster by 4% # 0.0.0-experimental.18 - 8 Aug 2022 Change: - With lazy initialization, KingWorld is faster by 15% (tested on 14' M1 Max) - Micro optimization - Remove `set` from headers # 0.0.0-experimental.17 - 15 Jul 2022 Change: - Remove dependencies: `fluent-json-schema`, `fluent-schema-validator` - Update `@saltyaom/trek-router` to `0.0.2` # 0.0.0-experimental.16 - 15 Jul 2022 Breaking Change: - Move `hook.schema` to separate plugin, [@kingworldjs/schema](https://github.com/saltyaom/kingworld-schema) - To migrate, simply move all `hook.schema` to `preHandler` instead Change: - Rename type `ParsedRequest` to `Context` - Exposed `#addHandler` to `_addHandler` # 0.0.0-experimental.15 - 14 Jul 2022 Breaking Change: - Rename `context.responseHeader` to `context.responseHeaders` - Change type of `responseHeaders` to `Header` instead of `Record` ================================================ FILE: CODE_OF_CONDUCT.md ================================================ # Contributor Covenant Code of Conduct ## Our Pledge We as members, contributors, and leaders pledge to make participation in our community a harassment-free experience for everyone, regardless of age, body size, visible or invisible disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation. We pledge to act and interact in ways that contribute to an open, welcoming, diverse, inclusive, and healthy community. ## Our Standards Examples of behavior that contributes to a positive environment for our community include: * Demonstrating empathy and kindness toward other people * Being respectful of differing opinions, viewpoints, and experiences * Giving and gracefully accepting constructive feedback * Accepting responsibility and apologizing to those affected by our mistakes, and learning from the experience * Focusing on what is best not just for us as individuals, but for the overall community Examples of unacceptable behavior include: * The use of sexualized language or imagery, and sexual attention or advances of any kind * Trolling, insulting or derogatory comments, and personal or political attacks * Public or private harassment * Publishing others' private information, such as a physical or email address, without their explicit permission * Contacting individual members, contributors, or leaders privately, outside designated community mechanisms, without their explicit permission * Other conduct which could reasonably be considered inappropriate in a professional setting ## Enforcement Responsibilities Maintaners are responsible for clarifying and enforcing our standards of acceptable behavior and will take appropriate and fair corrective action in response to any behavior that they deem inappropriate, threatening, offensive, or harmful. Mainteiners have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, and will communicate reasons for moderation decisions when appropriate. ## Scope This Code of Conduct applies within all community spaces, and also applies when an individual is officially representing the community in public spaces. Examples of representing our community include using an official e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. ## Enforcement Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to the maintainers. All complaints will be reviewed and investigated promptly and fairly. All maintainers are obligated to respect the privacy and security of the reporter of any incident. ## Enforcement Guidelines Community leaders will follow these Community Impact Guidelines in determining the consequences for any action they deem in violation of this Code of Conduct: ### 1. Correction **Community Impact**: Use of inappropriate language or other behavior deemed unprofessional or unwelcome in the community. **Consequence**: A private, written warning from maintainers, providing clarity around the nature of the violation and an explanation of why the behavior was inappropriate. A public apology may be requested. ### 2. Warning **Community Impact**: A violation through a single incident or series of actions. **Consequence**: A warning with consequences for continued behavior. No interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, for a specified period of time. This includes avoiding interactions in community spaces as well as external channels like social media. Violating these terms may lead to a temporary or permanent ban. ### 3. Temporary Ban **Community Impact**: A serious violation of community standards, including sustained inappropriate behavior. **Consequence**: A temporary ban from any sort of interaction or public communication with the community for a specified period of time. No public or private interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, is allowed during this period. Violating these terms may lead to a permanent ban. ### 4. Permanent Ban **Community Impact**: Demonstrating a pattern of violation of community standards, including sustained inappropriate behavior, harassment of an individual, or aggression toward or disparagement of classes of individuals. **Consequence**: A permanent ban from any sort of public interaction within the community. ## Attribution This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 2.0, available at . Community Impact Guidelines were inspired by [Mozilla's code of conduct enforcement ladder](https://github.com/mozilla/diversity). [homepage]: https://www.contributor-covenant.org For answers to common questions about this code of conduct, see the FAQ at . Translations are available at . ================================================ FILE: CONTRIBUTING.md ================================================ # Welcome to Elysia contributing guide Thank you for investing your time in contributing to Elysia! Any contribution you make will be amazing :sparkles:. Read our [Code of Conduct](./CODE_OF_CONDUCT.md) to keep our community approachable and respectable. In this guide you will get an overview of the contribution workflow from opening an issue, creating a PR, reviewing, and merging the PR. ## Setup Local Development Environment Elysia test cases are using [bun](https://bun.sh). Make sure you have the [latest version of bun](https://github.com/oven-sh/bun/releases) installed in your system. To run Elysia locally: 1. Clone this repository 2. run `bun install` in project's root 3. Run development with `bun run dev` ### Unit Testing All of the test files are located inside the [`test/`](test/) directory. Unit testing are powered by [bun's test](https://github.com/oven-sh/bun/tree/main/packages/bun-internal-test). - `bun test` to run all the test inside the [`test/`](test/) directory - `bun test test/.ts` to run a specific test ## Pull Request Guidelines Recommended to use `main` branch as a base to work on. #### General Recommendation - Please kindly verify that you have run test suite before request a review from maintainers with `bun run test` - We do not condone the usage of any form of plagiarism or copying code without proper attribution. - We do not tolerate disrespectful or inappropriate behavior within the community. - AI generated pull request without human interaction, review and supervision may result in close without further notice or ban from future contribution to Elysia. #### Adding New Features - Provide a reason why you would like to add this feature. Ideally before creating a PR, create a new issue with, explain the reason, tag as `feature request` and tag maintainer eg. "saltyaom" - It's recommended to add test cases to cover core feature of the feature you intent to add #### Fixing Bug - When opening an pull request fixing existing issue, please kindly include the issue link or id in the description - Provide a detailed description of the bug in the PR. Live demo preferred. - Add appropriate test coverage if applicable. - It's OK to have multiple small commits as you work on the PR. GitHub can automatically squash them before merging. ## Thanks :purple_heart: Thanks for all your contributions and efforts towards improving Elysia. We thank you for being part of our community :sparkles:! ================================================ FILE: LICENSE ================================================ Copyright 2022 saltyAom Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README.md ================================================

Elysia Banner

Elysia

Ergonomic Framework for Humans

Documentation | Discord | Sponsors


TypeScript with End-to-End Type Safety, type integrity, and exceptional developer experience. Supercharged by Bun.


![Elysia chan cover | bun creeate elysia app](https://github.com/user-attachments/assets/a649731a-8cba-4ca2-8424-6656cbf84956) ================================================ FILE: build.ts ================================================ import { $ } from 'bun' import { build } from 'tsup' import { fixImportsPlugin } from 'esbuild-fix-imports-plugin' import pack from './package.json' if ('elysia' in pack.dependencies) throw new Error("Error can't be a dependency of itself") await $`rm -rf dist` await build({ entry: ['src/**/*.ts'], outDir: 'dist', format: ['esm', 'cjs'], target: 'node20', minifySyntax: true, minifyWhitespace: false, minifyIdentifiers: false, splitting: false, sourcemap: false, cjsInterop: false, clean: true, bundle: false, external: ['@sinclair/typebox', 'file-type'], esbuildPlugins: [fixImportsPlugin()] }) await $`tsc --project tsconfig.dts.json` process.exit() ================================================ FILE: example/a.ts ================================================ import { Elysia, t } from '../src' import { req } from '../test/utils' const app = new Elysia() .get('/', async () => { const file = Bun.file('test/kyuukurarin.mp4') // Wrap the stream in another ReadableStream // perhaps we are concatenating streams or whatever const body = new ReadableStream({ async start(controller) { const reader = file.stream().getReader() try { while (true) { const { done, value } = await reader.read() if (done) break controller.enqueue(value) } controller.close() } catch (err) { controller.error(err) } finally { reader.releaseLock() } } }) // Returning the stream uses 100% for several minutes return body // Returning the same stream wrapped in a Response servers the stream in a fraction of a second // return new Response(body); }) .listen(3000) ================================================ FILE: example/async-recursive.ts ================================================ import { Elysia } from '../src' import { req } from '../test/utils' const delay = any>( callback: T, ms = 617 ): Promise> => Bun.sleep(ms).then(callback) const yay = () => delay(() => new Elysia().use(import('./lazy'))) const yay2 = () => delay(() => new Elysia().use(yay), 5) const yay3 = () => delay(() => new Elysia().use(yay2), 10) const wrapper = new Elysia().use(async () => delay(() => yay3(), 6)) const app = new Elysia().use(wrapper) await app.modules // should works app.handle(req('/lazy')) .then((x) => x.text()) .then(console.log) ================================================ FILE: example/body.ts ================================================ import { Elysia, t } from '../src' const app = new Elysia() // Add custom body parser .onParse(async ({ request, contentType }) => { switch (contentType) { case 'application/Elysia': return request.text() } }) .post('/', ({ body: { username } }) => `Hi ${username}`, { body: t.Object({ id: t.Number(), username: t.String() }) }) // Increase id by 1 from body before main handler .post('/transform', ({ body }) => body, { transform: ({ body }) => { body.id = body.id + 1 }, body: t.Object({ id: t.Number(), username: t.String() }), detail: { summary: 'A' } }) .post('/mirror', ({ body }) => body) .listen(3000) console.log('🦊 Elysia is running at :8080') ================================================ FILE: example/cookie.ts ================================================ import { Elysia, t } from '../src' const app = new Elysia({ cookie: { secrets: 'Fischl von Luftschloss Narfidort', sign: 'name' } }) .get( '/council', ({ cookie: { council } }) => (council.value = [ { name: 'Rin', affilation: 'Administration' } ]), { cookie: t.Cookie({ council: t.Array( t.Object({ name: t.String(), affilation: t.String() }) ) }) } ) .get('/create', ({ cookie: { name } }) => (name.value = 'Himari')) .get( '/update', ({ cookie: { name } }) => { name.value = 'seminar: Rio' name.value = 'seminar: Himari' name.maxAge = 86400 return name.value }, { cookie: t.Cookie({ name: t.Optional(t.String()) }) } ) .listen(3000) ================================================ FILE: example/counter.ts ================================================ import { Elysia } from '../src' new Elysia() .state('counter', 0) .get('/', ({ store }) => store.counter++) .listen(3000) ================================================ FILE: example/custom-response.ts ================================================ import { Elysia } from '../src' const prettyJson = new Elysia() .mapResponse(({ response }) => { if (response instanceof Object) return new Response(JSON.stringify(response, null, 4)) }) .as('scoped') new Elysia() .use(prettyJson) .get('/', () => ({ hello: 'world' })) .listen(3000) ================================================ FILE: example/derive.ts ================================================ import { Elysia } from '../src' new Elysia() .state('counter', 0) .derive(({ store }) => ({ increase() { store.counter++ } })) .derive(({ store }) => ({ store: { doubled: store.counter * 2, tripled: store.counter * 3 } })) .get('/', ({ increase, store }) => { increase() const { counter, doubled, tripled } = store return { counter, doubled, tripled } }) .listen(3000, ({ hostname, port }) => { console.log(`🦊 running at http://${hostname}:${port}`) }) ================================================ FILE: example/error.ts ================================================ import { Elysia, t } from '../src' new Elysia() .post('/', ({ body }) => body, { body: t.Object({ username: t.String(), password: t.String(), nested: t.Optional( t.Object({ hi: t.String() }) ) }), error({ error }) { console.log(error) } }) .listen(3000) ================================================ FILE: example/extension.ts ================================================ import { Elysia, t } from '../src' const a0 = new Elysia().get('/a0', () => 'a') const a1 = new Elysia().get('/a1', () => 'a') const a2 = new Elysia().get('/a2', () => 'a') const a3 = new Elysia().get('/a3', () => 'a') const a4 = new Elysia().get('/a4', () => 'a') const a5 = new Elysia().get('/a5', () => 'a') const a6 = new Elysia().get('/a6', () => 'a') const a7 = new Elysia().get('/a7', () => 'a') const a8 = new Elysia().get('/a8', () => 'a') const a9 = new Elysia().get('/a9', () => 'a') const a10 = new Elysia().get('/a10', () => 'a') const a11 = new Elysia().get('/a11', () => 'a') const a12 = new Elysia().get('/a12', () => 'a') const a13 = new Elysia().get('/a13', () => 'a') const a14 = new Elysia().get('/a14', () => 'a') const a15 = new Elysia().get('/a15', () => 'a') const a16 = new Elysia().get('/a16', () => 'a') const a17 = new Elysia().get('/a17', () => 'a') const a18 = new Elysia().get('/a18', () => 'a') const a19 = new Elysia().get('/a19', () => 'a') const a20 = new Elysia().get('/a20', () => 'a') const a21 = new Elysia().get('/a21', () => 'a') const a22 = new Elysia().get('/a22', () => 'a') const a23 = new Elysia().get('/a23', () => 'a') const a24 = new Elysia().get('/a24', () => 'a') const a25 = new Elysia().get('/a25', () => 'a') const a26 = new Elysia().get('/a26', () => 'a') const a27 = new Elysia().get('/a27', () => 'a') const a28 = new Elysia().get('/a28', () => 'a') const a29 = new Elysia().get('/a29', () => 'a') const a30 = new Elysia().get('/a30', () => 'a') const a31 = new Elysia().get('/a31', () => 'a') const a32 = new Elysia().get('/a32', () => 'a') const a33 = new Elysia().get('/a33', () => 'a') const a34 = new Elysia().get('/a34', () => 'a') const a35 = new Elysia().get('/a35', () => 'a') const a36 = new Elysia().get('/a36', () => 'a') const a37 = new Elysia().get('/a37', () => 'a') const a38 = new Elysia().get('/a38', () => 'a') const a39 = new Elysia().get('/a39', () => 'a') const a40 = new Elysia().get('/a40', () => 'a') const a41 = new Elysia().get('/a41', () => 'a') const a42 = new Elysia().get('/a42', () => 'a') const a43 = new Elysia().get('/a43', () => 'a') const a44 = new Elysia().get('/a44', () => 'a') const a45 = new Elysia().get('/a45', () => 'a') const a46 = new Elysia().get('/a46', () => 'a') const a47 = new Elysia().get('/a47', () => 'a') const a48 = new Elysia().get('/a48', () => 'a') const a49 = new Elysia().get('/a49', () => 'a') const a50 = new Elysia().get('/a50', () => 'a') const a51 = new Elysia().get('/a51', () => 'a') const a52 = new Elysia().get('/a52', () => 'a') const a53 = new Elysia().get('/a53', () => 'a') const a54 = new Elysia().get('/a54', () => 'a') const a55 = new Elysia().get('/a55', () => 'a') const a56 = new Elysia().get('/a56', () => 'a') const a57 = new Elysia().get('/a57', () => 'a') const a58 = new Elysia().get('/a58', () => 'a') const a59 = new Elysia().get('/a59', () => 'a') const a60 = new Elysia().get('/a60', () => 'a') const a61 = new Elysia().get('/a61', () => 'a') const a62 = new Elysia().get('/a62', () => 'a') const a63 = new Elysia().get('/a63', () => 'a') const a64 = new Elysia().get('/a64', () => 'a') const a65 = new Elysia().get('/a65', () => 'a') const a66 = new Elysia().get('/a66', () => 'a') const a67 = new Elysia().get('/a67', () => 'a') const a68 = new Elysia().get('/a68', () => 'a') const a69 = new Elysia().get('/a69', () => 'a') const a70 = new Elysia().get('/a70', () => 'a') const a71 = new Elysia().get('/a71', () => 'a') const a72 = new Elysia().get('/a72', () => 'a') const a73 = new Elysia().get('/a73', () => 'a') const a74 = new Elysia().get('/a74', () => 'a') const a75 = new Elysia().get('/a75', () => 'a') const a76 = new Elysia().get('/a76', () => 'a') const a77 = new Elysia().get('/a77', () => 'a') const a78 = new Elysia().get('/a78', () => 'a') const a79 = new Elysia().get('/a79', () => 'a') const a80 = new Elysia().get('/a80', () => 'a') const a81 = new Elysia().get('/a81', () => 'a') const a82 = new Elysia().get('/a82', () => 'a') const a83 = new Elysia().get('/a83', () => 'a') const a84 = new Elysia().get('/a84', () => 'a') const a85 = new Elysia().get('/a85', () => 'a') const a86 = new Elysia().get('/a86', () => 'a') const a87 = new Elysia().get('/a87', () => 'a') const a88 = new Elysia().get('/a88', () => 'a') const a89 = new Elysia().get('/a89', () => 'a') const a90 = new Elysia().get('/a90', () => 'a') const a91 = new Elysia().get('/a91', () => 'a') const a92 = new Elysia().get('/a92', () => 'a') const a93 = new Elysia().get('/a93', () => 'a') const a94 = new Elysia().get('/a94', () => 'a') const a95 = new Elysia().get('/a95', () => 'a') const a96 = new Elysia().get('/a96', () => 'a') const a97 = new Elysia().get('/a97', () => 'a') const a98 = new Elysia().get('/a98', () => 'a') const a99 = new Elysia().get('/a99', () => 'a') const app1 = new Elysia() .use(a0) .use(a1) .use(a2) .use(a3) .use(a4) .use(a5) .use(a6) .use(a7) .use(a8) .use(a9) .use(a10) .use(a11) .use(a12) .use(a13) .use(a14) .use(a15) .use(a16) .use(a17) .use(a18) .use(a19) .use(a20) .use(a21) .use(a22) .use(a23) .use(a24) .use(a25) .use(a26) .use(a27) .use(a28) .use(a29) .use(a30) .use(a31) .use(a32) .use(a33) .use(a34) .use(a35) .use(a36) .use(a37) .use(a38) .use(a39) .use(a40) const app2 = new Elysia() .use(a41) .use(a42) .use(a43) .use(a44) .use(a45) .use(a46) .use(a47) .use(a48) .use(a49) .use(a50) .use(a51) .use(a52) .use(a53) .use(a54) .use(a55) .use(a56) .use(a57) .use(a58) .use(a59) .use(a60) .use(a61) .use(a62) .use(a63) .use(a64) .use(a65) .use(a66) .use(a67) .use(a68) .use(a69) .use(a70) .use(a71) .use(a72) .use(a73) .use(a74) .use(a75) .use(a76) .use(a77) .use(a78) .use(a79) const app3 = new Elysia() .use(a80) .use(a81) .use(a82) .use(a83) .use(a84) .use(a85) .use(a86) .use(a87) .use(a88) .use(a89) .use(a90) .use(a91) .use(a92) .use(a93) .use(a94) .use(a95) .use(a96) .use(a97) .use(a98) .use(a99) const main = new Elysia().use(app1).use(app2).use(app3) ================================================ FILE: example/file.ts ================================================ import { Elysia, file } from '../src' import Page from './index.html' /** * Example of handle single static file * * @see https://github.com/elysiajs/elysia-static */ new Elysia() .headers({ server: 'Elysia' }) .onRequest(({ request }) => { console.log(request.method, request.url) }) .get('/', Page) .get('/tako', file('./example/takodachi.png')) .get('/mika.mp4', Bun.file('test/kyuukurarin.mp4')) .listen(3000) ================================================ FILE: example/guard.ts ================================================ import { Elysia, t } from '../src' new Elysia() .state('name', 'salt') .get('/', ({ store: { name } }) => `Hi ${name}`, { query: t.Object({ name: t.String() }) }) // If query 'name' is not preset, skip the whole handler .guard( { query: t.Object({ name: t.String() }) }, (app) => app // Query type is inherited from guard .get('/profile', ({ query }) => `Hi`) // Store is inherited .post('/name', ({ store: { name }, body, query }) => name, { body: t.Object({ id: t.Number({ minimum: 5 }), username: t.String(), profile: t.Object({ name: t.String() }) }) }) ) .listen(3000) ================================================ FILE: example/headers.ts ================================================ import { Elysia } from '../src' import cookie from '../src/index' new Elysia() .get('/', ({ set }) => { set.headers['x-powered-by'] = 'Elysia' set.status = 'Bad Request' }) .listen(3000) ================================================ FILE: example/hook.ts ================================================ import { Elysia } from '../src' new Elysia() // Create global mutable state .state('counter', 0) // Increase counter by 1 on every request on any handler .onTransform(({ store }) => { store.counter++ }) .get('/', ({ store: { counter } }) => counter, { // Increase counter only when this handler is called transform: [ ({ store }) => { store.counter++ }, ({ store }) => { store.counter++ } ] }) .listen(3000) ================================================ FILE: example/html-import.ts ================================================ import { Elysia, t } from '../src' import Page from './index.html' new Elysia() .get('/', Page) .get('/mika.mp4', Bun.file('test/kyuukurarin.mp4')) .listen(3000) ================================================ FILE: example/http.ts ================================================ import { Elysia, t } from '../src' const t1 = performance.now() const loggerPlugin = new Elysia() .get('/hi', () => 'Hi') .decorate('log', () => 'A') .decorate('date', () => new Date()) .state('fromPlugin', 'From Logger') .use((app) => app.state('abc', 'abc')) const app = new Elysia() .onRequest(({ set }) => { set.headers = { 'Access-Control-Allow-Origin': '*' } }) .use(loggerPlugin) .state('build', Date.now()) .get('/', () => 'Elysia') .get('/tako', () => Bun.file('./example/takodachi.png')) .get('/json', () => ({ hi: 'world' })) .get('/root/plugin/log', ({ log, store: { build } }) => { log() return build }) .get('/wildcard/*', () => 'Hi Wildcard') .get('/query', () => 'Elysia', { beforeHandle: ({ query }) => { console.log('Name:', query?.name) if (query?.name === 'aom') return 'Hi saltyaom' }, query: t.Object({ name: t.String() }) }) .post('/json', async ({ body }) => body, { body: t.Object({ name: t.String(), additional: t.String() }) }) .post('/transform-body', async ({ body }) => body, { beforeHandle: (ctx) => { ctx.body = { ...ctx.body, additional: 'Elysia' } }, body: t.Object({ name: t.String(), additional: t.String() }) }) .get('/id/:id', ({ params: { id } }) => id, { transform({ params }) { params.id = +params.id }, params: t.Object({ id: t.Number() }) }) .post('/new/:id', async ({ body, params }) => body, { params: t.Object({ id: t.Number() }), body: t.Object({ username: t.String() }) }) .get('/trailing-slash', () => 'A') .group('/group', (app) => app .onBeforeHandle<{ query: { name: string } }>(({ query }) => { if (query?.name === 'aom') return 'Hi saltyaom' }) .get('/', () => 'From Group') .get('/hi', () => 'HI GROUP') .get('/elysia', () => 'Welcome to Elysian Realm') .get('/fbk', () => 'FuBuKing') ) .get('/response-header', ({ set }) => { set.status = 404 set.headers['a'] = 'b' return 'A' }) .get('/this/is/my/deep/nested/root', () => 'Hi') .get('/build', ({ store: { build } }) => build) .get('/ref', ({ date }) => date()) .get('/response', () => new Response('Hi')) .get('/error', () => new Error('Something went wrong')) .get('/401', ({ set }) => { set.status = 401 return 'Status should be 401' }) .get('/timeout', async () => { await new Promise((resolve) => setTimeout(resolve, 2000)) return 'A' }) .all('/all', () => 'hi') .onError(({ code, error, set }) => { if (code === 'NOT_FOUND') { set.status = 404 return 'Not Found :(' } }) const t2 = performance.now() app.listen(8080, ({ hostname, port }) => { console.log(`🦊 Elysia is running at http://${hostname}:${port}`) }) console.log('took', t2 - t1, 'ms') ================================================ FILE: example/index.html ================================================ Bun HTML Import

Hi

================================================ FILE: example/lazy/index.ts ================================================ import Elysia from '../../src' export const lazy = (app: Elysia) => app.state('a', 'b').get('/lazy', 'Hi from lazy loaded module') export default lazy ================================================ FILE: example/lazy-module.ts ================================================ import { Elysia } from '../src' const plugin = (app: Elysia) => app.get('/plugin', () => 'Plugin') const asyncPlugin = async (app: Elysia) => app.get('/async', () => 'A') const app = new Elysia() .decorate('a', () => 'hello') .use(plugin) .use(import('./lazy')) .use((app) => app.get('/inline', ({ store: { a } }) => 'inline')) .get('/', ({ a }) => a()) .listen(3000) await app.modules console.log( `🦊 Elysia is running at ${app.server?.hostname}:${app.server?.port}` ) ================================================ FILE: example/native.ts ================================================ Bun.serve({ port: 3000, fetch: (request) => { throw new Error('A') }, error(request) { return new Response('error') } }) ================================================ FILE: example/nested-multipart-files.ts ================================================ import { Elysia, t } from '../src' /** * Example: Nested File Uploads with Multipart Forms * * Elysia supports nested file uploads using dot notation in multipart forms. * This allows you to organize files and data in a nested structure while * still using standard multipart/form-data encoding. * * How it works: * 1. Client sends files with dot notation keys (e.g., "user.avatar") * 2. Elysia automatically reconstructs the nested object structure * 3. Your handler receives a properly nested object */ const app = new Elysia() // Basic nested file upload .post( '/user/profile', ({ body }) => ({ message: 'Profile created!', user: { name: body.user.name, avatarSize: body.user.avatar.size } }), { body: t.Object({ user: t.Object({ name: t.String(), avatar: t.File() }) }) } ) // Deeply nested files .post( '/user/portfolio', ({ body }) => ({ bio: body.user.profile.bio, photoCount: body.user.profile.photos.length }), { body: t.Object({ user: t.Object({ profile: t.Object({ bio: t.String(), photos: t.Files() }) }) }) } ) // Mixed flat and nested fields .post( '/post', ({ body }) => ({ title: body.title, authorName: body.author.name, imageSize: body.author.avatar.size }), { body: t.Object({ title: t.String(), author: t.Object({ name: t.String(), avatar: t.File() }) }) } ) .listen(3000) console.log(`🦊 Server running at http://${app.server?.hostname}:${app.server?.port}`) /** * Client-side usage (with fetch): * * const formData = new FormData() * formData.append('user.name', 'John') * formData.append('user.avatar', fileBlob) * * await fetch('http://localhost:3000/user/profile', { * method: 'POST', * body: formData * }) * * * Eden client usage (future): * * await client.user.profile.post({ * user: { * name: 'John', * avatar: fileBlob // Eden will flatten this automatically * } * }) */ ================================================ FILE: example/nested-schema.ts ================================================ import { Elysia, t } from '../src' new Elysia() .guard( { query: t.Object({ name: t.String() }) }, (app) => app .get('/', ({ query }) => 'A', { beforeHandle: ({ query }) => {}, query: t.Object({ a: t.String() }) }) .guard( { headers: t.Object({ a: t.String() }) }, (app) => app.get('/a', () => 'A', { beforeHandle: ({ query }) => {}, body: t.Object({ username: t.String() }) }) ) ) .get('*', () => 'Star now work') .listen(3000) ================================================ FILE: example/newFile.ts ================================================ ================================================ FILE: example/openapi.ts ================================================ import { Elysia, t } from '../src' import { openapi as OpenAPI } from '@elysiajs/openapi' import { fromTypes } from '@elysiajs/openapi/gen' const openapi = (a: any) => new Elysia().use((app) => { app.use( // @ts-ignore OpenAPI(a) ) return app }) // Elysia 1.4: lifecycle event type soundness export const app = new Elysia() .use( openapi({ references: fromTypes('example/openapi.ts') }) ) .macro({ auth: { response: { 409: t.Literal('Conflict') }, beforeHandle({ status }) { if (Math.random() < 0.05) return status(410) }, resolve: () => ({ a: 'a' }) } }) .onError(({ status }) => { if (Math.random() < 0.05) return status(400) }) .resolve(({ status }) => { if (Math.random() < 0.05) return status(401) }) .onBeforeHandle([ ({ status }) => { if (Math.random() < 0.05) return status(402) }, ({ status }) => { if (Math.random() < 0.05) return status(403) } ]) .guard({ beforeHandle: [ ({ status }) => { if (Math.random() < 0.05) return status(405) }, ({ status }) => { if (Math.random() < 0.05) return status(406) } ], afterHandle({ status }) { if (Math.random() < 0.05) return status(407) }, error({ status }) { if (Math.random() < 0.05) return status(408) } }) .post( '/', ({ status }) => Math.random() < 0.05 ? status(409, 'Conflict') : 'Type Soundness', { auth: true, response: { 411: t.Literal('Length Required') } } ) .listen(3000) // app['~Routes']['post']['response'] ================================================ FILE: example/params.ts ================================================ import { Elysia } from '../src' const app = new Elysia() .get('/', () => 'Elysia') // Retrieve params, automatically typed .get('/id/:id', ({ params }) => params.id) .listen(3000) console.log('Listen') ================================================ FILE: example/proxy.ts ================================================ import { Elysia } from '../src' new Elysia() .all('/*', ({ request, params, query }) => fetch({ ...request, url: `https://macosplay.com/${params['*']}?${new URLSearchParams(query)}` }) ) .listen(3000) ================================================ FILE: example/redirect.ts ================================================ import { Elysia } from '../src' new Elysia() .get('/', () => 'Hi') .get('/redirect', ({ redirect }) => redirect('/')) .listen(3000) ================================================ FILE: example/rename.ts ================================================ import { Elysia, t } from '../src' // ? Elysia#83 | Proposal: Standardized way of renaming third party plugin-scoped stuff // this would be a plugin provided by a third party const myPlugin = new Elysia() .decorate('myProperty', 42) .model('salt', t.String()) new Elysia() .use( myPlugin // map decorator, rename "myProperty" to "renamedProperty" .decorate(({ myProperty, ...decorators }) => ({ renamedProperty: myProperty, ...decorators })) // map model, rename "salt" to "pepper" .model(({ salt, ...models }) => ({ ...models, pepper: t.String() })) // Add prefix .prefix('decorator', 'unstable') ) .get( '/mapped', ({ unstableRenamedProperty }) => unstableRenamedProperty ) .post('/pepper', ({ body }) => body, { body: 'pepper', // response: t.String() }) ================================================ FILE: example/response.ts ================================================ import { Elysia } from '../src' new Elysia() .get('/', ({ set }) => { set.headers['X-POWERED-BY'] = 'Elysia' // Return custom response return new Response('Shuba Shuba', { headers: { duck: 'shuba duck' }, status: 418 }) }) .listen(3000) ================================================ FILE: example/router.ts ================================================ import { Elysia } from '../src' const prefix = (prefix: Prefix) => new Elysia({ prefix }) .state('b', 'b') .get('/2', () => 2) .guard({}, (app) => app.get('/do', () => 'something')) .group('/v2', (app) => app.guard({}, (app) => app.get('/ok', () => 1))) const a = new Elysia() .get('/a', () => 'A') .guard({}, (app) => app.get('/guard', () => 'a')) .use(prefix('prefixed')) .listen(3000) ================================================ FILE: example/schema.ts ================================================ import { Elysia, t } from '../src' const app = new Elysia() .model({ name: t.Object({ name: t.String() }), b: t.Object({ response: t.Number() }), authorization: t.Object({ authorization: t.String() }) }) // Strictly validate response .get('/', () => 'hi') // Strictly validate body and response .post('/', ({ body, query }) => body.id, { body: t.Object({ id: t.Number(), username: t.String(), profile: t.Object({ name: t.String() }) }) }) // Strictly validate query, params, and body .get('/query/:id', ({ query: { name }, params }) => name, { query: t.Object({ name: t.String() }), params: t.Object({ id: t.String() }), response: { 200: t.String(), 300: t.Object({ error: t.String() }) } }) .guard( { headers: 'authorization' }, (app) => app .derive(({ headers }) => ({ userId: headers.authorization })) .get('/', ({ userId }) => 'A') .post('/id/:id', ({ query, body, params, userId }) => body, { params: t.Object({ id: t.Number() }), transform({ params }) { params.id = +params.id } }) ) .listen(3000) ================================================ FILE: example/simple.ts ================================================ import { Elysia } from '../src' // Simple Hello World const t1 = performance.now() new Elysia() .get('/', () => 'Hi') .listen(3000) console.log(performance.now() - t1) // Bun.serve({ // port: 8080, // fetch() { // return new Response('Hi') // } // }) ================================================ FILE: example/sleep.ts ================================================ const sleep = (time: number) => new Promise(resolve => setTimeout(resolve, time)) Bun.serve({ port: 3000, fetch: async () => { await sleep(1000) return new Response('Hi') } }) ================================================ FILE: example/spawn.ts ================================================ import { $ } from 'bun' import { cpus } from 'os' const total = cpus().length - 1 const ops = [] for (let i = 0; i < total; i++) ops.push($`NODE_ENV=production bun example/a.ts`) await Promise.all(ops) ================================================ FILE: example/store.ts ================================================ import { Elysia } from '../src' new Elysia() // Create globally mutable store .state('name', 'Fubuki') .get('/id/:id', ({ params: { id }, store: { name } }) => `${id} ${name}`) ================================================ FILE: example/stress/a.ts ================================================ import { getHeapSpaceStatistics } from 'v8' import { Elysia, t } from '../../src' import { generateHeapSnapshot } from 'bun' const memory = process.memoryUsage().heapTotal / 1024 / 1024 const total = 500 const sub = 1 const app = new Elysia() const plugin = new Elysia() const t1 = performance.now() for (let i = 0; i < total * sub; i++) plugin.get(`/${i}`, () => 'hi', { response: t.String() }) app.use(plugin) const t2 = performance.now() Bun.gc(true) const memoryAfter = process.memoryUsage().heapTotal / 1024 / 1024 const totalRoutes = total * sub const totalTime = t2 - t1 const avgTimePerRoute = totalTime / totalRoutes console.log(`${totalRoutes} routes took ${totalTime.toFixed(4)} ms`) console.log(`Average ${avgTimePerRoute.toFixed(4)} ms per route`) console.log(`${(memoryAfter - memory).toFixed(2)} MB memory used`) ================================================ FILE: example/stress/decorate.ts ================================================ import { Elysia, t } from '../../src' const total = 1000 const sub = 50 const app = new Elysia() const memory = process.memoryUsage().heapTotal / 1024 / 1024 console.log(`${total} Elysia instances with ${sub} decorations each`) const t1 = performance.now() for (let i = 0; i < total; i++) { const plugin = new Elysia() for (let j = 0; j < sub; j++) plugin.decorate('a', { [`value-${i * sub + j}`]: 1 }) app.use(plugin) } const t2 = performance.now() Bun.gc(true) const memoryAfter = process.memoryUsage().heapTotal / 1024 / 1024 console.log(+(memoryAfter - memory).toFixed(2), 'MB memory used') console.log('total', +(t2 - t1).toFixed(2), 'ms') console.log(+((t2 - t1) / (total * sub)).toFixed(6), 'decoration/ms') ================================================ FILE: example/stress/instance.ts ================================================ import { Elysia, t } from '../../src' const total = 100 const sub = 5 const app = new Elysia({ precompile: true }) const memory = process.memoryUsage().heapTotal / 1024 / 1024 const t1 = performance.now() for (let i = 0; i < total; i++) { const plugin = new Elysia({ cookie: { domain: 'saltyaom.com', priority: 'high', secrets: 'a', sign: 'a' } }) for (let j = 0; j < sub; j++) plugin.get(`/${i * sub + j}`, () => 'hi') app.use(plugin) } const t2 = performance.now() Bun.gc(true) const memoryAfter = process.memoryUsage().heapTotal / 1024 / 1024 console.log(+(memoryAfter - memory).toFixed(2), 'MB memory used') console.log(+(t2 - t1).toFixed(2), 'ms') ================================================ FILE: example/stress/memoir.ts ================================================ import { t } from '../../src' import { Memoirist } from 'memoirist' const total = 1000 const stack: Memoirist[] = [] { const t1 = performance.now() const memory = process.memoryUsage().heapTotal / 1024 / 1024 for (let i = 0; i < total; i++) { for (let i = 0; i < 2; i++) { const router = new Memoirist() // router.add('GET', '/a', () => 'Hello, World!') // router.add('GET', '/b', () => 'Hello, World!') stack.push(router) } } const memoryAfter = process.memoryUsage().heapTotal / 1024 / 1024 const took = performance.now() - t1 console.log( Intl.NumberFormat().format(total), 'routes took', +took.toFixed(4), 'ms' ) console.log('Average', +(took / total).toFixed(4), 'ms / route') console.log(memoryAfter - memory, 'MB memory used') } ================================================ FILE: example/stress/multiple-routes.ts ================================================ import { Elysia, t } from '../../src' import v8 from 'v8' const total = 500 { const app = new Elysia({ precompile: true }) const t1 = performance.now() const memory = process.memoryUsage().heapTotal / 1024 / 1024 for (let i = 0; i < total; i++) app.get(`/id/${i}`, () => 'hello', { response: t.String() }) const memoryAfter = process.memoryUsage().heapTotal / 1024 / 1024 const took = performance.now() - t1 console.log( Intl.NumberFormat().format(total), 'routes took', +took.toFixed(4), 'ms' ) console.log('Average', +(took / total).toFixed(4), 'ms / route') console.log(memoryAfter - memory, 'MB memory used') console.log(((memoryAfter - memory) / total) * 1024, 'KB memory used') // v8.writeHeapSnapshot() } ================================================ FILE: example/stress/sucrose.ts ================================================ // @ts-nocheck import { sucrose } from '../../src/sucrose' const total = 100_000 const t = performance.now() for (let i = 0; i < total; i++) { sucrose({ handler: function ({ query }) { query.a }, afterHandle: [], beforeHandle: [ function a({ params: { a, c: d }, ...rest }) { query.b }, ({ error }) => {} ], error: [ function a({ query, query: { a, c: d }, headers: { hello } }) { query.b }, ({ query: { f } }) => {} ], mapResponse: [], onResponse: [], parse: [], request: [], start: [], stop: [], trace: [], transform: [] }) } const took = performance.now() - t console.log( Intl.NumberFormat().format(total), 'check took', +took.toFixed(4), 'ms' ) console.log('Average', +(took / total).toFixed(4), 'ms / check') ================================================ FILE: example/type-inference.ts ================================================ import { Elysia } from '../src' const counter = (app: Elysia) => app.state('counter', 0) new Elysia() .use(counter) .guard({}, (app) => app.get('/id/:id', ({ store: { counter } }) => counter)) .listen(3000) ================================================ FILE: example/uint8array.ts ================================================ import { Elysia, t } from '../src' new Elysia() .post('/', ({ body }) => body, { body: t.Uint8Array() }) .listen(3000) const response = await fetch('http://localhost:3000', { method: 'POST', body: new Uint8Array([ 229, 143, 175, 230, 132, 155, 227, 129, 143, 227, 129, 166, 227, 129, 148, 227, 130, 129, 227, 130, 147 ]), headers: { 'content-type': 'application/octet-stream' } }) console.log(response.status) console.log(await response.text()) ================================================ FILE: example/upload.ts ================================================ import { Elysia, t } from '../src' import { upload } from '../test/utils' const app = new Elysia() .post('/single', ({ body: { file } }) => file, { body: t.Object({ file: t.File() }) }) .post( '/multiple', ({ body: { files } }) => files.reduce((a, b) => a + b.size, 0), { body: t.Object({ files: t.Files() }) } ) .listen(3000) const { request } = upload('/single', { file: 'millenium.jpg' }) app.handle(request) .then((r) => r.text()) .then(console.log) ================================================ FILE: example/video.ts ================================================ import { Elysia } from 'elysia' new Elysia() .get('/', Bun.file('test/kyuukurarin.mp4')) .listen(3000) ================================================ FILE: example/websocket.ts ================================================ import { Elysia } from '../src' const app = new Elysia() .state('start', 'here') .ws('/ws', { open(ws) { ws.subscribe('asdf') console.log('Open Connection:', ws.id) }, close(ws) { console.log('Closed Connection:', ws.id) }, message(ws, message) { ws.publish('asdf', message) ws.send(message) } }) .get('/publish/:publish', ({ params: { publish: text } }) => { app.server!.publish('asdf', text) return text }) .listen(3000, (server) => { console.log(`http://${server.hostname}:${server.port}`) }) ================================================ FILE: knip.json ================================================ { "entry": [ "src/index.ts" ], "project": [ "src/**/*.{js,ts}" ], "ignoreDependencies": [ "@types/cookie", "arktype", "@elysiajs/openapi", "eslint-plugin-security", "expect-type", "prettier", "valibot", "zod", "file-type" ], "ignoreBinaries": [ "dist/bun/index.js" ] } ================================================ FILE: package.json ================================================ { "name": "elysia", "description": "Ergonomic Framework for Human", "version": "1.4.28", "author": { "name": "saltyAom", "url": "https://github.com/SaltyAom", "email": "saltyaom@gmail.com" }, "main": "./dist/index.js", "module": "./dist/index.mjs", "types": "./dist/index.d.ts", "exports": { "./package.json": "./package.json", ".": { "types": "./dist/index.d.ts", "import": "./dist/index.mjs", "require": "./dist/index.js" }, "./ws": { "types": "./dist/ws/index.d.ts", "import": "./dist/ws/index.mjs", "require": "./dist/ws/index.js" }, "./ws/types": { "types": "./dist/ws/types.d.ts", "import": "./dist/ws/types.mjs", "require": "./dist/ws/types.js" }, "./ws/bun": { "types": "./dist/ws/bun.d.ts", "import": "./dist/ws/bun.mjs", "require": "./dist/ws/bun.js" }, "./compose": { "types": "./dist/compose.d.ts", "import": "./dist/compose.mjs", "require": "./dist/compose.js" }, "./context": { "types": "./dist/context.d.ts", "import": "./dist/context.mjs", "require": "./dist/context.js" }, "./cookies": { "types": "./dist/cookies.d.ts", "import": "./dist/cookies.mjs", "require": "./dist/cookies.js" }, "./error": { "types": "./dist/error.d.ts", "import": "./dist/error.mjs", "require": "./dist/error.js" }, "./schema": { "types": "./dist/schema.d.ts", "import": "./dist/schema.mjs", "require": "./dist/schema.js" }, "./sucrose": { "types": "./dist/sucrose.d.ts", "import": "./dist/sucrose.mjs", "require": "./dist/sucrose.js" }, "./trace": { "types": "./dist/trace.d.ts", "import": "./dist/trace.mjs", "require": "./dist/trace.js" }, "./type-system": { "types": "./dist/type-system/index.d.ts", "import": "./dist/type-system/index.mjs", "require": "./dist/type-system/index.js" }, "./type-system/format": { "types": "./dist/type-system/format.d.ts", "import": "./dist/type-system/format.mjs", "require": "./dist/type-system/format.js" }, "./type-system/utils": { "types": "./dist/type-system/utils.d.ts", "import": "./dist/type-system/utils.mjs", "require": "./dist/type-system/utils.js" }, "./type-system/types": { "types": "./dist/type-system/types.d.ts", "import": "./dist/type-system/types.mjs", "require": "./dist/type-system/types.js" }, "./types": { "types": "./dist/types.d.ts", "import": "./dist/types.mjs", "require": "./dist/types.js" }, "./utils": { "types": "./dist/utils.d.ts", "import": "./dist/utils.mjs", "require": "./dist/utils.js" }, "./parse-query": { "types": "./dist/parse-query.d.ts", "import": "./dist/parse-query.mjs", "require": "./dist/parse-query.js" }, "./adapter": { "types": "./dist/adapter/index.d.ts", "import": "./dist/adapter/index.mjs", "require": "./dist/adapter/index.js" }, "./adapter/utils": { "types": "./dist/adapter/utils.d.ts", "import": "./dist/adapter/utils.mjs", "require": "./dist/adapter/utils.js" }, "./adapter/bun": { "types": "./dist/adapter/bun/index.d.ts", "import": "./dist/adapter/bun/index.mjs", "require": "./dist/adapter/bun/index.js" }, "./adapter/bun/handler": { "types": "./dist/adapter/bun/handler.d.ts", "import": "./dist/adapter/bun/handler.mjs", "require": "./dist/adapter/bun/handler.js" }, "./adapter/bun/compose": { "types": "./dist/adapter/bun/compose.d.ts", "import": "./dist/adapter/bun/compose.mjs", "require": "./dist/adapter/bun/compose.js" }, "./adapter/cloudflare-worker": { "types": "./dist/adapter/cloudflare-worker/index.d.ts", "import": "./dist/adapter/cloudflare-worker/index.mjs", "require": "./dist/adapter/cloudflare-worker/index.js" }, "./adapter/web-standard": { "types": "./dist/adapter/web-standard/index.d.ts", "import": "./dist/adapter/web-standard/index.mjs", "require": "./dist/adapter/web-standard/index.js" }, "./adapter/web-standard/handler": { "types": "./dist/adapter/web-standard/handler.d.ts", "import": "./dist/adapter/web-standard/handler.mjs", "require": "./dist/adapter/web-standard/handler.js" }, "./universal": { "types": "./dist/universal/index.d.ts", "import": "./dist/universal/index.mjs", "require": "./dist/universal/index.js" }, "./universal/server": { "types": "./dist/universal/server.d.ts", "import": "./dist/universal/server.mjs", "require": "./dist/universal/server.js" }, "./universal/env": { "types": "./dist/universal/env.d.ts", "import": "./dist/universal/env.mjs", "require": "./dist/universal/env.js" }, "./universal/file": { "types": "./dist/universal/file.d.ts", "import": "./dist/universal/file.mjs", "require": "./dist/universal/file.js" } }, "repository": { "type": "git", "url": "https://github.com/elysiajs/elysia" }, "bugs": "https://github.com/elysiajs/elysia/issues", "homepage": "https://github.com/elysiajs/elysia", "keywords": [ "bun", "http", "web", "server" ], "license": "MIT", "scripts": { "test": "bun run test:functionality && bun run test:types && bun run test:node", "test:functionality": "bun test && bun run test:imports", "test:imports": "bun run test/type-system/import.ts", "test:types": "tsc --project tsconfig.test.json", "test:node": "npm install --prefix test/node/cjs && npm install --prefix test/node/esm/ && node test/node/cjs/index.js && node test/node/esm/index.js", "test:cf": "npm install --prefix test/cloudflare && cd test/cloudflare && bun run cf-typegen && bun run test", "dev": "bun run --watch example/a.ts", "build": "rm -rf dist && bun build.ts", "deadcode": "knip", "release": "bun run build && bun run test && bun publish" }, "dependencies": { "cookie": "^1.1.1", "exact-mirror": "^0.2.7", "fast-decode-uri-component": "^1.0.1", "memoirist": "^0.4.0" }, "devDependencies": { "@elysiajs/openapi": "^1.4.1", "@types/bun": "^1.3.5", "@types/cookie": "1.0.0", "@types/fast-decode-uri-component": "^1.0.0", "@typescript-eslint/eslint-plugin": "^8.30.1", "@typescript-eslint/parser": "^8.30.1", "arktype": "^2.1.22", "esbuild-fix-imports-plugin": "^1.0.22", "eslint": "^9.24.0", "eslint-plugin-security": "^3.0.1", "eslint-plugin-sonarjs": "^3.0.2", "expect-type": "^1.2.1", "file-type": "^20.4.1", "knip": "^5.64.1", "prettier": "^3.5.3", "tsup": "^8.4.0", "typescript": "^5.8.3", "valibot": "^1.1.0", "zod": "^4.1.5" }, "peerDependencies": { "@types/bun": ">= 1.2.0", "@sinclair/typebox": ">= 0.34.0 < 1", "exact-mirror": ">= 0.0.9", "file-type": ">= 20.0.0", "openapi-types": ">= 12.0.0", "typescript": ">= 5.0.0" }, "overrides": { "esbuild": "0.25.4" }, "peerDependenciesMeta": { "@types/bun": { "optional": true }, "typescript": { "optional": true } } } ================================================ FILE: src/adapter/bun/compose.ts ================================================ import { mapEarlyResponse } from './handler' import { sucrose, type Sucrose } from '../../sucrose' import { createHoc, createOnRequestHandler, isAsync } from '../../compose' import { randomId, ELYSIA_REQUEST_ID, redirect, isNotEmpty } from '../../utils' import { status } from '../../error' import { ELYSIA_TRACE } from '../../trace' import type { AnyElysia } from '../..' import type { InternalRoute, InputSchema } from '../../types' const allocateIf = (value: string, condition: unknown) => condition ? value : '' const createContext = ( app: AnyElysia, route: InternalRoute, inference: Sucrose.Inference, isInline = false ) => { let fnLiteral = '' // @ts-expect-error private const defaultHeaders = app.setHeaders const hasTrace = !!app.event.trace?.length if (hasTrace) fnLiteral += `const id=randomId()\n` const isDynamic = /[:*]/.test(route.path) const standardHostname = app.config.handler?.standardHostname ?? true const getQi = `const u=request.url,` + `s=u.indexOf('/',${standardHostname ? 11 : 7}),` + `qi=u.indexOf('?',s+1)\n` const needsQuery = inference.query || !!route.hooks.query || !!(route.hooks.standaloneValidator as InputSchema[])?.find( (x) => x.query ) || app.event.request?.length if (needsQuery) fnLiteral += getQi const getPath = !inference.path ? '' : !isDynamic ? `path:'${route.path}',` : `get path(){` + (needsQuery ? '' : getQi) + `if(qi===-1)return u.substring(s)\n` + `return u.substring(s,qi)\n` + `},` fnLiteral += allocateIf(`const c=`, !isInline) + `{request,` + `store,` + allocateIf(`qi,`, needsQuery) + allocateIf(`params:request.params,`, isDynamic) + getPath + allocateIf( `url:request.url,`, hasTrace || inference.url || needsQuery ) + `redirect,` + `status,` + `set:{headers:` + (isNotEmpty(defaultHeaders) ? 'Object.assign({},app.setHeaders)' : 'Object.create(null)') + `,status:200}` if (inference.server) fnLiteral += `,get server(){return app.getServer()}` if (hasTrace) fnLiteral += ',[ELYSIA_REQUEST_ID]:id' { let decoratorsLiteral = '' // @ts-expect-error private for (const key of Object.keys(app.singleton.decorator)) decoratorsLiteral += `,'${key}':decorator['${key}']` fnLiteral += decoratorsLiteral } fnLiteral += `}\n` return fnLiteral } export const createBunRouteHandler = (app: AnyElysia, route: InternalRoute) => { const hasTrace = !!app.event.trace?.length // @ts-expect-error private property const hasHoc = !!app.extender.higherOrderFunctions.length let inference = sucrose( route.hooks, // @ts-expect-error app.inference ) inference = sucrose( { handler: route.handler }, inference ) let fnLiteral = 'const handler=data.handler,' + `app=data.app,` + 'store=data.store,' + `decorator=data.decorator,` + 'redirect=data.redirect,' + 'route=data.route,' + 'mapEarlyResponse=data.mapEarlyResponse,' + allocateIf('randomId=data.randomId,', hasTrace) + allocateIf(`ELYSIA_REQUEST_ID=data.ELYSIA_REQUEST_ID,`, hasTrace) + allocateIf(`ELYSIA_TRACE=data.ELYSIA_TRACE,`, hasTrace) + allocateIf(`trace=data.trace,`, hasTrace) + allocateIf(`hoc=data.hoc,`, hasHoc) + 'status=data.status\n' if (app.event.request?.length) fnLiteral += `const onRequest=app.event.request.map(x=>x.fn)\n` fnLiteral += `${app.event.request?.find(isAsync) ? 'async' : ''} function map(request){` const needsQuery = inference.query || !!route.hooks.query || !!(route.hooks.standaloneValidator as InputSchema[])?.find( (x) => x.query ) // inference.query require declaring const 'qi' if (hasTrace || needsQuery || app.event.request?.length) { fnLiteral += createContext(app, route, inference) fnLiteral += createOnRequestHandler(app) fnLiteral += 'return handler(c)}' } else fnLiteral += `return handler(${createContext(app, route, inference, true)})}` fnLiteral += createHoc(app) return Function( 'data', fnLiteral )({ app, handler: route.compile?.() ?? route.composed, redirect, status, // @ts-expect-error private property hoc: app.extender.higherOrderFunctions.map((x) => x.fn), store: app.store, decorator: app.decorator, route: route.path, randomId: hasTrace ? randomId : undefined, ELYSIA_TRACE: hasTrace ? ELYSIA_TRACE : undefined, ELYSIA_REQUEST_ID: hasTrace ? ELYSIA_REQUEST_ID : undefined, trace: hasTrace ? app.event.trace?.map((x) => x?.fn ?? x) : undefined, mapEarlyResponse: mapEarlyResponse }) } ================================================ FILE: src/adapter/bun/handler-native.ts ================================================ import { isHTMLBundle } from './index' import type { Context } from '../../context' import type { AnyLocalHook, MaybePromise } from '../../types' import { mapResponse } from './handler' export const createNativeStaticHandler = ( handle: unknown, hooks: AnyLocalHook, set?: Context['set'] ): (() => MaybePromise) | undefined => { if (typeof handle === 'function' || handle instanceof Blob) return if (isHTMLBundle(handle)) return () => handle as any const response = mapResponse( handle instanceof Response ? handle.clone() : handle instanceof Promise ? handle.then((x) => x instanceof Response ? x.clone() : isHTMLBundle(x) ? () => x : x ) : handle, set ?? { headers: {} } ) if ( !hooks.parse?.length && !hooks.transform?.length && !hooks.beforeHandle?.length && !hooks.afterHandle?.length ) { if (response instanceof Promise) return response.then((response) => { if (!response) return return response.clone() }) as any as () => Promise return () => response.clone() as Response } } ================================================ FILE: src/adapter/bun/handler.ts ================================================ // Similar to adapter/web-standard/handler but // string case is omitted header out // (this has a significant performance difference on Bun) /* eslint-disable sonarjs/no-nested-switch */ /* eslint-disable sonarjs/no-duplicate-string */ import { createResponseHandler, createStreamHandler, handleFile, handleSet } from '../utils' import { ElysiaFile } from '../../universal/file' import { isNotEmpty } from '../../utils' import { Cookie } from '../../cookies' import { ElysiaCustomStatusResponse } from '../../error' import type { Context } from '../../context' import type { AnyLocalHook } from '../../types' // Response.json is faster than new Response(JSON.stringify()) in Bun // https://x.com/jarredsumner/status/2023328556210921948 export const mapResponse = ( response: unknown, set: Context['set'], request?: Request ): Response => { if (isNotEmpty(set.headers) || set.status !== 200 || set.cookie) { handleSet(set) switch (response?.constructor?.name) { case 'String': return new Response(response as string, set as any) case 'Array': case 'Object': return Response.json(response, set as any) case 'ElysiaFile': return handleFile((response as ElysiaFile).value as File, set, request) case 'File': return handleFile(response as File, set, request) case 'Blob': return handleFile(response as Blob, set, request) case 'ElysiaCustomStatusResponse': set.status = (response as ElysiaCustomStatusResponse<200>).code return mapResponse( (response as ElysiaCustomStatusResponse<200>).response, set, request ) case undefined: if (!response) return new Response('', set as any) return Response.json(response, set as any) case 'Response': return handleResponse(response as Response, set, request) case 'Error': return errorToResponse(response as Error, set) case 'Promise': return (response as Promise).then((x) => mapResponse(x, set, request) ) as any case 'Function': return mapResponse((response as Function)(), set, request) case 'Number': case 'Boolean': return new Response( (response as number | boolean).toString(), set as any ) case 'Cookie': if (response instanceof Cookie) return new Response(response.value, set as any) return new Response(response?.toString(), set as any) case 'FormData': return new Response(response as FormData, set as any) default: // recheck Response, Promise, Error because some library may extends Response if (response instanceof Response) return handleResponse(response as Response, set, request) if (response instanceof Promise) return response.then((x) => mapResponse(x, set)) as any if (response instanceof Error) return errorToResponse(response as Error, set) if (response instanceof ElysiaCustomStatusResponse) { set.status = ( response as ElysiaCustomStatusResponse<200> ).code return mapResponse( (response as ElysiaCustomStatusResponse<200>).response, set, request ) } if ( // @ts-expect-error typeof response?.next === 'function' || response instanceof ReadableStream ) return handleStream(response as any, set, request) as any if (typeof (response as Promise)?.then === 'function') return (response as Promise).then((x) => mapResponse(x, set) ) as any // custom class with an array-like value // eg. Bun.sql`` result if (Array.isArray(response)) return Response.json(response) as any // @ts-expect-error if (typeof response?.toResponse === 'function') return mapResponse((response as any).toResponse(), set) if ('charCodeAt' in (response as any)) { const code = (response as any).charCodeAt(0) if (code === 123 || code === 91) return Response.json(response, set as any) as any } return new Response(response as any, set as any) } } // Stream response defers a 'set' API, assume that it may include 'set' if ( // @ts-expect-error typeof response?.next === 'function' || response instanceof ReadableStream ) return handleStream(response as any, set, request) as any return mapCompactResponse(response, request) } export const mapEarlyResponse = ( response: unknown, set: Context['set'], request?: Request ): Response | undefined => { if (response === undefined || response === null) return if (isNotEmpty(set.headers) || set.status !== 200 || set.cookie) { handleSet(set) switch (response?.constructor?.name) { case 'String': return new Response(response as string, set as any) case 'Array': case 'Object': return Response.json(response, set as any) case 'ElysiaFile': return handleFile((response as ElysiaFile).value as File, set, request) case 'File': return handleFile(response as File, set, request) case 'Blob': return handleFile(response as File | Blob, set, request) case 'ElysiaCustomStatusResponse': set.status = (response as ElysiaCustomStatusResponse<200>).code return mapEarlyResponse( (response as ElysiaCustomStatusResponse<200>).response, set, request ) case undefined: if (!response) return return Response.json(response, set as any) case 'Response': return handleResponse(response as Response, set, request) case 'Promise': return (response as Promise).then((x) => mapEarlyResponse(x, set) ) as any case 'Error': return errorToResponse(response as Error, set) case 'Function': return mapEarlyResponse((response as Function)(), set) case 'Number': case 'Boolean': return new Response( (response as number | boolean).toString(), set as any ) case 'FormData': return new Response(response as FormData) case 'Cookie': if (response instanceof Cookie) return new Response(response.value, set as any) return new Response(response?.toString(), set as any) default: if (response instanceof Response) return handleResponse(response, set, request) if (response instanceof Promise) return response.then((x) => mapEarlyResponse(x, set)) as any if (response instanceof Error) return errorToResponse(response as Error, set) if (response instanceof ElysiaCustomStatusResponse) { set.status = ( response as ElysiaCustomStatusResponse<200> ).code return mapEarlyResponse( (response as ElysiaCustomStatusResponse<200>).response, set, request ) } if ( // @ts-expect-error typeof response?.next === 'function' || response instanceof ReadableStream ) return handleStream(response as any, set, request) as any if (typeof (response as Promise)?.then === 'function') return (response as Promise).then((x) => mapEarlyResponse(x, set) ) as any // @ts-expect-error if (typeof response?.toResponse === 'function') return mapEarlyResponse((response as any).toResponse(), set) // custom class with an array-like value // eg. Bun.sql`` result if (Array.isArray(response)) return Response.json(response) as any if ('charCodeAt' in (response as any)) { const code = (response as any).charCodeAt(0) if (code === 123 || code === 91) return Response.json(response, set as any) as any } return new Response(response as any, set as any) } } else switch (response?.constructor?.name) { case 'String': return new Response(response as string) case 'Array': case 'Object': return Response.json(response, set as any) case 'ElysiaFile': return handleFile((response as ElysiaFile).value as File, set, request) case 'File': return handleFile(response as File, set, request) case 'Blob': return handleFile(response as File | Blob, set, request) case 'ElysiaCustomStatusResponse': set.status = (response as ElysiaCustomStatusResponse<200>).code return mapEarlyResponse( (response as ElysiaCustomStatusResponse<200>).response, set, request ) case undefined: if (!response) return new Response('') return Response.json(response) case 'Response': return response as Response case 'Promise': return (response as Promise).then((x) => { const r = mapEarlyResponse(x, set) if (r !== undefined) return r }) as any case 'Error': return errorToResponse(response as Error, set) case 'Function': return mapCompactResponse((response as Function)(), request) case 'Number': case 'Boolean': return new Response((response as number | boolean).toString()) case 'Cookie': if (response instanceof Cookie) return new Response(response.value, set as any) return new Response(response?.toString(), set as any) case 'FormData': return new Response(response as FormData) default: if (response instanceof Response) return response if (response instanceof Promise) return response.then((x) => mapEarlyResponse(x, set)) as any if (response instanceof Error) return errorToResponse(response as Error, set) if (response instanceof ElysiaCustomStatusResponse) { set.status = ( response as ElysiaCustomStatusResponse<200> ).code return mapEarlyResponse( (response as ElysiaCustomStatusResponse<200>).response, set, request ) } if ( // @ts-expect-error typeof response?.next === 'function' || response instanceof ReadableStream ) return handleStream(response as any, set, request) as any if (typeof (response as Promise)?.then === 'function') return (response as Promise).then((x) => mapEarlyResponse(x, set) ) as any // @ts-expect-error if (typeof response?.toResponse === 'function') return mapEarlyResponse((response as any).toResponse(), set) // custom class with an array-like value // eg. Bun.sql`` result if (Array.isArray(response)) return Response.json(response) as any if ('charCodeAt' in (response as any)) { const code = (response as any).charCodeAt(0) if (code === 123 || code === 91) return Response.json(response, set as any) as any } return new Response(response as any) } } export const mapCompactResponse = ( response: unknown, request?: Request ): Response => { switch (response?.constructor?.name) { case 'String': return new Response(response as string) case 'Object': case 'Array': return Response.json(response) case 'ElysiaFile': return handleFile((response as ElysiaFile).value as File, undefined, request) case 'File': return handleFile(response as File, undefined, request) case 'Blob': return handleFile(response as File | Blob, undefined, request) case 'ElysiaCustomStatusResponse': return mapResponse( (response as ElysiaCustomStatusResponse<200>).response, { status: (response as ElysiaCustomStatusResponse<200>).code, headers: {} } ) case undefined: if (!response) return new Response('') return Response.json(response) case 'Response': return response as Response case 'Error': return errorToResponse(response as Error) case 'Promise': return (response as Promise).then((x) => mapCompactResponse(x, request) ) as any // ? Maybe response or Blob case 'Function': return mapCompactResponse((response as Function)(), request) case 'Number': case 'Boolean': return new Response((response as number | boolean).toString()) case 'FormData': return new Response(response as FormData) default: if (response instanceof Response) return response if (response instanceof Promise) return response.then((x) => mapCompactResponse(x, request) ) as any if (response instanceof Error) return errorToResponse(response as Error) if (response instanceof ElysiaCustomStatusResponse) return mapResponse( (response as ElysiaCustomStatusResponse<200>).response, { status: (response as ElysiaCustomStatusResponse<200>) .code, headers: {} } ) if ( // @ts-expect-error typeof response?.next === 'function' || response instanceof ReadableStream ) return handleStream(response as any, undefined, request) as any if (typeof (response as Promise)?.then === 'function') return (response as Promise).then((x) => mapCompactResponse(x, request) ) as any // @ts-expect-errors if (typeof response?.toResponse === 'function') return mapCompactResponse((response as any).toResponse()) // custom class with an array-like value // eg. Bun.sql`` result if (Array.isArray(response)) return Response.json(response) as any if ('charCodeAt' in (response as any)) { const code = (response as any).charCodeAt(0) if (code === 123 || code === 91) return Response.json(response) as any } return new Response(response as any) } } export const errorToResponse = (error: Error, set?: Context['set']) => { // @ts-expect-error if (typeof error?.toResponse === 'function') { // @ts-expect-error const raw = error.toResponse() const targetSet = set ?? ({ headers: {}, status: 200, redirect: '' } as Context['set']) const apply = (resolved: unknown) => { if (resolved instanceof Response) targetSet.status = resolved.status return mapResponse(resolved, targetSet) } return typeof raw?.then === 'function' ? raw.then(apply) : apply(raw) } return Response.json( { name: error?.name, message: error?.message, cause: error?.cause }, { status: set?.status !== 200 ? ((set?.status as number) ?? 500) : 500, headers: set?.headers as any } ) } export const createStaticHandler = ( handle: unknown, hooks: Partial, setHeaders: Context['set']['headers'] = {} ): (() => Response) | undefined => { if (typeof handle === 'function') return const response = mapResponse(handle, { headers: setHeaders }) if ( !hooks.parse?.length && !hooks.transform?.length && !hooks.beforeHandle?.length && !hooks.afterHandle?.length ) return () => response.clone() as Response } const handleResponse = createResponseHandler({ mapResponse, mapCompactResponse }) const handleStream = createStreamHandler({ mapResponse, mapCompactResponse }) ================================================ FILE: src/adapter/bun/index.ts ================================================ /* eslint-disable sonarjs/no-duplicate-string */ import type { TSchema } from '@sinclair/typebox' import { WebStandardAdapter } from '../web-standard/index' import { parseSetCookies } from '../utils' import type { ElysiaAdapter } from '../types' import type { Serve } from '../../universal/server' import { createBunRouteHandler } from './compose' import { createNativeStaticHandler } from './handler-native' import { serializeCookie } from '../../cookies' import { isProduction, status, ValidationError } from '../../error' import { getSchemaValidator } from '../../schema' import { hasHeaderShorthand, isNotEmpty, isNumericString, randomId, supportPerMethodInlineHandler } from '../../utils' import { mapResponse, mapEarlyResponse, mapCompactResponse, createStaticHandler } from './handler' import { createHandleWSResponse, createWSMessageParser, ElysiaWS, websocket } from '../../ws/index' import type { ServerWebSocket } from '../../ws/bun' import type { AnyElysia } from '../..' const optionalParam = /:.+?\?(?=\/|$)/ const getPossibleParams = (path: string) => { const match = optionalParam.exec(path) if (!match) return [path] const routes: string[] = [] const head = path.slice(0, match.index) const param = match[0].slice(0, -1) const tail = path.slice(match.index + match[0].length) routes.push(head.slice(0, -1)) routes.push(head + param) for (const fragment of getPossibleParams(tail)) { if (!fragment) continue if (!fragment.startsWith('/:')) routes.push(head.slice(0, -1) + fragment) routes.push(head + param + fragment) } return routes } export const isHTMLBundle = (handle: any) => typeof handle === 'object' && handle !== null && (handle.toString() === '[object HTMLBundle]' || typeof handle.index === 'string') const supportedMethods = { GET: true, HEAD: true, OPTIONS: true, DELETE: true, PATCH: true, POST: true, PUT: true } as const const mapRoutes = (app: AnyElysia) => { if (!app.config.aot || app.config.systemRouter === false) return undefined const routes = >>{} const add = ( route: { path: string method: string }, handler: Function ) => { const path = encodeURI(route.path) if (routes[path]) { // @ts-ignore if (!routes[path][route.method]) // @ts-ignore routes[path][route.method] = handler } else routes[path] = { [route.method]: handler } } // @ts-expect-error const tree = app.routeTree for (const route of app.router.history) { if (typeof route.handler !== 'function') continue const method = route.method if ( (method === 'GET' && `WS_${route.path}` in tree) || method === 'WS' || route.path.charCodeAt(route.path.length - 1) === 42 || !(method in supportedMethods) ) continue if (method === 'ALL') { if (!(`WS_${route.path}` in tree)) routes[route.path] = route.hooks?.config?.mount ? route.hooks.trace || app.event.trace || // @ts-expect-error private property app.extender.higherOrderFunctions ? createBunRouteHandler(app, route) : route.hooks.mount || route.handler : route.handler continue } let compiled: Function const handler = app.config.precompile ? createBunRouteHandler(app, route) : (request: Request) => { if (compiled) return compiled(request) return (compiled = createBunRouteHandler(app, route))( request ) } for (const path of getPossibleParams(route.path)) add( { method, path }, handler ) } return routes } type Routes = Record> const mergeRoutes = (r1: Routes, r2?: Routes) => { if (!r2) return r1 for (const key of Object.keys(r2)) { if (r1[key] === r2[key]) continue if (!r1[key]) { r1[key] = r2[key] continue } if (r1[key] && r2[key]) { if (typeof r1[key] === 'function' || r1[key] instanceof Response) { r1[key] = r2[key] continue } r1[key] = { ...r1[key], ...r2[key] } } } return r1 } // // https://github.com/elysiajs/elysia/issues/1752 // inconsistent behavior between trailing slash and non-trailing slash export const removeTrailingPath = (routes: Routes) => { for (const key of Object.keys(routes)) if (key.length > 1 && key.charCodeAt(key.length - 1) === 47) { routes[key.slice(0, -1)] = routes[key] delete routes[key] } return routes } export const BunAdapter: ElysiaAdapter = { ...WebStandardAdapter, name: 'bun', handler: { mapResponse, mapEarlyResponse, mapCompactResponse, createStaticHandler, createNativeStaticHandler }, composeHandler: { ...WebStandardAdapter.composeHandler, headers: hasHeaderShorthand ? 'c.headers=c.request.headers.toJSON()\n' : 'c.headers={}\n' + 'for(const [k,v] of c.request.headers.entries())' + 'c.headers[k]=v\n' }, listen(app) { return (options, callback) => { if (typeof Bun === 'undefined') throw new Error( '.listen() is designed to run on Bun only. If you are running Elysia in other environment please use a dedicated plugin or export the handler via Elysia.fetch' ) app.compile() if (typeof options === 'string') { if (!isNumericString(options)) throw new Error('Port must be a numeric value') options = parseInt(options) } const createStaticRoute = < WithAsync extends boolean | undefined = false >( iterator: AnyElysia['router']['response'], { withAsync = false }: { withAsync?: WithAsync } = {} ): true extends WithAsync ? Promise<{ [path: string]: | Response | { [method: string]: Response } }> : { [path: string]: | Response | { [method: string]: Response } } => { const staticRoutes = < { [path: string]: | Response | { [method: string]: Response } } >{} const ops = []>[] for (let [path, route] of Object.entries(iterator)) { path = encodeURI(path) if (supportPerMethodInlineHandler) { if (!route) continue for (const [method, value] of Object.entries(route)) { if (!value || !(method in supportedMethods)) continue if (value instanceof Promise) { if (withAsync) { if (!staticRoutes[path]) staticRoutes[path] = {} ops.push( value.then((awaited) => { if (awaited instanceof Response) // @ts-ignore staticRoutes[path][method] = awaited if (isHTMLBundle(awaited)) // @ts-ignore staticRoutes[path][method] = awaited }) ) } continue } if ( !(value instanceof Response) && !isHTMLBundle(value) ) continue if (!staticRoutes[path]) staticRoutes[path] = {} // @ts-ignore staticRoutes[path][method] = value } } else { if (!route) continue if (route instanceof Promise) { if (withAsync) { if (!staticRoutes[path]) staticRoutes[path] = {} ops.push( route.then((awaited) => { if (awaited instanceof Response) // @ts-ignore staticRoutes[path] = awaited }) ) } continue } if (!(route instanceof Response)) continue staticRoutes[path] = route } } if (withAsync) return Promise.all(ops).then(() => staticRoutes) as any return staticRoutes as any } const routes = removeTrailingPath( mergeRoutes( mergeRoutes( createStaticRoute(app.router.response), mapRoutes(app) ), // @ts-ignore app.config.serve?.routes ) ) const serve = typeof options === 'object' ? ({ development: !isProduction, reusePort: true, idleTimeout: 30, ...(app.config.serve || {}), ...(options || {}), routes, websocket: { ...(app.config.websocket || {}), ...(websocket || {}), ...(options.websocket || {}) }, fetch: app.fetch } as Serve) : ({ development: !isProduction, reusePort: true, idleTimeout: 30, ...(app.config.serve || {}), routes, websocket: { ...(app.config.websocket || {}), ...(websocket || {}) }, port: options, fetch: app.fetch } as Serve) app.server = Bun.serve(serve as any) as any if (app.event.start) for (let i = 0; i < app.event.start.length; i++) app.event.start[i].fn(app) if (callback) callback(app.server!) process.on('beforeExit', async () => { if (app.server) { await app.server.stop?.() app.server = null if (app.event.stop) for (let i = 0; i < app.event.stop.length; i++) app.event.stop[i].fn(app) } }) // @ts-expect-error private app.promisedModules.then(async () => { if (typeof app.config.aot) app.compile() const routes = removeTrailingPath( mergeRoutes( mergeRoutes( await createStaticRoute(app.router.response, { withAsync: true }), mapRoutes(app) ), // @ts-ignore app.config.serve?.routes ) ) app.server?.reload({ ...serve, fetch: app.fetch, // @ts-ignore routes }) Bun?.gc(false) }) } }, async stop(app, closeActiveConnections) { if (app.server) { await app.server.stop(closeActiveConnections) app.server = null if (app.event.stop?.length) for (let i = 0; i < app.event.stop.length; i++) app.event.stop[i].fn(app) } else console.log( "Elysia isn't running. Call `app.listen` to start the server.", new Error().stack ) }, ws(app, path, options) { // eslint-disable-next-line @typescript-eslint/no-unused-vars const { parse, body, response, ...rest } = options const messageValidator = getSchemaValidator(body, { // @ts-expect-error private property modules: app.definitions.typebox, // @ts-expect-error private property models: app.definitions.type as Record, normalize: app.config.normalize }) const validateMessage = messageValidator ? messageValidator.provider === 'standard' ? (data: unknown) => messageValidator.schema['~standard'].validate(data) .issues : (data: unknown) => messageValidator.Check(data) === false : undefined const responseValidator = getSchemaValidator(response as any, { // @ts-expect-error private property modules: app.definitions.typebox, // @ts-expect-error private property models: app.definitions.type as Record, normalize: app.config.normalize }) app.route( 'WS', path as any, async (context: any) => { const server = (context.server as (typeof app)['server']) ?? app.server // ! Enable static code analysis just in case resolveUnknownFunction doesn't work, do not remove // eslint-disable-next-line @typescript-eslint/no-unused-vars const { set, path, qi, headers, query, params } = context // @ts-ignore context.validator = responseValidator if (options.upgrade) { if (typeof options.upgrade === 'function') { const temp = options.upgrade(context as any) if (temp instanceof Promise) await temp } else if (options.upgrade) Object.assign( set.headers, options.upgrade as Record ) } if (set.cookie && isNotEmpty(set.cookie)) { const cookie = serializeCookie(set.cookie) if (cookie) set.headers['set-cookie'] = cookie } if ( set.headers['set-cookie'] && Array.isArray(set.headers['set-cookie']) ) set.headers = parseSetCookies( new Headers(set.headers as any) as Headers, set.headers['set-cookie'] ) as any const handleResponse = createHandleWSResponse(responseValidator) const parseMessage = createWSMessageParser(parse as any) let _id: string | undefined if (typeof options.beforeHandle === 'function') { const result = options.beforeHandle(context) if (result instanceof Promise) await result } const errorHandlers = [ ...(options.error ? Array.isArray(options.error) ? options.error : [options.error] : []), ...(app.event.error ?? []).map((x) => typeof x === 'function' ? x : x.fn ) ].filter((x) => x) const hasCustomErrorHandlers = errorHandlers.length > 0 const handleErrors = !hasCustomErrorHandlers ? () => {} : async (ws: ServerWebSocket, error: unknown) => { for (const handleError of errorHandlers) { let response = handleError( Object.assign(context, { error }) ) if (response instanceof Promise) response = await response await handleResponse(ws, response) if (response) break } } if ( server?.upgrade(context.request, { headers: isNotEmpty(set.headers) ? (set.headers as Record) : undefined, data: { ...context, get id() { if (_id) return _id return (_id = randomId()) }, validator: responseValidator, ping(ws: ServerWebSocket, data?: unknown) { options.ping?.(ws as any, data) }, pong(ws: ServerWebSocket, data?: unknown) { options.pong?.(ws as any, data) }, open: async (ws: ServerWebSocket) => { try { await handleResponse( ws, options.open?.( new ElysiaWS(ws, context as any) ) ) } catch (error) { handleErrors(ws, error) } }, message: async ( ws: ServerWebSocket, _message: any ) => { const message = await parseMessage(ws, _message) if ( validateMessage && validateMessage(message) ) { const validationError = new ValidationError( 'message', messageValidator!, message ) if (!hasCustomErrorHandlers) return void ws.send( validationError.message as string ) return handleErrors(ws, validationError) } try { await handleResponse( ws, options.message?.( new ElysiaWS( ws, context as any, message ), message as any ) ) } catch (error) { handleErrors(ws, error) } }, drain: async (ws: ServerWebSocket) => { try { await handleResponse( ws, options.drain?.( new ElysiaWS(ws, context as any) ) ) } catch (error) { handleErrors(ws, error) } }, close: async ( ws: ServerWebSocket, code: number, reason: string ) => { try { await handleResponse( ws, options.close?.( new ElysiaWS(ws, context as any), code, reason ) ) } catch (error) { handleErrors(ws, error) } } } }) ) return return status(400, 'Expected a websocket connection') }, { ...rest, websocket: options } as any ) } } ================================================ FILE: src/adapter/cloudflare-worker/index.ts ================================================ import { ElysiaAdapter } from '../..' import { WebStandardAdapter } from '../web-standard/index' import { composeErrorHandler } from '../../compose' /** * Cloudflare Adapter (Experimental) * @see https://elysiajs.com/integrations/cloudflare-worker * * @example * ```ts * import { Elysia } from 'elysia' * import { CloudflareAdapter } from 'elysia/adapter/cloudflare-worker' * * const app = new Elysia({ * adapter: CloudflareAdapter, * }) * .get('/', () => 'Hello Elysia') * .compile() * * export default app * ``` */ export const CloudflareAdapter: ElysiaAdapter = { ...WebStandardAdapter, name: 'cloudflare-worker', composeGeneralHandler: { ...WebStandardAdapter.composeGeneralHandler, error404(hasEventHook, hasErrorHook, afterHandle) { const { code } = WebStandardAdapter.composeGeneralHandler.error404( hasEventHook, hasErrorHook, afterHandle ) return { code, declare: hasErrorHook ? '' : // This only work because Elysia only clone the Response via .clone() `const error404Message=notFound.message.toString()\n` + `const error404={clone:()=>new Response(error404Message,{status:404})}\n` } } }, beforeCompile(app) { // @ts-ignore app.handleError = composeErrorHandler(app) for (const route of app.routes) route.compile() }, listen() { return () => { console.warn( 'Cloudflare Worker does not support listen method. Please export default Elysia instance instead.' ) } } } ================================================ FILE: src/adapter/index.ts ================================================ export type { ElysiaAdapter } from './types' ================================================ FILE: src/adapter/types.ts ================================================ import type { Serve, ListenCallback } from '../universal/server' import type { AnyElysia } from '..' import type { Context } from '../context' import type { Sucrose } from '../sucrose' import type { Prettify, AnyLocalHook, MaybePromise } from '../types' import type { AnyWSLocalHook } from '../ws/types' export interface ElysiaAdapter { name: string listen( app: AnyElysia ): ( options: string | number | Partial, callback?: ListenCallback ) => void /** * Stop server from serving * * --- * @example * ```typescript * app.stop() * ``` * * @example * ```typescript * app.stop(true) // Abruptly any requests inflight * ``` */ stop?(app: AnyElysia, closeActiveConnections?: boolean): Promise isWebStandard?: boolean handler: { /** * Map return response on every case */ mapResponse( response: unknown, set: Context['set'], ...params: unknown[] ): unknown /** * Map response on truthy value */ mapEarlyResponse( response: unknown, set: Context['set'], ...params: unknown[] ): unknown /** * Map response without cookie, status or headers */ mapCompactResponse(response: unknown, ...params: unknown[]): unknown /** * Compile inline to value * * @example * ```ts * Elysia().get('/', 'static') * ``` */ createStaticHandler?( handle: unknown, hooks: AnyLocalHook, setHeaders?: Context['set']['headers'], ...params: unknown[] ): (() => unknown) | undefined /** * If the runtime support cloning response * * eg. Bun.serve({ static }) */ createNativeStaticHandler?( handle: unknown, hooks: AnyLocalHook, set?: Context['set'] ): (() => MaybePromise) | undefined } composeHandler: { mapResponseContext?: string /** * Declare any variable that will be used in the general handler */ declare?(inference: Sucrose.Inference): string | undefined /** * Inject variable to the general handler */ inject?: Record /** * Whether retriving headers should be using webstandard headers * * @default false */ preferWebstandardHeaders?: boolean /** * fnLiteral for parsing request headers * * @declaration * c.headers: Context headers */ headers: string /** * fnLiteral for parsing the request body * * @declaration * c.body: Context body */ parser: Prettify< Record< 'json' | 'text' | 'urlencoded' | 'arrayBuffer' | 'formData', (isOptional: boolean) => string > & { declare?: string } > } composeGeneralHandler: { parameters?: string error404( hasEventHook: boolean, hasErrorHook: boolean, afterResponseHandler?: string ): { declare: string code: string } /** * fnLiteral of the general handler * * @declaration * c: Context * p: pathname */ createContext(app: AnyElysia): string /** * Inject variable to the general handler */ inject?: Record } composeError: { declare?: string inject?: Record mapResponseContext: string validationError: string /** * Handle thrown error which is instance of Error * * Despite its name of `unknownError`, it also handle named error like `NOT_FOUND`, `VALIDATION_ERROR` * It's named `unknownError` because it also catch unknown error */ unknownError: string } ws?(app: AnyElysia, path: string, handler: AnyWSLocalHook): unknown /** * Whether or not the runtime or framework the is built on top on has a router * eg. Bun.serve.routes, uWebSocket **/ createSystemRouterHandler?( method: string, path: string, hook: AnyLocalHook, app: AnyElysia ): void /** * Call thing before compile */ beforeCompile?(app: AnyElysia): void } ================================================ FILE: src/adapter/utils.ts ================================================ import { serializeCookie } from '../cookies' import { hasHeaderShorthand, isNotEmpty, StatusMap } from '../utils' import type { Context } from '../context' import { env } from '../universal' import { isBun } from '../universal/utils' import { MaybePromise } from '../types' export const handleFile = ( response: File | Blob, set?: Context['set'], request?: Request ): Response => { if (!isBun && response instanceof Promise) return response.then((res) => handleFile(res, set, request)) as any const size = response.size const rangeHeader = request?.headers.get('range') if (rangeHeader) { const match = /bytes=(\d*)-(\d*)/.exec(rangeHeader) if (match) { if (!match[1] && !match[2]) return new Response(null, { status: 416, headers: mergeHeaders( new Headers({ 'content-range': `bytes */${size}` }), set?.headers ?? {} ) }) let start: number let end: number if (!match[1] && match[2]) { const suffix = parseInt(match[2]) start = Math.max(0, size - suffix) end = size - 1 } else { start = match[1] ? parseInt(match[1]) : 0 end = match[2] ? Math.min(parseInt(match[2]), size - 1) : size - 1 } if (start >= size || start > end) { return new Response(null, { status: 416, headers: mergeHeaders( new Headers({ 'content-range': `bytes */${size}` }), set?.headers ?? {} ) }) } const contentLength = end - start + 1 const rangeHeaders = new Headers({ 'accept-ranges': 'bytes', 'content-range': `bytes ${start}-${end}/${size}`, 'content-length': String(contentLength) }) // Blob.slice() exists at runtime but is absent from the ESNext lib typings // (no DOM lib). Cast through unknown to the minimal interface we need. // Pass response.type as third arg so the sliced blob preserves MIME type. return new Response( ( response as unknown as { slice( start: number, end: number, contentType?: string ): Blob } ).slice(start, end + 1, response.type), { status: 206, headers: mergeHeaders(rangeHeaders, set?.headers ?? {}) } ) } } const immutable = set && (set.status === 206 || set.status === 304 || set.status === 412 || set.status === 416) const defaultHeader = immutable ? {} : ({ 'accept-ranges': 'bytes', 'content-range': size ? `bytes 0-${size - 1}/${size}` : undefined } as Record) if (!set && !size) return new Response(response as Blob) if (!set) return new Response(response as Blob, { headers: defaultHeader }) if (set.headers instanceof Headers) { for (const key of Object.keys(defaultHeader)) if (key in set.headers) set.headers.append(key, defaultHeader[key]) if (immutable) { set.headers.delete('content-length') set.headers.delete('accept-ranges') } return new Response(response as Blob, set as any) } if (isNotEmpty(set.headers)) return new Response(response as Blob, { status: set.status as number, headers: Object.assign(defaultHeader, set.headers) }) return new Response(response as Blob, { status: set.status as number, headers: defaultHeader }) } export const parseSetCookies = (headers: Headers, setCookie: string[]) => { if (!headers) return headers headers.delete('set-cookie') for (let i = 0; i < setCookie.length; i++) { const index = setCookie[i].indexOf('=') headers.append( 'set-cookie', `${setCookie[i].slice(0, index)}=${ setCookie[i].slice(index + 1) || '' }` ) } return headers } export const responseToSetHeaders = ( response: Response, set?: Context['set'] ) => { if (set?.headers) { if (response) { if (hasHeaderShorthand) Object.assign(set.headers, response.headers.toJSON()) else for (const [key, value] of response.headers.entries()) if (key in set.headers) set.headers[key] = value } if (set.status === 200) set.status = response.status // ? `content-encoding` prevent response streaming if (set.headers['content-encoding']) delete set.headers['content-encoding'] return set } if (!response) return { headers: {}, status: set?.status ?? 200 } if (hasHeaderShorthand) { set = { headers: response.headers.toJSON(), status: set?.status ?? 200 } // ? `content-encoding` prevent response streaming if (set.headers['content-encoding']) delete set.headers['content-encoding'] return set } set = { headers: {}, status: set?.status ?? 200 } for (const [key, value] of response.headers.entries()) { // ? `content-encoding` prevent response streaming if (key === 'content-encoding') continue if (key in set.headers) set.headers[key] = value } return set } interface CreateHandlerParameter { mapResponse( response: unknown, set: Context['set'], request?: Request ): Response mapCompactResponse(response: unknown, request?: Request): Response } const enqueueBinaryChunk = ( controller: ReadableStreamDefaultController, chunk: unknown ): MaybePromise => { if (chunk instanceof Blob) return chunk.arrayBuffer().then((buffer) => { controller.enqueue(new Uint8Array(buffer)) return true as const }) if (chunk instanceof Uint8Array) { controller.enqueue(chunk) return true } if (chunk instanceof ArrayBuffer) { controller.enqueue(new Uint8Array(chunk)) return true } if (ArrayBuffer.isView(chunk)) { controller.enqueue( new Uint8Array(chunk.buffer, chunk.byteOffset, chunk.byteLength) ) return true } return false } export const createStreamHandler = ({ mapResponse, mapCompactResponse }: CreateHandlerParameter) => async ( generator: Generator | AsyncGenerator | ReadableStream, set?: Context['set'], request?: Request, skipFormat?: boolean ) => { // Since ReadableStream doesn't have next, init might be undefined let init = (generator as Generator).next?.() as | IteratorResult | undefined if (set) handleSet(set) if (init instanceof Promise) init = await init // Generator or ReadableStream is returned from a generator function if (init?.value instanceof ReadableStream) { // @ts-ignore generator = init.value } else if (init && (typeof init?.done === 'undefined' || init?.done)) { if (set) return mapResponse(init.value, set, request) return mapCompactResponse(init.value, request) } // Check if stream is from a pre-formatted Response body const isSSE = !skipFormat && // @ts-ignore First SSE result is wrapped with sse() (init?.value?.sse ?? // @ts-ignore ReadableStream is wrapped with sse() generator?.sse ?? // User explicitly set content-type to SSE set?.headers['content-type']?.startsWith('text/event-stream')) const format = isSSE ? (data: string) => `data: ${data}\n\n` : (data: string) => data const contentType = isSSE ? 'text/event-stream' : init?.value && typeof init?.value === 'object' ? 'application/json' : 'text/plain' if (set?.headers) { if (!set.headers['transfer-encoding']) set.headers['transfer-encoding'] = 'chunked' if (!set.headers['content-type']) set.headers['content-type'] = contentType if (!set.headers['cache-control']) set.headers['cache-control'] = 'no-cache' } else set = { status: 200, headers: { 'content-type': contentType, 'transfer-encoding': 'chunked', 'cache-control': 'no-cache', connection: 'keep-alive' } } // Get an explicit async iterator so pull() can advance one step at a time. // Generators already implement the iterator protocol directly (.next()), // while ReadableStream (which generator may be reassigned to above) needs // [Symbol.asyncIterator]() to produce one. const iterator: AsyncIterator = typeof (generator as any).next === 'function' ? (generator as AsyncIterator) : (generator as any)[Symbol.asyncIterator]() let end = false return new Response( new ReadableStream({ start(controller) { // Register abort handler once — terminates the iterator and // closes the stream so pull() won't be called again. request?.signal?.addEventListener('abort', () => { end = true iterator.return?.() try { controller.close() } catch {} }) // Enqueue the already-extracted init value (first generator // result, used above for SSE detection). Subsequent values // are produced on-demand by pull(). if ( !init || init.value instanceof ReadableStream || init.value === undefined || init.value === null ) return // @ts-ignore if (init.value.toSSE) // @ts-ignore controller.enqueue(init.value.toSSE()) else if (enqueueBinaryChunk(controller, init.value)) return else if (typeof init.value === 'object') try { controller.enqueue( format(JSON.stringify(init.value)) ) } catch { controller.enqueue(format(init.value.toString())) } else controller.enqueue(format(init.value.toString())) }, async pull(controller) { // Respect abort/cancel that happened between pull() calls. if (end) { try { controller.close() } catch {} return } try { const { value: chunk, done } = await iterator.next() if (done || end) { try { controller.close() } catch {} return } // null/undefined chunks are skipped; the runtime will // call pull() again since nothing was enqueued. if (chunk === undefined || chunk === null) return // @ts-ignore if (chunk.toSSE) // @ts-ignore controller.enqueue(chunk.toSSE()) else if (enqueueBinaryChunk(controller, chunk)) return else if (typeof chunk === 'object') try { controller.enqueue( format(JSON.stringify(chunk)) ) } catch { controller.enqueue(format(chunk.toString())) } else controller.enqueue(format(chunk.toString())) } catch (error) { console.warn(error) try { controller.close() } catch {} } }, cancel() { end = true iterator.return?.() } }), set as any ) } export async function* streamResponse(response: Response) { const body = response.body if (!body) return const reader = body.getReader() const decoder = new TextDecoder() try { while (true) { const { done, value } = await reader.read() if (done) break if (typeof value === 'string') yield value else yield decoder.decode(value) } } finally { reader.releaseLock() } } export const handleSet = (set: Context['set']) => { if (typeof set.status === 'string') set.status = StatusMap[set.status] if (set.cookie && isNotEmpty(set.cookie)) { const cookie = serializeCookie(set.cookie) if (cookie) set.headers['set-cookie'] = cookie } if (set.headers['set-cookie'] && Array.isArray(set.headers['set-cookie'])) { set.headers = parseSetCookies( new Headers(set.headers as any) as Headers, set.headers['set-cookie'] ) as any } } // Merge header by allocating a new one // In Bun, response.headers can be mutable // while in Node and Cloudflare Worker is not // default to creating a new one instead export function mergeHeaders( responseHeaders: Headers, setHeaders: Context['set']['headers'] ) { // Direct clone preserves all headers including multiple set-cookie const headers = new Headers(responseHeaders) // Merge headers: Response headers take precedence, set.headers fill in non-conflicting ones if (setHeaders instanceof Headers) for (const key of setHeaders.keys()) { if (key === 'set-cookie') { if (headers.has('set-cookie')) continue for (const cookie of setHeaders.getSetCookie()) headers.append('set-cookie', cookie) } else if (!responseHeaders.has(key)) headers.set(key, setHeaders?.get(key) ?? '') } else for (const key in setHeaders) if (key === 'set-cookie') headers.append(key, setHeaders[key] as any) else if (!responseHeaders.has(key)) headers.set(key, setHeaders[key] as any) return headers } export function mergeStatus( responseStatus: number, setStatus: Context['set']['status'] ) { if (typeof setStatus === 'string') setStatus = StatusMap[setStatus] if (responseStatus === 200) return setStatus return responseStatus } export const createResponseHandler = (handler: CreateHandlerParameter) => { const handleStream = createStreamHandler(handler) return (response: Response, set: Context['set'], request?: Request) => { const newResponse = new Response(response.body, { headers: mergeHeaders(response.headers, set.headers), status: mergeStatus(response.status, set.status) }) if ( !(newResponse as Response).headers.has('content-length') && (newResponse as Response).headers.get('transfer-encoding') === 'chunked' ) return handleStream( streamResponse(newResponse as Response), responseToSetHeaders(newResponse as Response, set), request, true // don't auto-format SSE for pre-formatted Response ) as any return newResponse } } export async function tee( source: AsyncIterable, branches = 2 ): Promise[]> { const buffer: T[] = [] let done = false let waiting: { resolve: () => void }[] = [] ;(async () => { for await (const value of source) { buffer.push(value) waiting.forEach((w) => w.resolve()) waiting = [] } done = true waiting.forEach((w) => w.resolve()) })() async function* makeIterator(): AsyncIterableIterator { let i = 0 while (true) { if (i < buffer.length) { yield buffer[i++] } else if (done) { return } else { await new Promise((resolve) => waiting.push({ resolve })) } } } return Array.from({ length: branches }, makeIterator) } ================================================ FILE: src/adapter/web-standard/handler.ts ================================================ /* eslint-disable sonarjs/no-nested-switch */ /* eslint-disable sonarjs/no-duplicate-string */ import { createResponseHandler, createStreamHandler, handleFile, handleSet } from '../utils' import { ElysiaFile, mime } from '../../universal/file' import { isNotEmpty } from '../../utils' import { Cookie } from '../../cookies' import { ElysiaCustomStatusResponse } from '../../error' import type { Context } from '../../context' import type { AnyLocalHook, MaybePromise } from '../../types' const handleElysiaFile = ( file: ElysiaFile, set: Context['set'] = { headers: {} }, request?: Request ) => { const path = file.path const contentType = mime[path.slice(path.lastIndexOf('.') + 1) as any as keyof typeof mime] if (contentType) set.headers['content-type'] = contentType if ( file.stats && set.status !== 206 && set.status !== 304 && set.status !== 412 && set.status !== 416 ) return file.stats!.then((stat) => { const size = stat.size as number if (size !== undefined) { set.headers['content-range'] = `bytes 0-${size - 1}/${size}` set.headers['content-length'] = size } return handleFile(file.value as any, set, request) }) as any return handleFile(file.value as any, set, request) } export const mapResponse = ( response: unknown, set: Context['set'], request?: Request ): Response => { if (isNotEmpty(set.headers) || set.status !== 200 || set.cookie) { handleSet(set) switch (response?.constructor?.name) { case 'String': if (!set.headers['content-type']) set.headers['content-type'] = 'text/plain' return new Response(response as string, set as any) case 'Array': case 'Object': if (!set.headers['content-type']) set.headers['content-type'] = 'application/json' return new Response(JSON.stringify(response), set as any) case 'ElysiaFile': return handleElysiaFile(response as ElysiaFile, set, request) case 'File': return handleFile(response as File, set, request) case 'Blob': return handleFile(response as Blob, set, request) case 'ElysiaCustomStatusResponse': set.status = (response as ElysiaCustomStatusResponse<200>).code return mapResponse( (response as ElysiaCustomStatusResponse<200>).response, set, request ) case undefined: if (!response) return new Response('', set as any) return new Response(JSON.stringify(response), set as any) case 'Response': return handleResponse(response as Response, set, request) case 'Error': return errorToResponse(response as Error, set) case 'Promise': return (response as Promise).then((x) => mapResponse(x, set, request) ) as any case 'Function': return mapResponse((response as Function)(), set, request) case 'Number': case 'Boolean': return new Response( (response as number | boolean).toString(), set as any ) case 'Cookie': if (response instanceof Cookie) return new Response(response.value, set as any) return new Response(response?.toString(), set as any) case 'FormData': return new Response(response as FormData, set as any) default: // recheck Response, Promise, Error because some library may extends Response if (response instanceof Response) return handleResponse(response as Response, set, request) if (response instanceof Promise) return response.then((x) => mapResponse(x, set)) as any if (response instanceof Error) return errorToResponse(response as Error, set) if (response instanceof ElysiaCustomStatusResponse) { set.status = ( response as ElysiaCustomStatusResponse<200> ).code return mapResponse( (response as ElysiaCustomStatusResponse<200>).response, set, request ) } if ( // @ts-expect-error typeof response?.next === 'function' || response instanceof ReadableStream ) return handleStream(response as any, set, request) as any if (typeof (response as Promise)?.then === 'function') return (response as Promise).then((x) => mapResponse(x, set) ) as any // custom class with an array-like value // eg. Bun.sql`` result if (Array.isArray(response)) return new Response(JSON.stringify(response), { headers: { 'Content-Type': 'application/json' } }) as any // @ts-expect-error if (typeof response?.toResponse === 'function') return mapResponse((response as any).toResponse(), set) if ('charCodeAt' in (response as any)) { const code = (response as any).charCodeAt(0) if (code === 123 || code === 91) { if (!set.headers['Content-Type']) set.headers['Content-Type'] = 'application/json' return new Response( JSON.stringify(response), set as any ) as any } } return new Response(response as any, set as any) } } // Stream response defers a 'set' API, assume that it may include 'set' if ( // @ts-expect-error typeof response?.next === 'function' || response instanceof ReadableStream ) return handleStream(response as any, set, request) as any return mapCompactResponse(response, request) } export const mapEarlyResponse = ( response: unknown, set: Context['set'], request?: Request ): Response | undefined => { if (response === undefined || response === null) return if (isNotEmpty(set.headers) || set.status !== 200 || set.cookie) { handleSet(set) switch (response?.constructor?.name) { case 'String': if (!set.headers['content-type']) set.headers['content-type'] = 'text/plain' return new Response(response as string, set as any) case 'Array': case 'Object': if (!set.headers['content-type']) set.headers['content-type'] = 'application/json' return new Response(JSON.stringify(response), set as any) case 'ElysiaFile': return handleElysiaFile(response as ElysiaFile, set, request) case 'File': return handleFile(response as File, set, request) case 'Blob': return handleFile(response as File | Blob, set, request) case 'ElysiaCustomStatusResponse': set.status = (response as ElysiaCustomStatusResponse<200>).code return mapEarlyResponse( (response as ElysiaCustomStatusResponse<200>).response, set, request ) case undefined: if (!response) return return new Response(JSON.stringify(response), set as any) case 'Response': return handleResponse(response as Response, set, request) case 'Promise': return (response as Promise).then((x) => mapEarlyResponse(x, set) ) as any case 'Error': return errorToResponse(response as Error, set) case 'Function': return mapEarlyResponse((response as Function)(), set) case 'Number': case 'Boolean': return new Response( (response as number | boolean).toString(), set as any ) case 'FormData': return new Response(response as FormData) case 'Cookie': if (response instanceof Cookie) return new Response(response.value, set as any) return new Response(response?.toString(), set as any) default: if (response instanceof Response) return handleResponse(response, set, request) if (response instanceof Promise) return response.then((x) => mapEarlyResponse(x, set)) as any if (response instanceof Error) return errorToResponse(response as Error, set) if (response instanceof ElysiaCustomStatusResponse) { set.status = ( response as ElysiaCustomStatusResponse<200> ).code return mapEarlyResponse( (response as ElysiaCustomStatusResponse<200>).response, set, request ) } if ( // @ts-expect-error typeof response?.next === 'function' || response instanceof ReadableStream ) return handleStream(response as any, set, request) as any if (typeof (response as Promise)?.then === 'function') return (response as Promise).then((x) => mapEarlyResponse(x, set) ) as any // @ts-expect-error if (typeof response?.toResponse === 'function') return mapEarlyResponse((response as any).toResponse(), set) // custom class with an array-like value // eg. Bun.sql`` result if (Array.isArray(response)) return new Response(JSON.stringify(response), { headers: { 'Content-Type': 'application/json' } }) as any if ('charCodeAt' in (response as any)) { const code = (response as any).charCodeAt(0) if (code === 123 || code === 91) { if (!set.headers['Content-Type']) set.headers['Content-Type'] = 'application/json' return new Response( JSON.stringify(response), set as any ) as any } } return new Response(response as any, set as any) } } else switch (response?.constructor?.name) { case 'String': if (!set.headers['content-type']) set.headers['content-type'] = 'text/plain' return new Response(response as string) case 'Array': case 'Object': if (!set.headers['content-type']) set.headers['content-type'] = 'application/json' return new Response(JSON.stringify(response), set as any) case 'ElysiaFile': return handleElysiaFile(response as ElysiaFile, set, request) case 'File': return handleFile(response as File, set, request) case 'Blob': return handleFile(response as File | Blob, set, request) case 'ElysiaCustomStatusResponse': set.status = (response as ElysiaCustomStatusResponse<200>).code return mapEarlyResponse( (response as ElysiaCustomStatusResponse<200>).response, set, request ) case undefined: if (!response) return new Response('') return new Response(JSON.stringify(response), { headers: { 'content-type': 'application/json' } }) case 'Response': return response as Response case 'Promise': return (response as Promise).then((x) => { const r = mapEarlyResponse(x, set) if (r !== undefined) return r }) as any case 'Error': return errorToResponse(response as Error, set) case 'Function': return mapCompactResponse((response as Function)(), request) case 'Number': case 'Boolean': return new Response((response as number | boolean).toString()) case 'Cookie': if (response instanceof Cookie) return new Response(response.value, set as any) return new Response(response?.toString(), set as any) case 'FormData': return new Response(response as FormData) default: if (response instanceof Response) return response if (response instanceof Promise) return response.then((x) => mapEarlyResponse(x, set)) as any if (response instanceof Error) return errorToResponse(response as Error, set) if (response instanceof ElysiaCustomStatusResponse) { set.status = ( response as ElysiaCustomStatusResponse<200> ).code return mapEarlyResponse( (response as ElysiaCustomStatusResponse<200>).response, set, request ) } if ( // @ts-expect-error typeof response?.next === 'function' || response instanceof ReadableStream ) return handleStream(response as any, set, request) as any if (typeof (response as Promise)?.then === 'function') return (response as Promise).then((x) => mapEarlyResponse(x, set) ) as any // @ts-expect-error if (typeof response?.toResponse === 'function') return mapEarlyResponse((response as any).toResponse(), set) // custom class with an array-like value // eg. Bun.sql`` result if (Array.isArray(response)) return new Response(JSON.stringify(response), { headers: { 'Content-Type': 'application/json' } }) as any if ('charCodeAt' in (response as any)) { const code = (response as any).charCodeAt(0) if (code === 123 || code === 91) { if (!set.headers['Content-Type']) set.headers['Content-Type'] = 'application/json' return new Response( JSON.stringify(response), set as any ) as any } } return new Response(response as any) } } export const mapCompactResponse = ( response: unknown, request?: Request ): Response => { switch (response?.constructor?.name) { case 'String': return new Response(response as string, { headers: { 'Content-Type': 'text/plain' } }) case 'Object': case 'Array': return new Response(JSON.stringify(response), { headers: { 'Content-Type': 'application/json' } }) case 'ElysiaFile': return handleElysiaFile(response as ElysiaFile, undefined, request) case 'File': return handleFile(response as File, undefined, request) case 'Blob': return handleFile(response as File | Blob, undefined, request) case 'ElysiaCustomStatusResponse': return mapResponse( (response as ElysiaCustomStatusResponse<200>).response, { status: (response as ElysiaCustomStatusResponse<200>).code, headers: {} } ) case undefined: if (!response) return new Response('') return new Response(JSON.stringify(response), { headers: { 'content-type': 'application/json' } }) case 'Response': return response as Response case 'Error': return errorToResponse(response as Error) case 'Promise': return (response as any as Promise).then((x) => mapCompactResponse(x, request) ) as any // ? Maybe response or Blob case 'Function': return mapCompactResponse((response as Function)(), request) case 'Number': case 'Boolean': return new Response((response as number | boolean).toString()) case 'FormData': return new Response(response as FormData) default: if (response instanceof Response) return response if (response instanceof Promise) return response.then((x) => mapCompactResponse(x, request) ) as any if (response instanceof Error) return errorToResponse(response as Error) if (response instanceof ElysiaCustomStatusResponse) return mapResponse( (response as ElysiaCustomStatusResponse<200>).response, { status: (response as ElysiaCustomStatusResponse<200>) .code, headers: {} } ) if ( // @ts-expect-error typeof response?.next === 'function' || response instanceof ReadableStream ) return handleStream(response as any, undefined, request) as any if (typeof (response as Promise)?.then === 'function') return (response as Promise).then((x) => mapCompactResponse(x, request) ) as any // @ts-expect-error if (typeof response?.toResponse === 'function') return mapCompactResponse((response as any).toResponse()) // custom class with an array-like value // eg. Bun.sql`` result if (Array.isArray(response)) return new Response(JSON.stringify(response), { headers: { 'Content-Type': 'application/json' } }) as any if ('charCodeAt' in (response as any)) { const code = (response as any).charCodeAt(0) if (code === 123 || code === 91) { return new Response(JSON.stringify(response), { headers: { 'Content-Type': 'application/json' } }) as any } } return new Response(response as any) } } export const errorToResponse = ( error: Error & { toResponse?(): MaybePromise }, set?: Context['set'] ) => { if (typeof error?.toResponse === 'function') { const raw = error.toResponse() const targetSet = set ?? ({ headers: {}, status: 200, redirect: '' } as Context['set']) const apply = (resolved: unknown) => { if (resolved instanceof Response) targetSet.status = resolved.status return mapResponse(resolved, targetSet) } // @ts-ignore return typeof raw?.then === 'function' ? raw.then(apply) : apply(raw) } return new Response( JSON.stringify({ name: error?.name, message: error?.message, cause: error?.cause }), { status: set?.status !== 200 ? ((set?.status as number) ?? 500) : 500, headers: set?.headers as any } ) } export const createStaticHandler = ( handle: unknown, hooks: Partial, setHeaders: Context['set']['headers'] = {} ): (() => Response) | undefined => { if (typeof handle === 'function') return const response = mapResponse(handle, { headers: setHeaders }) if ( !hooks.parse?.length && !hooks.transform?.length && !hooks.beforeHandle?.length && !hooks.afterHandle?.length ) return () => response.clone() as Response } const handleResponse = createResponseHandler({ mapResponse, mapCompactResponse }) const handleStream = createStreamHandler({ mapResponse, mapCompactResponse }) ================================================ FILE: src/adapter/web-standard/index.ts ================================================ import { mapResponse, mapEarlyResponse, mapCompactResponse, createStaticHandler } from './handler' import type { ElysiaAdapter } from '../types' export const WebStandardAdapter: ElysiaAdapter = { name: 'web-standard', isWebStandard: true, handler: { mapResponse, mapEarlyResponse, mapCompactResponse, createStaticHandler }, composeHandler: { mapResponseContext: 'c.request', preferWebstandardHeaders: true, // @ts-ignore Bun specific headers: 'c.headers={}\n' + 'for(const [k,v] of c.request.headers.entries())' + 'c.headers[k]=v\n', parser: { json(isOptional) { if (isOptional) return `try{c.body=await c.request.json()}catch{}\n` return `c.body=await c.request.json()\n` }, text() { return `c.body=await c.request.text()\n` }, urlencoded() { return `c.body=parseQuery(await c.request.text())\n` }, arrayBuffer() { return `c.body=await c.request.arrayBuffer()\n` }, formData(isOptional) { let fnLiteral = '\nc.body={}\n' if (isOptional) fnLiteral += `let form;try{form=await c.request.formData()}catch{}` else fnLiteral += `const form=await c.request.formData()\n` return ( fnLiteral + `const dangerousKeys=new Set(['__proto__','constructor','prototype'])\n` + `const isDangerousKey=(k)=>{` + `if(dangerousKeys.has(k))return true;` + `const m=k.match(/^(.+)\\[(\\d+)\\]$/);` + `return m?dangerousKeys.has(m[1]):false` + `}\n` + `const parseArrayKey=(k)=>{` + `const m=k.match(/^(.+)\\[(\\d+)\\]$/);` + `return m?{name:m[1],index:parseInt(m[2],10)}:null` + `}\n` + `for(const key of form.keys()){` + `if(c.body[key])continue\n` + `const value=form.getAll(key)\n` + `let finalValue\n` + `if(value.length===1){\n` + `const sv=value[0]\n` + `if(typeof sv==='string'&&(sv.charCodeAt(0)===123||sv.charCodeAt(0)===91)){\n` + `try{\n` + `const p=JSON.parse(sv)\n` + `if(p&&typeof p==='object')finalValue=p\n` + `}catch{}\n` + `}\n` + `if(finalValue===undefined)finalValue=sv\n` + `}else finalValue=value\n` + `if(Array.isArray(finalValue)){\n` + `const stringValue=finalValue.find((entry)=>typeof entry==='string')\n` + `const files=typeof File==='undefined'?[]:finalValue.filter((entry)=>entry instanceof File)\n` + `if(stringValue&&files.length&&stringValue.charCodeAt(0)===123){\n` + `try{\n` + `const parsed=JSON.parse(stringValue)\n` + `if(parsed&&typeof parsed==='object'&&!Array.isArray(parsed)){\n` + `if(!('file' in parsed)&&files.length===1)parsed.file=files[0]\n` + `else if(!('files' in parsed)&&files.length>1)parsed.files=files\n` + `finalValue=parsed\n` + `}\n` + `}catch{}\n` + `}\n` + `}\n` + `if(key.includes('.')||key.includes('[')){` + `const keys=key.split('.')\n` + `const lastKey=keys.pop()\n` + `if(isDangerousKey(lastKey)||keys.some(isDangerousKey))continue\n` + `let current=c.body\n` + `for(const k of keys){` + `const arrayInfo=parseArrayKey(k)\n` + `if(arrayInfo){` + `if(!Array.isArray(current[arrayInfo.name]))current[arrayInfo.name]=[]\n` + `const existing=current[arrayInfo.name][arrayInfo.index]\n` + `const isFile=typeof File!=='undefined'&&existing instanceof File\n` + `if(!existing||typeof existing!=='object'||Array.isArray(existing)||isFile){\n` + `let parsed\n` + `if(typeof existing==='string'&&existing.charCodeAt(0)===123){\n` + `try{` + `parsed=JSON.parse(existing)\n` + `if(!parsed||typeof parsed!=='object'||Array.isArray(parsed))parsed=undefined` + `}catch{}\n` + `}\n` + `current[arrayInfo.name][arrayInfo.index]=parsed||{}\n` + `}\n` + `current=current[arrayInfo.name][arrayInfo.index]` + `}else{` + `if(!current[k]||typeof current[k]!=='object')current[k]={}\n` + `current=current[k]` + `}` + `}\n` + `const arrayInfo=parseArrayKey(lastKey)\n` + `if(arrayInfo){` + `if(!Array.isArray(current[arrayInfo.name]))current[arrayInfo.name]=[]\n` + `current[arrayInfo.name][arrayInfo.index]=finalValue` + `}else{` + `current[lastKey]=finalValue` + `}` + `}else c.body[key]=finalValue` + `}` ) } } }, async stop(app, closeActiveConnections) { if (!app.server) throw new Error( "Elysia isn't running. Call `app.listen` to start the server." ) if (app.server) { await app.server.stop(closeActiveConnections) app.server = null if (app.event.stop?.length) for (let i = 0; i < app.event.stop.length; i++) app.event.stop[i].fn(app) } }, composeGeneralHandler: { parameters: 'r', createContext(app) { let decoratorsLiteral = '' let fnLiteral = '' // @ts-expect-error private const defaultHeaders = app.setHeaders for (const key of Object.keys(app.decorator)) decoratorsLiteral += `,'${key}':decorator['${key}']` const standardHostname = app.config.handler?.standardHostname ?? true const hasTrace = !!app.event.trace?.length fnLiteral += `const u=r.url,` + `s=u.indexOf('/',${standardHostname ? 11 : 7}),` + `qi=u.indexOf('?',s+1),` + `p=u.substring(s,qi===-1?undefined:qi)\n` if (hasTrace) fnLiteral += `const id=randomId()\n` fnLiteral += `const c={request:r,` + `store,` + `qi,` + `path:p,` + `url:u,` + `redirect,` + `status,` + `set:{headers:` fnLiteral += Object.keys(defaultHeaders ?? {}).length ? 'Object.assign({},app.setHeaders)' : 'Object.create(null)' fnLiteral += `,status:200}` // @ts-expect-error private if (app.inference.server) fnLiteral += `,get server(){return app.getServer()}` if (hasTrace) fnLiteral += ',[ELYSIA_REQUEST_ID]:id' fnLiteral += decoratorsLiteral fnLiteral += `}\n` return fnLiteral }, error404(hasEventHook, hasErrorHook, afterHandle = '') { let findDynamicRoute = `if(route===null){` + afterHandle + (hasErrorHook ? '' : 'c.set.status=404') + '\nreturn ' if (hasErrorHook) findDynamicRoute += `app.handleError(c,notFound,false,${this.parameters})` else findDynamicRoute += hasEventHook ? `c.response=c.responseValue=new Response(error404Message,{` + `status:c.set.status===200?404:c.set.status,` + `headers:c.set.headers` + `})` : `c.response=c.responseValue=error404.clone()` findDynamicRoute += '}' return { declare: hasErrorHook ? '' : `const error404Message=notFound.message.toString()\n` + `const error404=new Response(error404Message,{status:404})\n`, code: findDynamicRoute } } }, composeError: { mapResponseContext: '', validationError: `set.headers['content-type']='application/json';` + `return mapResponse(error.message,set)`, unknownError: `set.status=error.status??set.status??500;` + `return mapResponse(error.message,set)` }, listen() { return () => { throw new Error( 'WebStandard does not support listen, you might want to export default Elysia.fetch instead' ) } } } ================================================ FILE: src/compose.ts ================================================ import { AnyElysia, Cookie } from './index' import { Value, TransformDecodeError } from '@sinclair/typebox/value' import { Kind, TypeBoxError, type TAnySchema, type TSchema } from '@sinclair/typebox' import decode from 'fast-decode-uri-component' import { parseQuery, parseQueryFromURL, parseQueryStandardSchema } from './parse-query' import { ELYSIA_REQUEST_ID, getLoosePath, hasSetImmediate, lifeCycleToFn, randomId, redirect, signCookie, isNotEmpty, encodePath, mergeCookie, getResponseLength } from './utils' import { isBun } from './universal/utils' import { ParseError, status } from './error' import { NotFoundError, ValidationError, ERROR_CODE, ElysiaCustomStatusResponse } from './error' import { ELYSIA_TRACE, type TraceHandler } from './trace' import { ElysiaTypeCheck, getCookieValidator, getSchemaProperties, getSchemaValidator, hasElysiaMeta, hasType, isUnion, unwrapImportSchema } from './schema' import { Sucrose, sucrose } from './sucrose' import { parseCookie, type CookieOptions } from './cookies' import { fileType } from './type-system/utils' import type { TraceEvent } from './trace' import type { ComposedHandler, ElysiaConfig, Handler, HookContainer, LifeCycleStore, MaybePromise, SchemaValidator } from './types' import { tee } from './adapter/utils' import { coercePrimitiveRoot } from './replace-schema' const allocateIf = (value: string, condition: unknown) => condition ? value : '' const defaultParsers = [ 'json', 'text', 'urlencoded', 'arrayBuffer', 'formdata', 'application/json', // eslint-disable-next-line sonarjs/no-duplicate-string 'text/plain', // eslint-disable-next-line sonarjs/no-duplicate-string 'application/x-www-form-urlencoded', // eslint-disable-next-line sonarjs/no-duplicate-string 'application/octet-stream', // eslint-disable-next-line sonarjs/no-duplicate-string 'multipart/form-data' ] const createReport = ({ context = 'c', trace = [], addFn }: { context?: string trace?: (TraceHandler | HookContainer)[] addFn(string: string): void }) => { if (!trace.length) return () => { return { resolveChild() { return () => {} }, resolve() {} } } for (let i = 0; i < trace.length; i++) addFn( `let report${i},reportChild${i},reportErr${i},reportErrChild${i};` + `let trace${i}=${context}[ELYSIA_TRACE]?.[${i}]??trace[${i}](${context});\n` ) // const aliases: string[] = [] return ( event: TraceEvent, { name, total = 0, alias }: { name?: string attribute?: string total?: number alias?: string } = {} ) => { // ? For debug specific event // if (event !== 'mapResponse') // return { // resolveChild() { // return () => {} // }, // resolve() {} // } if (!name) name = 'anonymous' const reporter = event === 'error' ? 'reportErr' : 'report' for (let i = 0; i < trace.length; i++) { addFn( `${alias ? 'const ' : ''}${alias ?? reporter}${i}=trace${i}.${event}({` + `id,` + `event:'${event}',` + `name:'${name}',` + `begin:performance.now(),` + `total:${total}` + `})\n` ) if (alias) addFn(`${reporter}${i}=${alias}${i}\n`) } // if (event === 'error') // for (const alias of aliases) // for (let i = 0; i < trace.length; i++) // addFn( // `const ${alias}Err${i}=trace${i}.${event}({` + // `id,` + // `event:'${event}',` + // `name:'${name}',` + // `begin:performance.now(),` + // `total:${total}` + // `})\n` // ) return { resolve() { for (let i = 0; i < trace.length; i++) addFn(`${alias ?? reporter}${i}.resolve()\n`) }, resolveChild(name: string) { for (let i = 0; i < trace.length; i++) { addFn( `${reporter}Child${i}=${reporter}${i}.resolveChild?.shift()?.({` + `id,` + `event:'${event}',` + `name:'${name}',` + `begin:performance.now()` + `})\n` ) } return (binding?: string) => { for (let i = 0; i < trace.length; i++) { if (binding) // Don't report error because HTTP response is expected and not an actual error to look for // if (${binding} instanceof ElysiaCustomStatusResponse) { // ${reporter}Child${i}?.(${binding}.error) // ${reporter}Child${i}?.()\n // } else addFn( `if(${binding} instanceof Error){` + `${reporter}Child${i}?.(${binding}) ` + `}else{` + `${reporter}Child${i}?.()` + '}' ) else addFn(`${reporter}Child${i}?.()\n`) } } } } } } const composeCleaner = ({ schema, name, type, typeAlias = type, normalize, ignoreTryCatch = false }: { schema: ElysiaTypeCheck name: string type: keyof SchemaValidator typeAlias?: string normalize: ElysiaConfig<''>['normalize'] ignoreTryCatch?: boolean }) => { if (!normalize || !schema.Clean) return '' if (normalize === true || normalize === 'exactMirror') { if (ignoreTryCatch) return `${name}=validator.${typeAlias}.Clean(${name})\n` return ( `try{` + `${name}=validator.${typeAlias}.Clean(${name})\n` + `}catch{}` ) } if (normalize === 'typebox') return `${name}=validator.${typeAlias}.Clean(${name})\n` return '' } const composeValidationFactory = ({ injectResponse = '', normalize = false, validator, encodeSchema = false, isStaticResponse = false, hasSanitize = false, allowUnsafeValidationDetails = false }: { injectResponse?: string normalize?: ElysiaConfig<''>['normalize'] validator: SchemaValidator encodeSchema?: boolean isStaticResponse?: boolean hasSanitize?: boolean allowUnsafeValidationDetails?: boolean }) => ({ validate: (type: string, value = `c.${type}`, error?: string) => `c.set.status=422;throw new ValidationError('${type}',validator.${type},${value},${allowUnsafeValidationDetails}${error ? ',' + error : ''})`, response: (name = 'r') => { if (isStaticResponse || !validator.response) return '' let code = injectResponse + '\n' code += `if(${name} instanceof ElysiaCustomStatusResponse){` + `c.set.status=${name}.code\n` + `${name}=${name}.response` + `}` + `if(${name} instanceof Response === false && typeof ${name}?.next !== 'function' && !(${name} instanceof ReadableStream))` + `switch(c.set.status){` for (const [status, value] of Object.entries(validator.response!)) { code += `\ncase ${status}:\n` if (value.provider === 'standard') { code += `let vare${status}=validator.response[${status}].Check(${name})\n` + `if(vare${status} instanceof Promise)vare${status}=await vare${status}\n` + `if(vare${status}.issues)` + `throw new ValidationError('response',validator.response[${status}],${name},${allowUnsafeValidationDetails},vare${status}.issues)\n` + `${name}=vare${status}.value\n` + `c.set.status=${status}\n` + 'break\n' continue } let noValidate = value.schema?.noValidate === true if (!noValidate && value.schema?.$ref && value.schema?.$defs) { const refKey = value.schema.$ref const defKey = typeof refKey === 'string' && refKey.includes('/') ? refKey.split('/').pop()! : refKey const referencedDef = value.schema.$defs[ defKey as keyof typeof value.schema.$defs ] if (referencedDef?.noValidate === true) { noValidate = true } } const appliedCleaner = noValidate || hasSanitize const clean = ({ ignoreTryCatch = false } = {}) => composeCleaner({ name, schema: value, type: 'response', typeAlias: `response[${status}]`, normalize, ignoreTryCatch }) if (appliedCleaner) code += clean() const applyErrorCleaner = !appliedCleaner && normalize && !noValidate // Encode call TypeCheck.Check internally if (encodeSchema && value.hasTransform && !noValidate) { code += `try{` + `${name}=validator.response[${status}].Encode(${name})\n` if (!appliedCleaner) code += clean({ ignoreTryCatch: true }) code += `c.set.status=${status}` + `}catch{` + (applyErrorCleaner ? `try{\n` + clean({ ignoreTryCatch: true }) + `${name}=validator.response[${status}].Encode(${name})\n` + `}catch{` + `throw new ValidationError('response',validator.response[${status}],${name},${allowUnsafeValidationDetails})` + `}` : `throw new ValidationError('response',validator.response[${status}],${name},${allowUnsafeValidationDetails})`) + `}` } else { if (!appliedCleaner) code += clean() if (!noValidate) code += `if(validator.response[${status}].Check(${name})===false)` + `throw new ValidationError('response',validator.response[${status}],${name},${allowUnsafeValidationDetails})\n` + `c.set.status=${status}\n` } code += 'break\n' } return code + '}' } }) const isAsyncName = (v: Function | HookContainer) => { // @ts-ignore const fn = v?.fn ?? v return fn.constructor.name === 'AsyncFunction' } const matchResponseClone = /=>\s?response\.clone\(/ const matchFnReturn = /(?:return|=>)\s?\S+\(|a(?:sync|wait)/ export const isAsync = (v: Function | HookContainer) => { const isObject = typeof v === 'object' if (isObject && v.isAsync !== undefined) return v.isAsync const fn = isObject ? v.fn : v // Check for both AsyncFunction and AsyncGeneratorFunction // AsyncGeneratorFunction needs to be treated as async because generator.next() // returns a Promise that may reject (e.g., when throwing from the generator) if ( fn.constructor.name === 'AsyncFunction' || fn.constructor.name === 'AsyncGeneratorFunction' ) return true const literal: string = fn.toString() if (matchResponseClone.test(literal)) { if (isObject) v.isAsync = false return false } const result = matchFnReturn.test(literal) if (isObject) v.isAsync = result return result } const hasReturn = (v: string | HookContainer | Function) => { const isObject = typeof v === 'object' if (isObject && v.hasReturn !== undefined) return v.hasReturn const fnLiteral = isObject ? v.fn.toString() : typeof v === 'string' ? v.toString() : v.toString() const parenthesisEnd = fnLiteral.indexOf(')') // Check for arrow function expression (direct return without braces) // Handle both `) => x` and `)=>x` formats (with or without spaces) const arrowIndex = fnLiteral.indexOf('=>', parenthesisEnd) if (arrowIndex !== -1) { // Skip any whitespace after `=>` (space, tab, newline, carriage return) let afterArrow = arrowIndex + 2 let charCode: number while ( afterArrow < fnLiteral.length && ((charCode = fnLiteral.charCodeAt(afterArrow)) === 32 || // space charCode === 9 || // tab charCode === 10 || // newline charCode === 13) // carriage return ) { afterArrow++ } // If the first non-whitespace char after `=>` is not `{`, it's a direct return if ( afterArrow < fnLiteral.length && fnLiteral.charCodeAt(afterArrow) !== 123 ) { if (isObject) v.hasReturn = true return true } } const result = fnLiteral.includes('return') if (isObject) v.hasReturn = result return result } const isGenerator = (v: Function | HookContainer) => { // @ts-ignore const fn = v?.fn ?? v return ( fn.constructor.name === 'AsyncGeneratorFunction' || fn.constructor.name === 'GeneratorFunction' ) } const coerceTransformDecodeError = ( fnLiteral: string, type: string, allowUnsafeValidationDetails = false, value = `c.${type}` ) => `try{${fnLiteral}}catch(error){` + `if(error.constructor.name === 'TransformDecodeError'){` + `c.set.status=422\n` + `throw error.error ?? new ValidationError('${type}',validator.${type},${value},${allowUnsafeValidationDetails})}` + `}` const setImmediateFn = hasSetImmediate ? 'setImmediate' : 'Promise.resolve().then' export const composeHandler = ({ app, path, method, hooks, validator, handler, allowMeta = false, inference }: { app: AnyElysia path: string method: string hooks: Partial validator: SchemaValidator handler: unknown | Handler allowMeta?: boolean inference: Sucrose.Inference }): ComposedHandler => { const adapter = app['~adapter'].composeHandler const adapterHandler = app['~adapter'].handler const isHandleFn = typeof handler === 'function' if (!isHandleFn) { handler = adapterHandler.mapResponse(handler, { // @ts-expect-error private property headers: app.setHeaders ?? {} }) const isResponse = handler instanceof Response || // @ts-ignore If it's not instanceof Response, it might be a polyfill (only on Node) (handler?.constructor?.name === 'Response' && typeof (handler as Response)?.clone === 'function') if ( hooks.parse?.length && hooks.transform?.length && hooks.beforeHandle?.length && hooks.afterHandle?.length ) { if (isResponse) return Function( 'a', '"use strict";\n' + `return function(){return a.clone()}` )(handler) return Function( 'a', '"use strict";\n' + 'return function(){return a}' )(handler) } if (isResponse) { const response = handler as Response handler = () => response.clone() } } const handle = isHandleFn ? `handler(c)` : `handler` const hasTrace = !!hooks.trace?.length let fnLiteral = '' inference = sucrose( Object.assign({ handler: handler as any }, hooks), inference, app.config.sucrose ) if (adapter.declare) { const literal = adapter.declare(inference) if (literal) fnLiteral += literal } if (inference.server) fnLiteral += "Object.defineProperty(c,'server',{" + 'get:function(){return getServer()}' + '})\n' validator.createBody?.() validator.createQuery?.() validator.createHeaders?.() validator.createParams?.() validator.createCookie?.() validator.createResponse?.() const hasValidation = !!validator.body || !!validator.headers || !!validator.params || !!validator.query || !!validator.cookie || !!validator.response const hasQuery = inference.query || !!validator.query const requestNoBody = hooks.parse?.length === 1 && // @ts-expect-error hooks.parse[0].fn === 'none' const hasBody = method !== '' && method !== 'GET' && method !== 'HEAD' && (inference.body || !!validator.body || !!hooks.parse?.length) && !requestNoBody // @ts-expect-error private const defaultHeaders = app.setHeaders const hasDefaultHeaders = defaultHeaders && !!Object.keys(defaultHeaders).length // ? defaultHeaders doesn't imply that user will use headers in handler const hasHeaders = inference.headers || !!validator.headers || (adapter.preferWebstandardHeaders !== true && inference.body) const hasCookie = inference.cookie || !!validator.cookie const cookieMeta: { secrets?: string | string[] sign: string[] | true properties: { [x: string]: Object } } = validator.cookie?.config ? mergeCookie(validator?.cookie?.config, app.config.cookie as any) : app.config.cookie let _encodeCookie = '' const encodeCookie = () => { if (_encodeCookie) return _encodeCookie if (cookieMeta?.sign) { if (cookieMeta.secrets === '') throw new Error( `cookie secret can't be an empty string at (${method}) ${path}`, { cause: `(${method}) ${path}` } ) if (!cookieMeta.secrets) throw new Error( `cookie secret must be defined (${method}) ${path}`, { cause: `(${method}) ${path}` } ) const secret = !cookieMeta.secrets ? undefined : typeof cookieMeta.secrets === 'string' ? cookieMeta.secrets : cookieMeta.secrets[0] _encodeCookie += 'const _setCookie = c.set.cookie\n' + 'if(_setCookie){' if (cookieMeta.sign === true) _encodeCookie += 'for(const [key, cookie] of Object.entries(_setCookie)){' + `c.set.cookie[key].value=await signCookie(cookie.value,${!secret ? 'undefined' : JSON.stringify(secret)})` + '}' else { if (typeof cookieMeta.sign === 'string') cookieMeta.sign = [cookieMeta.sign] for (const name of cookieMeta.sign) _encodeCookie += `if(_setCookie[${JSON.stringify(name)}]?.value)` + `c.set.cookie[${JSON.stringify(name)}].value=await signCookie(_setCookie[${JSON.stringify(name)}].value,${!secret ? 'undefined' : JSON.stringify(secret)})\n` } _encodeCookie += '}\n' } return _encodeCookie } const normalize = app.config.normalize const encodeSchema = app.config.encodeSchema const allowUnsafeValidationDetails = app.config.allowUnsafeValidationDetails const validation = composeValidationFactory({ normalize, validator, encodeSchema, isStaticResponse: handler instanceof Response, hasSanitize: !!app.config.sanitize, allowUnsafeValidationDetails }) if (hasHeaders) fnLiteral += adapter.headers if (hasTrace) fnLiteral += 'const id=c[ELYSIA_REQUEST_ID]\n' const report = createReport({ trace: hooks.trace, addFn: (word) => { fnLiteral += word } }) fnLiteral += 'try{' if (hasCookie) { const get = (name: keyof CookieOptions, defaultValue?: unknown) => { // @ts-ignore const value = cookieMeta?.[name] ?? defaultValue if (value === undefined) return '' if (!value) return typeof defaultValue === 'string' ? `${name}:"${defaultValue}",` : `${name}:${defaultValue},` if (typeof value === 'string') return `${name}:${JSON.stringify(value)},` if (value instanceof Date) return `${name}: new Date(${value.getTime()}),` return `${name}:${value},` } const options = cookieMeta ? `{secrets:${ cookieMeta.secrets !== undefined && cookieMeta.secrets !== null ? typeof cookieMeta.secrets === 'string' ? JSON.stringify(cookieMeta.secrets) : '[' + cookieMeta.secrets .map((x) => JSON.stringify(x)) .join(',') + ']' : 'undefined' },` + `sign:${ cookieMeta.sign === true ? true : cookieMeta.sign !== undefined ? typeof cookieMeta.sign === 'string' ? JSON.stringify(cookieMeta.sign) : '[' + cookieMeta.sign .map((x) => JSON.stringify(x)) .join(',') + ']' : 'undefined' },` + get('domain') + get('expires') + get('httpOnly') + get('maxAge') + get('path', '/') + get('priority') + get('sameSite') + get('secure') + '}' : 'undefined' if (hasHeaders) fnLiteral += `\nc.cookie=await parseCookie(c.set,c.headers.cookie,${options})\n` else fnLiteral += `\nc.cookie=await parseCookie(c.set,c.request.headers.get('cookie'),${options})\n` } if (hasQuery) { let arrayProperties: Record = {} let objectProperties: Record = {} let hasArrayProperty = false let hasObjectProperty = false if (validator.query?.schema) { const schema = unwrapImportSchema(validator.query?.schema) const properties = getSchemaProperties(schema) if (properties) { for (const [key, value] of Object.entries(properties)) { if (hasElysiaMeta('ArrayQuery', value as TSchema)) { arrayProperties[key] = true hasArrayProperty = true } if (hasElysiaMeta('ObjectString', value as TSchema)) { objectProperties[key] = true hasObjectProperty = true } } } } fnLiteral += 'if(c.qi===-1){' + 'c.query=Object.create(null)' + '}else{' + `c.query=parseQueryFromURL(c.url,c.qi+1${ // hasArrayProperty ? ',' + JSON.stringify(arrayProperties) : hasObjectProperty ? ',undefined' : '' }${ // hasObjectProperty ? ',' + JSON.stringify(objectProperties) : '' })` + '}' } const isAsyncHandler = typeof handler === 'function' && isAsync(handler) const saveResponse = hasTrace || hooks.afterResponse?.length ? 'c.response=c.responseValue= ' : '' const responseKeys = Object.keys(validator.response ?? {}) const hasMultipleResponses = responseKeys.length > 1 const hasSingle200 = responseKeys.length === 0 || (responseKeys.length === 1 && responseKeys[0] === '200') const maybeAsync = hasCookie || hasBody || isAsyncHandler || !!hooks.parse?.length || !!hooks.afterHandle?.some(isAsync) || !!hooks.beforeHandle?.some(isAsync) || !!hooks.transform?.some(isAsync) || !!hooks.mapResponse?.some(isAsync) || validator.body?.provider === 'standard' || validator.headers?.provider === 'standard' || validator.query?.provider === 'standard' || validator.params?.provider === 'standard' || validator.cookie?.provider === 'standard' || Object.values(validator.response ?? {}).find( (x) => x.provider === 'standard' ) const maybeStream = (typeof handler === 'function' ? isGenerator(handler as any) : false) || !!hooks.beforeHandle?.some(isGenerator) || !!hooks.afterHandle?.some(isGenerator) || !!hooks.transform?.some(isGenerator) const hasSet = inference.cookie || inference.set || hasHeaders || hasTrace || hasMultipleResponses || !hasSingle200 || (isHandleFn && hasDefaultHeaders) || maybeStream let _afterResponse: string | undefined const afterResponse = (hasStream = true) => { if (_afterResponse !== undefined) return _afterResponse if (!hooks.afterResponse?.length && !hasTrace) return '' let afterResponse = '' afterResponse += `\n${setImmediateFn}(async()=>{` + `if(c.responseValue){` + `if(c.responseValue instanceof ElysiaCustomStatusResponse) c.set.status=c.responseValue.code\n` + (hasStream ? `if(typeof afterHandlerStreamListener!=='undefined')for await(const v of afterHandlerStreamListener){}\n` : '') + `}\n` const reporter = createReport({ trace: hooks.trace, addFn: (word) => { afterResponse += word } })('afterResponse', { total: hooks.afterResponse?.length }) if (hooks.afterResponse?.length && hooks.afterResponse) { for (let i = 0; i < hooks.afterResponse.length; i++) { const endUnit = reporter.resolveChild( hooks.afterResponse[i].fn.name ) const prefix = isAsync(hooks.afterResponse[i]) ? 'await ' : '' afterResponse += `\n${prefix}e.afterResponse[${i}](c)\n` endUnit() } } reporter.resolve() afterResponse += '})\n' return (_afterResponse = afterResponse) } const mapResponse = (r = 'r') => { const after = afterResponse() // When maybeStream is true, mapResponse may return a Promise (from handleStream) // that can reject if the generator throws. We need to await it so the try-catch // can properly catch the rejection and route it to error handling. // Only add await if the function is async (maybeAsync), otherwise it would be a syntax error. const awaitStream = maybeStream && maybeAsync ? 'await ' : '' const response = `${awaitStream}${hasSet ? 'mapResponse' : 'mapCompactResponse'}(${saveResponse}${r}${hasSet ? ',c.set' : ''}${mapResponseContext})\n` if (!after) return `return ${response}` return `const _res=${response}` + after + `return _res` } const mapResponseContext = adapter.mapResponseContext ? `,${adapter.mapResponseContext}` : '' if (hasTrace || inference.route) fnLiteral += `c.route=\`${path}\`\n` if (hasTrace || hooks.afterResponse?.length) fnLiteral += 'let afterHandlerStreamListener\n' const parseReporter = report('parse', { total: hooks.parse?.length }) if (hasBody) { const hasBodyInference = !!hooks.parse?.length || inference.body || validator.body if (adapter.parser.declare) fnLiteral += adapter.parser.declare fnLiteral += '\ntry{' let parser: string | undefined = typeof hooks.parse === 'string' ? hooks.parse : Array.isArray(hooks.parse) && hooks.parse.length === 1 ? typeof hooks.parse[0] === 'string' ? hooks.parse[0] : typeof hooks.parse[0].fn === 'string' ? hooks.parse[0].fn : undefined : undefined if (!parser && validator.body && !hooks.parse?.length) { const schema = validator.body.schema if ( schema && schema.anyOf && schema[Kind] === 'Union' && schema.anyOf?.length === 2 && schema.anyOf?.find((x: TAnySchema) => x[Kind] === 'ElysiaForm') ) parser = 'formdata' } if (parser && defaultParsers.includes(parser)) { const reporter = report('parse', { total: hooks.parse?.length }) const isOptionalBody = !!validator.body?.isOptional switch (parser) { case 'json': case 'application/json': fnLiteral += adapter.parser.json(isOptionalBody) break case 'text': case 'text/plain': fnLiteral += adapter.parser.text(isOptionalBody) break case 'urlencoded': case 'application/x-www-form-urlencoded': fnLiteral += adapter.parser.urlencoded(isOptionalBody) break case 'arrayBuffer': case 'application/octet-stream': fnLiteral += adapter.parser.arrayBuffer(isOptionalBody) break case 'formdata': case 'multipart/form-data': fnLiteral += adapter.parser.formData(isOptionalBody) break default: if (parser in app['~parser']) { fnLiteral += hasHeaders ? `let contentType = c.headers['content-type']` : `let contentType = c.request.headers.get('content-type')` fnLiteral += `\nif(contentType){` + `const index=contentType.indexOf(';')\n` + `if(index!==-1)contentType=contentType.substring(0,index)}\n` + `else{contentType=''}` + `c.contentType=contentType\n` + `let result=parser['${parser}'](c, contentType)\n` + `if(result instanceof Promise)result=await result\n` + `if(result instanceof ElysiaCustomStatusResponse)throw result\n` + `if(result!==undefined)c.body=result\n` + 'delete c.contentType\n' } break } reporter.resolve() } else if (hasBodyInference) { fnLiteral += '\n' fnLiteral += 'let contentType\n' + 'if(c.request.body)' fnLiteral += hasHeaders ? `contentType=c.headers['content-type']\n` : `contentType=c.request.headers.get('content-type')\n` let hasDefaultParser = false if (hooks.parse?.length) fnLiteral += `if(contentType){\n` + `const index=contentType.indexOf(';')\n` + `\nif(index!==-1)contentType=contentType.substring(0,index)` + `}else{contentType=''}` + `let used=false\n` + `c.contentType=contentType\n` else { hasDefaultParser = true const isOptionalBody = !!validator.body?.isOptional fnLiteral += `if(contentType)` + `switch(contentType.charCodeAt(12)){` + `\ncase 106:` + adapter.parser.json(isOptionalBody) + 'break' + `\n` + `case 120:` + adapter.parser.urlencoded(isOptionalBody) + `break` + `\n` + `case 111:` + adapter.parser.arrayBuffer(isOptionalBody) + `break` + `\n` + `case 114:` + adapter.parser.formData(isOptionalBody) + `break` + `\n` + `default:` + `if(contentType.charCodeAt(0)===116){` + adapter.parser.text(isOptionalBody) + `}` + `break\n` + `}` } const reporter = report('parse', { total: hooks.parse?.length }) if (hooks.parse) for (let i = 0; i < hooks.parse.length; i++) { const name = `bo${i}` if (i !== 0) fnLiteral += `\nif(!used){` if (typeof hooks.parse[i].fn === 'string') { const endUnit = reporter.resolveChild( hooks.parse[i].fn as unknown as string ) const isOptionalBody = !!validator.body?.isOptional switch (hooks.parse[i].fn as unknown as string) { case 'json': case 'application/json': hasDefaultParser = true fnLiteral += adapter.parser.json(isOptionalBody) break case 'text': case 'text/plain': hasDefaultParser = true fnLiteral += adapter.parser.text(isOptionalBody) break case 'urlencoded': case 'application/x-www-form-urlencoded': hasDefaultParser = true fnLiteral += adapter.parser.urlencoded(isOptionalBody) break case 'arrayBuffer': case 'application/octet-stream': hasDefaultParser = true fnLiteral += adapter.parser.arrayBuffer(isOptionalBody) break case 'formdata': case 'multipart/form-data': hasDefaultParser = true fnLiteral += adapter.parser.formData(isOptionalBody) break default: fnLiteral += `let ${name}=parser['${hooks.parse[i].fn}'](c,contentType)\n` + `if(${name} instanceof Promise)${name}=await ${name}\n` + `if(${name}!==undefined){c.body=${name};used=true;}\n` } endUnit() } else { const endUnit = reporter.resolveChild( hooks.parse[i].fn.name ) fnLiteral += `let ${name}=e.parse[${i}]\n` + `${name}=${name}(c,contentType)\n` + `if(${name} instanceof Promise)${name}=await ${name}\n` + `if(${name}!==undefined){c.body=${name};used=true}` endUnit() } if (i !== 0) fnLiteral += `}` if (hasDefaultParser) break } reporter.resolve() if (!hasDefaultParser) { const isOptionalBody = !!validator.body?.isOptional if (hooks.parse?.length) fnLiteral += `\nif(!used){\n` fnLiteral += `switch(contentType){` + `case 'application/json':\n` + adapter.parser.json(isOptionalBody) + `break\n` + `case 'text/plain':` + adapter.parser.text(isOptionalBody) + `break` + '\n' + `case 'application/x-www-form-urlencoded':` + adapter.parser.urlencoded(isOptionalBody) + `break` + '\n' + `case 'application/octet-stream':` + adapter.parser.arrayBuffer(isOptionalBody) + `break` + '\n' + `case 'multipart/form-data':` + adapter.parser.formData(isOptionalBody) + `break` + '\n' for (const key of Object.keys(app['~parser'])) fnLiteral += `case '${key}':` + `let bo${key}=parser['${key}'](c,contentType)\n` + `if(bo${key} instanceof Promise)bo${key}=await bo${key}\n` + `if(bo${key} instanceof ElysiaCustomStatusResponse){` + mapResponse(`bo${key}`) + `}` + `if(bo${key}!==undefined)c.body=bo${key}\n` + `break` + '\n' if (hooks.parse?.length) fnLiteral += '}' fnLiteral += '}' } if (hooks.parse?.length) fnLiteral += '\ndelete c.contentType' } fnLiteral += '}catch(error){throw new ParseError(error)}' } parseReporter.resolve() if (hooks?.transform || hasTrace) { const reporter = report('transform', { total: hooks.transform?.length }) if (hooks.transform?.length) { fnLiteral += 'let transformed\n' for (let i = 0; i < hooks.transform.length; i++) { const transform = hooks.transform[i] const endUnit = reporter.resolveChild(transform.fn.name) fnLiteral += isAsync(transform) ? `transformed=await e.transform[${i}](c)\n` : `transformed=e.transform[${i}](c)\n` if (transform.subType === 'mapDerive') fnLiteral += `if(transformed instanceof ElysiaCustomStatusResponse){` + mapResponse('transformed') + `}else{` + `transformed.request=c.request\n` + `transformed.store=c.store\n` + `transformed.qi=c.qi\n` + `transformed.path=c.path\n` + `transformed.url=c.url\n` + `transformed.redirect=c.redirect\n` + `transformed.set=c.set\n` + `transformed.error=c.error\n` + `c=transformed` + '}' else fnLiteral += `if(transformed instanceof ElysiaCustomStatusResponse){` + mapResponse('transformed') + `}else Object.assign(c,transformed)\n` endUnit() } } reporter.resolve() } const fileUnions = []>[] if (validator) { if (validator.headers) { if (validator.headers.hasDefault) for (const [key, value] of Object.entries( Value.Default( // @ts-ignore validator.headers.schema, {} ) as Object )) { const parsed = typeof value === 'object' ? JSON.stringify(value) : typeof value === 'string' ? `'${value}'` : value if (parsed !== undefined) fnLiteral += `c.headers['${key}']??=${parsed}\n` } fnLiteral += composeCleaner({ name: 'c.headers', schema: validator.headers, type: 'headers', normalize }) if (validator.headers.isOptional) fnLiteral += `if(isNotEmpty(c.headers)){` if (validator.headers?.provider === 'standard') { fnLiteral += `let vah=validator.headers.Check(c.headers)\n` + `if(vah instanceof Promise)vah=await vah\n` + `if(vah.issues){` + validation.validate('headers', undefined, 'vah.issues') + '}else{c.headers=vah.value}\n' } else if (validator.headers?.schema?.noValidate !== true) fnLiteral += `if(validator.headers.Check(c.headers) === false){` + validation.validate('headers') + '}' if (validator.headers.hasTransform) fnLiteral += coerceTransformDecodeError( `c.headers=validator.headers.Decode(c.headers)\n`, 'headers', allowUnsafeValidationDetails ) if (validator.headers.isOptional) fnLiteral += '}' } if (validator.params) { if (validator.params.hasDefault) for (const [key, value] of Object.entries( Value.Default( // @ts-ignore validator.params.schema, {} ) as Object )) { const parsed = typeof value === 'object' ? JSON.stringify(value) : typeof value === 'string' ? `'${value}'` : value if (parsed !== undefined) fnLiteral += `c.params['${key}']??=${parsed}\n` } if (validator.params.provider === 'standard') { fnLiteral += `let vap=validator.params.Check(c.params)\n` + `if(vap instanceof Promise)vap=await vap\n` + `if(vap.issues){` + validation.validate('params', undefined, 'vap.issues') + '}else{c.params=vap.value}\n' } else if (validator.params?.schema?.noValidate !== true) fnLiteral += `if(validator.params.Check(c.params)===false){` + validation.validate('params') + '}' if (validator.params.hasTransform) fnLiteral += coerceTransformDecodeError( `c.params=validator.params.Decode(c.params)\n`, 'params', allowUnsafeValidationDetails ) } if (validator.query) { if (Kind in validator.query?.schema && validator.query.hasDefault) for (const [key, value] of Object.entries( Value.Default( // @ts-ignore validator.query.schema, {} ) as Object )) { const parsed = typeof value === 'object' ? JSON.stringify(value) : typeof value === 'string' ? `'${value}'` : value if (parsed !== undefined) fnLiteral += `if(c.query['${key}']===undefined)c.query['${key}']=${parsed}\n` } fnLiteral += composeCleaner({ name: 'c.query', schema: validator.query, type: 'query', normalize }) if (validator.query.isOptional) fnLiteral += `if(isNotEmpty(c.query)){` if (validator.query.provider === 'standard') { fnLiteral += `let vaq=validator.query.Check(c.query)\n` + `if(vaq instanceof Promise)vaq=await vaq\n` + `if(vaq.issues){` + validation.validate('query', undefined, 'vaq.issues') + '}else{c.query=vaq.value}\n' } else if (validator.query?.schema?.noValidate !== true) fnLiteral += `if(validator.query.Check(c.query)===false){` + validation.validate('query') + `}` if (validator.query.hasTransform) { // TypeBox Decode only work with single Decode at the time // If we have multiple Decode, it will handle only the first one // For query, we decode it twice to ensure that it works fnLiteral += coerceTransformDecodeError( `c.query=validator.query.Decode(c.query)\n`, 'query', allowUnsafeValidationDetails ) fnLiteral += coerceTransformDecodeError( `c.query=validator.query.Decode(c.query)\n`, 'query', allowUnsafeValidationDetails ) } if (validator.query.isOptional) fnLiteral += `}` } if (hasBody && validator.body) { if (validator.body.hasTransform || validator.body.isOptional) fnLiteral += `const isNotEmptyObject=c.body&&(typeof c.body==="object"&&(isNotEmpty(c.body)||c.body instanceof ArrayBuffer))\n` const hasUnion = isUnion(validator.body.schema) let hasNonUnionFileWithDefault = false if (validator.body.hasDefault) { let value = Value.Default( validator.body.schema, validator.body.schema.type === 'object' || unwrapImportSchema(validator.body.schema)[Kind] === 'Object' ? {} : undefined ) const schema = unwrapImportSchema(validator.body.schema) if ( !hasUnion && value && typeof value === 'object' && (hasType('File', schema) || hasType('Files', schema)) ) { hasNonUnionFileWithDefault = true for (const [k, v] of Object.entries(value)) if (v === 'File' || v === 'Files') // @ts-ignore delete value[k] if (!isNotEmpty(value)) value = undefined } const parsed = typeof value === 'object' ? JSON.stringify(value) : typeof value === 'string' ? `'${value}'` : value if (value !== undefined && value !== null) { if (Array.isArray(value)) fnLiteral += `if(!c.body)c.body=${parsed}\n` else if (typeof value === 'object') fnLiteral += `c.body=Object.assign(${parsed},c.body)\n` else fnLiteral += `c.body=${parsed}\n` } fnLiteral += composeCleaner({ name: 'c.body', schema: validator.body, type: 'body', normalize }) if (validator.body.provider === 'standard') { fnLiteral += `let vab=validator.body.Check(c.body)\n` + `if(vab instanceof Promise)vab=await vab\n` + `if(vab.issues){` + validation.validate('body', undefined, 'vab.issues') + '}else{c.body=vab.value}\n' } else if (validator.body?.schema?.noValidate !== true) { if (validator.body.isOptional) fnLiteral += `if(isNotEmptyObject&&validator.body.Check(c.body)===false){` + validation.validate('body') + '}' else fnLiteral += `if(validator.body.Check(c.body)===false){` + validation.validate('body') + `}` } } else { fnLiteral += composeCleaner({ name: 'c.body', schema: validator.body, type: 'body', normalize }) if (validator.body.provider === 'standard') { fnLiteral += `let vab=validator.body.Check(c.body)\n` + `if(vab instanceof Promise)vab=await vab\n` + `if(vab.issues){` + validation.validate('body', undefined, 'vab.issues') + '}else{c.body=vab.value}\n' } else if (validator.body?.schema?.noValidate !== true) { if (validator.body.isOptional) fnLiteral += `if(isNotEmptyObject&&validator.body.Check(c.body)===false){` + validation.validate('body') + '}' else fnLiteral += `if(validator.body.Check(c.body)===false){` + validation.validate('body') + '}' } } if (validator.body.hasTransform) fnLiteral += coerceTransformDecodeError( `if(isNotEmptyObject)c.body=validator.body.Decode(c.body)\n`, 'body', allowUnsafeValidationDetails ) if (hasUnion && validator.body.schema.anyOf?.length) { const iterator = Object.values( validator.body.schema.anyOf ) as TAnySchema[] for (let i = 0; i < iterator.length; i++) { const type = iterator[i] if (hasType('File', type) || hasType('Files', type)) { const candidate = getSchemaValidator(type, { // @ts-expect-error private property modules: app.definitions.typebox, dynamic: !app.config.aot, // @ts-expect-error private property models: app.definitions.type, normalize: app.config.normalize, additionalCoerce: coercePrimitiveRoot(), sanitize: () => app.config.sanitize }) if (candidate) { const isFirst = fileUnions.length === 0 // Handle case where schema is wrapped in a Union/Intersect (e.g., ObjectString coercion) const properties = getSchemaProperties(candidate.schema) ?? getSchemaProperties(type) if (!properties) continue const iterator = Object.entries(properties) as [ string, TSchema ][] let validator = isFirst ? '\n' : ' else ' validator += `if(fileUnions[${fileUnions.length}].Check(c.body)){` let validateFile = '' let validatorLength = 0 for (let i = 0; i < iterator.length; i++) { const [k, v] = iterator[i] if ( !v.extension || (v[Kind] !== 'File' && v[Kind] !== 'Files') ) continue if (validatorLength) validateFile += ',' validateFile += `fileType(c.body.${k},${JSON.stringify(v.extension)},'body.${k}')` validatorLength++ } if (validateFile) { if (validatorLength === 1) validator += `await ${validateFile}\n` else if (validatorLength > 1) validator += `await Promise.all([${validateFile}])\n` validator += '}' fnLiteral += validator fileUnions.push(candidate) } } } } } else if ( hasNonUnionFileWithDefault || (!hasUnion && (hasType( 'File', unwrapImportSchema(validator.body.schema) ) || hasType( 'Files', unwrapImportSchema(validator.body.schema) ))) ) { let validateFile = '' const bodyProperties = getSchemaProperties( unwrapImportSchema(validator.body.schema) ) let i = 0 if (bodyProperties) { for (const [k, v] of Object.entries(bodyProperties) as [ string, TSchema ][]) { if ( !v.extension || (v[Kind] !== 'File' && v[Kind] !== 'Files') ) continue if (i) validateFile += ',' validateFile += `fileType(c.body.${k},${JSON.stringify(v.extension)},'body.${k}')` i++ } } if (i) fnLiteral += '\n' if (i === 1) fnLiteral += `await ${validateFile}\n` else if (i > 1) fnLiteral += `await Promise.all([${validateFile}])\n` } } if (validator.cookie) { // ! Get latest app.config.cookie validator.cookie.config = mergeCookie( validator.cookie.config, app.config.cookie ?? {} ) fnLiteral += `let cookieValue={}\n` + `for(const [key,value] of Object.entries(c.cookie))` + `cookieValue[key]=value.value\n` if (validator.cookie.isOptional) fnLiteral += `if(isNotEmpty(c.cookie)){` if (validator.cookie.provider === 'standard') { fnLiteral += `let vac=validator.cookie.Check(cookieValue)\n` + `if(vac instanceof Promise)vac=await vac\n` + `if(vac.issues){` + validation.validate('cookie', undefined, 'vac.issues') + '}else{cookieValue=vac.value}\n' fnLiteral += `for(const k of Object.keys(cookieValue))` + `c.cookie[k].value=cookieValue[k]\n` } else if (validator.cookie?.schema?.noValidate !== true) { fnLiteral += `if(validator.cookie.Check(cookieValue)===false){` + validation.validate('cookie', 'cookieValue') + '}' if (validator.cookie.hasTransform) fnLiteral += coerceTransformDecodeError( `for(const [key,value] of Object.entries(validator.cookie.Decode(cookieValue))){` + `c.cookie[key].value = value` + `}`, 'cookie', allowUnsafeValidationDetails ) } if (validator.cookie.isOptional) fnLiteral += `}` } } if (hooks?.beforeHandle || hasTrace) { const reporter = report('beforeHandle', { total: hooks.beforeHandle?.length }) let hasResolve = false if (hooks.beforeHandle?.length) { for (let i = 0; i < hooks.beforeHandle.length; i++) { const beforeHandle = hooks.beforeHandle[i] const endUnit = reporter.resolveChild(beforeHandle.fn.name) const returning = hasReturn(beforeHandle) const isResolver = beforeHandle.subType === 'resolve' || beforeHandle.subType === 'mapResolve' if (isResolver) { if (!hasResolve) { hasResolve = true fnLiteral += '\nlet resolved\n' } fnLiteral += isAsync(beforeHandle) ? `resolved=await e.beforeHandle[${i}](c);\n` : `resolved=e.beforeHandle[${i}](c);\n` if (beforeHandle.subType === 'mapResolve') fnLiteral += `if(resolved instanceof ElysiaCustomStatusResponse){` + mapResponse('resolved') + `}else{` + `resolved.request=c.request\n` + `resolved.store=c.store\n` + `resolved.qi=c.qi\n` + `resolved.path=c.path\n` + `resolved.url=c.url\n` + `resolved.redirect=c.redirect\n` + `resolved.set=c.set\n` + `resolved.error=c.error\n` + `c=resolved` + `}` else fnLiteral += `if(resolved instanceof ElysiaCustomStatusResponse){` + mapResponse('resolved') + `}` + `else Object.assign(c, resolved)\n` endUnit() } else if (!returning) { fnLiteral += isAsync(beforeHandle) ? `await e.beforeHandle[${i}](c)\n` : `e.beforeHandle[${i}](c)\n` endUnit() } else { fnLiteral += isAsync(beforeHandle) ? `be=await e.beforeHandle[${i}](c)\n` : `be=e.beforeHandle[${i}](c)\n` endUnit('be') fnLiteral += `if(be!==undefined){` reporter.resolve() if (hooks.afterHandle?.length || hasTrace) { report('handle', { name: isHandleFn ? (handler as Function).name : undefined }).resolve() const reporter = report('afterHandle', { total: hooks.afterHandle?.length }) if (hooks.afterHandle?.length) { for (let i = 0; i < hooks.afterHandle.length; i++) { const hook = hooks.afterHandle[i] const returning = hasReturn(hook) const endUnit = reporter.resolveChild( hook.fn.name ) fnLiteral += `c.response=c.responseValue=be\n` if (!returning) { fnLiteral += isAsync(hook.fn) ? `await e.afterHandle[${i}](c, be)\n` : `e.afterHandle[${i}](c, be)\n` } else { fnLiteral += isAsync(hook.fn) ? `af=await e.afterHandle[${i}](c)\n` : `af=e.afterHandle[${i}](c)\n` fnLiteral += `if(af!==undefined) c.response=c.responseValue=be=af\n` } endUnit('af') } } reporter.resolve() } if (validator.response) fnLiteral += validation.response('be') const mapResponseReporter = report('mapResponse', { total: hooks.mapResponse?.length }) if (hooks.mapResponse?.length) { fnLiteral += `c.response=c.responseValue=be\n` for (let i = 0; i < hooks.mapResponse.length; i++) { const mapResponse = hooks.mapResponse[i] const endUnit = mapResponseReporter.resolveChild( mapResponse.fn.name ) fnLiteral += `if(mr===undefined){` + `mr=${isAsyncName(mapResponse) ? 'await ' : ''}e.mapResponse[${i}](c)\n` + `if(mr!==undefined)be=c.response=c.responseValue=mr` + '}' endUnit() } } mapResponseReporter.resolve() fnLiteral += afterResponse() fnLiteral += encodeCookie() fnLiteral += `return mapEarlyResponse(${saveResponse}be,c.set${ mapResponseContext })}\n` } } } reporter.resolve() } function reportHandler(name: string | undefined) { const handleReporter = report('handle', { name, alias: 'reportHandler' }) return () => { if (hasTrace) { fnLiteral += `if(r&&(r[Symbol.iterator]||r[Symbol.asyncIterator])&&typeof r.next==="function"){` + (maybeAsync ? '' : `(async()=>{`) + `const stream=await tee(r,3)\n` + `r=stream[0]\n` + (hooks.afterHandle?.length ? `c.response=c.responseValue=r\n` : '') + `const listener=stream[1]\n` + (hasTrace || hooks.afterResponse?.length ? `afterHandlerStreamListener=stream[2]\n` : '') + `${setImmediateFn}(async ()=>{` + `if(listener)for await(const v of listener){}\n` handleReporter.resolve() fnLiteral += `})` + (maybeAsync ? '' : `})()`) + `}else{` handleReporter.resolve() fnLiteral += '}\n' } } } if (hooks.afterHandle?.length || hasTrace) { const resolveHandler = reportHandler( isHandleFn ? (handler as Function).name : undefined ) if (hooks.afterHandle?.length) fnLiteral += isAsyncHandler ? `let r=c.response=c.responseValue=await ${handle}\n` : `let r=c.response=c.responseValue=${handle}\n` else fnLiteral += isAsyncHandler ? `let r=await ${handle}\n` : `let r=${handle}\n` resolveHandler() const reporter = report('afterHandle', { total: hooks.afterHandle?.length }) if (hooks.afterHandle?.length) { for (let i = 0; i < hooks.afterHandle.length; i++) { const hook = hooks.afterHandle[i] const returning = hasReturn(hook) const endUnit = reporter.resolveChild(hook.fn.name) if (!returning) { fnLiteral += isAsync(hook.fn) ? `await e.afterHandle[${i}](c)\n` : `e.afterHandle[${i}](c)\n` endUnit() } else { fnLiteral += isAsync(hook.fn) ? `af=await e.afterHandle[${i}](c)\n` : `af=e.afterHandle[${i}](c)\n` endUnit('af') if (validator.response) { fnLiteral += `if(af!==undefined){` reporter.resolve() fnLiteral += validation.response('af') fnLiteral += `c.response=c.responseValue=af}` } else { fnLiteral += `if(af!==undefined){` reporter.resolve() fnLiteral += `c.response=c.responseValue=af}` } } } } reporter.resolve() if (hooks.afterHandle?.length) fnLiteral += `r=c.response\n` if (validator.response) fnLiteral += validation.response() fnLiteral += encodeCookie() const mapResponseReporter = report('mapResponse', { total: hooks.mapResponse?.length }) if (hooks.mapResponse?.length) { for (let i = 0; i < hooks.mapResponse.length; i++) { const mapResponse = hooks.mapResponse[i] const endUnit = mapResponseReporter.resolveChild( mapResponse.fn.name ) fnLiteral += `mr=${ isAsyncName(mapResponse) ? 'await ' : '' }e.mapResponse[${i}](c)\n` + `if(mr!==undefined)r=c.response=c.responseValue=mr\n` endUnit() } } mapResponseReporter.resolve() fnLiteral += mapResponse() } else { const resolveHandler = reportHandler( isHandleFn ? (handler as Function).name : undefined ) if (validator.response || hooks.mapResponse?.length || hasTrace) { fnLiteral += isAsyncHandler ? `let r=await ${handle}\n` : `let r=${handle}\n` resolveHandler() if (validator.response) fnLiteral += validation.response() const mapResponseReporter = report('mapResponse', { total: hooks.mapResponse?.length }) if (hooks.mapResponse?.length) { fnLiteral += '\nc.response=c.responseValue=r\n' for (let i = 0; i < hooks.mapResponse.length; i++) { const mapResponse = hooks.mapResponse[i] const endUnit = mapResponseReporter.resolveChild( mapResponse.fn.name ) fnLiteral += `\nif(mr===undefined){` + `mr=${isAsyncName(mapResponse) ? 'await ' : ''}e.mapResponse[${i}](c)\n` + `if(mr!==undefined)r=c.response=c.responseValue=mr` + `}\n` endUnit() } } mapResponseReporter.resolve() fnLiteral += encodeCookie() if (handler instanceof Response) { fnLiteral += afterResponse() fnLiteral += inference.set ? `if(` + `isNotEmpty(c.set.headers)||` + `c.set.status!==200||` + `c.set.redirect||` + `c.set.cookie)return mapResponse(${saveResponse}${handle}.clone(),c.set${ mapResponseContext })\n` + `else return ${handle}.clone()` : `return ${handle}.clone()` fnLiteral += '\n' } else fnLiteral += mapResponse() } else if (hasCookie || hasTrace) { fnLiteral += isAsyncHandler ? `let r=await ${handle}\n` : `let r=${handle}\n` resolveHandler() const mapResponseReporter = report('mapResponse', { total: hooks.mapResponse?.length }) if (hooks.mapResponse?.length) { fnLiteral += 'c.response=c.responseValue= r\n' for (let i = 0; i < hooks.mapResponse.length; i++) { const mapResponse = hooks.mapResponse[i] const endUnit = mapResponseReporter.resolveChild( mapResponse.fn.name ) fnLiteral += `if(mr===undefined){` + `mr=${isAsyncName(mapResponse) ? 'await ' : ''}e.mapResponse[${i}](c)\n` + `if(mr!==undefined)r=c.response=c.responseValue=mr` + `}` endUnit() } } mapResponseReporter.resolve() fnLiteral += encodeCookie() + mapResponse() } else { resolveHandler() const handled = isAsyncHandler ? `await ${handle}` : handle if (handler instanceof Response) { fnLiteral += afterResponse() fnLiteral += inference.set ? `if(isNotEmpty(c.set.headers)||` + `c.set.status!==200||` + `c.set.redirect||` + `c.set.cookie)` + `return mapResponse(${saveResponse}${handle}.clone(),c.set${ mapResponseContext })\n` + `else return ${handle}.clone()\n` : `return ${handle}.clone()\n` } else fnLiteral += mapResponse(handled) } } fnLiteral += `\n}catch(error){` if (!maybeAsync && hooks.error?.length) fnLiteral += `return(async()=>{` fnLiteral += `const set=c.set\n` + `if(!set.status||set.status<300)set.status=error?.status||500\n` if (hasCookie) fnLiteral += encodeCookie() if (hasTrace && hooks.trace) for (let i = 0; i < hooks.trace.length; i++) // There's a case where the error is thrown before any trace is called fnLiteral += `report${i}?.resolve(error);reportChild${i}?.(error)\n` const errorReporter = report('error', { total: hooks.error?.length }) if (hooks.error?.length) { fnLiteral += `c.error=error\n` if (hasValidation) fnLiteral += `if(error instanceof TypeBoxError){` + 'c.code="VALIDATION"\n' + 'c.set.status=422' + '}else{' + `c.code=error.code??error[ERROR_CODE]??"UNKNOWN"}` else fnLiteral += `c.code=error.code??error[ERROR_CODE]??"UNKNOWN"\n` fnLiteral += `let er\n` // Mapped error Response if (hooks.mapResponse?.length) fnLiteral += 'let mep\n' for (let i = 0; i < hooks.error.length; i++) { const endUnit = errorReporter.resolveChild(hooks.error[i].fn.name) if (isAsync(hooks.error[i])) fnLiteral += `er=await e.error[${i}](c)\n` else fnLiteral += `er=e.error[${i}](c)\n` + `if(er instanceof Promise)er=await er\n` endUnit() if (hooks.mapResponse?.length) { const mapResponseReporter = report('mapResponse', { total: hooks.mapResponse?.length }) for (let i = 0; i < hooks.mapResponse.length; i++) { const mapResponse = hooks.mapResponse[i] const endUnit = mapResponseReporter.resolveChild( mapResponse.fn.name ) fnLiteral += `c.response=c.responseValue=er\n` + `mep=e.mapResponse[${i}](c)\n` + `if(mep instanceof Promise)mep=await mep\n` + `if(mep!==undefined)er=mep\n` endUnit() } mapResponseReporter.resolve() } fnLiteral += `er=mapEarlyResponse(er,set${mapResponseContext})\n` fnLiteral += `if(er){` if (hasTrace && hooks.trace) { for (let i = 0; i < hooks.trace.length; i++) fnLiteral += `report${i}.resolve()\n` errorReporter.resolve() } fnLiteral += afterResponse(false) fnLiteral += `return er}` } } errorReporter.resolve() fnLiteral += `return handleError(c,error,true)` if (!maybeAsync && hooks.error?.length) fnLiteral += '})()' fnLiteral += '}' const adapterVariables = adapter.inject ? Object.keys(adapter.inject).join(',') + ',' : '' let init = `const {` + `handler,` + `handleError,` + `hooks:e, ` + allocateIf(`validator,`, hasValidation) + `mapResponse,` + `mapCompactResponse,` + `mapEarlyResponse,` + `isNotEmpty,` + `utils:{` + allocateIf(`parseQuery,`, hasBody) + allocateIf(`parseQueryFromURL,`, hasQuery) + `},` + `error:{` + allocateIf(`ValidationError,`, hasValidation) + allocateIf(`ParseError`, hasBody) + `},` + `fileType,` + `schema,` + `definitions,` + `tee,` + `ERROR_CODE,` + allocateIf(`parseCookie,`, hasCookie) + allocateIf(`signCookie,`, hasCookie) + allocateIf(`decodeURIComponent,`, hasQuery) + `ElysiaCustomStatusResponse,` + allocateIf(`ELYSIA_TRACE,`, hasTrace) + allocateIf(`ELYSIA_REQUEST_ID,`, hasTrace) + allocateIf('parser,', hooks.parse?.length) + allocateIf(`getServer,`, inference.server) + allocateIf(`fileUnions,`, fileUnions.length) + adapterVariables + allocateIf('TypeBoxError', hasValidation) + `}=hooks\n` + `const trace=e.trace\n` + `return ${maybeAsync ? 'async ' : ''}function handle(c){` if (hooks.beforeHandle?.length) init += 'let be\n' if (hooks.afterHandle?.length) init += 'let af\n' if (hooks.mapResponse?.length) init += 'let mr\n' if (allowMeta) init += 'c.schema=schema\nc.defs=definitions\n' fnLiteral = init + fnLiteral + '}' init = '' try { return Function( 'hooks', '"use strict";\n' + fnLiteral )({ handler, hooks: lifeCycleToFn(hooks), validator: hasValidation ? validator : undefined, // @ts-expect-error handleError: app.handleError, mapResponse: adapterHandler.mapResponse, mapCompactResponse: adapterHandler.mapCompactResponse, mapEarlyResponse: adapterHandler.mapEarlyResponse, isNotEmpty, utils: { parseQuery: hasBody ? parseQuery : undefined, parseQueryFromURL: hasQuery ? validator.query?.provider === 'standard' ? parseQueryStandardSchema : parseQueryFromURL : undefined }, error: { ValidationError: hasValidation ? ValidationError : undefined, ParseError: hasBody ? ParseError : undefined }, fileType, schema: app.router.history, // @ts-expect-error definitions: app.definitions.type, tee, ERROR_CODE, parseCookie: hasCookie ? parseCookie : undefined, signCookie: hasCookie ? signCookie : undefined, Cookie: hasCookie ? Cookie : undefined, decodeURIComponent: hasQuery ? decode : undefined, ElysiaCustomStatusResponse, ELYSIA_TRACE: hasTrace ? ELYSIA_TRACE : undefined, ELYSIA_REQUEST_ID: hasTrace ? ELYSIA_REQUEST_ID : undefined, // @ts-expect-error private property getServer: inference.server ? () => app.getServer() : undefined, fileUnions: fileUnions.length ? fileUnions : undefined, TypeBoxError: hasValidation ? TypeBoxError : undefined, parser: app['~parser'], ...adapter.inject }) } catch (error) { const debugHooks = lifeCycleToFn(hooks) console.log('[Composer] failed to generate optimized handler') console.log('---') console.log({ handler: typeof handler === 'function' ? handler.toString() : handler, instruction: fnLiteral, hooks: { ...debugHooks, // @ts-ignore transform: debugHooks?.transform?.map?.((x) => x.toString()), // @ts-ignore resolve: debugHooks?.resolve?.map?.((x) => x.toString()), // @ts-ignore beforeHandle: debugHooks?.beforeHandle?.map?.((x) => x.toString() ), // @ts-ignore afterHandle: debugHooks?.afterHandle?.map?.((x) => x.toString() ), // @ts-ignore mapResponse: debugHooks?.mapResponse?.map?.((x) => x.toString() ), // @ts-ignore parse: debugHooks?.parse?.map?.((x) => x.toString()), // @ts-ignore error: debugHooks?.error?.map?.((x) => x.toString()), // @ts-ignore afterResponse: debugHooks?.afterResponse?.map?.((x) => x.toString() ), // @ts-ignore stop: debugHooks?.stop?.map?.((x) => x.toString()) }, validator, // @ts-expect-error definitions: app.definitions.type, error }) console.log('---') if (typeof process?.exit === 'function') process.exit(1) return () => new Response('Internal Server Error', { status: 500 }) } } export interface ComposerGeneralHandlerOptions { /** * optimization for standard internet hostname * this will assume hostname is always use a standard internet hostname * assuming hostname is at minimum of 11 length of string (http://a.bc) * * setting this to true will skip the first 11 character of the hostname * * @default true */ standardHostname?: boolean } export const createOnRequestHandler = ( app: AnyElysia, addFn?: (word: string) => void ) => { let fnLiteral = '' const report = createReport({ trace: app.event.trace, addFn: addFn ?? ((word) => { fnLiteral += word }) }) const reporter = report('request', { total: app.event.request?.length }) if (app.event.request?.length) { fnLiteral += `try{` for (let i = 0; i < app.event.request.length; i++) { const hook = app.event.request[i] const withReturn = hasReturn(hook) const maybeAsync = isAsync(hook) const endUnit = reporter.resolveChild(app.event.request[i].fn.name) if (withReturn) { fnLiteral += `re=mapEarlyResponse(` + `${maybeAsync ? 'await ' : ''}onRequest[${i}](c),` + `c.set)\n` endUnit('re') fnLiteral += `if(re!==undefined)return re\n` } else { fnLiteral += `${maybeAsync ? 'await ' : ''}onRequest[${i}](c)\n` endUnit() } } fnLiteral += `}catch(error){return app.handleError(c,error,false)}` } reporter.resolve() return fnLiteral } export const createHoc = (app: AnyElysia, fnName = 'map') => { // @ts-expect-error private property const hoc = app.extender.higherOrderFunctions if (!hoc.length) return 'return ' + fnName const adapter = app['~adapter'].composeGeneralHandler let handler = fnName for (let i = 0; i < hoc.length; i++) handler = `hoc[${i}](${handler},${adapter.parameters})` return `return function hocMap(${adapter.parameters}){return ${handler}(${adapter.parameters})}` } export const composeGeneralHandler = ( app: AnyElysia ): ((request: Request) => MaybePromise) => { const adapter = app['~adapter'].composeGeneralHandler app.router.http.build() const isWebstandard = app['~adapter'].isWebStandard const hasTrace = app.event.trace?.length let fnLiteral = '' const router = app.router let findDynamicRoute = router.http.root.WS ? `const route=router.find(r.method==='GET'&&r.headers.get('upgrade')==='websocket'?'WS':r.method,p)` : `const route=router.find(r.method,p)` findDynamicRoute += router.http.root.ALL ? `??router.find('ALL',p)\n` : '\n' if (isWebstandard) findDynamicRoute += `if(r.method==="HEAD"){` + `const route=router.find("GET",p);` + `if(route){` + `c.params=route.params;` + `const _res=route.store.handler?route.store.handler(c):route.store.compile()(c);` + `if(_res)` + `return Promise.resolve(_res).then((_res)=>{` + `if(!_res.headers)_res.headers=new Headers();` + `return getResponseLength(_res).then((length)=>{` + `_res.headers.set("content-length", length);` + `return new Response(null,{status:_res.status,statusText:_res.statusText,headers:_res.headers});` + `})` + `});` + `}` + `}` let afterResponse = `c.error=notFound\n` if (app.event.afterResponse?.length && !app.event.error) { afterResponse = '\nc.error=notFound\n' const prefix = app.event.afterResponse.some(isAsync) ? 'async' : '' afterResponse += `\n${setImmediateFn}(${prefix}()=>{` + `if(c.responseValue instanceof ElysiaCustomStatusResponse) c.set.status=c.responseValue.code\n` for (let i = 0; i < app.event.afterResponse.length; i++) { const fn = app.event.afterResponse[i].fn afterResponse += `\n${isAsyncName(fn) ? 'await ' : ''}afterResponse[${i}](c)\n` } afterResponse += `})\n` } // @ts-ignore if (app.inference.query) afterResponse += '\nif(c.qi===-1){' + 'c.query={}' + '}else{' + 'c.query=parseQueryFromURL(c.url,c.qi+1)' + '}' const error404 = adapter.error404( !!app.event.request?.length, !!app.event.error?.length, afterResponse ) findDynamicRoute += error404.code findDynamicRoute += `\nc.params=route.params\n` + `if(route.store.handler)return route.store.handler(c)\n` + `return route.store.compile()(c)\n` let switchMap = '' for (const [path, methods] of Object.entries(router.static)) { switchMap += `case'${path}':` if (app.config.strictPath !== true) switchMap += `case'${getLoosePath(path)}':` const encoded = encodePath(path) if (path !== encoded) switchMap += `case'${encoded}':` switchMap += 'switch(r.method){' if ('GET' in methods || 'WS' in methods) { switchMap += `case 'GET':` if ('WS' in methods) { switchMap += `if(r.headers.get('upgrade')==='websocket')` + `return ht[${methods.WS}].composed(c)\n` if ('GET' in methods === false) { if ('ALL' in methods) switchMap += `return ht[${methods.ALL}].composed(c)\n` else switchMap += `break map\n` } } if ('GET' in methods) switchMap += `return ht[${methods.GET}].composed(c)\n` } if ( isWebstandard && ('GET' in methods || 'ALL' in methods) && 'HEAD' in methods === false ) switchMap += `case 'HEAD':` + `return Promise.resolve(ht[${methods.GET ?? methods.ALL}].composed(c)).then(_ht=>getResponseLength(_ht).then((length)=>{` + `_ht.headers.set('content-length', length)\n` + `return new Response(null,{status:_ht.status,statusText:_ht.statusText,headers:_ht.headers})\n` + `}))\n` for (const [method, index] of Object.entries(methods)) { if (method === 'ALL' || method === 'GET' || method === 'WS') continue switchMap += `case '${method}':return ht[${index}].composed(c)\n` } if ('ALL' in methods) switchMap += `default:return ht[${methods.ALL}].composed(c)\n` else switchMap += `default:break map\n` switchMap += '}' } const maybeAsync = !!app.event.request?.some(isAsync) const adapterVariables = adapter.inject ? Object.keys(adapter.inject).join(',') + ',' : '' fnLiteral += `\nconst {` + `app,` + `mapEarlyResponse,` + `NotFoundError,` + `randomId,` + `handleError,` + `status,` + `redirect,` + `getResponseLength,` + `ElysiaCustomStatusResponse,` + // @ts-ignore allocateIf(`parseQueryFromURL,`, app.inference.query) + allocateIf(`ELYSIA_TRACE,`, hasTrace) + allocateIf(`ELYSIA_REQUEST_ID,`, hasTrace) + adapterVariables + `}=data\n` + `const store=app.singleton.store\n` + `const decorator=app.singleton.decorator\n` + `const staticRouter=app.router.static.http\n` + `const ht=app.router.history\n` + `const router=app.router.http\n` + `const trace=app.event.trace?.map(x=>typeof x==='function'?x:x.fn)??[]\n` + `const notFound=new NotFoundError()\n` + `const hoc=app.extender.higherOrderFunctions.map(x=>x.fn)\n` if (app.event.request?.length) fnLiteral += `const onRequest=app.event.request.map(x=>x.fn)\n` if (app.event.afterResponse?.length) fnLiteral += `const afterResponse=app.event.afterResponse.map(x=>x.fn)\n` fnLiteral += error404.declare if (app.event.trace?.length) fnLiteral += `const ` + app.event.trace .map((_, i) => `tr${i}=app.event.trace[${i}].fn`) .join(',') + '\n' fnLiteral += `${maybeAsync ? 'async ' : ''}function map(${adapter.parameters}){` if (app.event.request?.length) fnLiteral += `let re\n` fnLiteral += adapter.createContext(app) if (app.event.trace?.length) fnLiteral += `c[ELYSIA_TRACE]=[` + app.event.trace.map((_, i) => `tr${i}(c)`).join(',') + `]\n` fnLiteral += createOnRequestHandler(app) if (switchMap) fnLiteral += `\nmap: switch(p){\n` + switchMap + `}` fnLiteral += findDynamicRoute + `}\n` + createHoc(app) const handleError = composeErrorHandler(app) // @ts-expect-error private property app.handleError = handleError const fn = Function( 'data', '"use strict";\n' + fnLiteral )({ app, mapEarlyResponse: app['~adapter']['handler'].mapEarlyResponse, NotFoundError, randomId, handleError, status, redirect, getResponseLength, ElysiaCustomStatusResponse, // @ts-ignore parseQueryFromURL: app.inference.query ? parseQueryFromURL : undefined, ELYSIA_TRACE: hasTrace ? ELYSIA_TRACE : undefined, ELYSIA_REQUEST_ID: hasTrace ? ELYSIA_REQUEST_ID : undefined, ...adapter.inject }) if (isBun) Bun.gc(false) return fn } export const composeErrorHandler = (app: AnyElysia) => { const hooks = app.event let fnLiteral = '' const adapter = app['~adapter'].composeError const adapterVariables = adapter.inject ? Object.keys(adapter.inject).join(',') + ',' : '' const hasTrace = !!app.event.trace?.length fnLiteral += `const {` + `mapResponse,` + `ERROR_CODE,` + `ElysiaCustomStatusResponse,` + `ValidationError,` + `TransformDecodeError,` + allocateIf(`onError,`, app.event.error) + allocateIf(`afterResponse,`, app.event.afterResponse) + allocateIf(`trace,`, app.event.trace) + allocateIf(`onMapResponse,`, app.event.mapResponse) + allocateIf(`ELYSIA_TRACE,`, hasTrace) + allocateIf(`ELYSIA_REQUEST_ID,`, hasTrace) + adapterVariables + `}=inject\n` // Always make error handler async since toResponse() may return promises fnLiteral += `return async function(context,error,skipGlobal){` fnLiteral += '' if (hasTrace) fnLiteral += 'const id=context[ELYSIA_REQUEST_ID]\n' const report = createReport({ context: 'context', trace: hooks.trace, addFn: (word) => { fnLiteral += word } }) const afterResponse = () => { if (!hooks.afterResponse?.length && !hasTrace) return '' let afterResponse = '' const prefix = hooks.afterResponse?.some(isAsync) ? 'async' : '' afterResponse += `\n${setImmediateFn}(${prefix}()=>{` const reporter = createReport({ context: 'context', trace: hooks.trace, addFn: (word) => { afterResponse += word } })('afterResponse', { total: hooks.afterResponse?.length, name: 'context' }) if (hooks.afterResponse?.length && hooks.afterResponse) { for (let i = 0; i < hooks.afterResponse.length; i++) { const fn = hooks.afterResponse[i].fn const endUnit = reporter.resolveChild(fn.name) afterResponse += `\n${isAsyncName(fn) ? 'await ' : ''}afterResponse[${i}](context)\n` endUnit() } } reporter.resolve() afterResponse += `})\n` return afterResponse } fnLiteral += `const set=context.set\n` + `let _r\n` + `if(!context.code)context.code=error.code??error[ERROR_CODE]\n` + `if(!(context.error instanceof Error))context.error=error\n` + `if(error instanceof ElysiaCustomStatusResponse){` + `set.status=error.status=error.code\n` + `error.message=error.response` + `}` if (adapter.declare) fnLiteral += adapter.declare const saveResponse = hasTrace || !!hooks.afterResponse?.length ? 'context.response = ' : '' fnLiteral += `if(typeof error?.toResponse==='function'&&!(error instanceof ValidationError)&&!(error instanceof TransformDecodeError)){` + `try{` + `let raw=error.toResponse()\n` + `if(typeof raw?.then==='function')raw=await raw\n` + `if(raw instanceof Response)set.status=raw.status\n` + `context.response=context.responseValue=raw\n` + `}catch(toResponseError){\n` + `}\n` + `}\n` if (app.event.error) for (let i = 0; i < app.event.error.length; i++) { const handler = app.event.error[i] const response = `${ isAsync(handler) ? 'await ' : '' }onError[${i}](context)\n` fnLiteral += 'if(skipGlobal!==true&&!context.response){' if (hasReturn(handler)) { fnLiteral += `_r=${response}\nif(_r!==undefined){` + `if(_r instanceof Response){` + afterResponse() + `return mapResponse(_r,set${adapter.mapResponseContext})}` + `if(_r instanceof ElysiaCustomStatusResponse){` + `error.status=error.code\n` + `error.message=error.response` + `}` + `if(set.status===200||!set.status)set.status=error.status\n` const mapResponseReporter = report('mapResponse', { total: hooks.mapResponse?.length, name: 'context' }) if (hooks.mapResponse?.length) { for (let i = 0; i < hooks.mapResponse.length; i++) { const mapResponse = hooks.mapResponse[i] const endUnit = mapResponseReporter.resolveChild( mapResponse.fn.name ) fnLiteral += `context.response=context.responseValue=_r\n` + `_r=${isAsyncName(mapResponse) ? 'await ' : ''}onMapResponse[${i}](context)\n` endUnit() } } mapResponseReporter.resolve() fnLiteral += afterResponse() + `return mapResponse(${saveResponse}_r,set${adapter.mapResponseContext})}` } else fnLiteral += response fnLiteral += '}' } fnLiteral += `if(error instanceof ValidationError||error instanceof TransformDecodeError){\n` + `if(error.error)error=error.error\n` + `set.status=error.status??422\n` + afterResponse() + adapter.validationError + `\n}\n` fnLiteral += `if(!context.response&&error instanceof Error){` + afterResponse() + adapter.unknownError + `\n}` const mapResponseReporter = report('mapResponse', { total: hooks.mapResponse?.length, name: 'context' }) fnLiteral += '\nif(!context.response)context.response=context.responseValue=error.message??error\n' if (hooks.mapResponse?.length) { fnLiteral += 'let mr\n' for (let i = 0; i < hooks.mapResponse.length; i++) { const mapResponse = hooks.mapResponse[i] const endUnit = mapResponseReporter.resolveChild( mapResponse.fn.name ) fnLiteral += `if(mr===undefined){` + `mr=${isAsyncName(mapResponse) ? 'await ' : ''}onMapResponse[${i}](context)\n` + `if(mr!==undefined)error=context.response=context.responseValue=mr` + '}' endUnit() } } mapResponseReporter.resolve() fnLiteral += afterResponse() + `\nreturn mapResponse(${saveResponse}error,set${adapter.mapResponseContext})}` const mapFn = (x: Function | HookContainer) => typeof x === 'function' ? x : x.fn return Function( 'inject', '"use strict";\n' + fnLiteral )({ mapResponse: app['~adapter'].handler.mapResponse, ERROR_CODE, ElysiaCustomStatusResponse, ValidationError, TransformDecodeError, onError: app.event.error?.map(mapFn), afterResponse: app.event.afterResponse?.map(mapFn), trace: app.event.trace?.map(mapFn), onMapResponse: app.event.mapResponse?.map(mapFn), ELYSIA_TRACE: hasTrace ? ELYSIA_TRACE : undefined, ELYSIA_REQUEST_ID: hasTrace ? ELYSIA_REQUEST_ID : undefined, ...adapter.inject }) } ================================================ FILE: src/context.ts ================================================ import type { Server } from './universal/server' import type { Cookie, ElysiaCookie } from './cookies' import type { StatusMap, InvertedStatusMap, redirect as Redirect } from './utils' import { ElysiaCustomStatusResponse, status, type SelectiveStatus } from './error' import type { RouteSchema, Prettify, ResolvePath, SingletonBase, HTTPHeaders, InputSchema } from './types' type InvertedStatusMapKey = keyof InvertedStatusMap type CheckExcessProps = 0 extends 1 & T ? T // T is any : U extends U ? Exclude extends never ? T : { [K in keyof U]: U[K] } & { [K in Exclude]: never } : never export type ErrorContext< in out Route extends RouteSchema = {}, in out Singleton extends SingletonBase = { decorator: {} store: {} derive: {} resolve: {} }, Path extends string | undefined = undefined > = Prettify< { body: Route['body'] query: undefined extends Route['query'] ? Record : Route['query'] params: undefined extends Route['params'] ? Path extends `${string}/${':' | '*'}${string}` ? ResolvePath : { [key in string]: string } : Route['params'] headers: undefined extends Route['headers'] ? Record : Route['headers'] cookie: undefined extends Route['cookie'] ? Record> : Record> & { [key in keyof Route['cookie']]-?: NonNullable< Cookie > } server: Server | null redirect: Redirect set: { headers: HTTPHeaders status?: number | keyof StatusMap redirect?: string /** * ! Internal Property * * Use `Context.cookie` instead */ cookie?: Record } status: {} extends Route['response'] ? typeof status : < const Code extends | keyof Route['response'] | InvertedStatusMap[Extract< InvertedStatusMapKey, keyof Route['response'] >], T extends Code extends keyof Route['response'] ? Route['response'][Code] : Code extends keyof StatusMap ? // @ts-ignore StatusMap[Code] always valid because Code generic check Route['response'][StatusMap[Code]] : never >( code: Code, response: CheckExcessProps< T, Code extends keyof Route['response'] ? Route['response'][Code] : Code extends keyof StatusMap ? // @ts-ignore StatusMap[Code] always valid because Code generic check Route['response'][StatusMap[Code]] : never > ) => ElysiaCustomStatusResponse< // @ts-ignore trust me bro Code, T > /** * Path extracted from incoming URL * * Represent a value extracted from URL * * @example '/id/9' */ path: string /** * Path as registered to router * * Represent a path registered to a router, not a URL * * @example '/id/:id' */ route: string request: Request store: Singleton['store'] } & Singleton['decorator'] & Singleton['derive'] & Singleton['resolve'] > type PrettifyIfObject = T extends object ? Prettify : T export type Context< in out Route extends RouteSchema = {}, in out Singleton extends SingletonBase = { decorator: {} store: {} derive: {} resolve: {} }, Path extends string | undefined = undefined > = Prettify< { body: PrettifyIfObject query: undefined extends Route['query'] ? {} extends NonNullable ? Record : Singleton['resolve']['query'] : PrettifyIfObject params: undefined extends Route['params'] ? undefined extends Path ? {} extends NonNullable ? Record : Singleton['resolve']['params'] : Path extends `${string}/${':' | '*'}${string}` ? ResolvePath : never : PrettifyIfObject headers: undefined extends Route['headers'] ? {} extends NonNullable ? Record : Singleton['resolve']['headers'] : PrettifyIfObject< Route['headers'] & Singleton['resolve']['headers'] > cookie: undefined extends Route['cookie'] ? Record> : Record> & Prettify< { [key in keyof Route['cookie']]-?: Cookie< Route['cookie'][key] > } & { [key in keyof Singleton['resolve']['cookie']]-?: Cookie< Singleton['resolve']['cookie'][key] > } > server: Server | null redirect: Redirect set: { headers: HTTPHeaders status?: number | keyof StatusMap /** * @deprecated Use inline redirect instead * * @example Migration example * ```ts * new Elysia() * .get(({ redirect }) => redirect('/')) * ``` */ redirect?: string /** * ! Internal Property * * Use `Context.cookie` instead */ cookie?: Record } /** * Path extracted from incoming URL * * Represent a value extracted from URL * * @example '/id/9' */ path: string /** * Path as registered to router * * Represent a path registered to a router, not a URL * * @example '/id/:id' */ route: string request: Request store: Singleton['store'] status: {} extends Route['response'] ? typeof status : SelectiveStatus } & Singleton['decorator'] & Singleton['derive'] & Omit > // Use to mimic request before mapping route export type PreContext< in out Singleton extends SingletonBase = { decorator: {} store: {} derive: {} resolve: {} } > = Prettify< { store: Singleton['store'] request: Request redirect: Redirect server: Server | null set: { headers: HTTPHeaders status?: number redirect?: string } status: typeof status } & Singleton['decorator'] > ================================================ FILE: src/cookies.ts ================================================ import { parse, serialize } from 'cookie' import decode from 'fast-decode-uri-component' import { isNotEmpty, unsignCookie } from './utils' import { InvalidCookieSignature } from './error' import type { Context } from './context' import type { Prettify } from './types' // FNV-1a hash for fast string hashing const hashString = (str: string): number => { const FNV_OFFSET_BASIS = 2166136261 const FNV_PRIME = 16777619 let hash = FNV_OFFSET_BASIS const len = str.length for (let i = 0; i < len; i++) { hash ^= str.charCodeAt(i) hash = Math.imul(hash, FNV_PRIME) } return hash >>> 0 } export interface CookieOptions { /** * Specifies the value for the {@link https://tools.ietf.org/html/rfc6265#section-5.2.3|Domain Set-Cookie attribute}. By default, no * domain is set, and most clients will consider the cookie to apply to only * the current domain. */ domain?: string | undefined /** * Specifies the `Date` object to be the value for the {@link https://tools.ietf.org/html/rfc6265#section-5.2.1|`Expires` `Set-Cookie` attribute}. By default, * no expiration is set, and most clients will consider this a "non-persistent cookie" and will delete * it on a condition like exiting a web browser application. * * *Note* the {@link https://tools.ietf.org/html/rfc6265#section-5.3|cookie storage model specification} * states that if both `expires` and `maxAge` are set, then `maxAge` takes precedence, but it is * possible not all clients by obey this, so if both are set, they should * point to the same date and time. */ expires?: Date | undefined /** * Specifies the boolean value for the {@link https://tools.ietf.org/html/rfc6265#section-5.2.6|`HttpOnly` `Set-Cookie` attribute}. * When truthy, the `HttpOnly` attribute is set, otherwise it is not. By * default, the `HttpOnly` attribute is not set. * * *Note* be careful when setting this to true, as compliant clients will * not allow client-side JavaScript to see the cookie in `document.cookie`. */ httpOnly?: boolean | undefined /** * Specifies the number (in seconds) to be the value for the `Max-Age` * `Set-Cookie` attribute. The given number will be converted to an integer * by rounding down. By default, no maximum age is set. * * *Note* the {@link https://tools.ietf.org/html/rfc6265#section-5.3|cookie storage model specification} * states that if both `expires` and `maxAge` are set, then `maxAge` takes precedence, but it is * possible not all clients by obey this, so if both are set, they should * point to the same date and time. */ maxAge?: number | undefined /** * Specifies the value for the {@link https://tools.ietf.org/html/rfc6265#section-5.2.4|`Path` `Set-Cookie` attribute}. * By default, the path is considered the "default path". */ path?: string | undefined /** * Specifies the `string` to be the value for the [`Priority` `Set-Cookie` attribute][rfc-west-cookie-priority-00-4.1]. * * - `'low'` will set the `Priority` attribute to `Low`. * - `'medium'` will set the `Priority` attribute to `Medium`, the default priority when not set. * - `'high'` will set the `Priority` attribute to `High`. * * More information about the different priority levels can be found in * [the specification][rfc-west-cookie-priority-00-4.1]. * * **note** This is an attribute that has not yet been fully standardized, and may change in the future. * This also means many clients may ignore this attribute until they understand it. */ priority?: 'low' | 'medium' | 'high' | undefined /** * Specifies the `boolean` value for the [`Partitioned` `Set-Cookie`](rfc-cutler-httpbis-partitioned-cookies) * attribute. When truthy, the `Partitioned` attribute is set, otherwise it is not. By default, the * `Partitioned` attribute is not set. * * **note** This is an attribute that has not yet been fully standardized, and may change in the future. * This also means many clients may ignore this attribute until they understand it. * * More information about can be found in [the proposal](https://github.com/privacycg/CHIPS) */ partitioned?: boolean | undefined /** * Specifies the boolean or string to be the value for the {@link https://tools.ietf.org/html/draft-ietf-httpbis-rfc6265bis-03#section-4.1.2.7|`SameSite` `Set-Cookie` attribute}. * * - `true` will set the `SameSite` attribute to `Strict` for strict same * site enforcement. * - `false` will not set the `SameSite` attribute. * - `'lax'` will set the `SameSite` attribute to Lax for lax same site * enforcement. * - `'strict'` will set the `SameSite` attribute to Strict for strict same * site enforcement. * - `'none'` will set the SameSite attribute to None for an explicit * cross-site cookie. * * More information about the different enforcement levels can be found in {@link https://tools.ietf.org/html/draft-ietf-httpbis-rfc6265bis-03#section-4.1.2.7|the specification}. * * *note* This is an attribute that has not yet been fully standardized, and may change in the future. This also means many clients may ignore this attribute until they understand it. */ sameSite?: true | false | 'lax' | 'strict' | 'none' | undefined /** * Specifies the boolean value for the {@link https://tools.ietf.org/html/rfc6265#section-5.2.5|`Secure` `Set-Cookie` attribute}. When truthy, the * `Secure` attribute is set, otherwise it is not. By default, the `Secure` attribute is not set. * * *Note* be careful when setting this to `true`, as compliant clients will * not send the cookie back to the server in the future if the browser does * not have an HTTPS connection. */ secure?: boolean | undefined /** * Secret key for signing cookie * * If array is passed, will use Key Rotation. * * Key rotation is when an encryption key is retired * and replaced by generating a new cryptographic key. * * When null is provided in array, * Elysia will allow unsigned cookie for smooth * transition from unsign to sign */ secrets?: string | null | (string | null)[] } export type ElysiaCookie = Prettify< CookieOptions & { value?: unknown } > type Updater = T | ((value: T) => T) export class Cookie implements ElysiaCookie { private valueHash?: number constructor( private name: string, private jar: Record, private initial: Partial = Object.create(null) ) {} get cookie() { return this.jar[this.name] ?? this.initial } set cookie(jar: ElysiaCookie) { if (!(this.name in this.jar)) this.jar[this.name] = this.initial this.jar[this.name] = jar // Invalidate hash cache when jar is modified directly this.valueHash = undefined } protected get setCookie() { if (!(this.name in this.jar)) this.jar[this.name] = this.initial return this.jar[this.name] } protected set setCookie(jar: ElysiaCookie) { this.cookie = jar } get value(): T { return this.cookie.value as T } set value(value: T) { // Check if value actually changed before creating entry in jar const current = this.cookie.value // Simple equality check if (current === value) return // For objects, use hash-based comparison for performance // Note: Uses JSON.stringify for comparison, so key order matters // { a: 1, b: 2 } and { b: 2, a: 1 } are treated as different values if ( typeof current === 'object' && current !== null && typeof value === 'object' && value !== null ) { try { // Cache stringified value to avoid duplicate stringify calls const valueStr = JSON.stringify(value) const newHash = hashString(valueStr) // If hash differs from cached hash, value definitely changed if ( this.valueHash !== undefined && this.valueHash !== newHash ) { this.valueHash = newHash } // First set (valueHash undefined) OR hashes match: do deep comparison else { if (JSON.stringify(current) === valueStr) { this.valueHash = newHash return // Values are identical, skip update } this.valueHash = newHash } } catch {} } // Only create entry in jar if value actually changed if (!(this.name in this.jar)) this.jar[this.name] = { ...this.initial } this.jar[this.name].value = value } get expires() { return this.cookie.expires } set expires(expires: Date | undefined) { this.setCookie.expires = expires } get maxAge() { return this.cookie.maxAge } set maxAge(maxAge: number | undefined) { this.setCookie.maxAge = maxAge } get domain() { return this.cookie.domain } set domain(domain: string | undefined) { this.setCookie.domain = domain } get path() { return this.cookie.path } set path(path: string | undefined) { this.setCookie.path = path } get secure() { return this.cookie.secure } set secure(secure: boolean | undefined) { this.setCookie.secure = secure } get httpOnly() { return this.cookie.httpOnly } set httpOnly(httpOnly: boolean | undefined) { this.setCookie.httpOnly = httpOnly } get sameSite() { return this.cookie.sameSite } set sameSite( sameSite: true | false | 'lax' | 'strict' | 'none' | undefined ) { this.setCookie.sameSite = sameSite } get priority() { return this.cookie.priority } set priority(priority: 'low' | 'medium' | 'high' | undefined) { this.setCookie.priority = priority } get partitioned() { return this.cookie.partitioned } set partitioned(partitioned: boolean | undefined) { this.setCookie.partitioned = partitioned } get secrets() { return this.cookie.secrets } set secrets(secrets: ElysiaCookie['secrets']) { this.setCookie.secrets = secrets } update(config: Updater>) { this.setCookie = Object.assign( this.cookie, typeof config === 'function' ? config(this.cookie) : config ) return this } set(config: Updater>) { this.setCookie = Object.assign( { ...this.initial, value: this.value }, typeof config === 'function' ? config(this.cookie) : config ) return this } remove() { if (this.value === undefined) return this.set({ expires: new Date(0), maxAge: 0, value: '' }) return this } toString() { return typeof this.value === 'object' ? JSON.stringify(this.value) : (this.value?.toString() ?? '') } } export const createCookieJar = ( set: Context['set'], store: Record, initial?: Partial ): Record> => { if (!set.cookie) set.cookie = Object.create(null) return new Proxy(store, { get(_, key: string) { if (key in store) return new Cookie( key, set.cookie as Record, Object.assign({}, initial ?? {}, store[key]) ) return new Cookie( key, set.cookie as Record, Object.assign({}, initial) ) } }) as Record> } export const parseCookie = async ( set: Context['set'], cookieString?: string | null, { secrets, sign, ...initial }: CookieOptions & { sign?: true | string | string[] } = Object.create(null) ) => { if (!cookieString) return createCookieJar(set, Object.create(null), initial) const isStringKey = typeof secrets === 'string' if (sign && sign !== true && !Array.isArray(sign)) sign = [sign] const jar: Record = Object.create(null) const cookies = parse(cookieString) for (const [name, v] of Object.entries(cookies)) { if ( v === undefined || name === '__proto__' || name === 'constructor' || name === 'prototype' ) continue let value = decode(v) if (sign === true || sign?.includes(name)) { if (!secrets) throw new Error('No secret is provided to cookie plugin') if (isStringKey) { if (typeof value !== 'string') throw new InvalidCookieSignature(name) const temp = await unsignCookie(value, secrets) if (temp === false) throw new InvalidCookieSignature(name) value = temp } else { let decoded = false for (let i = 0; i < secrets.length; i++) { if (typeof value !== 'string') throw new InvalidCookieSignature(name) const temp = await unsignCookie(value, secrets[i]) if (temp !== false) { decoded = true value = temp break } } if (!decoded) throw new InvalidCookieSignature(name) } } // decode cookie after unsigned if (value) { const starts = value.charCodeAt(0) const ends = value.charCodeAt(value.length - 1) if ( (starts === 123 && ends === 125) || (starts === 91 && ends === 93) ) try { value = JSON.parse(value) } catch {} } jar[name] = Object.create(null) jar[name].value = value } return createCookieJar(set, jar, initial) } export const serializeCookie = (cookies: Context['set']['cookie']) => { if (!cookies || !isNotEmpty(cookies)) return undefined const set: string[] = [] for (const [key, property] of Object.entries(cookies)) { if (!key || !property) continue const value = property.value if (value === undefined || value === null) continue set.push( serialize( key, typeof value === 'object' ? JSON.stringify(value) : value + '', property ) ) } if (set.length === 0) return undefined if (set.length === 1) return set[0] return set } ================================================ FILE: src/dynamic-handle.ts ================================================ import { TransformDecodeError } from '@sinclair/typebox/value' import type { Context } from './context' import { parseCookie } from './cookies' import { ElysiaCustomStatusResponse, type ElysiaErrors, NotFoundError, status, ValidationError } from './error' import type { AnyElysia, CookieOptions } from './index' import { parseQuery } from './parse-query' import { getSchemaProperties, type ElysiaTypeCheck } from './schema' import type { TypeCheck } from './type-system' import type { Handler, LifeCycleStore, SchemaValidator } from './types' import { hasSetImmediate, redirect, StatusMap, signCookie } from './utils' // JIT Handler export type DynamicHandler = { handle: unknown | Handler content?: string hooks: Partial validator?: SchemaValidator route: string } /** * Matches array index notation in property paths * Examples: * "users[0]" → Group 1: "users", Group 2: "0" * "items[42]" → Group 1: "items", Group 2: "42" * "a[123]" → Group 1: "a", Group 2: "123" * * Does not match: * "users" → no brackets * "users[]" → no index * "users[ab]" → non-numeric index */ const ARRAY_INDEX_REGEX = /^(.+)\[(\d+)\]$/ const DANGEROUS_KEYS = new Set(['__proto__', 'constructor', 'prototype']) const isDangerousKey = (key: string): boolean => { if (DANGEROUS_KEYS.has(key)) return true const match = key.match(ARRAY_INDEX_REGEX) return match ? DANGEROUS_KEYS.has(match[1]) : false } const parseArrayKey = (key: string) => { const match = key.match(ARRAY_INDEX_REGEX) if (!match) return null return { name: match[1], index: parseInt(match[2], 10) } } const parseObjectString = (entry: unknown) => { if (typeof entry !== 'string' || entry.charCodeAt(0) !== 123) return try { const parsed = JSON.parse(entry) if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) return parsed } catch { return } } const setNestedValue = (obj: Record, path: string, value: any) => { const keys = path.split('.') const lastKey = keys.pop() as string // Validate all keys upfront if (isDangerousKey(lastKey) || keys.some(isDangerousKey)) return let current = obj // Traverse intermediate keys for (const key of keys) { const arrayInfo = parseArrayKey(key) if (arrayInfo) { // Initialize array if needed if (!Array.isArray(current[arrayInfo.name])) current[arrayInfo.name] = [] const existing = current[arrayInfo.name][arrayInfo.index] const isFile = typeof File !== 'undefined' && existing instanceof File // Initialize object at index if needed if ( !existing || typeof existing !== 'object' || Array.isArray(existing) || isFile ) current[arrayInfo.name][arrayInfo.index] = parseObjectString(existing) ?? {} current = current[arrayInfo.name][arrayInfo.index] } else { // Initialize object property if needed if (!current[key] || typeof current[key] !== 'object') current[key] = {} current = current[key] } } // Set final value const arrayInfo = parseArrayKey(lastKey) if (arrayInfo) { if (!Array.isArray(current[arrayInfo.name])) current[arrayInfo.name] = [] current[arrayInfo.name][arrayInfo.index] = value } else { current[lastKey] = value } } const normalizeFormValue = (value: unknown[]) => { if (value.length === 1) { const stringValue = value[0] if (typeof stringValue === 'string') { // Try to parse JSON objects (starting with '{') or arrays (starting with '[') if (stringValue.charCodeAt(0) === 123 || stringValue.charCodeAt(0) === 91) { try { const parsed = JSON.parse(stringValue) if (parsed && typeof parsed === 'object') { return parsed } } catch {} } } return value[0] } const stringValue = value.find( (entry): entry is string => typeof entry === 'string' ) if (!stringValue) return value if (typeof File === 'undefined') return value const files = value.filter((entry): entry is File => entry instanceof File) if (!files.length) return value if (stringValue.charCodeAt(0) !== 123) return value let parsed: unknown try { parsed = JSON.parse(stringValue) } catch { return value } if (typeof parsed !== 'object' || parsed === null) return value if (!('file' in parsed) && files.length === 1) (parsed as Record).file = files[0] else if (!('files' in parsed) && files.length > 1) (parsed as Record).files = files return parsed } const injectDefaultValues = ( typeChecker: TypeCheck | ElysiaTypeCheck, obj: Record ) => { // @ts-expect-error private property let schema = typeChecker.schema if (!schema) return if (schema.$defs?.[schema.$ref]) schema = schema.$defs[schema.$ref] const properties = getSchemaProperties(schema) if (!properties) return for (const [key, keySchema] of Object.entries(properties)) { obj[key] ??= keySchema.default } } export const createDynamicHandler = (app: AnyElysia) => { const { mapResponse, mapEarlyResponse } = app['~adapter'].handler // @ts-expect-error const defaultHeader = app.setHeaders return async (request: Request): Promise => { const url = request.url, s = url.indexOf('/', 11), qi = url.indexOf('?', s + 1), path = qi === -1 ? url.substring(s) : url.substring(s, qi) const set: Context['set'] = { cookie: {}, status: 200, headers: defaultHeader ? { ...defaultHeader } : {} } const context = Object.assign( {}, // @ts-expect-error app.singleton.decorator, { set, // @ts-expect-error store: app.singleton.store, request, path, qi, error: status, status, redirect } ) as unknown as Context & { response: unknown } let hooks: DynamicHandler['hooks'] try { if (app.event.request) for (let i = 0; i < app.event.request.length; i++) { const onRequest = app.event.request[i].fn let response = onRequest(context as any) if (response instanceof Promise) response = await response response = mapEarlyResponse(response, set) if (response) return (context.response = response) } const isWS = request.method === 'GET' && request.headers.get('upgrade')?.toLowerCase() === 'websocket' const methodKey = isWS ? 'WS' : request.method const handler = app.router.dynamic.find(request.method, path) ?? app.router.dynamic.find(methodKey, path) ?? app.router.dynamic.find('ALL', path) if (!handler) { // @ts-expect-error context.query = qi === -1 ? {} : parseQuery(url.substring(qi + 1)) throw new NotFoundError() } const { handle, validator, content, route } = handler.store hooks = handler.store.hooks // @ts-ignore if (hooks.config?.mount) // @ts-ignore return await hooks.config.mount(request) let body: string | Record | undefined if (request.method !== 'GET' && request.method !== 'HEAD') { if (content) { switch (content) { case 'application/json': body = (await request.json()) as any break case 'text/plain': body = await request.text() break case 'application/x-www-form-urlencoded': body = parseQuery(await request.text()) break case 'application/octet-stream': body = await request.arrayBuffer() break case 'multipart/form-data': { body = {} const form = await request.formData() for (const key of form.keys()) { if (body[key]) continue const value = form.getAll(key) const finalValue = normalizeFormValue(value) if (key.includes('.') || key.includes('[')) setNestedValue(body, key, finalValue) else body[key] = finalValue } break } } } else { let contentType if (request.body) contentType = request.headers.get('content-type') if (contentType) { const index = contentType.indexOf(';') if (index !== -1) contentType = contentType.slice(0, index) // @ts-expect-error context.contentType = contentType if (hooks.parse) for (let i = 0; i < hooks.parse.length; i++) { const hook = hooks.parse[i].fn if (typeof hook === 'string') switch (hook) { case 'json': case 'application/json': body = (await request.json()) as any break case 'text': case 'text/plain': body = await request.text() break case 'urlencoded': case 'application/x-www-form-urlencoded': body = parseQuery( await request.text() ) break case 'arrayBuffer': case 'application/octet-stream': body = await request.arrayBuffer() break case 'formdata': case 'multipart/form-data': { body = {} const form = await request.formData() for (const key of form.keys()) { if (body[key]) continue const value = form.getAll(key) const finalValue = normalizeFormValue(value) if (key.includes('.') || key.includes('[')) setNestedValue(body, key, finalValue) else body[key] = finalValue } break } default: { const parser = app['~parser'][hook] if (parser) { let temp = parser( context as any, contentType ) if (temp instanceof Promise) temp = await temp if (temp) { body = temp break } } break } } else { let temp = hook(context as any, contentType) if (temp instanceof Promise) temp = await temp if (temp) { body = temp break } } } // @ts-expect-error delete context.contentType // body might be empty string thus can't use !body if (body === undefined) { switch (contentType) { case 'application/json': body = (await request.json()) as any break case 'text/plain': body = await request.text() break case 'application/x-www-form-urlencoded': body = parseQuery(await request.text()) break case 'application/octet-stream': body = await request.arrayBuffer() break case 'multipart/form-data': { body = {} const form = await request.formData() for (const key of form.keys()) { if (body[key]) continue const value = form.getAll(key) const finalValue = normalizeFormValue(value) if (key.includes('.') || key.includes('[')) setNestedValue(body, key, finalValue) else body[key] = finalValue } break } } } } } } context.route = route context.body = body context.params = handler?.params || undefined // @ts-expect-error context.query = qi === -1 ? {} : parseQuery(url.substring(qi + 1)) context.headers = {} for (const [key, value] of request.headers.entries()) context.headers[key] = value const cookieMeta = { domain: app.config.cookie?.domain ?? // @ts-expect-error validator?.cookie?.config.domain, expires: app.config.cookie?.expires ?? // @ts-expect-error validator?.cookie?.config.expires, httpOnly: app.config.cookie?.httpOnly ?? // @ts-expect-error validator?.cookie?.config.httpOnly, maxAge: app.config.cookie?.maxAge ?? // @ts-expect-error validator?.cookie?.config.maxAge, // @ts-expect-error path: app.config.cookie?.path ?? validator?.cookie?.config.path, priority: app.config.cookie?.priority ?? // @ts-expect-error validator?.cookie?.config.priority, partitioned: app.config.cookie?.partitioned ?? // @ts-expect-error validator?.cookie?.config.partitioned, sameSite: app.config.cookie?.sameSite ?? // @ts-expect-error validator?.cookie?.config.sameSite, secure: app.config.cookie?.secure ?? // @ts-expect-error validator?.cookie?.config.secure, secrets: app.config.cookie?.secrets ?? // @ts-expect-error validator?.cookie?.config.secrets, // @ts-expect-error sign: app.config.cookie?.sign ?? validator?.cookie?.config.sign } as CookieOptions & { sign?: true | string | string[] } const cookieHeaderValue = request.headers.get('cookie') context.cookie = (await parseCookie( context.set, cookieHeaderValue, cookieMeta )) as any const headerValidator = validator?.createHeaders?.() if (headerValidator) injectDefaultValues(headerValidator, context.headers) const paramsValidator = validator?.createParams?.() if (paramsValidator) injectDefaultValues(paramsValidator, context.params) const queryValidator = validator?.createQuery?.() if (queryValidator) injectDefaultValues(queryValidator, context.query) if (hooks.transform) for (let i = 0; i < hooks.transform.length; i++) { const hook = hooks.transform[i] let response = hook.fn(context) if (response instanceof Promise) response = await response // @ts-expect-error just in case if (response instanceof ElysiaCustomStatusResponse) { const result = mapEarlyResponse(response, context.set) if (result) return (context.response = result) as Response } if (hook.subType === 'derive') Object.assign(context, response) } if (validator) { if (headerValidator) { const _header = structuredClone(context.headers) for (const [key, value] of request.headers) _header[key] = value if (validator.headers!.Check(_header) === false) throw new ValidationError( 'header', validator.headers!, _header ) } else if (validator.headers?.Decode) // @ts-ignore context.headers = validator.headers.Decode(context.headers) if (paramsValidator?.Check(context.params) === false) { throw new ValidationError( 'params', validator.params!, context.params ) } else if (validator.params?.Decode) // @ts-ignore context.params = validator.params.Decode(context.params) if (validator.query?.schema) { let schema = validator.query.schema if (schema.$defs?.[schema.$ref]) schema = schema.$defs[schema.$ref] const properties = getSchemaProperties(schema) if (properties) { for (const property of Object.keys(properties)) { const value = properties[property] if ( (value.type === 'array' || value.items?.type === 'string') && typeof context.query[property] === 'string' && context.query[property] ) { // @ts-expect-error context.query[property] = context.query[property].split(',') } } } } if (queryValidator?.Check(context.query) === false) throw new ValidationError( 'query', validator.query!, context.query ) else if (validator.query?.Decode) context.query = validator.query.Decode(context.query) as any if (validator.createCookie?.()) { let cookieValue: Record = {} for (const [key, value] of Object.entries(context.cookie)) cookieValue[key] = value.value if (validator.cookie!.Check(cookieValue) === false) throw new ValidationError( 'cookie', validator.cookie!, cookieValue ) else if (validator.cookie?.Decode) cookieValue = validator.cookie.Decode( cookieValue ) as any } if (validator.createBody?.()?.Check(body) === false) throw new ValidationError('body', validator.body!, body) else if (validator.body?.Decode) { let decoded = validator.body.Decode(body) as any if (decoded instanceof Promise) decoded = await decoded // Zod returns { value: ... } wrapper context.body = decoded?.value ?? decoded } } if (hooks.beforeHandle) for (let i = 0; i < hooks.beforeHandle.length; i++) { const hook = hooks.beforeHandle[i] let response = hook.fn(context) if (response instanceof Promise) response = await response if (response instanceof ElysiaCustomStatusResponse) { const result = mapEarlyResponse(response, context.set) if (result) return (context.response = result) as Response } if (hook.subType === 'resolve') { Object.assign(context, response) continue } // `false` is a falsey value, check for undefined instead if (response !== undefined) { ;( context as Context & { response: unknown } ).response = response if (hooks.afterHandle) for (let i = 0; i < hooks.afterHandle.length; i++) { let newResponse = hooks.afterHandle[i].fn( context as Context & { response: unknown } ) if (newResponse instanceof Promise) newResponse = await newResponse if (newResponse) response = newResponse } const result = mapEarlyResponse(response, context.set) // @ts-expect-error if (result) return (context.response = result) } } let response = typeof handle === 'function' ? handle(context) : handle if (response instanceof Promise) response = await response if (!hooks.afterHandle?.length) { const isCustomStatuResponse = response instanceof ElysiaCustomStatusResponse const status = isCustomStatuResponse ? response.code : set.status ? typeof set.status === 'string' ? StatusMap[set.status] : set.status : 200 if (isCustomStatuResponse) { set.status = status response = response.response } const responseValidator = validator?.createResponse?.()?.[status] if (responseValidator?.Check(response) === false) { if (responseValidator?.Clean) { try { const temp = responseValidator.Clean(response) if (responseValidator?.Check(temp) === false) throw new ValidationError( 'response', responseValidator, response ) response = temp } catch (error) { if (error instanceof ValidationError) throw error throw new ValidationError( 'response', responseValidator, response ) } } else throw new ValidationError( 'response', responseValidator, response ) } if (responseValidator?.Encode) response = responseValidator.Encode(response) if (responseValidator?.Clean) try { response = responseValidator.Clean(response) } catch {} } else { ;( context as Context & { response: unknown } ).response = response for (let i = 0; i < hooks.afterHandle.length; i++) { let response: unknown = hooks.afterHandle[i].fn( context as Context & { response: unknown } ) if (response instanceof Promise) response = await response const isCustomStatuResponse = response instanceof ElysiaCustomStatusResponse const status = isCustomStatuResponse ? (response as ElysiaCustomStatusResponse).code : set.status ? typeof set.status === 'string' ? StatusMap[set.status] : set.status : 200 if (isCustomStatuResponse) { set.status = status response = (response as ElysiaCustomStatusResponse) .response } const responseValidator = validator?.createResponse?.()?.[status] if (responseValidator?.Check(response) === false) { if (responseValidator?.Clean) { try { const temp = responseValidator.Clean(response) if (responseValidator?.Check(temp) === false) throw new ValidationError( 'response', responseValidator, response ) response = temp } catch (error) { if (error instanceof ValidationError) throw error throw new ValidationError( 'response', responseValidator, response ) } } else throw new ValidationError( 'response', responseValidator, response ) } if (responseValidator?.Encode) context.response = response = responseValidator.Encode(response) if (responseValidator?.Clean) try { context.response = response = responseValidator.Clean(response) } catch {} const result = mapEarlyResponse(response, context.set) // @ts-expect-error if (result !== undefined) return (context.response = result) } } if (context.set.cookie && cookieMeta?.sign) { const secret = !cookieMeta.secrets ? undefined : typeof cookieMeta.secrets === 'string' ? cookieMeta.secrets : cookieMeta.secrets[0] if (cookieMeta.sign === true) { if (secret) for (const [key, cookie] of Object.entries( context.set.cookie )) context.set.cookie[key].value = await signCookie( cookie.value as any, secret ) } else { const properties = getSchemaProperties(validator?.cookie?.schema) if (secret) for (const name of cookieMeta.sign) { if (!properties || !(name in properties)) continue if (context.set.cookie[name]?.value) { context.set.cookie[name].value = await signCookie( context.set.cookie[name].value as any, secret ) } } } } // @ts-expect-error return mapResponse((context.response = response), context.set) } catch (error) { const reportedError = error instanceof TransformDecodeError && error.error ? error.error : error // ? Since error is reconciled in mergeResponseWithHeaders, this is not needed (if I'm not drunk) // if ((reportedError as ElysiaErrors).status) // set.status = (reportedError as ElysiaErrors).status // @ts-expect-error private return app.handleError(context, reportedError) } finally { const afterResponses = hooks! ? hooks.afterResponse : app.event.afterResponse if (afterResponses) { if (hasSetImmediate) setImmediate(async () => { for (const afterResponse of afterResponses) await afterResponse.fn(context as any) }) else Promise.resolve().then(async () => { for (const afterResponse of afterResponses) await afterResponse.fn(context as any) }) } } } } export const createDynamicErrorHandler = (app: AnyElysia) => { const { mapResponse } = app['~adapter'].handler return async ( context: Context & { response: unknown }, error: ElysiaErrors ) => { const errorContext = Object.assign(context, { error, code: error.code }) errorContext.set = context.set if ( // @ts-expect-error typeof error?.toResponse === 'function' && !(error instanceof ValidationError) && !(error instanceof TransformDecodeError) ) { try { // @ts-expect-error let raw = error.toResponse() if (typeof raw?.then === 'function') raw = await raw if (raw instanceof Response) context.set.status = raw.status context.response = raw } catch (toResponseError) { // If toResponse() throws, fall through to normal error handling // Don't set context.response so onError hooks will run } } if (!context.response && app.event.error) for (let i = 0; i < app.event.error.length; i++) { const hook = app.event.error[i] let response = hook.fn(errorContext as any) if (response instanceof Promise) response = await response if (response !== undefined && response !== null) return (context.response = mapResponse( response, context.set )) } if (context.response) { if (app.event.mapResponse) for (let i = 0; i < app.event.mapResponse.length; i++) { const hook = app.event.mapResponse[i] let response = hook.fn(errorContext as any) if (response instanceof Promise) response = await response if (response !== undefined && response !== null) context.response = response } return mapResponse(context.response, context.set) } context.set.status = error.status ?? 500 return mapResponse( typeof error.cause === 'string' ? error.cause : error.message, context.set ) } } ================================================ FILE: src/error.ts ================================================ import type { TSchema } from '@sinclair/typebox' import { Value } from '@sinclair/typebox/value' import type { TypeCheck, ValueError, ValueErrorIterator } from '@sinclair/typebox/compiler' import { StatusMap, InvertedStatusMap } from './utils' import type { ElysiaTypeCheck } from './schema' import { StandardSchemaV1Like } from './types' // ? Cloudflare worker support const env = typeof Bun !== 'undefined' ? Bun.env : typeof process !== 'undefined' ? process?.env : undefined export const ERROR_CODE = Symbol('ElysiaErrorCode') export type ERROR_CODE = typeof ERROR_CODE export const isProduction = (env?.NODE_ENV ?? env?.ENV) === 'production' export type ElysiaErrors = | InternalServerError | NotFoundError | ParseError | ValidationError | InvalidCookieSignature const emptyHttpStatus = { 101: undefined, 204: undefined, 205: undefined, 304: undefined, 307: undefined, 308: undefined } as const type CheckExcessProps = 0 extends 1 & T ? T // T is any : U extends U ? Exclude extends never ? T : { [K in keyof U]: U[K] } & { [K in Exclude]: never } : never export type SelectiveStatus = < const Code extends | keyof Res | InvertedStatusMap[Extract], T extends Code extends keyof Res ? Res[Code] : Code extends keyof StatusMap ? // @ts-ignore StatusMap[Code] always valid because Code generic check Res[StatusMap[Code]] : never >( code: Code, response: CheckExcessProps< T, Code extends keyof Res ? Res[Code] : Code extends keyof StatusMap ? // @ts-ignore StatusMap[Code] always valid because Code generic check Res[StatusMap[Code]] : never > ) => ElysiaCustomStatusResponse< // @ts-ignore trust me bro Code, T > export class ElysiaCustomStatusResponse< const in out Code extends number | keyof StatusMap, // no in out here so the response can be sub type of return type T = Code extends keyof InvertedStatusMap ? InvertedStatusMap[Code] : Code, const in out Status extends Code extends keyof StatusMap ? StatusMap[Code] : Code = Code extends keyof StatusMap ? StatusMap[Code] : Code > { code: Status response: T constructor(code: Code, response: T) { const res = response ?? (code in InvertedStatusMap ? // @ts-expect-error Always correct InvertedStatusMap[code] : code) // @ts-ignore Trust me bro this.code = StatusMap[code] ?? code if (code in emptyHttpStatus) this.response = undefined as any else // @ts-ignore Trust me bro this.response = res } } export const ElysiaStatus = ElysiaCustomStatusResponse export const status = < const Code extends number | keyof StatusMap, const T = Code extends keyof InvertedStatusMap ? InvertedStatusMap[Code] : Code >( code: Code, response?: T ) => new ElysiaCustomStatusResponse(code, response as T) export class InternalServerError extends Error { code = 'INTERNAL_SERVER_ERROR' status = 500 constructor(message?: string) { super(message ?? 'INTERNAL_SERVER_ERROR') } } export class NotFoundError extends Error { code = 'NOT_FOUND' status = 404 constructor(message?: string) { super(message ?? 'NOT_FOUND') } } export class ParseError extends Error { code = 'PARSE' status = 400 constructor(cause?: Error) { super('Bad Request', { cause }) } } export class InvalidCookieSignature extends Error { code = 'INVALID_COOKIE_SIGNATURE' status = 400 constructor( public key: string, message?: string ) { super(message ?? `"${key}" has invalid cookie signature`) } } interface ValueErrorWithSummary extends ValueError { summary?: string } export const mapValueError = ( error: ValueError | undefined ): ValueErrorWithSummary | undefined => { if (!error) return error let { message, path, value, type } = error if (Array.isArray(path)) path = path[0] const property = typeof path === 'string' ? path.slice(1).replaceAll('/', '.') : 'unknown' const isRoot = path === '' switch (type) { case 42: return { ...error, summary: isRoot ? `Value should not be provided` : `Property '${property}' should not be provided` } case 45: return { ...error, summary: isRoot ? `Value is missing` : `Property '${property}' is missing` } case 50: // Expected string to match 'email' format const quoteIndex = message.indexOf("'")! const format = message.slice( quoteIndex + 1, message.indexOf("'", quoteIndex + 1) ) return { ...error, summary: isRoot ? `Value should be an email` : `Property '${property}' should be ${format}` } case 54: return { ...error, summary: `${message .slice(0, 9) .trim()} property '${property}' to be ${message .slice(8) .trim()} but found: ${value}` } case 62: const union = error.schema.anyOf .map((x: Record) => `'${x?.format ?? x.type}'`) .join(', ') return { ...error, summary: isRoot ? `Value should be one of ${union}` : `Property '${property}' should be one of: ${union}` } default: return { summary: message, ...error } } } export class InvalidFileType extends Error { code = 'INVALID_FILE_TYPE' status = 422 constructor( public property: string, public expected: string | string[], public message = `"${property}" has invalid file type` ) { super(message) Object.setPrototypeOf(this, InvalidFileType.prototype) } toResponse(headers?: Record) { if (isProduction) return new Response( JSON.stringify({ type: 'validation', on: 'body' }), { status: 422, headers: { ...headers, 'content-type': 'application/json' } } ) return new Response( JSON.stringify({ type: 'validation', on: 'body', summary: 'Invalid file type', message: this.message, property: this.property, expected: this.expected }), { status: 422, headers: { ...headers, 'content-type': 'application/json' } } ) } } export class ValidationError extends Error { code = 'VALIDATION' status = 422 /** * An actual value of `message` * * Since `message` is string * use this instead of message */ valueError?: ValueError /** * Alias of `valueError` */ get messageValue() { return this.valueError } /** * Expected value of the schema */ expected?: unknown /** * Custom error if provided */ customError?: unknown constructor( public type: string, public validator: | TSchema | TypeCheck | ElysiaTypeCheck | StandardSchemaV1Like, /** * Input value */ public value: unknown, private allowUnsafeValidationDetails = false, errors?: ValueErrorIterator ) { let message = '' let error let expected let customError if ( // @ts-ignore validator?.provider === 'standard' || '~standard' in validator || // @ts-ignore (validator.schema && '~standard' in validator.schema) ) { const standard = // @ts-ignore ('~standard' in validator ? validator : validator.schema)[ '~standard' ] const _errors = errors ?? standard.validate(value).issues error = _errors?.[0] if (isProduction && !allowUnsafeValidationDetails) message = JSON.stringify({ type: 'validation', on: type, found: value }) else message = JSON.stringify( { type: 'validation', on: type, property: error.path?.[0] || 'root', message: error?.message, summary: error?.problem, expected, found: value, errors }, null, 2 ) customError = error?.message } else { if ( value && typeof value === 'object' && value instanceof ElysiaCustomStatusResponse ) value = value.response error = errors?.First() ?? ('Errors' in validator ? validator.Errors(value).First() : Value.Errors(validator, value).First()) const accessor = error?.path || 'root' // @ts-ignore private field const schema = validator?.schema ?? validator if (!isProduction && !allowUnsafeValidationDetails) try { expected = Value.Create(schema) } catch (error) { expected = { type: 'Could not create expected value', // @ts-expect-error message: error?.message, error } } customError = error?.schema?.message || error?.schema?.error !== undefined ? typeof error.schema.error === 'function' ? error.schema.error( isProduction && !allowUnsafeValidationDetails ? { type: 'validation', on: type, found: value } : { type: 'validation', on: type, value, property: accessor, message: error?.message, summary: mapValueError(error)?.summary, found: value, expected, errors: 'Errors' in validator ? [ ...validator.Errors( value ) ].map(mapValueError) : [ ...Value.Errors( validator, value ) ].map(mapValueError) }, validator ) : error.schema.error : undefined if (customError !== undefined) { message = typeof customError === 'object' ? JSON.stringify(customError) : customError + '' } else if (isProduction && !allowUnsafeValidationDetails) { message = JSON.stringify({ type: 'validation', on: type, found: value }) } else { message = JSON.stringify( { type: 'validation', on: type, property: accessor, message: error?.message, summary: mapValueError(error)?.summary, expected, found: value, errors: 'Errors' in validator ? [...validator.Errors(value)].map( mapValueError ) : [...Value.Errors(validator, value)].map( mapValueError ) }, null, 2 ) } } super(message) this.valueError = error this.expected = expected this.customError = customError Object.setPrototypeOf(this, ValidationError.prototype) } get all(): ValueErrorWithSummary[] { // Handle standard schema validators (Zod, Valibot, etc.) if ( // @ts-ignore this.validator?.provider === 'standard' || '~standard' in this.validator || // @ts-ignore ('schema' in this.validator && // @ts-ignore this.validator.schema && // @ts-ignore '~standard' in this.validator.schema) ) { const standard = // @ts-ignore ( '~standard' in this.validator ? this.validator : // @ts-ignore this.validator.schema )['~standard'] const issues = standard.validate(this.value).issues // Map standard schema issues to the expected format return ( issues?.map((issue: any) => ({ summary: issue.message, path: issue.path?.join('.') || 'root', message: issue.message, value: this.value })) || [] ) } // Handle TypeBox validators return 'Errors' in this.validator ? [...this.validator.Errors(this.value)] .filter((x) => x) .map((x) => mapValueError(x) as ValueErrorWithSummary) : // @ts-ignore [...Value.Errors(this.validator, this.value)].map(mapValueError) } static simplifyModel( validator: TSchema | TypeCheck | ElysiaTypeCheck ) { // @ts-ignore const model = 'schema' in validator ? validator.schema : validator try { return Value.Create(model) } catch { return model } } get model() { if ('~standard' in this.validator) return this.validator return ValidationError.simplifyModel(this.validator) } toResponse(headers?: Record) { return new Response(this.message, { status: 400, headers: { ...headers, 'content-type': 'application/json' } }) } /** * Utility function to inherit add custom error and keep the original Validation error * * @since 1.3.14 * * @example * ```ts * new Elysia() * .onError(({ error, code }) => { * if (code === 'VALIDATION') return error.detail(error.message) * }) * .post('/', () => 'Hello World!', { * body: t.Object({ * x: t.Number({ * error: 'x must be a number' * }) * }) * }) * ``` */ detail( message: unknown, allowUnsafeValidatorDetails = this.allowUnsafeValidationDetails ) { if (!this.customError) return this.message const value = this.value const expected = this.expected const errors = this.all return isProduction && !allowUnsafeValidatorDetails ? { type: 'validation', on: this.type, found: value, message } : { type: 'validation', on: this.type, property: this.valueError?.path || 'root', message, summary: mapValueError(this.valueError)?.summary, found: value, expected, errors } } } ================================================ FILE: src/formats.ts ================================================ /** * ? Fork of ajv-formats without ajv as dependencies * * @see https://github.com/ajv-validator/ajv-formats/blob/master/src/formats.ts **/ /* eslint-disable no-control-regex */ export type FormatName = | 'date' | 'time' | 'date-time' | 'iso-time' | 'iso-date-time' | 'duration' | 'uri' | 'uri-reference' | 'uri-template' | 'url' | 'email' | 'hostname' | 'ipv4' | 'ipv6' | 'regex' | 'uuid' | 'json-pointer' | 'json-pointer-uri-fragment' | 'relative-json-pointer' | 'byte' | 'int32' | 'int64' | 'float' | 'double' | 'password' | 'binary' export const fullFormats = { // date: http://tools.ietf.org/html/rfc3339#section-5.6 date, // date-time: http://tools.ietf.org/html/rfc3339#section-5.6 time: getTime(true), 'date-time': getDateTime(true), 'iso-time': getTime(false), 'iso-date-time': getDateTime(false), // duration: https://tools.ietf.org/html/rfc3339#appendix-A duration: /^P(?!$)((\d+Y)?(\d+M)?(\d+D)?(T(?=\d)(\d+H)?(\d+M)?(\d+S)?)?|(\d+W)?)$/, uri, 'uri-reference': /^(?:[a-z][a-z0-9+\-.]*:)?(?:\/?\/(?:(?:[a-z0-9\-._~!$&'()*+,;=:]|%[0-9a-f]{2})*@)?(?:\[(?:(?:(?:(?:[0-9a-f]{1,4}:){6}|::(?:[0-9a-f]{1,4}:){5}|(?:[0-9a-f]{1,4})?::(?:[0-9a-f]{1,4}:){4}|(?:(?:[0-9a-f]{1,4}:){0,1}[0-9a-f]{1,4})?::(?:[0-9a-f]{1,4}:){3}|(?:(?:[0-9a-f]{1,4}:){0,2}[0-9a-f]{1,4})?::(?:[0-9a-f]{1,4}:){2}|(?:(?:[0-9a-f]{1,4}:){0,3}[0-9a-f]{1,4})?::[0-9a-f]{1,4}:|(?:(?:[0-9a-f]{1,4}:){0,4}[0-9a-f]{1,4})?::)(?:[0-9a-f]{1,4}:[0-9a-f]{1,4}|(?:(?:25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(?:25[0-5]|2[0-4]\d|[01]?\d\d?))|(?:(?:[0-9a-f]{1,4}:){0,5}[0-9a-f]{1,4})?::[0-9a-f]{1,4}|(?:(?:[0-9a-f]{1,4}:){0,6}[0-9a-f]{1,4})?::)|[Vv][0-9a-f]+\.[a-z0-9\-._~!$&'()*+,;=:]+)\]|(?:(?:25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(?:25[0-5]|2[0-4]\d|[01]?\d\d?)|(?:[a-z0-9\-._~!$&'"()*+,;=]|%[0-9a-f]{2})*)(?::\d*)?(?:\/(?:[a-z0-9\-._~!$&'"()*+,;=:@]|%[0-9a-f]{2})*)*|\/(?:(?:[a-z0-9\-._~!$&'"()*+,;=:@]|%[0-9a-f]{2})+(?:\/(?:[a-z0-9\-._~!$&'"()*+,;=:@]|%[0-9a-f]{2})*)*)?|(?:[a-z0-9\-._~!$&'"()*+,;=:@]|%[0-9a-f]{2})+(?:\/(?:[a-z0-9\-._~!$&'"()*+,;=:@]|%[0-9a-f]{2})*)*)?(?:\?(?:[a-z0-9\-._~!$&'"()*+,;=:@/?]|%[0-9a-f]{2})*)?(?:#(?:[a-z0-9\-._~!$&'"()*+,;=:@/?]|%[0-9a-f]{2})*)?$/i, // uri-template: https://tools.ietf.org/html/rfc6570 'uri-template': /^(?:(?:[^\x00-\x20"'<>%\\^`{|}]|%[0-9a-f]{2})|\{[+#./;?&=,!@|]?(?:[a-z0-9_]|%[0-9a-f]{2})+(?::[1-9][0-9]{0,3}|\*)?(?:,(?:[a-z0-9_]|%[0-9a-f]{2})+(?::[1-9][0-9]{0,3}|\*)?)*\})*$/i, // For the source: https://gist.github.com/dperini/729294 // For test cases: https://mathiasbynens.be/demo/url-regex url: /^(?:https?|ftp):\/\/(?:\S+(?::\S*)?@)?(?:(?!(?:10|127)(?:\.\d{1,3}){3})(?!(?:169\.254|192\.168)(?:\.\d{1,3}){2})(?!172\.(?:1[6-9]|2\d|3[0-1])(?:\.\d{1,3}){2})(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.(?:[1-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(?:(?:[a-z0-9\u{00a1}-\u{ffff}]+-)*[a-z0-9\u{00a1}-\u{ffff}]+)(?:\.(?:[a-z0-9\u{00a1}-\u{ffff}]+-)*[a-z0-9\u{00a1}-\u{ffff}]+)*(?:\.(?:[a-z\u{00a1}-\u{ffff}]{2,})))(?::\d{2,5})?(?:\/[^\s]*)?$/iu, email: /^[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/i, hostname: /^(?=.{1,253}\.?$)[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?(?:\.[a-z0-9](?:[-0-9a-z]{0,61}[0-9a-z])?)*\.?$/i, // optimized https://www.safaribooksonline.com/library/view/regular-expressions-cookbook/9780596802837/ch07s16.html ipv4: /^(?:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)\.){3}(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)$/, ipv6: /^((([0-9a-f]{1,4}:){7}([0-9a-f]{1,4}|:))|(([0-9a-f]{1,4}:){6}(:[0-9a-f]{1,4}|((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9a-f]{1,4}:){5}(((:[0-9a-f]{1,4}){1,2})|:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9a-f]{1,4}:){4}(((:[0-9a-f]{1,4}){1,3})|((:[0-9a-f]{1,4})?:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9a-f]{1,4}:){3}(((:[0-9a-f]{1,4}){1,4})|((:[0-9a-f]{1,4}){0,2}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9a-f]{1,4}:){2}(((:[0-9a-f]{1,4}){1,5})|((:[0-9a-f]{1,4}){0,3}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9a-f]{1,4}:){1}(((:[0-9a-f]{1,4}){1,6})|((:[0-9a-f]{1,4}){0,4}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(:(((:[0-9a-f]{1,4}){1,7})|((:[0-9a-f]{1,4}){0,5}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:)))$/i, regex, // uuid: http://tools.ietf.org/html/rfc4122 uuid: /^(?:urn:uuid:)?[0-9a-f]{8}-(?:[0-9a-f]{4}-){3}[0-9a-f]{12}$/i, // JSON-pointer: https://tools.ietf.org/html/rfc6901 // uri fragment: https://tools.ietf.org/html/rfc3986#appendix-A 'json-pointer': /^(?:\/(?:[^~/]|~0|~1)*)*$/, 'json-pointer-uri-fragment': /^#(?:\/(?:[a-z0-9_\-.!$&'()*+,;:=@]|%[0-9a-f]{2}|~0|~1)*)*$/i, // relative JSON-pointer: http://tools.ietf.org/html/draft-luff-relative-json-pointer-00 'relative-json-pointer': /^(?:0|[1-9][0-9]*)(?:#|(?:\/(?:[^~/]|~0|~1)*)*)$/, // the following formats are used by the openapi specification: https://spec.openapis.org/oas/v3.0.0#data-types // byte: https://github.com/miguelmota/is-base64 byte, // signed 32 bit integer int32: { type: 'number', validate: validateInt32 }, // signed 64 bit integer int64: { type: 'number', validate: validateInt64 }, // C-type float float: { type: 'number', validate: validateNumber }, // C-type double double: { type: 'number', validate: validateNumber }, // hint to the UI to hide input strings password: true, // unchecked string payload binary: true } as const function isLeapYear(year: number): boolean { // https://tools.ietf.org/html/rfc3339#appendix-C return year % 4 === 0 && (year % 100 !== 0 || year % 400 === 0) } const DATE = /^(\d\d\d\d)-(\d\d)-(\d\d)$/ const DAYS = [0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31] function date(str: string): boolean { // full-date from http://tools.ietf.org/html/rfc3339#section-5.6 const matches: string[] | null = DATE.exec(str) if (!matches) return false const year: number = +matches[1] const month: number = +matches[2] const day: number = +matches[3] return ( month >= 1 && month <= 12 && day >= 1 && day <= (month === 2 && isLeapYear(year) ? 29 : DAYS[month]) ) } const TIME = /^(\d\d):(\d\d):(\d\d(?:\.\d+)?)(z|([+-])(\d\d)(?::?(\d\d))?)?$/i function getTime(strictTimeZone?: boolean): (str: string) => boolean { return function time(str: string): boolean { const matches: string[] | null = TIME.exec(str) if (!matches) return false const hr: number = +matches[1] const min: number = +matches[2] const sec: number = +matches[3] const tz: string | undefined = matches[4] const tzSign: number = matches[5] === '-' ? -1 : 1 const tzH: number = +(matches[6] || 0) const tzM: number = +(matches[7] || 0) if (tzH > 23 || tzM > 59 || (strictTimeZone && !tz)) return false if (hr <= 23 && min <= 59 && sec < 60) return true // leap second const utcMin = min - tzM * tzSign const utcHr = hr - tzH * tzSign - (utcMin < 0 ? 1 : 0) return ( (utcHr === 23 || utcHr === -1) && (utcMin === 59 || utcMin === -1) && sec < 61 ) } } const DATE_TIME_SEPARATOR = /t|\s/i function getDateTime(strictTimeZone?: boolean): (str: string) => boolean { const time = getTime(strictTimeZone) return function date_time(str: string): boolean { // http://tools.ietf.org/html/rfc3339#section-5.6 const dateTime: string[] = str.split(DATE_TIME_SEPARATOR) return dateTime.length === 2 && date(dateTime[0]) && time(dateTime[1]) } } const NOT_URI_FRAGMENT = /\/|:/ const URI = /^(?:[a-z][a-z0-9+\-.]*:)(?:\/?\/(?:(?:[a-z0-9\-._~!$&'()*+,;=:]|%[0-9a-f]{2})*@)?(?:\[(?:(?:(?:(?:[0-9a-f]{1,4}:){6}|::(?:[0-9a-f]{1,4}:){5}|(?:[0-9a-f]{1,4})?::(?:[0-9a-f]{1,4}:){4}|(?:(?:[0-9a-f]{1,4}:){0,1}[0-9a-f]{1,4})?::(?:[0-9a-f]{1,4}:){3}|(?:(?:[0-9a-f]{1,4}:){0,2}[0-9a-f]{1,4})?::(?:[0-9a-f]{1,4}:){2}|(?:(?:[0-9a-f]{1,4}:){0,3}[0-9a-f]{1,4})?::[0-9a-f]{1,4}:|(?:(?:[0-9a-f]{1,4}:){0,4}[0-9a-f]{1,4})?::)(?:[0-9a-f]{1,4}:[0-9a-f]{1,4}|(?:(?:25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(?:25[0-5]|2[0-4]\d|[01]?\d\d?))|(?:(?:[0-9a-f]{1,4}:){0,5}[0-9a-f]{1,4})?::[0-9a-f]{1,4}|(?:(?:[0-9a-f]{1,4}:){0,6}[0-9a-f]{1,4})?::)|[Vv][0-9a-f]+\.[a-z0-9\-._~!$&'()*+,;=:]+)\]|(?:(?:25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(?:25[0-5]|2[0-4]\d|[01]?\d\d?)|(?:[a-z0-9\-._~!$&'()*+,;=]|%[0-9a-f]{2})*)(?::\d*)?(?:\/(?:[a-z0-9\-._~!$&'()*+,;=:@]|%[0-9a-f]{2})*)*|\/(?:(?:[a-z0-9\-._~!$&'()*+,;=:@]|%[0-9a-f]{2})+(?:\/(?:[a-z0-9\-._~!$&'()*+,;=:@]|%[0-9a-f]{2})*)*)?|(?:[a-z0-9\-._~!$&'()*+,;=:@]|%[0-9a-f]{2})+(?:\/(?:[a-z0-9\-._~!$&'()*+,;=:@]|%[0-9a-f]{2})*)*)(?:\?(?:[a-z0-9\-._~!$&'()*+,;=:@/?]|%[0-9a-f]{2})*)?(?:#(?:[a-z0-9\-._~!$&'()*+,;=:@/?]|%[0-9a-f]{2})*)?$/i function uri(str: string): boolean { // http://jmrware.com/articles/2009/uri_regexp/URI_regex.html + optional protocol + required "." return NOT_URI_FRAGMENT.test(str) && URI.test(str) } const BYTE = /^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/gm function byte(str: string): boolean { BYTE.lastIndex = 0 return BYTE.test(str) } const MIN_INT32 = -(2 ** 31) const MAX_INT32 = 2 ** 31 - 1 function validateInt32(value: number): boolean { return Number.isInteger(value) && value <= MAX_INT32 && value >= MIN_INT32 } function validateInt64(value: number): boolean { // JSON and javascript max Int is 2**53, so any int that passes isInteger is valid for Int64 return Number.isInteger(value) } function validateNumber(): boolean { return true } const Z_ANCHOR = /[^\\]\\Z/ function regex(str: string): boolean { if (Z_ANCHOR.test(str)) return false try { new RegExp(str) return true } catch (e) { return false } } /** * @license * * MIT License * * Copyright (c) 2020 Evgeny Poberezkin * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ ================================================ FILE: src/index.ts ================================================ import { Memoirist } from 'memoirist' import { Kind, type TObject, type TSchema, type TModule, type TRef, type TAnySchema } from '@sinclair/typebox' import fastDecodeURIComponent from 'fast-decode-uri-component' import type { Context, PreContext } from './context' import { t } from './type-system' import { mergeInference, sucrose, type Sucrose } from './sucrose' import type { WSLocalHook } from './ws/types' import { BunAdapter } from './adapter/bun/index' import { WebStandardAdapter } from './adapter/web-standard/index' import type { ElysiaAdapter } from './adapter/types' import { env } from './universal/env' import type { ListenCallback, Serve, Server } from './universal/server' import { cloneInference, deduplicateChecksum, fnToContainer, getLoosePath, localHookToLifeCycleStore, mergeDeep, mergeSchemaValidator, PromiseGroup, promoteEvent, isNotEmpty, encodePath, lifeCycleToArray, supportPerMethodInlineHandler, redirect, emptySchema, insertStandaloneValidator } from './utils' import { getSchemaValidator, getResponseSchemaValidator, getCookieValidator, ElysiaTypeCheck, hasType, resolveSchema } from './schema' import { composeHandler, composeGeneralHandler, composeErrorHandler } from './compose' import { createTracer } from './trace' import { mergeHook, checksum, mergeLifeCycle, filterGlobalHook, asHookType, replaceUrlPath } from './utils' import { createDynamicErrorHandler, createDynamicHandler, type DynamicHandler } from './dynamic-handle' import { status, ERROR_CODE, ValidationError, type ParseError, type NotFoundError, type InternalServerError, type ElysiaCustomStatusResponse } from './error' import type { TraceHandler } from './trace' import type { ElysiaConfig, SingletonBase, DefinitionBase, Handler, ComposedHandler, InputSchema, LocalHook, AnyLocalHook, MergeSchema, RouteSchema, UnwrapRoute, InternalRoute, HTTPMethod, SchemaValidator, PreHandler, BodyHandler, OptionalHandler, ErrorHandler, LifeCycleStore, MaybePromise, Prettify, AddPrefix, AddSuffix, AddPrefixCapitalize, AddSuffixCapitalize, MaybeArray, GracefulHandler, MapResponse, Checksum, MacroManager, MacroToProperty, TransformHandler, MetadataBase, RouteBase, CreateEden, ComposeElysiaResponse, InlineHandler, HookContainer, LifeCycleType, EphemeralType, ExcludeElysiaResponse, ModelValidator, ContextAppendType, Reconcile, AfterResponseHandler, HigherOrderFunction, ResolvePath, JoinPath, ValidatorLayer, MergeElysiaInstances, Macro, MacroToContext, StandaloneValidator, GuardSchemaType, Or, DocumentDecoration, AfterHandler, NonResolvableMacroKey, StandardSchemaV1Like, ElysiaHandlerToResponseSchema, ElysiaHandlerToResponseSchemas, ExtractErrorFromHandle, ElysiaHandlerToResponseSchemaAmbiguous, GuardLocalHook, PickIfExists, SimplifyToSchema, UnionResponseStatus, CreateEdenResponse, MacroProperty, MaybeValueOrVoidFunction, IntersectIfObject, IntersectIfObjectSchema, EmptyRouteSchema, UnknownRouteSchema, MaybeFunction, InlineHandlerNonMacro, Router, } from './types' import { coercePrimitiveRoot, coerceFormData, queryCoercions, stringToStructureCoercions } from './replace-schema' export type AnyElysia = Elysia /** * ### Elysia Server * Main instance to create web server using Elysia * * --- * @example * ```typescript * import { Elysia } from 'elysia' * * new Elysia() * .get("/", () => "Hello") * .listen(3000) * ``` */ export default class Elysia< const in out BasePath extends string = '', const in out Singleton extends SingletonBase = { decorator: {} store: {} derive: {} resolve: {} }, const in out Definitions extends DefinitionBase = { typebox: {} error: {} }, const in out Metadata extends MetadataBase = { schema: {} standaloneSchema: {} macro: {} macroFn: {} parser: {} response: {} }, const in out Routes extends RouteBase = {}, // ? scoped const in out Ephemeral extends EphemeralType = { derive: {} resolve: {} schema: {} standaloneSchema: {} response: {} }, // ? local const in out Volatile extends EphemeralType = { derive: {} resolve: {} schema: {} standaloneSchema: {} response: {} } > { config: ElysiaConfig server: Server | null = null private dependencies: { [key: string]: Checksum[] } = {} '~Prefix' = '' as BasePath '~Singleton' = null as unknown as Singleton '~Definitions' = null as unknown as Definitions '~Metadata' = null as unknown as Metadata '~Ephemeral' = null as unknown as Ephemeral '~Volatile' = null as unknown as Volatile '~Routes' = null as unknown as Routes protected singleton = { decorator: {}, store: {}, derive: {}, resolve: {} } as SingletonBase get store(): Singleton['store'] { return this.singleton.store } get decorator(): Singleton['decorator'] { return this.singleton.decorator } protected definitions = { typebox: t.Module({}), type: {} as Record, error: {} as Record } protected extender = { macro: {}, higherOrderFunctions: []>[] } protected validator: ValidatorLayer = { global: null, scoped: null, local: null, getCandidate() { if (!this.global && !this.scoped && !this.local) return { body: undefined, headers: undefined, params: undefined, query: undefined, cookie: undefined, response: undefined } return mergeSchemaValidator( mergeSchemaValidator(this.global, this.scoped), this.local ) } } protected standaloneValidator: StandaloneValidator = { global: null, scoped: null, local: null } event: Partial = {} protected telemetry: | undefined | { stack: string | undefined } router = { '~http': undefined, get http() { if (!this['~http']) this['~http'] = new Memoirist({ lazy: true, onParam: fastDecodeURIComponent }) return this['~http'] }, '~dynamic': undefined, // Use in non-AOT mode get dynamic() { if (!this['~dynamic']) this['~dynamic'] = new Memoirist({ onParam: fastDecodeURIComponent }) return this['~dynamic'] }, // Static Router static: {}, // Native Static Response response: {}, history: [] } as Router protected routeTree: Record = {} get routes(): InternalRoute[] { return this.router.history } protected getGlobalRoutes(): InternalRoute[] { return this.router.history } protected getGlobalDefinitions() { return this.definitions } protected inference: Sucrose.Inference = { body: false, cookie: false, headers: false, query: false, set: false, server: false, path: false, route: false, url: false } private getServer() { return this.server } private getParent(): Elysia | null { return null } '~parser': { [K: string]: BodyHandler } = {} private _promisedModules: PromiseGroup | undefined private get promisedModules() { if (!this._promisedModules) this._promisedModules = new PromiseGroup(console.error, () => { // this.compile() }) return this._promisedModules } constructor(config: ElysiaConfig = {}) { if (config.tags) { if (!config.detail) config.detail = { tags: config.tags } else config.detail.tags = config.tags } this.config = { aot: env.ELYSIA_AOT !== 'false', nativeStaticResponse: true, encodeSchema: true, normalize: true, ...config, prefix: config.prefix ? config.prefix.charCodeAt(0) === 47 ? config.prefix : `/${config.prefix}` : (undefined as any), cookie: { path: '/', ...config?.cookie }, experimental: config?.experimental ?? {}, seed: config?.seed === undefined ? '' : config?.seed } this['~adapter'] = config.adapter ?? (typeof Bun !== 'undefined' ? BunAdapter : WebStandardAdapter) if (config?.analytic && (config?.name || config?.seed !== undefined)) this.telemetry = { stack: new Error().stack } } '~adapter': ElysiaAdapter env(model: TObject, _env = env) { const validator = getSchemaValidator(model, { modules: this.definitions.typebox, dynamic: true, additionalProperties: true, coerce: true, sanitize: () => this.config.sanitize }) if (validator.Check(_env) === false) { const error = new ValidationError('env', model, _env) throw new Error(error.all.map((x) => x.summary).join('\n')) } return this } /** * @private DO_NOT_USE_OR_YOU_WILL_BE_FIRED * @version 1.1.0 * * ! Do not use unless you know exactly what you are doing * ? Add Higher order function to Elysia.fetch */ wrap(fn: HigherOrderFunction) { this.extender.higherOrderFunctions.push({ checksum: checksum( JSON.stringify({ name: this.config.name, seed: this.config.seed, content: fn.toString() }) ), fn }) return this } get models(): { [K in keyof Definitions['typebox']]: ModelValidator< Definitions['typebox'][K] > } & { modules: | TModule> | Extract } { const models: Record> = {} for (const name of Object.keys(this.definitions.type)) models[name] = getSchemaValidator( this.definitions.typebox.Import(name as never), { models: this.definitions.type } ) // @ts-expect-error models.modules = this.definitions.typebox return models as any } private add( method: HTTPMethod, path: string, handle: Handler | any, localHook?: AnyLocalHook, options?: { allowMeta?: boolean skipPrefix?: boolean } ) { const skipPrefix = options?.skipPrefix ?? false const allowMeta = options?.allowMeta ?? false localHook ??= {} this.applyMacro(localHook) let standaloneValidators = [] as InputSchema[] if (localHook.standaloneValidator) standaloneValidators = standaloneValidators.concat( localHook.standaloneValidator ) if (this.standaloneValidator.local) standaloneValidators = standaloneValidators.concat( this.standaloneValidator.local ) if (this.standaloneValidator.scoped) standaloneValidators = standaloneValidators.concat( this.standaloneValidator.scoped ) if (this.standaloneValidator.global) standaloneValidators = standaloneValidators.concat( this.standaloneValidator.global ) if (path !== '' && path.charCodeAt(0) !== 47) path = '/' + path if (this.config.prefix && !skipPrefix) path = this.config.prefix + path if (localHook?.type) switch (localHook.type) { case 'text': localHook.type = 'text/plain' break case 'json': localHook.type = 'application/json' break case 'formdata': localHook.type = 'multipart/form-data' break case 'urlencoded': localHook.type = 'application/x-www-form-urlencoded' break case 'arrayBuffer': localHook.type = 'application/octet-stream' break default: break } const instanceValidator = this.validator.getCandidate() const cloned = { body: localHook?.body ?? (instanceValidator?.body as any), headers: localHook?.headers ?? (instanceValidator?.headers as any), params: localHook?.params ?? (instanceValidator?.params as any), query: localHook?.query ?? (instanceValidator?.query as any), cookie: localHook?.cookie ?? (instanceValidator?.cookie as any), response: localHook?.response ?? (instanceValidator?.response as any) } const shouldPrecompile = this.config.precompile === true || (typeof this.config.precompile === 'object' && this.config.precompile.compose === true) const createValidator = () => { const models = this.definitions.type const dynamic = !this.config.aot const normalize = this.config.normalize const modules = this.definitions.typebox const sanitize = () => this.config.sanitize const cookieValidator = () => { if (cloned.cookie || standaloneValidators.find((x) => x.cookie)) return getCookieValidator({ modules, validator: cloned.cookie, defaultConfig: this.config.cookie, normalize, config: cloned.cookie?.config ?? {}, dynamic, models, validators: standaloneValidators.map((x) => x.cookie), sanitize }) } return shouldPrecompile ? { body: getSchemaValidator(cloned.body, { modules, dynamic, models, normalize, additionalCoerce: (() => { const resolved = resolveSchema( cloned.body, models, modules ) // Only check for Files if resolved schema is a TypeBox schema (has Kind symbol) return resolved && Kind in resolved && (hasType('File', resolved) || hasType('Files', resolved)) ? coerceFormData() : coercePrimitiveRoot() })(), validators: standaloneValidators.map((x) => x.body), sanitize }), headers: getSchemaValidator(cloned.headers, { modules, dynamic, models, additionalProperties: true, coerce: true, additionalCoerce: stringToStructureCoercions(), validators: standaloneValidators.map( (x) => x.headers ), sanitize }), params: getSchemaValidator(cloned.params, { modules, dynamic, models, coerce: true, additionalCoerce: stringToStructureCoercions(), validators: standaloneValidators.map( (x) => x.params ), sanitize }), query: getSchemaValidator(cloned.query, { modules, dynamic, models, normalize, coerce: true, additionalCoerce: queryCoercions(), validators: standaloneValidators.map( (x) => x.query ), sanitize }), cookie: cookieValidator(), response: getResponseSchemaValidator(cloned.response, { modules, dynamic, models, normalize, validators: standaloneValidators.map( (x) => x.response as any ), sanitize }) } : ({ createBody() { if (this.body) return this.body return (this.body = getSchemaValidator( cloned.body, { modules, dynamic, models, normalize, additionalCoerce: (() => { const resolved = resolveSchema( cloned.body, models, modules ) // Only check for Files if resolved schema is a TypeBox schema (has Kind symbol) return resolved && Kind in resolved && (hasType('File', resolved) || hasType('Files', resolved)) ? coerceFormData() : coercePrimitiveRoot() })(), validators: standaloneValidators.map( (x) => x.body ), sanitize } )) }, createHeaders() { if (this.headers) return this.headers return (this.headers = getSchemaValidator( cloned.headers, { modules, dynamic, models, normalize, additionalProperties: !normalize, coerce: true, additionalCoerce: stringToStructureCoercions(), validators: standaloneValidators.map( (x) => x.headers ), sanitize } )) }, createParams() { if (this.params) return this.params return (this.params = getSchemaValidator( cloned.params, { modules, dynamic, models, normalize, coerce: true, additionalCoerce: stringToStructureCoercions(), validators: standaloneValidators.map( (x) => x.params ), sanitize } )) }, createQuery() { if (this.query) return this.query return (this.query = getSchemaValidator( cloned.query, { modules, dynamic, models, normalize, coerce: true, additionalCoerce: queryCoercions(), validators: standaloneValidators.map( (x) => x.query ), sanitize } )) }, createCookie() { if (this.cookie) return this.cookie return (this.cookie = cookieValidator()) }, createResponse() { if (this.response) return this.response return (this.response = getResponseSchemaValidator( cloned.response, { modules, dynamic, models, normalize, validators: standaloneValidators.map( (x) => x.response as any ), sanitize } )) } } as any) } if ( instanceValidator.body || instanceValidator.cookie || instanceValidator.headers || instanceValidator.params || instanceValidator.query || instanceValidator.response ) localHook = mergeHook(localHook, instanceValidator) if (localHook.tags) { if (!localHook.detail) localHook.detail = { tags: localHook.tags } else localHook.detail.tags = localHook.tags } if (isNotEmpty(this.config.detail)) localHook.detail = mergeDeep( Object.assign({}, this.config.detail!), localHook.detail ) if (path === '/ip') { // console.log(path, this.event, localHookToLifeCycleStore(localHook)) } const hooks = isNotEmpty(this.event) ? mergeHook(this.event, localHookToLifeCycleStore(localHook)) : { ...lifeCycleToArray(localHookToLifeCycleStore(localHook)) } if (standaloneValidators.length) Object.assign(hooks, { standaloneValidator: standaloneValidators }) if (this.config.aot === false) { const validator = createValidator() this.router.dynamic.add(method, path, { validator, hooks, content: localHook?.type as string, handle, route: path }) const encoded = encodePath(path, { dynamic: true }) if (path !== encoded) { this.router.dynamic.add(method, encoded, { validator, hooks, content: localHook?.type as string, handle, route: path }) } if (!this.config.strictPath) { const loosePath = getLoosePath(path) this.router.dynamic.add(method, loosePath, { validator, hooks, content: localHook?.type as string, handle, route: path }) const encoded = encodePath(loosePath) if (loosePath !== encoded) this.router.dynamic.add(method, loosePath, { validator, hooks, content: localHook?.type as string, handle, route: path }) } this.router.history.push({ method, path, composed: null, handler: handle, compile: undefined as any, hooks }) return } const adapter = this['~adapter'].handler const nativeStaticHandler = typeof handle !== 'function' ? () => { const context: PreContext = { redirect, request: this['~adapter'].isWebStandard ? new Request(`http://ely.sia${path}`, { method }) : (undefined as any as Request), server: null, set: { headers: Object.assign({}, this.setHeaders) }, status, store: this.store } try { this.event.request?.map((x) => { if (typeof x.fn === 'function') return x.fn(context) // @ts-ignore just in case if (typeof x === 'function') return x(context) }) } catch (error) { let res // @ts-ignore context.error = error this.event.error?.some((x) => { if (typeof x.fn === 'function') return (res = x.fn(context)) if (typeof x === 'function') // @ts-ignore just in case return (res = x(context)) }) if (res !== undefined) handle = res } const fn = adapter.createNativeStaticHandler?.( handle, hooks, context.set as Context['set'] ) return fn instanceof Promise ? fn.then((fn) => { if (fn) return fn }) : fn?.() } : undefined const useNativeStaticResponse = this.config.nativeStaticResponse === true const addResponsePath = (path: string) => { if (!useNativeStaticResponse || !nativeStaticHandler) return if (supportPerMethodInlineHandler) { if (this.router.response[path]) // @ts-expect-error this.router.response[path]![method] = nativeStaticHandler() else this.router.response[path] = { [method]: nativeStaticHandler() } } else this.router.response[path] = nativeStaticHandler() } addResponsePath(path) // For pre-compilation stage, eg. Cloudflare Worker let _compiled: ComposedHandler const compile = () => { if (_compiled) return _compiled const compiled = composeHandler({ app: this, path, method, hooks, validator: createValidator(), handler: typeof handle !== 'function' && typeof adapter.createStaticHandler !== 'function' ? () => handle : handle, allowMeta, inference: this.inference }) if (this.router.history[index]) _compiled = this.router.history[index].composed = compiled return compiled } let oldIndex: number | undefined if (`${method}_${path}` in this.routeTree) for (let i = 0; i < this.router.history.length; i++) { const route = this.router.history[i] if (route.path === path && route.method === method) { oldIndex = i break } } else this.routeTree[`${method}_${path}`] = this.router.history.length const index = oldIndex ?? this.router.history.length const route = this.router.history const mainHandler = shouldPrecompile ? compile() : (ctx: Context) => _compiled ? _compiled(ctx) : ( (route[index].composed = compile!()) as ComposedHandler )(ctx) if (oldIndex !== undefined) this.router.history[oldIndex] = Object.assign( { method, path, composed: mainHandler, compile, handler: handle, hooks }, standaloneValidators.length ? { standaloneValidators } : undefined, localHook.webSocket ? { websocket: localHook.websocket as any } : undefined ) else this.router.history.push( Object.assign( { method, path, composed: mainHandler, compile, handler: handle, hooks }, localHook.webSocket ? { websocket: localHook.websocket as any } : undefined ) ) const handler = { handler: shouldPrecompile ? (route[index].composed as ComposedHandler) : undefined, compile() { return (this.handler = compile!()) } } const staticRouter = this.router.static const isStaticPath = path.indexOf(':') === -1 && path.indexOf('*') === -1 if (method === 'WS') { if (isStaticPath) { if (path in staticRouter) staticRouter[path][method] = index else staticRouter[path] = { [method]: index } return } this.router.http.add('WS', path, handler) if (!this.config.strictPath) this.router.http.add('WS', getLoosePath(path), handler) const encoded = encodePath(path, { dynamic: true }) if (path !== encoded) this.router.http.add('WS', encoded, handler) // Static path doesn't need encode as it's done in compilation process return } if (isStaticPath) { if (path in staticRouter) staticRouter[path][method] = index else staticRouter[path] = { [method]: index } as const if (!this.config.strictPath) addResponsePath(getLoosePath(path)) // Static path doesn't need encode as it's done in compilation process } else { this.router.http.add(method, path, handler) if (!this.config.strictPath) { const loosePath = getLoosePath(path) addResponsePath(loosePath) this.router.http.add(method, loosePath, handler) } const encoded = encodePath(path, { dynamic: true }) if (path !== encoded) { this.router.http.add(method, encoded, handler) addResponsePath(encoded) } } } private setHeaders?: Context['set']['headers'] headers(header: Context['set']['headers'] | undefined) { if (!header) return this if (!this.setHeaders) this.setHeaders = {} this.setHeaders = mergeDeep(this.setHeaders, header) return this } /** * ### start | Life cycle event * Called after server is ready for serving * * --- * @example * ```typescript * new Elysia() * .onStart(({ server }) => { * console.log("Running at ${server?.url}:${server?.port}") * }) * .listen(3000) * ``` */ onStart(handler: MaybeArray>) { this.on('start', handler as any) return this } /** * ### request | Life cycle event * Called on every new request is accepted * * --- * @example * ```typescript * new Elysia() * .onRequest(({ method, url }) => { * saveToAnalytic({ method, url }) * }) * ``` */ onRequest< const Schema extends RouteSchema, const Handler extends PreHandler< MergeSchema< Schema, MergeSchema< Volatile['schema'], MergeSchema > > & Metadata['standaloneSchema'] & Ephemeral['standaloneSchema'] & Volatile['standaloneSchema'], { decorator: Singleton['decorator'] store: Singleton['store'] derive: {} resolve: {} } > >( handler: Handler ): Elysia< BasePath, Singleton, Definitions, Metadata, Routes, Ephemeral, { derive: Volatile['derive'] resolve: Volatile['resolve'] schema: Volatile['schema'] standaloneSchema: Volatile['standaloneSchema'] response: UnionResponseStatus< Volatile['response'], ElysiaHandlerToResponseSchema > } > /** * ### request | Life cycle event * Called on every new request is accepted * * --- * @example * ```typescript * new Elysia() * .onRequest(({ method, url }) => { * saveToAnalytic({ method, url }) * }) * ``` */ onRequest< const Schema extends RouteSchema, const Handlers extends PreHandler< MergeSchema< Schema, MergeSchema< Volatile['schema'], MergeSchema > > & Metadata['standaloneSchema'] & Ephemeral['standaloneSchema'] & Volatile['standaloneSchema'], { decorator: Singleton['decorator'] store: Singleton['store'] derive: {} resolve: {} } >[] >( handler: Handlers ): Elysia< BasePath, Singleton, Definitions, Metadata, Routes, Ephemeral, { derive: Volatile['derive'] resolve: Volatile['resolve'] schema: Volatile['schema'] standaloneSchema: Volatile['standaloneSchema'] response: UnionResponseStatus< Volatile['response'], ElysiaHandlerToResponseSchema > } > onRequest(handler: MaybeArray): any { this.on('request', handler as any) return this as any } /** * ### parse | Life cycle event * Callback function to handle body parsing * * If truthy value is returned, will be assigned to `context.body` * Otherwise will skip the callback and look for the next one. * * Equivalent to Express's body parser * * --- * @example * ```typescript * new Elysia() * .onParse((request, contentType) => { * if(contentType === "application/json") * return request.json() * }) * ``` */ onParse( parser: MaybeArray< BodyHandler< MergeSchema< Schema, MergeSchema< Volatile['schema'], MergeSchema >, BasePath > & Metadata['standaloneSchema'] & Ephemeral['standaloneSchema'] & Volatile['standaloneSchema'], { decorator: Singleton['decorator'] store: Singleton['store'] derive: Singleton['derive'] & Ephemeral['derive'] & Volatile['derive'] resolve: {} } > > ): this /** * ### parse | Life cycle event * Callback function to handle body parsing * * If truthy value is returned, will be assigned to `context.body` * Otherwise will skip the callback and look for the next one. * * Equivalent to Express's body parser * * --- * @example * ```typescript * new Elysia() * .onParse((request, contentType) => { * if(contentType === "application/json") * return request.json() * }) * ``` */ onParse( options: { as: Type }, parser: MaybeArray< BodyHandler< MergeSchema< Schema, MergeSchema< Volatile['schema'], MergeSchema >, BasePath > & Metadata['standaloneSchema'] & Ephemeral['standaloneSchema'] & Volatile['standaloneSchema'] & 'global' extends Type ? { params: { [name: string]: string | undefined } } : 'scoped' extends Type ? { params: { [name: string]: string | undefined } } : {}, 'global' extends Type ? { decorator: Singleton['decorator'] store: Singleton['store'] derive: Singleton['derive'] & Partial< Ephemeral['derive'] & Volatile['derive'] > resolve: {} } : 'scoped' extends Type ? { decorator: Singleton['decorator'] store: Singleton['store'] derive: Singleton['derive'] & Ephemeral['derive'] & Partial resolve: {} } : { decorator: Singleton['decorator'] store: Singleton['store'] derive: Singleton['derive'] & Ephemeral['derive'] & Volatile['derive'] resolve: {} } > > ): this onParse( parser: Parsers ): this onParse( options: { as: LifeCycleType } | MaybeArray | string, handler?: MaybeArray ): unknown { if (!handler) { if (typeof options === 'string') return this.on('parse', this['~parser'][options] as any) return this.on('parse', options as any) } return this.on( options as { as: LifeCycleType }, 'parse', handler as any ) } /** * ### parse | Life cycle event * Callback function to handle body parsing * * If truthy value is returned, will be assigned to `context.body` * Otherwise will skip the callback and look for the next one. * * Equivalent to Express's body parser * * --- * @example * ```typescript * new Elysia() * .onParse((request, contentType) => { * if(contentType === "application/json") * return request.json() * }) * ``` */ parser< const Parser extends string, const Schema extends RouteSchema, const Handler extends BodyHandler< MergeSchema< Schema, MergeSchema< Volatile['schema'], MergeSchema >, BasePath > & Metadata['standaloneSchema'] & Ephemeral['standaloneSchema'] & Volatile['standaloneSchema'], { decorator: Singleton['decorator'] store: Singleton['store'] derive: Singleton['derive'] & Ephemeral['derive'] & Volatile['derive'] resolve: {} } > >( name: Parser, parser: Handler ): Elysia< BasePath, Singleton, Definitions, { schema: Metadata['schema'] standaloneSchema: Metadata['standaloneSchema'] macro: Metadata['macro'] macroFn: Metadata['macroFn'] parser: Metadata['parser'] & { [K in Parser]: Handler } response: Metadata['response'] }, Routes, Ephemeral, Volatile > { this['~parser'][name] = parser as any return this as any } /** * ### transform | Life cycle event * Assign or transform anything related to context before validation. * * --- * @example * ```typescript * new Elysia() * .onTransform(({ params }) => { * if(params.id) * params.id = +params.id * }) * ``` */ onTransform( handler: MaybeArray< TransformHandler< UnknownRouteSchema>, { decorator: Singleton['decorator'] store: Singleton['store'] derive: Singleton['derive'] & Ephemeral['derive'] & Volatile['derive'] resolve: {} } > > ): this /** * ### transform | Life cycle event * Assign or transform anything related to context before validation. * * --- * @example * ```typescript * new Elysia() * .onTransform(({ params }) => { * if(params.id) * params.id = +params.id * }) * ``` */ onTransform< const Schema extends RouteSchema, const Type extends LifeCycleType >( options: { as: Type }, handler: MaybeArray< TransformHandler< UnknownRouteSchema< 'global' extends Type ? { [name: string]: string | undefined } : 'scoped' extends Type ? { [name: string]: string | undefined } : ResolvePath >, 'global' extends Type ? { decorator: Singleton['decorator'] store: Singleton['store'] derive: Singleton['derive'] & Ephemeral['derive'] & Volatile['derive'] resolve: {} } : 'scoped' extends Type ? { decorator: Singleton['decorator'] store: Singleton['store'] derive: Singleton['derive'] & Ephemeral['derive'] & Partial resolve: {} } : { decorator: Singleton['decorator'] store: Singleton['store'] derive: Singleton['derive'] & Partial< Ephemeral['derive'] & Volatile['derive'] > resolve: {} } > > ): this onTransform( options: { as: LifeCycleType } | MaybeArray, handler?: MaybeArray ) { if (!handler) return this.on('transform', options as any) return this.on( options as { as: LifeCycleType }, 'transform', handler as any ) } /** * Derive new property for each request with access to `Context`. * * If error is thrown, the scope will skip to handling error instead. * * --- * @example * new Elysia() * .state('counter', 1) * .derive(({ store }) => ({ * increase() { * store.counter++ * } * })) */ resolve< const Resolver extends | Record | ElysiaCustomStatusResponse, const Type extends LifeCycleType >( options: { as: Type }, resolver: ( context: Context< MergeSchema< Volatile['schema'], MergeSchema, BasePath > & Metadata['standaloneSchema'] & Ephemeral['standaloneSchema'] & Volatile['standaloneSchema'] & 'global' extends Type ? { params: { [name: string]: string | undefined } } : 'scoped' extends Type ? { params: { [name: string]: string | undefined } } : {}, Singleton & ('global' extends Type ? { derive: Partial< Ephemeral['derive'] & Volatile['derive'] > resolve: Partial< Ephemeral['resolve'] & Volatile['resolve'] > } : 'scoped' extends Type ? { derive: Ephemeral['derive'] & Partial resolve: Ephemeral['resolve'] & Partial } : { derive: Ephemeral['derive'] & Volatile['derive'] resolve: Ephemeral['resolve'] & Volatile['resolve'] }) > ) => MaybePromise ): Type extends 'global' ? Elysia< BasePath, { decorator: Singleton['decorator'] store: Singleton['store'] derive: Singleton['derive'] resolve: Singleton['resolve'] & ExcludeElysiaResponse }, Definitions, { schema: Metadata['schema'] standaloneSchema: Metadata['standaloneSchema'] macro: Metadata['macro'] macroFn: Metadata['macroFn'] parser: Metadata['parser'] response: UnionResponseStatus< Metadata['response'], ExtractErrorFromHandle > }, Routes, Ephemeral, Volatile > : Type extends 'scoped' ? Elysia< BasePath, Singleton, Definitions, Metadata, Routes, { derive: Ephemeral['derive'] resolve: Ephemeral['resolve'] & ExcludeElysiaResponse schema: Ephemeral['schema'] standaloneSchema: Ephemeral['standaloneSchema'] response: UnionResponseStatus< Ephemeral['response'], ExtractErrorFromHandle > }, Volatile > : Elysia< BasePath, Singleton, Definitions, Metadata, Routes, Ephemeral, { derive: Volatile['derive'] resolve: Volatile['resolve'] & ExcludeElysiaResponse schema: Volatile['schema'] standaloneSchema: Volatile['standaloneSchema'] response: UnionResponseStatus< Volatile['response'], ExtractErrorFromHandle > } > /** * Derive new property for each request with access to `Context`. * * If error is thrown, the scope will skip to handling error instead. * * --- * @example * new Elysia() * .state('counter', 1) * .derive(({ store }) => ({ * increase() { * store.counter++ * } * })) */ resolve< const Resolver extends | Record | ElysiaCustomStatusResponse | void >( resolver: ( context: Context< MergeSchema< Volatile['schema'], MergeSchema, BasePath > & Metadata['standaloneSchema'] & Ephemeral['standaloneSchema'] & Volatile['standaloneSchema'], Singleton & { derive: Ephemeral['derive'] & Volatile['derive'] resolve: Ephemeral['resolve'] & Volatile['resolve'] }, BasePath > ) => MaybePromise ): Elysia< BasePath, Singleton, Definitions, Metadata, Routes, Ephemeral, { derive: Volatile['derive'] resolve: Volatile['resolve'] & ExcludeElysiaResponse schema: Volatile['schema'] standaloneSchema: Volatile['standaloneSchema'] response: UnionResponseStatus< Volatile['response'], ExtractErrorFromHandle > } > resolve( optionsOrResolve: { as: LifeCycleType } | Function, resolve?: Function ) { if (!resolve) { resolve = optionsOrResolve as any optionsOrResolve = { as: 'local' } } const hook: HookContainer = { subType: 'resolve', fn: resolve! } return this.onBeforeHandle(optionsOrResolve as any, hook as any) as any } mapResolve< const NewResolver extends | Record | ElysiaCustomStatusResponse >( mapper: ( context: Context< MergeSchema< Metadata['schema'], MergeSchema > & Metadata['standaloneSchema'] & Ephemeral['standaloneSchema'] & Volatile['standaloneSchema'], Singleton & { derive: Ephemeral['derive'] & Volatile['derive'] resolve: Ephemeral['resolve'] & Volatile['resolve'] }, BasePath > ) => MaybePromise ): Elysia< BasePath, Singleton, Definitions, Metadata, Routes, Ephemeral, { derive: Volatile['derive'] resolve: ExcludeElysiaResponse schema: Volatile['schema'] standaloneSchema: Volatile['standaloneSchema'] response: UnionResponseStatus< Volatile['response'], ExtractErrorFromHandle > } > mapResolve< const NewResolver extends | Record | ElysiaCustomStatusResponse, const Type extends LifeCycleType >( options: { as: Type }, mapper: ( context: Context< MergeSchema< Metadata['schema'], MergeSchema > & Metadata['standaloneSchema'] & Ephemeral['standaloneSchema'] & Volatile['standaloneSchema'], Singleton & ('global' extends Type ? { derive: Partial< Ephemeral['derive'] & Volatile['derive'] > resolve: Partial< Ephemeral['resolve'] & Volatile['resolve'] > } : 'scoped' extends Type ? { derive: Ephemeral['derive'] & Partial resolve: Ephemeral['resolve'] & Partial } : { derive: Ephemeral['derive'] & Volatile['derive'] resolve: Ephemeral['resolve'] & Volatile['resolve'] }) > ) => MaybePromise ): Type extends 'global' ? Elysia< BasePath, { decorator: Singleton['decorator'] store: Singleton['store'] derive: Singleton['derive'] resolve: ExcludeElysiaResponse }, Definitions, { schema: Metadata['schema'] standaloneSchema: Metadata['standaloneSchema'] macro: Metadata['macro'] macroFn: Metadata['macroFn'] parser: Metadata['parser'] response: UnionResponseStatus< Metadata['response'], ExtractErrorFromHandle > }, Routes, Ephemeral, Volatile > : Type extends 'scoped' ? Elysia< BasePath, Singleton, Definitions, Metadata, Routes, { derive: Ephemeral['derive'] resolve: Ephemeral['resolve'] & ExcludeElysiaResponse schema: Ephemeral['schema'] standaloneSchema: Ephemeral['standaloneSchema'] response: UnionResponseStatus< Ephemeral['response'], ExtractErrorFromHandle > }, Volatile > : Elysia< BasePath, Singleton, Definitions, Metadata, Routes, Ephemeral, { derive: Volatile['derive'] resolve: Volatile['resolve'] & ExcludeElysiaResponse schema: Volatile['schema'] standaloneSchema: Volatile['standaloneSchema'] response: UnionResponseStatus< Volatile['response'], ExtractErrorFromHandle > } > mapResolve( optionsOrResolve: Function | { as: LifeCycleType }, mapper?: Function ) { if (!mapper) { mapper = optionsOrResolve as any optionsOrResolve = { as: 'local' } } const hook: HookContainer = { subType: 'mapResolve', fn: mapper! } return this.onBeforeHandle(optionsOrResolve as any, hook as any) as any } /** * ### Before Handle | Life cycle event * Execute after validation and before the main route handler. * * If truthy value is returned, will be assigned as `Response` and skip the main handler * * --- * @example * ```typescript * new Elysia() * .onBeforeHandle(({ params: { id }, status }) => { * if(id && !isExisted(id)) { * status(401) * * return "Unauthorized" * } * }) * ``` */ onBeforeHandle< const Schema extends RouteSchema, const Handler extends OptionalHandler< MergeSchema< Schema, MergeSchema< Volatile['schema'], MergeSchema >, BasePath > & Metadata['standaloneSchema'] & Ephemeral['standaloneSchema'] & Volatile['standaloneSchema'], Singleton & { derive: Ephemeral['derive'] & Volatile['derive'] resolve: Ephemeral['resolve'] & Volatile['resolve'] } > >( handler: Handler ): Elysia< BasePath, Singleton, Definitions, Metadata, Routes, Ephemeral, { derive: Volatile['derive'] resolve: Volatile['resolve'] schema: Volatile['schema'] standaloneSchema: Volatile['standaloneSchema'] response: UnionResponseStatus< Volatile['response'], ElysiaHandlerToResponseSchema > } > /** * ### Before Handle | Life cycle event * Execute after validation and before the main route handler. * * If truthy value is returned, will be assigned as `Response` and skip the main handler * * --- * @example * ```typescript * new Elysia() * .onBeforeHandle(({ params: { id }, status }) => { * if(id && !isExisted(id)) { * status(401) * * return "Unauthorized" * } * }) * ``` */ onBeforeHandle< const Schema extends RouteSchema, const Handlers extends OptionalHandler< MergeSchema< Schema, MergeSchema< Volatile['schema'], MergeSchema >, BasePath > & Metadata['standaloneSchema'] & Ephemeral['standaloneSchema'] & Volatile['standaloneSchema'], Singleton & { derive: Ephemeral['derive'] & Volatile['derive'] resolve: Ephemeral['resolve'] & Volatile['resolve'] } >[] >( handlers: Handlers ): Elysia< BasePath, Singleton, Definitions, Metadata, Routes, Ephemeral, { derive: Volatile['derive'] resolve: Volatile['resolve'] schema: Volatile['schema'] standaloneSchema: Volatile['standaloneSchema'] response: UnionResponseStatus< Volatile['response'], ElysiaHandlerToResponseSchemas > } > /** * ### Before Handle | Life cycle event * Execute after validation and before the main route handler. * * If truthy value is returned, will be assigned as `Response` and skip the main handler * * --- * @example * ```typescript * new Elysia() * .onBeforeHandle(({ params: { id }, status }) => { * if(id && !isExisted(id)) { * status(401) * * return "Unauthorized" * } * }) * ``` */ onBeforeHandle< const Schema extends RouteSchema, const Type extends LifeCycleType, const Handler extends OptionalHandler< MergeSchema< Schema, MergeSchema< Volatile['schema'], MergeSchema >, BasePath > & Metadata['standaloneSchema'] & Ephemeral['standaloneSchema'] & Volatile['standaloneSchema'] & 'global' extends Type ? { params: { [name: string]: string | undefined } } : 'scoped' extends Type ? { params: { [name: string]: string | undefined } } : {}, Singleton & ('global' extends Type ? { derive: Partial< Ephemeral['derive'] & Volatile['derive'] > resolve: Partial< Ephemeral['resolve'] & Volatile['resolve'] > } : 'scoped' extends Type ? { derive: Ephemeral['derive'] & Partial resolve: Ephemeral['resolve'] & Partial } : { derive: Ephemeral['derive'] & Volatile['derive'] resolve: Ephemeral['resolve'] & Volatile['resolve'] }), BasePath > >( options: { as: Type }, handler: Handler ): Type extends 'global' ? Elysia< BasePath, Singleton, Definitions, { schema: Metadata['schema'] standaloneSchema: Metadata['standaloneSchema'] macro: Metadata['macro'] macroFn: Metadata['macroFn'] parser: Metadata['parser'] response: UnionResponseStatus< Metadata['response'], ElysiaHandlerToResponseSchema > }, Routes, Ephemeral, Volatile > : Type extends 'scoped' ? Elysia< BasePath, Singleton, Definitions, Metadata, Routes, { derive: Ephemeral['derive'] resolve: Ephemeral['resolve'] schema: Ephemeral['schema'] standaloneSchema: Ephemeral['standaloneSchema'] response: UnionResponseStatus< Ephemeral['response'], ElysiaHandlerToResponseSchema > }, Volatile > : Elysia< BasePath, Singleton, Definitions, Metadata, Routes, Ephemeral, { derive: Volatile['derive'] resolve: Volatile['resolve'] schema: Volatile['schema'] standaloneSchema: Volatile['standaloneSchema'] response: UnionResponseStatus< Volatile['response'], ElysiaHandlerToResponseSchema > } > /** * ### Before Handle | Life cycle event * Execute after validation and before the main route handler. * * If truthy value is returned, will be assigned as `Response` and skip the main handler * * --- * @example * ```typescript * new Elysia() * .onBeforeHandle(({ params: { id }, status }) => { * if(id && !isExisted(id)) { * status(401) * * return "Unauthorized" * } * }) * ``` */ onBeforeHandle< const Schema extends RouteSchema, const Type extends LifeCycleType, const Handlers extends OptionalHandler< MergeSchema< Schema, MergeSchema< Volatile['schema'], MergeSchema >, BasePath > & Metadata['standaloneSchema'] & Ephemeral['standaloneSchema'] & Volatile['standaloneSchema'] & 'global' extends Type ? { params: { [name: string]: string | undefined } } : 'scoped' extends Type ? { params: { [name: string]: string | undefined } } : {}, Singleton & ('global' extends Type ? { derive: Partial< Ephemeral['derive'] & Volatile['derive'] > resolve: Partial< Ephemeral['resolve'] & Volatile['resolve'] > } : 'scoped' extends Type ? { derive: Ephemeral['derive'] & Partial resolve: Ephemeral['resolve'] & Partial } : { derive: Ephemeral['derive'] & Volatile['derive'] resolve: Ephemeral['resolve'] & Volatile['resolve'] }), BasePath >[] >( options: { as: Type }, handlers: Handlers ): Type extends 'global' ? Elysia< BasePath, Singleton, Definitions, { schema: Metadata['schema'] standaloneSchema: Metadata['standaloneSchema'] macro: Metadata['macro'] macroFn: Metadata['macroFn'] parser: Metadata['parser'] response: UnionResponseStatus< Metadata['response'], ElysiaHandlerToResponseSchemas > }, Routes, Ephemeral, Volatile > : Type extends 'scoped' ? Elysia< BasePath, Singleton, Definitions, Metadata, Routes, { derive: Ephemeral['derive'] resolve: Ephemeral['resolve'] schema: Ephemeral['schema'] standaloneSchema: Ephemeral['standaloneSchema'] response: UnionResponseStatus< Ephemeral['response'], ElysiaHandlerToResponseSchemas > }, Volatile > : Elysia< BasePath, Singleton, Definitions, Metadata, Routes, Ephemeral, { derive: Volatile['derive'] resolve: Volatile['resolve'] schema: Volatile['schema'] standaloneSchema: Volatile['standaloneSchema'] response: UnionResponseStatus< Volatile['response'], ElysiaHandlerToResponseSchemas > } > onBeforeHandle( options: { as: LifeCycleType } | MaybeArray, handler?: MaybeArray ): any { if (!handler) return this.on('beforeHandle', options as any) return this.on( options as { as: LifeCycleType }, 'beforeHandle', handler as any ) } /** * ### After Handle | Life cycle event * Intercept request **after** main handler is called. * * If truthy value is returned, will be assigned as `Response` * * --- * @example * ```typescript * new Elysia() * .onAfterHandle((context, response) => { * if(typeof response === "object") * return JSON.stringify(response) * }) * ``` */ onAfterHandle< const Schema extends RouteSchema, const Handler extends AfterHandler< MergeSchema< Schema, MergeSchema< Volatile['schema'], MergeSchema >, BasePath > & Metadata['standaloneSchema'] & Ephemeral['standaloneSchema'] & Volatile['standaloneSchema'], Singleton & { derive: Ephemeral['derive'] & Volatile['derive'] resolve: Ephemeral['resolve'] & Volatile['resolve'] } > >( handler: Handler ): Elysia< BasePath, Singleton, Definitions, Metadata, Routes, Ephemeral, { derive: Volatile['derive'] resolve: Volatile['resolve'] schema: Volatile['schema'] standaloneSchema: Volatile['standaloneSchema'] response: UnionResponseStatus< Volatile['response'], ElysiaHandlerToResponseSchema > } > /** * ### After Handle | Life cycle event * Intercept request **after** main handler is called. * * If truthy value is returned, will be assigned as `Response` * * --- * @example * ```typescript * new Elysia() * .onAfterHandle((context, response) => { * if(typeof response === "object") * return JSON.stringify(response) * }) * ``` */ onAfterHandle< const Schema extends RouteSchema, const Handlers extends AfterHandler< MergeSchema< Schema, MergeSchema< Volatile['schema'], MergeSchema >, BasePath > & Metadata['standaloneSchema'] & Ephemeral['standaloneSchema'] & Volatile['standaloneSchema'], Singleton & { derive: Ephemeral['derive'] & Volatile['derive'] resolve: Ephemeral['resolve'] & Volatile['resolve'] } >[] >( handlers: Handlers ): Elysia< BasePath, Singleton, Definitions, Metadata, Routes, Ephemeral, { derive: Volatile['derive'] resolve: Volatile['resolve'] schema: Volatile['schema'] standaloneSchema: Volatile['standaloneSchema'] response: UnionResponseStatus< Volatile['response'], ElysiaHandlerToResponseSchemas > } > /** * ### After Handle | Life cycle event * Intercept request **after** main handler is called. * * If truthy value is returned, will be assigned as `Response` * * --- * @example * ```typescript * new Elysia() * .onAfterHandle((context, response) => { * if(typeof response === "object") * return JSON.stringify(response) * }) * ``` */ onAfterHandle< const Schema extends RouteSchema, const Type extends LifeCycleType, const Handler extends AfterHandler< MergeSchema< Schema, MergeSchema< Volatile['schema'], MergeSchema >, BasePath > & Metadata['standaloneSchema'] & Ephemeral['standaloneSchema'] & Volatile['standaloneSchema'] & 'global' extends Type ? { params: { [name: string]: string | undefined } } : 'scoped' extends Type ? { params: { [name: string]: string | undefined } } : {}, Singleton & ('global' extends Type ? { derive: Partial< Ephemeral['derive'] & Volatile['derive'] > resolve: Partial< Ephemeral['resolve'] & Volatile['resolve'] > } : 'scoped' extends Type ? { derive: Ephemeral['derive'] & Partial resolve: Ephemeral['resolve'] & Partial } : { derive: Ephemeral['derive'] & Volatile['derive'] resolve: Ephemeral['resolve'] & Volatile['resolve'] }) > >( options: { as: Type }, handler: Handler ): Type extends 'global' ? Elysia< BasePath, Singleton, Definitions, { schema: Metadata['schema'] standaloneSchema: Metadata['standaloneSchema'] macro: Metadata['macro'] macroFn: Metadata['macroFn'] parser: Metadata['parser'] response: UnionResponseStatus< Metadata['response'], ElysiaHandlerToResponseSchema > }, Routes, Ephemeral, Volatile > : Type extends 'scoped' ? Elysia< BasePath, Singleton, Definitions, Metadata, Routes, { derive: Ephemeral['derive'] resolve: Ephemeral['resolve'] schema: Ephemeral['schema'] standaloneSchema: Ephemeral['standaloneSchema'] response: UnionResponseStatus< Ephemeral['response'], ElysiaHandlerToResponseSchema > }, Volatile > : Elysia< BasePath, Singleton, Definitions, Metadata, Routes, Ephemeral, { derive: Volatile['derive'] resolve: Volatile['resolve'] schema: Volatile['schema'] standaloneSchema: Volatile['standaloneSchema'] response: UnionResponseStatus< Volatile['response'], ElysiaHandlerToResponseSchema > } > /** * ### After Handle | Life cycle event * Intercept request **after** main handler is called. * * If truthy value is returned, will be assigned as `Response` * * --- * @example * ```typescript * new Elysia() * .onAfterHandle((context, response) => { * if(typeof response === "object") * return JSON.stringify(response) * }) * ``` */ onAfterHandle< const Schema extends RouteSchema, const Type extends LifeCycleType, const Handlers extends AfterHandler< MergeSchema< Schema, MergeSchema< Volatile['schema'], MergeSchema >, BasePath > & Metadata['standaloneSchema'] & Ephemeral['standaloneSchema'] & Volatile['standaloneSchema'] & 'global' extends Type ? { params: { [name: string]: string | undefined } } : 'scoped' extends Type ? { params: { [name: string]: string | undefined } } : {}, Singleton & ('global' extends Type ? { derive: Partial< Ephemeral['derive'] & Volatile['derive'] > resolve: Partial< Ephemeral['resolve'] & Volatile['resolve'] > } : 'scoped' extends Type ? { derive: Ephemeral['derive'] & Partial resolve: Ephemeral['resolve'] & Partial } : { derive: Ephemeral['derive'] & Volatile['derive'] resolve: Ephemeral['resolve'] & Volatile['resolve'] }) >[] >( options: { as: Type }, handler: Handlers ): Type extends 'global' ? Elysia< BasePath, Singleton, Definitions, { schema: Metadata['schema'] standaloneSchema: Metadata['standaloneSchema'] macro: Metadata['macro'] macroFn: Metadata['macroFn'] parser: Metadata['parser'] response: UnionResponseStatus< Metadata['response'], ElysiaHandlerToResponseSchemas > }, Routes, Ephemeral, Volatile > : Type extends 'scoped' ? Elysia< BasePath, Singleton, Definitions, Metadata, Routes, { derive: Ephemeral['derive'] resolve: Ephemeral['resolve'] schema: Ephemeral['schema'] standaloneSchema: Ephemeral['standaloneSchema'] response: UnionResponseStatus< Ephemeral['response'], ElysiaHandlerToResponseSchemas > }, Volatile > : Elysia< BasePath, Singleton, Definitions, Metadata, Routes, Ephemeral, { derive: Volatile['derive'] resolve: Volatile['resolve'] schema: Volatile['schema'] standaloneSchema: Volatile['standaloneSchema'] response: UnionResponseStatus< Volatile['response'], ElysiaHandlerToResponseSchemas > } > onAfterHandle( options: { as: LifeCycleType } | MaybeArray, handler?: MaybeArray ): any { if (!handler) return this.on('afterHandle', options as any) return this.on( options as { as: LifeCycleType }, 'afterHandle', handler as any ) } /** * ### After Handle | Life cycle event * Intercept request **after** main handler is called. * * If truthy value is returned, will be assigned as `Response` * * --- * @example * ```typescript * new Elysia() * .mapResponse((context, response) => { * if(typeof response === "object") * return JSON.stringify(response) * }) * ``` */ mapResponse( handler: MaybeArray< MapResponse< MergeSchema< Schema, MergeSchema< Volatile['schema'], MergeSchema >, BasePath > & Metadata['standaloneSchema'] & Ephemeral['standaloneSchema'] & Volatile['standaloneSchema'], Singleton & { derive: Ephemeral['derive'] & Volatile['derive'] resolve: Ephemeral['resolve'] & Volatile['resolve'] } > > ): this /** * ### After Handle | Life cycle event * Intercept request **after** main handler is called. * * If truthy value is returned, will be assigned as `Response` * * --- * @example * ```typescript * new Elysia() * .mapResponse((context, response) => { * if(typeof response === "object") * return JSON.stringify(response) * }) * ``` */ mapResponse( options: { as: Type }, handler: MaybeArray< MapResponse< MergeSchema< Schema, MergeSchema< Volatile['schema'], MergeSchema >, BasePath > & Metadata['standaloneSchema'] & Ephemeral['standaloneSchema'] & Volatile['standaloneSchema'] & 'global' extends Type ? { params: { [name: string]: string | undefined } } : 'scoped' extends Type ? { params: { [name: string]: string | undefined } } : {}, Singleton & ('global' extends Type ? { derive: Partial< Ephemeral['derive'] & Volatile['derive'] > resolve: Partial< Ephemeral['resolve'] & Volatile['resolve'] > } : 'scoped' extends Type ? { derive: Ephemeral['derive'] & Partial resolve: Ephemeral['resolve'] & Partial } : { derive: Ephemeral['derive'] & Volatile['derive'] resolve: Ephemeral['resolve'] & Volatile['resolve'] }) > > ): this mapResponse( options: { as: LifeCycleType } | MaybeArray, handler?: MaybeArray ) { if (!handler) return this.on('mapResponse', options as any) return this.on( options as { as: LifeCycleType }, 'mapResponse', handler as any ) } /** * ### response | Life cycle event * Call AFTER main handler is executed * Good for analytic metrics * --- * @example * ```typescript * new Elysia() * .onAfterResponse(() => { * cleanup() * }) * ``` */ onAfterResponse( handler: MaybeArray< AfterResponseHandler< MergeSchema< Schema, MergeSchema< Volatile['schema'], MergeSchema >, BasePath > & Metadata['standaloneSchema'] & Ephemeral['standaloneSchema'] & Volatile['standaloneSchema'], Singleton & { derive: Ephemeral['derive'] & Volatile['derive'] resolve: Ephemeral['resolve'] & Volatile['resolve'] } > > ): this /** * ### response | Life cycle event * Call AFTER main handler is executed * Good for analytic metrics * * --- * @example * ```typescript * new Elysia() * .onAfterResponse(() => { * cleanup() * }) * ``` */ onAfterResponse< const Schema extends RouteSchema, const Type extends LifeCycleType >( options: { as: Type }, handler: MaybeArray< AfterResponseHandler< MergeSchema< Schema, MergeSchema< Volatile['schema'], MergeSchema >, BasePath > & Metadata['standaloneSchema'] & Ephemeral['standaloneSchema'] & Volatile['standaloneSchema'] & 'global' extends Type ? { params: { [name: string]: string | undefined } } : 'scoped' extends Type ? { params: { [name: string]: string | undefined } } : {}, Singleton & ('global' extends Type ? { derive: Partial< Ephemeral['derive'] & Volatile['derive'] > resolve: Partial< Ephemeral['resolve'] & Volatile['resolve'] > } : 'scoped' extends Type ? { derive: Ephemeral['derive'] & Partial resolve: Ephemeral['resolve'] & Partial } : { derive: Ephemeral['derive'] & Volatile['derive'] resolve: Ephemeral['resolve'] & Volatile['resolve'] }) > > ): this onAfterResponse( options: { as: LifeCycleType } | MaybeArray, handler?: MaybeArray ) { if (!handler) return this.on('afterResponse', options as any) return this.on( options as { as: LifeCycleType }, 'afterResponse', handler as any ) } /** * ### After Handle | Life cycle event * Intercept request **after** main handler is called. * * If truthy value is returned, will be assigned as `Response` * * --- * @example * ```typescript * new Elysia() * .onAfterHandle((context, response) => { * if(typeof response === "object") * return JSON.stringify(response) * }) * ``` */ trace( handler: MaybeArray> ): this /** * ### After Handle | Life cycle event * Intercept request **after** main handler is called. * * If truthy value is returned, will be assigned as `Response` * * --- * @example * ```typescript * new Elysia() * .onAfterHandle((context, response) => { * if(typeof response === "object") * return JSON.stringify(response) * }) * ``` */ trace( options: { as: LifeCycleType }, handler: MaybeArray> ): this /** * ### After Handle | Life cycle event * Intercept request **after** main handler is called. * * If truthy value is returned, will be assigned as `Response` * * --- * @example * ```typescript * new Elysia() * .onAfterHandle((context, response) => { * if(typeof response === "object") * return JSON.stringify(response) * }) * ``` */ trace( options: { as: LifeCycleType } | MaybeArray, handler?: MaybeArray ) { if (!handler) { handler = options as MaybeArray options = { as: 'local' } } if (!Array.isArray(handler)) handler = [handler] as Function[] for (const fn of handler) this.on( options as { as: LifeCycleType }, 'trace', createTracer(fn as any) as any ) return this } /** * Register errors * * --- * @example * ```typescript * class CustomError extends Error { * constructor() { * super() * } * } * * new Elysia() * .error('CUSTOM_ERROR', CustomError) * ``` */ error< const Errors extends Record< string, { prototype: Error } > >( errors: Errors ): Elysia< BasePath, Singleton, { typebox: Definitions['typebox'] error: Definitions['error'] & { [K in keyof Errors]: Errors[K] extends { prototype: infer LiteralError extends Error } ? LiteralError : Errors[K] } }, Metadata, Routes, Ephemeral, Volatile > /** * Register errors * * --- * @example * ```typescript * class CustomError extends Error { * constructor() { * super() * } * } * * new Elysia() * .error({ * CUSTOM_ERROR: CustomError * }) * ``` */ error< Name extends string, const CustomError extends { prototype: Error } >( name: Name, errors: CustomError ): Elysia< BasePath, Singleton, { typebox: Definitions['typebox'] error: Definitions['error'] & { [name in Name]: CustomError extends { prototype: infer LiteralError extends Error } ? LiteralError : CustomError } }, Metadata, Routes, Ephemeral, Volatile > /** * Register errors * * --- * @example * ```typescript * class CustomError extends Error { * constructor() { * super() * } * } * * new Elysia() * .error('CUSTOM_ERROR', CustomError) * ``` */ error>( mapper: (decorators: Definitions['error']) => NewErrors ): Elysia< BasePath, Singleton, { typebox: Definitions['typebox'] error: { [K in keyof NewErrors]: NewErrors[K] extends { prototype: infer LiteralError extends Error } ? LiteralError : never } }, Metadata, Routes, Ephemeral, Volatile > error( // eslint-disable-next-line @typescript-eslint/no-unused-vars name: | string | Record< string, { prototype: Error } > | Function, // eslint-disable-next-line @typescript-eslint/no-unused-vars error?: { prototype: Error } ): AnyElysia { switch (typeof name) { case 'string': // @ts-ignore error.prototype[ERROR_CODE] = name // @ts-ignore this.definitions.error[name] = error return this case 'function': this.definitions.error = name(this.definitions.error) return this as any } for (const [code, error] of Object.entries(name)) { // @ts-ignore error.prototype[ERROR_CODE] = code as any this.definitions.error[code] = error as any } return this } /** * ### Error | Life cycle event * Called when error is thrown during processing request * * --- * @example * ```typescript * new Elysia() * .onError(({ code }) => { * if(code === "NOT_FOUND") * return "Path not found :(" * }) * ``` */ onError< const Schema extends RouteSchema, const Handler extends ErrorHandler< Definitions['error'], MergeSchema< Schema, MergeSchema< Volatile['schema'], MergeSchema > > & Metadata['standaloneSchema'] & Ephemeral['standaloneSchema'] & Volatile['standaloneSchema'], Singleton, Ephemeral, Volatile > >( handler: Handler ): Elysia< BasePath, Singleton, Definitions, Metadata, Routes, Ephemeral, { derive: Volatile['derive'] resolve: Volatile['resolve'] schema: Volatile['schema'] standaloneSchema: Volatile['standaloneSchema'] response: UnionResponseStatus< Volatile['response'], ElysiaHandlerToResponseSchema > } > /** * ### Error | Life cycle event * Called when error is thrown during processing request * * --- * @example * ```typescript * new Elysia() * .onError(({ code }) => { * if(code === "NOT_FOUND") * return "Path not found :(" * }) * ``` */ onError< const Schema extends RouteSchema, const Handlers extends ErrorHandler< Definitions['error'], MergeSchema< Schema, MergeSchema< Volatile['schema'], MergeSchema > > & Metadata['standaloneSchema'] & Ephemeral['standaloneSchema'] & Volatile['standaloneSchema'], Singleton, Ephemeral, Volatile >[] >( handler: Handlers ): Elysia< BasePath, Singleton, Definitions, Metadata, Routes, Ephemeral, { derive: Volatile['derive'] resolve: Volatile['resolve'] schema: Volatile['schema'] standaloneSchema: Volatile['standaloneSchema'] response: UnionResponseStatus< Volatile['response'], ElysiaHandlerToResponseSchemas > } > /** * ### Error | Life cycle event * Called when error is thrown during processing request * * --- * @example * ```typescript * new Elysia() * .onError(({ code }) => { * if(code === "NOT_FOUND") * return "Path not found :(" * }) * ``` */ onError< const Schema extends RouteSchema, const Type extends LifeCycleType, const Handler extends ErrorHandler< Definitions['error'], MergeSchema< Schema, MergeSchema< Volatile['schema'], MergeSchema > > & Metadata['standaloneSchema'] & Ephemeral['standaloneSchema'] & Volatile['standaloneSchema'], Type extends 'global' ? { store: Singleton['store'] decorator: Singleton['decorator'] derive: Singleton['derive'] & Ephemeral['derive'] & Volatile['derive'] resolve: Singleton['resolve'] & Ephemeral['resolve'] & Volatile['resolve'] } : Type extends 'scoped' ? { store: Singleton['store'] decorator: Singleton['decorator'] derive: Singleton['derive'] & Ephemeral['derive'] resolve: Singleton['resolve'] & Ephemeral['resolve'] } : Singleton, Type extends 'global' ? Ephemeral : { derive: Partial resolve: Partial schema: Ephemeral['schema'] standaloneSchema: Ephemeral['standaloneSchema'] response: Ephemeral['response'] }, Type extends 'global' ? Ephemeral : Type extends 'scoped' ? Ephemeral : { derive: Partial resolve: Partial schema: Ephemeral['schema'] standaloneSchema: Ephemeral['standaloneSchema'] response: Ephemeral['response'] } > >( options: { as: Type }, handler: Handler ): Type extends 'global' ? Elysia< BasePath, Singleton, Definitions, { schema: Metadata['schema'] standaloneSchema: Metadata['standaloneSchema'] macro: Metadata['macro'] macroFn: Metadata['macroFn'] parser: Metadata['parser'] response: UnionResponseStatus< Metadata['response'], ElysiaHandlerToResponseSchema > }, Routes, Ephemeral, Volatile > : Type extends 'scoped' ? Elysia< BasePath, Singleton, Definitions, Metadata, Routes, { derive: Ephemeral['derive'] resolve: Ephemeral['resolve'] schema: Ephemeral['schema'] standaloneSchema: Ephemeral['standaloneSchema'] response: UnionResponseStatus< Ephemeral['response'], ElysiaHandlerToResponseSchema > }, Volatile > : Elysia< BasePath, Singleton, Definitions, Metadata, Routes, Ephemeral, { derive: Volatile['derive'] resolve: Volatile['resolve'] schema: Volatile['schema'] standaloneSchema: Volatile['standaloneSchema'] response: UnionResponseStatus< Volatile['response'], ElysiaHandlerToResponseSchema > } > /** * ### Error | Life cycle event * Called when error is thrown during processing request * * --- * @example * ```typescript * new Elysia() * .onError(({ code }) => { * if(code === "NOT_FOUND") * return "Path not found :(" * }) * ``` */ onError< const Schema extends RouteSchema, const Type extends LifeCycleType, const Handlers extends ErrorHandler< Definitions['error'], MergeSchema< Schema, MergeSchema< Volatile['schema'], MergeSchema > > & Metadata['standaloneSchema'] & Ephemeral['standaloneSchema'] & Volatile['standaloneSchema'], Type extends 'global' ? { store: Singleton['store'] decorator: Singleton['decorator'] derive: Singleton['derive'] & Ephemeral['derive'] & Volatile['derive'] resolve: Singleton['resolve'] & Ephemeral['resolve'] & Volatile['resolve'] } : Type extends 'scoped' ? { store: Singleton['store'] decorator: Singleton['decorator'] derive: Singleton['derive'] & Ephemeral['derive'] resolve: Singleton['resolve'] & Ephemeral['resolve'] } : Singleton, Type extends 'global' ? Ephemeral : { derive: Partial resolve: Partial schema: Ephemeral['schema'] standaloneSchema: Ephemeral['standaloneSchema'] response: Ephemeral['response'] }, Type extends 'global' ? Ephemeral : Type extends 'scoped' ? Ephemeral : { derive: Partial resolve: Partial schema: Ephemeral['schema'] standaloneSchema: Ephemeral['standaloneSchema'] response: Ephemeral['response'] } >[] >( options: { as: Type }, handler: Handlers ): Type extends 'global' ? Elysia< BasePath, Singleton, Definitions, { schema: Metadata['schema'] standaloneSchema: Metadata['standaloneSchema'] macro: Metadata['macro'] macroFn: Metadata['macroFn'] parser: Metadata['parser'] response: UnionResponseStatus< Metadata['response'], ElysiaHandlerToResponseSchemas > }, Routes, Ephemeral, Volatile > : Type extends 'scoped' ? Elysia< BasePath, Singleton, Definitions, Metadata, Routes, { derive: Ephemeral['derive'] resolve: Ephemeral['resolve'] schema: Ephemeral['schema'] standaloneSchema: Ephemeral['standaloneSchema'] response: UnionResponseStatus< Ephemeral['response'], ElysiaHandlerToResponseSchemas > }, Volatile > : Elysia< BasePath, Singleton, Definitions, Metadata, Routes, Ephemeral, { derive: Volatile['derive'] resolve: Volatile['resolve'] schema: Volatile['schema'] standaloneSchema: Volatile['standaloneSchema'] response: UnionResponseStatus< Volatile['response'], ElysiaHandlerToResponseSchemas > } > /** * ### Error | Life cycle event * Called when error is thrown during processing request * * --- * @example * ```typescript * new Elysia() * .onError(({ code }) => { * if(code === "NOT_FOUND") * return "Path not found :(" * }) * ``` */ onError( options: { as: LifeCycleType } | MaybeArray, handler?: MaybeArray ): any { if (!handler) return this.on('error', options as any) return this.on( options as { as: LifeCycleType }, 'error', handler as any ) } /** * ### stop | Life cycle event * Called after server stop serving request * * --- * @example * ```typescript * new Elysia() * .onStop((app) => { * cleanup() * }) * ``` */ onStop(handler: MaybeArray>) { this.on('stop', handler as any) return this } /** * ### on * Syntax sugar for attaching life cycle event by name * * Does the exact same thing as `.on[Event]()` * * --- * @example * ```typescript * new Elysia() * .on('error', ({ code }) => { * if(code === "NOT_FOUND") * return "Path not found :(" * }) * ``` */ on( type: Event, handlers: MaybeArray< Extract[0]['fn'] > ): this /** * ### on * Syntax sugar for attaching life cycle event by name * * Does the exact same thing as `.on[Event]()` * * --- * @example * ```typescript * new Elysia() * .on('error', ({ code }) => { * if(code === "NOT_FOUND") * return "Path not found :(" * }) * ``` */ on( options: { as: LifeCycleType }, type: Event, handlers: MaybeArray[0]> ): this on( optionsOrType: { as: LifeCycleType } | string, typeOrHandlers: MaybeArray | string, handlers?: MaybeArray ) { let type: keyof LifeCycleStore switch (typeof optionsOrType) { case 'string': type = optionsOrType as any handlers = typeOrHandlers as any break case 'object': type = typeOrHandlers as any if ( !Array.isArray(typeOrHandlers) && typeof typeOrHandlers === 'object' ) handlers = typeOrHandlers break } if (Array.isArray(handlers)) handlers = fnToContainer(handlers) else { if (typeof handlers === 'function') handlers = [ { fn: handlers } ] else handlers = [handlers!] } const handles = handlers as HookContainer[] for (const handle of handles) { handle.scope = typeof optionsOrType === 'string' ? 'local' : (optionsOrType?.as ?? 'local') // @ts-expect-error if (type === 'resolve' || type === 'derive') handle.subType = type } if (type !== 'trace') this.inference = sucrose( { [type]: handles.map((x) => x.fn) }, this.inference, this.config.sucrose ) for (const handle of handles) { const fn = asHookType(handle, 'global', { skipIfHasType: true }) if (this.config.name || this.config.seed) fn.checksum = checksum( this.config.name + JSON.stringify(this.config.seed) ) switch (type) { case 'start': this.event.start ??= [] this.event.start.push(fn as any) break case 'request': this.event.request ??= [] this.event.request.push(fn as any) break case 'parse': this.event.parse ??= [] this.event.parse.push(fn as any) break case 'transform': this.event.transform ??= [] this.event.transform.push(fn as any) break // @ts-expect-error case 'derive': this.event.transform ??= [] this.event.transform.push( fnToContainer(fn as any, 'derive') as any ) break case 'beforeHandle': this.event.beforeHandle ??= [] this.event.beforeHandle.push(fn as any) break // @ts-expect-error // eslint-disable-next-line sonarjs/no-duplicated-branches case 'resolve': this.event.beforeHandle ??= [] this.event.beforeHandle.push( fnToContainer(fn as any, 'resolve') as any ) break case 'afterHandle': this.event.afterHandle ??= [] this.event.afterHandle.push(fn as any) break case 'mapResponse': this.event.mapResponse ??= [] this.event.mapResponse.push(fn as any) break case 'afterResponse': this.event.afterResponse ??= [] this.event.afterResponse.push(fn as any) break case 'trace': this.event.trace ??= [] this.event.trace.push(fn as any) break case 'error': this.event.error ??= [] this.event.error.push(fn as any) break case 'stop': this.event.stop ??= [] this.event.stop.push(fn as any) break } } return this } as(type: 'global'): Elysia< BasePath, { decorator: Singleton['decorator'] store: Singleton['store'] derive: Singleton['derive'] & Ephemeral['derive'] & Volatile['derive'] resolve: Singleton['resolve'] & Ephemeral['resolve'] & Volatile['resolve'] }, Definitions, { schema: MergeSchema< MergeSchema, Metadata['schema'] > standaloneSchema: Metadata['standaloneSchema'] & Volatile['standaloneSchema'] & Ephemeral['standaloneSchema'] macro: Metadata['macro'] macroFn: Metadata['macroFn'] parser: Metadata['parser'] response: Metadata['response'] & Ephemeral['response'] & Volatile['response'] }, Routes, { derive: {} resolve: {} schema: {} standaloneSchema: {} response: {} }, { derive: {} resolve: {} schema: {} standaloneSchema: {} response: {} } > as(type: 'scoped'): Elysia< BasePath, Singleton, Definitions, Metadata, Routes, { derive: Ephemeral['derive'] & Volatile['derive'] resolve: Ephemeral['resolve'] & Volatile['resolve'] schema: MergeSchema standaloneSchema: Volatile['standaloneSchema'] & Ephemeral['standaloneSchema'] response: Volatile['response'] & Ephemeral['response'] }, { derive: {} resolve: {} schema: {} standaloneSchema: {} response: {} } > as(type: 'global' | 'scoped') { promoteEvent(this.event.parse, type) promoteEvent(this.event.transform, type) promoteEvent(this.event.beforeHandle, type) promoteEvent(this.event.afterHandle, type) promoteEvent(this.event.mapResponse, type) promoteEvent(this.event.afterResponse, type) promoteEvent(this.event.trace, type) promoteEvent(this.event.error, type) if (type === 'scoped') { this.validator.scoped = mergeSchemaValidator( this.validator.scoped, this.validator.local ) this.validator.local = null if (this.standaloneValidator.local !== null) { this.standaloneValidator.scoped ||= [] this.standaloneValidator.scoped.push( ...this.standaloneValidator.local ) this.standaloneValidator.local = null } } else if (type === 'global') { this.validator.global = mergeSchemaValidator( this.validator.global, mergeSchemaValidator( this.validator.scoped, this.validator.local ) as SchemaValidator ) as SchemaValidator this.validator.scoped = null this.validator.local = null if (this.standaloneValidator.local !== null) { this.standaloneValidator.scoped ||= [] this.standaloneValidator.scoped.push( ...this.standaloneValidator.local ) this.standaloneValidator.local = null } if (this.standaloneValidator.scoped !== null) { this.standaloneValidator.global ||= [] this.standaloneValidator.global.push( ...this.standaloneValidator.scoped ) this.standaloneValidator.scoped = null } } return this as any } group( prefix: Prefix, run: ( group: Elysia< Prefix extends '' ? BasePath : JoinPath, Singleton, Definitions, { schema: MergeSchema< UnwrapRoute<{}, Definitions['typebox']>, Metadata['schema'] > standaloneSchema: UnwrapRoute<{}, Definitions['typebox']> & Metadata['standaloneSchema'] macro: Metadata['macro'] macroFn: Metadata['macroFn'] parser: Metadata['parser'] response: Metadata['response'] }, {}, Ephemeral, Volatile > ) => NewElysia ): Elysia< BasePath, Singleton, Definitions, Metadata, Routes & NewElysia['~Routes'], Ephemeral, Volatile > group< const Prefix extends string, const Input extends Metadata['macro'] & InputSchema, const Schema extends MergeSchema< UnwrapRoute< Input, Definitions['typebox'], JoinPath >, MergeSchema< Volatile['schema'], MergeSchema > > & Metadata['standaloneSchema'] & Ephemeral['standaloneSchema'] & Volatile['standaloneSchema'], const MacroContext extends {} extends Metadata['macroFn'] ? {} : MacroToContext< Metadata['macroFn'], Omit, Definitions['typebox'] >, const BeforeHandle extends MaybeArray< OptionalHandler >, const AfterHandle extends MaybeArray>, const ErrorHandle extends MaybeArray< ErrorHandler >, const NewElysia extends AnyElysia >( prefix: Prefix, schema: GuardLocalHook< Input, // @ts-ignore Schema & MacroContext, Singleton & { derive: Ephemeral['derive'] & Volatile['derive'] resolve: Ephemeral['resolve'] & Volatile['resolve'] & // @ts-ignore MacroContext['response'] }, keyof Metadata['parser'], BeforeHandle, AfterHandle, ErrorHandle >, run: ( group: Elysia< JoinPath, { decorator: Singleton['decorator'] store: Singleton['store'] derive: Singleton['derive'] resolve: Singleton['resolve'] & // @ts-ignore MacroContext['resolve'] }, Definitions, { schema: Schema standaloneSchema: Metadata['standaloneSchema'] & Schema & MacroContext macro: Metadata['macro'] macroFn: Metadata['macroFn'] parser: Metadata['parser'] response: Metadata['response'] & // @ts-ignore MacroContext['response'] & ElysiaHandlerToResponseSchemaAmbiguous & ElysiaHandlerToResponseSchemaAmbiguous & ElysiaHandlerToResponseSchemaAmbiguous }, {}, Ephemeral, Volatile > ) => NewElysia ): Elysia< BasePath, Singleton, Definitions, Metadata, Routes & NewElysia['~Routes'], Ephemeral, Volatile > /** * ### group * Encapsulate and group path with prefix * * --- * @example * ```typescript * new Elysia() * .group('/v1', app => app * .get('/', () => 'Hi') * .get('/name', () => 'Elysia') * }) * ``` */ group( prefix: string, schemaOrRun: AnyLocalHook | ((group: AnyElysia) => AnyElysia), run?: (group: AnyElysia) => AnyElysia ): AnyElysia { const instance = new Elysia({ ...this.config, prefix: '' }) instance.singleton = { ...this.singleton } instance.definitions = { ...this.definitions } instance.getServer = () => this.getServer() instance.inference = cloneInference(this.inference) instance.extender = { ...this.extender } instance['~parser'] = this['~parser'] instance.standaloneValidator = { local: [...(this.standaloneValidator.local ?? [])], scoped: [...(this.standaloneValidator.scoped ?? [])], global: [...(this.standaloneValidator.global ?? [])] } const isSchema = typeof schemaOrRun === 'object' const sandbox = (isSchema ? run! : schemaOrRun)(instance) this.singleton = mergeDeep(this.singleton, instance.singleton) as any this.definitions = mergeDeep(this.definitions, instance.definitions) if (sandbox.event.request?.length) this.event.request = [ ...(this.event.request || []), ...((sandbox.event.request || []) as any) ] if (sandbox.event.mapResponse?.length) this.event.mapResponse = [ ...(this.event.mapResponse || []), ...((sandbox.event.mapResponse || []) as any) ] this.model(sandbox.definitions.type) Object.values(instance.router.history).forEach( ({ method, path, handler, hooks }) => { path = (isSchema ? '' : (this.config.prefix ?? '')) + prefix + path if (isSchema) { const { body, headers, query, params, cookie, response, ...hook } = schemaOrRun const localHook = hooks as AnyLocalHook // Apply macros to expand group options before merging // This ensures macro-defined hooks run before nested plugin hooks this.applyMacro(hook) const hasStandaloneSchema = body || headers || query || params || cookie || response this.add( method, path, handler, mergeHook(hook, { ...(localHook || {}), error: !localHook.error ? sandbox.event.error : Array.isArray(localHook.error) ? [ ...(localHook.error ?? []), ...(sandbox.event.error ?? []) ] : [ localHook.error, ...(sandbox.event.error ?? []) ], // Merge macro's standaloneValidator with local and group schema standaloneValidator: hook.standaloneValidator || localHook.standaloneValidator || hasStandaloneSchema ? [ ...(hook.standaloneValidator ?? []), ...(localHook.standaloneValidator ?? []), ...(hasStandaloneSchema ? [ { body, headers, query, params, cookie, response } ] : []) ] : undefined }), undefined ) } else { this.add( method, path, handler, mergeHook(hooks as AnyLocalHook, { error: sandbox.event.error }), { skipPrefix: true } ) } } ) return this as any } /** * ### guard * Encapsulate and pass hook into all child handler * * --- * @example * ```typescript * import { t } from 'elysia' * * new Elysia() * .guard({ * body: t.Object({ * username: t.String(), * password: t.String() * }) * }) * ``` */ guard< const Input extends Metadata['macro'] & InputSchema, const Schema extends MergeSchema< UnwrapRoute, Metadata['schema'] >, const MacroContext extends MacroToContext< Metadata['macroFn'], Omit, Definitions['typebox'] >, const GuardType extends GuardSchemaType, const AsType extends LifeCycleType, const BeforeHandle extends MaybeArray< OptionalHandler >, const AfterHandle extends MaybeArray>, const ErrorHandle extends MaybeArray< ErrorHandler > >( hook: GuardLocalHook< Input, // @ts-ignore Schema & MacroContext, Singleton & { derive: Ephemeral['derive'] & Volatile['derive'] resolve: Ephemeral['resolve'] & Volatile['resolve'] & // @ts-ignore MacroContext['response'] }, keyof Metadata['parser'], BeforeHandle, AfterHandle, ErrorHandle, GuardType, AsType > ): Or< GuardSchemaType extends GuardType ? true : false, GuardType extends 'override' ? true : false > extends true ? Or< LifeCycleType extends AsType ? true : false, AsType extends 'local' ? true : false > extends true ? Elysia< BasePath, Singleton, Definitions, Metadata, Routes, Ephemeral, { derive: Volatile['derive'] resolve: Volatile['resolve'] & // @ts-ignore MacroContext['resolve'] schema: {} extends PickIfExists< Input, keyof InputSchema > ? Volatile['schema'] : MergeSchema< UnwrapRoute, Metadata['schema'] > standaloneSchema: Volatile['standaloneSchema'] & SimplifyToSchema response: Volatile['response'] & ElysiaHandlerToResponseSchemaAmbiguous & ElysiaHandlerToResponseSchemaAmbiguous & ElysiaHandlerToResponseSchemaAmbiguous & // @ts-ignore MacroContext['return'] } > : AsType extends 'global' ? Elysia< BasePath, { decorator: Singleton['decorator'] store: Singleton['store'] derive: Singleton['derive'] resolve: Singleton['resolve'] & // @ts-ignore MacroContext['resolve'] }, Definitions, { schema: {} extends PickIfExists< Input, keyof InputSchema > ? Metadata['schema'] : MergeSchema< UnwrapRoute< Input, Definitions['typebox'], BasePath >, Metadata['schema'] > standaloneSchema: Metadata['standaloneSchema'] & SimplifyToSchema macro: Metadata['macro'] macroFn: Metadata['macroFn'] parser: Metadata['parser'] response: Metadata['response'] & ElysiaHandlerToResponseSchemaAmbiguous & ElysiaHandlerToResponseSchemaAmbiguous & ElysiaHandlerToResponseSchemaAmbiguous & // @ts-ignore MacroContext['return'] }, Routes, Ephemeral, Volatile > : Elysia< BasePath, Singleton, Definitions, Metadata, Routes, { derive: Ephemeral['derive'] resolve: Ephemeral['resolve'] & // @ts-ignore MacroContext['resolve'] schema: {} extends PickIfExists< Input, keyof InputSchema > ? EphemeralType['schema'] : MergeSchema< UnwrapRoute< Input, Definitions['typebox'] >, Metadata['schema'] & Ephemeral['schema'] > standaloneSchema: Ephemeral['standaloneSchema'] & SimplifyToSchema response: Ephemeral['response'] & ElysiaHandlerToResponseSchemaAmbiguous & ElysiaHandlerToResponseSchemaAmbiguous & ElysiaHandlerToResponseSchemaAmbiguous & // @ts-ignore MacroContext['return'] }, Volatile > : Or< LifeCycleType extends AsType ? true : false, AsType extends 'local' ? true : false > extends true ? Elysia< BasePath, Singleton, Definitions, Metadata, Routes, Ephemeral, { derive: Volatile['derive'] resolve: Volatile['resolve'] & // @ts-ignore MacroContext['resolve'] schema: Volatile['schema'] standaloneSchema: SimplifyToSchema & ({} extends PickIfExists ? Volatile['standaloneSchema'] : Volatile['standaloneSchema'] & UnwrapRoute< Input, Definitions['typebox'] >) response: Volatile['response'] & ElysiaHandlerToResponseSchemaAmbiguous & ElysiaHandlerToResponseSchemaAmbiguous & ElysiaHandlerToResponseSchemaAmbiguous & // @ts-ignore MacroContext['return'] } > : AsType extends 'global' ? Elysia< BasePath, { decorator: Singleton['decorator'] store: Singleton['store'] derive: Singleton['derive'] resolve: Singleton['resolve'] & // @ts-ignore MacroContext['resolve'] }, Definitions, { schema: Metadata['schema'] standaloneSchema: SimplifyToSchema & ({} extends PickIfExists< Input, keyof InputSchema > ? Metadata['standaloneSchema'] : UnwrapRoute< Input, Definitions['typebox'], BasePath > & Metadata['standaloneSchema']) macro: Metadata['macro'] macroFn: Metadata['macroFn'] parser: Metadata['parser'] response: Metadata['response'] & ElysiaHandlerToResponseSchemaAmbiguous & ElysiaHandlerToResponseSchemaAmbiguous & ElysiaHandlerToResponseSchemaAmbiguous & // @ts-ignore MacroContext['return'] }, Routes, Ephemeral, Volatile > : Elysia< BasePath, Singleton, Definitions, Metadata, Routes, { derive: Ephemeral['derive'] resolve: Ephemeral['resolve'] & // @ts-ignore MacroContext['resolve'] schema: Ephemeral['schema'] standaloneSchema: SimplifyToSchema & ({} extends PickIfExists< Input, keyof InputSchema > ? Ephemeral['standaloneSchema'] : Ephemeral['standaloneSchema'] & UnwrapRoute< Input, Definitions['typebox'] >) response: Ephemeral['response'] & ElysiaHandlerToResponseSchemaAmbiguous & ElysiaHandlerToResponseSchemaAmbiguous & ElysiaHandlerToResponseSchemaAmbiguous & // @ts-ignore MacroContext['return'] }, Volatile > guard< const Input extends Metadata['macro'] & InputSchema, const Schema extends MergeSchema< UnwrapRoute, MergeSchema< Volatile['schema'], MergeSchema > > & Metadata['standaloneSchema'] & Ephemeral['standaloneSchema'] & Volatile['standaloneSchema'], const MacroContext extends {} extends Metadata['macroFn'] ? {} : MacroToContext< Metadata['macroFn'], Omit, Definitions['typebox'] >, const BeforeHandle extends MaybeArray< OptionalHandler >, const AfterHandle extends MaybeArray>, const ErrorHandle extends MaybeArray< ErrorHandler >, const NewElysia extends AnyElysia >( schema: GuardLocalHook< Input, // @ts-ignore Schema & MacroContext, Singleton & { derive: Ephemeral['derive'] & Volatile['derive'] resolve: Ephemeral['resolve'] & Volatile['resolve'] }, keyof Metadata['parser'], BeforeHandle, AfterHandle, ErrorHandle >, run: ( group: Elysia< BasePath, { decorator: Singleton['decorator'] store: Singleton['store'] derive: Singleton['derive'] resolve: Singleton['resolve'] & // @ts-ignore MacroContext['resolve'] }, Definitions, { schema: Schema standaloneSchema: Metadata['standaloneSchema'] & Schema & MacroContext macro: Metadata['macro'] macroFn: Metadata['macroFn'] parser: Metadata['parser'] response: Metadata['response'] & // @ts-ignore MacroContext['response'] & ElysiaHandlerToResponseSchemaAmbiguous & ElysiaHandlerToResponseSchemaAmbiguous & ElysiaHandlerToResponseSchemaAmbiguous }, {}, Ephemeral, Volatile > ) => NewElysia ): Elysia< BasePath, Singleton, Definitions, Metadata, Routes & NewElysia['~Routes'], Ephemeral, { derive: Volatile['derive'] resolve: Volatile['resolve'] & // @ts-ignore MacroContext['resolve'] schema: Volatile['schema'] standaloneSchema: Volatile['standaloneSchema'] response: Volatile['response'] & // @ts-ignore MacroContext['response'] } > /** * ### guard * Encapsulate and pass hook into all child handler * * --- * @example * ```typescript * import { t } from 'elysia' * * new Elysia() * .guard({ * body: t.Object({ * username: t.String(), * password: t.String() * }) * }, app => app * .get("/", () => 'Hi') * .get("/name", () => 'Elysia') * }) * ``` */ guard( hook: | (AnyLocalHook & { as: LifeCycleType }) | ((group: AnyElysia) => AnyElysia), run?: (group: AnyElysia) => AnyElysia ): AnyElysia { if (!run) { if (typeof hook === 'object') { this.applyMacro(hook) if (hook.detail) { if (this.config.detail) this.config.detail = mergeDeep( Object.assign({}, this.config.detail), hook.detail ) else this.config.detail = hook.detail } if (hook.tags) { if (!this.config.detail) this.config.detail = { tags: hook.tags } else this.config.detail.tags = hook.tags } const type: LifeCycleType = hook.as ?? 'local' if (hook.schema === 'standalone') { if (!this.standaloneValidator[type]) this.standaloneValidator[type] = [] const response = !hook?.response ? undefined : typeof hook.response === 'string' || Kind in hook.response || '~standard' in hook.response ? { 200: hook.response } : hook?.response this.standaloneValidator[type].push({ body: hook.body, headers: hook.headers, params: hook.params, query: hook.query, response, cookie: hook.cookie }) } else { this.validator[type] = { body: hook.body ?? this.validator[type]?.body, headers: hook.headers ?? this.validator[type]?.headers, params: hook.params ?? this.validator[type]?.params, query: hook.query ?? this.validator[type]?.query, response: hook.response ?? this.validator[type]?.response, cookie: hook.cookie ?? this.validator[type]?.cookie } } if (hook.parse) this.on({ as: type }, 'parse', hook.parse) if (hook.transform) this.on({ as: type }, 'transform', hook.transform) // @ts-expect-error if (hook.derive) this.on({ as: type }, 'derive', hook.derive) if (hook.beforeHandle) this.on({ as: type }, 'beforeHandle', hook.beforeHandle) // @ts-expect-error if (hook.resolve) this.on({ as: type }, 'resolve', hook.resolve) if (hook.afterHandle) this.on({ as: type }, 'afterHandle', hook.afterHandle) if (hook.mapResponse) this.on({ as: type }, 'mapResponse', hook.mapResponse) if (hook.afterResponse) this.on({ as: type }, 'afterResponse', hook.afterResponse) if (hook.error) this.on({ as: type }, 'error', hook.error) return this } return this.guard({} as any, hook) } const instance = new Elysia({ ...this.config, prefix: '' }) instance.singleton = { ...this.singleton } instance.definitions = { ...this.definitions } instance.inference = cloneInference(this.inference) instance.extender = { ...this.extender } instance.getServer = () => this.getServer() const sandbox = run(instance) this.singleton = mergeDeep(this.singleton, instance.singleton) as any this.definitions = mergeDeep(this.definitions, instance.definitions) // ? Inject getServer for websocket and trace (important, do not remove) sandbox.getServer = () => this.server if (sandbox.event.request?.length) this.event.request = [ ...(this.event.request || []), ...(sandbox.event.request || []) ] if (sandbox.event.mapResponse?.length) this.event.mapResponse = [ ...(this.event.mapResponse || []), ...(sandbox.event.mapResponse || []) ] this.model(sandbox.definitions.type) Object.values(instance.router.history).forEach( ({ method, path, handler, hooks: localHook }) => { const { body, headers, query, params, cookie, response, ...guardHook } = hook const hasStandaloneSchema = body || headers || query || params || cookie || response this.add( method, path, handler, mergeHook(guardHook as AnyLocalHook, { ...((localHook || {}) as AnyLocalHook), error: !localHook.error ? sandbox.event.error : Array.isArray(localHook.error) ? [ ...(localHook.error ?? []), ...(sandbox.event.error ?? []) ] : [ localHook.error, ...(sandbox.event.error ?? []) ], standaloneValidator: !hasStandaloneSchema ? localHook.standaloneValidator : [ ...(localHook.standaloneValidator ?? []), { body, headers, query, params, cookie, response } ] }) ) } ) // Handle dynamic imports (Promises) used inside guard callback if (instance.promisedModules.size > 0) { let processedUntil = instance.router.history.length for (const promise of instance.promisedModules.promises) { this.promisedModules.add( promise.then(() => { const { body, headers, query, params, cookie, response, ...guardHook } = hook const hasStandaloneSchema = body || headers || query || params || cookie || response const startIndex = processedUntil processedUntil = instance.router.history.length for ( let i = startIndex; i < instance.router.history.length; i++ ) { const { method, path, handler, hooks: localHook } = instance.router.history[i] this.add( method, path, handler, mergeHook(guardHook as AnyLocalHook, { ...((localHook || {}) as AnyLocalHook), error: !localHook.error ? sandbox.event.error : Array.isArray(localHook.error) ? [ ...(localHook.error ?? []), ...(sandbox.event.error ?? []) ] : [ localHook.error, ...(sandbox.event.error ?? []) ], standaloneValidator: !hasStandaloneSchema ? localHook.standaloneValidator : [ ...(localHook.standaloneValidator ?? []), { body, headers, query, params, cookie, response } ] }) ) } }) ) } } return this as any } /** * Entire Instance **/ use( instance: MaybePromise ): Elysia< BasePath, { decorator: Singleton['decorator'] & NewElysia['~Singleton']['decorator'] store: Prettify< Singleton['store'] & NewElysia['~Singleton']['store'] > derive: Singleton['derive'] & NewElysia['~Singleton']['derive'] resolve: Singleton['resolve'] & NewElysia['~Singleton']['resolve'] }, Definitions & NewElysia['~Definitions'], Metadata & NewElysia['~Metadata'], BasePath extends `` ? Routes & NewElysia['~Routes'] : Routes & CreateEden, Ephemeral, Volatile & NewElysia['~Ephemeral'] > /** * Entire multiple Instance **/ use( instance: MaybePromise ): MergeElysiaInstances /** * Import fn */ use( plugin: Promise<{ default: (elysia: AnyElysia) => MaybePromise }> ): Elysia< BasePath, { decorator: Singleton['decorator'] & NewElysia['~Singleton']['decorator'] store: Prettify< Singleton['store'] & NewElysia['~Singleton']['store'] > derive: Singleton['derive'] & NewElysia['~Singleton']['derive'] resolve: Singleton['resolve'] & NewElysia['~Singleton']['resolve'] }, Definitions & NewElysia['~Definitions'], Metadata & NewElysia['~Metadata'], BasePath extends `` ? Routes & NewElysia['~Routes'] : Routes & CreateEden, Ephemeral & NewElysia['~Ephemeral'], Volatile & NewElysia['~Volatile'] > /** * Import entire instance */ use( plugin: Promise<{ default: LazyLoadElysia }> ): Elysia< BasePath, { decorator: Singleton['decorator'] & Partial store: Prettify< Singleton['store'] & Partial > derive: Singleton['derive'] & Partial resolve: Singleton['resolve'] & Partial }, Definitions & LazyLoadElysia['~Definitions'], Metadata & LazyLoadElysia['~Metadata'], BasePath extends `` ? Routes & LazyLoadElysia['~Routes'] : Routes & CreateEden, Ephemeral, { schema: Volatile['schema'] & Partial standaloneSchema: Volatile['standaloneSchema'] & Partial resolve: Volatile['resolve'] & Partial derive: Volatile['derive'] & Partial response: Volatile['response'] & LazyLoadElysia['~Ephemeral']['response'] } > /** * Inline fn */ use< const NewElysia extends AnyElysia, const Param extends AnyElysia = this >( plugin: (app: Param) => NewElysia ): Elysia< BasePath, { decorator: Singleton['decorator'] & NewElysia['~Singleton']['decorator'] store: Prettify< Singleton['store'] & NewElysia['~Singleton']['store'] > derive: Singleton['derive'] & NewElysia['~Singleton']['derive'] resolve: Singleton['resolve'] & NewElysia['~Singleton']['resolve'] }, Definitions & NewElysia['~Definitions'], Metadata & NewElysia['~Metadata'], BasePath extends `` ? Routes & NewElysia['~Routes'] : Routes & CreateEden, Ephemeral & NewElysia['~Ephemeral'], Volatile & NewElysia['~Volatile'] > /** * Inline async fn */ use< const NewElysia extends AnyElysia, const Param extends AnyElysia = this >( plugin: | ((app: Param) => Promise) | Promise<(app: Param) => NewElysia> ): Elysia< BasePath, { decorator: Singleton['decorator'] & Partial store: Prettify< Singleton['store'] & Partial > derive: Singleton['derive'] & Partial resolve: Singleton['resolve'] & Partial }, Definitions & NewElysia['~Definitions'], Metadata & NewElysia['~Metadata'], BasePath extends `` ? Routes & NewElysia['~Routes'] : Routes & CreateEden, { schema: Ephemeral['schema'] & Partial standaloneSchema: Ephemeral['standaloneSchema'] & Partial resolve: Ephemeral['resolve'] & Partial derive: Ephemeral['derive'] & Partial response: Ephemeral['response'] & NewElysia['~Ephemeral']['response'] }, { schema: Volatile['schema'] & Partial standaloneSchema: Volatile['standaloneSchema'] & Partial resolve: Volatile['resolve'] & Partial derive: Volatile['derive'] & Partial response: Volatile['response'] & NewElysia['~Volatile']['response'] } > /** * conditional undefined ignore type */ use( instance: | MaybeArray> | MaybePromise< AnyElysia | ((app: AnyElysia) => MaybePromise) > | Promise<{ default: | AnyElysia | ((app: AnyElysia) => MaybePromise) }> | undefined | false ): this /** * ### use * Merge separate logic of Elysia with current * * --- * @example * ```typescript * const plugin = (app: Elysia) => app * .get('/plugin', () => 'hi') * * new Elysia() * .use(plugin) * ``` */ use( plugin: | MaybeArray> | MaybePromise< AnyElysia | ((app: AnyElysia) => MaybePromise) > | Promise<{ default: | AnyElysia | ((app: AnyElysia) => MaybePromise) }> | undefined | false ): AnyElysia { if (!plugin) return this if (Array.isArray(plugin)) { // eslint-disable-next-line @typescript-eslint/no-this-alias let app = this for (const p of plugin) app = app.use(p) as any return app } if (plugin instanceof Promise) { this.promisedModules.add( plugin .then((plugin) => { if (typeof plugin === 'function') return plugin(this) if (plugin instanceof Elysia) return this._use(plugin).compile() if (plugin.constructor?.name === 'Elysia') return this._use( plugin as unknown as Elysia ).compile() if (typeof plugin.default === 'function') return plugin.default(this) if (plugin.default instanceof Elysia) return this._use(plugin.default) if (plugin.constructor?.name === 'Elysia') return this._use(plugin.default) if (plugin.constructor?.name === '_Elysia') return this._use(plugin.default) try { return this._use(plugin.default) } catch (error) { console.error( 'Invalid plugin type. Expected Elysia instance, function, or module with "default" as Elysia instance or function that returns Elysia instance.' ) throw error } }) .then((v) => { if (v && typeof v.compile === 'function') v.compile() return v }) ) return this } return this._use(plugin) } private propagatePromiseModules(plugin: Elysia) { if (plugin.promisedModules.size <= 0) return this for (const promise of plugin.promisedModules.promises) this.promisedModules.add( promise.then((v) => { if (!v) return const t = this._use(v) if (t instanceof Promise) return t.then((v2) => { if (v2) v2.compile() else v.compile() }) return v.compile() }) ) return this } private _use( plugin: AnyElysia | ((app: AnyElysia) => MaybePromise) ) { if (typeof plugin === 'function') { const instance = plugin(this as unknown as any) as unknown as any if (instance instanceof Promise) { this.promisedModules.add( instance .then((plugin) => { if (plugin instanceof Elysia) { plugin.getServer = () => this.getServer() plugin.getGlobalRoutes = () => this.getGlobalRoutes() plugin.getGlobalDefinitions = () => this.getGlobalDefinitions() /** * Model and error is required for Swagger generation */ plugin.model(this.definitions.type as any) plugin.error(this.definitions.error as any) // Recompile async plugin routes for (const { method, path, handler, hooks } of Object.values(plugin.router.history)) this.add( method, path, handler, hooks, undefined ) if (plugin === this) return this.propagatePromiseModules(plugin) return plugin } if (typeof plugin === 'function') return plugin( this as unknown as any ) as unknown as Elysia if (typeof plugin.default === 'function') return plugin.default( this as unknown as any ) as unknown as Elysia return this._use(plugin) }) .then((v) => { if (v && typeof v.compile === 'function') v.compile() return v }) ) return this as unknown as any } return instance } this.propagatePromiseModules(plugin) const name = plugin.config.name const seed = plugin.config.seed plugin.getParent = () => this as any plugin.getServer = () => this.getServer() plugin.getGlobalRoutes = () => this.getGlobalRoutes() plugin.getGlobalDefinitions = () => this.getGlobalDefinitions() if (plugin.standaloneValidator?.scoped) { if (this.standaloneValidator.local) this.standaloneValidator.local = this.standaloneValidator.local.concat( plugin.standaloneValidator.scoped ) else this.standaloneValidator.local = plugin.standaloneValidator.scoped } if (plugin.standaloneValidator?.global) { if (this.standaloneValidator.global) this.standaloneValidator.global = this.standaloneValidator.global.concat( plugin.standaloneValidator.global ) else this.standaloneValidator.global = plugin.standaloneValidator.global } /** * Model and error is required for Swagger generation */ // plugin.model(this.definitions.type as any) // plugin.error(this.definitions.error as any) if (isNotEmpty(plugin['~parser'])) this['~parser'] = { ...plugin['~parser'], ...this['~parser'] } if (plugin.setHeaders) this.headers(plugin.setHeaders) if (name) { if (!(name in this.dependencies)) this.dependencies[name] = [] const current = seed !== undefined ? checksum(name + JSON.stringify(seed)) : 0 if ( !this.dependencies[name].some( ({ checksum }) => current === checksum ) ) { this.extender.macro = { ...this.extender.macro, ...plugin.extender.macro } this.extender.higherOrderFunctions = this.extender.higherOrderFunctions.concat( plugin.extender.higherOrderFunctions ) } } else { if (isNotEmpty(plugin.extender.macro)) this.extender.macro = { ...this.extender.macro, ...plugin.extender.macro } if (plugin.extender.higherOrderFunctions.length) this.extender.higherOrderFunctions = this.extender.higherOrderFunctions.concat( plugin.extender.higherOrderFunctions ) } if (plugin.extender.higherOrderFunctions.length) { deduplicateChecksum(this.extender.higherOrderFunctions) // ! Deduplicate current instance const hofHashes: number[] = [] for ( let i = 0; i < this.extender.higherOrderFunctions.length; i++ ) { const hof = this.extender.higherOrderFunctions[i] if (hof.checksum) { if (hofHashes.includes(hof.checksum)) { this.extender.higherOrderFunctions.splice(i, 1) i-- } hofHashes.push(hof.checksum) } } hofHashes.length = 0 } this.inference = mergeInference(this.inference, plugin.inference) if (isNotEmpty(plugin.singleton.decorator)) this.decorate(plugin.singleton.decorator) if (isNotEmpty(plugin.singleton.store)) this.state(plugin.singleton.store) if (isNotEmpty(plugin.definitions.type)) this.model(plugin.definitions.type) if (isNotEmpty(plugin.definitions.error)) this.error(plugin.definitions.error as any) if (isNotEmpty(plugin.extender.macro)) this.extender.macro = { ...this.extender.macro, ...plugin.extender.macro } for (const { method, path, handler, hooks } of Object.values( plugin.router.history )) this.add(method, path, handler, hooks) if (name) { if (!(name in this.dependencies)) this.dependencies[name] = [] const current = seed !== undefined ? checksum(name + JSON.stringify(seed)) : 0 if ( this.dependencies[name].some( ({ checksum }) => current === checksum ) ) return this this.dependencies[name].push( this.config?.analytic ? ({ name: plugin.config.name, seed: plugin.config.seed, checksum: current, dependencies: plugin.dependencies, stack: plugin.telemetry?.stack, routes: plugin.router.history, decorators: plugin.singleton, store: plugin.singleton.store, error: plugin.definitions.error, derive: plugin.event.transform ?.filter((x) => x?.subType === 'derive') .map((x) => ({ fn: x.toString(), stack: new Error().stack ?? '' })), resolve: plugin.event.transform ?.filter((x) => x?.subType === 'resolve') .map((x) => ({ fn: x.toString(), stack: new Error().stack ?? '' })) } as any) : { name: plugin.config.name, seed: plugin.config.seed, checksum: current, dependencies: plugin.dependencies } ) if (isNotEmpty(plugin.event)) this.event = mergeLifeCycle( this.event, filterGlobalHook(plugin.event), current ) } else { if (isNotEmpty(plugin.event)) this.event = mergeLifeCycle( this.event, filterGlobalHook(plugin.event) ) } if (plugin.validator.global) // @ts-ignore this.validator.global = mergeHook(this.validator.global, { ...plugin.validator.global }) as any if (plugin.validator.scoped) // @ts-ignore this.validator.local = mergeHook(this.validator.local, { ...plugin.validator.scoped }) return this } macro< const Name extends string, const Input extends Metadata['macro'] & InputSchema, const Schema extends MergeSchema< UnwrapRoute, MergeSchema< Volatile['schema'], MergeSchema > & Metadata['standaloneSchema'] & Ephemeral['standaloneSchema'] & Volatile['standaloneSchema'] >, const MacroContext extends {} extends Metadata['macroFn'] ? {} : MacroToContext< Metadata['macroFn'], Omit, Definitions['typebox'] >, const Property extends MaybeValueOrVoidFunction< MacroProperty< Metadata['macro'] & InputSchema & { [name in Name]?: boolean }, Schema & MacroContext, Singleton & { derive: Partial resolve: Partial< Ephemeral['resolve'] & Volatile['resolve'] > & // @ts-ignore MacroContext['resolve'] }, Definitions['error'] > > >( name: Name, macro: (Input extends any ? Input : Prettify) & Property ): Elysia< BasePath, Singleton, Definitions, { schema: Metadata['schema'] standaloneSchema: Metadata['standaloneSchema'] macro: Metadata['macro'] & { [name in Name]?: Property extends (a: infer Params) => any ? Params : boolean } macroFn: Metadata['macroFn'] & { [name in Name]: Property } parser: Metadata['parser'] response: Metadata['response'] }, Routes, Ephemeral, Volatile > macro< const Input extends Metadata['macro'] & InputSchema, const NewMacro extends Macro< Metadata['macro'] & InputSchema, Input, IntersectIfObjectSchema< MergeSchema< UnwrapRoute, MergeSchema< Volatile['schema'], MergeSchema > >, Metadata['standaloneSchema'] & Ephemeral['standaloneSchema'] & Volatile['standaloneSchema'] >, Singleton & { derive: Partial resolve: Partial }, Definitions['error'] > >( macro: NewMacro ): Elysia< BasePath, Singleton, Definitions, { schema: Metadata['schema'] standaloneSchema: Metadata['standaloneSchema'] macro: Metadata['macro'] & Partial> macroFn: Metadata['macroFn'] & NewMacro parser: Metadata['parser'] response: Metadata['response'] }, Routes, Ephemeral, Volatile > macro< const Input extends Metadata['macro'] & InputSchema, const NewMacro extends MaybeFunction< Macro< Input, // @ts-ignore trust me bro IntersectIfObjectSchema< MergeSchema< UnwrapRoute, MergeSchema< Volatile['schema'], MergeSchema > >, Metadata['standaloneSchema'] & Ephemeral['standaloneSchema'] & Volatile['standaloneSchema'] >, Singleton & { derive: Partial resolve: Partial }, Definitions['error'] > > >( macro: NewMacro ): Elysia< BasePath, Singleton, Definitions, { schema: Metadata['schema'] standaloneSchema: Metadata['standaloneSchema'] macro: Metadata['macro'] & Partial> macroFn: Metadata['macroFn'] & NewMacro parser: Metadata['parser'] response: Metadata['response'] }, Routes, Ephemeral, Volatile > macro(macroOrName: string | Macro, macro?: Macro) { if (typeof macroOrName === 'string' && !macro) throw new Error('Macro function is required') if (typeof macroOrName === 'string') this.extender.macro[macroOrName] = macro! else this.extender.macro = { ...this.extender.macro, ...macroOrName } return this as any } private applyMacro( localHook: AnyLocalHook, appliable: AnyLocalHook = localHook, { iteration = 0, applied = {} }: { iteration?: number; applied?: { [key: number]: true } } = {} ) { if (iteration >= 16) return const macro = this.extender.macro for (let [key, value] of Object.entries(appliable)) { if (key in macro === false) continue const macroHook = typeof macro[key] === 'function' ? macro[key](value) : macro[key] if ( !macroHook || (typeof macro[key] === 'object' && value === false) ) return const seed = checksum(key + JSON.stringify(macroHook.seed ?? value)) if (seed in applied) continue applied[seed] = true for (let [k, value] of Object.entries(macroHook)) { if (k === 'seed') continue if (k in emptySchema) { insertStandaloneValidator( localHook, k as keyof RouteSchema, value ) delete localHook[key] continue } if (k === 'introspect') { value?.(localHook) delete localHook[key] continue } if (k === 'detail') { if (!localHook.detail) localHook.detail = {} localHook.detail = mergeDeep(localHook.detail, value, { mergeArray: true }) delete localHook[key] continue } if (k in macro) { this.applyMacro( localHook, { [k]: value }, { applied, iteration: iteration + 1 } ) delete localHook[key] continue } if ( (k === 'derive' || k === 'resolve') && typeof value === 'function' ) // @ts-ignore value = { fn: value, subType: k } as HookContainer switch (typeof localHook[k]) { case 'function': localHook[k] = [localHook[k], value] break case 'object': if (Array.isArray(localHook[k])) (localHook[k] as any[]).push(value) else localHook[k] = [localHook[k], value] break case 'undefined': localHook[k] = value break } delete localHook[key] } } } mount( handle: ((request: Request) => MaybePromise) | AnyElysia, detail?: { detail?: DocumentDecoration } ): this mount( path: string, handle: ((request: Request) => MaybePromise) | AnyElysia, detail?: { detail?: DocumentDecoration } ): this mount( path: | string | ((request: Request) => MaybePromise) | AnyElysia, handleOrConfig?: | ((request: Request) => MaybePromise) | AnyElysia | { detail?: DocumentDecoration }, config?: { detail?: DocumentDecoration } ) { if ( path instanceof Elysia || typeof path === 'function' || path.length === 0 || path === '/' ) { const run = typeof path === 'function' ? path : path instanceof Elysia ? path.compile().fetch : handleOrConfig instanceof Elysia ? handleOrConfig.compile().fetch : typeof handleOrConfig === 'function' ? handleOrConfig : (() => { throw new Error('Invalid handler') })() const handler: Handler = ({ request, path }) => run(new Request(replaceUrlPath(request.url, path), request)) this.route('ALL', '/*', handler as any, { parse: 'none', ...config, detail: { ...config?.detail, hide: true }, config: { mount: run } }) return this } const handle = handleOrConfig instanceof Elysia ? handleOrConfig.compile().fetch : typeof handleOrConfig === 'function' ? handleOrConfig : (() => { throw new Error('Invalid handler') })() const fullPath = typeof path === 'string' && this.config.prefix ? this.config.prefix + path : path const length = fullPath.length - (path.endsWith('*') ? 1 : 0) const handler: Handler = ({ request, path }) => handle( new Request( replaceUrlPath(request.url, path.slice(length) || '/'), request ) ) this.route('ALL', path, handler as any, { parse: 'none', ...config, detail: { ...config?.detail, hide: true }, config: { mount: handle } }) this.route( 'ALL', path + (path.endsWith('/') ? '*' : '/*'), handler as any, { parse: 'none', ...config, detail: { ...config?.detail, hide: true }, config: { mount: handle } } ) return this } /** * ### get * Register handler for path with method [GET] * * --- * @example * ```typescript * import { Elysia, t } from 'elysia' * * new Elysia() * .get('/', () => 'hi') * .get('/with-hook', () => 'hi', { * response: t.String() * }) * ``` */ get< const Path extends string, const Input extends Metadata['macro'] & InputSchema, const Schema extends IntersectIfObjectSchema< MergeSchema< UnwrapRoute< Input, Definitions['typebox'], JoinPath >, MergeSchema< Volatile['schema'], MergeSchema > >, Metadata['standaloneSchema'] & Ephemeral['standaloneSchema'] & Volatile['standaloneSchema'] >, const Decorator extends Singleton & { derive: Ephemeral['derive'] & Volatile['derive'] resolve: Ephemeral['resolve'] & Volatile['resolve'] }, const MacroContext extends {} extends Metadata['macroFn'] ? {} : MacroToContext< Metadata['macroFn'], Omit, Definitions['typebox'] >, const Handle extends {} extends MacroContext ? InlineHandlerNonMacro, NoInfer> : InlineHandler< NoInfer, NoInfer, // @ts-ignore MacroContext > >( path: Path, // handler: (a: { a: MacroContext }) => any, handler: Handle, hook?: LocalHook< Input, // @ts-ignore Schema & MacroContext, Decorator, Definitions['error'], keyof Metadata['parser'] > ): Elysia< BasePath, Singleton, Definitions, Metadata, Routes & CreateEden< JoinPath, { get: CreateEdenResponse< Path, Schema, MacroContext, ComposeElysiaResponse< Schema & MacroContext & Metadata['standaloneSchema'] & Ephemeral['standaloneSchema'] & Volatile['standaloneSchema'], Handle, UnionResponseStatus< Metadata['response'], UnionResponseStatus< Ephemeral['response'], UnionResponseStatus< Volatile['response'], // @ts-ignore MacroContext['return'] & {} > > > > > } >, Ephemeral, Volatile > { this.add('GET', path, handler as any, hook) return this as any } /** * ### post * Register handler for path with method [POST] * * --- * @example * ```typescript * import { Elysia, t } from 'elysia' * * new Elysia() * .post('/', () => 'hi') * .post('/with-hook', () => 'hi', { * response: t.String() * }) * ``` */ post< const Path extends string, const Input extends Metadata['macro'] & InputSchema, const Schema extends IntersectIfObjectSchema< MergeSchema< UnwrapRoute< Input, Definitions['typebox'], JoinPath >, MergeSchema< Volatile['schema'], MergeSchema > >, Metadata['standaloneSchema'] & Ephemeral['standaloneSchema'] & Volatile['standaloneSchema'] >, const Decorator extends Singleton & { derive: Ephemeral['derive'] & Volatile['derive'] resolve: Ephemeral['resolve'] & Volatile['resolve'] }, const MacroContext extends {} extends Metadata['macroFn'] ? {} : MacroToContext< Metadata['macroFn'], Omit, Definitions['typebox'] >, const Handle extends {} extends MacroContext ? InlineHandlerNonMacro, NoInfer> : InlineHandler< NoInfer, NoInfer, // @ts-ignore MacroContext > >( path: Path, handler: Handle, hook?: LocalHook< Input, // @ts-ignore Schema & MacroContext, Decorator, Definitions['error'], keyof Metadata['parser'] > ): Elysia< BasePath, Singleton, Definitions, Metadata, Routes & CreateEden< JoinPath, { post: CreateEdenResponse< Path, Schema, MacroContext, ComposeElysiaResponse< Schema & MacroContext & Metadata['standaloneSchema'] & Ephemeral['standaloneSchema'] & Volatile['standaloneSchema'], Handle, UnionResponseStatus< Metadata['response'], UnionResponseStatus< Ephemeral['response'], UnionResponseStatus< Volatile['response'], // @ts-ignore MacroContext['return'] & {} > > > > > } >, Ephemeral, Volatile > { this.add('POST', path, handler as any, hook) return this as any } /** * ### put * Register handler for path with method [PUT] * * --- * @example * ```typescript * import { Elysia, t } from 'elysia' * * new Elysia() * .put('/', () => 'hi') * .put('/with-hook', () => 'hi', { * response: t.String() * }) * ``` */ put< const Path extends string, const Input extends Metadata['macro'] & InputSchema, const Schema extends IntersectIfObjectSchema< MergeSchema< UnwrapRoute< Input, Definitions['typebox'], JoinPath >, MergeSchema< Volatile['schema'], MergeSchema > >, Metadata['standaloneSchema'] & Ephemeral['standaloneSchema'] & Volatile['standaloneSchema'] >, const Decorator extends Singleton & { derive: Ephemeral['derive'] & Volatile['derive'] resolve: Ephemeral['resolve'] & Volatile['resolve'] }, const MacroContext extends {} extends Metadata['macroFn'] ? {} : MacroToContext< Metadata['macroFn'], Omit, Definitions['typebox'] >, const Handle extends {} extends MacroContext ? InlineHandlerNonMacro, NoInfer> : InlineHandler< NoInfer, NoInfer, // @ts-ignore MacroContext > >( path: Path, handler: Handle, hook?: LocalHook< Input, // @ts-ignore Schema & MacroContext, Decorator, Definitions['error'], keyof Metadata['parser'] > ): Elysia< BasePath, Singleton, Definitions, Metadata, Routes & CreateEden< JoinPath, { put: CreateEdenResponse< Path, Schema, MacroContext, ComposeElysiaResponse< Schema & MacroContext & Metadata['standaloneSchema'] & Ephemeral['standaloneSchema'] & Volatile['standaloneSchema'], Handle, UnionResponseStatus< Metadata['response'], UnionResponseStatus< Ephemeral['response'], UnionResponseStatus< Volatile['response'], // @ts-ignore MacroContext['return'] & {} > > > > > } >, Ephemeral, Volatile > { this.add('PUT', path, handler as any, hook) return this as any } /** * ### patch * Register handler for path with method [PATCH] * * --- * @example * ```typescript * import { Elysia, t } from 'elysia' * * new Elysia() * .patch('/', () => 'hi') * .patch('/with-hook', () => 'hi', { * response: t.String() * }) * ``` */ patch< const Path extends string, const Input extends Metadata['macro'] & InputSchema, const Schema extends IntersectIfObjectSchema< MergeSchema< UnwrapRoute< Input, Definitions['typebox'], JoinPath >, MergeSchema< Volatile['schema'], MergeSchema > >, Metadata['standaloneSchema'] & Ephemeral['standaloneSchema'] & Volatile['standaloneSchema'] >, const Decorator extends Singleton & { derive: Ephemeral['derive'] & Volatile['derive'] resolve: Ephemeral['resolve'] & Volatile['resolve'] }, const MacroContext extends {} extends Metadata['macroFn'] ? {} : MacroToContext< Metadata['macroFn'], Omit, Definitions['typebox'] >, const Handle extends InlineHandler< NoInfer, NoInfer, // @ts-ignore MacroContext > >( path: Path, handler: Handle, hook?: LocalHook< Input, // @ts-ignore Schema & MacroContext, Decorator, Definitions['error'], keyof Metadata['parser'] > ): Elysia< BasePath, Singleton, Definitions, Metadata, Routes & CreateEden< JoinPath, { patch: CreateEdenResponse< Path, Schema, MacroContext, ComposeElysiaResponse< Schema & MacroContext & Metadata['standaloneSchema'] & Ephemeral['standaloneSchema'] & Volatile['standaloneSchema'], Handle, UnionResponseStatus< Metadata['response'], UnionResponseStatus< Ephemeral['response'], UnionResponseStatus< Volatile['response'], // @ts-ignore MacroContext['return'] & {} > > > > > } >, Ephemeral, Volatile > { this.add('PATCH', path, handler as any, hook) return this as any } /** * ### delete * Register handler for path with method [DELETE] * * --- * @example * ```typescript * import { Elysia, t } from 'elysia' * * new Elysia() * .delete('/', () => 'hi') * .delete('/with-hook', () => 'hi', { * response: t.String() * }) * ``` */ delete< const Path extends string, const Input extends Metadata['macro'] & InputSchema, const Schema extends IntersectIfObjectSchema< MergeSchema< UnwrapRoute< Input, Definitions['typebox'], JoinPath >, MergeSchema< Volatile['schema'], MergeSchema > >, Metadata['standaloneSchema'] & Ephemeral['standaloneSchema'] & Volatile['standaloneSchema'] >, const Decorator extends Singleton & { derive: Ephemeral['derive'] & Volatile['derive'] resolve: Ephemeral['resolve'] & Volatile['resolve'] }, const MacroContext extends {} extends Metadata['macroFn'] ? {} : MacroToContext< Metadata['macroFn'], Omit, Definitions['typebox'] >, const Handle extends InlineHandler< NoInfer, NoInfer, // @ts-ignore MacroContext > >( path: Path, handler: Handle, hook?: LocalHook< Input, // @ts-ignore Schema & MacroContext, Decorator, Definitions['error'], keyof Metadata['parser'] > ): Elysia< BasePath, Singleton, Definitions, Metadata, Routes & CreateEden< JoinPath, { delete: CreateEdenResponse< Path, Schema, MacroContext, ComposeElysiaResponse< Schema & MacroContext & Metadata['standaloneSchema'] & Ephemeral['standaloneSchema'] & Volatile['standaloneSchema'], Handle, UnionResponseStatus< Metadata['response'], UnionResponseStatus< Ephemeral['response'], UnionResponseStatus< Volatile['response'], // @ts-ignore MacroContext['return'] & {} > > > > > } >, Ephemeral, Volatile > { this.add('DELETE', path, handler as any, hook) return this as any } /** * ### options * Register handler for path with method [POST] * * --- * @example * ```typescript * import { Elysia, t } from 'elysia' * * new Elysia() * .options('/', () => 'hi') * .options('/with-hook', () => 'hi', { * response: t.String() * }) * ``` */ options< const Path extends string, const Input extends Metadata['macro'] & InputSchema, const Schema extends IntersectIfObjectSchema< MergeSchema< UnwrapRoute< Input, Definitions['typebox'], JoinPath >, MergeSchema< Volatile['schema'], MergeSchema > >, Metadata['standaloneSchema'] & Ephemeral['standaloneSchema'] & Volatile['standaloneSchema'] >, const Decorator extends Singleton & { derive: Ephemeral['derive'] & Volatile['derive'] resolve: Ephemeral['resolve'] & Volatile['resolve'] }, const MacroContext extends {} extends Metadata['macroFn'] ? {} : MacroToContext< Metadata['macroFn'], Omit, Definitions['typebox'] >, const Handle extends InlineHandler< NoInfer, NoInfer, // @ts-ignore MacroContext > >( path: Path, handler: Handle, hook?: LocalHook< Input, // @ts-ignore Schema & MacroContext, Decorator, Definitions['error'], keyof Metadata['parser'] > ): Elysia< BasePath, Singleton, Definitions, Metadata, Routes & CreateEden< JoinPath, { options: CreateEdenResponse< Path, Schema, MacroContext, ComposeElysiaResponse< Schema & MacroContext & Metadata['standaloneSchema'] & Ephemeral['standaloneSchema'] & Volatile['standaloneSchema'], Handle, UnionResponseStatus< Metadata['response'], UnionResponseStatus< Ephemeral['response'], UnionResponseStatus< Volatile['response'], // @ts-ignore MacroContext['return'] & {} > > > > > } >, Ephemeral, Volatile > { this.add('OPTIONS', path, handler as any, hook) return this as any } /** * ### all * Register handler for path with method [ALL] * * --- * @example * ```typescript * import { Elysia, t } from 'elysia' * * new Elysia() * .all('/', () => 'hi') * .all('/with-hook', () => 'hi', { * response: t.String() * }) * ``` */ all< const Path extends string, const Input extends Metadata['macro'] & InputSchema, const Schema extends IntersectIfObjectSchema< MergeSchema< UnwrapRoute< Input, Definitions['typebox'], JoinPath >, MergeSchema< Volatile['schema'], MergeSchema > >, Metadata['standaloneSchema'] & Ephemeral['standaloneSchema'] & Volatile['standaloneSchema'] >, const Decorator extends Singleton & { derive: Ephemeral['derive'] & Volatile['derive'] resolve: Ephemeral['resolve'] & Volatile['resolve'] }, const MacroContext extends {} extends Metadata['macroFn'] ? {} : MacroToContext< Metadata['macroFn'], Omit, Definitions['typebox'] >, const Handle extends InlineHandler< NoInfer, NoInfer, // @ts-ignore MacroContext > >( path: Path, handler: Handle, hook?: LocalHook< Input, // @ts-ignore Schema & MacroContext, Decorator, Definitions['error'], keyof Metadata['parser'] > ): Elysia< BasePath, Singleton, Definitions, Metadata, Routes & CreateEden< JoinPath, { [method in string]: CreateEdenResponse< Path, Schema, MacroContext, ComposeElysiaResponse< Schema & MacroContext & Metadata['standaloneSchema'] & Ephemeral['standaloneSchema'] & Volatile['standaloneSchema'], Handle, UnionResponseStatus< Metadata['response'], UnionResponseStatus< Ephemeral['response'], UnionResponseStatus< Volatile['response'], // @ts-ignore MacroContext['return'] & {} > > > > > } >, Ephemeral, Volatile > { this.add('ALL', path, handler as any, hook) return this as any } /** * ### head * Register handler for path with method [HEAD] * * --- * @example * ```typescript * import { Elysia, t } from 'elysia' * * new Elysia() * .head('/', () => 'hi') * .head('/with-hook', () => 'hi', { * response: t.String() * }) * ``` */ head< const Path extends string, const Input extends Metadata['macro'] & InputSchema, const Schema extends IntersectIfObjectSchema< MergeSchema< UnwrapRoute< Input, Definitions['typebox'], JoinPath >, MergeSchema< Volatile['schema'], MergeSchema > >, Metadata['standaloneSchema'] & Ephemeral['standaloneSchema'] & Volatile['standaloneSchema'] >, const Decorator extends Singleton & { derive: Ephemeral['derive'] & Volatile['derive'] resolve: Ephemeral['resolve'] & Volatile['resolve'] }, const MacroContext extends {} extends Metadata['macroFn'] ? {} : MacroToContext< Metadata['macroFn'], Omit, Definitions['typebox'] >, const Handle extends InlineHandler< NoInfer, NoInfer, // @ts-ignore MacroContext > >( path: Path, handler: Handle, hook?: LocalHook< Input, // @ts-ignore Schema & MacroContext, Decorator, Definitions['error'], keyof Metadata['parser'] > ): Elysia< BasePath, Singleton, Definitions, Metadata, Routes & CreateEden< JoinPath, { head: CreateEdenResponse< Path, Schema, MacroContext, ComposeElysiaResponse< Schema & MacroContext & Metadata['standaloneSchema'] & Ephemeral['standaloneSchema'] & Volatile['standaloneSchema'], Handle, UnionResponseStatus< Metadata['response'], UnionResponseStatus< Ephemeral['response'], UnionResponseStatus< Volatile['response'], // @ts-ignore MacroContext['return'] & {} > > > > > } >, Ephemeral, Volatile > { this.add('HEAD', path, handler as any, hook) return this as any } /** * ### connect * Register handler for path with method [CONNECT] * * --- * @example * ```typescript * import { Elysia, t } from 'elysia' * * new Elysia() * .connect('/', () => 'hi') * .connect('/with-hook', () => 'hi', { * response: t.String() * }) * ``` */ connect< const Path extends string, const Input extends Metadata['macro'] & InputSchema, const Schema extends IntersectIfObjectSchema< MergeSchema< UnwrapRoute< Input, Definitions['typebox'], JoinPath >, MergeSchema< Volatile['schema'], MergeSchema > >, Metadata['standaloneSchema'] & Ephemeral['standaloneSchema'] & Volatile['standaloneSchema'] >, const Decorator extends Singleton & { derive: Ephemeral['derive'] & Volatile['derive'] resolve: Ephemeral['resolve'] & Volatile['resolve'] }, const MacroContext extends {} extends Metadata['macroFn'] ? {} : MacroToContext< Metadata['macroFn'], Omit, Definitions['typebox'] >, const Handle extends InlineHandler< NoInfer, NoInfer, // @ts-ignore MacroContext > >( path: Path, handler: Handle, hook?: LocalHook< Input, // @ts-ignore Schema & MacroContext, Decorator, Definitions['error'], keyof Metadata['parser'] > ): Elysia< BasePath, Singleton, Definitions, Metadata, Routes & CreateEden< JoinPath, { connect: CreateEdenResponse< Path, Schema, MacroContext, ComposeElysiaResponse< Schema & MacroContext & Metadata['standaloneSchema'] & Ephemeral['standaloneSchema'] & Volatile['standaloneSchema'], Handle, UnionResponseStatus< Metadata['response'], UnionResponseStatus< Ephemeral['response'], UnionResponseStatus< Volatile['response'], // @ts-ignore MacroContext['return'] & {} > > > > > } >, Ephemeral, Volatile > { this.add('CONNECT', path, handler as any, hook) return this as any } /** * ### route * Register handler for path with method [ROUTE] * * --- * @example * ```typescript * import { Elysia, t } from 'elysia' * * new Elysia() * .route('/', () => 'hi') * .route('/with-hook', () => 'hi', { * response: t.String() * }) * ``` */ route< const Method extends HTTPMethod, const Path extends string, const Input extends Metadata['macro'] & InputSchema, const Schema extends IntersectIfObjectSchema< MergeSchema< UnwrapRoute< Input, Definitions['typebox'], JoinPath >, MergeSchema< Volatile['schema'], MergeSchema > >, Metadata['standaloneSchema'] & Ephemeral['standaloneSchema'] & Volatile['standaloneSchema'] >, const Decorator extends Singleton & { derive: Ephemeral['derive'] & Volatile['derive'] resolve: Ephemeral['resolve'] & Volatile['resolve'] }, const MacroContext extends {} extends Metadata['macroFn'] ? {} : MacroToContext< Metadata['macroFn'], Omit, Definitions['typebox'] >, const Handle extends InlineHandler< NoInfer, NoInfer, // @ts-ignore MacroContext > >( method: Method, path: Path, handler: Handle, hook?: LocalHook< Input, // @ts-ignore Schema & MacroContext, Decorator, Definitions['error'], keyof Metadata['parser'] > & { config?: { allowMeta?: boolean mount?: Function } } ): Elysia< BasePath, Singleton, Definitions, Metadata, Routes & CreateEden< JoinPath, { [method in Method]: CreateEdenResponse< Path, Schema, MacroContext, ComposeElysiaResponse< Schema & MacroContext & Metadata['standaloneSchema'] & Ephemeral['standaloneSchema'] & Volatile['standaloneSchema'], Handle, UnionResponseStatus< Metadata['response'], UnionResponseStatus< Ephemeral['response'], UnionResponseStatus< Volatile['response'], // @ts-ignore MacroContext['return'] & {} > > > > > } >, Ephemeral, Volatile > { this.add(method.toUpperCase(), path, handler as any, hook, hook?.config) return this as any } /** * ### ws * Register handler for path with method [ws] * * --- * @example * ```typescript * import { Elysia, t } from 'elysia' * * new Elysia() * .ws('/', { * message(ws, message) { * ws.send(message) * } * }) * ``` */ ws< const Path extends string, const Input extends Metadata['macro'] & InputSchema, const Schema extends IntersectIfObjectSchema< MergeSchema< UnwrapRoute< Input, Definitions['typebox'], JoinPath >, MergeSchema< Volatile['schema'], MergeSchema > >, Metadata['standaloneSchema'] & Ephemeral['standaloneSchema'] & Volatile['standaloneSchema'] >, const MacroContext extends MacroToContext< Metadata['macroFn'], Omit > >( path: Path, options: WSLocalHook< Input, Schema, Singleton & { derive: Ephemeral['derive'] & Volatile['derive'] resolve: Ephemeral['resolve'] & Volatile['resolve'] & // @ts-ignore MacroContext['resolve'] } > ): Elysia< BasePath, Singleton, Definitions, Metadata, Routes & CreateEden< JoinPath, { subscribe: CreateEdenResponse< Path, Schema, MacroContext, ComposeElysiaResponse< Schema & MacroContext & Metadata['standaloneSchema'] & Ephemeral['standaloneSchema'] & Volatile['standaloneSchema'], {} extends Schema['response'] ? unknown : Schema['response'] extends { [200]: any } ? Schema['response'][200] : unknown, UnionResponseStatus< Metadata['response'], UnionResponseStatus< Ephemeral['response'], UnionResponseStatus< Volatile['response'], // @ts-ignore MacroContext['return'] & {} > > > > > } >, Ephemeral, Volatile > { if (this['~adapter'].ws) this['~adapter'].ws(this, path, options as any) else console.warn(`Current adapter doesn't support WebSocket`) return this as any } /** * ### state * Assign global mutatable state accessible for all handler * * --- * @example * ```typescript * new Elysia() * .state('counter', 0) * .get('/', (({ counter }) => ++counter) * ``` */ state( name: Name, value: Value ): Elysia< BasePath, { decorator: Singleton['decorator'] store: Prettify< Singleton['store'] & { [name in Name]: Value } > derive: Singleton['derive'] resolve: Singleton['resolve'] }, Definitions, Metadata, Routes, Ephemeral, Volatile > /** * ### state * Assign global mutatable state accessible for all handler * * --- * @example * ```typescript * new Elysia() * .state({ counter: 0 }) * .get('/', (({ counter }) => ++counter) * ``` */ state>( store: Store ): Elysia< BasePath, { decorator: Singleton['decorator'] store: Prettify derive: Singleton['derive'] resolve: Singleton['resolve'] }, Definitions, Metadata, Routes, Ephemeral, Volatile > /** * ### state * Assign global mutatable state accessible for all handler * * --- * @example * ```typescript * new Elysia() * .state('counter', 0) * .get('/', (({ counter }) => ++counter) * ``` */ state< const Type extends ContextAppendType, const Name extends string | number | symbol, Value >( options: { as: Type }, name: Name, value: Value ): Elysia< BasePath, { decorator: Singleton['decorator'] store: Type extends 'override' ? Reconcile< Singleton['store'], { [name in Name]: Value }, true > : Prettify< Singleton['store'] & { [name in Name]: Value } > derive: Singleton['derive'] resolve: Singleton['resolve'] }, Definitions, Metadata, Routes, Ephemeral, Volatile > /** * ### state * Assign global mutatable state accessible for all handler * * --- * @example * ```typescript * new Elysia() * .state({ counter: 0 }) * .get('/', (({ counter }) => ++counter) * ``` */ state< const Type extends ContextAppendType, Store extends Record >( options: { as: Type }, store: Store ): Elysia< BasePath, { decorator: Singleton['decorator'] store: Type extends 'override' ? Reconcile : Prettify derive: Singleton['derive'] resolve: Singleton['resolve'] }, Definitions, Metadata, Routes, Ephemeral, Volatile > state>( mapper: (decorators: Singleton['store']) => NewStore ): Elysia< BasePath, { decorator: Singleton['decorator'] store: NewStore derive: Singleton['derive'] resolve: Singleton['resolve'] }, Definitions, Metadata, Routes, Ephemeral, Volatile > /** * ### state * Assign global mutatable state accessible for all handler * * --- * @example * ```typescript * new Elysia() * .state('counter', 0) * .get('/', (({ counter }) => ++counter) * ``` */ state( options: | { as: ContextAppendType } | string | Record | Function, name?: | string | Record | Function | { as: ContextAppendType }, value?: unknown ) { if (name === undefined) { /** * Using either * - decorate({ name: value }) */ value = options options = { as: 'append' } name = '' } else if (value === undefined) { /** * Using either * - decorate({ as: 'override' }, { name: value }) * - decorate('name', value) */ // decorate('name', value) if (typeof options === 'string') { value = name name = options options = { as: 'append' } } else if (typeof options === 'object') { // decorate({ as: 'override' }, { name: value }) value = name name = '' } } const { as } = options as { as: ContextAppendType } if (typeof name !== 'string') return this switch (typeof value) { case 'object': if (!value || !isNotEmpty(value)) return this if (name) { if (name in this.singleton.store) this.singleton.store[name] = mergeDeep( this.singleton.store[name] as any, value!, { override: as === 'override' } ) else this.singleton.store[name] = value return this } if (value === null) return this this.singleton.store = mergeDeep(this.singleton.store, value, { override: as === 'override' }) return this as any case 'function': if (name) { if (as === 'override' || !(name in this.singleton.store)) this.singleton.store[name] = value } else this.singleton.store = value(this.singleton.store) return this as any default: if (as === 'override' || !(name in this.singleton.store)) this.singleton.store[name] = value return this } } /** * ### decorate * Define custom method to `Context` accessible for all handler * * --- * @example * ```typescript * new Elysia() * .decorate('getDate', () => Date.now()) * .get('/', (({ getDate }) => getDate()) * ``` */ decorate( name: Name, value: Value ): Elysia< BasePath, { decorator: Singleton['decorator'] & { [name in Name]: Value } store: Singleton['store'] derive: Singleton['derive'] resolve: Singleton['resolve'] }, Definitions, Metadata, Routes, Ephemeral, Volatile > /** * ### decorate * Define custom method to `Context` accessible for all handler * * --- * @example * ```typescript * new Elysia() * .decorate('getDate', () => Date.now()) * .get('/', (({ getDate }) => getDate()) * ``` */ decorate>( decorators: NewDecorators ): Elysia< BasePath, { decorator: Singleton['decorator'] & NewDecorators store: Singleton['store'] derive: Singleton['derive'] resolve: Singleton['resolve'] }, Definitions, Metadata, Routes, Ephemeral, Volatile > decorate>( mapper: (decorators: Singleton['decorator']) => NewDecorators ): Elysia< BasePath, { decorator: NewDecorators store: Singleton['store'] derive: Singleton['derive'] resolve: Singleton['resolve'] }, Definitions, Metadata, Routes, Ephemeral, Volatile > /** * ### decorate * Define custom method to `Context` accessible for all handler * * --- * @example * ```typescript * new Elysia() * .decorate({ as: 'override' }, 'getDate', () => Date.now()) * .get('/', (({ getDate }) => getDate()) * ``` */ decorate< const Type extends ContextAppendType, const Name extends string, Value >( options: { as: Type }, name: Name, value: Value ): Elysia< BasePath, { decorator: Type extends 'override' ? Reconcile< Singleton['decorator'], { [name in Name]: Value }, true > : Singleton['decorator'] & { [name in Name]: Value } store: Singleton['store'] derive: Singleton['derive'] resolve: Singleton['resolve'] }, Definitions, Metadata, Routes, Ephemeral, Volatile > /** * ### decorate * Define custom method to `Context` accessible for all handler * * --- * @example * ```typescript * new Elysia() * .decorate('getDate', () => Date.now()) * .get('/', (({ getDate }) => getDate()) * ``` */ decorate< const Type extends ContextAppendType, NewDecorators extends Record >( options: { as: Type }, decorators: NewDecorators ): Elysia< BasePath, { decorator: Type extends 'override' ? Reconcile : Singleton['decorator'] & NewDecorators store: Singleton['store'] derive: Singleton['derive'] resolve: Singleton['resolve'] }, Definitions, Metadata, Routes, Ephemeral, Volatile > /** * ### decorate * Define custom method to `Context` accessible for all handler * * --- * @example * ```typescript * new Elysia() * .decorate('getDate', () => Date.now()) * .get('/', (({ getDate }) => getDate()) * ``` */ decorate( options: | { as: ContextAppendType } | string | Record | Function, name?: | string | Record | Function | { as: ContextAppendType }, value?: unknown ) { if (name === undefined) { /** * Using either * - decorate({ name: value }) */ value = options options = { as: 'append' } name = '' } else if (value === undefined) { /** * Using either * - decorate({ as: 'override' }, { name: value }) * - decorate('name', value) */ // decorate('name', value) if (typeof options === 'string') { value = name name = options options = { as: 'append' } } else if (typeof options === 'object') { // decorate({ as: 'override' }, { name: value }) value = name name = '' } } const { as } = options as { as: ContextAppendType } if (typeof name !== 'string') return this switch (typeof value) { case 'object': if (name) { if (name in this.singleton.decorator) this.singleton.decorator[name] = mergeDeep( this.singleton.decorator[name] as any, value!, { override: as === 'override' } ) else this.singleton.decorator[name] = value return this } if (value === null) return this this.singleton.decorator = mergeDeep( this.singleton.decorator, value, { override: as === 'override' } ) return this as any case 'function': if (name) { if ( as === 'override' || !(name in this.singleton.decorator) ) this.singleton.decorator[name] = value } else this.singleton.decorator = value(this.singleton.decorator) return this as any default: if (as === 'override' || !(name in this.singleton.decorator)) this.singleton.decorator[name] = value return this } } /** * Derive new property for each request with access to `Context`. * * If error is thrown, the scope will skip to handling error instead. * * --- * @example * new Elysia() * .state('counter', 1) * .derive(({ store }) => ({ * increase() { * store.counter++ * } * })) */ derive< const Derivative extends | Record | ElysiaCustomStatusResponse | void >( transform: ( context: Context< MergeSchema< Volatile['schema'], MergeSchema, BasePath > & Metadata['standaloneSchema'] & Ephemeral['standaloneSchema'] & Volatile['standaloneSchema'], Singleton & { derive: Ephemeral['derive'] & Volatile['derive'] resolve: Ephemeral['resolve'] & Volatile['resolve'] } > ) => MaybePromise ): Elysia< BasePath, Singleton, Definitions, Metadata, Routes, Ephemeral, { derive: Volatile['derive'] & ExcludeElysiaResponse resolve: Volatile['resolve'] schema: Volatile['schema'] standaloneSchema: Volatile['standaloneSchema'] response: UnionResponseStatus< Volatile['response'], ExtractErrorFromHandle > } > /** * Derive new property for each request with access to `Context`. * * If error is thrown, the scope will skip to handling error instead. * * --- * @example * new Elysia() * .state('counter', 1) * .derive(({ store }) => ({ * increase() { * store.counter++ * } * })) */ derive< const Derivative extends | Record | ElysiaCustomStatusResponse | void, const Type extends LifeCycleType >( options: { as: Type }, transform: ( context: Context< MergeSchema< Volatile['schema'], MergeSchema, BasePath > & Metadata['standaloneSchema'] & Ephemeral['standaloneSchema'] & Volatile['standaloneSchema'] & 'global' extends Type ? { params: { [name: string]: string | undefined } } : 'scoped' extends Type ? { params: { [name: string]: string | undefined } } : {}, Singleton & ('global' extends Type ? { derive: Partial< Ephemeral['derive'] & Volatile['derive'] > resolve: Partial< Ephemeral['resolve'] & Volatile['resolve'] > } : 'scoped' extends Type ? { derive: Ephemeral['derive'] & Partial resolve: Ephemeral['resolve'] & Partial } : { derive: Ephemeral['derive'] & Volatile['derive'] resolve: Ephemeral['resolve'] & Volatile['resolve'] }), BasePath > ) => MaybePromise ): Type extends 'global' ? Elysia< BasePath, { decorator: Singleton['decorator'] store: Singleton['store'] derive: Singleton['derive'] & ExcludeElysiaResponse resolve: Singleton['resolve'] }, Definitions, { schema: Metadata['schema'] standaloneSchema: Metadata['standaloneSchema'] macro: Metadata['macro'] macroFn: Metadata['macroFn'] parser: Metadata['parser'] response: UnionResponseStatus< Metadata['response'], ExtractErrorFromHandle > }, Routes, Ephemeral, Volatile > : Type extends 'scoped' ? Elysia< BasePath, Singleton, Definitions, Metadata, Routes, { derive: Ephemeral['derive'] & ExcludeElysiaResponse resolve: Ephemeral['resolve'] schema: Ephemeral['schema'] standaloneSchema: Ephemeral['standaloneSchema'] response: UnionResponseStatus< Ephemeral['response'], ExtractErrorFromHandle > }, Volatile > : Elysia< BasePath, Singleton, Definitions, Metadata, Routes, Ephemeral, { derive: Volatile['derive'] & ExcludeElysiaResponse resolve: Ephemeral['resolve'] schema: Volatile['schema'] standaloneSchema: Volatile['standaloneSchema'] response: UnionResponseStatus< Volatile['response'], ExtractErrorFromHandle > } > derive( optionsOrTransform: { as: LifeCycleType } | Function, transform?: Function ) { if (!transform) { transform = optionsOrTransform as any optionsOrTransform = { as: 'local' } } const hook: HookContainer = { subType: 'derive', fn: transform! } return this.onTransform(optionsOrTransform as any, hook as any) as any } model< const Name extends string, const Model extends TSchema | StandardSchemaV1Like >( name: Name, model: Model ): Elysia< BasePath, Singleton, { typebox: Definitions['typebox'] & { [name in Name]: Model } error: Definitions['error'] }, Metadata, Routes, Ephemeral, Volatile > model< const Recorder extends Record >( record: Recorder ): Elysia< BasePath, Singleton, { typebox: Definitions['typebox'] & Recorder error: Definitions['error'] }, Metadata, Routes, Ephemeral, Volatile > model>( mapper: ( decorators: Definitions['typebox'] extends infer Models ? { [Name in keyof Models]: Models[Name] extends TSchema ? TRef : Models[Name] } : {} ) => NewType ): Elysia< BasePath, Singleton, { typebox: { [Name in keyof NewType]: NewType[Name] extends TRef< Name & string > ? // @ts-ignore Definitions['typebox'][Name] : NewType[Name] } error: Definitions['error'] }, Metadata, Routes, Ephemeral, Volatile > model( name: | string | Record | Function, model?: TAnySchema | StandardSchemaV1Like ): AnyElysia { const onlyTypebox = < A extends Record >( a: A ): Extract => { const res = {} as Record for (const key in a) if (!('~standard' in a[key])) res[key] = a[key] return res as Extract } switch (typeof name) { case 'object': const parsedTypebox = {} as Record< string, TSchema | StandardSchemaV1Like > const kvs = Object.entries(name) if (!kvs.length) return this for (const [key, value] of kvs) { if (key in this.definitions.type) continue if ('~standard' in value) { this.definitions.type[key] = value } else { parsedTypebox[key] = this.definitions.type[key] = value parsedTypebox[key].$id ??= `#/components/schemas/${key}` } } // @ts-expect-error this.definitions.typebox = t.Module({ ...(this.definitions.typebox['$defs'] as TModule<{}>), ...parsedTypebox } as any) return this case 'function': const result = name(this.definitions.type) this.definitions.type = result this.definitions.typebox = t.Module(onlyTypebox(result)) return this case 'string': if (!model) break this.definitions.type[name] = model if ('~standard' in model) return this const newModel = { ...model, id: model.$id ?? `#/components/schemas/${name}` } this.definitions.typebox = t.Module({ ...(this.definitions.typebox['$defs'] as TModule<{}>), ...newModel } as any) return this } if (!model) return this this.definitions.type[name] = model! if ('~standard' in model) return this this.definitions.typebox = t.Module({ ...this.definitions.typebox['$defs'], [name]: model! }) return this } Ref & string>( key: K ) { return t.Ref(key) } mapDerive< const NewDerivative extends | Record | ElysiaCustomStatusResponse >( mapper: ( context: Context< {}, Singleton & { derive: Ephemeral['derive'] & Volatile['derive'] resolve: Ephemeral['resolve'] & Volatile['resolve'] }, BasePath > ) => MaybePromise ): Elysia< BasePath, Singleton, Definitions, Metadata, Routes, Ephemeral, { derive: ExcludeElysiaResponse resolve: Volatile['resolve'] schema: Volatile['schema'] standaloneSchema: Volatile['standaloneSchema'] response: UnionResponseStatus< Volatile['response'], ExtractErrorFromHandle > } > mapDerive< const NewDerivative extends | Record | ElysiaCustomStatusResponse, const Type extends LifeCycleType >( options: { as: Type }, mapper: ( context: Context< {}, Singleton & ('global' extends Type ? { derive: Partial< Ephemeral['derive'] & Volatile['derive'] > resolve: Partial< Ephemeral['resolve'] & Volatile['resolve'] > } : 'scoped' extends Type ? { derive: Ephemeral['derive'] & Partial resolve: Ephemeral['resolve'] & Partial } : { derive: Ephemeral['derive'] & Volatile['derive'] resolve: Ephemeral['resolve'] & Volatile['resolve'] }), BasePath > ) => MaybePromise ): Type extends 'global' ? Elysia< BasePath, { decorator: Singleton['decorator'] store: Singleton['store'] derive: Singleton['derive'] & ExcludeElysiaResponse resolve: Singleton['resolve'] }, Definitions, { schema: Metadata['schema'] standaloneSchema: Metadata['standaloneSchema'] macro: Metadata['macro'] macroFn: Metadata['macroFn'] parser: Metadata['parser'] response: UnionResponseStatus< Metadata['response'], ExtractErrorFromHandle > }, Routes, Ephemeral, Volatile > : Type extends 'scoped' ? Elysia< BasePath, Singleton, Definitions, Metadata, Routes, { derive: Ephemeral['derive'] & ExcludeElysiaResponse resolve: Ephemeral['resolve'] schema: Ephemeral['schema'] standaloneSchema: Ephemeral['standaloneSchema'] response: UnionResponseStatus< Ephemeral['response'], ExtractErrorFromHandle > }, Volatile > : Elysia< BasePath, Singleton, Definitions, Metadata, Routes, Ephemeral, { derive: Volatile['derive'] resolve: Volatile['resolve'] & ExcludeElysiaResponse schema: Volatile['schema'] standaloneSchema: Volatile['standaloneSchema'] response: UnionResponseStatus< Volatile['response'], ExtractErrorFromHandle > } > mapDerive( optionsOrDerive: { as: LifeCycleType } | Function, mapper?: Function ) { if (!mapper) { mapper = optionsOrDerive as any optionsOrDerive = { as: 'local' } } const hook: HookContainer = { subType: 'mapDerive', fn: mapper! } return this.onTransform(optionsOrDerive as any, hook as any) as any } affix< const Base extends 'prefix' | 'suffix', const Type extends 'all' | 'decorator' | 'state' | 'model' | 'error', const Word extends string >( base: Base, type: Type, word: Word ): Elysia< BasePath, { decorator: Type extends 'decorator' | 'all' ? 'prefix' extends Base ? Word extends `${string}${'_' | '-' | ' '}` ? AddPrefix : AddPrefixCapitalize : AddSuffixCapitalize : Singleton['decorator'] store: Type extends 'state' | 'all' ? 'prefix' extends Base ? Word extends `${string}${'_' | '-' | ' '}` ? AddPrefix : AddPrefixCapitalize : AddSuffix : Singleton['store'] derive: Type extends 'decorator' | 'all' ? 'prefix' extends Base ? Word extends `${string}${'_' | '-' | ' '}` ? AddPrefix : AddPrefixCapitalize : AddSuffixCapitalize : Singleton['derive'] resolve: Type extends 'decorator' | 'all' ? 'prefix' extends Base ? Word extends `${string}${'_' | '-' | ' '}` ? AddPrefix : AddPrefixCapitalize : AddSuffixCapitalize : Singleton['resolve'] }, { typebox: Type extends 'model' | 'all' ? 'prefix' extends Base ? Word extends `${string}${'_' | '-' | ' '}` ? AddPrefix : AddPrefixCapitalize : AddSuffixCapitalize : Definitions['typebox'] error: Type extends 'error' | 'all' ? 'prefix' extends Base ? Word extends `${string}${'_' | '-' | ' '}` ? AddPrefix : AddPrefixCapitalize : AddSuffixCapitalize : Definitions['error'] }, Metadata, Routes, Ephemeral, Volatile > { if (word === '') return this as any const delimieter = ['_', '-', ' '] const capitalize = (word: string) => word[0].toUpperCase() + word.slice(1) const joinKey = base === 'prefix' ? (prefix: string, word: string) => delimieter.includes(prefix.at(-1) ?? '') ? prefix + word : prefix + capitalize(word) : delimieter.includes(word.at(-1) ?? '') ? (suffix: string, word: string) => word + suffix : (suffix: string, word: string) => word + capitalize(suffix) const remap = (type: 'decorator' | 'state' | 'model' | 'error') => { const store: Record = {} switch (type) { case 'decorator': for (const key in this.singleton.decorator) { store[joinKey(word, key)] = this.singleton.decorator[key] } this.singleton.decorator = store break case 'state': for (const key in this.singleton.store) store[joinKey(word, key)] = this.singleton.store[key] this.singleton.store = store break case 'model': for (const key in this.definitions.type) store[joinKey(word, key)] = this.definitions.type[key] this.definitions.type = store break case 'error': for (const key in this.definitions.error) store[joinKey(word, key)] = this.definitions.error[key] this.definitions.error = store break } } const types = Array.isArray(type) ? type : [type] for (const type of types.some((x) => x === 'all') ? ['decorator', 'state', 'model', 'error'] : types) remap(type as 'decorator') return this as any } prefix< const Type extends 'all' | 'decorator' | 'state' | 'model' | 'error', const Word extends string >(type: Type, word: Word) { return this.affix('prefix', type, word) } suffix< const Type extends 'all' | 'decorator' | 'state' | 'model' | 'error', const Word extends string >(type: Type, word: Word) { return this.affix('suffix', type, word) } compile() { this['~adapter'].beforeCompile?.(this) if (this['~adapter'].isWebStandard) { this._handle = this.config.aot ? composeGeneralHandler(this) : createDynamicHandler(this) Object.defineProperty(this, 'fetch', { value: this._handle, configurable: true, writable: true }) if (typeof this.server?.reload === 'function') this.server.reload({ ...(this.server || {}), fetch: this.fetch }) return this } if (typeof this.server?.reload === 'function') this.server.reload(this.server || {}) this._handle = composeGeneralHandler(this) return this } handle = async (request: Request) => this.fetch(request) /** * Use handle can be either sync or async to save performance. * * Beside benchmark purpose, please use 'handle' instead. */ get fetch(): (request: Request) => MaybePromise { const fetch = this.config.aot ? composeGeneralHandler(this) : createDynamicHandler(this) Object.defineProperty(this, 'fetch', { value: fetch, configurable: true, writable: true }) return fetch } /** * Custom handle written by adapter */ protected _handle?(...a: unknown[]): unknown protected handleError = async ( context: Partial< Context< MergeSchema< Metadata['schema'], MergeSchema > & Metadata['standaloneSchema'] & Ephemeral['standaloneSchema'] & Volatile['standaloneSchema'], Singleton & { derive: Ephemeral['derive'] & Volatile['derive'] resolve: Ephemeral['resolve'] & Volatile['resolve'] }, BasePath > > & { request: Request }, error: | Error | ValidationError | ParseError | NotFoundError | InternalServerError ) => { return (this.handleError = this.config.aot ? composeErrorHandler(this) : createDynamicErrorHandler(this))(context, error) } /** * ### listen * Assign current instance to port and start serving * * --- * @example * ```typescript * new Elysia() * .get("/", () => 'hi') * .listen(3000) * ``` */ listen = ( options: string | number | Partial, callback?: ListenCallback ) => { this['~adapter'].listen(this)(options, callback) return this } /** * ### stop * Stop server from serving * * --- * @example * ```typescript * const app = new Elysia() * .get("/", () => 'hi') * .listen(3000) * * // Sometime later * app.stop() * ``` * * @example * ```typescript * const app = new Elysia() * .get("/", () => 'hi') * .listen(3000) * * app.stop(true) // Abruptly any requests inflight * ``` */ stop = async (closeActiveConnections?: boolean) => { await this['~adapter'].stop?.(this, closeActiveConnections) return this }; [Symbol.dispose] = () => { if (this.server) this.stop() } /** * Wait until all lazy loaded modules all load is fully */ get modules() { return this.promisedModules } } export { Elysia } export { t } from './type-system' export { validationDetail, fileType } from './type-system/utils' export type { ElysiaTypeCustomError, ElysiaTypeCustomErrorCallback } from './type-system/types' export { serializeCookie, Cookie, type CookieOptions } from './cookies' export type { Context, PreContext, ErrorContext } from './context' export { ELYSIA_TRACE, type TraceEvent, type TraceListener, type TraceHandler, type TraceProcess, type TraceStream } from './trace' export { getSchemaValidator, getResponseSchemaValidator } from './schema' export { replaceSchemaTypeFromManyOptions as replaceSchemaType } from './replace-schema' export { mergeHook, mergeObjectArray, redirect, StatusMap, InvertedStatusMap, form, replaceUrlPath, checksum, cloneInference, deduplicateChecksum, ELYSIA_FORM_DATA, ELYSIA_REQUEST_ID, sse } from './utils' export { status, mapValueError, ParseError, NotFoundError, ValidationError, InvalidFileType, InternalServerError, InvalidCookieSignature, ERROR_CODE, ElysiaStatus, ElysiaCustomStatusResponse, type SelectiveStatus } from './error' export type { EphemeralType, CreateEden, ComposeElysiaResponse, ElysiaConfig, SingletonBase, DefinitionBase, RouteBase, Handler, ComposedHandler, InputSchema, LocalHook, MergeSchema, RouteSchema, UnwrapRoute, InternalRoute, HTTPMethod, SchemaValidator, VoidHandler, PreHandler, BodyHandler, OptionalHandler, AfterResponseHandler, ErrorHandler, LifeCycleEvent, LifeCycleStore, LifeCycleType, MaybePromise, UnwrapSchema, AnySchema, ModelsToTypes, Checksum, DocumentDecoration, InferContext, InferHandler, ResolvePath, MapResponse, BaseMacro, MacroManager, MacroToProperty, MergeElysiaInstances, MaybeArray, ModelValidator, MetadataBase, UnwrapBodySchema, UnwrapGroupGuardRoute, ModelValidatorError, ExcludeElysiaResponse, SSEPayload, StandaloneInputSchema, MergeStandaloneSchema, MergeTypeModule, GracefulHandler, AfterHandler, InlineHandler, ResolveHandler, TransformHandler, HTTPHeaders, EmptyRouteSchema, ExtractErrorFromHandle } from './types' export { env } from './universal/env' export { file, ElysiaFile } from './universal/file' export type { ElysiaAdapter } from './adapter' export { TypeSystemPolicy } from '@sinclair/typebox/system' export type { Static, TSchema } from '@sinclair/typebox' ================================================ FILE: src/manifest.ts ================================================ // import { stat, mkdir, writeFile } from 'fs/promises' // import type { AnyElysia } from '.' // import { checksum } from './utils' // const mkdirIfNotExists = async (path: string) => { // if ( // await stat(path) // .then(() => false) // .catch(() => true) // ) // await mkdir(path) // } // export const manifest = async (app: AnyElysia) => { // await app.modules // app.compile() // console.log(process.cwd()) // await mkdirIfNotExists('.elysia') // await mkdirIfNotExists('.elysia/routes') // const ops = []>[] // let appChecksum = 0 // for (const route of app.routes) { // const { path, method } = route // const code = route.compile().toString() // const name = `.elysia/routes/${path === '' ? 'index' : path.endsWith('/') ? path.replace(/\//g, '_') + 'index' : path.replace(/\//g, '_')}.${method.toLowerCase()}.js` // appChecksum = checksum(appChecksum + path + method + code) // ops.push(writeFile(name, '//' + checksum(code) + '\n' + code)) // } // const code = app.fetch.toString() // appChecksum = checksum(appChecksum + code) // ops.push(writeFile(`.elysia/handler.js`, '//' + appChecksum + '\n' + code)) // await Promise.all(ops) // console.log('DONE') // } ================================================ FILE: src/parse-query.ts ================================================ import decode from 'fast-decode-uri-component' // bit flags const KEY_HAS_PLUS = 1 const KEY_NEEDS_DECODE = 2 const VALUE_HAS_PLUS = 4 const VALUE_NEEDS_DECODE = 8 // Parse query without array export function parseQueryFromURL( input: string, startIndex: number = 0, array?: { [key: string]: 1 }, object?: { [key: string]: 1 } ): Record { const result = Object.create(null) let flags = 0 const inputLength = input.length let startingIndex = startIndex - 1 let equalityIndex = startingIndex for (let i = 0; i < inputLength; i++) switch (input.charCodeAt(i)) { // '&' case 38: processKeyValuePair(input, i) // Reset state variables startingIndex = i equalityIndex = i flags = 0 break // '=' case 61: if (equalityIndex <= startingIndex) equalityIndex = i // If '=' character occurs again, we should decode the input else flags |= VALUE_NEEDS_DECODE break // '+' case 43: if (equalityIndex > startingIndex) flags |= VALUE_HAS_PLUS else flags |= KEY_HAS_PLUS break // '%' case 37: if (equalityIndex > startingIndex) flags |= VALUE_NEEDS_DECODE else flags |= KEY_NEEDS_DECODE break } // Process the last pair if needed if (startingIndex < inputLength) processKeyValuePair(input, inputLength) return result function processKeyValuePair(input: string, endIndex: number) { const hasBothKeyValuePair = equalityIndex > startingIndex const effectiveEqualityIndex = hasBothKeyValuePair ? equalityIndex : endIndex const keySlice = input.slice(startingIndex + 1, effectiveEqualityIndex) // Skip processing if key is empty if (!hasBothKeyValuePair && keySlice.length === 0) return let finalKey = keySlice if (flags & KEY_HAS_PLUS) finalKey = finalKey.replace(/\+/g, ' ') if (flags & KEY_NEEDS_DECODE) finalKey = decode(finalKey) || finalKey let finalValue = '' if (hasBothKeyValuePair) { let valueSlice = input.slice(equalityIndex + 1, endIndex) if (flags & VALUE_HAS_PLUS) valueSlice = valueSlice.replace(/\+/g, ' ') if (flags & VALUE_NEEDS_DECODE) valueSlice = decode(valueSlice) || valueSlice finalValue = valueSlice } const currentValue = result[finalKey] if (array && array?.[finalKey]) { if (finalValue.charCodeAt(0) === 91) { if (object && object?.[finalKey]) finalValue = JSON.parse(finalValue) as any else finalValue = finalValue.slice(1, -1).split(',') as any if (currentValue === undefined) result[finalKey] = finalValue else if (Array.isArray(currentValue)) currentValue.push(...finalValue) else { result[finalKey] = finalValue result[finalKey].unshift(currentValue) } } else { if (currentValue === undefined) result[finalKey] = finalValue else if (Array.isArray(currentValue)) currentValue.push(finalValue) else result[finalKey] = [currentValue, finalValue] } } else { result[finalKey] = finalValue } } } /** * @callback parse * @param {string} input */ export function parseQueryStandardSchema( input: string, startIndex: number = 0 ) { const result = Object.create(null) as Record let flags = 0 const inputLength = input.length let startingIndex = startIndex - 1 let equalityIndex = startingIndex for (let i = 0; i < inputLength; i++) switch (input.charCodeAt(i)) { // '&' case 38: processKeyValuePair(input, i) // Reset state variables startingIndex = i equalityIndex = i flags = 0 break // '=' case 61: if (equalityIndex <= startingIndex) equalityIndex = i // If '=' character occurs again, we should decode the input else flags |= VALUE_NEEDS_DECODE break // '+' case 43: if (equalityIndex > startingIndex) flags |= VALUE_HAS_PLUS else flags |= KEY_HAS_PLUS break // '%' case 37: if (equalityIndex > startingIndex) flags |= VALUE_NEEDS_DECODE else flags |= KEY_NEEDS_DECODE break } // Process the last pair if needed if (startingIndex < inputLength) processKeyValuePair(input, inputLength) return result function processKeyValuePair(input: string, endIndex: number) { const hasBothKeyValuePair = equalityIndex > startingIndex const effectiveEqualityIndex = hasBothKeyValuePair ? equalityIndex : endIndex const keySlice = input.slice(startingIndex + 1, effectiveEqualityIndex) // Skip processing if key is empty if (!hasBothKeyValuePair && keySlice.length === 0) return let finalKey = keySlice if (flags & KEY_HAS_PLUS) finalKey = finalKey.replace(/\+/g, ' ') if (flags & KEY_NEEDS_DECODE) finalKey = decode(finalKey) || finalKey let finalValue = '' if (hasBothKeyValuePair) { let valueSlice = input.slice(equalityIndex + 1, endIndex) if (flags & VALUE_HAS_PLUS) valueSlice = valueSlice.replace(/\+/g, ' ') if (flags & VALUE_NEEDS_DECODE) valueSlice = decode(valueSlice) || valueSlice finalValue = valueSlice } const currentValue = result[finalKey] if ( finalValue.charCodeAt(0) === 91 && finalValue.charCodeAt(finalValue.length - 1) === 93 ) { try { // @ts-ignore finalValue = JSON.parse(finalValue) } catch { // If JSON parsing fails, treat it as a regular string } if (currentValue === undefined) result[finalKey] = finalValue else if (Array.isArray(currentValue)) currentValue.push(finalValue) else result[finalKey] = [currentValue, finalValue] } else if ( finalValue.charCodeAt(0) === 123 && finalValue.charCodeAt(finalValue.length - 1) === 125 ) { try { // @ts-ignore finalValue = JSON.parse(finalValue) } catch { // If JSON parsing fails, treat it as a regular string } if (currentValue === undefined) result[finalKey] = finalValue else if (Array.isArray(currentValue)) currentValue.push(finalValue) else result[finalKey] = [currentValue, finalValue] } else { if (finalValue.includes(',')) // @ts-ignore finalValue = finalValue.split(',') if (currentValue === undefined) result[finalKey] = finalValue else if (Array.isArray(currentValue)) currentValue.push(finalValue) else result[finalKey] = [currentValue, finalValue] } } } /** * @callback parse * @param {string} input */ export function parseQuery(input: string) { const result = Object.create(null) as Record let flags = 0 const inputLength = input.length let startingIndex = -1 let equalityIndex = -1 for (let i = 0; i < inputLength; i++) switch (input.charCodeAt(i)) { // '&' case 38: processKeyValuePair(input, i) // Reset state variables startingIndex = i equalityIndex = i flags = 0 break // '=' case 61: if (equalityIndex <= startingIndex) equalityIndex = i // If '=' character occurs again, we should decode the input else flags |= VALUE_NEEDS_DECODE break // '+' case 43: if (equalityIndex > startingIndex) flags |= VALUE_HAS_PLUS else flags |= KEY_HAS_PLUS break // '%' case 37: if (equalityIndex > startingIndex) flags |= VALUE_NEEDS_DECODE else flags |= KEY_NEEDS_DECODE break } // Process the last pair if needed if (startingIndex < inputLength) processKeyValuePair(input, inputLength) return result function processKeyValuePair(input: string, endIndex: number) { const hasBothKeyValuePair = equalityIndex > startingIndex const effectiveEqualityIndex = hasBothKeyValuePair ? equalityIndex : endIndex const keySlice = input.slice(startingIndex + 1, effectiveEqualityIndex) // Skip processing if key is empty if (!hasBothKeyValuePair && keySlice.length === 0) return let finalKey = keySlice if (flags & KEY_HAS_PLUS) finalKey = finalKey.replace(/\+/g, ' ') if (flags & KEY_NEEDS_DECODE) finalKey = decode(finalKey) || finalKey let finalValue = '' if (hasBothKeyValuePair) { let valueSlice = input.slice(equalityIndex + 1, endIndex) if (flags & VALUE_HAS_PLUS) valueSlice = valueSlice.replace(/\+/g, ' ') if (flags & VALUE_NEEDS_DECODE) valueSlice = decode(valueSlice) || valueSlice finalValue = valueSlice } const currentValue = result[finalKey] if (currentValue === undefined) result[finalKey] = finalValue else if (Array.isArray(currentValue)) currentValue.push(finalValue) else result[finalKey] = [currentValue, finalValue] } } ================================================ FILE: src/replace-schema.ts ================================================ import { Kind, type TAnySchema, type TSchema } from "@sinclair/typebox"; import { t } from "./type-system"; import type { MaybeArray } from "./types"; export interface ReplaceSchemaTypeOptions { from: TSchema; to(schema: TSchema): TSchema | null; excludeRoot?: boolean; rootOnly?: boolean; original?: TAnySchema; /** * Traverse until object is found except root object **/ untilObjectFound?: boolean; /** * Only replace first object type, can be paired with excludeRoot **/ onlyFirst?: "object" | "array" | (string & {}); } /** * Replace schema types with custom transformation * * @param schema - The schema to transform * @param options - Transformation options (single or array) * @returns Transformed schema * * @example * // Transform Object to ObjectString * replaceSchemaType(schema, { * from: t.Object({}), * to: (s) => t.ObjectString(s.properties || {}, s), * excludeRoot: true, * onlyFirst: 'object' * }) */ export const replaceSchemaTypeFromManyOptions = ( schema: TSchema, options: MaybeArray, ): TSchema => { if (Array.isArray(options)) { let result = schema; for (const option of options) { result = replaceSchemaTypeFromOption(result, option); } return result; } return replaceSchemaTypeFromOption(schema, options); }; const replaceSchemaTypeFromOption = ( schema: TSchema, option: ReplaceSchemaTypeOptions, ): TSchema => { if (option.rootOnly && option.excludeRoot) { throw new Error("Can't set both rootOnly and excludeRoot"); } if (option.rootOnly && option.onlyFirst) { throw new Error("Can't set both rootOnly and onlyFirst"); } if (option.rootOnly && option.untilObjectFound) { throw new Error("Can't set both rootOnly and untilObjectFound"); } type WalkProps = { s: TSchema; isRoot: boolean; treeLvl: number }; const walk = ({ s, isRoot, treeLvl }: WalkProps): TSchema => { if (!s) return s; // console.log("walk iteration", { s, isRoot, treeLvl, transformTo: option.to.toString() }) const skipRoot = isRoot && option.excludeRoot; const fromKind = option.from[Kind]; // Double-wrapping check if (s.elysiaMeta) { const fromElysiaMeta = option.from.elysiaMeta; if (fromElysiaMeta === s.elysiaMeta && !skipRoot) { return option.to(s) as TSchema; } return s; } const shouldTransform = fromKind && s[Kind] === fromKind; if (!skipRoot && option.onlyFirst && s.type === option.onlyFirst) { if (shouldTransform) { return option.to(s) as TSchema; } return s; } if (isRoot && option.rootOnly) { if (shouldTransform) { return option.to(s) as TSchema; } return s; } if (!isRoot && option.untilObjectFound && s.type === "object") { return s; } const newWalkInput = { isRoot: false, treeLvl: treeLvl + 1 }; const withTransformedChildren = { ...s }; if (s.oneOf) { withTransformedChildren.oneOf = s.oneOf.map((x: TSchema) => walk({ ...newWalkInput, s: x }), ); } if (s.anyOf) { withTransformedChildren.anyOf = s.anyOf.map((x: TSchema) => walk({ ...newWalkInput, s: x }), ); } if (s.allOf) { withTransformedChildren.allOf = s.allOf.map((x: TSchema) => walk({ ...newWalkInput, s: x }), ); } if (s.not) { withTransformedChildren.not = walk({ ...newWalkInput, s: s.not }); } if (s.properties) { withTransformedChildren.properties = {}; for (const [k, v] of Object.entries(s.properties)) { withTransformedChildren.properties[k] = walk({ ...newWalkInput, s: v as TSchema, }); } } if (s.items) { const items = s.items; withTransformedChildren.items = Array.isArray(items) ? items.map((x: TSchema) => walk({ ...newWalkInput, s: x })) : walk({ ...newWalkInput, s: items as TSchema }); } // Transform THIS node (with children already transformed) const shouldTransformThis = !skipRoot && fromKind && withTransformedChildren[Kind] === fromKind; if (shouldTransformThis) { return option.to(withTransformedChildren) as TSchema; } return withTransformedChildren; }; return walk({ s: schema, isRoot: true, treeLvl: 0 }); }; /** * Helper: Extract plain Object from ObjectString * * @example * ObjectString structure: * { * elysiaMeta: "ObjectString", * anyOf: [ * { type: "string", format: "ObjectString" }, // ← String branch * { type: "object", properties: {...} } // ← Object branch (we want this) * ] * } * ArrayString structure: * { * elysiaMeta: "ArrayString", * anyOf: [ * { type: "string", format: "ArrayString" }, // ← String branch * { type: "array", items: {...} } // ← Array branch (we want this) * ] * } */ export const revertObjAndArrStr = (schema: TSchema): TSchema => { if (schema.elysiaMeta !== "ObjectString" && schema.elysiaMeta !== "ArrayString") return schema; const anyOf = schema.anyOf; if (!anyOf?.[1]) return schema; // anyOf[1] is the object branch (already clean, no elysiaMeta) return anyOf[1]; }; let _stringToStructureCoercions: ReplaceSchemaTypeOptions[]; export const stringToStructureCoercions = () => { if (!_stringToStructureCoercions) { _stringToStructureCoercions = [ { from: t.Object({}), to: (schema) => t.ObjectString(schema.properties || {}, schema), excludeRoot: true, }, { from: t.Array(t.Any()), to: (schema) => t.ArrayString(schema.items || t.Any(), schema), }, ] satisfies ReplaceSchemaTypeOptions[]; } return _stringToStructureCoercions; }; let _queryCoercions: ReplaceSchemaTypeOptions[]; export const queryCoercions = () => { if (!_queryCoercions) { _queryCoercions = [ { from: t.Object({}), to: (schema) => t.ObjectString(schema.properties ?? {}, schema), excludeRoot: true, }, { from: t.Array(t.Any()), to: (schema) => t.ArrayQuery(schema.items ?? t.Any(), schema), }, ] satisfies ReplaceSchemaTypeOptions[]; } return _queryCoercions; }; let _coercePrimitiveRoot: ReplaceSchemaTypeOptions[]; export const coercePrimitiveRoot = () => { if (!_coercePrimitiveRoot) _coercePrimitiveRoot = [ { from: t.Number(), to: (schema) => t.Numeric(schema), rootOnly: true, }, { from: t.Boolean(), to: (schema) => t.BooleanString(schema), rootOnly: true, }, ] satisfies ReplaceSchemaTypeOptions[]; return _coercePrimitiveRoot; }; let _coerceFormData: ReplaceSchemaTypeOptions[]; export const coerceFormData = () => { if (!_coerceFormData) _coerceFormData = [ { from: t.Object({}), to: (schema) => t.ObjectString(schema.properties ?? {}, schema), onlyFirst: 'object', excludeRoot: true }, { from: t.Array(t.Any()), to: (schema) => t.ArrayString(schema.items ?? t.Any(), schema), onlyFirst: 'array', excludeRoot: true }, ] satisfies ReplaceSchemaTypeOptions[]; return _coerceFormData; }; ================================================ FILE: src/schema.ts ================================================ /* eslint-disable sonarjs/no-duplicate-string */ import { Kind, OptionalKind, TModule, TObject, TransformKind, TSchema, type TAnySchema } from '@sinclair/typebox' import { Value } from '@sinclair/typebox/value' import { TypeCompiler } from '@sinclair/typebox/compiler' import { createMirror, type Instruction as ExactMirrorInstruction } from 'exact-mirror' import { t, type TypeCheck } from './type-system' import { mergeCookie, mergeDeep, randomId } from './utils' import { mapValueError } from './error' import type { CookieOptions } from './cookies' import type { AnySchema, ElysiaConfig, InputSchema, IsAny, MaybeArray, StandaloneInputSchema, StandardSchemaV1LikeValidate, UnwrapSchema } from './types' import type { StandardSchemaV1Like } from './types' import { replaceSchemaTypeFromManyOptions, type ReplaceSchemaTypeOptions, stringToStructureCoercions } from './replace-schema' type MapValueError = ReturnType export interface ElysiaTypeCheck extends Omit, 'schema' | 'Check'> { provider: 'typebox' | 'standard' schema: T config: Object Check(value: unknown): T extends TSchema ? boolean : | { value: UnwrapSchema } | { issues: unknown[] } Clean?(v: unknown): UnwrapSchema parse(v: unknown): UnwrapSchema safeParse(v: unknown): | { success: true; data: UnwrapSchema; error: null } | { success: false data: null error: string | undefined errors: MapValueError[] } hasAdditionalProperties: boolean '~hasAdditionalProperties'?: boolean hasDefault: boolean '~hasDefault'?: boolean isOptional: boolean '~isOptional'?: boolean hasTransform: boolean '~hasTransform'?: boolean hasRef: boolean '~hasRef'?: boolean } export const isOptional = ( schema?: TSchema | TypeCheck | ElysiaTypeCheck ) => { if (!schema) return false // @ts-ignore if (schema?.[Kind] === 'Import' && schema.References) return schema.References().some(isOptional as any) // @ts-expect-error private property if (schema.schema) // @ts-expect-error private property schema = schema.schema return !!schema && OptionalKind in schema } export const hasAdditionalProperties = ( _schema: TAnySchema | TypeCheck | ElysiaTypeCheck ): boolean => { if (!_schema) return false // @ts-expect-error private property const schema: TAnySchema = (_schema as TypeCheck)?.schema ?? _schema if (schema[Kind] === 'Import' && _schema.References) return _schema.References().some(hasAdditionalProperties) if (schema.anyOf) return schema.anyOf.some(hasAdditionalProperties) if (schema.someOf) return schema.someOf.some(hasAdditionalProperties) if (schema.allOf) return schema.allOf.some(hasAdditionalProperties) if (schema.not) return schema.not.some(hasAdditionalProperties) if (schema.type === 'object') { const properties = schema.properties as Record if ('additionalProperties' in schema) return schema.additionalProperties if ('patternProperties' in schema) return false for (const key of Object.keys(properties)) { const property = properties[key] if (property.type === 'object') { if (hasAdditionalProperties(property)) return true } else if (property.anyOf) { for (let i = 0; i < property.anyOf.length; i++) if (hasAdditionalProperties(property.anyOf[i])) return true } return property.additionalProperties } return false } if (schema.type === 'array' && schema.items && !Array.isArray(schema.items)) return hasAdditionalProperties(schema.items) return false } /** * Resolve a schema that might be a model reference (string) to the actual schema */ export const resolveSchema = ( schema: TAnySchema | string | undefined, models?: Record, modules?: TModule ): TAnySchema | StandardSchemaV1Like | undefined => { if (!schema) return undefined if (typeof schema !== 'string') return schema // Check modules first (higher priority) // @ts-expect-error private property if (modules && schema in modules.$defs) { return (modules as TModule<{}, {}>).Import(schema as never) } // Then check models return models?.[schema] } export const hasType = (type: string, schema: TAnySchema): boolean => { if (!schema) return false if (Kind in schema && schema[Kind] === type) return true // Handle Import/Ref schemas (unwrap) if (Kind in schema && schema[Kind] === 'Import') { if (schema.$defs && schema.$ref) { const ref = schema.$ref.replace('#/$defs/', '') if (schema.$defs[ref]) { return hasType(type, schema.$defs[ref]) } } } if (schema.anyOf) return schema.anyOf.some((s: TSchema) => hasType(type, s)) if (schema.oneOf) return schema.oneOf.some((s: TSchema) => hasType(type, s)) if (schema.allOf) return schema.allOf.some((s: TSchema) => hasType(type, s)) if (schema.type === 'array' && schema.items) { if ( type === 'Files' && Kind in schema.items && schema.items[Kind] === 'File' ) { return true } return hasType(type, schema.items) } if (schema.type === 'object') { const properties = schema.properties as Record if (!properties) return false for (const key of Object.keys(properties)) { if (hasType(type, properties[key])) return true } } return false } export const hasElysiaMeta = (meta: string, _schema: TAnySchema): boolean => { if (!_schema) return false // @ts-expect-error private property const schema: TAnySchema = (_schema as TypeCheck)?.schema ?? _schema if (schema.elysiaMeta === meta) return true if (schema[Kind] === 'Import' && _schema.References) return _schema .References() .some((schema: TSchema) => hasElysiaMeta(meta, schema)) if (schema.anyOf) return schema.anyOf.some((schema: TSchema) => hasElysiaMeta(meta, schema) ) if (schema.someOf) return schema.someOf.some((schema: TSchema) => hasElysiaMeta(meta, schema) ) if (schema.allOf) return schema.allOf.some((schema: TSchema) => hasElysiaMeta(meta, schema) ) if (schema.not) return schema.not.some((schema: TSchema) => hasElysiaMeta(meta, schema)) if (schema.type === 'object') { const properties = schema.properties as Record if (!properties) return false for (const key of Object.keys(properties)) { const property = properties[key] if (property.type === 'object') { if (hasElysiaMeta(meta, property)) return true } else if (property.anyOf) { for (let i = 0; i < property.anyOf.length; i++) if (hasElysiaMeta(meta, property.anyOf[i])) return true } return schema.elysiaMeta === meta } return false } if (schema.type === 'array' && schema.items && !Array.isArray(schema.items)) return hasElysiaMeta(meta, schema.items) return false } export const hasProperty = ( expectedProperty: string, _schema: TAnySchema | TypeCheck | ElysiaTypeCheck ): boolean => { if (!_schema) return false // @ts-expect-error private property const schema = _schema.schema ?? _schema if (schema[Kind] === 'Import' && _schema.References) return _schema .References() .some((schema: TAnySchema) => hasProperty(expectedProperty, schema)) if (schema.anyOf) return schema.anyOf.some((s: TSchema) => hasProperty(expectedProperty, s) ) if (schema.allOf) return schema.allOf.some((s: TSchema) => hasProperty(expectedProperty, s) ) if (schema.oneOf) return schema.oneOf.some((s: TSchema) => hasProperty(expectedProperty, s) ) if (schema.type === 'object') { const properties = schema.properties as Record if (!properties) return false for (const key of Object.keys(properties)) { const property = properties[key] if (expectedProperty in property) return true if (property.type === 'object') { if (hasProperty(expectedProperty, property)) return true } else if (property.anyOf) for (let i = 0; i < property.anyOf.length; i++) if (hasProperty(expectedProperty, property.anyOf[i])) return true } return false } return expectedProperty in schema } export const hasRef = (schema: TAnySchema): boolean => { if (!schema) return false if (schema.oneOf) for (let i = 0; i < schema.oneOf.length; i++) if (hasRef(schema.oneOf[i])) return true if (schema.anyOf) for (let i = 0; i < schema.anyOf.length; i++) if (hasRef(schema.anyOf[i])) return true if (schema.oneOf) for (let i = 0; i < schema.oneOf.length; i++) if (hasRef(schema.oneOf[i])) return true if (schema.allOf) for (let i = 0; i < schema.allOf.length; i++) if (hasRef(schema.allOf[i])) return true if (schema.not && hasRef(schema.not)) return true if (schema.type === 'object' && schema.properties) { const properties = schema.properties as Record for (const key of Object.keys(properties)) { const property = properties[key] if (hasRef(property)) return true if ( property.type === 'array' && property.items && hasRef(property.items) ) return true } } if (schema.type === 'array' && schema.items && hasRef(schema.items)) return true return schema[Kind] === 'Ref' && '$ref' in schema } export const hasTransform = (schema: TAnySchema): boolean => { if (!schema) return false if ( schema.$ref && schema.$defs && schema.$ref in schema.$defs && hasTransform(schema.$defs[schema.$ref]) ) return true if (schema.oneOf) for (let i = 0; i < schema.oneOf.length; i++) if (hasTransform(schema.oneOf[i])) return true if (schema.anyOf) for (let i = 0; i < schema.anyOf.length; i++) if (hasTransform(schema.anyOf[i])) return true if (schema.allOf) for (let i = 0; i < schema.allOf.length; i++) if (hasTransform(schema.allOf[i])) return true if (schema.not && hasTransform(schema.not)) return true if (schema.type === 'object' && schema.properties) { const properties = schema.properties as Record for (const key of Object.keys(properties)) { const property = properties[key] if (hasTransform(property)) return true if ( property.type === 'array' && property.items && hasTransform(property.items) ) return true } } if (schema.type === 'array' && schema.items && hasTransform(schema.items)) return true return TransformKind in schema } const createCleaner = (schema: TAnySchema) => (value: unknown) => { if (typeof value === 'object') try { return Value.Clean(schema, value) } catch {} return value } // const caches = >>{} export const getSchemaValidator = ( s: T, { models = {}, dynamic = false, modules, normalize = false, additionalProperties = false, forceAdditionalProperties = false, coerce = false, additionalCoerce = [], validators, sanitize }: { models?: Record modules?: TModule additionalProperties?: boolean forceAdditionalProperties?: boolean dynamic?: boolean normalize?: ElysiaConfig<''>['normalize'] coerce?: boolean additionalCoerce?: MaybeArray validators?: InputSchema['body'][] sanitize?: () => ExactMirrorInstruction['sanitize'] } = {} ): IsAny extends true ? ElysiaTypeCheck : undefined extends T ? undefined : ElysiaTypeCheck>> => { validators = validators?.filter((x) => x) if (!s) { if (!validators?.length) return undefined as any s = validators[0] as any validators = validators.slice(1) } let doesHaveRef: boolean | undefined = undefined const replaceSchema = (schema: TAnySchema): TAnySchema => { if (coerce) return replaceSchemaTypeFromManyOptions(schema, [ { from: t.Number(), to: (options) => t.Numeric(options), untilObjectFound: true }, { from: t.Boolean(), to: (options) => t.BooleanString(options), untilObjectFound: true }, ...(Array.isArray(additionalCoerce) ? additionalCoerce : [additionalCoerce]) ]) return replaceSchemaTypeFromManyOptions(schema, additionalCoerce) } const mapSchema = ( s: string | TSchema | StandardSchemaV1Like | undefined ): TSchema | StandardSchemaV1Like => { if (s && typeof s !== 'string' && '~standard' in s) return s as StandardSchemaV1Like if (!s) return undefined as any let schema: TSchema | StandardSchemaV1Like if (typeof s !== 'string') schema = s else { schema = // @ts-expect-error private property modules && s in modules.$defs ? (modules as TModule<{}, {}>).Import(s as never) : models[s] if (!schema) return undefined as any } const hasAdditionalCoerce = Array.isArray(additionalCoerce) ? additionalCoerce.length > 0 : !!additionalCoerce if (Kind in schema) { if (schema[Kind] === 'Import') { if (!hasRef(schema.$defs[schema.$ref])) { schema = schema.$defs[schema.$ref] ?? models[schema.$ref] if (coerce || hasAdditionalCoerce) { schema = replaceSchema(schema as TSchema) if ('$id' in schema && !schema.$defs) schema.$id = `${schema.$id}_coerced_${randomId()}` } } } else { if (hasRef(schema)) { const id = randomId() const model: any = t.Module({ // @ts-expect-error private property ...modules?.$defs, [id]: schema }) schema = model.Import(id) } else if (coerce || hasAdditionalCoerce) schema = replaceSchema(schema as TSchema) } } return schema } let schema = mapSchema(s) // console.log([s, schema]) let _validators = validators if ( '~standard' in schema || (validators?.length && validators.some( (x) => x && typeof x !== 'string' && '~standard' in x )) ) { const typeboxSubValidator = ( schema: TSchema ): StandardSchemaV1LikeValidate => { let mirror: Function if (normalize === true || normalize === 'exactMirror') try { mirror = createMirror(schema as TSchema, { TypeCompiler, sanitize: sanitize?.(), modules }) } catch { console.warn( 'Failed to create exactMirror. Please report the following code to https://github.com/elysiajs/elysia/issues' ) console.warn(schema) mirror = createCleaner(schema as TSchema) } const vali = getSchemaValidator(schema, { models, modules, dynamic, normalize, additionalProperties: true, forceAdditionalProperties: true, coerce, additionalCoerce })! // @ts-ignore vali.Decode = mirror return { // @ts-ignore validate: (v) => { if (vali.Check(v)) return { value: mirror ? mirror(v) : v } else return { issues: [...vali.Errors(v)] } } } } const mainCheck = schema['~standard'] ? schema['~standard'] : typeboxSubValidator(schema as TSchema) let checkers = [] if (validators?.length) for (const validator of validators) { if (!validator) continue if (typeof validator === 'string') continue if (validator?.['~standard']) { checkers.push(validator['~standard']) continue } if (Kind in validator) { checkers.push(typeboxSubValidator(validator)) continue } } function Check( value: unknown, validated = false ): value is UnwrapSchema { let v = validated ? value : mainCheck.validate(value) if (v instanceof Promise) return v.then((v) => Check(v, true)) as any if (v.issues) return v const values = <(Record | unknown[])[]>[] if (v && typeof v === 'object') values.push(v.value as any) return runCheckers(value, 0, values, v) } function runCheckers( value: unknown, startIndex: number, values: (Record | unknown[])[], lastV: any ): any { for (let i = startIndex; i < checkers.length; i++) { // @ts-ignore let v = checkers[i].validate(value) if (v instanceof Promise) return v.then((resolved) => { if (resolved.issues) return resolved const nextValues = [...values] if (resolved && typeof resolved === 'object') nextValues.push(resolved.value) return runCheckers(value, i + 1, nextValues, resolved) }) if (v.issues) return v // @ts-ignore if (v && typeof v === 'object') values.push(v.value) lastV = v } return mergeValues(values, lastV) } function mergeValues( values: (Record | unknown[])[], lastV: any ) { if (!values.length) return { value: lastV } if (values.length === 1) return { value: values[0] } if (values.length === 2) return { value: mergeDeep(values[0], values[1]) } let newValue = mergeDeep(values[0], values[1]) for (let i = 2; i < values.length; i++) newValue = mergeDeep(newValue, values[i]) return { value: newValue } } const validator: ElysiaTypeCheck = { provider: 'standard', schema, references: '', checkFunc: () => {}, code: '', // @ts-ignore Check, // @ts-ignore Errors: (value: unknown) => Check(value)?.then?.((x) => x?.issues), Code: () => '', // @ts-ignore Decode: Check, // @ts-ignore Encode: (value: unknown) => value, hasAdditionalProperties: false, hasDefault: false, isOptional: false, hasTransform: false, hasRef: false } validator.parse = (v) => { try { return validator.Decode(validator.Clean?.(v) ?? v) } catch (error) { throw [...validator.Errors(v)].map(mapValueError) } } validator.safeParse = (v) => { try { return { success: true, data: validator.Decode(validator.Clean?.(v) ?? v), error: null } } catch (error) { const errors = [...compiled.Errors(v)].map(mapValueError) return { success: false, data: null, error: errors[0]?.summary, errors } } } return validator as any } else if (validators?.length) { let hasAdditional = false const validators = _validators as TSchema[] const { schema: mergedObjectSchema, notObjects } = mergeObjectSchemas([ schema, ...(validators.map(mapSchema) as TSchema[]) ]) if (notObjects) { schema = t.Intersect([ ...(mergedObjectSchema ? [mergedObjectSchema] : []), ...notObjects.map((x) => { const schema = mapSchema(x) as TSchema if ( schema.type === 'object' && 'additionalProperties' in schema ) { if ( !hasAdditional && schema.additionalProperties === false ) { hasAdditional = true } delete schema.additionalProperties } return schema }) ]) if (schema.type === 'object' && hasAdditional) schema.additionalProperties = false } } else { if ( schema.type === 'object' && ('additionalProperties' in schema === false || forceAdditionalProperties) ) schema.additionalProperties = additionalProperties else schema = replaceSchemaTypeFromManyOptions(schema, { onlyFirst: 'object', from: t.Object({}), to(schema) { if (!schema.properties) return schema if ('additionalProperties' in schema) return schema return t.Object(schema.properties, { ...schema, additionalProperties: false }) } }) } if (dynamic) { if (Kind in schema) { const validator: ElysiaTypeCheck = { provider: 'typebox', schema, // @ts-ignore references: '', checkFunc: () => {}, code: '', Check: (value: unknown) => Value.Check(schema, value), Errors: (value: unknown) => Value.Errors(schema, value), Code: () => '', Clean: createCleaner(schema), Decode: (value: unknown) => Value.Decode(schema, value), Encode: (value: unknown) => Value.Encode(schema, value), get hasAdditionalProperties() { if ('~hasAdditionalProperties' in this) return this['~hasAdditionalProperties'] as boolean return (this['~hasAdditionalProperties'] = hasAdditionalProperties(schema)) }, get hasDefault() { if ('~hasDefault' in this) return this['~hasDefault']! return (this['~hasDefault'] = hasProperty( 'default', schema )) }, get isOptional() { if ('~isOptional' in this) return this['~isOptional']! return (this['~isOptional'] = isOptional(schema)) }, get hasTransform() { if ('~hasTransform' in this) return this['~hasTransform']! return (this['~hasTransform'] = hasTransform(schema)) }, '~hasRef': doesHaveRef, get hasRef() { if ('~hasRef' in this) return this['~hasRef']! return (this['~hasRef'] = hasTransform(schema)) } } if (schema.config) { validator.config = schema.config if (validator?.schema?.config) delete validator.schema.config } if (normalize && schema.additionalProperties === false) { if (normalize === true || normalize === 'exactMirror') { try { validator.Clean = createMirror(schema, { TypeCompiler, sanitize: sanitize?.(), modules }) } catch { console.warn( 'Failed to create exactMirror. Please report the following code to https://github.com/elysiajs/elysia/issues' ) console.warn(schema) validator.Clean = createCleaner(schema) } } else validator.Clean = createCleaner(schema) } validator.parse = (v) => { try { return validator.Decode(validator.Clean?.(v) ?? v) } catch (error) { throw [...validator.Errors(v)].map(mapValueError) } } validator.safeParse = (v) => { try { return { success: true, data: validator.Decode(validator.Clean?.(v) ?? v), error: null } } catch (error) { const errors = [...compiled.Errors(v)].map(mapValueError) return { success: false, data: null, error: errors[0]?.summary, errors } } } // if (cacheKey) caches[cacheKey] = validator return validator as any } else { const validator: ElysiaTypeCheck = { provider: 'standard', schema, references: '', checkFunc: () => {}, code: '', // @ts-ignore Check: (v) => schema['~standard'].validate(v), // @ts-ignore Errors(value: unknown) { // @ts-ignore const response = schema['~standard'].validate(value) if (response instanceof Promise) throw Error( 'Async validation is not supported in non-dynamic schema' ) return response.issues }, Code: () => '', // @ts-ignore Decode(value) { // @ts-ignore const response = schema['~standard'].validate(value) if (response instanceof Promise) throw Error( 'Async validation is not supported in non-dynamic schema' ) return response }, // @ts-ignore Encode: (value: unknown) => value, hasAdditionalProperties: false, hasDefault: false, isOptional: false, hasTransform: false, hasRef: false } validator.parse = (v) => { try { return validator.Decode(validator.Clean?.(v) ?? v) } catch (error) { throw [...validator.Errors(v)].map(mapValueError) } } validator.safeParse = (v) => { try { return { success: true, data: validator.Decode(validator.Clean?.(v) ?? v), error: null } } catch (error) { const errors = [...compiled.Errors(v)].map(mapValueError) return { success: false, data: null, error: errors[0]?.summary, errors } } } // if (cacheKey) caches[cacheKey] = validator return validator as any } } let compiled: ElysiaTypeCheck if (Kind in schema) { compiled = TypeCompiler.Compile( schema, Object.values(models).filter((x) => Kind in x) ) as any compiled.provider = 'typebox' if (schema.config) { compiled.config = schema.config if (compiled?.schema?.config) delete compiled.schema.config } if (normalize === true || normalize === 'exactMirror') { try { compiled.Clean = createMirror(schema, { TypeCompiler, sanitize: sanitize?.(), modules }) } catch (error) { console.warn( 'Failed to create exactMirror. Please report the following code to https://github.com/elysiajs/elysia/issues' ) console.dir(schema, { depth: null }) compiled.Clean = createCleaner(schema) } } else if (normalize === 'typebox') compiled.Clean = createCleaner(schema) } else { compiled = { provider: 'standard', schema, references: '', checkFunc(value: unknown) { // @ts-ignore const response = schema['~standard'].validate(value) if (response instanceof Promise) throw Error( 'Async validation is not supported in non-dynamic schema' ) return response }, code: '', // @ts-ignore Check: (v) => schema['~standard'].validate(v), // @ts-ignore Errors(value: unknown) { // @ts-ignore const response = schema['~standard'].validate(value) if (response instanceof Promise) throw Error( 'Async validation is not supported in non-dynamic schema' ) return response.issues }, Code: () => '', // @ts-ignore Decode(value) { // @ts-ignore const response = schema['~standard'].validate(value) if (response instanceof Promise) throw Error( 'Async validation is not supported in non-dynamic schema' ) return response }, // @ts-ignore Encode: (value: unknown) => value, hasAdditionalProperties: false, hasDefault: false, isOptional: false, hasTransform: false, hasRef: false } } compiled.parse = (v) => { try { return compiled.Decode(compiled.Clean?.(v) ?? v) } catch (error) { throw [...compiled.Errors(v)].map(mapValueError) } } compiled.safeParse = (v) => { try { return { success: true, data: compiled.Decode(compiled.Clean?.(v) ?? v), error: null } } catch (error) { const errors = [...compiled.Errors(v)].map(mapValueError) return { success: false, data: null, error: errors[0]?.summary, errors } } } if (Kind in schema) Object.assign(compiled, { get hasAdditionalProperties() { if ('~hasAdditionalProperties' in this) return this['~hasAdditionalProperties'] return (this['~hasAdditionalProperties'] = hasAdditionalProperties(compiled)) }, get hasDefault() { if ('~hasDefault' in this) return this['~hasDefault'] return (this['~hasDefault'] = hasProperty('default', compiled)) }, get isOptional() { if ('~isOptional' in this) return this['~isOptional']! return (this['~isOptional'] = isOptional(compiled)) }, get hasTransform() { if ('~hasTransform' in this) return this['~hasTransform']! return (this['~hasTransform'] = hasTransform(schema)) }, get hasRef() { if ('~hasRef' in this) return this['~hasRef']! return (this['~hasRef'] = hasRef(schema)) }, '~hasRef': doesHaveRef } as ElysiaTypeCheck) // if (cacheKey) caches[cacheKey] = compiled return compiled as any } export const isUnion = (schema: TSchema) => schema[Kind] === 'Union' || (!schema.schema && !!schema.anyOf) // Returns all properties as a flat map, handling Union/Intersect // See: https://github.com/sinclairzx81/typebox/blob/0.34.3/src/type/indexed/indexed.ts#L152-L162 export const getSchemaProperties = ( schema: TAnySchema | undefined ): Record | undefined => { if (!schema) return undefined if (schema.properties) return schema.properties const members = schema.allOf ?? schema.anyOf ?? schema.oneOf if (members) { const result: Record = {} for (const member of members) { const props = getSchemaProperties(member) if (props) Object.assign(result, props) } return Object.keys(result).length > 0 ? result : undefined } return undefined } export const mergeObjectSchemas = ( schemas: TSchema[] ): { schema: TObject | undefined notObjects: TSchema[] } => { if (schemas.length === 0) { return { schema: undefined, notObjects: [] } } if (schemas.length === 1) return schemas[0].type === 'object' ? { schema: schemas[0] as TObject, notObjects: [] } : { schema: undefined, notObjects: schemas } let newSchema: TObject const notObjects = [] let additionalPropertiesIsTrue = false let additionalPropertiesIsFalse = false for (const schema of schemas) { if (schema.type !== 'object') { notObjects.push(schema) continue } if ('additionalProperties' in schema) { if (schema.additionalProperties === true) additionalPropertiesIsTrue = true else if (schema.additionalProperties === false) additionalPropertiesIsFalse = true } if (!newSchema!) { newSchema = schema as TObject continue } newSchema = { ...newSchema, ...schema, properties: { ...newSchema.properties, ...schema.properties }, required: [ ...(newSchema?.required ?? []), ...(schema.required ?? []) ] } as TObject } if (newSchema!) { if (newSchema.required) newSchema.required = [...new Set(newSchema.required)] if (additionalPropertiesIsFalse) newSchema.additionalProperties = false else if (additionalPropertiesIsTrue) newSchema.additionalProperties = true } return { schema: newSchema!, notObjects } } export const getResponseSchemaValidator = ( s: InputSchema['response'] | undefined, { models = {}, modules, dynamic = false, normalize = false, additionalProperties = false, validators = [], sanitize }: { modules: TModule models?: Record additionalProperties?: boolean dynamic?: boolean normalize?: ElysiaConfig<''>['normalize'] validators?: StandaloneInputSchema['response'][] sanitize?: () => ExactMirrorInstruction['sanitize'] } ): Record> | undefined => { validators = validators.filter((x) => x) if (!s) { if (!validators?.length) return undefined as any s = validators[0] as any validators = validators.slice(1) } let maybeSchemaOrRecord: | TSchema | StandardSchemaV1Like | Record // @ts-ignore if (typeof s !== 'string') maybeSchemaOrRecord = s! else { maybeSchemaOrRecord = // @ts-expect-error private property modules && s in modules.$defs ? (modules as TModule<{}, {}>).Import(s as never) : models[s] if (!maybeSchemaOrRecord) return undefined as any } if (!maybeSchemaOrRecord) return if (Kind in maybeSchemaOrRecord || '~standard' in maybeSchemaOrRecord) return { 200: getSchemaValidator( maybeSchemaOrRecord as TSchema | StandardSchemaV1Like, { modules, models, additionalProperties, dynamic, normalize, coerce: false, additionalCoerce: [], validators: validators.map((x) => x![200]), sanitize } )! } const record: Record> = {} Object.keys(maybeSchemaOrRecord).forEach((status): TSchema | undefined => { if (isNaN(+status)) return const maybeNameOrSchema = maybeSchemaOrRecord[+status] if (typeof maybeNameOrSchema === 'string') { if (maybeNameOrSchema in models) { const schema = models[maybeNameOrSchema] if (!schema) return // Inherits model maybe already compiled record[+status] = Kind in schema || '~standard' in schema ? getSchemaValidator(schema as TSchema, { modules, models, additionalProperties, dynamic, normalize, coerce: false, additionalCoerce: [], validators: validators.map((x) => x![+status]), sanitize })! : (schema as ElysiaTypeCheck) } return undefined } // Inherits model maybe already compiled record[+status] = Kind in maybeNameOrSchema || '~standard' in maybeNameOrSchema ? getSchemaValidator(maybeNameOrSchema as TSchema, { modules, models, additionalProperties, dynamic, normalize, coerce: false, additionalCoerce: [], validators: validators.map((x) => x![+status]), sanitize }) : (maybeNameOrSchema as ElysiaTypeCheck) }) return record } export const getCookieValidator = ({ validator, modules, defaultConfig = {}, config, dynamic, normalize = false, models, validators, sanitize }: { validator: | TSchema | StandardSchemaV1Like | ElysiaTypeCheck | string | undefined modules: TModule defaultConfig: CookieOptions | undefined config: CookieOptions dynamic: boolean normalize: ElysiaConfig<''>['normalize'] | undefined models: Record | undefined validators?: InputSchema['cookie'][] sanitize?: () => ExactMirrorInstruction['sanitize'] }) => { let cookieValidator = // @ts-ignore validator?.provider ? (validator as ElysiaTypeCheck) : // @ts-ignore getSchemaValidator(validator, { modules, dynamic, models, normalize, additionalProperties: true, coerce: true, additionalCoerce: stringToStructureCoercions(), validators, sanitize }) if (cookieValidator) cookieValidator.config = mergeCookie(cookieValidator.config, config) else { cookieValidator = getSchemaValidator(t.Cookie(t.Any()), { modules, dynamic, models, additionalProperties: true, validators, sanitize }) cookieValidator.config = defaultConfig } return cookieValidator } /** * This function will return the type of unioned if all unioned type is the same. * It's intent to use for content-type mapping only * * ```ts * t.Union([ * t.Object({ * password: t.String() * }), * t.Object({ * token: t.String() * }) * ]) * ``` */ // const getUnionedType = (validator: TypeCheck | undefined) => { // if (!validator) return // // @ts-ignore // const schema = validator?.schema ?? validator // if (schema && 'anyOf' in schema) { // let foundDifference = false // const type: string = schema.anyOf[0].type // for (const validator of schema.anyOf as { type: string }[]) { // if (validator.type !== type) { // foundDifference = true // break // } // } // if (!foundDifference) return type // } // // @ts-ignore // return validator.schema?.type // } export const unwrapImportSchema = (schema: TSchema): TSchema => schema && schema[Kind] === 'Import' && schema.$defs[schema.$ref][Kind] === 'Object' ? schema.$defs[schema.$ref] : schema ================================================ FILE: src/sucrose.ts ================================================ /* eslint-disable @typescript-eslint/no-unused-vars */ /* eslint-disable no-constant-condition */ import { checksum } from './utils' import { isBun, isCloudflareWorker } from './universal/utils' import type { Handler, HookContainer, LifeCycleStore } from './types' export namespace Sucrose { export interface Inference { query: boolean headers: boolean body: boolean cookie: boolean set: boolean server: boolean route: boolean url: boolean path: boolean } export interface LifeCycle extends Partial { handler?: Handler } export interface Settings { /** * If no sucrose usage is found in time * it's likely that server is either idle or * no new compilation is happening * clear the cache to free up memory * * @default 4 * 60 * 1000 + 55 * 1000 (4 minutes 55 seconds) */ gcTime?: number | null } } /** * Separate stringified function body and parameter * * @example * ```typescript * separateFunction('async ({ hello }) => { return hello }') // => ['({ hello })', '{ return hello }'] * ``` */ export const separateFunction = ( code: string ): [string, string, { isArrowReturn: boolean }] => { // Remove async keyword without removing space (both minify and non-minify) if (code.startsWith('async')) code = code.slice(5) code = code.trimStart() let index = -1 // JSC: Starts with '(', is an arrow function if (code.charCodeAt(0) === 40) { index = code.indexOf('=>', code.indexOf(')')) if (index !== -1) { let bracketEndIndex = index // Walk back to find bracket end while (bracketEndIndex > 0) if (code.charCodeAt(--bracketEndIndex) === 41) break let body = code.slice(index + 2) if (body.charCodeAt(0) === 32) body = body.trimStart() return [ code.slice(1, bracketEndIndex), body, { isArrowReturn: body.charCodeAt(0) !== 123 } ] } } // V8: bracket is removed for 1 parameter arrow function if (/^(\w+)=>/g.test(code)) { index = code.indexOf('=>') if (index !== -1) { let body = code.slice(index + 2) if (body.charCodeAt(0) === 32) body = body.trimStart() return [ code.slice(0, index), body, { isArrowReturn: body.charCodeAt(0) !== 123 } ] } } // Using function keyword if (code.startsWith('function')) { index = code.indexOf('(') const end = code.indexOf(')') return [ code.slice(index + 1, end), code.slice(end + 2), { isArrowReturn: false } ] } // Probably Declare as method const start = code.indexOf('(') if (start !== -1) { const sep = code.indexOf('\n', 2) const parameter = code.slice(0, sep) const end = parameter.lastIndexOf(')') + 1 const body = code.slice(sep + 1) return [ parameter.slice(start, end), '{' + body, { isArrowReturn: false } ] } // Unknown case const x = code.split('\n', 2) return [x[0], x[1], { isArrowReturn: false }] } /** * Get range between bracket pair * * @example * ```typescript * bracketPairRange('hello: { world: { a } }, elysia') // [6, 20] * ``` */ export const bracketPairRange = (parameter: string): [number, number] => { const start = parameter.indexOf('{') if (start === -1) return [-1, 0] let end = start + 1 let deep = 1 for (; end < parameter.length; end++) { const char = parameter.charCodeAt(end) // Open bracket if (char === 123) deep++ // Close bracket else if (char === 125) deep-- if (deep === 0) break } if (deep !== 0) return [0, parameter.length] return [start, end + 1] } /** * Similar to `bracketPairRange` but in reverse order * Get range between bracket pair from end to beginning * * @example * ```typescript * bracketPairRange('hello: { world: { a } }, elysia') // [6, 20] * ``` */ export const bracketPairRangeReverse = ( parameter: string ): [number, number] => { const end = parameter.lastIndexOf('}') if (end === -1) return [-1, 0] let start = end - 1 let deep = 1 for (; start >= 0; start--) { const char = parameter.charCodeAt(start) // Open bracket if (char === 125) deep++ // Close bracket else if (char === 123) deep-- if (deep === 0) break } if (deep !== 0) return [-1, 0] return [start, end + 1] } export const removeColonAlias = (parameter: string) => { while (true) { const start = parameter.indexOf(':') if (start === -1) break let end = parameter.indexOf(',', start) if (end === -1) end = parameter.indexOf('}', start) - 1 if (end === -2) end = parameter.length parameter = parameter.slice(0, start) + parameter.slice(end) } return parameter } /** * Retrieve only root parameters of a function * * @example * ```typescript * retrieveRootParameters('({ hello: { world: { a } }, elysia })') // => { * parameters: ['hello', 'elysia'], * hasParenthesis: true * } * ``` */ export const retrieveRootparameters = (parameter: string) => { let hasParenthesis = false // Remove () from parameter if (parameter.charCodeAt(0) === 40) parameter = parameter.slice(1, -1) // Remove {} from parameter if (parameter.charCodeAt(0) === 123) { hasParenthesis = true parameter = parameter.slice(1, -1) } parameter = parameter.replace(/( |\t|\n)/g, '').trim() let parameters = [] // Object destructuring while (true) { // eslint-disable-next-line prefer-const let [start, end] = bracketPairRange(parameter) if (start === -1) break // Remove colon from object structuring cast parameters.push(parameter.slice(0, start - 1)) if (parameter.charCodeAt(end) === 44) end++ parameter = parameter.slice(end) } parameter = removeColonAlias(parameter) if (parameter) parameters = parameters.concat(parameter.split(',')) const parameterMap: Record = Object.create(null) for (const p of parameters) { if (p.indexOf(',') === -1) { parameterMap[p] = true continue } for (const q of p.split(',')) parameterMap[q.trim()] = true } return { hasParenthesis, parameters: parameterMap } } /** * Find inference from parameter * * @param parameter stringified parameter */ export const findParameterReference = ( parameter: string, inference: Sucrose.Inference ) => { const { parameters, hasParenthesis } = retrieveRootparameters(parameter) // Check if root is an object destructuring if (parameters.query) inference.query = true if (parameters.headers) inference.headers = true if (parameters.body) inference.body = true if (parameters.cookie) inference.cookie = true if (parameters.set) inference.set = true if (parameters.server) inference.server = true if (parameters.route) inference.route = true if (parameters.url) inference.url = true if (parameters.path) inference.path = true if (hasParenthesis) return `{ ${Object.keys(parameters).join(', ')} }` return Object.keys(parameters).join(', ') } const findEndIndex = ( type: string, content: string, index?: number | undefined ) => { const regex = new RegExp( `${type.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}[\\n\\t,; ]` ) if (index !== undefined) regex.lastIndex = index const match = regex.exec(content) return match ? match.index : -1 } const findEndQueryBracketIndex = ( type: string, content: string, index?: number | undefined ) => { const bracketEndIndex = content.indexOf(type + ']', index) const singleQuoteIndex = content.indexOf(type + "'", index) const doubleQuoteIndex = content.indexOf(type + '"', index) // Pick the smallest index that is not -1 or 0 return ( [bracketEndIndex, singleQuoteIndex, doubleQuoteIndex] .filter((i) => i > 0) .sort((a, b) => a - b)[0] || -1 ) } /** * Find alias of variable from function body * * @example * ```typescript * findAlias('body', '{ const a = body, b = body }') // => ['a', 'b'] * ``` */ export const findAlias = (type: string, body: string, depth = 0) => { if (depth > 5) return [] const aliases: string[] = [] let content = body while (true) { let index = findEndIndex(' = ' + type, content) // V8 engine minified the code if (index === -1) index = findEndIndex('=' + type, content) if (index === -1) { /** * Check if pattern is at the end of the string * * @example * ```typescript * 'const a = body' // true * ``` **/ let lastIndex = content.indexOf(' = ' + type) if (lastIndex === -1) lastIndex = content.indexOf('=' + type) if (lastIndex + 3 + type.length !== content.length) break index = lastIndex } const part = content.slice(0, index) // V8 engine minified the code const lastPart = part.lastIndexOf(' ') /** * aliased variable last character * * @example * ```typescript * const { hello } = body // } is the last character * ``` **/ let variable = part.slice(lastPart !== -1 ? lastPart + 1 : -1) // Variable is using object destructuring, find the bracket pair if (variable === '}') { const [start, end] = bracketPairRangeReverse(part) aliases.push(removeColonAlias(content.slice(start, end))) content = content.slice(index + 3 + type.length) continue } // Remove comma while (variable.charCodeAt(0) === 44) variable = variable.slice(1) while (variable.charCodeAt(0) === 9) variable = variable.slice(1) if (!variable.includes('(')) aliases.push(variable) content = content.slice(index + 3 + type.length) } for (const alias of aliases) { if (alias.charCodeAt(0) === 123) continue const deepAlias = findAlias(alias, body) if (deepAlias.length > 0) aliases.push(...deepAlias) } return aliases } // ? This is normalized to dot notation in Bun // const accessor = (parent: T, prop: P) => // [ // parent + '.' + prop, // parent + '["' + prop + '"]', // parent + "['" + prop + "']" // ] as const export const extractMainParameter = (parameter: string) => { if (!parameter) return if (parameter.charCodeAt(0) !== 123) return parameter parameter = parameter.slice(2, -2) const hasComma = parameter.includes(',') if (!hasComma) { const index = parameter.indexOf('...') // This happens when spread operator is used as the only parameter if (index !== -1) return parameter.slice(parameter.indexOf('...') + 3) return } const spreadIndex = parameter.indexOf('...') if (spreadIndex === -1) return // Spread parameter is always the last parameter, no need for further checking return parameter.slice(spreadIndex + 3).trimEnd() } /** * Analyze if context is mentioned in body */ export const inferBodyReference = ( code: string, aliases: string[], inference: Sucrose.Inference ) => { const access = (type: string, alias: string) => new RegExp( `${alias}\\.(${type})|${alias}\\["${type}"\\]|${alias}\\['${type}'\\]` ).test(code) for (const alias of aliases) { if (!alias) continue // Scan object destructured property if (alias.charCodeAt(0) === 123) { const parameters = retrieveRootparameters(alias).parameters if (parameters.query) inference.query = true if (parameters.headers) inference.headers = true if (parameters.body) inference.body = true if (parameters.cookie) inference.cookie = true if (parameters.set) inference.set = true if (parameters.server) inference.server = true if (parameters.url) inference.url = true if (parameters.route) inference.route = true if (parameters.path) inference.path = true continue } if ( !inference.query && (access('query', alias) || code.includes('return ' + alias) || code.includes('return ' + alias + '.query')) ) inference.query = true if (!inference.headers && access('headers', alias)) inference.headers = true if (!inference.body && access('body', alias)) inference.body = true if (!inference.cookie && access('cookie', alias)) inference.cookie = true if (!inference.set && access('set', alias)) inference.set = true if (!inference.server && access('server', alias)) inference.server = true if (!inference.route && access('route', alias)) inference.route = true if (!inference.url && access('url', alias)) inference.url = true if (!inference.path && access('path', alias)) inference.path = true if ( inference.query && inference.headers && inference.body && inference.cookie && inference.set && inference.server && inference.route && inference.url && inference.path ) break } return aliases } export const removeDefaultParameter = (parameter: string) => { while (true) { const index = parameter.indexOf('=') if (index === -1) break const commaIndex = parameter.indexOf(',', index) const bracketIndex = parameter.indexOf('}', index) const end = [commaIndex, bracketIndex] .filter((i) => i > 0) .sort((a, b) => a - b)[0] || -1 if (end === -1) { parameter = parameter.slice(0, index) break } parameter = parameter.slice(0, index) + parameter.slice(end) } return parameter .split(',') .map((i) => i.trim()) .join(', ') } export const isContextPassToFunction = ( context: string, body: string, inference: Sucrose.Inference ) => { // ! Function is passed to another function, assume as all is accessed try { const captureFunction = new RegExp( `\\w\\((?:.*?)?${context}(?:.*?)?\\)`, 'gs' ) const exactParameter = new RegExp(`${context}(,|\\))`, 'gs') const length = body.length let fn fn = captureFunction.exec(body) + '' while ( captureFunction.lastIndex !== 0 && captureFunction.lastIndex < length + (fn ? fn.length : 0) ) { if (fn && exactParameter.test(fn)) { inference.query = true inference.headers = true inference.body = true inference.cookie = true inference.set = true inference.server = true inference.url = true inference.route = true inference.path = true return true } fn = captureFunction.exec(body) + '' } /* Since JavaScript engine already format the code (removing whitespace, newline, etc.), we can safely assume that the next character is either a closing bracket or a comma if the function is passed to another function */ const nextChar = body.charCodeAt(captureFunction.lastIndex) if (nextChar === 41 || nextChar === 44) { inference.query = true inference.headers = true inference.body = true inference.cookie = true inference.set = true inference.server = true inference.url = true inference.route = true inference.path = true return true } return false } catch (error) { console.log( '[Sucrose] warning: unexpected isContextPassToFunction error, you may continue development as usual but please report the following to maintainers:' ) console.log('--- body ---') console.log(body) console.log('--- context ---') console.log(context) return true } } let pendingGC: Timer | undefined let caches = >{} export const clearSucroseCache = (delay: Sucrose.Settings['gcTime']) => { // Can't setTimeout outside fetch in Cloudflare Worker if (delay === null || isCloudflareWorker()) return if (delay === undefined) delay = 4 * 60 * 1000 + 55 * 1000 if (pendingGC) clearTimeout(pendingGC) pendingGC = setTimeout(() => { caches = {} pendingGC = undefined if (isBun) Bun.gc(false) }, delay) pendingGC.unref?.() } export const mergeInference = (a: Sucrose.Inference, b: Sucrose.Inference) => { return { body: a.body || b.body, cookie: a.cookie || b.cookie, headers: a.headers || b.headers, query: a.query || b.query, set: a.set || b.set, server: a.server || b.server, url: a.url || b.url, route: a.route || b.route, path: a.path || b.path } } export const sucrose = ( lifeCycle: Sucrose.LifeCycle, inference: Sucrose.Inference = { query: false, headers: false, body: false, cookie: false, set: false, server: false, url: false, route: false, path: false }, settings: Sucrose.Settings = {} ): Sucrose.Inference => { const events = <(Handler | HookContainer)[]>[] if (lifeCycle.request?.length) events.push(...lifeCycle.request) if (lifeCycle.beforeHandle?.length) events.push(...lifeCycle.beforeHandle) if (lifeCycle.parse?.length) events.push(...lifeCycle.parse) if (lifeCycle.error?.length) events.push(...lifeCycle.error) if (lifeCycle.transform?.length) events.push(...lifeCycle.transform) if (lifeCycle.afterHandle?.length) events.push(...lifeCycle.afterHandle) if (lifeCycle.mapResponse?.length) events.push(...lifeCycle.mapResponse) if (lifeCycle.afterResponse?.length) events.push(...lifeCycle.afterResponse) if (lifeCycle.handler && typeof lifeCycle.handler === 'function') events.push(lifeCycle.handler as Handler) for (let i = 0; i < events.length; i++) { const e = events[i] if (!e) continue const event = typeof e === 'object' ? e.fn : e // parse can be either a function or string if (typeof event !== 'function') continue const content = event.toString() const key = checksum(content) const cachedInference = caches[key] if (cachedInference) { inference = mergeInference(inference, cachedInference) continue } // If no sucrose usage is found in 4:55 minutes // it's likely that server is either idle or // no new compilation is happening // Clear the cache to free up memory clearSucroseCache(settings.gcTime) const fnInference: Sucrose.Inference = { query: false, headers: false, body: false, cookie: false, set: false, server: false, url: false, route: false, path: false } const [parameter, body] = separateFunction(content) const rootParameters = findParameterReference(parameter, fnInference) const mainParameter = extractMainParameter(rootParameters) if (mainParameter) { const aliases = findAlias(mainParameter, body.slice(1, -1)) aliases.splice(0, -1, mainParameter) let code = body if ( code.charCodeAt(0) === 123 && code.charCodeAt(body.length - 1) === 125 ) code = code.slice(1, -1).trim() if (!isContextPassToFunction(mainParameter, code, fnInference)) inferBodyReference(code, aliases, fnInference) if ( !fnInference.query && code.includes('return ' + mainParameter + '.query') ) fnInference.query = true } if (!caches[key]) caches[key] = fnInference inference = mergeInference(inference, fnInference) if ( inference.query && inference.headers && inference.body && inference.cookie && inference.set && inference.server && inference.url && inference.route && inference.path ) break } return inference } ================================================ FILE: src/trace.ts ================================================ import { ELYSIA_REQUEST_ID } from './utils' import type { Context } from './context' import type { Prettify, RouteSchema, SingletonBase } from './types' export type TraceEvent = | 'request' | 'parse' | 'transform' | 'beforeHandle' | 'handle' | 'afterHandle' | 'mapResponse' | 'afterResponse' | 'error' export type TraceStream = { id: number event: TraceEvent type: 'begin' | 'end' begin: number name?: string total?: number } type TraceEndDetail = { /** * Timestamp of a function after it's executed since the server start */ end: TraceProcess<'end'> /** * Error that was thrown in the lifecycle */ error: Error | null /** * Elapsed time of the lifecycle */ elapsed: number } export type TraceProcess< Type extends 'begin' | 'end' = 'begin' | 'end', WithChildren extends boolean = true > = Type extends 'begin' ? Prettify< { /** * Function name */ name: string /** * Timestamp of a function is called since the server start */ begin: number /** * Timestamp of a function after it's executed since the server start */ end: Promise /** * Error that was thrown in the lifecycle */ error: Promise /** * Listener to intercept the end of the lifecycle * * If you want to mutate the context, you must do it in this function * as there's a lock mechanism to ensure the context is mutate successfully */ onStop( /** * A callback function that will be called when the function ends * * If you want to mutate the context, you must do it in this function * as there's a lock mechanism to ensure the context is mutate successfully */ callback?: (detail: TraceEndDetail) => unknown ): Promise } & (WithChildren extends true ? { /** * total number of lifecycle's children and * total number of `onEvent` will be called * if there were no early exists or error thrown */ total: number /** * Listener to intercept each child lifecycle */ onEvent( /** * Callback function that will be called for when each child start */ callback?: ( process: TraceProcess<'begin', false> ) => unknown ): Promise } : { /** * Index of the child event */ index: number }) > : number export type TraceListener = ( callback?: (process: TraceProcess<'begin'>) => unknown ) => Promise> export type TraceHandler< in out Route extends RouteSchema = {}, in out Singleton extends SingletonBase = { decorator: {} store: {} derive: {} resolve: {} } > = { ( lifecycle: Prettify< { id: number context: Context set: Context['set'] time: number store: Singleton['store'] response: unknown } & { [x in `on${Capitalize}`]: TraceListener } > ): unknown } export const ELYSIA_TRACE = Symbol('ElysiaTrace') const createProcess = () => { const { promise, resolve } = Promise.withResolvers() const { promise: end, resolve: resolveEnd } = Promise.withResolvers() const { promise: error, resolve: resolveError } = Promise.withResolvers() const callbacks = [] const callbacksEnd = [] return [ (callback?: Function) => { if (callback) callbacks.push(callback) return promise }, (process: TraceStream) => { const processes = <((callback?: Function) => Promise)[]>[] const resolvers = <((process: TraceStream) => () => void)[]>[] // When error is return but not thrown let groupError: Error | null = null for (let i = 0; i < (process.total ?? 0); i++) { const { promise, resolve } = Promise.withResolvers() const { promise: end, resolve: resolveEnd } = Promise.withResolvers() const { promise: error, resolve: resolveError } = Promise.withResolvers() const callbacks = [] const callbacksEnd = [] processes.push((callback?: Function) => { if (callback) callbacks.push(callback) return promise }) resolvers.push((process: TraceStream) => { const result = { ...process, end, error, index: i, onStop(callback?: Function) { if (callback) callbacksEnd.push(callback) return end } } as any resolve(result) for (let i = 0; i < callbacks.length; i++) callbacks[i](result) return (error: Error | null = null) => { const end = performance.now() // Catch return error if (error) groupError = error const detail = { end, error, get elapsed() { return end - process.begin } } for (let i = 0; i < callbacksEnd.length; i++) callbacksEnd[i](detail) resolveEnd(end) resolveError(error) } }) } const result = { ...process, end, error, onEvent(callback?: Function) { for (let i = 0; i < processes.length; i++) processes[i](callback) }, onStop(callback?: Function) { if (callback) callbacksEnd.push(callback) return end } } as any resolve(result) for (let i = 0; i < callbacks.length; i++) callbacks[i](result) return { resolveChild: resolvers, resolve(error: Error | null = null) { const end = performance.now() // If error is return, parent group will not catch an error // but the child group will catch the error if (!error && groupError) error = groupError const detail = { end, error, get elapsed() { return end - process.begin } } for (let i = 0; i < callbacksEnd.length; i++) callbacksEnd[i](detail) resolveEnd(end) resolveError(error) } } } ] as const } export const createTracer = (traceListener: TraceHandler) => { return (context: Context) => { const [onRequest, resolveRequest] = createProcess() const [onParse, resolveParse] = createProcess() const [onTransform, resolveTransform] = createProcess() const [onBeforeHandle, resolveBeforeHandle] = createProcess() const [onHandle, resolveHandle] = createProcess() const [onAfterHandle, resolveAfterHandle] = createProcess() const [onError, resolveError] = createProcess() const [onMapResponse, resolveMapResponse] = createProcess() const [onAfterResponse, resolveAfterResponse] = createProcess() traceListener({ // @ts-ignore id: context[ELYSIA_REQUEST_ID], context, set: context.set, // @ts-ignore onRequest, // @ts-ignore onParse, // @ts-ignore onTransform, // @ts-ignore onBeforeHandle, // @ts-ignore onHandle, // @ts-ignore onAfterHandle, // @ts-ignore onMapResponse, // @ts-ignore onAfterResponse, // @ts-ignore onError, time: Date.now(), store: context.store }) // ? This is pass to compiler return { request: resolveRequest, parse: resolveParse, transform: resolveTransform, beforeHandle: resolveBeforeHandle, handle: resolveHandle, afterHandle: resolveAfterHandle, error: resolveError, mapResponse: resolveMapResponse, afterResponse: resolveAfterResponse } } } ================================================ FILE: src/type-system/format.ts ================================================ import { FormatRegistry } from '@sinclair/typebox' /** * ? Fork of ajv-formats without ajv as dependencies * * @see https://github.com/ajv-validator/ajv-formats/blob/master/src/formats.ts **/ /* eslint-disable no-control-regex */ export type FormatName = | 'date' | 'time' | 'date-time' | 'iso-time' | 'iso-date-time' | 'duration' | 'uri' | 'uri-reference' | 'uri-template' | 'url' | 'email' | 'hostname' | 'ipv4' | 'ipv6' | 'regex' | 'uuid' | 'json-pointer' | 'json-pointer-uri-fragment' | 'relative-json-pointer' | 'byte' | 'int32' | 'int64' | 'float' | 'double' | 'password' | 'binary' export const fullFormats = { // date: http://tools.ietf.org/html/rfc3339#section-5.6 date, // date-time: http://tools.ietf.org/html/rfc3339#section-5.6 time: getTime(true), 'date-time': getDateTime(true), 'iso-time': getTime(false), 'iso-date-time': getDateTime(false), // duration: https://tools.ietf.org/html/rfc3339#appendix-A duration: /^P(?!$)((\d+Y)?(\d+M)?(\d+D)?(T(?=\d)(\d+H)?(\d+M)?(\d+S)?)?|(\d+W)?)$/, uri, 'uri-reference': /^(?:[a-z][a-z0-9+\-.]*:)?(?:\/?\/(?:(?:[a-z0-9\-._~!$&'()*+,;=:]|%[0-9a-f]{2})*@)?(?:\[(?:(?:(?:(?:[0-9a-f]{1,4}:){6}|::(?:[0-9a-f]{1,4}:){5}|(?:[0-9a-f]{1,4})?::(?:[0-9a-f]{1,4}:){4}|(?:(?:[0-9a-f]{1,4}:){0,1}[0-9a-f]{1,4})?::(?:[0-9a-f]{1,4}:){3}|(?:(?:[0-9a-f]{1,4}:){0,2}[0-9a-f]{1,4})?::(?:[0-9a-f]{1,4}:){2}|(?:(?:[0-9a-f]{1,4}:){0,3}[0-9a-f]{1,4})?::[0-9a-f]{1,4}:|(?:(?:[0-9a-f]{1,4}:){0,4}[0-9a-f]{1,4})?::)(?:[0-9a-f]{1,4}:[0-9a-f]{1,4}|(?:(?:25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(?:25[0-5]|2[0-4]\d|[01]?\d\d?))|(?:(?:[0-9a-f]{1,4}:){0,5}[0-9a-f]{1,4})?::[0-9a-f]{1,4}|(?:(?:[0-9a-f]{1,4}:){0,6}[0-9a-f]{1,4})?::)|[Vv][0-9a-f]+\.[a-z0-9\-._~!$&'()*+,;=:]+)\]|(?:(?:25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(?:25[0-5]|2[0-4]\d|[01]?\d\d?)|(?:[a-z0-9\-._~!$&'"()*+,;=]|%[0-9a-f]{2})*)(?::\d*)?(?:\/(?:[a-z0-9\-._~!$&'"()*+,;=:@]|%[0-9a-f]{2})*)*|\/(?:(?:[a-z0-9\-._~!$&'"()*+,;=:@]|%[0-9a-f]{2})+(?:\/(?:[a-z0-9\-._~!$&'"()*+,;=:@]|%[0-9a-f]{2})*)*)?|(?:[a-z0-9\-._~!$&'"()*+,;=:@]|%[0-9a-f]{2})+(?:\/(?:[a-z0-9\-._~!$&'"()*+,;=:@]|%[0-9a-f]{2})*)*)?(?:\?(?:[a-z0-9\-._~!$&'"()*+,;=:@/?]|%[0-9a-f]{2})*)?(?:#(?:[a-z0-9\-._~!$&'"()*+,;=:@/?]|%[0-9a-f]{2})*)?$/i, // uri-template: https://tools.ietf.org/html/rfc6570 'uri-template': /^(?:(?:[^\x00-\x20"'<>%\\^`{|}]|%[0-9a-f]{2})|\{[+#./;?&=,!@|]?(?:[a-z0-9_]|%[0-9a-f]{2})+(?::[1-9][0-9]{0,3}|\*)?(?:,(?:[a-z0-9_]|%[0-9a-f]{2})+(?::[1-9][0-9]{0,3}|\*)?)*\})*$/i, // For the source: https://gist.github.com/dperini/729294 // For test cases: https://mathiasbynens.be/demo/url-regex url: /^(?:https?|ftp):\/\/(?:[^\s:@]+(?::[^\s@]*)?@)?(?:(?!(?:10|127)(?:\.\d{1,3}){3})(?!(?:169\.254|192\.168)(?:\.\d{1,3}){2})(?!172\.(?:1[6-9]|2\d|3[0-1])(?:\.\d{1,3}){2})(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.(?:[1-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(?:(?:[a-z0-9\u{00a1}-\u{ffff}]+-)*[a-z0-9\u{00a1}-\u{ffff}]+)(?:\.(?:[a-z0-9\u{00a1}-\u{ffff}]+-)*[a-z0-9\u{00a1}-\u{ffff}]+)*(?:\.(?:[a-z\u{00a1}-\u{ffff}]{2,})))(?::\d{2,5})?(?:\/[^\s]*)?$/iu, email: /^[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/i, hostname: /^(?=.{1,253}\.?$)[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?(?:\.[a-z0-9](?:[-0-9a-z]{0,61}[0-9a-z])?)*\.?$/i, // optimized https://www.safaribooksonline.com/library/view/regular-expressions-cookbook/9780596802837/ch07s16.html ipv4: /^(?:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)\.){3}(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)$/, ipv6: /^((([0-9a-f]{1,4}:){7}([0-9a-f]{1,4}|:))|(([0-9a-f]{1,4}:){6}(:[0-9a-f]{1,4}|((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9a-f]{1,4}:){5}(((:[0-9a-f]{1,4}){1,2})|:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9a-f]{1,4}:){4}(((:[0-9a-f]{1,4}){1,3})|((:[0-9a-f]{1,4})?:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9a-f]{1,4}:){3}(((:[0-9a-f]{1,4}){1,4})|((:[0-9a-f]{1,4}){0,2}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9a-f]{1,4}:){2}(((:[0-9a-f]{1,4}){1,5})|((:[0-9a-f]{1,4}){0,3}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9a-f]{1,4}:){1}(((:[0-9a-f]{1,4}){1,6})|((:[0-9a-f]{1,4}){0,4}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(:(((:[0-9a-f]{1,4}){1,7})|((:[0-9a-f]{1,4}){0,5}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:)))$/i, regex, // uuid: http://tools.ietf.org/html/rfc4122 uuid: /^(?:urn:uuid:)?[0-9a-f]{8}-(?:[0-9a-f]{4}-){3}[0-9a-f]{12}$/i, // JSON-pointer: https://tools.ietf.org/html/rfc6901 // uri fragment: https://tools.ietf.org/html/rfc3986#appendix-A 'json-pointer': /^(?:\/(?:[^~/]|~0|~1)*)*$/, 'json-pointer-uri-fragment': /^#(?:\/(?:[a-z0-9_\-.!$&'()*+,;:=@]|%[0-9a-f]{2}|~0|~1)*)*$/i, // relative JSON-pointer: http://tools.ietf.org/html/draft-luff-relative-json-pointer-00 'relative-json-pointer': /^(?:0|[1-9][0-9]*)(?:#|(?:\/(?:[^~/]|~0|~1)*)*)$/, // the following formats are used by the openapi specification: https://spec.openapis.org/oas/v3.0.0#data-types // byte: https://github.com/miguelmota/is-base64 byte, // signed 32 bit integer int32: { type: 'number', validate: validateInt32 }, // signed 64 bit integer int64: { type: 'number', validate: validateInt64 }, // C-type float float: { type: 'number', validate: validateNumber }, // C-type double double: { type: 'number', validate: validateNumber }, // hint to the UI to hide input strings password: true, // unchecked string payload binary: true } as const function isLeapYear(year: number): boolean { // https://tools.ietf.org/html/rfc3339#appendix-C return year % 4 === 0 && (year % 100 !== 0 || year % 400 === 0) } const DATE = /^(\d\d\d\d)-(\d\d)-(\d\d)$/ const DAYS = [0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31] function date(str: string): boolean { // full-date from http://tools.ietf.org/html/rfc3339#section-5.6 const matches: string[] | null = DATE.exec(str) if (!matches) return false const year: number = +matches[1] const month: number = +matches[2] const day: number = +matches[3] return ( month >= 1 && month <= 12 && day >= 1 && day <= (month === 2 && isLeapYear(year) ? 29 : DAYS[month]) ) } const TIME = /^(\d\d):(\d\d):(\d\d(?:\.\d+)?)(z|([+-])(\d\d)(?::?(\d\d))?)?$/i function getTime(strictTimeZone?: boolean): (str: string) => boolean { return function time(str: string): boolean { const matches: string[] | null = TIME.exec(str) if (!matches) return false const hr: number = +matches[1] const min: number = +matches[2] const sec: number = +matches[3] const tz: string | undefined = matches[4] const tzSign: number = matches[5] === '-' ? -1 : 1 const tzH: number = +(matches[6] || 0) const tzM: number = +(matches[7] || 0) if (tzH > 23 || tzM > 59 || (strictTimeZone && !tz)) return false if (hr <= 23 && min <= 59 && sec < 60) return true // leap second const utcMin = min - tzM * tzSign const utcHr = hr - tzH * tzSign - (utcMin < 0 ? 1 : 0) return ( (utcHr === 23 || utcHr === -1) && (utcMin === 59 || utcMin === -1) && sec < 61 ) } } export const parseDateTimeEmptySpace = (str: string) => { if (str.charCodeAt(str.length - 6) === 32) return str.slice(0, -6) + '+' + str.slice(-5) return str } const DATE_TIME_SEPARATOR = /t|\s/i function getDateTime(strictTimeZone?: boolean): (str: string) => boolean { const time = getTime(strictTimeZone) return function date_time(str: string): boolean { // http://tools.ietf.org/html/rfc3339#section-5.6 const dateTime: string[] = str.split(DATE_TIME_SEPARATOR) return dateTime.length === 2 && date(dateTime[0]) && time(dateTime[1]) } } const NOT_URI_FRAGMENT = /\/|:/ const URI = /^(?:[a-z][a-z0-9+\-.]*:)(?:\/?\/(?:(?:[a-z0-9\-._~!$&'()*+,;=:]|%[0-9a-f]{2})*@)?(?:\[(?:(?:(?:(?:[0-9a-f]{1,4}:){6}|::(?:[0-9a-f]{1,4}:){5}|(?:[0-9a-f]{1,4})?::(?:[0-9a-f]{1,4}:){4}|(?:(?:[0-9a-f]{1,4}:){0,1}[0-9a-f]{1,4})?::(?:[0-9a-f]{1,4}:){3}|(?:(?:[0-9a-f]{1,4}:){0,2}[0-9a-f]{1,4})?::(?:[0-9a-f]{1,4}:){2}|(?:(?:[0-9a-f]{1,4}:){0,3}[0-9a-f]{1,4})?::[0-9a-f]{1,4}:|(?:(?:[0-9a-f]{1,4}:){0,4}[0-9a-f]{1,4})?::)(?:[0-9a-f]{1,4}:[0-9a-f]{1,4}|(?:(?:25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(?:25[0-5]|2[0-4]\d|[01]?\d\d?))|(?:(?:[0-9a-f]{1,4}:){0,5}[0-9a-f]{1,4})?::[0-9a-f]{1,4}|(?:(?:[0-9a-f]{1,4}:){0,6}[0-9a-f]{1,4})?::)|[Vv][0-9a-f]+\.[a-z0-9\-._~!$&'()*+,;=:]+)\]|(?:(?:25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(?:25[0-5]|2[0-4]\d|[01]?\d\d?)|(?:[a-z0-9\-._~!$&'()*+,;=]|%[0-9a-f]{2})*)(?::\d*)?(?:\/(?:[a-z0-9\-._~!$&'()*+,;=:@]|%[0-9a-f]{2})*)*|\/(?:(?:[a-z0-9\-._~!$&'()*+,;=:@]|%[0-9a-f]{2})+(?:\/(?:[a-z0-9\-._~!$&'()*+,;=:@]|%[0-9a-f]{2})*)*)?|(?:[a-z0-9\-._~!$&'()*+,;=:@]|%[0-9a-f]{2})+(?:\/(?:[a-z0-9\-._~!$&'()*+,;=:@]|%[0-9a-f]{2})*)*)(?:\?(?:[a-z0-9\-._~!$&'()*+,;=:@/?]|%[0-9a-f]{2})*)?(?:#(?:[a-z0-9\-._~!$&'()*+,;=:@/?]|%[0-9a-f]{2})*)?$/i function uri(str: string): boolean { // http://jmrware.com/articles/2009/uri_regexp/URI_regex.html + optional protocol + required "." return NOT_URI_FRAGMENT.test(str) && URI.test(str) } const BYTE = /^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/gm function byte(str: string): boolean { BYTE.lastIndex = 0 return BYTE.test(str) } const MIN_INT32 = -(2 ** 31) const MAX_INT32 = 2 ** 31 - 1 function validateInt32(value: number): boolean { return Number.isInteger(value) && value <= MAX_INT32 && value >= MIN_INT32 } function validateInt64(value: number): boolean { // JSON and javascript max Int is 2**53, so any int that passes isInteger is valid for Int64 return Number.isInteger(value) } function validateNumber(): boolean { return true } const Z_ANCHOR = /[^\\]\\Z/ function regex(str: string): boolean { if (Z_ANCHOR.test(str)) return false try { new RegExp(str) return true } catch (e) { return false } } /** * @license * * MIT License * * Copyright (c) 2020 Evgeny Poberezkin * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ const isISO8601 = /(\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d:[0-5]\d\.\d+([+-][0-2]\d:[0-5]\d|Z))|(\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d:[0-5]\d([+-][0-2]\d:[0-5]\d|Z))|(\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d([+-][0-2]\d:[0-5]\d|Z))/ const isFormalDate = /(?:Sun|Mon|Tue|Wed|Thu|Fri|Sat)\s(?:Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)\s\d{2}\s\d{4}\s\d{2}:\d{2}:\d{2}\sGMT(?:\+|-)\d{4}\s\([^)]+\)/ const isShortenDate = /^(?:(?:(?:(?:0?[1-9]|[12][0-9]|3[01])[/\s-](?:0?[1-9]|1[0-2])[/\s-](?:19|20)\d{2})|(?:(?:19|20)\d{2}[/\s-](?:0?[1-9]|1[0-2])[/\s-](?:0?[1-9]|[12][0-9]|3[01]))))(?:\s(?:1[012]|0?[1-9]):[0-5][0-9](?::[0-5][0-9])?(?:\s[AP]M)?)?$/ const _validateDate = fullFormats.date const _validateDateTime = fullFormats['date-time'] if (!FormatRegistry.Has('date')) FormatRegistry.Set('date', (value: string) => { // Remove quote from stringified date const temp = parseDateTimeEmptySpace(value).replace(/"/g, '') if ( isISO8601.test(temp) || isFormalDate.test(temp) || isShortenDate.test(temp) || _validateDate(temp) ) { const date = new Date(temp) if (!Number.isNaN(date.getTime())) return true } return false }) if (!FormatRegistry.Has('date-time')) FormatRegistry.Set('date-time', (value: string) => { // Remove quote from stringified date const temp = value.replace(/"/g, '') if ( isISO8601.test(temp) || isFormalDate.test(temp) || isShortenDate.test(temp) || _validateDateTime(temp) ) { const date = new Date(temp) if (!Number.isNaN(date.getTime())) return true } return false }) Object.entries(fullFormats).forEach((formatEntry) => { const [formatName, formatValue] = formatEntry if (!FormatRegistry.Has(formatName)) { if (formatValue instanceof RegExp) FormatRegistry.Set(formatName, (value) => formatValue.test(value)) else if (typeof formatValue === 'function') FormatRegistry.Set(formatName, formatValue) } }) if (!FormatRegistry.Has('numeric')) FormatRegistry.Set('numeric', (value) => !!value && !isNaN(+value)) if (!FormatRegistry.Has('integer')) FormatRegistry.Set( 'integer', (value) => !!value && Number.isInteger(+value) ) if (!FormatRegistry.Has('boolean')) FormatRegistry.Set( 'boolean', (value) => value === 'true' || value === 'false' ) if (!FormatRegistry.Has('ObjectString')) FormatRegistry.Set('ObjectString', (value) => { let start = value.charCodeAt(0) // If starts with ' ', '\t', '\n', then trim first if (start === 9 || start === 10 || start === 32) start = value.trimStart().charCodeAt(0) if (start !== 123 && start !== 91) return false try { JSON.parse(value) return true } catch { return false } }) if (!FormatRegistry.Has('ArrayString')) FormatRegistry.Set('ArrayString', (value) => { let start = value.charCodeAt(0) // If starts with ' ', '\t', '\n', then trim first if (start === 9 || start === 10 || start === 32) start = value.trimStart().charCodeAt(0) if (start !== 123 && start !== 91) return false try { JSON.parse(value) return true } catch { return false } }) ================================================ FILE: src/type-system/index.ts ================================================ import { Type, Kind } from '@sinclair/typebox' import type { ArrayOptions, DateOptions, IntegerOptions, ObjectOptions, SchemaOptions, TAnySchema, TArray, TBoolean, TDate, TEnumValue, TInteger, TNumber, TObject, TProperties, TSchema, TString, NumberOptions, JavaScriptTypeBuilder, StringOptions, TUnsafe, Uint8ArrayOptions, TEnum } from '@sinclair/typebox' import { compile, createType, loadFileType, tryParse, validateFile } from './utils' import { CookieValidatorOptions, TFile, TFiles, FileOptions, FilesOptions, NonEmptyArray, TForm, TUnionEnum, ElysiaTransformDecodeBuilder, TArrayBuffer, AssertNumericEnum } from './types' import { ELYSIA_FORM_DATA, form } from '../utils' import { ValidationError } from '../error' import { parseDateTimeEmptySpace } from './format' const t = Object.assign({}, Type) as unknown as Omit< JavaScriptTypeBuilder, 'String' | 'Transform' > & typeof ElysiaType & { Transform( type: Type ): ElysiaTransformDecodeBuilder } createType( 'UnionEnum', (schema, value) => (typeof value === 'number' || typeof value === 'string' || value === null) && schema.enum.includes(value as never) ) createType( 'ArrayBuffer', (schema, value) => value instanceof ArrayBuffer ) const internalFiles = createType( 'Files', (options, value) => { if (options.minItems && options.minItems > 1 && !Array.isArray(value)) return false if (!Array.isArray(value)) return validateFile(options, value) if (options.minItems && value.length < options.minItems) return false if (options.maxItems && value.length > options.maxItems) return false for (let i = 0; i < value.length; i++) if (!validateFile(options, value[i])) return false return true } ) as unknown as TFiles const internalFormData = createType( 'ElysiaForm', ({ compiler, ...schema }, value) => { if (!(value instanceof FormData)) return false if (compiler) { if (!(ELYSIA_FORM_DATA in value)) throw new ValidationError('property', schema, value) if (!compiler.Check(value[ELYSIA_FORM_DATA])) throw compiler.Error(value[ELYSIA_FORM_DATA]) } return true } ) as unknown as TForm interface ElysiaStringOptions extends StringOptions { /** * Whether the value include JSON escape sequences or not * * When using JSON Accelerator, this will bypass the JSON escape sequence validation * * Set to `true` if the value doesn't include JSON escape sequences * * @default false */ trusted?: boolean } export const ElysiaType = { // @ts-ignore String: (property?: ElysiaStringOptions) => Type.String(property), Numeric: (property?: NumberOptions) => { const schema = Type.Number(property) const compiler = compile(schema) return t .Transform( t.Union( [ t.String({ format: 'numeric', default: 0 }), t.Number(property) ], property ) ) .Decode((value) => { const number = +value if (isNaN(number)) return value if (property && !compiler.Check(number)) throw compiler.Error(number) return number }) .Encode((value) => value) as any as TNumber }, NumericEnum>( item: T, property?: SchemaOptions ) { const schema = Type.Enum(item, property) const compiler = compile(schema) return t .Transform( t.Union([t.String({ format: 'numeric' }), t.Number()], property) ) .Decode((value) => { const number = +value if (isNaN(number)) throw compiler.Error(number) if (!compiler.Check(number)) throw compiler.Error(number) return number }) .Encode((value) => value) as any as TEnum }, Integer: (property?: IntegerOptions): TInteger => { const schema = Type.Integer(property) const compiler = compile(schema) return t .Transform( t.Union( [ t.String({ format: 'integer', default: 0 }), Type.Integer(property) ], property ) ) .Decode((value) => { const number = +value if (!compiler.Check(number)) throw compiler.Error(number) return number }) .Encode((value) => value) as any as TInteger }, Date: (property?: DateOptions) => { const schema = Type.Date(property) const compiler = compile(schema) const _default = property?.default ? new Date(property.default) // in case the default is an ISO string or milliseconds from epoch : undefined return t .Transform( t.Union( [ Type.Date(property), t.String({ format: 'date-time', default: _default?.toISOString() }), t.String({ format: 'date', default: _default?.toISOString() }), t.Number({ default: _default?.getTime() }) ], property ) ) .Decode((value) => { if (typeof value === 'number') { const date = new Date(value) if (!compiler.Check(date)) throw compiler.Error(date) return date } if (value instanceof Date) return value const date = new Date(parseDateTimeEmptySpace(value)) if (!date || isNaN(date.getTime())) throw new ValidationError('property', schema, date) if (!compiler.Check(date)) throw compiler.Error(date) return date }) .Encode((value) => { if (value instanceof Date) return value.toISOString() if (typeof value === 'string') { const parsed = new Date(parseDateTimeEmptySpace(value)) if (isNaN(parsed.getTime())) throw new ValidationError('property', schema, value) return parsed.toISOString() } if (!compiler.Check(value)) throw compiler.Error(value) return value }) as any as TDate }, BooleanString: (property?: SchemaOptions) => { const schema = Type.Boolean(property) const compiler = compile(schema) return t .Transform( t.Union( [ t.Boolean(property), t.String({ format: 'boolean', default: false }) ], property ) ) .Decode((value) => { if (typeof value === 'string') return value === 'true' if (value !== undefined && !compiler.Check(value)) throw compiler.Error(value) return value }) .Encode((value) => value) as any as TBoolean }, ObjectString: ( properties: T, options?: ObjectOptions ) => { const schema = t.Object(properties, options) const compiler = compile(schema) return t .Transform( t.Union( [ t.String({ format: 'ObjectString', default: options?.default }), schema ], { elysiaMeta: 'ObjectString' } ) ) .Decode((value) => { if (typeof value === 'string') { if (value.charCodeAt(0) !== 123) throw new ValidationError('property', schema, value) if (!compiler.Check((value = tryParse(value, schema)))) throw compiler.Error(value) return compiler.Decode(value) } return value }) .Encode((value) => { let original if (typeof value === 'string') value = tryParse((original = value), schema) if (!compiler.Check(value)) throw compiler.Error(value) return original ?? JSON.stringify(value) }) as any as TObject }, ArrayString: ( children: T = t.String() as any, options?: ArrayOptions ) => { const schema = t.Array(children, options) const compiler = compile(schema) const decode = (value: string, isProperty = false) => { if (value.charCodeAt(0) === 91) { if (!compiler.Check((value = tryParse(value, schema)))) throw compiler.Error(value) return compiler.Decode(value) } // has , (as used in nuqs) // if (value.indexOf(',') !== -1) { // // const newValue = value.split(',').map((v) => v.trim()) // if (!compiler.Check(value)) throw compiler.Error(value) // return compiler.Decode(value) // } if (isProperty) return value throw new ValidationError('property', schema, value) } return t .Transform( t.Union( [ t.String({ format: 'ArrayString', default: options?.default }), schema ], { elysiaMeta: 'ArrayString' } ) ) .Decode((value) => { if (Array.isArray(value)) { let values = [] for (let i = 0; i < value.length; i++) { const v = value[i] if (typeof v === 'string') { const t = decode(v, true) if (Array.isArray(t)) values = values.concat(t) else values.push(t) continue } values.push(v) } return values } if (typeof value === 'string') return decode(value) // Is probably transformed, unable to check schema return value }) .Encode((value) => { let original if (typeof value === 'string') value = tryParse((original = value), schema) if (!compiler.Check(value)) throw new ValidationError('property', schema, value) return original ?? JSON.stringify(value) }) as any as TArray }, ArrayQuery: ( children: T = t.String() as any, options?: ArrayOptions ) => { const schema = t.Array(children, options) const compiler = compile(schema) const decode = (value: string) => { // has , (as used in nuqs) if (value.indexOf(',') !== -1) return compiler.Decode(value.split(',')) return compiler.Decode([value]) } return t .Transform( t.Union( [ t.String({ default: options?.default }), schema ], { elysiaMeta: 'ArrayQuery' } ) ) .Decode((value) => { if (Array.isArray(value)) { let values = [] for (let i = 0; i < value.length; i++) { const v = value[i] if (typeof v === 'string') { const t = decode(v) if (Array.isArray(t)) values = values.concat(t) else values.push(t) continue } values.push(v) } return values } if (typeof value === 'string') return decode(value) // Is probably transformed, unable to check schema return value }) .Encode((value) => { let original if (typeof value === 'string') value = tryParse((original = value), schema) if (!compiler.Check(value)) throw new ValidationError('property', schema, value) return original ?? JSON.stringify(value) }) as any as TArray }, File: createType( 'File', validateFile ) as unknown as TFile, Files: (options: FilesOptions = {}): TUnsafe => t .Transform(internalFiles(options)) .Decode((value) => { if (Array.isArray(value)) return value return [value] }) .Encode((value) => value) as unknown as TUnsafe, Nullable: (schema: T, options?: SchemaOptions) => t.Union([schema, t.Null()], { ...options, nullable: true }), /** * Allow Optional, Nullable and Undefined */ MaybeEmpty: (schema: T, options?: SchemaOptions) => t.Union([schema, t.Null(), t.Undefined()], options), Cookie: ( properties: T, { domain, expires, httpOnly, maxAge, path, priority, sameSite, secure, secrets, sign, ...options }: CookieValidatorOptions = {} ) => { const v = t.Object(properties, options) v.config = { domain, expires, httpOnly, maxAge, path, priority, sameSite, secure, secrets, sign } return v }, UnionEnum: < const T extends | NonEmptyArray | Readonly> >( values: T, options: SchemaOptions = {} ) => { const type = values.every((value) => typeof value === 'string') ? { type: 'string' } : values.every((value) => typeof value === 'number') ? { type: 'number' } : values.every((value) => value === null) ? { type: 'null' } : {} if (values.some((x) => typeof x === 'object' && x !== null)) throw new Error('This type does not support objects or arrays') return { // default is need for generating error message default: values[0], ...options, [Kind]: 'UnionEnum', ...type, enum: values } as any as TUnionEnum }, NoValidate: (v: T, enabled = true) => { v.noValidate = enabled return v }, Form: ( v: T, options: ObjectOptions = {} ): TForm => { const schema = t.Object(v, { default: form({}), ...options }) const compiler = compile(schema) return t.Union([ schema, // @ts-expect-error internalFormData({ compiler }) ]) }, ArrayBuffer(options: TArrayBuffer = {}) { return { // default is need for generating error message default: [1, 2, 3], ...options, [Kind]: 'ArrayBuffer' } as any as TUnsafe }, Uint8Array: (options: Uint8ArrayOptions) => { const schema = Type.Uint8Array(options) const compiler = compile(schema) return t .Transform(t.Union([t.ArrayBuffer(), Type.Uint8Array(options)])) .Decode((value) => { if (value instanceof ArrayBuffer) { if (!compiler.Check((value = new Uint8Array(value)))) throw compiler.Error(value) return value } return value }) .Encode((value) => value) as any as TUnsafe } } t.BooleanString = ElysiaType.BooleanString t.ObjectString = ElysiaType.ObjectString t.ArrayString = ElysiaType.ArrayString t.ArrayQuery = ElysiaType.ArrayQuery t.Numeric = ElysiaType.Numeric t.NumericEnum = ElysiaType.NumericEnum t.Integer = ElysiaType.Integer t.File = (arg) => { if (arg?.type) loadFileType() return ElysiaType.File({ default: 'File', ...arg, extension: arg?.type, type: 'string', format: 'binary' }) } t.Files = (arg) => { if (arg?.type) loadFileType() return ElysiaType.Files({ ...arg, elysiaMeta: 'Files', default: 'Files', extension: arg?.type, type: 'array', items: { ...arg, default: 'Files', type: 'string', format: 'binary' } }) } t.Nullable = ElysiaType.Nullable t.MaybeEmpty = ElysiaType.MaybeEmpty t.Cookie = ElysiaType.Cookie t.Date = ElysiaType.Date t.UnionEnum = ElysiaType.UnionEnum t.NoValidate = ElysiaType.NoValidate t.Form = ElysiaType.Form t.ArrayBuffer = ElysiaType.ArrayBuffer t.Uint8Array = ElysiaType.Uint8Array as any export { t } export { TypeSystemPolicy, TypeSystem, TypeSystemDuplicateFormat, TypeSystemDuplicateTypeKind } from '@sinclair/typebox/system' export { TypeRegistry, FormatRegistry } from '@sinclair/typebox' export { TypeCompiler, TypeCheck } from '@sinclair/typebox/compiler' ================================================ FILE: src/type-system/types.ts ================================================ import type { Kind, ObjectOptions, SchemaOptions, StaticDecode, TObject, TProperties, TransformKind, TSchema, TUnsafe, Uint8ArrayOptions } from '@sinclair/typebox' import type { ValueError } from '@sinclair/typebox/errors' import type { TypeCheck } from '@sinclair/typebox/compiler' import type { ElysiaFormData } from '../utils' import type { CookieOptions } from '../cookies' import type { MaybeArray } from '../types' export type FileUnit = number | `${number}${'k' | 'm'}` export type StrictFileType = | 'image' | 'image/*' | 'image/jpeg' | 'image/png' | 'image/gif' | 'image/tiff' | 'image/x-icon' | 'image/svg' | 'image/webp' | 'image/avif' | 'audio' | 'audio/*' | 'audio/aac' | 'audio/mpeg' | 'audio/x-ms-wma' | 'audio/vnd.rn-realaudio' | 'audio/x-wav' | 'video' | 'video/*' | 'video/mpeg' | 'video/mp4' | 'video/quicktime' | 'video/x-ms-wmv' | 'video/x-msvideo' | 'video/x-flv' | 'video/webm' | 'text' | 'text/*' | 'text/css' | 'text/csv' | 'text/html' | 'text/javascript' | 'text/plain' | 'text/xml' | 'application' | 'application/*' | 'application/graphql' | 'application/graphql-response+json' | 'application/ogg' | 'application/pdf' | 'application/xhtml' | 'application/xhtml+html' | 'application/xml-dtd' | 'application/html' | 'application/json' | 'application/ld+json' | 'application/xml' | 'application/zip' | 'font' | 'font/*' | 'font/woff2' | 'font/woff' | 'font/ttf' | 'font/otf' export type FileType = (string & {}) | StrictFileType export interface FileOptions extends SchemaOptions { type?: MaybeArray /** * Each file must be at least the specified size. * * @example '600k' (600 kilobytes), '3m' (3 megabytes) */ minSize?: FileUnit /** * Each file must be less than or equal to the specified size. * * @example '3m' (3 megabytes), '600k' (600 kilobytes) */ maxSize?: FileUnit } export interface FilesOptions extends FileOptions { minItems?: number maxItems?: number } export interface CookieValidatorOptions extends ObjectOptions, CookieOptions { /** * Secret key for signing cookie * * If array is passed, will use Key Rotation. * * Key rotation is when an encryption key is retired * and replaced by generating a new cryptographic key. */ secrets?: string | string[] /** * Specified cookie name to be signed globally */ sign?: Readonly<(keyof T | (string & {}))[]> } export type TFile = ( options?: Partial | undefined ) => TUnsafe export type TFiles = ( options?: Partial | undefined ) => TUnsafe export type NonEmptyArray = [T, ...T[]] export type TEnumValue = number | string | null export interface TUnionEnum< T extends | NonEmptyArray | Readonly> = [TEnumValue] > extends TSchema { type?: 'number' | 'string' | 'null' [Kind]: 'UnionEnum' static: T[number] enum: T } export interface TArrayBuffer extends Uint8ArrayOptions {} export type TForm = TUnsafe< ElysiaFormData['static']> > /** * !important * @see https://github.com/elysiajs/elysia/pull/1613 * * Augment this interface to extend allowed values for SchemaOptions['error']. * Defining custom string templates will add autocomplete while allowing any arbitrary string, number, etc. * * The names of keys only used to map to the values, unless you globally augment a specific key. * * ```ts * declare module 'elysia/type-system/types' { * interface ElysiaTypeCustomErrors { * myPlugin: 'my.plugin.error' | `my.plugin.${string}` * } * } * * const schema = t.String({ error: 'my.plugin.hello' }) * ``` */ export interface ElysiaTypeCustomErrors { /** * The default error types that the library supports. * * `string & {}` `number & {}` are used to allow string templates and numbers respectively. */ default: (string & {}) | boolean | (number & {}) | ElysiaTypeCustomErrorCallback } export type ElysiaTypeCustomError = ElysiaTypeCustomErrors[keyof ElysiaTypeCustomErrors] export type ElysiaTypeCustomErrorCallback = ( error: { /** * Error type */ type: 'validation' /** * Where the error was found */ on: 'body' | 'query' | 'params' | 'headers' | 'cookie' | 'response' found: unknown /** * Value that caused the error */ value: unknown /** * Human readable summary of the error * (omitted on production) */ summary?: string /** * Property that caused the error * (omitted on production) */ property?: string /** * Error message * (omitted on production) */ message?: string /** * Expected value * (omitted on production) */ expected?: unknown /** * Array of validation errors * (omitted on production) */ errors: ValueError[] }, validator: TypeCheck ) => unknown declare module '@sinclair/typebox' { interface SchemaOptions { error?: ElysiaTypeCustomError } } export declare class ElysiaTransformDecodeBuilder { private readonly schema constructor(schema: T) Decode, U>>( decode: D ): ElysiaTransformEncodeBuilder } export declare class ElysiaTransformEncodeBuilder< T extends TSchema, D extends TransformFunction > { private readonly schema private readonly decode constructor(schema: T, decode: D) private EncodeTransform private EncodeSchema Encode, StaticDecode>>( encode: E ): TTransform> } export type TransformFunction = (value: T) => U export interface TransformOptions< I extends TSchema = TSchema, O extends unknown = unknown > { Decode: TransformFunction, O> Encode: TransformFunction> } export interface TTransform< I extends TSchema = TSchema, O extends unknown = unknown > extends TSchema { static: O [TransformKind]: TransformOptions [key: string]: any } export type AssertNumericEnum> = { [K in keyof T]: K extends number ? string : K extends `${number}` ? string : K extends string ? number : never } ================================================ FILE: src/type-system/utils.ts ================================================ import { Kind, TUnsafe, TypeRegistry, Unsafe, type TAnySchema } from '@sinclair/typebox' import { Value } from '@sinclair/typebox/value' import { TypeCheck, TypeCompiler } from '@sinclair/typebox/compiler' import { ElysiaFile } from '../universal/file' import { InvalidFileType, ValidationError } from '../error' import type { ElysiaTypeCustomErrorCallback, FileOptions, FileUnit, FileType } from './types' import type { MaybeArray } from '../types' export const tryParse = (v: unknown, schema: TAnySchema) => { try { return JSON.parse(v as string) } catch { throw new ValidationError('property', schema, v) } } export function createType( kind: string, func: TypeRegistry.TypeRegistryValidationFunction ): TUnsafe { if (!TypeRegistry.Has(kind)) TypeRegistry.Set(kind, func) return ((options = {}) => Unsafe({ ...options, [Kind]: kind })) as any } export const compile = (schema: T) => { try { const compiler = TypeCompiler.Compile(schema) as TypeCheck & { Create(): T['static'] Error(v: unknown): asserts v is T['static'] } compiler.Create = () => Value.Create(schema) compiler.Error = (v: unknown) => // @ts-ignore new ValidationError('property', schema, v, compiler.Errors(v)) return compiler } catch { return { Check: (v: unknown) => Value.Check(schema, v), CheckThrow: (v: unknown) => { if (!Value.Check(schema, v)) throw new ValidationError( 'property', schema, v, // @ts-ignore Value.Errors(schema, v) ) }, Decode: (v: unknown) => Value.Decode(schema, v), Create: () => Value.Create(schema), Error: (v: unknown) => new ValidationError( 'property', schema, v, // @ts-ignore Value.Errors(schema, v) ) } } } export const parseFileUnit = (size: FileUnit) => { if (typeof size === 'string') switch (size.slice(-1)) { case 'k': return +size.slice(0, size.length - 1) * 1024 case 'm': return +size.slice(0, size.length - 1) * 1048576 default: return +size } return size } export const checkFileExtension = (type: string, extension: string) => { if (type.startsWith(extension)) return true return ( extension.charCodeAt(extension.length - 1) === 42 && extension.charCodeAt(extension.length - 2) === 47 && type.startsWith(extension.slice(0, -1)) ) } let _fileTypeFromBlobWarn = false const warnIfFileTypeIsNotInstalled = () => { if (!_fileTypeFromBlobWarn) { console.warn( "[Elysia] Attempt to validate file type without 'file-type'. This may lead to security risks. We recommend installing 'file-type' to properly validate file extension." ) _fileTypeFromBlobWarn = true } } export const loadFileType = async () => import('file-type') .then((x) => { _fileTypeFromBlob = x.fileTypeFromBlob return _fileTypeFromBlob }) .catch(warnIfFileTypeIsNotInstalled) let _fileTypeFromBlob: Function export const fileTypeFromBlob = (file: Blob | File) => { if (_fileTypeFromBlob) return _fileTypeFromBlob(file) return loadFileType().then((mod) => { if (mod) return mod(file) }) } export const fileType = async ( file: MaybeArray, extension: FileType | FileType[], // @ts-ignore name = file?.name ?? '' ): Promise => { if (Array.isArray(file)) { await Promise.all(file.map((f) => fileType(f, extension, name))) return true } if (!file) return false const result = await fileTypeFromBlob(file) if (!result) throw new InvalidFileType(name, extension) if (typeof extension === 'string') if (!checkFileExtension(result.mime, extension)) throw new InvalidFileType(name, extension) for (let i = 0; i < extension.length; i++) if (checkFileExtension(result.mime, extension[i])) return true throw new InvalidFileType(name, extension) } /** * This only check file extension based on it's name / mimetype * to actual check the file type, use `validateFileExtension` instead */ export const validateFile = (options: FileOptions, value: any) => { if (value instanceof ElysiaFile) return true if (!(value instanceof Blob)) return false if (options.minSize && value.size < parseFileUnit(options.minSize)) return false if (options.maxSize && value.size > parseFileUnit(options.maxSize)) return false if (options.extension) { if (typeof options.extension === 'string') return checkFileExtension(value.type, options.extension) for (let i = 0; i < options.extension.length; i++) if (checkFileExtension(value.type, options.extension[i])) return true return false } return true } /** * Utility function to inherit add custom error and keep the original Validation error * * @since 1.3.14 * * @example * ```ts * import { Elysia, t, errorWithDetail } from 'elysia' * * new Elysia() * .post('/', () => 'Hello World!', { * body: t.Object({ * x: t.Number({ * error: validationDetail('x must be a number') * }) * }) * }) */ export const validationDetail = (message: T) => (error: Parameters[0]) => ({ ...error, message }) ================================================ FILE: src/types.ts ================================================ /* eslint-disable @typescript-eslint/no-unused-vars */ import type { Elysia, AnyElysia, InvertedStatusMap } from './index' import type { ElysiaFile } from './universal/file' import type { Serve } from './universal/server' import { TSchema, TAnySchema, OptionalKind, TModule, TImport, TProperties } from '@sinclair/typebox' import type { TypeCheck, ValueError } from '@sinclair/typebox/compiler' import type { OpenAPIV3 } from 'openapi-types' import type { ElysiaAdapter } from './adapter' import type { ElysiaTypeCheck } from './schema' import type { Context, ErrorContext, PreContext } from './context' import type { ComposerGeneralHandlerOptions } from './compose' import type { CookieOptions } from './cookies' import type { TraceHandler } from './trace' import type { ElysiaCustomStatusResponse, InternalServerError, InvalidCookieSignature, InvalidFileType, NotFoundError, ParseError, ValidationError } from './error' import type { AnyWSLocalHook } from './ws/types' import type { WebSocketHandler } from './ws/bun' import type { Instruction as ExactMirrorInstruction } from 'exact-mirror' import { BunHTMLBundlelike } from './universal/types' import { Sucrose } from './sucrose' import type Memoirist from 'memoirist' import type { DynamicHandler } from './dynamic-handle' export type Equal = (() => T extends X ? 1 : 2) extends () => T extends Y ? 1 : 2 ? true : false export type IsNever = [T] extends [never] ? true : false export type PickIfExists = {} extends T ? {} : { // @ts-ignore [P in K as P extends keyof T ? P : never]: T[P] } // Standard Schema reduce to bare minimum to save inference time export interface StandardSchemaV1Like< in out Input = unknown, in out Output = Input > { readonly '~standard': { readonly types?: | { readonly input: Input readonly output: Output } | undefined } } // ? Fast check if the generic is enforced to StandardSchemaV1Like export interface FastStandardSchemaV1Like { readonly '~standard': {} } export type StandardSchemaV1LikeValidate = ( v: T ) => MaybePromise< { value: T; issues?: never } | { value?: never; issues: unknown[] } > export type AnySchema = TSchema | StandardSchemaV1Like export type FastAnySchema = TAnySchema | FastStandardSchemaV1Like export interface ElysiaConfig { /** * @default BunAdapter * @since 1.1.11 */ adapter?: ElysiaAdapter /** * Path prefix of the instance * * @default ''' */ prefix?: Prefix /** * Name of the instance for debugging, and plugin deduplication purpose */ name?: string /** * Seed for generating checksum for plugin deduplication * * @see https://elysiajs.com/essential/plugin.html#plugin-deduplication */ seed?: unknown /** * Bun serve * * @see https://bun.sh/docs/api/http */ serve?: Partial /** * OpenAPI documentation (use in Swagger) * * @see https://swagger.io/specification/ */ detail?: DocumentDecoration /** * OpenAPI tags * * current instance' routes with tags * * @see https://swagger.io/specification/#tag-object */ tags?: DocumentDecoration['tags'] /** * Warm up Elysia before starting the server * * This will perform Ahead of Time compilation and generate code for route handlers * * If set to false, Elysia will perform Just in Time compilation * * Only required for root instance (instance which use listen) to effect * * ! If performing a benchmark, it's recommended to set this to `true` * * @default false */ precompile?: | boolean | { /** * Perform dynamic code generation for route handlers before starting the server * * @default false */ compose?: boolean /** * Perform Ahead of Time compilation for schema before starting the server * * @default false */ schema?: boolean } /** * Enable Ahead of Time compilation * * Trade significant performance with slightly faster startup time and reduced memory usage */ aot?: boolean /** * Whether should Elysia tolerate suffix '/' or vice-versa * * @default false */ strictPath?: boolean /** * Override websocket configuration * * @see https://bun.sh/docs/api/websockets */ websocket?: Omit< WebSocketHandler, 'open' | 'close' | 'message' | 'drain' > cookie?: CookieOptions & { /** * Specified cookie name to be signed globally */ sign?: true | string | string[] } /** * Capture more detail information for each dependencies */ analytic?: boolean /** * If enabled, the schema with `t.Transform` will call `Encode` before sending the response * * @default true * @since 1.3.0 * @since 1.2.16 (experimental) **/ encodeSchema?: boolean /** * Enable experimental features */ experimental?: {} /** * If enabled, Elysia will attempt to coerce value to defined type on incoming and outgoing bodies. * * This allows for sending unknown or disallowed properties in the bodies. These will simply be filtered out instead of failing the request. * This has no effect when the schemas allow additional properties. * Since this uses dynamic schema it may have an impact on performance. * * options: * - true: use 'exactMirror' * - false: do not normalize the value * - 'exactMirror': use Elysia's custom exact-mirror which precompile a schema * - 'typebox': Since this uses dynamic Value.Clean, it have performance impact * * Note: This option only works when Elysia schema is provided, doesn't work with Standard Schema * * @default true */ normalize?: boolean | 'exactMirror' | 'typebox' handler?: ComposerGeneralHandlerOptions /** * Enable Bun static response * * @default true * @since 1.1.11 */ nativeStaticResponse?: boolean /** * Use runtime/framework provided router if possible * * @default true * @since 1.3.0 */ systemRouter?: boolean /** * Array of callback function to transform a string value defined in a schema * * This option only works when `sanitlize` is `exactMirror` * * This only works when set on the main instance * * @default true * @since 1.3.0 */ sanitize?: ExactMirrorInstruction['sanitize'] /** * Sucrose (Static Code Analysis) configuration */ sucrose?: Sucrose.Settings /** * Allow unsafe validation details in errors thrown by Elysia's schema validator (422 status code) * * Ideally, this should only be used in development environment or public APIs * This may leak sensitive information about the server implementation and should be used with caution in production environments. * * @default false */ allowUnsafeValidationDetails?: boolean } export interface ValidatorLayer { global: SchemaValidator | null scoped: SchemaValidator | null local: SchemaValidator | null getCandidate(): SchemaValidator } export interface StandaloneInputSchema { body?: AnySchema | Name | `${Name}[]` headers?: AnySchema | Name | `${Name}[]` query?: AnySchema | Name | `${Name}[]` params?: AnySchema | Name | `${Name}[]` cookie?: AnySchema | Name | `${Name}[]` response?: { [status in number]: `${Name}[]` | Name | AnySchema } } export interface StandaloneValidator { global: InputSchema[] | null scoped: InputSchema[] | null local: InputSchema[] | null } export type MaybeArray = T | T[] export type MaybeReadonlyArray = T | readonly T[] export type MaybePromise = T | Promise export type ObjectValues = T[keyof T] type IsPathParameter = Part extends `:${infer Parameter}` ? Parameter : Part extends `*` ? '*' : never export type GetPathParameter = Path extends `${infer A}/${infer B}` ? IsPathParameter | GetPathParameter : IsPathParameter type _ResolvePath = { [Param in GetPathParameter as Param extends `${string}?` ? never : Param]: string } & { [Param in GetPathParameter as Param extends `${infer OptionalParam}?` ? OptionalParam : never]?: string } export type ResolvePath = Path extends '' ? {} : Path extends PathParameterLike ? _ResolvePath : {} export type Or = T1 extends true ? true : T2 extends true ? true : false // https://twitter.com/mattpocockuk/status/1622730173446557697?s=20 export type Prettify = { [K in keyof T]: T[K] } & {} export type NeverKey = { [K in keyof T]?: never } & {} type IsBothObject = A extends Record ? B extends Record ? IsClass extends false ? IsClass extends false ? true : false : false : false : false type IsClass = V extends abstract new (...args: any) => any ? true : false type And = A extends true ? (B extends true ? true : false) : false export type Reconcile< A extends Object, B extends Object, Override extends boolean = false, // Detect Stack limit, eg. circular dependency Stack extends number[] = [] > = Stack['length'] extends 16 ? A : Override extends true ? { [key in keyof A as key extends keyof B ? never : key]: A[key] } extends infer Collision ? {} extends Collision ? { [key in keyof B]: IsBothObject< // @ts-ignore trust me bro A[key], B[key] > extends true ? Reconcile< // @ts-ignore trust me bro A[key], B[key], Override, [0, ...Stack] > : B[key] } : Prettify< Collision & { [key in keyof B]: B[key] } > : never : { [key in keyof B as key extends keyof A ? never : key]: B[key] } extends infer Collision ? {} extends Collision ? { [key in keyof A]: IsBothObject< A[key], // @ts-ignore trust me bro B[key] > extends true ? Reconcile< // @ts-ignore trust me bro A[key], // @ts-ignore trust me bro B[key], Override, [0, ...Stack] > : A[key] } : Prettify< { [key in keyof A]: A[key] } & Collision > : never export interface SingletonBase { decorator: Record store: Record derive: Record resolve: Record } export interface PossibleResponse { [status: number]: unknown } export interface EphemeralType { derive: SingletonBase['derive'] resolve: SingletonBase['resolve'] schema: MetadataBase['schema'] standaloneSchema: MetadataBase['schema'] response: PossibleResponse } export interface DefinitionBase { typebox: Record error: Record } export type RouteBase = Record export interface MetadataBase { schema: RouteSchema standaloneSchema: MetadataBase['schema'] macro: BaseMacro macroFn: Macro parser: Record> response: PossibleResponse } export interface RouteSchema { body?: unknown headers?: unknown query?: unknown params?: unknown cookie?: unknown response?: unknown } interface OptionalField { [OptionalKind]: 'Optional' } export type UnwrapSchema< Schema extends AnySchema | string | undefined, Definitions extends DefinitionBase['typebox'] = {} > = Schema extends undefined ? unknown : Schema extends TSchema ? Schema extends OptionalField ? Partial< TImport< // @ts-expect-error Internal typebox already filter for TSchema Definitions & { readonly __elysia: Schema }, '__elysia' >['static'] > : TImport< // @ts-expect-error Internal typebox already filter for TSchema Definitions & { readonly __elysia: Schema }, '__elysia' >['static'] : Schema extends FastStandardSchemaV1Like ? // @ts-ignore Schema is StandardSchemaV1Like NonNullable['output'] : Schema extends string ? Schema extends keyof Definitions ? Definitions[Schema] extends TAnySchema ? TImport< // @ts-expect-error Internal typebox already filter for TSchema Definitions, Schema >['static'] : NonNullable< Definitions[Schema]['~standard']['types'] >['output'] : unknown : unknown export type UnwrapBodySchema< Schema extends AnySchema | string | undefined, Definitions extends DefinitionBase['typebox'] = {} > = undefined extends Schema ? unknown : Schema extends TSchema ? Schema extends OptionalField ? Partial< TImport< // @ts-expect-error Internal typebox already filter for TSchema Definitions & { readonly __elysia: Schema }, '__elysia' >['static'] > | null : TImport< // @ts-expect-error Internal typebox already filter for TSchema Definitions & { readonly __elysia: Schema }, '__elysia' >['static'] : Schema extends FastStandardSchemaV1Like ? // @ts-ignore Schema is StandardSchemaV1Like NonNullable['output'] : Schema extends string ? Schema extends keyof Definitions ? Definitions[Schema] extends TAnySchema ? TImport< // @ts-expect-error Internal typebox already filter for TSchema Definitions, Schema >['static'] : // @ts-ignore Schema is StandardSchemaV1Like NonNullable< Definitions[Schema]['~standard']['types'] >['output'] : unknown : unknown export interface UnwrapRoute< in out Schema extends InputSchema, in out Definitions extends DefinitionBase['typebox'] = {}, in out Path extends string = '' > { body: UnwrapBodySchema headers: UnwrapSchema query: UnwrapSchema params: {} extends Schema['params'] ? ResolvePath : {} extends Schema ? ResolvePath : UnwrapSchema cookie: UnwrapSchema response: Schema['response'] extends FastAnySchema | string ? { 200: UnwrapSchema< Schema['response'], Definitions > extends infer A ? A extends File ? File | ElysiaFile : A : unknown } : Schema['response'] extends { [status in number]: FastAnySchema | string } ? { [k in keyof Schema['response']]: UnwrapSchema< Schema['response'][k], Definitions > extends infer A ? A extends File ? File | ElysiaFile : A : unknown } : unknown | void } export interface UnwrapGroupGuardRoute< in out Schema extends InputSchema, in out Definitions extends DefinitionBase['typebox'] = {}, Path extends string | undefined = undefined > { body: UnwrapBodySchema headers: UnwrapSchema< Schema['headers'], Definitions > extends infer A extends Record ? A : undefined query: UnwrapSchema extends infer A extends Record ? A : undefined params: UnwrapSchema extends infer A extends Record ? A : Path extends PathParameterLike ? Record, string> : never cookie: UnwrapSchema extends infer A extends Record ? A : undefined response: Schema['response'] extends TSchema | string ? UnwrapSchema : Schema['response'] extends { [k in string]: TSchema | string } ? UnwrapSchema< Schema['response'][keyof Schema['response']], Definitions > : unknown | void } export type HookContainer = { checksum?: number scope?: LifeCycleType subType?: 'derive' | 'resolve' | 'mapDerive' | 'mapResolve' | (string & {}) fn: T isAsync?: boolean hasReturn?: boolean } export interface LifeCycleStore { type?: ContentType start: HookContainer>[] request: HookContainer>[] parse: HookContainer>[] transform: HookContainer>[] beforeHandle: HookContainer>[] afterHandle: HookContainer>[] mapResponse: HookContainer>[] afterResponse: HookContainer>[] trace: HookContainer>[] error: HookContainer>[] stop: HookContainer>[] } export type LifeCycleEvent = | 'start' | 'request' | 'parse' | 'transform' | 'beforeHandle' | 'afterHandle' | 'response' | 'error' | 'stop' export type ContentType = MaybeArray< | 'none' | 'text' | 'json' | 'formdata' | 'urlencoded' | 'arrayBuffer' | 'text/plain' | 'application/json' | 'multipart/form-data' | 'application/x-www-form-urlencoded' | 'application/octet-stream' > export type HTTPMethod = | (string & {}) | 'ACL' | 'BIND' | 'CHECKOUT' | 'CONNECT' | 'COPY' | 'DELETE' | 'GET' | 'HEAD' | 'LINK' | 'LOCK' | 'M-SEARCH' | 'MERGE' | 'MKACTIVITY' | 'MKCALENDAR' | 'MKCOL' | 'MOVE' | 'NOTIFY' | 'OPTIONS' | 'PATCH' | 'POST' | 'PROPFIND' | 'PROPPATCH' | 'PURGE' | 'PUT' | 'REBIND' | 'REPORT' | 'SEARCH' | 'SOURCE' | 'SUBSCRIBE' | 'TRACE' | 'UNBIND' | 'UNLINK' | 'UNLOCK' | 'UNSUBSCRIBE' | 'ALL' export interface InputSchema { body?: AnySchema | Name headers?: AnySchema | Name query?: AnySchema | Name params?: AnySchema | Name cookie?: AnySchema | Name response?: | AnySchema | { [status in number]: AnySchema } | Name | { [status in number]: Name | AnySchema } } type PathParameterLike = `${string}/${':' | '*'}${string}` export type IntersectIfObject = A extends Record ? B extends Record ? A & B : A : B extends Record ? B : A export interface IntersectIfObjectSchema< A extends RouteSchema, B extends RouteSchema > { body: IntersectIfObject headers: IntersectIfObject query: IntersectIfObject params: IntersectIfObject cookie: IntersectIfObject response: IntersectIfObject } export type MergeSchema< A extends RouteSchema, B extends RouteSchema, Path extends string = '' > = {} extends A ? Path extends PathParameterLike ? Omit & { params: ResolvePath } : B : {} extends B ? Path extends PathParameterLike ? Omit & { params: ResolvePath } : A : { body: undefined extends A['body'] ? B['body'] : A['body'] headers: undefined extends A['headers'] ? B['headers'] : A['headers'] query: undefined extends A['query'] ? B['query'] : A['query'] params: IsNever extends true ? IsNever extends true ? ResolvePath : B['params'] : IsNever extends true ? A['params'] : Prettify< B['params'] & Omit > cookie: undefined extends A['cookie'] ? B['cookie'] : A['cookie'] response: {} extends A['response'] ? {} extends B['response'] ? {} : B['response'] : {} extends B['response'] ? A['response'] : A['response'] & Omit } export interface MergeStandaloneSchema< in out A extends RouteSchema, in out B extends RouteSchema, Path extends string = '' > { body: undefined extends A['body'] ? undefined extends B['body'] ? undefined : B['body'] : undefined extends B['body'] ? A['body'] : Prettify headers: undefined extends A['headers'] ? undefined extends B['headers'] ? undefined : B['headers'] : undefined extends B['headers'] ? A['headers'] : Prettify query: undefined extends A['query'] ? undefined extends B['query'] ? undefined : B['query'] : undefined extends B['query'] ? A['query'] : Prettify params: IsNever extends true ? IsNever extends true ? ResolvePath : B['params'] : IsNever extends true ? A['params'] : Prettify cookie: undefined extends A['cookie'] ? undefined extends B['cookie'] ? undefined : B['cookie'] : undefined extends B['cookie'] ? A['cookie'] : Prettify response: {} extends A['response'] ? {} extends B['response'] ? {} : B['response'] : {} extends B['response'] ? A['response'] : Prettify } export type Handler< in out Route extends RouteSchema = {}, in out Singleton extends SingletonBase = { decorator: {} store: {} derive: {} resolve: {} }, Path extends string | undefined = undefined > = ( context: Context ) => MaybePromise< {} extends Route['response'] ? unknown : Route['response'][keyof Route['response']] > export type IsAny = 0 extends 1 & T ? true : false export type Replace = IsAny extends true ? Original : Original extends Record ? { [K in keyof Original]: Original[K] extends Target ? With : Original[K] } : Original extends Target ? With : Original export type CoExist = IsAny extends true ? Original : Original extends Record ? { [K in keyof Original]: Original[K] extends Target ? Original[K] | With : Original[K] } : Original extends Target ? Original | With : Original // These properties shall not be resolve in macro export type MacroContextBlacklistKey = | 'type' | 'detail' | 'parse' | 'transform' | 'resolve' | 'beforeHandle' | 'afterHandle' | 'mapResponse' | 'afterResponse' | 'error' | 'tags' | keyof RouteSchema type ReturnTypeIfPossible = false extends Enabled ? {} : T extends (...a: any) => infer R ? R : T type AnyElysiaCustomStatusResponse = ElysiaCustomStatusResponse type FunctionArrayReturnType = // If nothing is provided, it will be resolved as any any[] extends T ? never : T extends any[] ? _FunctionArrayReturnType : // @ts-ignore Awaited> type _FunctionArrayReturnType = T extends [ infer Fn, ...infer Rest ] ? _FunctionArrayReturnType< Rest, Awaited< // @ts-ignore Trust me bro ReturnType > extends infer A ? IsNever extends true ? Carry : A | Carry : Carry > : Carry type FunctionArrayReturnTypeNonNullable = // If nothing is provided, it will be resolved as any any[] extends T ? never : T extends any[] ? _FunctionArrayReturnTypeNonNullable : // @ts-ignore NonNullable>> type _FunctionArrayReturnTypeNonNullable = T extends [ infer Fn, ...infer Rest ] ? _FunctionArrayReturnTypeNonNullable< Rest, NonNullable< Awaited< // @ts-ignore Trust me bro ReturnType > > extends infer A ? IsNever extends true ? Carry : A | Carry : Carry > : Carry type ExtractResolveFromMacro = IsNever extends true ? {} : A extends AnyElysiaCustomStatusResponse ? A : Exclude extends infer A ? IsAny extends true ? {} : A : {} type ExtractOnlyResponseFromMacro = IsNever extends true ? {} : Extract extends infer A ? IsNever extends true ? {} : { return: MergeResponseStatus } : {} type MergeResponseStatus = { [status in keyof UnionToIntersect< // Must be using generic to separate literal from Box A extends ElysiaCustomStatusResponse ? { [A in Status]: 1 } : never // @ts-ignore A is checked in key computation >]: Extract['response'] extends infer Value ? IsAny extends true ? // @ts-ignore status is always in Status Map InvertedStatusMap[status] : Value : never } type MergeAllStatus = { [K in T extends any ? keyof T : never]: T extends Record ? V : never } type ExtractAllResponseFromMacro = IsNever extends true ? {} : { // Merge all status to single object first return: MergeResponseStatus & (Exclude extends infer A ? IsAny extends true ? {} : IsNever extends true ? {} : // FunctionArrayReturnType NonNullable extends A ? {} : undefined extends A ? {} : { 200: A } : {}) } type FlattenMacroResponse = T extends object ? '_' extends keyof T ? MergeFlattenMacroResponse< Omit, FlattenMacroResponse> > : T : T type MergeFlattenMacroResponse = { [K in keyof A | keyof B]: K extends keyof A ? K extends keyof B ? A[K] | B[K] : A[K] : K extends keyof B ? B[K] : never } type UnionMacroContext = UnionToIntersect<{ [K in Exclude]: A[K] }> & { // @ts-ignore Allow recursive Macro.return without collapse into return: { _: A['return'] } } export type MacroToContext< in out MacroFn extends Macro = {}, in out SelectedMacro extends BaseMacro = {}, in out Definitions extends DefinitionBase['typebox'] = {}, in out R extends 1[] = [] > = Prettify< InnerMacroToContext< MacroFn, Pick>, Definitions, R > extends infer A ? { [K in Exclude]: UnionToIntersect } & Prettify<{ // @ts-ignore return: FlattenMacroResponse }> : {} > // There's only resolve that can add new properties to Context type InnerMacroToContext< MacroFn extends Macro = {}, SelectedMacro extends BaseMacro = {}, Definitions extends DefinitionBase['typebox'] = {}, R extends 1[] = [] > = {} extends SelectedMacro ? {} : R['length'] extends 15 ? {} : UnionMacroContext< { [key in keyof SelectedMacro]: ReturnTypeIfPossible< MacroFn[key], SelectedMacro[key] > extends infer Value ? { resolve: ExtractResolveFromMacro< Extract< Exclude< FunctionArrayReturnType< // @ts-ignore Trust me bro Value['resolve'] >, AnyElysiaCustomStatusResponse >, Record > > } & UnwrapMacroSchema< // @ts-ignore Trust me bro Value, Definitions > & ExtractAllResponseFromMacro< FunctionArrayReturnTypeNonNullable< // @ts-expect-error type is checked in key mapping Value['beforeHandle'] > > & ExtractAllResponseFromMacro< FunctionArrayReturnTypeNonNullable< // @ts-expect-error type is checked in key mapping Value['afterHandle'] > > & ExtractAllResponseFromMacro< // @ts-expect-error type is checked in key mapping FunctionArrayReturnType > & ExtractOnlyResponseFromMacro< FunctionArrayReturnTypeNonNullable< // @ts-expect-error type is checked in key mapping Value['resolve'] > > & InnerMacroToContext< MacroFn, // @ts-ignore trust me bro Pick< Value, Extract >, Definitions, [...R, 1] > : {} }[keyof SelectedMacro] > type UnwrapMacroSchema< T extends Partial>, Definitions extends DefinitionBase['typebox'] = {} > = UnwrapRoute< { body: 'body' extends keyof T ? T['body'] : undefined headers: 'headers' extends keyof T ? T['headers'] : undefined query: 'query' extends keyof T ? T['query'] : undefined params: 'params' extends keyof T ? T['params'] : undefined cookie: 'cookie' extends keyof T ? T['cookie'] : undefined response: 'response' extends keyof T ? T['response'] : undefined }, Definitions > export type SimplifyToSchema> = IsUnknown extends false ? _SimplifyToSchema : IsUnknown extends false ? _SimplifyToSchema : IsUnknown extends false ? _SimplifyToSchema : IsUnknown extends false ? _SimplifyToSchema : IsUnknown extends false ? _SimplifyToSchema : IsUnknown extends false ? _SimplifyToSchema : {} export type _SimplifyToSchema> = Omit< { body: T['body'] headers: T['headers'] query: T['query'] params: T['params'] cookie: T['cookie'] response: T['response'] }, | ('body' extends keyof T ? never : 'body') | ('headers' extends keyof T ? never : 'headers') | ('query' extends keyof T ? never : 'query') | ('params' extends keyof T ? never : 'params') | ('cookie' extends keyof T ? never : 'cookie') | ('response' extends keyof T ? never : 'response') > type InlineHandlerResponse = { [Status in keyof Route]: ElysiaCustomStatusResponse< // @ts-ignore Status is always a number Status, Route[Status], Status > }[keyof Route] type InlineResponse = | string | number | boolean | Record | Response | AnyElysiaCustomStatusResponse | ElysiaFile | Record | BunHTMLBundlelike type LastOf = UnionToIntersect T : never> extends () => infer R ? R : never type Push = [...T, V] type TuplifyUnion< T, L = LastOf, N = [T] extends [never] ? true : false > = true extends N ? [] : Push>, L> export type Tuple< T, A extends T[] = [] > = TuplifyUnion['length'] extends A['length'] ? [...A] : Tuple export type InlineHandler< Route extends RouteSchema = {}, Singleton extends SingletonBase = { decorator: {} store: {} derive: {} resolve: {} }, MacroContext extends { response: PossibleResponse return: PossibleResponse resolve: Record } = { response: {} return: {} resolve: {} } > = | MaybePromise | (( context: Context< Route & MacroContext, Singleton & { resolve: MacroContext['resolve'] } > ) => | MaybePromise | MaybePromise< {} extends Route['response'] ? unknown : | (Route['response'] extends { 200: any } ? | Route['response'][200] | ElysiaCustomStatusResponse< 200, Route['response'][200], 200 > | Generator< Route['response'][200] > | AsyncGenerator< Route['response'][200] > : unknown) // This could be possible because of set.status | Route['response'][keyof Route['response']] | InlineHandlerResponse< Route['response'] & MacroContext['response'] > >) export type InlineHandlerNonMacro< Route extends RouteSchema = {}, Singleton extends SingletonBase = { decorator: {} store: {} derive: {} resolve: {} } > = | MaybePromise | ((context: Context) => | MaybePromise | MaybePromise< {} extends Route['response'] ? unknown : | (Route['response'] extends { 200: any } ? | Route['response'][200] | ElysiaCustomStatusResponse< 200, Route['response'][200], 200 > | Generator< Route['response'][200] > | AsyncGenerator< Route['response'][200] > : unknown) // This could be possible because of set.status | Route['response'][keyof Route['response']] | InlineHandlerResponse >) export type OptionalHandler< in out Route extends RouteSchema = {}, in out Singleton extends SingletonBase = { decorator: {} store: {} derive: {} resolve: {} }, Path extends string | undefined = undefined > = ( context: Context ) => MaybePromise< {} extends Route['response'] ? unknown : | Route['response'][keyof Route['response']] | InlineHandlerResponse | void > export type AfterHandler< in out Route extends RouteSchema = {}, in out Singleton extends SingletonBase = { decorator: {} store: {} derive: {} resolve: {} }, Path extends string | undefined = undefined > = ( context: Context & { responseValue: {} extends Route['response'] ? unknown : Route['response'][keyof Route['response']] /** * @deprecated use `context.responseValue` instead */ response: {} extends Route['response'] ? unknown : Route['response'][keyof Route['response']] } ) => MaybePromise< {} extends Route['response'] ? unknown : | Route['response'][keyof Route['response']] | InlineHandlerResponse | void > export type MapResponse< in out Route extends RouteSchema = {}, in out Singleton extends SingletonBase = { decorator: {} store: {} derive: {} resolve: {} }, Path extends string | undefined = undefined > = ( context: Context & { responseValue: {} extends Route['response'] ? unknown : Route['response'][keyof Route['response']] /** * @deprecated use `context.responseValue` instead */ response: {} extends Route['response'] ? unknown : Route['response'][keyof Route['response']] } ) => MaybePromise // Handler< // Omit & {}, // Singleton & { // derive: { // response: {} extends Route['response'] ? unknown : Route['response'] // } // }, // Path // > export type VoidHandler< in out Route extends RouteSchema = {}, in out Singleton extends SingletonBase = { decorator: {} store: {} derive: {} resolve: {} } > = (context: Context) => MaybePromise export type TransformHandler< in out Route extends RouteSchema = {}, in out Singleton extends SingletonBase = { decorator: {} store: {} derive: {} resolve: {} }, Path extends string | undefined = undefined > = ( context: Context< Route, Omit & { resolve: {} }, Path > ) => MaybePromise export type BodyHandler< in out Route extends RouteSchema = {}, in out Singleton extends SingletonBase = { decorator: {} store: {} derive: {} resolve: {} }, Path extends string | undefined = undefined > = ( context: Context< Route, Singleton & { decorator: { contentType: string } }, Path >, /** * @deprecated * * use `context.contentType` instead * * @example * ```ts * new Elysia() * .onParse(({ contentType, request }) => { * if (contentType === 'application/json') * return request.json() * }) * ``` */ contentType: string ) => MaybePromise export type PreHandler< in out Route extends RouteSchema = {}, in out Singleton extends SingletonBase = { decorator: {} store: {} derive: {} resolve: {} } > = ( context: PreContext ) => MaybePromise< Route['response'] | InlineHandlerResponse | void > export type AfterResponseHandler< in out Route extends RouteSchema = {}, in out Singleton extends SingletonBase = { decorator: {} store: {} derive: {} resolve: {} } > = ( context: Context & { responseValue: {} extends Route['response'] ? unknown : Route['response'][keyof Route['response']] /** * @deprecated use `context.responseValue` instead */ response: {} extends Route['response'] ? unknown : | Route['response'][keyof Route['response']] | InlineHandlerResponse } ) => MaybePromise export type GracefulHandler = ( data: Instance ) => any export type ErrorHandler< in out T extends Record = {}, in out Route extends RouteSchema = {}, in out Singleton extends SingletonBase = { decorator: {} store: {} derive: {} resolve: {} }, // ? scoped in out Ephemeral extends EphemeralType = { derive: {} resolve: {} schema: {} standaloneSchema: {} response: {} }, // ? local in out Volatile extends EphemeralType = { derive: {} resolve: {} schema: {} standaloneSchema: {} response: {} } > = ( context: ErrorContext< Route, { store: Singleton['store'] decorator: Singleton['decorator'] derive: {} resolve: {} } > & ( | Prettify< { request: Request code: 'UNKNOWN' error: Readonly set: Context['set'] } & Partial< Singleton['derive'] & Ephemeral['derive'] & Volatile['derive'] & Singleton['resolve'] & Ephemeral['resolve'] & Volatile['resolve'] > > | Prettify< { request: Request code: 'VALIDATION' error: Readonly set: Context['set'] } & Singleton['derive'] & Ephemeral['derive'] & Volatile['derive'] & NeverKey< Singleton['resolve'] & Ephemeral['resolve'] & Volatile['resolve'] > > | Prettify< { request: Request code: 'NOT_FOUND' error: Readonly set: Context['set'] } & NeverKey< Singleton['derive'] & Ephemeral['derive'] & Volatile['derive'] & Singleton['resolve'] & Ephemeral['resolve'] & Volatile['resolve'] > > | Prettify< { request: Request code: 'PARSE' error: Readonly set: Context['set'] } & NeverKey< Singleton['derive'] & Ephemeral['derive'] & Volatile['derive'] & Singleton['resolve'] & Ephemeral['resolve'] & Volatile['resolve'] > > | Prettify< { request: Request code: 'INTERNAL_SERVER_ERROR' error: Readonly set: Context['set'] } & Partial< Singleton['derive'] & Ephemeral['derive'] & Volatile['derive'] & Singleton['resolve'] & Ephemeral['resolve'] & Volatile['resolve'] > > | Prettify< { request: Request code: 'INVALID_COOKIE_SIGNATURE' error: Readonly set: Context['set'] } & NeverKey< Singleton['derive'] & Ephemeral['derive'] & Volatile['derive'] & Singleton['resolve'] & Ephemeral['resolve'] & Volatile['resolve'] > > | Prettify< { request: Request code: 'INVALID_FILE_TYPE' error: Readonly set: Context['set'] } & Singleton['derive'] & Ephemeral['derive'] & Volatile['derive'] & NeverKey< Singleton['resolve'] & Ephemeral['resolve'] & Volatile['resolve'] > > | Prettify< { request: Request code: number error: Readonly> set: Context['set'] } & Partial< Singleton['derive'] & Ephemeral['derive'] & Volatile['derive'] & Singleton['resolve'] & Ephemeral['resolve'] & Volatile['resolve'] > > | Prettify< { [K in keyof T]: { request: Request code: K error: Readonly set: Context['set'] } }[keyof T] & Partial< Singleton['derive'] & Ephemeral['derive'] & Volatile['derive'] & Singleton['resolve'] & Ephemeral['resolve'] & Volatile['resolve'] > > ) ) => any | Promise export interface DocumentDecoration extends Partial { /** * Pass `true` to hide route from OpenAPI/swagger document * */ hide?: boolean } export type ResolveHandler< in out Route extends RouteSchema, in out Singleton extends SingletonBase, Derivative extends | Record | AnyElysiaCustomStatusResponse | void = Record | AnyElysiaCustomStatusResponse | void > = (context: Context) => MaybePromise export type ResolveReturnType> = // If no macro are provided, it will be resolved as any any[] extends T ? {} : // Is any, return T extends any[] ? _ResolveReturnTypeArray : Exclude< // @ts-ignore Trust me bro Awaited>, AnyElysiaCustomStatusResponse > extends infer Value extends Record ? Value : {} type _ResolveReturnTypeArray = T extends [ infer Fn, ...infer Rest ] ? Exclude< // @ts-ignore Trust me bro Awaited>, AnyElysiaCustomStatusResponse > extends infer Value extends Record ? _ResolveReturnTypeArray : _ResolveReturnTypeArray : Prettify export type AnyLocalHook = LocalHook export interface BaseHookLifeCycle< in out Schema extends RouteSchema, in out Singleton extends SingletonBase, in out Errors extends { [key in string]: Error }, in out Parser extends keyof any = '' > { detail?: DocumentDecoration /** * Short for 'Content-Type' * * Available: * - 'none': do not parse body * - 'text' / 'text/plain': parse body as string * - 'json' / 'application/json': parse body as json * - 'formdata' / 'multipart/form-data': parse body as form-data * - 'urlencoded' / 'application/x-www-form-urlencoded: parse body as urlencoded * - 'arraybuffer': parse body as readable stream */ parse?: MaybeArray | ContentType | Parser> /** * Transform context's value */ transform?: MaybeArray> /** * Execute before main handler */ beforeHandle?: MaybeArray> /** * Execute after main handler */ afterHandle?: MaybeArray> /** * Execute after main handler */ mapResponse?: MaybeArray> /** * Execute after response is sent */ afterResponse?: MaybeArray> /** * Catch error */ error?: MaybeArray> tags?: DocumentDecoration['tags'] } export type CreateDecorator< Singleton extends SingletonBase, Ephemeral extends EphemeralType, Volatile extends EphemeralType > = {} extends Ephemeral ? {} extends Volatile ? Singleton : Singleton & Volatile : {} extends Volatile ? Singleton & Ephemeral : Singleton & Ephemeral & Volatile export type AnyBaseHookLifeCycle = BaseHookLifeCycle export type NonResolvableMacroKey = | keyof AnyBaseHookLifeCycle | keyof InputSchema | 'resolve' interface RouteSchemaWithResolvedMacro extends RouteSchema { response: PossibleResponse return: PossibleResponse resolve: Record } export type LocalHook< Input extends BaseMacro, Schema extends RouteSchemaWithResolvedMacro, Singleton extends SingletonBase, Errors extends { [key in string]: Error }, Parser extends keyof any = '' > = { detail?: DocumentDecoration /** * Short for 'Content-Type' * * Available: * - 'none': do not parse body * - 'text' / 'text/plain': parse body as string * - 'json' / 'application/json': parse body as json * - 'formdata' / 'multipart/form-data': parse body as form-data * - 'urlencoded' / 'application/x-www-form-urlencoded: parse body as urlencoded * - 'arraybuffer': parse body as readable stream */ parse?: MaybeArray< | BodyHandler | ContentType | Parser > /** * Transform context's value */ transform?: MaybeArray< TransformHandler > /** * Execute before main handler */ beforeHandle?: MaybeArray< OptionalHandler > /** * Execute after main handler */ afterHandle?: MaybeArray< AfterHandler > /** * Execute after main handler */ mapResponse?: MaybeArray< MapResponse > /** * Execute after response is sent */ afterResponse?: MaybeArray< AfterResponseHandler > /** * Catch error */ error?: MaybeArray< ErrorHandler > tags?: DocumentDecoration['tags'] } & (Input extends any ? Input : Prettify) export type GuardLocalHook< Input extends BaseMacro | undefined, Schema extends RouteSchema, Singleton extends SingletonBase, Parser extends keyof any, BeforeHandle extends MaybeArray>, AfterHandle extends MaybeArray>, ErrorHandle extends MaybeArray>, GuardType extends GuardSchemaType = 'standalone', AsType extends LifeCycleType = 'local' > = (Input extends any ? Input : Prettify) & { /** * @default 'override' */ as?: AsType /** * @default 'standalone' * @since 1.3.0 */ schema?: GuardType detail?: DocumentDecoration /** * Short for 'Content-Type' * * Available: * - 'none': do not parse body * - 'text' / 'text/plain': parse body as string * - 'json' / 'application/json': parse body as json * - 'formdata' / 'multipart/form-data': parse body as form-data * - 'urlencoded' / 'application/x-www-form-urlencoded: parse body as urlencoded * - 'arraybuffer': parse body as readable stream */ parse?: MaybeArray | ContentType | Parser> /** * Transform context's value */ transform?: MaybeArray> /** * Execute before main handler */ beforeHandle?: BeforeHandle /** * Execute after main handler */ afterHandle?: AfterHandle /** * Execute after main handler */ mapResponse?: MaybeArray> /** * Execute after response is sent */ afterResponse?: MaybeArray> /** * Catch error */ error?: ErrorHandle tags?: DocumentDecoration['tags'] } export type ComposedHandler = (context: Context) => MaybePromise export interface InternalRoute { method: HTTPMethod path: string composed: ComposedHandler | Response | null compile(): ComposedHandler handler: Handler hooks: AnyLocalHook websocket?: AnyWSLocalHook } export interface SchemaValidator { createBody?(): ElysiaTypeCheck createHeaders?(): ElysiaTypeCheck createQuery?(): ElysiaTypeCheck createParams?(): ElysiaTypeCheck createCookie?(): ElysiaTypeCheck createResponse?(): Record> body?: ElysiaTypeCheck headers?: ElysiaTypeCheck query?: ElysiaTypeCheck params?: ElysiaTypeCheck cookie?: ElysiaTypeCheck response?: Record> } export type AddPrefix = { [K in keyof T as Prefix extends string ? `${Prefix}${K & string}` : K]: T[K] } export type AddPrefixCapitalize = { [K in keyof T as `${Prefix}${Capitalize}`]: T[K] } export type AddSuffix = { [K in keyof T as `${K & string}${Suffix}`]: T[K] } export type AddSuffixCapitalize = { [K in keyof T as `${K & string}${Capitalize}`]: T[K] } export interface Checksum { name?: string seed?: unknown checksum: number stack?: string routes?: InternalRoute[] decorators?: SingletonBase['decorator'] store?: SingletonBase['store'] error?: DefinitionBase['error'] dependencies?: Record derive?: { fn: string stack: string }[] resolve?: { fn: string stack: string }[] } export type BaseMacro = Record< string, string | number | boolean | Object | undefined | null > export type MaybeValueOrVoidFunction = T | ((...a: any) => void | T) export interface MacroProperty< in out Macro extends BaseMacro = {}, in out TypedRoute extends RouteSchema = {}, in out Singleton extends SingletonBase = { decorator: {} store: {} derive: {} resolve: {} }, in out Errors extends Record = {} > { /** * Deduplication similar to Elysia.constructor.seed */ seed?: unknown parse?: MaybeArray> transform?: MaybeArray> beforeHandle?: MaybeArray> afterHandle?: MaybeArray> error?: MaybeArray> mapResponse?: MaybeArray> afterResponse?: MaybeArray> resolve?: MaybeArray> detail?: DocumentDecoration /** * Introspect hook option for documentation generation or analysis * * @param option */ introspect?(option: Prettify): unknown } export interface Macro< in out Macro extends BaseMacro = {}, in out Input extends BaseMacro = {}, in out TypedRoute extends RouteSchema = {}, in out Singleton extends SingletonBase = { decorator: {} store: {} derive: {} resolve: {} }, in out Errors extends Record = {} > { [K: keyof any]: MaybeValueOrVoidFunction< Input & MacroProperty > } export type MaybeFunction = T | ((...args: any[]) => T) export type MacroToProperty> = Prettify<{ [K in keyof T]: T[K] extends Function ? T[K] extends (a: infer Params) => any ? Params : boolean : boolean }> interface MacroOptions { insert?: 'before' | 'after' stack?: 'global' | 'local' } export interface MacroManager< in out TypedRoute extends RouteSchema = {}, in out Singleton extends SingletonBase = { decorator: {} store: {} derive: {} resolve: {} }, in out Errors extends Record = {} > { body(schema: InputSchema['body']): unknown headers(schema: InputSchema['headers']): unknown query(schema: InputSchema['query']): unknown params(schema: InputSchema['params']): unknown cookie(schema: InputSchema['cookie']): unknown response(schema: InputSchema['response']): unknown detail(detail: DocumentDecoration): unknown onParse(fn: MaybeArray>): unknown onParse( options: MacroOptions, fn: MaybeArray> ): unknown onTransform(fn: MaybeArray>): unknown onTransform( options: MacroOptions, fn: MaybeArray> ): unknown onBeforeHandle( fn: MaybeArray> ): unknown onBeforeHandle( options: MacroOptions, fn: MaybeArray> ): unknown onAfterHandle(fn: MaybeArray>): unknown onAfterHandle( options: MacroOptions, fn: MaybeArray> ): unknown onError( fn: MaybeArray> ): unknown onError( options: MacroOptions, fn: MaybeArray> ): unknown mapResponse(fn: MaybeArray>): unknown mapResponse( options: MacroOptions, fn: MaybeArray> ): unknown onAfterResponse( fn: MaybeArray> ): unknown onAfterResponse( options: MacroOptions, fn: MaybeArray> ): unknown events: { global: Partial local: Partial } } type _CreateEden< Path extends string, Property extends Record = {} > = Path extends `${infer Start}/${infer Rest}` ? { [x in Start]: _CreateEden } : Path extends '' ? Property : { [x in Path]: Property } type RemoveStartingSlash = T extends `/${infer Rest}` ? Rest : T export type CreateEden< Path extends string, Property extends Record = {} > = Path extends `/${infer Rest}` ? _CreateEden : Path extends '' | '/' ? Property : _CreateEden export interface EmptyRouteSchema { body: unknown headers: unknown query: unknown params: {} cookie: unknown response: unknown } export interface UnknownRouteSchema< Params = { [name: string]: string | undefined } > { body: unknown headers: { [name: string]: string | undefined } query: { [name: string]: string | undefined } params: Params cookie: {} response: unknown } type Extract200 = T extends AnyElysiaCustomStatusResponse ? | Exclude | Extract>['response'] : T export type IsUnknown = [unknown] extends [T] ? IsAny extends true ? false : true : false export type ValueToResponseSchema = ExtractErrorFromHandle & (Extract200 extends infer R200 ? undefined extends R200 ? {} : IsNever extends true ? {} : { 200: R200 } : {}) export type ValueOrFunctionToResponseSchema = T extends ( ...a: any ) => MaybePromise ? ValueToResponseSchema : ValueToResponseSchema export type ElysiaHandlerToResponseSchema = Prettify< Handle extends (...a: any) => MaybePromise ? ValueToResponseSchema> : {} > export type ElysiaHandlerToResponseSchemas< Handle extends Function[], Carry extends PossibleResponse = {} > = Handle extends [infer Current, ...infer Rest] ? ElysiaHandlerToResponseSchemas< // @ts-ignore Trust me bro Rest, // @ts-ignore trust me bro UnionResponseStatus, Carry> > : Prettify export type ElysiaHandlerToResponseSchemaAmbiguous< Schemas extends MaybeArray > = MaybeArray<(...a: any) => any> extends Schemas ? {} : Schemas extends Function ? ElysiaHandlerToResponseSchema : Schemas extends Function[] ? ElysiaHandlerToResponseSchemas : {} type ReconcileStatus< in out A extends Record, in out B extends Record > = { // @ts-ignore Trust me bro [K in keyof A | keyof B]: K extends keyof A ? A[K] : B[K] } export type ComposeElysiaResponse< Schema extends RouteSchema, Handle, Possibility extends PossibleResponse > = ReconcileStatus< // @ts-ignore Schema['response'], UnionResponseStatus< ValueOrFunctionToResponseSchema, Possibility & (EmptyRouteSchema extends Pick ? {} : { 422: { type: 'validation' on: string summary?: string message?: string found?: unknown property?: string expected?: string } }) > > export type ExtractErrorFromHandle = { [ErrorResponse in Extract< Handle, AnyElysiaCustomStatusResponse > as ErrorResponse extends AnyElysiaCustomStatusResponse ? ErrorResponse['code'] : never]: Prettify } export type MergeElysiaInstances< Instances extends AnyElysia[] = [], Prefix extends string = '', Singleton extends SingletonBase = { decorator: {} store: {} derive: {} resolve: {} }, Definitions extends DefinitionBase = { typebox: {} error: {} }, Metadata extends MetadataBase = { schema: {} standaloneSchema: {} macro: {} macroFn: {} parser: {} response: {} }, Ephemeral extends EphemeralType = { derive: {} resolve: {} schema: {} standaloneSchema: {} response: {} }, Volatile extends EphemeralType = { derive: {} resolve: {} schema: {} standaloneSchema: {} response: {} }, Routes extends RouteBase = {} > = Instances extends [ infer Current extends AnyElysia, ...infer Rest extends AnyElysia[] ] ? MergeElysiaInstances< Rest, Prefix, Singleton & Current['~Singleton'], Definitions & Current['~Definitions'], Metadata & Current['~Metadata'], Ephemeral, Volatile & Current['~Ephemeral'], Routes & (Prefix extends `` ? Current['~Routes'] : CreateEden) > : Elysia< Prefix, { decorator: Singleton['decorator'] store: Prettify derive: Singleton['derive'] resolve: Singleton['resolve'] }, Definitions, Metadata, Routes, Ephemeral, Volatile > export type LifeCycleType = 'global' | 'local' | 'scoped' export type GuardSchemaType = 'override' | 'standalone' type PartialIf = Condition extends true ? Partial : T // Exclude return error() export type ExcludeElysiaResponse = PartialIf< Exclude, AnyElysiaCustomStatusResponse> extends infer A ? IsNever extends true ? {} : // Intersect all union and fallback never to {} A & {} : {}, undefined extends Awaited ? true : false > /** * @deprecated */ export type InferContext< T extends AnyElysia, Path extends string = T['~Prefix'], Schema extends RouteSchema = T['~Metadata']['schema'] > = Context< MergeSchema, T['~Singleton'] & { derive: T['~Ephemeral']['derive'] & T['~Volatile']['derive'] resolve: T['~Ephemeral']['resolve'] & T['~Volatile']['resolve'] }, Path > /** * @deprecated */ export type InferHandler< T extends AnyElysia, Path extends string = T['~Prefix'], Schema extends RouteSchema = T['~Metadata']['schema'] > = InlineHandler< MergeSchema, T['~Singleton'] & { derive: T['~Ephemeral']['derive'] & T['~Volatile']['derive'] resolve: T['~Ephemeral']['resolve'] & T['~Volatile']['resolve'] } > export interface ModelValidatorError extends ValueError { summary: string } // @ts-ignore trust me bro export interface ModelValidator extends TypeCheck { schema: T parse(a: T): T safeParse(a: T): | { success: true; data: T; error: null } | { success: true data: null error: string errors: ModelValidatorError[] } } export type UnionToIntersect = ( U extends unknown ? (arg: U) => 0 : never ) extends (arg: infer I) => 0 ? I : never export type ContextAppendType = 'append' | 'override' // new Elysia() // .wrap((fn) => { // return fn() // }) export type HigherOrderFunction< T extends (...arg: unknown[]) => Function = (...arg: unknown[]) => Function > = (fn: T, request: Request) => ReturnType type SetContentType = | 'application/octet-stream' | 'application/vnd.ms-fontobject' | 'application/epub+zip' | 'application/gzip' | 'application/json' | 'application/ld+json' | 'application/ogg' | 'application/pdf' | 'application/rtf' | 'application/wasm' | 'application/xhtml+xml' | 'application/xml' | 'application/zip' | 'text/css' | 'text/csv' | 'text/calendar' | 'text/event-stream' | 'text/html' | 'text/javascript' | 'text/plain' | 'text/xml' | 'image/avif' | 'image/bmp' | 'image/gif' | 'image/x-icon' | 'image/jpeg' | 'image/png' | 'image/svg+xml' | 'image/tiff' | 'image/webp' | 'multipart/mixed' | 'multipart/alternative' | 'multipart/form-data' | 'audio/aac' | 'audio/x-midi' | 'audio/mpeg' | 'audio/ogg' | 'audio/opus' | 'audio/webm' | 'video/x-msvideo' | 'video/quicktime' | 'video/x-ms-wmv' | 'video/x-msvideo' | 'video/x-flv' | 'video/av1' | 'video/mp4' | 'video/mpeg' | 'video/ogg' | 'video/mp2t' | 'video/webm' | 'video/3gpp' | 'video/3gpp2' | 'font/otf' | 'font/ttf' | 'font/woff' | 'font/woff2' | 'model/gltf+json' | 'model/gltf-binary' export type HTTPHeaders = Record & { // Authentication 'www-authenticate'?: string authorization?: string 'proxy-authenticate'?: string 'proxy-authorization'?: string // Caching age?: string 'cache-control'?: string 'clear-site-data'?: string expires?: string 'no-vary-search'?: string pragma?: string // Conditionals 'last-modified'?: string etag?: string 'if-match'?: string 'if-none-match'?: string 'if-modified-since'?: string 'if-unmodified-since'?: string vary?: string // Connection management connection?: string 'keep-alive'?: string // Content negotiation accept?: string 'accept-encoding'?: string 'accept-language'?: string // Controls expect?: string 'max-forwards'?: string // Cokies cookie?: string 'set-cookie'?: string | string[] // CORS 'access-control-allow-origin'?: string 'access-control-allow-credentials'?: string 'access-control-allow-headers'?: string 'access-control-allow-methods'?: string 'access-control-expose-headers'?: string 'access-control-max-age'?: string 'access-control-request-headers'?: string 'access-control-request-method'?: string origin?: string 'timing-allow-origin'?: string // Downloads 'content-disposition'?: string // Message body information 'content-length'?: string | number 'content-type'?: SetContentType | (string & {}) 'content-encoding'?: string 'content-language'?: string 'content-location'?: string // Proxies forwarded?: string via?: string // Redirects location?: string refresh?: string // Request context // from?: string // host?: string // referer?: string // 'user-agent'?: string // Response context allow?: string server?: 'Elysia' | (string & {}) // Range requests 'accept-ranges'?: string range?: string 'if-range'?: string 'content-range'?: string // Security 'content-security-policy'?: string 'content-security-policy-report-only'?: string 'cross-origin-embedder-policy'?: string 'cross-origin-opener-policy'?: string 'cross-origin-resource-policy'?: string 'expect-ct'?: string 'permission-policy'?: string 'strict-transport-security'?: string 'upgrade-insecure-requests'?: string 'x-content-type-options'?: string 'x-frame-options'?: string 'x-xss-protection'?: string // Server-sent events 'last-event-id'?: string 'ping-from'?: string 'ping-to'?: string 'report-to'?: string // Transfer coding te?: string trailer?: string 'transfer-encoding'?: string // Other 'alt-svg'?: string 'alt-used'?: string date?: string dnt?: string 'early-data'?: string 'large-allocation'?: string link?: string 'retry-after'?: string 'service-worker-allowed'?: string 'source-map'?: string upgrade?: string // Non-standard 'x-dns-prefetch-control'?: string 'x-forwarded-for'?: string 'x-forwarded-host'?: string 'x-forwarded-proto'?: string 'x-powered-by'?: 'Elysia' | (string & {}) 'x-request-id'?: string 'x-requested-with'?: string 'x-robots-tag'?: string 'x-ua-compatible'?: string } export type JoinPath< A extends string, B extends string > = B extends `/${string}` ? `${A}${B}` : `${A}/${B}` export type UnwrapTypeModule> = Module extends TModule ? Type : {} export type MergeTypeModule< A extends TModule, B extends TModule > = TModule & UnwrapTypeModule>> export type SSEPayload< Data extends unknown = unknown, Event extends string | undefined = string | undefined > = { /** id of the event */ id?: string | number | null /** event name */ event?: Event /** retry in millisecond */ retry?: number /** data to send */ data?: Data } export type UnionResponseStatus = {} extends A ? B : {} extends B ? A : { [key in keyof A | keyof B]: key extends keyof A ? key extends keyof B ? A[key] | B[key] : A[key] : key extends keyof B ? B[key] : never } export type CreateEdenResponse< Path extends string, Schema extends RouteSchema, MacroContext extends RouteSchema, // This should be handled by ComposeElysiaResponse Res extends PossibleResponse > = RouteSchema extends MacroContext ? { body: Schema['body'] params: IsNever extends true ? ResolvePath : Schema['params'] query: Schema['query'] headers: Schema['headers'] response: Prettify } : { body: Prettify params: IsNever< keyof (Schema['params'] & MacroContext['params']) > extends true ? ResolvePath : Prettify query: Prettify headers: Prettify response: Prettify } export interface Router { '~http': | Memoirist<{ compile: Function handler?: ComposedHandler }> | undefined get http(): Memoirist<{ compile: Function handler?: ComposedHandler }> '~dynamic': Memoirist | undefined get dynamic(): Memoirist // Static Router static: { [path: string]: { [method: string]: number } } // Native Static Response response: { [path: string]: | MaybePromise | { [method: string]: MaybePromise } } history: InternalRoute[] } export type ModelsToTypes> = { [K in keyof T]: UnwrapSchema } ================================================ FILE: src/universal/env.ts ================================================ import { isBun } from './utils' export const env = isBun ? Bun.env : typeof process !== 'undefined' && process?.env ? process.env : {} ================================================ FILE: src/universal/file.ts ================================================ /* eslint-disable sonarjs/no-duplicate-string */ import { type createReadStream as CreateReadStream } from 'fs' import { type stat as Stat } from 'fs/promises' import type { BunFile } from 'bun' import { isBun } from './utils' export const mime = { aac: 'audio/aac', abw: 'application/x-abiword', ai: 'application/postscript', arc: 'application/octet-stream', avi: 'video/x-msvideo', azw: 'application/vnd.amazon.ebook', bin: 'application/octet-stream', bz: 'application/x-bzip', bz2: 'application/x-bzip2', csh: 'application/x-csh', css: 'text/css', csv: 'text/csv', doc: 'application/msword', dll: 'application/octet-stream', eot: 'application/vnd.ms-fontobject', epub: 'application/epub+zip', gif: 'image/gif', htm: 'text/html', html: 'text/html', ico: 'image/x-icon', ics: 'text/calendar', jar: 'application/java-archive', jpeg: 'image/jpeg', jpg: 'image/jpeg', js: 'application/javascript', json: 'application/json', mid: 'audio/midi', midi: 'audio/midi', mp2: 'audio/mpeg', mp3: 'audio/mpeg', mp4: 'video/mp4', mpa: 'video/mpeg', mpe: 'video/mpeg', mpeg: 'video/mpeg', mpkg: 'application/vnd.apple.installer+xml', odp: 'application/vnd.oasis.opendocument.presentation', ods: 'application/vnd.oasis.opendocument.spreadsheet', odt: 'application/vnd.oasis.opendocument.text', oga: 'audio/ogg', ogv: 'video/ogg', ogx: 'application/ogg', otf: 'font/otf', png: 'image/png', pdf: 'application/pdf', ppt: 'application/vnd.ms-powerpoint', rar: 'application/x-rar-compressed', rtf: 'application/rtf', sh: 'application/x-sh', svg: 'image/svg+xml', swf: 'application/x-shockwave-flash', tar: 'application/x-tar', tif: 'image/tiff', tiff: 'image/tiff', ts: 'application/typescript', ttf: 'font/ttf', txt: 'text/plain', vsd: 'application/vnd.visio', wav: 'audio/x-wav', weba: 'audio/webm', webm: 'video/webm', webp: 'image/webp', woff: 'font/woff', woff2: 'font/woff2', xhtml: 'application/xhtml+xml', xls: 'application/vnd.ms-excel', xlsx: 'application/vnd.ms-excel', xlsx_OLD: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', xml: 'application/xml', xul: 'application/vnd.mozilla.xul+xml', zip: 'application/zip', '3gp': 'video/3gpp', '3gp_DOES_NOT_CONTAIN_VIDEO': 'audio/3gpp', '3gp2': 'video/3gpp2', '3gp2_DOES_NOT_CONTAIN_VIDEO': 'audio/3gpp2', '7z': 'application/x-7z-compressed' } as const export const getFileExtension = (path: string) => { const index = path.lastIndexOf('.') if (index === -1) return '' return path.slice(index + 1) } export const file = (path: string) => new ElysiaFile(path) let createReadStream: typeof CreateReadStream let stat: typeof Stat export class ElysiaFile { readonly value: unknown readonly stats: ReturnType | undefined constructor(public path: string) { if (isBun) this.value = Bun.file(path) else { // Browser // @ts-ignore if (!createReadStream || !stat) { // @ts-ignore if (typeof window !== 'undefined') { console.warn('Browser environment does not support file') return } const warnMissing = (name?: string) => console.warn( new Error( `[elysia] \`file\` require \`fs${name ? '.' + name : ''}\` ${name?.includes('.') ? 'module ' : ''}which is not available in this environment` ) ) if ( typeof process === 'undefined' || typeof process.getBuiltinModule !== 'function' ) { warnMissing() return } const fs = process.getBuiltinModule('fs') if (!fs) { warnMissing() return } if (typeof fs.createReadStream !== 'function') { warnMissing() return } if (typeof fs.promises?.stat !== 'function') { warnMissing() return } createReadStream = fs.createReadStream stat = fs.promises.stat } // Readstream can be only readonce // IIFE to ensure it's created immediately this.value = (() => createReadStream(path))() this.stats = stat(path)! } } get type() { return ( // @ts-ignore mime[getFileExtension(this.path)] || 'application/octet-stream' ) } get length() { if (isBun) return (this.value as BunFile).size return this.stats?.then((x) => x.size) ?? 0 } } ================================================ FILE: src/universal/index.ts ================================================ export { env } from './env' export { file } from './file' export type { ErrorLike, GenericServeOptions, Serve, ServeOptions, Server, ServerWebSocketSendStatus, SocketAddress } from './server' ================================================ FILE: src/universal/request.ts ================================================ import type { HeadersInit, RequestCache, RequestCredentials, RequestDestination, RequestDuplex, RequestInfo, RequestInit, RequestMode, RequestRedirect, WebStandardRequest } from './types' export class ElysiaRequest implements WebStandardRequest { constructor( private input: RequestInfo, private init?: RequestInit ) { if (typeof input === 'string') this.url = input else if (input instanceof URL) this.url = input.href else if (input instanceof Request) this.url = input.url else throw new TypeError('Invalid url') if (init) { if (init.method) this.method = init.method if (init.keepalive) this.keepalive = init.keepalive if (init.redirect) this.redirect = init.redirect if (init.integrity) this.integrity = init.integrity if (init.signal) this._signal = init.signal if (init.credentials) this.credentials = init.credentials if (init.mode) this.mode = init.mode if (init.referrerPolicy) this.referrerPolicy = init.referrerPolicy if (init.duplex) this.duplex = init.duplex } } readonly cache = 'default' as RequestCache readonly credentials = 'omit' as RequestCredentials readonly destination = '' as RequestDestination readonly integrity: string = '' readonly method: string = 'GET' readonly mode = 'no-cors' as RequestMode readonly redirect = 'manual' as RequestRedirect readonly referrerPolicy: string = '' readonly url: string private _headers: Headers | undefined get headers() { if (this._headers) return this._headers // @ts-ignore Bun if (!this.init?.headers) return (this._headers = new Headers()) const headers = this.init.headers if (Array.isArray(headers)) // @ts-ignore Bun return (this._headers = new Headers(headers)) if (headers instanceof Headers) // @ts-ignore Bun return (this._headers = headers) if (headers) // @ts-ignore Bun return (this._headers = new Headers(headers as HeadersInit)) return (this._headers = new Headers() as Headers) } readonly keepalive: boolean = false private _signal: AbortSignal | undefined get signal() { if (this._signal) return this._signal return (this._signal = new AbortController().signal) } readonly duplex = 'half' as RequestDuplex readonly bodyUsed: boolean = false get body(): ReadableStream | null { if (this.method === 'GET' || this.method === 'HEAD' || !this.init?.body) return null const body = this.init.body if (body instanceof ReadableStream) return body if (body instanceof ArrayBuffer) return new ReadableStream({ start(controller) { controller.enqueue(body) controller.close() } }) if (body instanceof Blob) return body.stream() if (typeof body === 'string') return new ReadableStream({ start(controller) { controller.enqueue(new TextEncoder().encode(body)) controller.close() } }) if (body instanceof URLSearchParams || body instanceof FormData) return new ReadableStream({ start(controller) { controller.enqueue( new TextEncoder().encode(body.toString()) ) controller.close() } }) if (body instanceof DataView) return new ReadableStream({ start(controller) { controller.enqueue(body.buffer) controller.close() } }) if (Symbol.iterator in body) return new ReadableStream({ start(controller) { for (const chunk of body) controller.enqueue(chunk) controller.close() } }) if (Symbol.asyncIterator in body) return new ReadableStream({ async start(controller) { for await (const chunk of body) controller.enqueue(chunk) controller.close() } }) return null } async arrayBuffer() { if (this.init?.body instanceof ArrayBuffer) return this.init.body if (!this.body) return new ArrayBuffer(0) const chunks = [] for await (const chunk of this.body) chunks.push(chunk) return Buffer.concat(chunks) as unknown as ArrayBuffer } async blob() { if (this.init?.body instanceof Blob) return this.init.body const buffer = await this.arrayBuffer() return new Blob([buffer]) as Blob } async formData() { if (this.init?.body instanceof FormData) return this.init.body throw new Error('Unable to parse body as FormData') } async json() { if (this.init?.body instanceof ReadableStream) return JSON.parse(await readableStreamToString(this.init.body)) if (typeof this.init?.body === 'string') return JSON.parse(this.init.body) if (this.init?.body instanceof ArrayBuffer) return JSON.parse(Buffer.from(this.init.body).toString()) return JSON.parse(Buffer.from(await this.arrayBuffer()).toString()) } async text() { if (this.init?.body instanceof ReadableStream) return readableStreamToString(this.init.body) if (typeof this.init?.body === 'string') return this.init.body if (this.init?.body instanceof ArrayBuffer) return Buffer.from(this.init.body).toString() const buffer = await this.arrayBuffer() return Buffer.from(buffer).toString() } // @ts-ignore clone(): ElysiaRequest { return new ElysiaRequest(this.input, this.init) } } async function readableStreamToString(stream: ReadableStream) { const chunks = [] for await (const chunk of stream) chunks.push(chunk) // @ts-ignore this is intentional, it works return Buffer.from(Buffer.concat(chunks)).toString() } ================================================ FILE: src/universal/server.ts ================================================ import { Serve as BunServe, type Server as BunServer } from 'bun' import type { Equal, MaybePromise } from '../types' export interface ErrorLike extends Error { code?: string errno?: number syscall?: string } export interface GenericServeOptions { /** * What URI should be used to make {@link Request.url} absolute? * * By default, looks at {@link hostname}, {@link port}, and whether or not SSL is enabled to generate one * * @example * ```js * "http://my-app.com" * ``` * * @example * ```js * "https://wongmjane.com/" * ``` * * This should be the public, absolute URL – include the protocol and {@link hostname}. If the port isn't 80 or 443, then include the {@link port} too. * * @example * "http://localhost:3000" */ // baseURI?: string; /** * What is the maximum size of a request body? (in bytes) * @default 1024 * 1024 * 128 // 128MB */ maxRequestBodySize?: number /** * Render contextual errors? This enables bun's error page * @default process.env.NODE_ENV !== 'production' */ development?: boolean error?: ( this: Server, request: ErrorLike ) => Response | Promise | undefined | Promise /** * Uniquely identify a server instance with an ID * * ### When bun is started with the `--hot` flag * * This string will be used to hot reload the server without interrupting * pending requests or websockets. If not provided, a value will be * generated. To disable hot reloading, set this value to `null`. * * ### When bun is not started with the `--hot` flag * * This string will currently do nothing. But in the future it could be useful for logs or metrics. */ id?: string | null } export interface ServeOptions extends GenericServeOptions { /** * What port should the server listen on? * @default process.env.PORT || "3000" */ port?: string | number /** * If the `SO_REUSEPORT` flag should be set. * * This allows multiple processes to bind to the same port, which is useful for load balancing. * * @default false */ reusePort?: boolean /** * What hostname should the server listen on? * * @default * ```js * "0.0.0.0" // listen on all interfaces * ``` * @example * ```js * "127.0.0.1" // Only listen locally * ``` * @example * ```js * "remix.run" // Only listen on remix.run * ```` * * note: hostname should not include a {@link port} */ hostname?: string /** * If set, the HTTP server will listen on a unix socket instead of a port. * (Cannot be used with hostname+port) */ unix?: never /** * Handle HTTP requests * * Respond to {@link Request} objects with a {@link Response} object. */ fetch( this: Server, request: Request, server: Server ): Response | Promise routes: Record< string, Function | Response | Record > } export type Serve = Equal, unknown> extends false ? BunServe.Options : ServeOptions export type Server = Equal, unknown> extends false ? BunServer : ServerOptions export type ServerWebSocketSendStatus = number export interface SocketAddress { /** * The IP address of the client. */ address: string /** * The port of the client. */ port: number /** * The IP family ("IPv4" or "IPv6"). */ family: 'IPv4' | 'IPv6' } export interface ServerOptions extends Disposable { /** * Stop listening to prevent new connections from being accepted. * * By default, it does not cancel in-flight requests or websockets. That means it may take some time before all network activity stops. * * @param closeActiveConnections Immediately terminate in-flight requests, websockets, and stop accepting new connections. * @default false */ stop(closeActiveConnections?: boolean): void /** * Update the `fetch` and `error` handlers without restarting the server. * * This is useful if you want to change the behavior of your server without * restarting it or for hot reloading. * * @example * * ```js * // create the server * const server = Bun.serve({ * fetch(request) { * return new Response("Hello World v1") * } * }); * * // Update the server to return a different response * server.reload({ * fetch(request) { * return new Response("Hello World v2") * } * }); * ``` * * Passing other options such as `port` or `hostname` won't do anything. */ reload(options: Serve): void /** * Mock the fetch handler for a running server. * * This feature is not fully implemented yet. It doesn't normalize URLs * consistently in all cases and it doesn't yet call the `error` handler * consistently. This needs to be fixed */ fetch(request: Request | string): Response | Promise /** * Upgrade a {@link Request} to a {@link ServerWebSocket} * * @param request The {@link Request} to upgrade * @param options Pass headers or attach data to the {@link ServerWebSocket} * * @returns `true` if the upgrade was successful and `false` if it failed * * @example * ```js * import { serve } from "bun"; * serve({ * websocket: { * open: (ws) => { * console.log("Client connected"); * }, * message: (ws, message) => { * console.log("Client sent message", message); * }, * close: (ws) => { * console.log("Client disconnected"); * }, * }, * fetch(req, server) { * const url = new URL(req.url); * if (url.pathname === "/chat") { * const upgraded = server.upgrade(req); * if (!upgraded) { * return new Response("Upgrade failed", { status: 400 }); * } * } * return new Response("Hello World"); * }, * }); * ``` * What you pass to `data` is available on the {@link ServerWebSocket.data} property */ upgrade( request: Request, options?: { /** * Send any additional headers while upgrading, like cookies */ headers?: Bun.HeadersInit /** * This value is passed to the {@link ServerWebSocket.data} property */ data?: T } ): boolean /** * Send a message to all connected {@link ServerWebSocket} subscribed to a topic * * @param topic The topic to publish to * @param data The data to send * @param compress Should the data be compressed? Ignored if the client does not support compression. * * @returns 0 if the message was dropped, -1 if backpressure was applied, or the number of bytes sent. * * @example * * ```js * server.publish("chat", "Hello World"); * ``` * * @example * ```js * server.publish("chat", new Uint8Array([1, 2, 3, 4])); * ``` * * @example * ```js * server.publish("chat", new ArrayBuffer(4), true); * ``` * * @example * ```js * server.publish("chat", new DataView(new ArrayBuffer(4))); * ``` */ publish( topic: string, data: string | ArrayBufferView | ArrayBuffer | SharedArrayBuffer, compress?: boolean ): ServerWebSocketSendStatus /** * Returns the client IP address and port of the given Request. If the request was closed or is a unix socket, returns null. * * @example * ```js * export default { * async fetch(request, server) { * return new Response(server.requestIP(request)); * } * } * ``` */ requestIP(request: Request): SocketAddress | null /** * Reset the idleTimeout of the given Request to the number in seconds. 0 means no timeout. * * @example * ```js * export default { * async fetch(request, server) { * server.timeout(request, 60); * await Bun.sleep(30000); * return new Response("30 seconds have passed"); * } * } * ``` */ timeout(request: Request, seconds: number): void /** * Undo a call to {@link Server.unref} * * If the Server has already been stopped, this does nothing. * * If {@link Server.ref} is called multiple times, this does nothing. Think of it as a boolean toggle. */ ref(): void /** * Don't keep the process alive if this server is the only thing left. * Active connections may continue to keep the process alive. * * By default, the server is ref'd. * * To prevent new connections from being accepted, use {@link Server.stop} */ unref(): void /** * How many requests are in-flight right now? */ readonly pendingRequests: number /** * How many {@link ServerWebSocket}s are in-flight right now? */ readonly pendingWebSockets: number readonly url: URL readonly port: number /** * The hostname the server is listening on. Does not include the port * @example * ```js * "localhost" * ``` */ readonly hostname: string /** * Is the server running in development mode? * * In development mode, `Bun.serve()` returns rendered error messages with * stack traces instead of a generic 500 error. This makes debugging easier, * but development mode shouldn't be used in production or you will risk * leaking sensitive information. */ readonly development: boolean /** * An identifier of the server instance * * When bun is started with the `--hot` flag, this ID is used to hot reload the server without interrupting pending requests or websockets. * * When bun is not started with the `--hot` flag, this ID is currently unused. */ readonly id: string } export type ListenCallback = (server: Server) => MaybePromise ================================================ FILE: src/universal/types.ts ================================================ /* eslint-disable @typescript-eslint/no-unused-vars */ // based on https://github.com/Ethan-Arrowood/undici-fetch/blob/249269714db874351589d2d364a0645d5160ae71/index.d.ts (MIT license) // and https://github.com/node-fetch/node-fetch/blob/914ce6be5ec67a8bab63d68510aabf07cb818b6d/index.d.ts (MIT license) export type RequestInfo = string | URL | Request export declare function fetch( input: RequestInfo, init?: RequestInit ): Promise export type BodyInit = | ArrayBuffer | AsyncIterable | Blob | FormData | Iterable | NodeJS.ArrayBufferView | URLSearchParams | null | string export interface BodyMixin { readonly body: ReadableStream | null readonly bodyUsed: boolean readonly arrayBuffer: () => Promise readonly blob: () => Promise readonly formData: () => Promise readonly json: () => Promise readonly text: () => Promise } export interface SpecIterator { next(...args: [] | [TNext]): IteratorResult } export interface SpecIterableIterator extends SpecIterator { [Symbol.iterator](): SpecIterableIterator } export interface SpecIterable { [Symbol.iterator](): SpecIterator } export type HeadersInit = | string[][] | Record> | Headers export declare class Headers implements SpecIterable<[string, string]> { constructor(init?: HeadersInit) readonly append: (name: string, value: string) => void readonly delete: (name: string) => void readonly get: (name: string) => string | null readonly has: (name: string) => boolean readonly set: (name: string, value: string) => void readonly getSetCookie: () => string[] readonly forEach: ( callbackfn: (value: string, key: string, iterable: Headers) => void, thisArg?: unknown ) => void readonly keys: () => SpecIterableIterator readonly values: () => SpecIterableIterator readonly entries: () => SpecIterableIterator<[string, string]> readonly [Symbol.iterator]: () => SpecIterator<[string, string]> } export type RequestCache = | 'default' | 'force-cache' | 'no-cache' | 'no-store' | 'only-if-cached' | 'reload' export type RequestCredentials = 'omit' | 'include' | 'same-origin' export type RequestDestination = | '' | 'audio' | 'audioworklet' | 'document' | 'embed' | 'font' | 'image' | 'manifest' | 'object' | 'paintworklet' | 'report' | 'script' | 'sharedworker' | 'style' | 'track' | 'video' | 'worker' | 'xslt' export interface RequestInit { method?: string keepalive?: boolean headers?: HeadersInit body?: BodyInit redirect?: RequestRedirect integrity?: string signal?: AbortSignal credentials?: RequestCredentials mode?: RequestMode referrer?: string referrerPolicy?: ReferrerPolicy window?: null dispatcher?: unknown duplex?: RequestDuplex } export type ReferrerPolicy = | '' | 'no-referrer' | 'no-referrer-when-downgrade' | 'origin' | 'origin-when-cross-origin' | 'same-origin' | 'strict-origin' | 'strict-origin-when-cross-origin' | 'unsafe-url' export type RequestMode = 'cors' | 'navigate' | 'no-cors' | 'same-origin' export type RequestRedirect = 'error' | 'follow' | 'manual' export type RequestDuplex = 'half' export abstract class WebStandardRequest implements BodyMixin { abstract readonly cache: RequestCache abstract readonly credentials: RequestCredentials abstract readonly destination: RequestDestination abstract readonly headers: Headers abstract readonly integrity: string abstract readonly method: string abstract readonly mode: RequestMode abstract readonly redirect: RequestRedirect abstract readonly referrerPolicy: string abstract readonly url: string abstract readonly keepalive: boolean abstract readonly signal: AbortSignal abstract readonly duplex: RequestDuplex abstract readonly body: ReadableStream | null abstract readonly bodyUsed: boolean abstract readonly arrayBuffer: () => Promise abstract readonly blob: () => Promise abstract readonly formData: () => Promise abstract readonly json: () => Promise abstract readonly text: () => Promise abstract readonly clone: () => Request } export interface ResponseInit { readonly status?: number readonly statusText?: string readonly headers?: HeadersInit } export type ResponseType = | 'basic' | 'cors' | 'default' | 'error' | 'opaque' | 'opaqueredirect' export type ResponseRedirectStatus = 301 | 302 | 303 | 307 | 308 export abstract class WebStandardResponse implements BodyMixin { constructor(body?: BodyInit, init?: ResponseInit) {} abstract readonly headers: Headers abstract readonly ok: boolean abstract readonly status: number abstract readonly statusText: string abstract readonly type: ResponseType abstract readonly url: string abstract readonly redirected: boolean abstract readonly body: ReadableStream | null abstract readonly bodyUsed: boolean abstract readonly arrayBuffer: () => Promise abstract readonly blob: () => Promise abstract readonly formData: () => Promise abstract readonly json: () => Promise abstract readonly text: () => Promise abstract readonly clone: () => Response static error() { return Response.error() } static json(data: any, init?: ResponseInit) { return Response.json(data, init as any) } static redirect(url: string, status: ResponseRedirectStatus) { return Response.redirect(url, status) } } export interface BunHTMLBundlelike { index: string files?: { input?: string path: string loader: any isEntry: boolean headers: { etag: string 'content-type': string [key: string]: string } }[] } ================================================ FILE: src/universal/utils.ts ================================================ // @ts-ignore export const isBun = typeof Bun !== 'undefined' // @ts-ignore export const isDeno = typeof Deno !== 'undefined' export function isCloudflareWorker() { try { // Check for the presence of caches.default, which is a global in Workers if ( // @ts-ignore typeof caches !== 'undefined' && // @ts-ignore typeof caches.default !== 'undefined' ) return true // @ts-ignore if (typeof WebSocketPair !== 'undefined') return true } catch { return false } return false } ================================================ FILE: src/utils.ts ================================================ import type { Sucrose } from './sucrose' import type { TraceHandler } from './trace' import type { LifeCycleStore, MaybeArray, InputSchema, LifeCycleType, HookContainer, GracefulHandler, PreHandler, BodyHandler, TransformHandler, OptionalHandler, MapResponse, ErrorHandler, Replace, AfterResponseHandler, SchemaValidator, AnyLocalHook, SSEPayload, Prettify, RouteSchema } from './types' import { ElysiaFile } from './universal/file' import { isBun, isCloudflareWorker } from './universal/utils' export const replaceUrlPath = (url: string, pathname: string) => { const pathStartIndex = url.indexOf('/', 11) const queryIndex = url.indexOf('?', pathStartIndex) if (queryIndex === -1) return `${url.slice(0, pathStartIndex)}${pathname.charCodeAt(0) === 47 ? '' : '/'}${pathname}` return `${url.slice(0, pathStartIndex)}${pathname.charCodeAt(0) === 47 ? '' : '/'}${pathname}${url.slice(queryIndex)}` } export const isClass = (v: Object) => (typeof v === 'function' && /^\s*class\s+/.test(v.toString())) || // Handle Object.create(null) (v.toString && // Handle import * as Sentry from '@sentry/bun' // This also handle [object Date], [object Array] // and FFI value like [object Prisma] v.toString().startsWith('[object ') && v.toString() !== '[object Object]') || // If object prototype is not pure, then probably a class-like object isNotEmpty(Object.getPrototypeOf(v)) const isObject = (item: any): item is Object => item && typeof item === 'object' && !Array.isArray(item) export const mergeDeep = < A extends Record, B extends Record >( target: A, source: B, options?: { skipKeys?: string[] override?: boolean mergeArray?: boolean seen?: WeakSet } ): A & B => { const skipKeys = options?.skipKeys const override = options?.override ?? true const mergeArray = options?.mergeArray ?? false const seen = options?.seen ?? new WeakSet() if (!isObject(target) || !isObject(source)) return target as A & B if (seen.has(source)) return target as A & B seen.add(source) for (const [key, value] of Object.entries(source)) { if ( skipKeys?.includes(key) || ['__proto__', 'constructor', 'prototype'].includes(key) ) continue if (mergeArray && Array.isArray(value)) { target[key as keyof typeof target] = Array.isArray( (target as any)[key] ) ? [...(target as any)[key], ...value] : (target[key as keyof typeof target] = value as any) continue } if (!isObject(value) || !(key in target) || isClass(value)) { if ((override || !(key in target)) && !Object.isFrozen(target)) try { target[key as keyof typeof target] = value } catch {} continue } if (!Object.isFrozen(target[key])) try { target[key as keyof typeof target] = mergeDeep( (target as any)[key] as any, value, { skipKeys, override, mergeArray, seen } ) } catch {} } seen.delete(source) return target as A & B } export const mergeCookie = ( a: A, b: B ): A & B => { const v = mergeDeep(Object.assign({}, a), b, { skipKeys: ['properties'], mergeArray: false }) as A & B // @ts-expect-error if (v.properties) delete v.properties return v } export const mergeObjectArray = ( a: T | T[] | undefined, b: T | T[] | undefined ): T[] | undefined => { if (!b) return a as any // ! Must copy to remove side-effect const array = [] const checksums = <(number | undefined)[]>[] if (a) { if (!Array.isArray(a)) a = [a] for (const item of a) { array.push(item) if (item.checksum) checksums.push(item.checksum) } } if (b) { if (!Array.isArray(b)) b = [b] for (const item of b) if (!checksums.includes(item.checksum)) array.push(item) } return array } export const primitiveHooks = [ 'start', 'request', 'parse', 'transform', 'resolve', 'beforeHandle', 'afterHandle', 'mapResponse', 'afterResponse', 'trace', 'error', 'stop', 'body', 'headers', 'params', 'query', 'response', 'type', 'detail' ] as const const primitiveHookMap = primitiveHooks.reduce( (acc, x) => ((acc[x] = true), acc), {} as Record ) // If both are Record then merge them, // giving preference to b. type RecordNumber = Record const isRecordNumber = ( x: Record | undefined ): x is RecordNumber => typeof x === 'object' && Object.keys(x).every((x) => !isNaN(+x)) export const mergeResponse = ( a: InputSchema['response'], b: InputSchema['response'] ) => { if (isRecordNumber(a) && isRecordNumber(b)) // Prevent side effect return Object.assign({}, a, b) else if (a && !isRecordNumber(a) && isRecordNumber(b)) return Object.assign({ 200: a }, b) return b ?? a } export const mergeSchemaValidator = ( a?: SchemaValidator | null, b?: SchemaValidator | null ): SchemaValidator => { if (!a && !b) return { body: undefined, headers: undefined, params: undefined, query: undefined, cookie: undefined, response: undefined } return { body: b?.body ?? a?.body, headers: b?.headers ?? a?.headers, params: b?.params ?? a?.params, query: b?.query ?? a?.query, cookie: b?.cookie ?? a?.cookie, // @ts-ignore ? This order is correct - SaltyAom response: mergeResponse( // @ts-ignore a?.response, // @ts-ignore b?.response ) } } export const mergeHook = ( a?: Partial, b?: AnyLocalHook // { allowMacro = false }: { allowMacro?: boolean } = {} ): LifeCycleStore => { // In case if merging union is need // const customAStore: Record = {} // const customBStore: Record = {} // for (const [key, value] of Object.entries(a)) { // if (primitiveHooks.includes(key as any)) continue // customAStore[key] = value // } // for (const [key, value] of Object.entries(b)) { // if (primitiveHooks.includes(key as any)) continue // customBStore[key] = value // } // const unioned = Object.keys(customAStore).filter((x) => // Object.keys(customBStore).includes(x) // ) // // Must provide empty object to prevent reference side-effect // const customStore = Object.assign({}, customAStore, customBStore) // for (const union of unioned) // customStore[union] = mergeObjectArray( // customAStore[union], // customBStore[union] // ) if (!b) return (a as any) ?? {} if (!a) return b ?? {} if (!Object.values(b).find((x) => x !== undefined && x !== null)) return { ...a } as any const hook = { ...a, ...b, // Merge local hook first // @ts-ignore body: b.body ?? a.body, // @ts-ignore headers: b.headers ?? a.headers, // @ts-ignore params: b.params ?? a.params, // @ts-ignore query: b.query ?? a.query, // @ts-ignore cookie: b.cookie ?? a.cookie, // ? This order is correct - SaltyAom response: mergeResponse( // @ts-ignore a.response, // @ts-ignore b.response ), type: a.type || b.type, detail: mergeDeep( // @ts-ignore b.detail ?? {}, // @ts-ignore a.detail ?? {} ), parse: mergeObjectArray(a.parse as any, b.parse), transform: mergeObjectArray(a.transform, b.transform), beforeHandle: mergeObjectArray( mergeObjectArray( // @ts-ignore fnToContainer(a.resolve, 'resolve'), a.beforeHandle ), mergeObjectArray( fnToContainer(b.resolve, 'resolve'), b.beforeHandle ) ), afterHandle: mergeObjectArray(a.afterHandle, b.afterHandle), mapResponse: mergeObjectArray(a.mapResponse, b.mapResponse) as any, afterResponse: mergeObjectArray( a.afterResponse, b.afterResponse ) as any, trace: mergeObjectArray(a.trace, b.trace) as any, error: mergeObjectArray(a.error, b.error), // @ts-ignore standaloneSchema: // @ts-ignore a.standaloneSchema || b.standaloneSchema ? // @ts-ignore a.standaloneSchema && !b.standaloneSchema ? // @ts-ignore a.standaloneSchema : // @ts-ignore b.standaloneSchema && !a.standaloneSchema ? b.standaloneSchema : [ // @ts-ignore ...(a.standaloneSchema ?? []), ...(b.standaloneSchema ?? []) ] : undefined } if (hook.resolve) delete hook.resolve return hook } export const lifeCycleToArray = (a: LifeCycleStore) => { if (a.parse && !Array.isArray(a.parse)) a.parse = [a.parse] if (a.transform && !Array.isArray(a.transform)) a.transform = [a.transform] if (a.afterHandle && !Array.isArray(a.afterHandle)) a.afterHandle = [a.afterHandle] if (a.mapResponse && !Array.isArray(a.mapResponse)) a.mapResponse = [a.mapResponse] if (a.afterResponse && !Array.isArray(a.afterResponse)) a.afterResponse = [a.afterResponse] if (a.trace && !Array.isArray(a.trace)) a.trace = [a.trace] if (a.error && !Array.isArray(a.error)) a.error = [a.error] let beforeHandle = [] // @ts-expect-error if (a.resolve) { beforeHandle = fnToContainer( // @ts-expect-error Array.isArray(a.resolve) ? a.resolve : [a.resolve], 'resolve' ) as any[] // @ts-expect-error delete a.resolve } if (a.beforeHandle) { if (beforeHandle.length) beforeHandle = beforeHandle.concat( Array.isArray(a.beforeHandle) ? a.beforeHandle : [a.beforeHandle] ) else beforeHandle = Array.isArray(a.beforeHandle) ? a.beforeHandle : [a.beforeHandle] } if (beforeHandle.length) a.beforeHandle = beforeHandle return a } export const hasHeaderShorthand = isBun ? 'toJSON' in new Headers() : false export const hasSetImmediate = typeof setImmediate === 'function' // https://stackoverflow.com/a/52171480 export const checksum = (s: string) => { let h = 9 for (let i = 0; i < s.length; ) h = Math.imul(h ^ s.charCodeAt(i++), 9 ** 9) return (h = h ^ (h >>> 9)) } export const injectChecksum = ( checksum: number | undefined, x: MaybeArray | undefined ) => { if (!x) return if (!Array.isArray(x)) { // ? clone fn is required to prevent side-effect from changing hookType const fn = x if (checksum && !fn.checksum) fn.checksum = checksum if (fn.scope === 'scoped') fn.scope = 'local' return fn } // ? clone fns is required to prevent side-effect from changing hookType const fns = [...x] for (const fn of fns) { if (checksum && !fn.checksum) fn.checksum = checksum if (fn.scope === 'scoped') fn.scope = 'local' } return fns } export const mergeLifeCycle = ( a: Partial, b: Partial, checksum?: number ): LifeCycleStore => { return { start: mergeObjectArray( a.start, injectChecksum(checksum, b?.start) ) as HookContainer>[], request: mergeObjectArray( a.request, injectChecksum(checksum, b?.request) ) as HookContainer>[], parse: mergeObjectArray( a.parse, injectChecksum(checksum, b?.parse) ) as HookContainer>[], transform: mergeObjectArray( a.transform, injectChecksum(checksum, b?.transform) ) as HookContainer>[], beforeHandle: mergeObjectArray( mergeObjectArray( // @ts-ignore fnToContainer(a.resolve, 'resolve'), a.beforeHandle ), injectChecksum( checksum, mergeObjectArray( fnToContainer(b?.resolve, 'resolve'), b?.beforeHandle ) ) ) as HookContainer>[], afterHandle: mergeObjectArray( a.afterHandle, injectChecksum(checksum, b?.afterHandle) ) as HookContainer>[], mapResponse: mergeObjectArray( a.mapResponse, injectChecksum(checksum, b?.mapResponse) ) as HookContainer>[], afterResponse: mergeObjectArray( a.afterResponse, injectChecksum(checksum, b?.afterResponse) ) as HookContainer>[], // Already merged on Elysia._use, also logic is more complicated, can't directly merge trace: mergeObjectArray( a.trace, injectChecksum(checksum, b?.trace) ) as HookContainer>[], error: mergeObjectArray( a.error, injectChecksum(checksum, b?.error) ) as HookContainer>[], stop: mergeObjectArray( a.stop, injectChecksum(checksum, b?.stop) ) as HookContainer>[] } } export const asHookType = ( fn: HookContainer, inject: LifeCycleType, { skipIfHasType = false }: { skipIfHasType?: boolean } ) => { if (!fn) return fn if (!Array.isArray(fn)) { if (skipIfHasType) fn.scope ??= inject else fn.scope = inject return fn } for (const x of fn) if (skipIfHasType) x.scope ??= inject else x.scope = inject return fn } const filterGlobal = (fn: MaybeArray) => { if (!fn) return fn if (!Array.isArray(fn)) switch (fn.scope) { case 'global': case 'scoped': return { ...fn } default: return { fn } } const array = [] for (const x of fn) switch (x.scope) { case 'global': case 'scoped': array.push({ ...x }) break } return array } export const filterGlobalHook = (hook: AnyLocalHook): AnyLocalHook => { return { // rest is validator ...hook, type: hook?.type, detail: hook?.detail, parse: filterGlobal(hook?.parse), transform: filterGlobal(hook?.transform), beforeHandle: filterGlobal(hook?.beforeHandle), afterHandle: filterGlobal(hook?.afterHandle), mapResponse: filterGlobal(hook?.mapResponse), afterResponse: filterGlobal(hook?.afterResponse), error: filterGlobal(hook?.error), trace: filterGlobal(hook?.trace) } } export const StatusMap = { Continue: 100, 'Switching Protocols': 101, Processing: 102, 'Early Hints': 103, OK: 200, Created: 201, Accepted: 202, 'Non-Authoritative Information': 203, 'No Content': 204, 'Reset Content': 205, 'Partial Content': 206, 'Multi-Status': 207, 'Already Reported': 208, 'Multiple Choices': 300, 'Moved Permanently': 301, Found: 302, 'See Other': 303, 'Not Modified': 304, 'Temporary Redirect': 307, 'Permanent Redirect': 308, 'Bad Request': 400, Unauthorized: 401, 'Payment Required': 402, Forbidden: 403, 'Not Found': 404, 'Method Not Allowed': 405, 'Not Acceptable': 406, 'Proxy Authentication Required': 407, 'Request Timeout': 408, Conflict: 409, Gone: 410, 'Length Required': 411, 'Precondition Failed': 412, 'Payload Too Large': 413, 'URI Too Long': 414, 'Unsupported Media Type': 415, 'Range Not Satisfiable': 416, 'Expectation Failed': 417, "I'm a teapot": 418, 'Enhance Your Calm': 420, 'Misdirected Request': 421, 'Unprocessable Content': 422, Locked: 423, 'Failed Dependency': 424, 'Too Early': 425, 'Upgrade Required': 426, 'Precondition Required': 428, 'Too Many Requests': 429, 'Request Header Fields Too Large': 431, 'Unavailable For Legal Reasons': 451, 'Internal Server Error': 500, 'Not Implemented': 501, 'Bad Gateway': 502, 'Service Unavailable': 503, 'Gateway Timeout': 504, 'HTTP Version Not Supported': 505, 'Variant Also Negotiates': 506, 'Insufficient Storage': 507, 'Loop Detected': 508, 'Not Extended': 510, 'Network Authentication Required': 511 } as const export const InvertedStatusMap = Object.fromEntries( Object.entries(StatusMap).map(([k, v]) => [v, k]) ) as { [K in keyof StatusMap as StatusMap[K]]: K } export type StatusMap = typeof StatusMap export type InvertedStatusMap = typeof InvertedStatusMap function removeTrailingEquals(digest: string): string { let trimmedDigest = digest while (trimmedDigest.endsWith('=')) trimmedDigest = trimmedDigest.slice(0, -1) return trimmedDigest } const encoder = new TextEncoder() export const signCookie = async (val: string, secret: string | null) => { if (typeof val === 'object') val = JSON.stringify(val) else if (typeof val !== 'string') val = val + '' if (secret === null || secret === undefined) throw new TypeError('Secret key must be provided') const secretKey = await crypto.subtle.importKey( 'raw', encoder.encode(secret), { name: 'HMAC', hash: 'SHA-256' }, false, ['sign'] ) const hmacBuffer = await crypto.subtle.sign( 'HMAC', secretKey, encoder.encode(val) ) // console.log({ // val, // secret, // hash: removeTrailingEquals(Buffer.from(hmacBuffer).toString('base64')) // }) return ( val + '.' + removeTrailingEquals(Buffer.from(hmacBuffer).toString('base64')) ) } const constantTimeEqual = typeof crypto?.timingSafeEqual === 'function' ? (a: string, b: string) => { // Compare as UTF-8 bytes; timingSafeEqual requires equal length const ab = Buffer.from(a, 'utf8') const bb = Buffer.from(b, 'utf8') if (ab.length !== bb.length) return false return crypto.timingSafeEqual(ab, bb) } : (a: string, b: string) => a === b export const unsignCookie = async (input: string, secret: string | null) => { if (typeof input !== 'string') throw new TypeError('Signed cookie string must be provided.') const dot = input.lastIndexOf('.') if (dot === -1) { if (secret === null) return input return false } const tentativeValue = input.slice(0, dot) const expectedInput = await signCookie(tentativeValue, secret) return constantTimeEqual(expectedInput, input) ? tentativeValue : false } export const insertStandaloneValidator = ( hook: { standaloneValidator: InputSchema[] }, name: Name, value: InputSchema[Name] ) => { if ( !hook.standaloneValidator?.length || !Array.isArray(hook.standaloneValidator) ) { hook.standaloneValidator = [ { [name]: value } ] return } const last = hook.standaloneValidator[hook.standaloneValidator.length - 1] if (name in last) hook.standaloneValidator.push({ [name]: value }) else last[name] = value } const parseNumericString = (message: string | number): number | null => { if (typeof message === 'number') return message if (message.length < 16) { if (message.trim().length === 0) return null const length = Number(message) if (Number.isNaN(length)) return null return length } // if 16 digit but less then 9,007,199,254,740,991 then can be parsed if (message.length === 16) { if (message.trim().length === 0) return null const number = Number(message) if (Number.isNaN(number) || number.toString() !== message) return null return number } return null } export const isNumericString = (message: string | number): boolean => parseNumericString(message) !== null export class PromiseGroup implements PromiseLike { root: Promise | null = null promises: Promise[] = [] constructor( public onError: (error: any) => void = console.error, public onFinally: () => void = () => {} ) {} /** * The number of promises still being awaited. */ get size() { return this.promises.length } /** * Add a promise to the group. * @returns The promise that was added. */ add(promise: Promise) { this.promises.push(promise) this.root ||= this.drain() if (this.promises.length === 1) this.then(this.onFinally) return promise } private async drain() { while (this.promises.length > 0) { try { await this.promises[0] } catch (error) { this.onError(error) } this.promises.shift() } this.root = null } // Allow the group to be awaited. then( onfulfilled?: | ((value: void) => TResult1 | PromiseLike) | undefined | null, onrejected?: | ((reason: any) => TResult2 | PromiseLike) | undefined | null ): PromiseLike { return (this.root ?? Promise.resolve()).then(onfulfilled, onrejected) } } export const fnToContainer = ( fn: MaybeArray, /** Only add subType to non contained fn */ subType?: HookContainer['subType'] ): MaybeArray => { if (!fn) return fn if (!Array.isArray(fn)) { // parse can be a label since 1.2.0 if (typeof fn === 'function' || typeof fn === 'string') return subType ? { fn, subType } : { fn } else if ('fn' in fn) return fn } const fns = [] for (const x of fn) { // parse can be a label since 1.2.0 if (typeof x === 'function' || typeof x === 'string') fns.push(subType ? { fn: x, subType } : { fn: x }) else if ('fn' in x) fns.push(x) } return fns } export const localHookToLifeCycleStore = (a: AnyLocalHook): LifeCycleStore => { if (a.start) a.start = fnToContainer(a.start) if (a.request) a.request = fnToContainer(a.request) if (a.parse) a.parse = fnToContainer(a.parse) if (a.transform) a.transform = fnToContainer(a.transform) if (a.beforeHandle) a.beforeHandle = fnToContainer(a.beforeHandle) if (a.afterHandle) a.afterHandle = fnToContainer(a.afterHandle) if (a.mapResponse) a.mapResponse = fnToContainer(a.mapResponse) if (a.afterResponse) a.afterResponse = fnToContainer(a.afterResponse) if (a.trace) a.trace = fnToContainer(a.trace) if (a.error) a.error = fnToContainer(a.error) if (a.stop) a.stop = fnToContainer(a.stop) return a } export const lifeCycleToFn = (a: Partial): AnyLocalHook => { const lifecycle = Object.create(null) if (a.start?.map) lifecycle.start = a.start.map((x) => x.fn) if (a.request?.map) lifecycle.request = a.request.map((x) => x.fn) if (a.parse?.map) lifecycle.parse = a.parse.map((x) => x.fn) if (a.transform?.map) lifecycle.transform = a.transform.map((x) => x.fn) if (a.beforeHandle?.map) lifecycle.beforeHandle = a.beforeHandle.map((x) => x.fn) if (a.afterHandle?.map) lifecycle.afterHandle = a.afterHandle.map((x) => x.fn) if (a.mapResponse?.map) lifecycle.mapResponse = a.mapResponse.map((x) => x.fn) if (a.afterResponse?.map) lifecycle.afterResponse = a.afterResponse.map((x) => x.fn) if (a.error?.map) lifecycle.error = a.error.map((x) => x.fn) if (a.stop?.map) lifecycle.stop = a.stop.map((x) => x.fn) if (a.trace?.map) lifecycle.trace = a.trace.map((x) => x.fn) else lifecycle.trace = [] return lifecycle } export const cloneInference = (inference: Sucrose.Inference) => ({ body: inference.body, cookie: inference.cookie, headers: inference.headers, query: inference.query, set: inference.set, server: inference.server, path: inference.path, route: inference.route, url: inference.url }) satisfies Sucrose.Inference /** * * @param url URL to redirect to * @param HTTP status code to send, */ export const redirect = ( url: string, status: 301 | 302 | 303 | 307 | 308 = 302 ) => Response.redirect(url, status) export type redirect = typeof redirect export const ELYSIA_FORM_DATA = Symbol('ElysiaFormData') export type ELYSIA_FORM_DATA = typeof ELYSIA_FORM_DATA type IsTuple = T extends readonly any[] ? number extends T['length'] ? false : true : false export type ElysiaFormData> = FormData & { [ELYSIA_FORM_DATA]: Replace extends infer A ? { [key in keyof A]: IsTuple extends true ? // @ts-ignore Trust me bro A[key][number] extends Blob | ElysiaFile ? File[] : A[key] : A[key] } : T } export const ELYSIA_REQUEST_ID = Symbol('ElysiaRequestId') export type ELYSIA_REQUEST_ID = typeof ELYSIA_REQUEST_ID export const form = >( items: T ): ElysiaFormData => { const formData = new FormData() // @ts-ignore formData[ELYSIA_FORM_DATA] = {} if (items) for (const [key, value] of Object.entries(items)) { if (Array.isArray(value)) { // @ts-expect-error formData[ELYSIA_FORM_DATA][key] = [] for (const v of value) { if (value instanceof File) formData.append(key, value, value.name) else if (value instanceof ElysiaFile) // @ts-expect-error formData.append(key, value.value, value.value?.name) else formData.append(key, value as any) // @ts-expect-error formData[ELYSIA_FORM_DATA][key].push(value) } continue } if (value instanceof File) formData.append(key, value, value.name) else if (value instanceof ElysiaFile) // @ts-expect-error formData.append(key, value.value, value.value?.name) else formData.append(key, value as any) // @ts-expect-error formData[ELYSIA_FORM_DATA][key] = value } return formData as any } /** * Generates a random ID for schema identification. * * Uses crypto.randomUUID() when available, with a Math.random() fallback * for environments where crypto.randomUUID() throws an error * (e.g., Cloudflare Workers global scope). * * @see https://developers.cloudflare.com/workers/runtime-apis/handlers/ */ export const randomId = typeof crypto === 'undefined' || isCloudflareWorker() ? (): string => { let result = '' const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789' for (let i = 0; i < 16; i++) result += characters.charAt( // 62 is characters.length Math.floor(Math.random() * 62) ) return result } : (): string => { const uuid = crypto.randomUUID() return uuid.slice(0, 8) + uuid.slice(24, 32) } // ! Deduplicate current instance export const deduplicateChecksum = ( array: HookContainer[] ): HookContainer[] => { if (!array.length) return [] const hashes: number[] = [] for (let i = 0; i < array.length; i++) { const item = array[i] if (item.checksum) { if (hashes.includes(item.checksum)) { array.splice(i, 1) i-- } hashes.push(item.checksum) } } return array } /** * Since it's a plugin, which means that ephemeral is demoted to volatile. * Which means there's no volatile and all previous ephemeral become volatile * We can just promote back without worry */ export const promoteEvent = ( events?: (HookContainer | Function)[], as: 'scoped' | 'global' = 'scoped' ): void => { if (!events) return if (as === 'scoped') { for (const event of events) if ('scope' in event && event.scope === 'local') event.scope = 'scoped' return } for (const event of events) if ('scope' in event) event.scope = 'global' } // type PropertyKeys = { // [K in keyof T]: T[K] extends (...args: any[]) => any ? never : K // }[keyof T] // type PropertiesOnly = Pick> // export const classToObject = ( // instance: T, // processed: WeakMap = new WeakMap() // ): T extends object ? PropertiesOnly : T => { // if (typeof instance !== 'object' || instance === null) // return instance as any // if (Array.isArray(instance)) // return instance.map((x) => classToObject(x, processed)) as any // if (processed.has(instance)) return processed.get(instance) as any // const result: Partial = {} // for (const key of Object.keys(instance) as Array) { // const value = instance[key] // if (typeof value === 'object' && value !== null) // result[key] = classToObject(value, processed) as T[keyof T] // else result[key] = value // } // const prototype = Object.getPrototypeOf(instance) // if (!prototype) return result as any // const properties = Object.getOwnPropertyNames(prototype) // for (const property of properties) { // const descriptor = Object.getOwnPropertyDescriptor( // Object.getPrototypeOf(instance), // property // ) // if (descriptor && typeof descriptor.get === 'function') { // // ? Very important to prevent prototype pollution // if (property === '__proto__') continue // ;(result as any)[property as keyof typeof instance] = classToObject( // instance[property as keyof typeof instance] // ) // } // } // return result as any // } export const getLoosePath = (path: string) => { if (path.charCodeAt(path.length - 1) === 47) return path.slice(0, path.length - 1) return path + '/' } export const isNotEmpty = (obj?: Object) => { if (!obj) return false for (const _ in obj) return true return false } export const encodePath = (path: string, { dynamic = false } = {}) => { let encoded = encodeURIComponent(path).replace(/%2F/g, '/') if (dynamic) encoded = encoded.replace(/%3A/g, ':').replace(/%3F/g, '?') return encoded } export const supportPerMethodInlineHandler = (() => { if (typeof Bun === 'undefined') return true if (Bun.semver?.satisfies?.(Bun.version, '>=1.2.14')) return true return false })() type FormatSSEPayload = T extends string ? { readonly data: T } : Prettify> /** * Return a Server Sent Events (SSE) payload * * @example * ```ts * import { sse } from 'elysia' * * new Elysia() * .get('/sse', function*() { * yield sse('Hello, world!') * yield sse({ * event: 'message', * data: { message: 'This is a JSON object' } * }) * } */ export const sse = < const T extends | string | SSEPayload | Generator | AsyncGenerator | ReadableStream >( _payload: T ): T extends string ? { readonly data: T } : T extends SSEPayload ? T : T extends ReadableStream ? ReadableStream> : T extends Generator ? Generator, B, C> : T extends AsyncGenerator ? AsyncGenerator, B, C> : T => { if (_payload instanceof ReadableStream) { // @ts-expect-error _payload.sse = true return _payload as any } const payload: SSEPayload = typeof _payload === 'string' ? { data: _payload } : (_payload as SSEPayload) // if (payload.id === undefined) payload.id = randomId() // @ts-ignore payload.sse = true // @ts-ignore payload.toSSE = () => { let payloadString = '' if (payload.id !== undefined && payload.id !== null) payloadString += `id: ${payload.id}\n` if (payload.event) payloadString += `event: ${payload.event}\n` if (payload.retry !== undefined) payloadString += `retry: ${payload.retry}\n` if (payload.data === null) payloadString += 'data: null\n' else if (typeof payload.data === 'string') payloadString += `data: ${payload.data}\n` else if (typeof payload.data === 'object') payloadString += `data: ${JSON.stringify(payload.data)}\n` if (payloadString) payloadString += '\n' return payloadString } return payload as any } export async function getResponseLength(response: Response) { if (response.bodyUsed || !response.body) return 0 let length = 0 const reader = response.body.getReader() while (true) { const { done, value } = await reader.read() if (done) break length += value.byteLength } return length } export const emptySchema = { headers: true, cookie: true, query: true, params: true, body: true, response: true } as const satisfies RouteSchema export function deepClone(source: T, weak = new WeakMap()): T { if ( source === null || typeof source !== 'object' || typeof source === 'function' ) return source // Circular‑reference guard if (weak.has(source as object)) return weak.get(source as object) if (Array.isArray(source)) { const copy: any[] = new Array(source.length) weak.set(source, copy) for (let i = 0; i < source.length; i++) copy[i] = deepClone(source[i], weak) return copy as any } if (typeof source === 'object') { const keys = Object.keys(source).concat( Object.getOwnPropertySymbols(source) as any[] ) const cloned: Partial = {} weak.set(source as object, cloned) for (const key of keys) cloned[key as keyof T] = deepClone((source as any)[key], weak) return cloned as T } return source } ================================================ FILE: src/ws/bun.ts ================================================ // ? Copy from bun.d.ts for universal runtime support type TypedArray = | Uint8Array | Uint8ClampedArray | Uint16Array | Uint32Array | Int8Array | Int16Array | Int32Array | BigUint64Array | BigInt64Array | Float32Array | Float64Array export type BufferSource = TypedArray | DataView | ArrayBufferLike /** * A status that represents the outcome of a sent message. * * - if **0**, the message was **dropped**. * - if **-1**, there is **backpressure** of messages. * - if **>0**, it represents the **number of bytes sent**. * * @example * ```js * const status = ws.send("Hello!"); * if (status === 0) { * console.log("Message was dropped"); * } else if (status === -1) { * console.log("Backpressure was applied"); * } else { * console.log(`Success! Sent ${status} bytes`); * } * ``` */ export type ServerWebSocketSendStatus = number /** * A state that represents if a WebSocket is connected. * * - `WebSocket.CONNECTING` is `0`, the connection is pending. * - `WebSocket.OPEN` is `1`, the connection is established and `send()` is possible. * - `WebSocket.CLOSING` is `2`, the connection is closing. * - `WebSocket.CLOSED` is `3`, the connection is closed or couldn't be opened. * * @link https://developer.mozilla.org/en-US/docs/Web/API/WebSocket/readyState */ export type WebSocketReadyState = 0 | 1 | 2 | 3 /** * A fast WebSocket designed for servers. * * Features: * - **Message compression** - Messages can be compressed * - **Backpressure** - If the client is not ready to receive data, the server will tell you. * - **Dropped messages** - If the client cannot receive data, the server will tell you. * - **Topics** - Messages can be {@link ServerWebSocket.publish}ed to a specific topic and the client can {@link ServerWebSocket.subscribe} to topics * * This is slightly different than the browser {@link WebSocket} which Bun supports for clients. * * Powered by [uWebSockets](https://github.com/uNetworking/uWebSockets). * * @example * import { serve } from "bun"; * * serve({ * websocket: { * open(ws) { * console.log("Connected", ws.remoteAddress); * }, * message(ws, data) { * console.log("Received", data); * ws.send(data); * }, * close(ws, code, reason) { * console.log("Disconnected", code, reason); * }, * } * }); */ export interface ServerWebSocket { /** * Sends a message to the client. * * @param data The data to send. * @param compress Should the data be compressed? If the client does not support compression, this is ignored. * @example * ws.send("Hello!"); * ws.send("Compress this.", true); * ws.send(new Uint8Array([1, 2, 3, 4])); */ send( data: string | BufferSource, compress?: boolean ): ServerWebSocketSendStatus /** * Sends a text message to the client. * * @param data The data to send. * @param compress Should the data be compressed? If the client does not support compression, this is ignored. * @example * ws.send("Hello!"); * ws.send("Compress this.", true); */ sendText(data: string, compress?: boolean): ServerWebSocketSendStatus /** * Sends a binary message to the client. * * @param data The data to send. * @param compress Should the data be compressed? If the client does not support compression, this is ignored. * @example * ws.send(new TextEncoder().encode("Hello!")); * ws.send(new Uint8Array([1, 2, 3, 4]), true); */ sendBinary( data: BufferSource, compress?: boolean ): ServerWebSocketSendStatus /** * Closes the connection. * * Here is a list of close codes: * - `1000` means "normal closure" **(default)** * - `1009` means a message was too big and was rejected * - `1011` means the server encountered an error * - `1012` means the server is restarting * - `1013` means the server is too busy or the client is rate-limited * - `4000` through `4999` are reserved for applications (you can use it!) * * To close the connection abruptly, use `terminate()`. * * @param code The close code to send * @param reason The close reason to send */ close(code?: number, reason?: string): void /** * Abruptly close the connection. * * To gracefully close the connection, use `close()`. */ terminate(): void /** * Sends a ping. * * @param data The data to send */ ping(data?: string | BufferSource): ServerWebSocketSendStatus /** * Sends a pong. * * @param data The data to send */ pong(data?: string | BufferSource): ServerWebSocketSendStatus /** * Sends a message to subscribers of the topic. * * @param topic The topic name. * @param data The data to send. * @param compress Should the data be compressed? If the client does not support compression, this is ignored. * @example * ws.publish("chat", "Hello!"); * ws.publish("chat", "Compress this.", true); * ws.publish("chat", new Uint8Array([1, 2, 3, 4])); */ publish( topic: string, data: string | BufferSource, compress?: boolean ): ServerWebSocketSendStatus /** * Sends a text message to subscribers of the topic. * * @param topic The topic name. * @param data The data to send. * @param compress Should the data be compressed? If the client does not support compression, this is ignored. * @example * ws.publish("chat", "Hello!"); * ws.publish("chat", "Compress this.", true); */ publishText( topic: string, data: string, compress?: boolean ): ServerWebSocketSendStatus /** * Sends a binary message to subscribers of the topic. * * @param topic The topic name. * @param data The data to send. * @param compress Should the data be compressed? If the client does not support compression, this is ignored. * @example * ws.publish("chat", new TextEncoder().encode("Hello!")); * ws.publish("chat", new Uint8Array([1, 2, 3, 4]), true); */ publishBinary( topic: string, data: BufferSource, compress?: boolean ): ServerWebSocketSendStatus /** * Subscribes a client to the topic. * * @param topic The topic name. * @example * ws.subscribe("chat"); */ subscribe(topic: string): void /** * Unsubscribes a client to the topic. * * @param topic The topic name. * @example * ws.unsubscribe("chat"); */ unsubscribe(topic: string): void /** * Is the client subscribed to a topic? * * @param topic The topic name. * @example * ws.subscribe("chat"); * console.log(ws.isSubscribed("chat")); // true */ isSubscribed(topic: string): boolean /** * Returns an array of all topics the client is currently subscribed to. * * @example * ws.subscribe("chat"); * ws.subscribe("notifications"); * console.log(ws.subscriptions); // ["chat", "notifications"] */ readonly subscriptions: string[] /** * Batches `send()` and `publish()` operations, which makes it faster to send data. * * The `message`, `open`, and `drain` callbacks are automatically corked, so * you only need to call this if you are sending messages outside of those * callbacks or in async functions. * * @param callback The callback to run. * @example * ws.cork((ctx) => { * ctx.send("These messages"); * ctx.sendText("are sent"); * ctx.sendBinary(new TextEncoder().encode("together!")); * }); */ cork(callback: (ws: ServerWebSocket) => T): T /** * The IP address of the client. * * @example * console.log(socket.remoteAddress); // "127.0.0.1" */ readonly remoteAddress: string /** * The ready state of the client. * * - if `0`, the client is connecting. * - if `1`, the client is connected. * - if `2`, the client is closing. * - if `3`, the client is closed. * * @example * console.log(socket.readyState); // 1 */ readonly readyState: WebSocketReadyState /** * Sets how binary data is returned in events. * * - if `nodebuffer`, binary data is returned as `Buffer` objects. **(default)** * - if `arraybuffer`, binary data is returned as `ArrayBuffer` objects. * - if `uint8array`, binary data is returned as `Uint8Array` objects. * * @example * let ws: WebSocket; * ws.binaryType = "uint8array"; * ws.addEventListener("message", ({ data }) => { * console.log(data instanceof Uint8Array); // true * }); */ binaryType?: 'nodebuffer' | 'arraybuffer' | 'uint8array' /** * Custom data that you can assign to a client, can be read and written at any time. * * @example * import { serve } from "bun"; * * serve({ * fetch(request, server) { * const data = { * accessToken: request.headers.get("Authorization"), * }; * if (server.upgrade(request, { data })) { * return; * } * return new Response(); * }, * websocket: { * open(ws) { * console.log(ws.data.accessToken); * } * } * }); */ data: T } /** * Compression options for WebSocket messages. */ export type WebSocketCompressor = | 'disable' | 'shared' | 'dedicated' | '3KB' | '4KB' | '8KB' | '16KB' | '32KB' | '64KB' | '128KB' | '256KB' /** * Create a server-side {@link ServerWebSocket} handler for use with {@link Bun.serve} * * @example * ```ts * import { websocket, serve } from "bun"; * * serve<{name: string}>({ * port: 3000, * websocket: { * open: (ws) => { * console.log("Client connected"); * }, * message: (ws, message) => { * console.log(`${ws.data.name}: ${message}`); * }, * close: (ws) => { * console.log("Client disconnected"); * }, * }, * * fetch(req, server) { * const url = new URL(req.url); * if (url.pathname === "/chat") { * const upgraded = server.upgrade(req, { * data: { * name: new URL(req.url).searchParams.get("name"), * }, * }); * if (!upgraded) { * return new Response("Upgrade failed", { status: 400 }); * } * return; * } * return new Response("Hello World"); * }, * }); * ``` */ export interface WebSocketHandler { /** * Called when the server receives an incoming message. * * If the message is not a `string`, its type is based on the value of `binaryType`. * - if `nodebuffer`, then the message is a `Buffer`. * - if `arraybuffer`, then the message is an `ArrayBuffer`. * - if `uint8array`, then the message is a `Uint8Array`. * * @param ws The websocket that sent the message * @param message The message received */ message( ws: ServerWebSocket, message: string | Buffer ): void | Promise /** * Called when a connection is opened. * * @param ws The websocket that was opened */ open?(ws: ServerWebSocket): void | Promise /** * Called when a connection was previously under backpressure, * meaning it had too many queued messages, but is now ready to receive more data. * * @param ws The websocket that is ready for more data */ drain?(ws: ServerWebSocket): void | Promise /** * Called when a connection is closed. * * @param ws The websocket that was closed * @param code The close code * @param message The close message */ close?( ws: ServerWebSocket, code: number, reason: string ): void | Promise /** * Called when a ping is sent. * * @param ws The websocket that received the ping * @param data The data sent with the ping */ ping?(ws: ServerWebSocket, data: Buffer): void | Promise /** * Called when a pong is received. * * @param ws The websocket that received the ping * @param data The data sent with the ping */ pong?(ws: ServerWebSocket, data: Buffer): void | Promise /** * Sets the maximum size of messages in bytes. * * Default is 16 MB, or `1024 * 1024 * 16` in bytes. */ maxPayloadLength?: number /** * Sets the maximum number of bytes that can be buffered on a single connection. * * Default is 16 MB, or `1024 * 1024 * 16` in bytes. */ backpressureLimit?: number /** * Sets if the connection should be closed if `backpressureLimit` is reached. * * Default is `false`. */ closeOnBackpressureLimit?: boolean /** * Sets the the number of seconds to wait before timing out a connection * due to no messages or pings. * * Default is 2 minutes, or `120` in seconds. */ idleTimeout?: number /** * Should `ws.publish()` also send a message to `ws` (itself), if it is subscribed? * * Default is `false`. */ publishToSelf?: boolean /** * Should the server automatically send and respond to pings to clients? * * Default is `true`. */ sendPings?: boolean /** * Sets the compression level for messages, for clients that supports it. By default, compression is disabled. * * Default is `false`. */ perMessageDeflate?: | boolean | { /** * Sets the compression level. */ compress?: WebSocketCompressor | boolean /** * Sets the decompression level. */ decompress?: WebSocketCompressor | boolean } } ================================================ FILE: src/ws/index.ts ================================================ import { isNumericString } from '../utils' import type { ServerWebSocket, ServerWebSocketSendStatus, BufferSource, WebSocketHandler } from './bun' import type { TSchema } from '@sinclair/typebox' import type { TypeCheck } from '../type-system' import type { ElysiaTypeCheck } from '../schema' import type { FlattenResponse, WSParseHandler } from './types' import type { MaybeArray, Prettify, RouteSchema } from '../types' import { ValidationError } from '../error' export const websocket: WebSocketHandler = { open(ws) { ws.data.open?.(ws) }, message(ws, message) { ws.data.message?.(ws, message) }, drain(ws) { ws.data.drain?.(ws) }, close(ws, code, reason) { ws.data.close?.(ws, code, reason) }, ping(ws) { ws.data.ping?.(ws) }, pong(ws) { ws.data.pong?.(ws) } } type ElysiaServerWebSocket = Omit< ServerWebSocket, 'send' | 'ping' | 'pong' | 'publish' > export class ElysiaWS implements ElysiaServerWebSocket { constructor( public raw: ServerWebSocket<{ id?: string validator?: TypeCheck }>, public data: Prettify< Omit >, public body: Route['body'] = undefined ) { this.validator = raw.data?.validator this.sendText = raw.sendText.bind(raw) this.sendBinary = raw.sendBinary.bind(raw) this.close = raw.close.bind(raw) this.terminate = raw.terminate.bind(raw) this.publishText = raw.publishText.bind(raw) this.publishBinary = raw.publishBinary.bind(raw) this.subscribe = raw.subscribe.bind(raw) this.unsubscribe = raw.unsubscribe.bind(raw) this.isSubscribed = raw.isSubscribed.bind(raw) this.cork = raw.cork.bind(raw) this.remoteAddress = raw.remoteAddress this.binaryType = raw.binaryType this.data = raw.data as any this.subscriptions = raw.subscriptions this.send = this.send.bind(this) this.ping = this.ping.bind(this) this.pong = this.pong.bind(this) this.publish = this.publish.bind(this) } /** * Sends a message to the client. * * @param data The data to send. * @param compress Should the data be compressed? If the client does not support compression, this is ignored. * @example * ws.send("Hello!"); * ws.send("Compress this.", true); * ws.send(new Uint8Array([1, 2, 3, 4])); */ send( data: FlattenResponse | BufferSource, compress?: boolean ): ServerWebSocketSendStatus { if (Buffer.isBuffer(data)) return this.raw.send(data as unknown as BufferSource, compress) if (this.validator?.Check(data) === false) return this.raw.send( new ValidationError('message', this.validator, data).message ) if (typeof data === 'object') data = JSON.stringify(data) as any return this.raw.send(data as unknown as string, compress) } /** * Sends a ping. * * @param data The data to send */ ping( data?: FlattenResponse | BufferSource ): ServerWebSocketSendStatus { if (Buffer.isBuffer(data)) return this.raw.ping(data as unknown as BufferSource) if (this.validator?.Check(data) === false) return this.raw.send( new ValidationError('message', this.validator, data).message ) if (typeof data === 'object') data = JSON.stringify(data) as any return this.raw.ping(data as string) } /** * Sends a pong. * * @param data The data to send */ pong( data?: FlattenResponse | BufferSource ): ServerWebSocketSendStatus { if (Buffer.isBuffer(data)) return this.raw.pong(data as unknown as BufferSource) if (this.validator?.Check(data) === false) return this.raw.send( new ValidationError('message', this.validator, data).message ) if (typeof data === 'object') data = JSON.stringify(data) as any return this.raw.pong(data as string) } /** * Sends a message to subscribers of the topic. * * @param topic The topic name. * @param data The data to send. * @param compress Should the data be compressed? If the client does not support compression, this is ignored. * @example * ws.publish("chat", "Hello!"); * ws.publish("chat", "Compress this.", true); * ws.publish("chat", new Uint8Array([1, 2, 3, 4])); */ publish( topic: string, data: FlattenResponse | BufferSource, compress?: boolean ): ServerWebSocketSendStatus { if (Buffer.isBuffer(data)) return this.raw.publish( topic, data as unknown as BufferSource, compress ) if (this.validator?.Check(data) === false) return this.raw.send( new ValidationError('message', this.validator, data).message ) if (typeof data === 'object') data = JSON.stringify(data) as any return this.raw.publish(topic, data as unknown as string, compress) } sendText: ServerWebSocket['sendText'] sendBinary: ServerWebSocket['sendBinary'] close: ServerWebSocket['close'] terminate: ServerWebSocket['terminate'] publishText: ServerWebSocket['publishText'] publishBinary: ServerWebSocket['publishBinary'] subscribe: ServerWebSocket['subscribe'] unsubscribe: ServerWebSocket['unsubscribe'] isSubscribed: ServerWebSocket['isSubscribed'] cork: ServerWebSocket['cork'] remoteAddress: ServerWebSocket['remoteAddress'] binaryType: ServerWebSocket['binaryType'] subscriptions: ServerWebSocket['subscriptions'] get readyState() { return this.raw.readyState } validator?: TypeCheck; ['~types']?: { validator: Prettify } get id(): string { // @ts-ignore return this.data.id } } export const createWSMessageParser = ( parse: MaybeArray> ) => { const parsers = typeof parse === 'function' ? [parse] : parse return async function parseMessage(ws: ServerWebSocket, message: any) { if (typeof message === 'string') { const start = message?.charCodeAt(0) if (start === 34 || start === 47 || start === 91 || start === 123) try { message = JSON.parse(message) } catch { // Not empty } else if (isNumericString(message)) message = +message else if (message === 'true') message = true else if (message === 'false') message = false else if (message === 'null') message = null } if (parsers) for (let i = 0; i < parsers.length; i++) { let temp = parsers[i](ws as any, message) if (temp instanceof Promise) temp = await temp if (temp !== undefined) return temp } return message } } export const createHandleWSResponse = ( responseValidator: TypeCheck | ElysiaTypeCheck | undefined ) => { const handleWSResponse = ( ws: ServerWebSocket, data: unknown ): unknown => { if (data instanceof Promise) return data.then((data) => handleWSResponse(ws, data)) if (Buffer.isBuffer(data)) return ws.send(data.toString()) if (data === undefined) return const validateResponse = responseValidator ? // @ts-ignore responseValidator.provider === 'standard' ? (data: unknown) => // @ts-ignore responseValidator.schema['~standard'].validate(data) .issues : (data: unknown) => responseValidator.Check(data) === false : undefined const send = (datum: unknown) => { if (validateResponse && validateResponse(datum) === false) return ws.send( new ValidationError('message', responseValidator!, datum) .message ) if (typeof datum === 'object') return ws.send(JSON.stringify(datum)) ws.send(datum as any) } if (typeof (data as Generator)?.next !== 'function') return void send(data) const init = (data as Generator | AsyncGenerator).next() if (init instanceof Promise) return (async () => { const first = await init if (validateResponse && validateResponse(first)) return ws.send( new ValidationError( 'message', responseValidator!, first ).message ) send(first.value as any) if (!first.done) for await (const datum of data as Generator) send(datum) })() send(init.value) if (!init.done) for (const datum of data as Generator) send(datum) } return handleWSResponse } export type { WSLocalHook } from './types' ================================================ FILE: src/ws/types.ts ================================================ import { TSchema } from '@sinclair/typebox' import type { ElysiaWS } from './index' import { WebSocketHandler } from './bun' import type { Context } from '../context' import { AfterResponseHandler, BaseMacro, DocumentDecoration, ErrorHandler, InputSchema, MacroToContext, MapResponse, MaybeArray, MaybePromise, MetadataBase, OptionalHandler, Prettify, RouteSchema, SingletonBase, TransformHandler, UnwrapSchema } from '../types' type TypedWebSocketMethod = | 'open' | 'message' | 'drain' | 'close' | 'ping' | 'pong' export type FlattenResponse = {} extends Response ? unknown : Response[keyof Response] interface TypedWebSocketHandler< in out Context, in out Route extends RouteSchema = {} > extends Omit, TypedWebSocketMethod> { open?( ws: Prettify & { body: never }>> ): MaybePromise | void> message?( ws: Prettify>, message: Route['body'] ): MaybePromise< | FlattenResponse | void | Generator< FlattenResponse, void | FlattenResponse > | AsyncGenerator< FlattenResponse, void | FlattenResponse > > drain?( ws: Prettify & { body: never }>> ): MaybePromise< | FlattenResponse | void | Generator< FlattenResponse, void | FlattenResponse > | AsyncGenerator< FlattenResponse, void | FlattenResponse > > close?( ws: Prettify & { body: never }>>, code: number, reason: string ): MaybePromise< | FlattenResponse | void | Generator< FlattenResponse, void | FlattenResponse > | AsyncGenerator< FlattenResponse, void | FlattenResponse > > ping?( ws: Prettify>, message: Route['body'] ): MaybePromise< | FlattenResponse | void | Generator< FlattenResponse, void | FlattenResponse > | AsyncGenerator< FlattenResponse, void | FlattenResponse > > pong?( ws: Prettify>, message: Route['body'] ): MaybePromise< | FlattenResponse | void | Generator< FlattenResponse, void | FlattenResponse > | AsyncGenerator< FlattenResponse, void | FlattenResponse > > } export type WSParseHandler = ( ws: Prettify & { body: unknown }>>, message: unknown ) => MaybePromise export type AnyWSLocalHook = WSLocalHook export type WSLocalHook< Input extends BaseMacro, Schema extends RouteSchema, Singleton extends SingletonBase > = Prettify & { detail?: DocumentDecoration /** * Headers to register to websocket before `upgrade` */ upgrade?: Record | ((context: Context) => unknown) parse?: MaybeArray> /** * Transform context's value */ transform?: MaybeArray> /** * Execute before main handler */ beforeHandle?: MaybeArray> /** * Execute after main handler */ afterHandle?: MaybeArray> /** * Execute after main handler */ mapResponse?: MaybeArray> /** * Execute after response is sent */ afterResponse?: MaybeArray> /** * Catch error */ error?: MaybeArray> tags?: DocumentDecoration['tags'] } & TypedWebSocketHandler< Omit, 'body'> & { body: never }, Schema > ================================================ FILE: test/adapter/bun/index.test.ts ================================================ import { describe, it, expect } from 'bun:test' import { Elysia, t } from '../../../src' describe('Bun adapter', () => { it('handle query guard', async () => { const app = new Elysia() .guard({ query: t.Object({ a: t.String() }) }) .get('/works-with', ({ query }) => 'Works' + query.a) .get('/works-without', () => 'Works without') .listen(0) const query = await fetch( `http://localhost:${app.server!.port}/works-with?a=with` ).then((x) => x.text()) expect(query).toEqual('Workswith') const query2 = await fetch( `http://localhost:${app.server!.port}/works-without?a=1` ).then((x) => x.text()) expect(query2).toEqual('Works without') }) it('handle standalone query guard', async () => { const app = new Elysia() .guard({ query: t.Object({ a: t.String() }), schema: 'standalone' }) .get('/works-with', ({ query }) => 'Works' + query.a) .get('/works-without', () => 'Works without') .listen(0) const query = await fetch( `http://localhost:${app.server!.port}/works-with?a=with` ).then((x) => x.text()) expect(query).toEqual('Workswith') const query2 = await fetch( `http://localhost:${app.server!.port}/works-without?a=1` ).then((x) => x.text()) expect(query2).toEqual('Works without') }) it('handle static response with onRequest and onError', async () => { let caughtError: Error let onErrorCalled = false let onRequestCalled = false const app = new Elysia() .onError(({ error }) => { caughtError = error as Error return 'handled' }) .onRequest(({ set }) => { set.headers['x-header'] = 'test' set.status = 400 throw new Error('A') }) .get('/', 'yay') .listen(0) const response = await fetch(`http://localhost:${app.server!.port}`) const text = await response.text() expect(text).toBe('handled') expect(response.status).toBe(400) expect(response.headers.get('x-header')).toBe('test') expect(caughtError!.message).toBe('A') }) it('handle non-ASCII path', async () => { const app = new Elysia().get('/สวัสดี', 'สบายดีไหม').listen(0) const response = await fetch( `http://localhost:${app.server!.port}/สวัสดี` ) const text = await response.text() expect(text).toBe('สบายดีไหม') }) }) ================================================ FILE: test/adapter/web-standard/cookie-to-header.test.ts ================================================ import { describe, expect, it } from 'bun:test' import { serializeCookie } from '../../../src/cookies' describe('Web Standard - Cookie to Header', () => { it('return undefined on empty object', () => { const cookies = {} const result = serializeCookie(cookies) expect(result).toBeUndefined() }) it('correctly serialize a single value cookie', () => { const cookies = { cookie1: { value: 'value1' } } const result = serializeCookie(cookies) expect(result).toEqual('cookie1=value1') }) it('correctly serialize a multi-value cookie', () => { const cookies = { cookie1: { value: ['value1', 'value2'] } } // @ts-ignore const result = serializeCookie(cookies) expect(result).toEqual('cookie1=%5B%22value1%22%2C%22value2%22%5D') }) it('return undefined when the input is undefined', () => { const cookies = undefined // @ts-ignore const result = serializeCookie(cookies) expect(result).toBeUndefined() }) it('return undefined when the input is null', () => { const cookies = null // @ts-ignore const result = serializeCookie(cookies) expect(result).toBeUndefined() }) it('return undefined when the input is not an object', () => { const cookies = 'invalid' // @ts-ignore const result = serializeCookie(cookies) expect(result).toBeUndefined() }) it('return undefined when the input is an empty object', () => { const cookies = {} const result = serializeCookie(cookies) expect(result).toBeUndefined() }) it('return undefined when the input is an empty object', () => { const cookies = {} const result = serializeCookie(cookies) expect(result).toBeUndefined() }) it('return undefined when the input is an object with null values', () => { const cookies = { cookie1: null, cookie2: null } // @ts-ignore const result = serializeCookie(cookies) expect(result).toBeUndefined() }) it('return undefined when the input is an empty object', () => { const cookies = {} const result = serializeCookie(cookies) expect(result).toBeUndefined() }) it('return undefined when the input is an object with non-string or non-array values', () => { const cookies = { key1: 123, key2: true, key3: { prop: 'value' }, key4: [1, 2, 3] } // @ts-ignore const result = serializeCookie(cookies) expect(result).toBeUndefined() }) it('return undefined when the input is an empty object', () => { const cookies = {} const result = serializeCookie(cookies) expect(result).toBeUndefined() }) it('return undefined when the input is an empty object', () => { const cookies = {} const result = serializeCookie(cookies) expect(result).toBeUndefined() }) it('return undefined when the input is an empty object', () => { const cookies = {} const result = serializeCookie(cookies) expect(result).toBeUndefined() }) it('return undefined when the input is an object with non-string keys', () => { const cookies = { 1: 'value1', 2: 'value2' } // @ts-ignore const result = serializeCookie(cookies) expect(result).toBeUndefined() }) }) ================================================ FILE: test/adapter/web-standard/map-compact-response.test.ts ================================================ import { describe, it, expect } from 'bun:test' import { mapCompactResponse } from '../../../src/adapter/web-standard/handler' import { form } from '../../../src/utils' import { Passthrough } from './utils' class Student { constructor(public name: string) {} toString() { return JSON.stringify({ name: this.name }) } } class CustomResponse extends Response {} describe('Web Standard - Map Compact Response', () => { it('map string', async () => { const response = mapCompactResponse('Shiroko') expect(response).toBeInstanceOf(Response) expect(await response.text()).toBe('Shiroko') expect(response.status).toBe(200) }) it('map number', async () => { const response = mapCompactResponse(1) expect(response).toBeInstanceOf(Response) expect(await response.text()).toBe('1') expect(response.status).toBe(200) }) it('map boolean', async () => { const response = mapCompactResponse(true) expect(response).toBeInstanceOf(Response) expect(await response.text()).toBe('true') expect(response.status).toBe(200) }) it('map object', async () => { const body = { name: 'Shiroko' } const response = mapCompactResponse(body) expect(response).toBeInstanceOf(Response) expect(await response.json()).toEqual(body) expect(response.status).toBe(200) }) it('map function', async () => { const response = mapCompactResponse(() => 1) expect(response).toBeInstanceOf(Response) expect(await response.text()).toBe('1') expect(response.status).toBe(200) }) it('map undefined', async () => { const response = mapCompactResponse(undefined) expect(response).toBeInstanceOf(Response) expect(await response.text()).toEqual('') expect(response.status).toBe(200) }) it('map null', async () => { const response = mapCompactResponse(null) expect(response).toBeInstanceOf(Response) expect(await response.text()).toEqual('') expect(response.status).toBe(200) }) it('map Blob', async () => { const file = Bun.file('./test/images/aris-yuzu.jpg') const response = mapCompactResponse(file) expect(response).toBeInstanceOf(Response) expect(await response.arrayBuffer()).toEqual(await file.arrayBuffer()) expect(response.status).toBe(200) }) it('map File', async () => { const file = new File(['Hello'], 'hello.txt', { type: 'text/plain' }) const response = mapCompactResponse(file) expect(response).toBeInstanceOf(Response) expect(await response.text()).toEqual('Hello') expect(response.status).toBe(200) }) it('map Promise', async () => { const body = { name: 'Shiroko' } const response = await mapCompactResponse( new Promise((resolve) => resolve(body)) ) expect(response).toBeInstanceOf(Response) expect(await response.json()).toEqual(body) expect(response.status).toBe(200) }) it('map Error', async () => { const response = mapCompactResponse(new Error('Hello')) expect(response).toBeInstanceOf(Response) expect(await response.json()).toEqual({ name: 'Error', message: 'Hello' }) expect(response.status).toBe(500) }) it('map Response', async () => { const response = mapCompactResponse(new Response('Shiroko')) expect(response).toBeInstanceOf(Response) expect(await response.text()).toEqual('Shiroko') expect(response.status).toBe(200) }) it('map custom Response', async () => { const response = mapCompactResponse(new CustomResponse('Shiroko')) expect(response).toBeInstanceOf(Response) expect(await response.text()).toEqual('Shiroko') expect(response.status).toBe(200) }) it('map custom class', async () => { const response = mapCompactResponse(new Student('Himari')) expect(response).toBeInstanceOf(Response) expect(await response.json()).toEqual({ name: 'Himari' }) expect(response.status).toBe(200) }) it('map Response and merge Headers', async () => { const response = await mapCompactResponse( new Response('Shiroko', { headers: { Name: 'Himari' } }) ) // @ts-ignore const headers = response.headers.toJSON() expect(response).toBeInstanceOf(Response) expect(await response.text()).toEqual('Shiroko') // @ts-ignore expect(response.headers.toJSON()).toEqual({ ...headers, name: 'Himari' }) }) it('map toResponse', async () => { const response = mapCompactResponse(new Passthrough()) expect(response).toBeInstanceOf(Response) expect(await response.text()).toEqual('hi') expect(response.status).toBe(200) }) it('map video content-range', async () => { const kyuukararin = Bun.file('test/kyuukurarin.mp4') const response = mapCompactResponse(kyuukararin) expect(response).toBeInstanceOf(Response) expect(response.headers.get('accept-ranges')).toEqual('bytes') expect(response.headers.get('content-range')).toEqual( `bytes 0-${kyuukararin.size - 1}/${kyuukararin.size}` ) expect(response.status).toBe(200) }) it('map formdata', async () => { const response = mapCompactResponse( form({ a: Bun.file('test/kyuukurarin.mp4') }) )! expect(response.headers.get('content-type')).toStartWith( 'multipart/form-data' ) expect(response.status).toBe(200) expect(await response.formData()).toBeInstanceOf(FormData) }) it('map custom thenable', async () => { // Custom thenable object (e.g., like some ORMs return such as Drizzle) // Using a class to avoid being caught by the 'Object' case class CustomThenable { then(onFulfilled: (value: any) => any) { const data = { name: 'Shiroko', id: 42 } return Promise.resolve(data).then(onFulfilled) } } const customThenable = new CustomThenable() const responsePromise = mapCompactResponse(customThenable) expect(responsePromise).toBeInstanceOf(Promise) const response = await responsePromise expect(response).toBeInstanceOf(Response) const body = await response.text() const parsed = JSON.parse(body) expect(parsed).toEqual({ name: 'Shiroko', id: 42 }) expect(response.status).toBe(200) }) }) ================================================ FILE: test/adapter/web-standard/map-early-response.test.ts ================================================ import { describe, it, expect } from 'bun:test' import { mapEarlyResponse } from '../../../src/adapter/web-standard/handler' import { form, redirect } from '../../../src/utils' import { Passthrough } from './utils' const defaultContext = { headers: {}, status: 200, cookie: {} } const createContext = () => ({ headers: { 'x-powered-by': 'Elysia', 'coffee-scheme': 'Coffee' }, status: 418, cookie: {} }) class Student { constructor(public name: string) {} toString() { return JSON.stringify({ name: this.name }) } } class CustomResponse extends Response {} describe('Web Standard - Map Early Response', () => { it('map string', async () => { const response = mapEarlyResponse('Shiroko', defaultContext) expect(response).toBeInstanceOf(Response) expect(await response?.text()).toBe('Shiroko') expect(response?.status).toBe(200) }) it('map number', async () => { const response = mapEarlyResponse(1, defaultContext) expect(response).toBeInstanceOf(Response) expect(await response?.text()).toBe('1') expect(response?.status).toBe(200) }) it('map boolean', async () => { const response = mapEarlyResponse(true, defaultContext) expect(response).toBeInstanceOf(Response) expect(await response?.text()).toBe('true') expect(response?.status).toBe(200) }) it('map object', async () => { const body = { name: 'Shiroko' } const response = mapEarlyResponse(body, defaultContext) expect(response).toBeInstanceOf(Response) expect(await response?.json()).toEqual(body) expect(response?.status).toBe(200) }) it('map function', async () => { const response = mapEarlyResponse(() => 1, defaultContext) expect(response).toBeInstanceOf(Response) expect(await response?.text()).toBe('1') expect(response?.status).toBe(200) }) it('map Blob', async () => { const file = Bun.file('./test/images/aris-yuzu.jpg') const response = mapEarlyResponse(file, defaultContext) expect(response).toBeInstanceOf(Response) expect(await response?.arrayBuffer()).toEqual(await file.arrayBuffer()) expect(response?.status).toBe(200) }) it('map File', async () => { const file = new File(['Hello'], 'hello.txt', { type: 'text/plain' }) const response = mapEarlyResponse(file, defaultContext) expect(response).toBeInstanceOf(Response) expect(await response?.text()).toEqual('Hello') expect(response?.status).toBe(200) }) it('map Promise', async () => { const body = { name: 'Shiroko' } const response = await mapEarlyResponse( new Promise((resolve) => resolve(body)), defaultContext ) expect(response).toBeInstanceOf(Response) expect(await response?.json()).toEqual(body) expect(response?.status).toBe(200) }) it('map Response', async () => { const response = mapEarlyResponse( new Response('Shiroko'), defaultContext ) expect(response).toBeInstanceOf(Response) expect(await response?.text()).toEqual('Shiroko') expect(response?.status).toBe(200) }) it('map custom Response', async () => { const response = mapEarlyResponse( new CustomResponse('Shiroko'), defaultContext )! expect(response).toBeInstanceOf(Response) expect(await response.text()).toEqual('Shiroko') expect(response.status).toBe(200) }) it('map custom Response with custom headers', async () => { const response = mapEarlyResponse(new CustomResponse('Shiroko'), { ...defaultContext, headers: { 'content-type': 'text/html; charset=utf8' } })! expect(response).toBeInstanceOf(Response) expect(await response.text()).toEqual('Shiroko') expect(response.status).toBe(200) expect(response.headers.get('content-type')).toBe( 'text/html; charset=utf8' ) }) it('map custom class', async () => { const response = mapEarlyResponse(new Student('Himari'), defaultContext) expect(response).toBeInstanceOf(Response) expect(await response?.json()).toEqual({ name: 'Himari' }) expect(response?.status).toBe(200) }) it('map primitive with custom context', async () => { const context = createContext() const response = mapEarlyResponse('Shiroko', context) expect(response).toBeInstanceOf(Response) expect(await response?.text()).toBe('Shiroko') expect(response?.headers.toJSON()).toEqual(context.headers) expect(response?.status).toBe(418) }) it('map Function with custom context', async () => { const context = createContext() const response = await mapEarlyResponse(() => 1, context) expect(response).toBeInstanceOf(Response) expect(await response?.text()).toEqual('1') expect(response?.headers.toJSON()).toEqual({ ...context.headers }) expect(response?.status).toBe(418) }) it('map Promise with custom context', async () => { const context = createContext() const body = { name: 'Shiroko' } const response = await mapEarlyResponse( new Promise((resolve) => resolve(body)), context ) expect(response).toBeInstanceOf(Response) expect(await response?.json()).toEqual(body) expect(response?.headers.toJSON()).toEqual({ ...context.headers, 'content-type': 'application/json' }) expect(response?.status).toBe(418) }) it('map Error with custom context', async () => { const context = createContext() const response = mapEarlyResponse(new Error('Hello'), context) expect(response).toBeInstanceOf(Response) expect(await response?.json()).toEqual({ name: 'Error', message: 'Hello' }) expect(response?.headers.toJSON()).toEqual(context.headers) expect(response?.status).toBe(418) }) it('map Response with custom context', async () => { const context = createContext() const response = await mapEarlyResponse( new Response('Shiroko'), context ) const headers = response?.headers.toJSON() expect(response).toBeInstanceOf(Response) expect(await response?.text()).toEqual('Shiroko') expect(response?.headers.toJSON()).toEqual(headers as any) }) it('map Response and merge Headers', async () => { const context = createContext() const response = await mapEarlyResponse( new Response('Shiroko', { headers: { Name: 'Himari' } }), context ) const headers = response?.headers.toJSON() expect(response).toBeInstanceOf(Response) expect(await response?.text()).toEqual('Shiroko') // @ts-ignore expect(response?.headers.toJSON()).toEqual({ ...headers, name: 'Himari' }) }) it('map named status', async () => { const response = mapEarlyResponse('Shiroko', { status: "I'm a teapot", headers: {}, cookie: {} }) expect(response).toBeInstanceOf(Response) expect(await response?.text()).toBe('Shiroko') expect(response?.status).toBe(418) }) it('map redirect', async () => { const response = mapEarlyResponse(redirect('https://cunny.school'), { status: "I'm a teapot", cookie: {}, headers: { Name: 'Sorasaki Hina' }, redirect: 'https://cunny.school' }) expect(response).toBeInstanceOf(Response) // expect(await response?.text()).toEqual('Shiroko') expect(response?.headers.toJSON()).toEqual({ name: 'Sorasaki Hina', location: 'https://cunny.school' }) expect(response).toBeInstanceOf(Response) expect(response?.status).toBe(302) }) it('map undefined', async () => { const response = mapEarlyResponse(undefined, defaultContext) expect(response).toBeUndefined() }) it('map null', async () => { const response = mapEarlyResponse(null, defaultContext) expect(response).toBeUndefined() }) it('set cookie', async () => { const response = mapEarlyResponse('Hina', { status: 200, headers: { Name: 'Sorasaki Hina' }, cookie: { name: { value: 'hina' } } }) expect(response).toBeInstanceOf(Response) expect(await response?.text()).toEqual('Hina') expect(response?.headers.get('name')).toEqual('Sorasaki Hina') expect(response?.headers.getAll('set-cookie')).toEqual(['name=hina']) }) it('set multiple cookie', async () => { const response = mapEarlyResponse('Hina', { status: 200, headers: { Name: 'Sorasaki Hina' }, cookie: { name: { value: 'hina' }, affiliation: { value: 'gehenna' } } }) expect(response).toBeInstanceOf(Response) expect(await response?.text()).toEqual('Hina') expect(response?.headers.get('name')).toEqual('Sorasaki Hina') expect(response?.headers.getAll('set-cookie')).toEqual([ 'name=hina', 'affiliation=gehenna' ]) }) it('map toResponse', async () => { const response = mapEarlyResponse(new Passthrough(), defaultContext) expect(response).toBeInstanceOf(Response) expect(await response?.text()).toEqual('hi') expect(response?.status).toBe(200) }) it('map video content-range', async () => { const kyuukararin = Bun.file('test/kyuukurarin.mp4') const response = mapEarlyResponse(kyuukararin, defaultContext) expect(response).toBeInstanceOf(Response) expect(response?.headers.get('accept-ranges')).toEqual('bytes') expect(response?.headers.get('content-range')).toEqual( `bytes 0-${kyuukararin.size - 1}/${kyuukararin.size}` ) expect(response?.status).toBe(200) }) it('skip content-range on not modified', async () => { const kyuukararin = Bun.file('test/kyuukurarin.mp4') const response = mapEarlyResponse(kyuukararin, { ...defaultContext, status: 304 }) expect(response).toBeInstanceOf(Response) expect(response?.headers.get('accept-ranges')).toBeNull() expect(response?.headers.get('content-range')).toBeNull() expect(response?.status).toBe(304) }) it('map formdata', async () => { const response = mapEarlyResponse( form({ a: Bun.file('test/kyuukurarin.mp4') }), defaultContext )! expect(response.headers.get('content-type')).toStartWith( 'multipart/form-data' ) expect(response.status).toBe(200) expect(await response.formData()).toBeInstanceOf(FormData) }) }) ================================================ FILE: test/adapter/web-standard/map-response.test.ts ================================================ import { describe, it, expect } from 'bun:test' import { Elysia, form, redirect } from '../../../src' import { mapResponse } from '../../../src/adapter/web-standard/handler' import { Passthrough } from './utils' import { req } from '../../utils' const createContext = () => ({ cookie: {}, headers: {}, status: 200 }) class Student { constructor(public name: string) {} toString() { return JSON.stringify({ name: this.name }) } } class CustomResponse extends Response {} describe('Web Standard - Map Response', () => { it('map string', async () => { const response = mapResponse('Shiroko', createContext()) expect(response).toBeInstanceOf(Response) expect(await response.text()).toBe('Shiroko') expect(response.status).toBe(200) }) it('map number', async () => { const response = mapResponse(1, createContext()) expect(response).toBeInstanceOf(Response) expect(await response.text()).toBe('1') expect(response.status).toBe(200) }) it('map boolean', async () => { const response = mapResponse(true, createContext()) expect(response).toBeInstanceOf(Response) expect(await response.text()).toBe('true') expect(response.status).toBe(200) }) it('map object', async () => { const body = { name: 'Shiroko' } const response = mapResponse(body, createContext()) expect(response).toBeInstanceOf(Response) expect(await response.json()).toEqual(body) expect(response.status).toBe(200) }) it('map function', async () => { const response = mapResponse(() => 1, createContext()) expect(response).toBeInstanceOf(Response) expect(await response.text()).toBe('1') expect(response.status).toBe(200) }) it('map undefined', async () => { const response = mapResponse(undefined, createContext()) expect(response).toBeInstanceOf(Response) expect(await response.text()).toEqual('') expect(response.status).toBe(200) }) it('map null', async () => { const response = mapResponse(null, createContext()) expect(response).toBeInstanceOf(Response) expect(await response.text()).toEqual('') expect(response.status).toBe(200) }) it('map Blob', async () => { const file = Bun.file('./test/images/aris-yuzu.jpg') const response = mapResponse(file, createContext()) expect(response).toBeInstanceOf(Response) expect(await response.arrayBuffer()).toEqual(await file.arrayBuffer()) expect(response.status).toBe(200) }) it('map File', async () => { const file = new File(['Hello'], 'hello.txt', { type: 'text/plain' }) const response = mapResponse(file, createContext()) expect(response).toBeInstanceOf(Response) expect(await response.text()).toEqual('Hello') expect(response.status).toBe(200) }) it('map Promise', async () => { const body = { name: 'Shiroko' } const response = await mapResponse( new Promise((resolve) => resolve(body)), createContext() ) expect(response).toBeInstanceOf(Response) expect(await response.json()).toEqual(body) expect(response.status).toBe(200) }) it('map Error', async () => { const response = mapResponse(new Error('Hello'), createContext()) expect(response).toBeInstanceOf(Response) expect(await response.json()).toEqual({ name: 'Error', message: 'Hello' }) expect(response.status).toBe(500) }) it('map Response', async () => { const response = mapResponse(new Response('Shiroko'), createContext()) expect(response).toBeInstanceOf(Response) expect(await response.text()).toEqual('Shiroko') expect(response.status).toBe(200) }) it('map custom Response', async () => { const response = mapResponse( new CustomResponse('Shiroko'), createContext() ) expect(response).toBeInstanceOf(Response) expect(await response.text()).toEqual('Shiroko') expect(response.status).toBe(200) }) it('map custom Response with custom headers', async () => { const response = mapResponse(new CustomResponse('Shiroko'), { ...createContext(), headers: { 'content-type': 'text/html; charset=utf8' } }) expect(response).toBeInstanceOf(Response) expect(await response.text()).toEqual('Shiroko') expect(response.status).toBe(200) expect(response.headers.get('content-type')).toBe( 'text/html; charset=utf8' ) }) it('map custom class', async () => { const response = mapResponse(new Student('Himari'), createContext()) expect(response).toBeInstanceOf(Response) expect(await response.json()).toEqual({ name: 'Himari' }) expect(response.status).toBe(200) }) it('map primitive with custom context', async () => { const context = createContext() const response = mapResponse('Shiroko', context) expect(response).toBeInstanceOf(Response) expect(await response.text()).toBe('Shiroko') expect(response.headers.toJSON()).toEqual(context.headers) expect(response.status).toBe(200) }) it('map undefined with context', async () => { const context = createContext() const response = mapResponse(undefined, context) expect(response).toBeInstanceOf(Response) expect(await response.text()).toEqual('') expect(response.headers.toJSON()).toEqual(context.headers) expect(response.status).toBe(200) }) it('map null with custom context', async () => { const context = createContext() const response = mapResponse(null, context) expect(response).toBeInstanceOf(Response) expect(await response.text()).toEqual('') expect(response.headers.toJSON()).toEqual(context.headers) expect(response.status).toBe(200) }) it('map Function with custom context', async () => { const context = createContext() const response = await mapResponse(() => 1, context) expect(response).toBeInstanceOf(Response) expect(await response.text()).toEqual('1') expect(response.headers.toJSON()).toEqual({ ...context.headers }) expect(response.status).toBe(200) }) it('map Promise with custom context', async () => { const context = createContext() const body = { name: 'Shiroko' } const response = await mapResponse( new Promise((resolve) => resolve(body)), context ) expect(response).toBeInstanceOf(Response) expect(await response.json()).toEqual(body) expect(response.headers.toJSON()).toEqual({ ...context.headers, 'content-type': 'application/json' }) expect(response.status).toBe(200) }) it('map Error with custom context', async () => { const context = createContext() const response = mapResponse(new Error('Hello'), context) expect(response).toBeInstanceOf(Response) expect(await response.json()).toEqual({ name: 'Error', message: 'Hello' }) expect(response.headers.toJSON()).toEqual(context.headers) expect(response.status).toBe(500) }) it('map Response with custom context', async () => { const context = createContext() const response = await mapResponse(new Response('Shiroko'), context) const headers = response.headers.toJSON() expect(response).toBeInstanceOf(Response) expect(await response.text()).toEqual('Shiroko') expect(response.headers.toJSON()).toEqual(headers) }) it('map Response and merge Headers', async () => { const context = createContext() const response = await mapResponse( new Response('Shiroko', { headers: { Name: 'Himari' } }), context ) const headers = response.headers.toJSON() expect(response).toBeInstanceOf(Response) expect(await response.text()).toEqual('Shiroko') // @ts-ignore expect(response.headers.toJSON()).toEqual({ ...headers, name: 'Himari' }) }) it('map named status', async () => { const response = mapResponse('Shiroko', { status: "I'm a teapot", headers: {}, cookie: {} }) expect(response).toBeInstanceOf(Response) expect(await response.text()).toBe('Shiroko') expect(response.status).toBe(418) }) it('map redirect', async () => { const response = mapResponse(redirect('https://cunny.school', 302), { status: "I'm a teapot", headers: { Name: 'Sorasaki Hina' }, redirect: 'https://cunny.school', cookie: {} }) expect(response).toBeInstanceOf(Response) expect(response.status).toBe(302) // expect(await response.text()).toEqual('Shiroko') expect(response.headers.toJSON()).toEqual({ name: 'Sorasaki Hina', location: 'https://cunny.school' }) }) it('set cookie', async () => { const response = mapResponse('Hina', { status: 200, headers: { Name: 'Sorasaki Hina' }, cookie: { name: { value: 'hina' } } }) expect(response).toBeInstanceOf(Response) expect(await response.text()).toEqual('Hina') expect(response.headers.get('name')).toEqual('Sorasaki Hina') expect(response.headers.getAll('set-cookie')).toEqual(['name=hina']) }) it('set multiple cookie', async () => { const response = mapResponse('Hina', { status: 200, headers: { Name: 'Sorasaki Hina' }, cookie: { name: { value: 'hina' }, affiliation: { value: 'gehenna' } } }) expect(response).toBeInstanceOf(Response) expect(await response.text()).toEqual('Hina') expect(response.headers.get('name')).toEqual('Sorasaki Hina') expect(response.headers.getAll('set-cookie')).toEqual([ 'name=hina', 'affiliation=gehenna' ]) }) it('map toResponse', async () => { const response = mapResponse(new Passthrough(), createContext()) expect(response).toBeInstanceOf(Response) expect(await response.text()).toEqual('hi') expect(response.status).toBe(200) }) it('map video content-range', async () => { const kyuukararin = Bun.file('test/kyuukurarin.mp4') const response = mapResponse(kyuukararin, createContext()) expect(response).toBeInstanceOf(Response) expect(response.headers.get('accept-ranges')).toEqual('bytes') expect(response.headers.get('content-range')).toEqual( `bytes 0-${kyuukararin.size - 1}/${kyuukararin.size}` ) expect(response.status).toBe(200) }) it('skip content-range on not modified', async () => { const kyuukararin = Bun.file('test/kyuukurarin.mp4') const response = mapResponse(kyuukararin, { ...createContext(), status: 304 }) expect(response).toBeInstanceOf(Response) expect(response.headers.get('accept-ranges')).toBeNull() expect(response.headers.get('content-range')).toBeNull() expect(response.status).toBe(304) }) it('map formdata', async () => { const response = mapResponse( form({ a: Bun.file('test/kyuukurarin.mp4') }), createContext() ) expect(await response.formData()).toBeInstanceOf(FormData) // ? Auto appended by Bun // expect(response.headers.get('content-type')).toStartWith( // 'multipart/form-data' // ) expect(response.status).toBe(200) }) it('map beforeHandle', async () => { const app = new Elysia() .mapResponse(() => { return new Response('b') }) .get('/', () => 'a', { beforeHandle() { return 'a' } }) const response = await app.handle(req('/')).then((x) => x.text()) expect(response).toBe('b') }) it('map afterHandle', async () => { const app = new Elysia() .mapResponse(() => { return new Response('b') }) .get('/', () => 'a', { beforeHandle() { return 'a' } }) const response = await app.handle(req('/')).then((x) => x.text()) expect(response).toBe('b') }) it('respect set.headers on string response', async () => { const app = new Elysia() .onAfterHandle(({ set }) => { set.headers['content-type'] = 'text/html; charset=utf8' return '

Hina

' }) .get('/', () => 'a') const response = await app.handle(req('/')) expect(response.headers.get('content-type')).toBe( 'text/html; charset=utf8' ) }) }) ================================================ FILE: test/adapter/web-standard/set-cookie.test.ts ================================================ import { describe, expect, it } from 'bun:test' import { parseSetCookies } from '../../../src/adapter/utils' describe('Web Standard - Parse Set Cookie', () => { it('should handle empty arrays', () => { const headers = new Headers([]) const setCookie: string[] = [] const result = parseSetCookies(headers, setCookie) expect(result).toEqual(headers) }) it('should handle a setCookie array with one element containing a single key-value pair', () => { const headers = new Headers([]) const setCookie = ['key=value'] const result = parseSetCookies(headers, setCookie) expect(result.get('Set-Cookie')).toEqual('key=value') }) it('should handle a setCookie array with multiple elements, each containing a single key-value pair', () => { const headers = new Headers([]) const setCookie = ['key1=value1', 'key2=value2'] const result = parseSetCookies(headers, setCookie) expect(result.get('Set-Cookie')).toEqual('key1=value1, key2=value2') }) it('should handle a setCookie array with one element containing multiple key-value pairs', () => { const headers = new Headers([]) const setCookie = ['key1=value1; key2=value2'] const result = parseSetCookies(headers, setCookie) expect(result.get('Set-Cookie')).toEqual('key1=value1; key2=value2') }) it('should handle a setCookie array with multiple elements, each containing multiple key-value pairs', () => { const headers = new Headers([]) const setCookie = [ 'key1=value1; key2=value2', 'key3=value3; key4=value4' ] const result = parseSetCookies(headers, setCookie) expect(result.get('Set-Cookie')).toEqual( 'key1=value1; key2=value2, key3=value3; key4=value4' ) }) it('should handle null values', () => { const headers = null const setCookie = null // @ts-ignore const result = parseSetCookies(headers, setCookie) expect(result).toBeNull() }) }) ================================================ FILE: test/adapter/web-standard/utils.ts ================================================ export class Passthrough { toResponse() { return this.custom } get custom() { return 'hi' } } ================================================ FILE: test/aot/analysis.test.ts ================================================ /* eslint-disable @typescript-eslint/no-unused-vars */ import { Elysia, t } from '../../src' import { describe, expect, it } from 'bun:test' import { post, req } from '../utils' import { hasType } from '../../src/schema' const payload = { hello: 'world' } describe('Static code analysis', () => { it('parse object destructuring', async () => { const app = new Elysia().post('/', ({ body }) => body) const res = await app.handle(post('/', payload)).then((x) => x.json()) expect(res).toEqual(payload) }) it('parse context access', async () => { const app = new Elysia().post('/', (context) => context.body) const res = await app.handle(post('/', payload)).then((x) => x.json()) expect(res).toEqual(payload) }) it('parse context access using square bracket', async () => { const app = new Elysia().post('/', (context) => context['body']) const res = await app.handle(post('/', payload)).then((x) => x.json()) expect(res).toEqual(payload) }) it('parse assignment', async () => { const app = new Elysia().post('/', (context) => { const a = context.body return a }) const res = await app.handle(post('/', payload)).then((x) => x.json()) expect(res).toEqual(payload) }) it('parse multiple assignment', async () => { const app = new Elysia().post('/', (context) => { const _ = 1, b = context.body return b }) const res = await app.handle(post('/', payload)).then((x) => x.json()) expect(res).toEqual(payload) }) it('parse multiple assignment with object destructuring', async () => { const app = new Elysia().post('/', (context) => { const _ = 1, { body } = context return body }) const res = await app.handle(post('/', payload)).then((x) => x.json()) expect(res).toEqual(payload) }) it('parse lazy object destructuring', async () => { const app = new Elysia().post('/', (context) => { const { body: a } = context return a }) const res = await app.handle(post('/', payload)).then((x) => x.json()) expect(res).toEqual(payload) }) it('parse headers', async () => { const app = new Elysia().get('/', ({ headers }) => headers) const res = await app .handle( new Request('http://localhost/', { headers: payload }) ) .then((x) => x.json()) expect(res).toEqual(payload) }) it('parse body type json', async () => { const body = { message: 'Rikuhachima Aru' } const app = new Elysia().post('/json', (c) => c.body, { parse: 'json' }) const res = await app.handle(post('/json', body)).then((x) => x.json()) expect(res).toEqual(body) }) it('find nested Elysia Schema', () => { const schema = t.Object({ a: t.Object({ b: t.Object({ c: t.File() }), d: t.String() }), id: t.Numeric(), b: t.Object({ c: t.File() }) }) expect(hasType('File', schema)).toBeTrue() }) it('find Elysia Schema on root', () => { const schema = t.Numeric() expect(hasType('Numeric', schema)).toBeTrue }) it('find return null if Elysia Schema is not found', () => { const schema = t.Object({ a: t.Object({ b: t.Object({ c: t.Number() }), d: t.String() }), id: t.Number(), b: t.Object({ c: t.Number() }) }) expect(hasType('File', schema)).toBeFalse() }) it('restart server once analyze', async () => { const plugin = async () => { await new Promise((resolve) => setTimeout(resolve, 1)) return (app: Elysia) => app.get('/', () => 'hi') } const app = new Elysia().use(plugin()) await new Promise((resolve) => setTimeout(resolve, 25)) const response = await app.handle(req('/')).then((x) => x.text()) expect(response).toBe('hi') }) it('parse custom parser with schema', async () => { const app = new Elysia() .onParse((request, contentType) => { if (contentType === 'application/elysia') return 'hi' }) .post('/', ({ body }) => body, { body: t.String() }) await new Promise((resolve) => setTimeout(resolve, 25)) const response = await app .handle( new Request('http://localhost/', { method: 'POST', headers: { 'content-type': 'application/elysia' }, body: 'need correction' }) ) .then((x) => x.text()) expect(response).toBe('hi') }) it('handle multiple numeric type', async () => { const app = new Elysia() .get('/', () => 'Hello Elysia1') .get( '/products', ({ query }) => `pageIndex=${query.pageIndex}; pageSize=${query.pageSize}`, { query: t.Object({ pageIndex: t.Numeric(), pageSize: t.Numeric() }) } ) const response = await app .handle(req('/products?pageIndex=1&pageSize=2')) .then((x) => x.text()) expect(response).toBe(`pageIndex=1; pageSize=2`) }) it('break out of switch map when path is not found', async () => { const app = new Elysia() .options('/', () => 'hi') .get('/games', () => 'games') const response = await app .handle( new Request('http://localhost/', { method: 'OPTIONS' }) ) .then((x) => x.text()) expect(response).toBe('hi') }) it('handle accurate trie properties', async () => { const app = new Elysia().get('/what', ({ query }) => query, { query: t.Object({ stee: t.Optional(t.Literal('on')), mtee: t.Optional(t.Literal('on')), ltee: t.Optional(t.Literal('on')), xltee: t.Optional(t.Literal('on')), xxltee: t.Optional(t.Literal('on')), xxxltee: t.Optional(t.Literal('on')) }) }) const response = await app .handle(req('/what?xxltee=on')) .then((x) => x.json()) expect(response).toEqual({ xxltee: 'on' }) }) }) ================================================ FILE: test/aot/generation.test.ts ================================================ /* eslint-disable @typescript-eslint/no-unused-vars */ import { describe, it, expect } from 'bun:test' import { Context, Elysia, t } from '../../src' import { post, req } from '../utils' describe('code generation', () => { it('fallback query if not presented', async () => { const app = new Elysia().get('/', () => 'hi', { query: t.Object({ id: t.Optional(t.Number()) }) }) const res = await app.handle(req('/')).then((x) => x.text()) expect(res).toBe('hi') }) it('process isFnUse', async () => { const body = { hello: 'Wanderschaffen' } const app = new Elysia() .post('/1', ({ body }) => body) .post('/2', function ({ body }) { return body }) .post('/3', (context) => { return context.body }) .post('/4', (context) => { const c = context const { body } = c return body }) .post('/5', (context) => { const _ = context, a = context const { body } = a return body }) .post('/6', () => body, { transform({ body }) { // not empty } }) .post('/7', () => body, { beforeHandle({ body }) { // not empty } }) .post('/8', () => body, { afterHandle({ body }) { // not empty } }) .post('/9', ({ ...rest }) => rest.body) const from = (number: number) => app.handle(post(`/${number}`, body)).then((r) => r.json()) const cases = Promise.all( Array(9) .fill(null) .map((_, i) => from(i + 1)) ) for (const unit of await cases) expect(unit).toEqual(body) }) it('process isContextPassToUnknown', async () => { const body = { hello: 'Wanderschaffen' } const handle = (context: Context) => context.body const app = new Elysia() .post('/1', (context) => handle(context)) .post('/2', function (context) { return handle(context) }) .post('/3', (context) => { const c = context return handle(c) }) .post('/4', (context) => { const _ = context, a = context return handle(a) }) .post('/5', () => '', { beforeHandle(context) { return handle(context) } }) .post('/6', () => body, { afterHandle(context) { return handle(context) } }) .post('/7', ({ ...rest }) => handle(rest)) const from = (number: number) => app.handle(post(`/${number}`, body)).then((r) => r.json()) const cases = Promise.all( Array(7) .fill(null) .map((_, i) => from(i + 1)) ) for (const unit of await cases) expect(unit).toEqual(body) }) }) ================================================ FILE: test/aot/has-transform.test.ts ================================================ import { describe, it, expect } from 'bun:test' import { t } from '../../src' import { hasTransform } from '../../src/schema' describe('Has Transform', () => { it('find primitive', () => { const schema = t .Transform(t.String()) .Decode((v) => v) .Encode((v) => v) expect(hasTransform(schema)).toBe(true) }) it('find in root object', () => { const schema = t.Object({ liyue: t .Transform(t.String()) .Decode((v) => v) .Encode((v) => v) }) expect(hasTransform(schema)).toBe(true) }) it('find in nested object', () => { const schema = t.Object({ liyue: t.Object({ id: t .Transform(t.String()) .Decode((v) => v) .Encode((v) => v) }) }) expect(hasTransform(schema)).toBe(true) }) it('find in Optional', () => { const schema = t.Optional( t.Object({ prop1: t .Transform(t.String()) .Decode((v) => v) .Encode((v) => v) }) ) expect(hasTransform(schema)).toBe(true) }) it('find on multiple transform', () => { const schema = t.Object({ id: t .Transform(t.String()) .Decode((v) => v) .Encode((v) => v), name: t .Transform(t.String()) .Decode((v) => v) .Encode((v) => v) }) expect(hasTransform(schema)).toBe(true) }) it('return false on not found', () => { const schema = t.Object({ name: t.String(), age: t.Number() }) expect(hasTransform(schema)).toBe(false) }) it('found on Union', () => { const schema = t.Object({ id: t.Number(), liyue: t.Union([ t .Transform(t.String()) .Decode((v) => v) .Encode((v) => v), t.Number() ]) }) expect(hasTransform(schema)).toBe(true) }) it('Found t.Numeric', () => { const schema = t.Object({ id: t.Numeric(), liyue: t.String() }) expect(hasTransform(schema)).toBe(true) }) it('Found t.NumericEnum', () => { const schema = t.Object({ gender: t.NumericEnum({ UNKNOWN: 0, MALE: 1, FEMALE: 2 }), liyue: t.String() }) expect(hasTransform(schema)).toBe(true) }) it('Found t.ObjectString', () => { const schema = t.Object({ id: t.String(), liyue: t.ObjectString({ name: t.String() }) }) expect(hasTransform(schema)).toBe(true) }) }) ================================================ FILE: test/aot/has-type.test.ts ================================================ import { describe, it, expect } from 'bun:test' import { t } from '../../src' import { hasType } from '../../src/schema' describe('Has Transform', () => { it('find primitive', () => { const schema = t .Transform(t.File()) .Decode((v) => v) .Encode((v) => v) expect(hasType('File', schema)).toBe(true) }) it('find in root object', () => { const schema = t.Object({ liyue: t.File() }) expect(hasType('File', schema)).toBe(true) }) it('find in nested object', () => { const schema = t.Object({ liyue: t.Object({ id: t.File() }) }) expect(hasType('File', schema)).toBe(true) }) it('find in Optional', () => { const schema = t.Optional( t.Object({ prop1: t.File() }) ) expect(hasType('File', schema)).toBe(true) }) it('find on multiple transform', () => { const schema = t.Object({ id: t.File(), name: t.File() }) expect(hasType('File', schema)).toBe(true) }) it('return false on not found', () => { const schema = t.Object({ name: t.String(), age: t.Number() }) expect(hasType('File', schema)).toBe(false) }) it('found on Union', () => { const schema = t.Object({ id: t.Number(), liyue: t.Union([t.Number(), t.File()]) }) expect(hasType('File', schema)).toBe(true) }) it('found on direct Union', () => { const schema = t.Union([ t.Object({ id: t.Number(), liyue: t.File() }), t.Object({ id: t.Number(), liyue: t.Number(), }), ]) expect(hasType('File', schema)).toBe(true) }) it('find in Import wrapping File', () => { const schema = t.Module({ Avatar: t.File() }).Import('Avatar') expect(hasType('File', schema)).toBe(true) }) it('find in Import wrapping Object with File', () => { const schema = t.Module({ Upload: t.Object({ name: t.String(), file: t.File() }) }).Import('Upload') expect(hasType('File', schema)).toBe(true) }) it('return false for Import wrapping Object without File', () => { const schema = t.Module({ User: t.Object({ name: t.String(), age: t.Number() }) }).Import('User') expect(hasType('File', schema)).toBe(false) }) it('find in Import wrapping Union with File', () => { const schema = t.Module({ Data: t.Union([ t.Object({ file: t.File() }), t.Null() ]) }).Import('Data') expect(hasType('File', schema)).toBe(true) }) it('find in Import wrapping Array of Files', () => { const schema = t.Module({ Uploads: t.Array(t.File()) }).Import('Uploads') expect(hasType('Files', schema)).toBe(true) }) it('find in Import wrapping Array of Files using t.Files', () => { const schema = t.Module({ Uploads: t.Files() }).Import('Uploads') expect(hasType('Files', schema)).toBe(true) }) it('find in Array of Files (direct)', () => { const schema = t.Array(t.File()) expect(hasType('Files', schema)).toBe(true) }) it('find in Array of Files using t.Files (direct)', () => { const schema = t.Files() expect(hasType('Files', schema)).toBe(true) }) // Intersect schema tests it('find on direct Intersect', () => { const schema = t.Intersect([ t.Object({ id: t.Number() }), t.Object({ file: t.File() }) ]) expect(hasType('File', schema)).toBe(true) }) it('do not find on Intersect without File', () => { const schema = t.Intersect([ t.Object({ id: t.Number() }), t.Object({ name: t.String() }) ]) expect(hasType('File', schema)).toBe(false) }) it('find on nested Union in Intersect', () => { const schema = t.Intersect([ t.Object({ id: t.Number() }), t.Union([t.Object({ file: t.File() }), t.Null()]) ]) expect(hasType('File', schema)).toBe(true) }) it('find File in Intersect referenced via Module.Import()', () => { const schema = t .Module({ Data: t.Intersect([ t.Object({ id: t.Number() }), t.Object({ file: t.File() }) ]) }) .Import('Data') expect(hasType('File', schema)).toBe(true) }) }) ================================================ FILE: test/aot/response.test.ts ================================================ import { describe, expect, it } from 'bun:test' import { Elysia, t } from '../../src' import { req } from '../utils' import { signCookie } from '../../src/utils' const secrets = 'We long for the seven wailings. We bear the koan of Jericho.' const getCookies = (response: Response) => response.headers.getAll('Set-Cookie').map((x) => { return decodeURIComponent(x) }) const app = new Elysia() .get( '/council', ({ cookie: { council } }) => (council.value = [ { name: 'Rin', affilation: 'Administration' } ]) ) .get('/create', ({ cookie: { name } }) => (name.value = 'Himari')) .get('/multiple', ({ cookie: { name, president } }) => { name.value = 'Himari' president.value = 'Rio' return 'ok' }) .get( '/update', ({ cookie: { name } }) => { name.value = 'seminar: Himari' return name.value }, { cookie: t.Cookie( { name: t.Optional(t.String()) }, { secrets, sign: ['name'] } ) } ) .get('/remove', ({ cookie }) => { for (const self of Object.values(cookie)) self.remove() return 'Deleted' }) describe('Dynamic Cookie Response', () => { it('set cookie', async () => { const response = await app.handle(req('/create')) expect(getCookies(response)).toEqual(['name=Himari; Path=/']) }) it('set multiple cookie', async () => { const response = await app.handle(req('/multiple')) expect(getCookies(response)).toEqual([ 'name=Himari; Path=/', 'president=Rio; Path=/' ]) }) it('set JSON cookie', async () => { const response = await app.handle(req('/council')) expect(getCookies(response)).toEqual([ 'council=[{"name":"Rin","affilation":"Administration"}]; Path=/' ]) }) it('write cookie on difference value', async () => { const response = await app.handle( req('/council', { headers: { cookie: 'council=' + encodeURIComponent( JSON.stringify([ { name: 'Aoi', affilation: 'Financial' } ]) ) + '; Path=/' } }) ) expect(getCookies(response)).toEqual([ 'council=[{"name":"Rin","affilation":"Administration"}]; Path=/' ]) }) it('remove cookie', async () => { const response = await app.handle( req('/remove', { headers: { cookie: 'council=' + encodeURIComponent( JSON.stringify([ { name: 'Rin', affilation: 'Administration' } ]) ) } }) ) expect(getCookies(response)[0]).toInclude( `council=; Max-Age=0; Path=/; Expires=${new Date(0).toUTCString()}` ) }) it('sign cookie', async () => { const response = await app.handle(req('/update')) expect(getCookies(response)).toEqual([ `name=${await signCookie('seminar: Himari', secrets)}; Path=/` ]) }) it('sign/unsign cookie', async () => { const response = await app.handle( req('/update', { headers: { cookie: `name=${await signCookie( 'seminar: Himari', secrets )}` } }) ) expect(response.status).toBe(200) }) it('inherits cookie settings', async () => { const app = new Elysia({ cookie: { secrets, sign: ['name'] } }).get( '/update', ({ cookie: { name } }) => { if (!name.value) name.value = 'seminar: Himari' return name.value }, { cookie: t.Cookie({ name: t.Optional(t.String()) }) } ) const response = await app.handle( req('/update', { headers: { cookie: `name=${await signCookie( 'seminar: Himari', secrets )}` } }) ) expect(response.status).toBe(200) }) it('sign all cookie', async () => { const app = new Elysia({ cookie: { secrets, sign: true } }).get( '/update', ({ cookie: { name } }) => { if (!name.value) name.value = 'seminar: Himari' return name.value }, { cookie: t.Cookie({ name: t.Optional(t.String()) }) } ) const response = await app.handle( req('/update', { headers: { cookie: `name=${await signCookie('seminar: Himari', secrets)}` } }) ) expect(response.status).toBe(200) }) it("don't share context between race condition", async () => { const resolver = Promise.withResolvers() const app = new Elysia({ aot: false }) .onRequest(() => new Promise((resolve) => setTimeout(resolve, 1))) .get('/test', ({ request }) => { resolver.resolve(request.url) }) app.handle(new Request('http://localhost:1025/test')) app.handle(new Request('http://localhost:1025/bad')) expect(await resolver.promise).toBe('http://localhost:1025/test') }) }) ================================================ FILE: test/bun/router.test.ts ================================================ import { describe, it, expect } from 'bun:test' import { Elysia, ELYSIA_REQUEST_ID, t } from '../../src' import { req } from '../utils' describe('Bun router', () => { it('works', async () => { let trace = false let wrapped = false let onRequest = false let traceOnRequest = false const app = new Elysia({ systemRouter: true }) .trace(({ onHandle, onRequest }) => { onRequest(() => { traceOnRequest = true }) onHandle(() => { trace = true }) }) .onRequest(() => { onRequest = true }) .decorate('decorated', 'decorated') .state('state', 'state') .derive(() => ({ derived: 'derived' })) .resolve(() => ({ resolved: 'resolved' })) .get('/', ({ store, decorated, derived, resolved }) => ({ store, decorated, derived, resolved })) .wrap((fn) => { wrapped = true return fn }) .listen(0) const response = await fetch( `http://localhost:${app.server!.port}` ).then((x) => x.json()) expect(response).toEqual({ store: { state: 'state' }, decorated: 'decorated', derived: 'derived', resolved: 'resolved' }) expect(wrapped).toEqual(true) expect(trace).toBe(true) expect(onRequest).toBe(true) expect(traceOnRequest).toBe(true) }) it('handle params and query', async () => { const app = new Elysia({ systemRouter: true }) .get('/id/:id', ({ query, params }) => ({ query, params })) .listen(0) const query = await fetch( `http://localhost:${app.server!.port}/id/1?q=s` ).then((x) => x.json()) expect(query).toEqual({ query: { q: 's' }, params: { id: '1' } }) }) it('handle optional params', async () => { const app = new Elysia({ systemRouter: false }) .get('/id/:id?/:name?', ({ params }) => params) .listen(0) const query = await Promise.all([ fetch(`http://localhost:${app.server!.port}/id`).then((x) => x.json() ), fetch(`http://localhost:${app.server!.port}/id/1`).then((x) => x.json() ), fetch(`http://localhost:${app.server!.port}/id/1/saltyaom`).then( (x) => x.json() ) ]) expect(query).toEqual([{}, { id: '1' }, { id: '1', name: 'saltyaom' }]) }) it('handle async static route', async () => { const app = new Elysia() .get( '/', Promise.resolve( new Response(`

Hello World

`, { headers: { 'Content-Type': 'text/html' } }) ) ) .listen(0) await Bun.sleep(20) const response = await fetch( `http://localhost:${app.server!.port}` ).then((x) => x.text()) expect(response).toEqual('

Hello World

') }) it('handle mount', async () => { const app = new Elysia() .mount((request: Request) => new Response(request.url)) .mount('/prefix', (request: Request) => new Response(request.url)) .listen(0) const response = await Promise.all([ fetch(`http://localhost:${app.server?.port}/a`), fetch(`http://localhost:${app.server?.port}/prefix/a`) ]) expect(response[0].status).toBe(200) expect(response[1].status).toBe(200) }) it('handle trace url', async () => { let url = '' let hasRequestId = false const app = new Elysia() .trace((a) => { a.onHandle(() => { // @ts-expect-error private property url = a.context.url // @ts-expect-error private property hasRequestId = !!a.context[ELYSIA_REQUEST_ID] }) }) .get('/', () => 'ok') .listen(0) await fetch(`http://localhost:${app.server!.port}/`) expect(url).toBe(`http://localhost:${app.server!.port}/`) expect(hasRequestId).toBe(true) }) it('handle wrap in mount', async () => { let url = '' let hasWrap = false const app = new Elysia() .wrap((fn) => { hasWrap = true return fn }) .mount('/', (request) => new Response((url = request.url))) .listen(0) await fetch(`http://localhost:${app.server!.port}/`) expect(url).toBe(`http://localhost:${app.server!.port}/`) expect(hasWrap).toBe(true) }) it('handle trace url with wrap', async () => { let url = '' let hasRequestId = false let hasWrap = false const app = new Elysia() .wrap((fn) => { hasWrap = true return fn }) .trace((a) => { a.onHandle(() => { // @ts-expect-error private property url = a.context.url // @ts-expect-error private property hasRequestId = !!a.context[ELYSIA_REQUEST_ID] }) }) .get('/', () => 'ok') .listen(0) await fetch(`http://localhost:${app.server!.port}/`) expect(url).toBe(`http://localhost:${app.server!.port}/`) expect(hasWrap).toBe(true) expect(hasRequestId).toBe(true) }) it('handle mount', async () => { let url = '' let hasRequestId = false let hasWrap = false const app = new Elysia() .wrap((fn) => { hasWrap = true return fn }) .trace((a) => { a.onHandle(() => { // @ts-expect-error private property url = a.context.url // @ts-expect-error private property hasRequestId = !!a.context[ELYSIA_REQUEST_ID] }) }) .get('/', () => 'ok') .listen(0) await fetch(`http://localhost:${app.server!.port}/`) expect(url).toBe(`http://localhost:${app.server!.port}/`) expect(hasWrap).toBe(true) expect(hasRequestId).toBe(true) }) it('handle async plugin', async () => { const asyncPlugin = async () => new Elysia({ name: 'async' }) .get('/router', () => 'OK') .get('/static', 'OK') const app = new Elysia({ name: 'main' }).use(asyncPlugin).listen(0) const [router, _static] = await Promise.all([ fetch(`http://localhost:${app.server?.port}/router`).then((x) => x.text() ), fetch(`http://localhost:${app.server?.port}/static`).then((x) => x.text() ) ]) expect(router).toBe('OK') expect(_static).toBe('OK') }) it('handle async request', async () => { const app = new Elysia() .onRequest(async () => {}) .mount('/auth', () => new Response('OK')) .listen(0) const response = await fetch( `http://localhost:${app.server?.port}/auth` ).then((x) => x.text()) expect(response).toBe('OK') }) it('handle wildcard', async () => { const app = new Elysia() .get('/hi/:id', ({ params }) => params) .get('/hi/*', ({ params }) => params) .listen(0) const [response1, response2] = await Promise.all([ fetch( `http://${app.server?.hostname}:${app.server?.port}/hi/saltyaom` ).then((x) => x.json()), fetch( `http://${app.server?.hostname}:${app.server?.port}/hi/salty/aom` ).then((x) => x.json()) ]) expect(response1).toEqual({ id: 'saltyaom' }) expect(response2).toEqual({ '*': 'salty/aom' }) }) it('mapEarlyResponse onRequest', async () => { const app = new Elysia() .onRequest(() => 'OK!! XD') .get('/', () => '') .listen(0) const response = await fetch( `http://localhost:${app.server?.port}/auth` ).then((x) => x.text()) expect(response).toBe('OK!! XD') }) // https://github.com/elysiajs/elysia/issues/1752 it('trailing slash should be consistent with non-trailing slash', async () => { const app = new Elysia() .get('/items/types/', () => '/items/types') .get('/items/types/:id', () => '/items/types/:id') .get('/items/:id', () => '/items/:id') .listen(0) expect( fetch(`http://localhost:${app.server?.port}/items/types`).then( (x) => x.text() ) ).resolves.toBe('/items/types') expect( fetch(`http://localhost:${app.server?.port}/items/types/`).then( (x) => x.text() ) ).resolves.toBe('/items/types') expect( fetch(`http://localhost:${app.server?.port}/items/1`).then((x) => x.text() ) ).resolves.toBe('/items/:id') expect( fetch(`http://localhost:${app.server?.port}/items/types/a`).then( (x) => x.text() ) ).resolves.toBe('/items/types/:id') }) }) ================================================ FILE: test/bun/sql.test.ts ================================================ import { SQL } from 'bun' import { describe, it, expect } from 'bun:test' import Elysia from '../../src' import { req } from '../utils' describe('Bun.SQL', () => { it('serialize custom array-like custom class with array sub class', async () => { const sql = new SQL(':memory:') await sql`CREATE TABLE elysia_repro_users (id SERIAL PRIMARY KEY, name TEXT)` const { count } = await sql`SELECT COUNT(*) as count FROM elysia_repro_users`.then( (x) => x[0] ) if (!count) await sql`INSERT INTO elysia_repro_users (name) VALUES ('Alice'), ('Bob')` const app = new Elysia().get( '/', () => sql`SELECT * FROM elysia_repro_users` ) const value = await app.handle(req('/')).then((x) => x.json()) expect(value).toEqual([ { id: null, name: 'Alice' }, { id: null, name: 'Bob' } ]) }) }) ================================================ FILE: test/cloudflare/.gitignore ================================================ node_modules/ package-lock.json .wrangler ================================================ FILE: test/cloudflare/package.json ================================================ { "name": "elysia-cf", "version": "0.0.0", "private": true, "scripts": { "dev": "wrangler dev", "start": "wrangler dev", "test": "bun script/test.ts", "cf-typegen": "wrangler types" }, "devDependencies": { "typescript": "^5.8.2", "wrangler": "^4.40.2" }, "dependencies": { "elysia": "../../" }, "overrides": { "esbuild": "0.25.4" } } ================================================ FILE: test/cloudflare/script/test.ts ================================================ const server = Bun.spawn({ cmd: ['bunx', 'wrangler', 'dev', '--port', '8787', '--local'], }) Bun.sleepSync(750) setInterval(async () => { const response = await fetch('http://localhost:8787').catch(() => {}) if (!response) return const value = await response.text() if (value === 'Elysia on Cloudflare Worker!' && response.status) { console.log('✅ Cloudflare Worker works') server.kill('SIGKILL') process.exit(0) } }, 500) setTimeout(() => { console.log("❌ Cloudflare Worker doesn't work") server.kill('SIGKILL') process.exit(1) }, 8000) ================================================ FILE: test/cloudflare/src/index.ts ================================================ import { Elysia, t } from 'elysia' import { CloudflareAdapter } from 'elysia/adapter/cloudflare-worker' const sub = new Elysia().get('/test', () => 'hello') export default new Elysia({ adapter: CloudflareAdapter }) .get('/', () => 'Elysia on Cloudflare Worker!') .use(sub) .compile() ================================================ FILE: test/cloudflare/tsconfig.json ================================================ { "compilerOptions": { /* Visit https://aka.ms/tsconfig.json to read more about this file */ /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */ "target": "es2021", /* Specify a set of bundled library declaration files that describe the target runtime environment. */ "lib": ["es2021"], /* Specify what JSX code is generated. */ "jsx": "react-jsx", /* Specify what module code is generated. */ "module": "es2022", /* Specify how TypeScript looks up a file from a given module specifier. */ "moduleResolution": "Bundler", /* Enable importing .json files */ "resolveJsonModule": true, /* Allow JavaScript files to be a part of your program. Use the `checkJS` option to get errors from these files. */ "allowJs": true, /* Enable error reporting in type-checked JavaScript files. */ "checkJs": false, /* Disable emitting files from a compilation. */ "noEmit": true, /* Ensure that each file can be safely transpiled without relying on other imports. */ "isolatedModules": true, /* Allow 'import x from y' when a module doesn't have a default export. */ "allowSyntheticDefaultImports": true, /* Ensure that casing is correct in imports. */ "forceConsistentCasingInFileNames": true, /* Enable all strict type-checking options. */ "strict": true, /* Skip type checking all .d.ts files. */ "skipLibCheck": true, "types": [ "./worker-configuration.d.ts" ] }, "exclude": ["test"], "include": ["worker-configuration.d.ts", "src/**/*.ts"] } ================================================ FILE: test/cloudflare/worker-configuration.d.ts ================================================ /* eslint-disable */ // Generated by Wrangler by running `wrangler types` (hash: b739a9c19cff1463949c4db47674ed86) // Runtime types generated with workerd@1.20250924.0 2025-09-06 declare namespace Cloudflare { interface GlobalProps { mainModule: typeof import("./src/index"); } interface Env { } } interface Env extends Cloudflare.Env {} // Begin runtime types /*! ***************************************************************************** Copyright (c) Cloudflare. All rights reserved. Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE, MERCHANTABLITY OR NON-INFRINGEMENT. See the Apache Version 2.0 License for specific language governing permissions and limitations under the License. ***************************************************************************** */ /* eslint-disable */ // noinspection JSUnusedGlobalSymbols declare var onmessage: never; /** * An abnormal event (called an exception) which occurs as a result of calling a method or accessing a property of a web API. * * [MDN Reference](https://developer.mozilla.org/docs/Web/API/DOMException) */ declare class DOMException extends Error { constructor(message?: string, name?: string); /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/DOMException/message) */ readonly message: string; /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/DOMException/name) */ readonly name: string; /** * @deprecated * * [MDN Reference](https://developer.mozilla.org/docs/Web/API/DOMException/code) */ readonly code: number; static readonly INDEX_SIZE_ERR: number; static readonly DOMSTRING_SIZE_ERR: number; static readonly HIERARCHY_REQUEST_ERR: number; static readonly WRONG_DOCUMENT_ERR: number; static readonly INVALID_CHARACTER_ERR: number; static readonly NO_DATA_ALLOWED_ERR: number; static readonly NO_MODIFICATION_ALLOWED_ERR: number; static readonly NOT_FOUND_ERR: number; static readonly NOT_SUPPORTED_ERR: number; static readonly INUSE_ATTRIBUTE_ERR: number; static readonly INVALID_STATE_ERR: number; static readonly SYNTAX_ERR: number; static readonly INVALID_MODIFICATION_ERR: number; static readonly NAMESPACE_ERR: number; static readonly INVALID_ACCESS_ERR: number; static readonly VALIDATION_ERR: number; static readonly TYPE_MISMATCH_ERR: number; static readonly SECURITY_ERR: number; static readonly NETWORK_ERR: number; static readonly ABORT_ERR: number; static readonly URL_MISMATCH_ERR: number; static readonly QUOTA_EXCEEDED_ERR: number; static readonly TIMEOUT_ERR: number; static readonly INVALID_NODE_TYPE_ERR: number; static readonly DATA_CLONE_ERR: number; get stack(): any; set stack(value: any); } type WorkerGlobalScopeEventMap = { fetch: FetchEvent; scheduled: ScheduledEvent; queue: QueueEvent; unhandledrejection: PromiseRejectionEvent; rejectionhandled: PromiseRejectionEvent; }; declare abstract class WorkerGlobalScope extends EventTarget { EventTarget: typeof EventTarget; } /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/console) */ interface Console { "assert"(condition?: boolean, ...data: any[]): void; /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/console/clear_static) */ clear(): void; /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/console/count_static) */ count(label?: string): void; /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/console/countReset_static) */ countReset(label?: string): void; /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/console/debug_static) */ debug(...data: any[]): void; /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/console/dir_static) */ dir(item?: any, options?: any): void; /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/console/dirxml_static) */ dirxml(...data: any[]): void; /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/console/error_static) */ error(...data: any[]): void; /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/console/group_static) */ group(...data: any[]): void; /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/console/groupCollapsed_static) */ groupCollapsed(...data: any[]): void; /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/console/groupEnd_static) */ groupEnd(): void; /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/console/info_static) */ info(...data: any[]): void; /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/console/log_static) */ log(...data: any[]): void; /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/console/table_static) */ table(tabularData?: any, properties?: string[]): void; /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/console/time_static) */ time(label?: string): void; /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/console/timeEnd_static) */ timeEnd(label?: string): void; /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/console/timeLog_static) */ timeLog(label?: string, ...data: any[]): void; timeStamp(label?: string): void; /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/console/trace_static) */ trace(...data: any[]): void; /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/console/warn_static) */ warn(...data: any[]): void; } declare const console: Console; type BufferSource = ArrayBufferView | ArrayBuffer; type TypedArray = Int8Array | Uint8Array | Uint8ClampedArray | Int16Array | Uint16Array | Int32Array | Uint32Array | Float32Array | Float64Array | BigInt64Array | BigUint64Array; declare namespace WebAssembly { class CompileError extends Error { constructor(message?: string); } class RuntimeError extends Error { constructor(message?: string); } type ValueType = "anyfunc" | "externref" | "f32" | "f64" | "i32" | "i64" | "v128"; interface GlobalDescriptor { value: ValueType; mutable?: boolean; } class Global { constructor(descriptor: GlobalDescriptor, value?: any); value: any; valueOf(): any; } type ImportValue = ExportValue | number; type ModuleImports = Record; type Imports = Record; type ExportValue = Function | Global | Memory | Table; type Exports = Record; class Instance { constructor(module: Module, imports?: Imports); readonly exports: Exports; } interface MemoryDescriptor { initial: number; maximum?: number; shared?: boolean; } class Memory { constructor(descriptor: MemoryDescriptor); readonly buffer: ArrayBuffer; grow(delta: number): number; } type ImportExportKind = "function" | "global" | "memory" | "table"; interface ModuleExportDescriptor { kind: ImportExportKind; name: string; } interface ModuleImportDescriptor { kind: ImportExportKind; module: string; name: string; } abstract class Module { static customSections(module: Module, sectionName: string): ArrayBuffer[]; static exports(module: Module): ModuleExportDescriptor[]; static imports(module: Module): ModuleImportDescriptor[]; } type TableKind = "anyfunc" | "externref"; interface TableDescriptor { element: TableKind; initial: number; maximum?: number; } class Table { constructor(descriptor: TableDescriptor, value?: any); readonly length: number; get(index: number): any; grow(delta: number, value?: any): number; set(index: number, value?: any): void; } function instantiate(module: Module, imports?: Imports): Promise; function validate(bytes: BufferSource): boolean; } /** * This ServiceWorker API interface represents the global execution context of a service worker. * Available only in secure contexts. * * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ServiceWorkerGlobalScope) */ interface ServiceWorkerGlobalScope extends WorkerGlobalScope { DOMException: typeof DOMException; WorkerGlobalScope: typeof WorkerGlobalScope; btoa(data: string): string; atob(data: string): string; setTimeout(callback: (...args: any[]) => void, msDelay?: number): number; setTimeout(callback: (...args: Args) => void, msDelay?: number, ...args: Args): number; clearTimeout(timeoutId: number | null): void; setInterval(callback: (...args: any[]) => void, msDelay?: number): number; setInterval(callback: (...args: Args) => void, msDelay?: number, ...args: Args): number; clearInterval(timeoutId: number | null): void; queueMicrotask(task: Function): void; structuredClone(value: T, options?: StructuredSerializeOptions): T; reportError(error: any): void; fetch(input: RequestInfo | URL, init?: RequestInit): Promise; self: ServiceWorkerGlobalScope; crypto: Crypto; caches: CacheStorage; scheduler: Scheduler; performance: Performance; Cloudflare: Cloudflare; readonly origin: string; Event: typeof Event; ExtendableEvent: typeof ExtendableEvent; CustomEvent: typeof CustomEvent; PromiseRejectionEvent: typeof PromiseRejectionEvent; FetchEvent: typeof FetchEvent; TailEvent: typeof TailEvent; TraceEvent: typeof TailEvent; ScheduledEvent: typeof ScheduledEvent; MessageEvent: typeof MessageEvent; CloseEvent: typeof CloseEvent; ReadableStreamDefaultReader: typeof ReadableStreamDefaultReader; ReadableStreamBYOBReader: typeof ReadableStreamBYOBReader; ReadableStream: typeof ReadableStream; WritableStream: typeof WritableStream; WritableStreamDefaultWriter: typeof WritableStreamDefaultWriter; TransformStream: typeof TransformStream; ByteLengthQueuingStrategy: typeof ByteLengthQueuingStrategy; CountQueuingStrategy: typeof CountQueuingStrategy; ErrorEvent: typeof ErrorEvent; MessageChannel: typeof MessageChannel; MessagePort: typeof MessagePort; EventSource: typeof EventSource; ReadableStreamBYOBRequest: typeof ReadableStreamBYOBRequest; ReadableStreamDefaultController: typeof ReadableStreamDefaultController; ReadableByteStreamController: typeof ReadableByteStreamController; WritableStreamDefaultController: typeof WritableStreamDefaultController; TransformStreamDefaultController: typeof TransformStreamDefaultController; CompressionStream: typeof CompressionStream; DecompressionStream: typeof DecompressionStream; TextEncoderStream: typeof TextEncoderStream; TextDecoderStream: typeof TextDecoderStream; Headers: typeof Headers; Body: typeof Body; Request: typeof Request; Response: typeof Response; WebSocket: typeof WebSocket; WebSocketPair: typeof WebSocketPair; WebSocketRequestResponsePair: typeof WebSocketRequestResponsePair; AbortController: typeof AbortController; AbortSignal: typeof AbortSignal; TextDecoder: typeof TextDecoder; TextEncoder: typeof TextEncoder; navigator: Navigator; Navigator: typeof Navigator; URL: typeof URL; URLSearchParams: typeof URLSearchParams; URLPattern: typeof URLPattern; Blob: typeof Blob; File: typeof File; FormData: typeof FormData; Crypto: typeof Crypto; SubtleCrypto: typeof SubtleCrypto; CryptoKey: typeof CryptoKey; CacheStorage: typeof CacheStorage; Cache: typeof Cache; FixedLengthStream: typeof FixedLengthStream; IdentityTransformStream: typeof IdentityTransformStream; HTMLRewriter: typeof HTMLRewriter; } declare function addEventListener(type: Type, handler: EventListenerOrEventListenerObject, options?: EventTargetAddEventListenerOptions | boolean): void; declare function removeEventListener(type: Type, handler: EventListenerOrEventListenerObject, options?: EventTargetEventListenerOptions | boolean): void; /** * Dispatches a synthetic event event to target and returns true if either event's cancelable attribute value is false or its preventDefault() method was not invoked, and false otherwise. * * [MDN Reference](https://developer.mozilla.org/docs/Web/API/EventTarget/dispatchEvent) */ declare function dispatchEvent(event: WorkerGlobalScopeEventMap[keyof WorkerGlobalScopeEventMap]): boolean; /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Window/btoa) */ declare function btoa(data: string): string; /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Window/atob) */ declare function atob(data: string): string; /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Window/setTimeout) */ declare function setTimeout(callback: (...args: any[]) => void, msDelay?: number): number; /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Window/setTimeout) */ declare function setTimeout(callback: (...args: Args) => void, msDelay?: number, ...args: Args): number; /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Window/clearTimeout) */ declare function clearTimeout(timeoutId: number | null): void; /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Window/setInterval) */ declare function setInterval(callback: (...args: any[]) => void, msDelay?: number): number; /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Window/setInterval) */ declare function setInterval(callback: (...args: Args) => void, msDelay?: number, ...args: Args): number; /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Window/clearInterval) */ declare function clearInterval(timeoutId: number | null): void; /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Window/queueMicrotask) */ declare function queueMicrotask(task: Function): void; /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Window/structuredClone) */ declare function structuredClone(value: T, options?: StructuredSerializeOptions): T; /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Window/reportError) */ declare function reportError(error: any): void; /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Window/fetch) */ declare function fetch(input: RequestInfo | URL, init?: RequestInit): Promise; declare const self: ServiceWorkerGlobalScope; /** * The Web Crypto API provides a set of low-level functions for common cryptographic tasks. * The Workers runtime implements the full surface of this API, but with some differences in * the [supported algorithms](https://developers.cloudflare.com/workers/runtime-apis/web-crypto/#supported-algorithms) * compared to those implemented in most browsers. * * [Cloudflare Docs Reference](https://developers.cloudflare.com/workers/runtime-apis/web-crypto/) */ declare const crypto: Crypto; /** * The Cache API allows fine grained control of reading and writing from the Cloudflare global network cache. * * [Cloudflare Docs Reference](https://developers.cloudflare.com/workers/runtime-apis/cache/) */ declare const caches: CacheStorage; declare const scheduler: Scheduler; /** * The Workers runtime supports a subset of the Performance API, used to measure timing and performance, * as well as timing of subrequests and other operations. * * [Cloudflare Docs Reference](https://developers.cloudflare.com/workers/runtime-apis/performance/) */ declare const performance: Performance; declare const Cloudflare: Cloudflare; declare const origin: string; declare const navigator: Navigator; interface TestController { } interface ExecutionContext { waitUntil(promise: Promise): void; passThroughOnException(): void; readonly props: Props; } type ExportedHandlerFetchHandler = (request: Request>, env: Env, ctx: ExecutionContext) => Response | Promise; type ExportedHandlerTailHandler = (events: TraceItem[], env: Env, ctx: ExecutionContext) => void | Promise; type ExportedHandlerTraceHandler = (traces: TraceItem[], env: Env, ctx: ExecutionContext) => void | Promise; type ExportedHandlerTailStreamHandler = (event: TailStream.TailEvent, env: Env, ctx: ExecutionContext) => TailStream.TailEventHandlerType | Promise; type ExportedHandlerScheduledHandler = (controller: ScheduledController, env: Env, ctx: ExecutionContext) => void | Promise; type ExportedHandlerQueueHandler = (batch: MessageBatch, env: Env, ctx: ExecutionContext) => void | Promise; type ExportedHandlerTestHandler = (controller: TestController, env: Env, ctx: ExecutionContext) => void | Promise; interface ExportedHandler { fetch?: ExportedHandlerFetchHandler; tail?: ExportedHandlerTailHandler; trace?: ExportedHandlerTraceHandler; tailStream?: ExportedHandlerTailStreamHandler; scheduled?: ExportedHandlerScheduledHandler; test?: ExportedHandlerTestHandler; email?: EmailExportedHandler; queue?: ExportedHandlerQueueHandler; } interface StructuredSerializeOptions { transfer?: any[]; } /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/PromiseRejectionEvent) */ declare abstract class PromiseRejectionEvent extends Event { /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/PromiseRejectionEvent/promise) */ readonly promise: Promise; /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/PromiseRejectionEvent/reason) */ readonly reason: any; } declare abstract class Navigator { sendBeacon(url: string, body?: (ReadableStream | string | (ArrayBuffer | ArrayBufferView) | Blob | FormData | URLSearchParams | URLSearchParams)): boolean; readonly userAgent: string; readonly hardwareConcurrency: number; readonly language: string; readonly languages: string[]; } interface AlarmInvocationInfo { readonly isRetry: boolean; readonly retryCount: number; } interface Cloudflare { readonly compatibilityFlags: Record; } interface DurableObject { fetch(request: Request): Response | Promise; alarm?(alarmInfo?: AlarmInvocationInfo): void | Promise; webSocketMessage?(ws: WebSocket, message: string | ArrayBuffer): void | Promise; webSocketClose?(ws: WebSocket, code: number, reason: string, wasClean: boolean): void | Promise; webSocketError?(ws: WebSocket, error: unknown): void | Promise; } type DurableObjectStub = Fetcher & { readonly id: DurableObjectId; readonly name?: string; }; interface DurableObjectId { toString(): string; equals(other: DurableObjectId): boolean; readonly name?: string; } declare abstract class DurableObjectNamespace { newUniqueId(options?: DurableObjectNamespaceNewUniqueIdOptions): DurableObjectId; idFromName(name: string): DurableObjectId; idFromString(id: string): DurableObjectId; get(id: DurableObjectId, options?: DurableObjectNamespaceGetDurableObjectOptions): DurableObjectStub; getByName(name: string, options?: DurableObjectNamespaceGetDurableObjectOptions): DurableObjectStub; jurisdiction(jurisdiction: DurableObjectJurisdiction): DurableObjectNamespace; } type DurableObjectJurisdiction = "eu" | "fedramp" | "fedramp-high"; interface DurableObjectNamespaceNewUniqueIdOptions { jurisdiction?: DurableObjectJurisdiction; } type DurableObjectLocationHint = "wnam" | "enam" | "sam" | "weur" | "eeur" | "apac" | "oc" | "afr" | "me"; interface DurableObjectNamespaceGetDurableObjectOptions { locationHint?: DurableObjectLocationHint; } interface DurableObjectClass<_T extends Rpc.DurableObjectBranded | undefined = undefined> { } interface DurableObjectState { waitUntil(promise: Promise): void; readonly props: Props; readonly id: DurableObjectId; readonly storage: DurableObjectStorage; container?: Container; blockConcurrencyWhile(callback: () => Promise): Promise; acceptWebSocket(ws: WebSocket, tags?: string[]): void; getWebSockets(tag?: string): WebSocket[]; setWebSocketAutoResponse(maybeReqResp?: WebSocketRequestResponsePair): void; getWebSocketAutoResponse(): WebSocketRequestResponsePair | null; getWebSocketAutoResponseTimestamp(ws: WebSocket): Date | null; setHibernatableWebSocketEventTimeout(timeoutMs?: number): void; getHibernatableWebSocketEventTimeout(): number | null; getTags(ws: WebSocket): string[]; abort(reason?: string): void; } interface DurableObjectTransaction { get(key: string, options?: DurableObjectGetOptions): Promise; get(keys: string[], options?: DurableObjectGetOptions): Promise>; list(options?: DurableObjectListOptions): Promise>; put(key: string, value: T, options?: DurableObjectPutOptions): Promise; put(entries: Record, options?: DurableObjectPutOptions): Promise; delete(key: string, options?: DurableObjectPutOptions): Promise; delete(keys: string[], options?: DurableObjectPutOptions): Promise; rollback(): void; getAlarm(options?: DurableObjectGetAlarmOptions): Promise; setAlarm(scheduledTime: number | Date, options?: DurableObjectSetAlarmOptions): Promise; deleteAlarm(options?: DurableObjectSetAlarmOptions): Promise; } interface DurableObjectStorage { get(key: string, options?: DurableObjectGetOptions): Promise; get(keys: string[], options?: DurableObjectGetOptions): Promise>; list(options?: DurableObjectListOptions): Promise>; put(key: string, value: T, options?: DurableObjectPutOptions): Promise; put(entries: Record, options?: DurableObjectPutOptions): Promise; delete(key: string, options?: DurableObjectPutOptions): Promise; delete(keys: string[], options?: DurableObjectPutOptions): Promise; deleteAll(options?: DurableObjectPutOptions): Promise; transaction(closure: (txn: DurableObjectTransaction) => Promise): Promise; getAlarm(options?: DurableObjectGetAlarmOptions): Promise; setAlarm(scheduledTime: number | Date, options?: DurableObjectSetAlarmOptions): Promise; deleteAlarm(options?: DurableObjectSetAlarmOptions): Promise; sync(): Promise; sql: SqlStorage; kv: SyncKvStorage; transactionSync(closure: () => T): T; getCurrentBookmark(): Promise; getBookmarkForTime(timestamp: number | Date): Promise; onNextSessionRestoreBookmark(bookmark: string): Promise; } interface DurableObjectListOptions { start?: string; startAfter?: string; end?: string; prefix?: string; reverse?: boolean; limit?: number; allowConcurrency?: boolean; noCache?: boolean; } interface DurableObjectGetOptions { allowConcurrency?: boolean; noCache?: boolean; } interface DurableObjectGetAlarmOptions { allowConcurrency?: boolean; } interface DurableObjectPutOptions { allowConcurrency?: boolean; allowUnconfirmed?: boolean; noCache?: boolean; } interface DurableObjectSetAlarmOptions { allowConcurrency?: boolean; allowUnconfirmed?: boolean; } declare class WebSocketRequestResponsePair { constructor(request: string, response: string); get request(): string; get response(): string; } interface AnalyticsEngineDataset { writeDataPoint(event?: AnalyticsEngineDataPoint): void; } interface AnalyticsEngineDataPoint { indexes?: ((ArrayBuffer | string) | null)[]; doubles?: number[]; blobs?: ((ArrayBuffer | string) | null)[]; } /** * An event which takes place in the DOM. * * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Event) */ declare class Event { constructor(type: string, init?: EventInit); /** * Returns the type of event, e.g. "click", "hashchange", or "submit". * * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Event/type) */ get type(): string; /** * Returns the event's phase, which is one of NONE, CAPTURING_PHASE, AT_TARGET, and BUBBLING_PHASE. * * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Event/eventPhase) */ get eventPhase(): number; /** * Returns true or false depending on how event was initialized. True if event invokes listeners past a ShadowRoot node that is the root of its target, and false otherwise. * * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Event/composed) */ get composed(): boolean; /** * Returns true or false depending on how event was initialized. True if event goes through its target's ancestors in reverse tree order, and false otherwise. * * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Event/bubbles) */ get bubbles(): boolean; /** * Returns true or false depending on how event was initialized. Its return value does not always carry meaning, but true can indicate that part of the operation during which event was dispatched, can be canceled by invoking the preventDefault() method. * * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Event/cancelable) */ get cancelable(): boolean; /** * Returns true if preventDefault() was invoked successfully to indicate cancelation, and false otherwise. * * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Event/defaultPrevented) */ get defaultPrevented(): boolean; /** * @deprecated * * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Event/returnValue) */ get returnValue(): boolean; /** * Returns the object whose event listener's callback is currently being invoked. * * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Event/currentTarget) */ get currentTarget(): EventTarget | undefined; /** * Returns the object to which event is dispatched (its target). * * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Event/target) */ get target(): EventTarget | undefined; /** * @deprecated * * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Event/srcElement) */ get srcElement(): EventTarget | undefined; /** * Returns the event's timestamp as the number of milliseconds measured relative to the time origin. * * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Event/timeStamp) */ get timeStamp(): number; /** * Returns true if event was dispatched by the user agent, and false otherwise. * * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Event/isTrusted) */ get isTrusted(): boolean; /** * @deprecated * * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Event/cancelBubble) */ get cancelBubble(): boolean; /** * @deprecated * * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Event/cancelBubble) */ set cancelBubble(value: boolean); /** * Invoking this method prevents event from reaching any registered event listeners after the current one finishes running and, when dispatched in a tree, also prevents event from reaching any other objects. * * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Event/stopImmediatePropagation) */ stopImmediatePropagation(): void; /** * If invoked when the cancelable attribute value is true, and while executing a listener for the event with passive set to false, signals to the operation that caused event to be dispatched that it needs to be canceled. * * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Event/preventDefault) */ preventDefault(): void; /** * When dispatched in a tree, invoking this method prevents event from reaching any objects other than the current object. * * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Event/stopPropagation) */ stopPropagation(): void; /** * Returns the invocation target objects of event's path (objects on which listeners will be invoked), except for any nodes in shadow trees of which the shadow root's mode is "closed" that are not reachable from event's currentTarget. * * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Event/composedPath) */ composedPath(): EventTarget[]; static readonly NONE: number; static readonly CAPTURING_PHASE: number; static readonly AT_TARGET: number; static readonly BUBBLING_PHASE: number; } interface EventInit { bubbles?: boolean; cancelable?: boolean; composed?: boolean; } type EventListener = (event: EventType) => void; interface EventListenerObject { handleEvent(event: EventType): void; } type EventListenerOrEventListenerObject = EventListener | EventListenerObject; /** * EventTarget is a DOM interface implemented by objects that can receive events and may have listeners for them. * * [MDN Reference](https://developer.mozilla.org/docs/Web/API/EventTarget) */ declare class EventTarget = Record> { constructor(); /** * Appends an event listener for events whose type attribute value is type. The callback argument sets the callback that will be invoked when the event is dispatched. * * The options argument sets listener-specific options. For compatibility this can be a boolean, in which case the method behaves exactly as if the value was specified as options's capture. * * When set to true, options's capture prevents callback from being invoked when the event's eventPhase attribute value is BUBBLING_PHASE. When false (or not present), callback will not be invoked when event's eventPhase attribute value is CAPTURING_PHASE. Either way, callback will be invoked if event's eventPhase attribute value is AT_TARGET. * * When set to true, options's passive indicates that the callback will not cancel the event by invoking preventDefault(). This is used to enable performance optimizations described in § 2.8 Observing event listeners. * * When set to true, options's once indicates that the callback will only be invoked once after which the event listener will be removed. * * If an AbortSignal is passed for options's signal, then the event listener will be removed when signal is aborted. * * The event listener is appended to target's event listener list and is not appended if it has the same type, callback, and capture. * * [MDN Reference](https://developer.mozilla.org/docs/Web/API/EventTarget/addEventListener) */ addEventListener(type: Type, handler: EventListenerOrEventListenerObject, options?: EventTargetAddEventListenerOptions | boolean): void; /** * Removes the event listener in target's event listener list with the same type, callback, and options. * * [MDN Reference](https://developer.mozilla.org/docs/Web/API/EventTarget/removeEventListener) */ removeEventListener(type: Type, handler: EventListenerOrEventListenerObject, options?: EventTargetEventListenerOptions | boolean): void; /** * Dispatches a synthetic event event to target and returns true if either event's cancelable attribute value is false or its preventDefault() method was not invoked, and false otherwise. * * [MDN Reference](https://developer.mozilla.org/docs/Web/API/EventTarget/dispatchEvent) */ dispatchEvent(event: EventMap[keyof EventMap]): boolean; } interface EventTargetEventListenerOptions { capture?: boolean; } interface EventTargetAddEventListenerOptions { capture?: boolean; passive?: boolean; once?: boolean; signal?: AbortSignal; } interface EventTargetHandlerObject { handleEvent: (event: Event) => any | undefined; } /** * A controller object that allows you to abort one or more DOM requests as and when desired. * * [MDN Reference](https://developer.mozilla.org/docs/Web/API/AbortController) */ declare class AbortController { constructor(); /** * Returns the AbortSignal object associated with this object. * * [MDN Reference](https://developer.mozilla.org/docs/Web/API/AbortController/signal) */ get signal(): AbortSignal; /** * Invoking this method will set this object's AbortSignal's aborted flag and signal to any observers that the associated activity is to be aborted. * * [MDN Reference](https://developer.mozilla.org/docs/Web/API/AbortController/abort) */ abort(reason?: any): void; } /** * A signal object that allows you to communicate with a DOM request (such as a Fetch) and abort it if required via an AbortController object. * * [MDN Reference](https://developer.mozilla.org/docs/Web/API/AbortSignal) */ declare abstract class AbortSignal extends EventTarget { /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/AbortSignal/abort_static) */ static abort(reason?: any): AbortSignal; /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/AbortSignal/timeout_static) */ static timeout(delay: number): AbortSignal; /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/AbortSignal/any_static) */ static any(signals: AbortSignal[]): AbortSignal; /** * Returns true if this AbortSignal's AbortController has signaled to abort, and false otherwise. * * [MDN Reference](https://developer.mozilla.org/docs/Web/API/AbortSignal/aborted) */ get aborted(): boolean; /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/AbortSignal/reason) */ get reason(): any; /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/AbortSignal/abort_event) */ get onabort(): any | null; /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/AbortSignal/abort_event) */ set onabort(value: any | null); /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/AbortSignal/throwIfAborted) */ throwIfAborted(): void; } interface Scheduler { wait(delay: number, maybeOptions?: SchedulerWaitOptions): Promise; } interface SchedulerWaitOptions { signal?: AbortSignal; } /** * Extends the lifetime of the install and activate events dispatched on the global scope as part of the service worker lifecycle. This ensures that any functional events (like FetchEvent) are not dispatched until it upgrades database schemas and deletes the outdated cache entries. * * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ExtendableEvent) */ declare abstract class ExtendableEvent extends Event { /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/ExtendableEvent/waitUntil) */ waitUntil(promise: Promise): void; } /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/CustomEvent) */ declare class CustomEvent extends Event { constructor(type: string, init?: CustomEventCustomEventInit); /** * Returns any custom data event was created with. Typically used for synthetic events. * * [MDN Reference](https://developer.mozilla.org/docs/Web/API/CustomEvent/detail) */ get detail(): T; } interface CustomEventCustomEventInit { bubbles?: boolean; cancelable?: boolean; composed?: boolean; detail?: any; } /** * A file-like object of immutable, raw data. Blobs represent data that isn't necessarily in a JavaScript-native format. The File interface is based on Blob, inheriting blob functionality and expanding it to support files on the user's system. * * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Blob) */ declare class Blob { constructor(type?: ((ArrayBuffer | ArrayBufferView) | string | Blob)[], options?: BlobOptions); /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Blob/size) */ get size(): number; /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Blob/type) */ get type(): string; /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Blob/slice) */ slice(start?: number, end?: number, type?: string): Blob; /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Blob/arrayBuffer) */ arrayBuffer(): Promise; /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Blob/bytes) */ bytes(): Promise; /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Blob/text) */ text(): Promise; /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Blob/stream) */ stream(): ReadableStream; } interface BlobOptions { type?: string; } /** * Provides information about files and allows JavaScript in a web page to access their content. * * [MDN Reference](https://developer.mozilla.org/docs/Web/API/File) */ declare class File extends Blob { constructor(bits: ((ArrayBuffer | ArrayBufferView) | string | Blob)[] | undefined, name: string, options?: FileOptions); /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/File/name) */ get name(): string; /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/File/lastModified) */ get lastModified(): number; } interface FileOptions { type?: string; lastModified?: number; } /** * The Cache API allows fine grained control of reading and writing from the Cloudflare global network cache. * * [Cloudflare Docs Reference](https://developers.cloudflare.com/workers/runtime-apis/cache/) */ declare abstract class CacheStorage { /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/CacheStorage/open) */ open(cacheName: string): Promise; readonly default: Cache; } /** * The Cache API allows fine grained control of reading and writing from the Cloudflare global network cache. * * [Cloudflare Docs Reference](https://developers.cloudflare.com/workers/runtime-apis/cache/) */ declare abstract class Cache { /* [Cloudflare Docs Reference](https://developers.cloudflare.com/workers/runtime-apis/cache/#delete) */ delete(request: RequestInfo | URL, options?: CacheQueryOptions): Promise; /* [Cloudflare Docs Reference](https://developers.cloudflare.com/workers/runtime-apis/cache/#match) */ match(request: RequestInfo | URL, options?: CacheQueryOptions): Promise; /* [Cloudflare Docs Reference](https://developers.cloudflare.com/workers/runtime-apis/cache/#put) */ put(request: RequestInfo | URL, response: Response): Promise; } interface CacheQueryOptions { ignoreMethod?: boolean; } /** * The Web Crypto API provides a set of low-level functions for common cryptographic tasks. * The Workers runtime implements the full surface of this API, but with some differences in * the [supported algorithms](https://developers.cloudflare.com/workers/runtime-apis/web-crypto/#supported-algorithms) * compared to those implemented in most browsers. * * [Cloudflare Docs Reference](https://developers.cloudflare.com/workers/runtime-apis/web-crypto/) */ declare abstract class Crypto { /** * Available only in secure contexts. * * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Crypto/subtle) */ get subtle(): SubtleCrypto; /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Crypto/getRandomValues) */ getRandomValues(buffer: T): T; /** * Available only in secure contexts. * * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Crypto/randomUUID) */ randomUUID(): string; DigestStream: typeof DigestStream; } /** * This Web Crypto API interface provides a number of low-level cryptographic functions. It is accessed via the Crypto.subtle properties available in a window context (via Window.crypto). * Available only in secure contexts. * * [MDN Reference](https://developer.mozilla.org/docs/Web/API/SubtleCrypto) */ declare abstract class SubtleCrypto { /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/SubtleCrypto/encrypt) */ encrypt(algorithm: string | SubtleCryptoEncryptAlgorithm, key: CryptoKey, plainText: ArrayBuffer | ArrayBufferView): Promise; /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/SubtleCrypto/decrypt) */ decrypt(algorithm: string | SubtleCryptoEncryptAlgorithm, key: CryptoKey, cipherText: ArrayBuffer | ArrayBufferView): Promise; /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/SubtleCrypto/sign) */ sign(algorithm: string | SubtleCryptoSignAlgorithm, key: CryptoKey, data: ArrayBuffer | ArrayBufferView): Promise; /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/SubtleCrypto/verify) */ verify(algorithm: string | SubtleCryptoSignAlgorithm, key: CryptoKey, signature: ArrayBuffer | ArrayBufferView, data: ArrayBuffer | ArrayBufferView): Promise; /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/SubtleCrypto/digest) */ digest(algorithm: string | SubtleCryptoHashAlgorithm, data: ArrayBuffer | ArrayBufferView): Promise; /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/SubtleCrypto/generateKey) */ generateKey(algorithm: string | SubtleCryptoGenerateKeyAlgorithm, extractable: boolean, keyUsages: string[]): Promise; /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/SubtleCrypto/deriveKey) */ deriveKey(algorithm: string | SubtleCryptoDeriveKeyAlgorithm, baseKey: CryptoKey, derivedKeyAlgorithm: string | SubtleCryptoImportKeyAlgorithm, extractable: boolean, keyUsages: string[]): Promise; /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/SubtleCrypto/deriveBits) */ deriveBits(algorithm: string | SubtleCryptoDeriveKeyAlgorithm, baseKey: CryptoKey, length?: number | null): Promise; /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/SubtleCrypto/importKey) */ importKey(format: string, keyData: (ArrayBuffer | ArrayBufferView) | JsonWebKey, algorithm: string | SubtleCryptoImportKeyAlgorithm, extractable: boolean, keyUsages: string[]): Promise; /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/SubtleCrypto/exportKey) */ exportKey(format: string, key: CryptoKey): Promise; /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/SubtleCrypto/wrapKey) */ wrapKey(format: string, key: CryptoKey, wrappingKey: CryptoKey, wrapAlgorithm: string | SubtleCryptoEncryptAlgorithm): Promise; /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/SubtleCrypto/unwrapKey) */ unwrapKey(format: string, wrappedKey: ArrayBuffer | ArrayBufferView, unwrappingKey: CryptoKey, unwrapAlgorithm: string | SubtleCryptoEncryptAlgorithm, unwrappedKeyAlgorithm: string | SubtleCryptoImportKeyAlgorithm, extractable: boolean, keyUsages: string[]): Promise; timingSafeEqual(a: ArrayBuffer | ArrayBufferView, b: ArrayBuffer | ArrayBufferView): boolean; } /** * The CryptoKey dictionary of the Web Crypto API represents a cryptographic key. * Available only in secure contexts. * * [MDN Reference](https://developer.mozilla.org/docs/Web/API/CryptoKey) */ declare abstract class CryptoKey { /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/CryptoKey/type) */ readonly type: string; /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/CryptoKey/extractable) */ readonly extractable: boolean; /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/CryptoKey/algorithm) */ readonly algorithm: CryptoKeyKeyAlgorithm | CryptoKeyAesKeyAlgorithm | CryptoKeyHmacKeyAlgorithm | CryptoKeyRsaKeyAlgorithm | CryptoKeyEllipticKeyAlgorithm | CryptoKeyArbitraryKeyAlgorithm; /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/CryptoKey/usages) */ readonly usages: string[]; } interface CryptoKeyPair { publicKey: CryptoKey; privateKey: CryptoKey; } interface JsonWebKey { kty: string; use?: string; key_ops?: string[]; alg?: string; ext?: boolean; crv?: string; x?: string; y?: string; d?: string; n?: string; e?: string; p?: string; q?: string; dp?: string; dq?: string; qi?: string; oth?: RsaOtherPrimesInfo[]; k?: string; } interface RsaOtherPrimesInfo { r?: string; d?: string; t?: string; } interface SubtleCryptoDeriveKeyAlgorithm { name: string; salt?: (ArrayBuffer | ArrayBufferView); iterations?: number; hash?: (string | SubtleCryptoHashAlgorithm); $public?: CryptoKey; info?: (ArrayBuffer | ArrayBufferView); } interface SubtleCryptoEncryptAlgorithm { name: string; iv?: (ArrayBuffer | ArrayBufferView); additionalData?: (ArrayBuffer | ArrayBufferView); tagLength?: number; counter?: (ArrayBuffer | ArrayBufferView); length?: number; label?: (ArrayBuffer | ArrayBufferView); } interface SubtleCryptoGenerateKeyAlgorithm { name: string; hash?: (string | SubtleCryptoHashAlgorithm); modulusLength?: number; publicExponent?: (ArrayBuffer | ArrayBufferView); length?: number; namedCurve?: string; } interface SubtleCryptoHashAlgorithm { name: string; } interface SubtleCryptoImportKeyAlgorithm { name: string; hash?: (string | SubtleCryptoHashAlgorithm); length?: number; namedCurve?: string; compressed?: boolean; } interface SubtleCryptoSignAlgorithm { name: string; hash?: (string | SubtleCryptoHashAlgorithm); dataLength?: number; saltLength?: number; } interface CryptoKeyKeyAlgorithm { name: string; } interface CryptoKeyAesKeyAlgorithm { name: string; length: number; } interface CryptoKeyHmacKeyAlgorithm { name: string; hash: CryptoKeyKeyAlgorithm; length: number; } interface CryptoKeyRsaKeyAlgorithm { name: string; modulusLength: number; publicExponent: ArrayBuffer | ArrayBufferView; hash?: CryptoKeyKeyAlgorithm; } interface CryptoKeyEllipticKeyAlgorithm { name: string; namedCurve: string; } interface CryptoKeyArbitraryKeyAlgorithm { name: string; hash?: CryptoKeyKeyAlgorithm; namedCurve?: string; length?: number; } declare class DigestStream extends WritableStream { constructor(algorithm: string | SubtleCryptoHashAlgorithm); readonly digest: Promise; get bytesWritten(): number | bigint; } /** * A decoder for a specific method, that is a specific character encoding, like utf-8, iso-8859-2, koi8, cp1261, gbk, etc. A decoder takes a stream of bytes as input and emits a stream of code points. For a more scalable, non-native library, see StringView – a C-like representation of strings based on typed arrays. * * [MDN Reference](https://developer.mozilla.org/docs/Web/API/TextDecoder) */ declare class TextDecoder { constructor(label?: string, options?: TextDecoderConstructorOptions); /** * Returns the result of running encoding's decoder. The method can be invoked zero or more times with options's stream set to true, and then once without options's stream (or set to false), to process a fragmented input. If the invocation without options's stream (or set to false) has no input, it's clearest to omit both arguments. * * ``` * var string = "", decoder = new TextDecoder(encoding), buffer; * while(buffer = next_chunk()) { * string += decoder.decode(buffer, {stream:true}); * } * string += decoder.decode(); // end-of-queue * ``` * * If the error mode is "fatal" and encoding's decoder returns error, throws a TypeError. * * [MDN Reference](https://developer.mozilla.org/docs/Web/API/TextDecoder/decode) */ decode(input?: (ArrayBuffer | ArrayBufferView), options?: TextDecoderDecodeOptions): string; get encoding(): string; get fatal(): boolean; get ignoreBOM(): boolean; } /** * TextEncoder takes a stream of code points as input and emits a stream of bytes. For a more scalable, non-native library, see StringView – a C-like representation of strings based on typed arrays. * * [MDN Reference](https://developer.mozilla.org/docs/Web/API/TextEncoder) */ declare class TextEncoder { constructor(); /** * Returns the result of running UTF-8's encoder. * * [MDN Reference](https://developer.mozilla.org/docs/Web/API/TextEncoder/encode) */ encode(input?: string): Uint8Array; /** * Runs the UTF-8 encoder on source, stores the result of that operation into destination, and returns the progress made as an object wherein read is the number of converted code units of source and written is the number of bytes modified in destination. * * [MDN Reference](https://developer.mozilla.org/docs/Web/API/TextEncoder/encodeInto) */ encodeInto(input: string, buffer: ArrayBuffer | ArrayBufferView): TextEncoderEncodeIntoResult; get encoding(): string; } interface TextDecoderConstructorOptions { fatal: boolean; ignoreBOM: boolean; } interface TextDecoderDecodeOptions { stream: boolean; } interface TextEncoderEncodeIntoResult { read: number; written: number; } /** * Events providing information related to errors in scripts or in files. * * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ErrorEvent) */ declare class ErrorEvent extends Event { constructor(type: string, init?: ErrorEventErrorEventInit); /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/ErrorEvent/filename) */ get filename(): string; /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/ErrorEvent/message) */ get message(): string; /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/ErrorEvent/lineno) */ get lineno(): number; /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/ErrorEvent/colno) */ get colno(): number; /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/ErrorEvent/error) */ get error(): any; } interface ErrorEventErrorEventInit { message?: string; filename?: string; lineno?: number; colno?: number; error?: any; } /** * A message received by a target object. * * [MDN Reference](https://developer.mozilla.org/docs/Web/API/MessageEvent) */ declare class MessageEvent extends Event { constructor(type: string, initializer: MessageEventInit); /** * Returns the data of the message. * * [MDN Reference](https://developer.mozilla.org/docs/Web/API/MessageEvent/data) */ readonly data: any; /** * Returns the origin of the message, for server-sent events and cross-document messaging. * * [MDN Reference](https://developer.mozilla.org/docs/Web/API/MessageEvent/origin) */ readonly origin: string | null; /** * Returns the last event ID string, for server-sent events. * * [MDN Reference](https://developer.mozilla.org/docs/Web/API/MessageEvent/lastEventId) */ readonly lastEventId: string; /** * Returns the WindowProxy of the source window, for cross-document messaging, and the MessagePort being attached, in the connect event fired at SharedWorkerGlobalScope objects. * * [MDN Reference](https://developer.mozilla.org/docs/Web/API/MessageEvent/source) */ readonly source: MessagePort | null; /** * Returns the MessagePort array sent with the message, for cross-document messaging and channel messaging. * * [MDN Reference](https://developer.mozilla.org/docs/Web/API/MessageEvent/ports) */ readonly ports: MessagePort[]; } interface MessageEventInit { data: ArrayBuffer | string; } /** * Provides a way to easily construct a set of key/value pairs representing form fields and their values, which can then be easily sent using the XMLHttpRequest.send() method. It uses the same format a form would use if the encoding type were set to "multipart/form-data". * * [MDN Reference](https://developer.mozilla.org/docs/Web/API/FormData) */ declare class FormData { constructor(); /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/FormData/append) */ append(name: string, value: string): void; /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/FormData/append) */ append(name: string, value: Blob, filename?: string): void; /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/FormData/delete) */ delete(name: string): void; /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/FormData/get) */ get(name: string): (File | string) | null; /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/FormData/getAll) */ getAll(name: string): (File | string)[]; /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/FormData/has) */ has(name: string): boolean; /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/FormData/set) */ set(name: string, value: string): void; /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/FormData/set) */ set(name: string, value: Blob, filename?: string): void; /* Returns an array of key, value pairs for every entry in the list. */ entries(): IterableIterator<[ key: string, value: File | string ]>; /* Returns a list of keys in the list. */ keys(): IterableIterator; /* Returns a list of values in the list. */ values(): IterableIterator<(File | string)>; forEach(callback: (this: This, value: File | string, key: string, parent: FormData) => void, thisArg?: This): void; [Symbol.iterator](): IterableIterator<[ key: string, value: File | string ]>; } interface ContentOptions { html?: boolean; } declare class HTMLRewriter { constructor(); on(selector: string, handlers: HTMLRewriterElementContentHandlers): HTMLRewriter; onDocument(handlers: HTMLRewriterDocumentContentHandlers): HTMLRewriter; transform(response: Response): Response; } interface HTMLRewriterElementContentHandlers { element?(element: Element): void | Promise; comments?(comment: Comment): void | Promise; text?(element: Text): void | Promise; } interface HTMLRewriterDocumentContentHandlers { doctype?(doctype: Doctype): void | Promise; comments?(comment: Comment): void | Promise; text?(text: Text): void | Promise; end?(end: DocumentEnd): void | Promise; } interface Doctype { readonly name: string | null; readonly publicId: string | null; readonly systemId: string | null; } interface Element { tagName: string; readonly attributes: IterableIterator; readonly removed: boolean; readonly namespaceURI: string; getAttribute(name: string): string | null; hasAttribute(name: string): boolean; setAttribute(name: string, value: string): Element; removeAttribute(name: string): Element; before(content: string | ReadableStream | Response, options?: ContentOptions): Element; after(content: string | ReadableStream | Response, options?: ContentOptions): Element; prepend(content: string | ReadableStream | Response, options?: ContentOptions): Element; append(content: string | ReadableStream | Response, options?: ContentOptions): Element; replace(content: string | ReadableStream | Response, options?: ContentOptions): Element; remove(): Element; removeAndKeepContent(): Element; setInnerContent(content: string | ReadableStream | Response, options?: ContentOptions): Element; onEndTag(handler: (tag: EndTag) => void | Promise): void; } interface EndTag { name: string; before(content: string | ReadableStream | Response, options?: ContentOptions): EndTag; after(content: string | ReadableStream | Response, options?: ContentOptions): EndTag; remove(): EndTag; } interface Comment { text: string; readonly removed: boolean; before(content: string, options?: ContentOptions): Comment; after(content: string, options?: ContentOptions): Comment; replace(content: string, options?: ContentOptions): Comment; remove(): Comment; } interface Text { readonly text: string; readonly lastInTextNode: boolean; readonly removed: boolean; before(content: string | ReadableStream | Response, options?: ContentOptions): Text; after(content: string | ReadableStream | Response, options?: ContentOptions): Text; replace(content: string | ReadableStream | Response, options?: ContentOptions): Text; remove(): Text; } interface DocumentEnd { append(content: string, options?: ContentOptions): DocumentEnd; } /** * This is the event type for fetch events dispatched on the service worker global scope. It contains information about the fetch, including the request and how the receiver will treat the response. It provides the event.respondWith() method, which allows us to provide a response to this fetch. * * [MDN Reference](https://developer.mozilla.org/docs/Web/API/FetchEvent) */ declare abstract class FetchEvent extends ExtendableEvent { /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/FetchEvent/request) */ readonly request: Request; /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/FetchEvent/respondWith) */ respondWith(promise: Response | Promise): void; passThroughOnException(): void; } type HeadersInit = Headers | Iterable> | Record; /** * This Fetch API interface allows you to perform various actions on HTTP request and response headers. These actions include retrieving, setting, adding to, and removing. A Headers object has an associated header list, which is initially empty and consists of zero or more name and value pairs.  You can add to this using methods like append() (see Examples.) In all methods of this interface, header names are matched by case-insensitive byte sequence. * * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Headers) */ declare class Headers { constructor(init?: HeadersInit); /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Headers/get) */ get(name: string): string | null; getAll(name: string): string[]; /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Headers/getSetCookie) */ getSetCookie(): string[]; /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Headers/has) */ has(name: string): boolean; /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Headers/set) */ set(name: string, value: string): void; /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Headers/append) */ append(name: string, value: string): void; /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Headers/delete) */ delete(name: string): void; forEach(callback: (this: This, value: string, key: string, parent: Headers) => void, thisArg?: This): void; /* Returns an iterator allowing to go through all key/value pairs contained in this object. */ entries(): IterableIterator<[ key: string, value: string ]>; /* Returns an iterator allowing to go through all keys of the key/value pairs contained in this object. */ keys(): IterableIterator; /* Returns an iterator allowing to go through all values of the key/value pairs contained in this object. */ values(): IterableIterator; [Symbol.iterator](): IterableIterator<[ key: string, value: string ]>; } type BodyInit = ReadableStream | string | ArrayBuffer | ArrayBufferView | Blob | URLSearchParams | FormData; declare abstract class Body { /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Request/body) */ get body(): ReadableStream | null; /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Request/bodyUsed) */ get bodyUsed(): boolean; /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Request/arrayBuffer) */ arrayBuffer(): Promise; /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Request/bytes) */ bytes(): Promise; /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Request/text) */ text(): Promise; /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Request/json) */ json(): Promise; /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Request/formData) */ formData(): Promise; /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Request/blob) */ blob(): Promise; } /** * This Fetch API interface represents the response to a request. * * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Response) */ declare var Response: { prototype: Response; new (body?: BodyInit | null, init?: ResponseInit): Response; error(): Response; redirect(url: string, status?: number): Response; json(any: any, maybeInit?: (ResponseInit | Response)): Response; }; /** * This Fetch API interface represents the response to a request. * * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Response) */ interface Response extends Body { /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Response/clone) */ clone(): Response; /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Response/status) */ status: number; /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Response/statusText) */ statusText: string; /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Response/headers) */ headers: Headers; /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Response/ok) */ ok: boolean; /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Response/redirected) */ redirected: boolean; /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Response/url) */ url: string; webSocket: WebSocket | null; cf: any | undefined; /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Response/type) */ type: "default" | "error"; } interface ResponseInit { status?: number; statusText?: string; headers?: HeadersInit; cf?: any; webSocket?: (WebSocket | null); encodeBody?: "automatic" | "manual"; } type RequestInfo> = Request | string; /** * This Fetch API interface represents a resource request. * * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Request) */ declare var Request: { prototype: Request; new >(input: RequestInfo | URL, init?: RequestInit): Request; }; /** * This Fetch API interface represents a resource request. * * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Request) */ interface Request> extends Body { /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Request/clone) */ clone(): Request; /** * Returns request's HTTP method, which is "GET" by default. * * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Request/method) */ method: string; /** * Returns the URL of request as a string. * * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Request/url) */ url: string; /** * Returns a Headers object consisting of the headers associated with request. Note that headers added in the network layer by the user agent will not be accounted for in this object, e.g., the "Host" header. * * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Request/headers) */ headers: Headers; /** * Returns the redirect mode associated with request, which is a string indicating how redirects for the request will be handled during fetching. A request will follow redirects by default. * * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Request/redirect) */ redirect: string; fetcher: Fetcher | null; /** * Returns the signal associated with request, which is an AbortSignal object indicating whether or not request has been aborted, and its abort event handler. * * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Request/signal) */ signal: AbortSignal; cf: Cf | undefined; /** * Returns request's subresource integrity metadata, which is a cryptographic hash of the resource being fetched. Its value consists of multiple hashes separated by whitespace. [SRI] * * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Request/integrity) */ integrity: string; /** * Returns a boolean indicating whether or not request can outlive the global in which it was created. * * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Request/keepalive) */ keepalive: boolean; /** * Returns the cache mode associated with request, which is a string indicating how the request will interact with the browser's cache when fetching. * * [MDN Reference](https://developer.mozilla.org/docs/Web/API/Request/cache) */ cache?: "no-store" | "no-cache"; } interface RequestInit { /* A string to set request's method. */ method?: string; /* A Headers object, an object literal, or an array of two-item arrays to set request's headers. */ headers?: HeadersInit; /* A BodyInit object or null to set request's body. */ body?: BodyInit | null; /* A string indicating whether request follows redirects, results in an error upon encountering a redirect, or returns the redirect (in an opaque fashion). Sets request's redirect. */ redirect?: string; fetcher?: (Fetcher | null); cf?: Cf; /* A string indicating how the request will interact with the browser's cache to set request's cache. */ cache?: "no-store" | "no-cache"; /* A cryptographic hash of the resource to be fetched by request. Sets request's integrity. */ integrity?: string; /* An AbortSignal to set request's signal. */ signal?: (AbortSignal | null); encodeResponseBody?: "automatic" | "manual"; } type Service Rpc.WorkerEntrypointBranded) | Rpc.WorkerEntrypointBranded | ExportedHandler | undefined = undefined> = T extends new (...args: any[]) => Rpc.WorkerEntrypointBranded ? Fetcher> : T extends Rpc.WorkerEntrypointBranded ? Fetcher : T extends Exclude ? never : Fetcher; type Fetcher = (T extends Rpc.EntrypointBranded ? Rpc.Provider : unknown) & { fetch(input: RequestInfo | URL, init?: RequestInit): Promise; connect(address: SocketAddress | string, options?: SocketOptions): Socket; }; interface KVNamespaceListKey { name: Key; expiration?: number; metadata?: Metadata; } type KVNamespaceListResult = { list_complete: false; keys: KVNamespaceListKey[]; cursor: string; cacheStatus: string | null; } | { list_complete: true; keys: KVNamespaceListKey[]; cacheStatus: string | null; }; interface KVNamespace { get(key: Key, options?: Partial>): Promise; get(key: Key, type: "text"): Promise; get(key: Key, type: "json"): Promise; get(key: Key, type: "arrayBuffer"): Promise; get(key: Key, type: "stream"): Promise; get(key: Key, options?: KVNamespaceGetOptions<"text">): Promise; get(key: Key, options?: KVNamespaceGetOptions<"json">): Promise; get(key: Key, options?: KVNamespaceGetOptions<"arrayBuffer">): Promise; get(key: Key, options?: KVNamespaceGetOptions<"stream">): Promise; get(key: Array, type: "text"): Promise>; get(key: Array, type: "json"): Promise>; get(key: Array, options?: Partial>): Promise>; get(key: Array, options?: KVNamespaceGetOptions<"text">): Promise>; get(key: Array, options?: KVNamespaceGetOptions<"json">): Promise>; list(options?: KVNamespaceListOptions): Promise>; put(key: Key, value: string | ArrayBuffer | ArrayBufferView | ReadableStream, options?: KVNamespacePutOptions): Promise; getWithMetadata(key: Key, options?: Partial>): Promise>; getWithMetadata(key: Key, type: "text"): Promise>; getWithMetadata(key: Key, type: "json"): Promise>; getWithMetadata(key: Key, type: "arrayBuffer"): Promise>; getWithMetadata(key: Key, type: "stream"): Promise>; getWithMetadata(key: Key, options: KVNamespaceGetOptions<"text">): Promise>; getWithMetadata(key: Key, options: KVNamespaceGetOptions<"json">): Promise>; getWithMetadata(key: Key, options: KVNamespaceGetOptions<"arrayBuffer">): Promise>; getWithMetadata(key: Key, options: KVNamespaceGetOptions<"stream">): Promise>; getWithMetadata(key: Array, type: "text"): Promise>>; getWithMetadata(key: Array, type: "json"): Promise>>; getWithMetadata(key: Array, options?: Partial>): Promise>>; getWithMetadata(key: Array, options?: KVNamespaceGetOptions<"text">): Promise>>; getWithMetadata(key: Array, options?: KVNamespaceGetOptions<"json">): Promise>>; delete(key: Key): Promise; } interface KVNamespaceListOptions { limit?: number; prefix?: (string | null); cursor?: (string | null); } interface KVNamespaceGetOptions { type: Type; cacheTtl?: number; } interface KVNamespacePutOptions { expiration?: number; expirationTtl?: number; metadata?: (any | null); } interface KVNamespaceGetWithMetadataResult { value: Value | null; metadata: Metadata | null; cacheStatus: string | null; } type QueueContentType = "text" | "bytes" | "json" | "v8"; interface Queue { send(message: Body, options?: QueueSendOptions): Promise; sendBatch(messages: Iterable>, options?: QueueSendBatchOptions): Promise; } interface QueueSendOptions { contentType?: QueueContentType; delaySeconds?: number; } interface QueueSendBatchOptions { delaySeconds?: number; } interface MessageSendRequest { body: Body; contentType?: QueueContentType; delaySeconds?: number; } interface QueueRetryOptions { delaySeconds?: number; } interface Message { readonly id: string; readonly timestamp: Date; readonly body: Body; readonly attempts: number; retry(options?: QueueRetryOptions): void; ack(): void; } interface QueueEvent extends ExtendableEvent { readonly messages: readonly Message[]; readonly queue: string; retryAll(options?: QueueRetryOptions): void; ackAll(): void; } interface MessageBatch { readonly messages: readonly Message[]; readonly queue: string; retryAll(options?: QueueRetryOptions): void; ackAll(): void; } interface R2Error extends Error { readonly name: string; readonly code: number; readonly message: string; readonly action: string; readonly stack: any; } interface R2ListOptions { limit?: number; prefix?: string; cursor?: string; delimiter?: string; startAfter?: string; include?: ("httpMetadata" | "customMetadata")[]; } declare abstract class R2Bucket { head(key: string): Promise; get(key: string, options: R2GetOptions & { onlyIf: R2Conditional | Headers; }): Promise; get(key: string, options?: R2GetOptions): Promise; put(key: string, value: ReadableStream | ArrayBuffer | ArrayBufferView | string | null | Blob, options?: R2PutOptions & { onlyIf: R2Conditional | Headers; }): Promise; put(key: string, value: ReadableStream | ArrayBuffer | ArrayBufferView | string | null | Blob, options?: R2PutOptions): Promise; createMultipartUpload(key: string, options?: R2MultipartOptions): Promise; resumeMultipartUpload(key: string, uploadId: string): R2MultipartUpload; delete(keys: string | string[]): Promise; list(options?: R2ListOptions): Promise; } interface R2MultipartUpload { readonly key: string; readonly uploadId: string; uploadPart(partNumber: number, value: ReadableStream | (ArrayBuffer | ArrayBufferView) | string | Blob, options?: R2UploadPartOptions): Promise; abort(): Promise; complete(uploadedParts: R2UploadedPart[]): Promise; } interface R2UploadedPart { partNumber: number; etag: string; } declare abstract class R2Object { readonly key: string; readonly version: string; readonly size: number; readonly etag: string; readonly httpEtag: string; readonly checksums: R2Checksums; readonly uploaded: Date; readonly httpMetadata?: R2HTTPMetadata; readonly customMetadata?: Record; readonly range?: R2Range; readonly storageClass: string; readonly ssecKeyMd5?: string; writeHttpMetadata(headers: Headers): void; } interface R2ObjectBody extends R2Object { get body(): ReadableStream; get bodyUsed(): boolean; arrayBuffer(): Promise; bytes(): Promise; text(): Promise; json(): Promise; blob(): Promise; } type R2Range = { offset: number; length?: number; } | { offset?: number; length: number; } | { suffix: number; }; interface R2Conditional { etagMatches?: string; etagDoesNotMatch?: string; uploadedBefore?: Date; uploadedAfter?: Date; secondsGranularity?: boolean; } interface R2GetOptions { onlyIf?: (R2Conditional | Headers); range?: (R2Range | Headers); ssecKey?: (ArrayBuffer | string); } interface R2PutOptions { onlyIf?: (R2Conditional | Headers); httpMetadata?: (R2HTTPMetadata | Headers); customMetadata?: Record; md5?: ((ArrayBuffer | ArrayBufferView) | string); sha1?: ((ArrayBuffer | ArrayBufferView) | string); sha256?: ((ArrayBuffer | ArrayBufferView) | string); sha384?: ((ArrayBuffer | ArrayBufferView) | string); sha512?: ((ArrayBuffer | ArrayBufferView) | string); storageClass?: string; ssecKey?: (ArrayBuffer | string); } interface R2MultipartOptions { httpMetadata?: (R2HTTPMetadata | Headers); customMetadata?: Record; storageClass?: string; ssecKey?: (ArrayBuffer | string); } interface R2Checksums { readonly md5?: ArrayBuffer; readonly sha1?: ArrayBuffer; readonly sha256?: ArrayBuffer; readonly sha384?: ArrayBuffer; readonly sha512?: ArrayBuffer; toJSON(): R2StringChecksums; } interface R2StringChecksums { md5?: string; sha1?: string; sha256?: string; sha384?: string; sha512?: string; } interface R2HTTPMetadata { contentType?: string; contentLanguage?: string; contentDisposition?: string; contentEncoding?: string; cacheControl?: string; cacheExpiry?: Date; } type R2Objects = { objects: R2Object[]; delimitedPrefixes: string[]; } & ({ truncated: true; cursor: string; } | { truncated: false; }); interface R2UploadPartOptions { ssecKey?: (ArrayBuffer | string); } declare abstract class ScheduledEvent extends ExtendableEvent { readonly scheduledTime: number; readonly cron: string; noRetry(): void; } interface ScheduledController { readonly scheduledTime: number; readonly cron: string; noRetry(): void; } interface QueuingStrategy { highWaterMark?: (number | bigint); size?: (chunk: T) => number | bigint; } interface UnderlyingSink { type?: string; start?: (controller: WritableStreamDefaultController) => void | Promise; write?: (chunk: W, controller: WritableStreamDefaultController) => void | Promise; abort?: (reason: any) => void | Promise; close?: () => void | Promise; } interface UnderlyingByteSource { type: "bytes"; autoAllocateChunkSize?: number; start?: (controller: ReadableByteStreamController) => void | Promise; pull?: (controller: ReadableByteStreamController) => void | Promise; cancel?: (reason: any) => void | Promise; } interface UnderlyingSource { type?: "" | undefined; start?: (controller: ReadableStreamDefaultController) => void | Promise; pull?: (controller: ReadableStreamDefaultController) => void | Promise; cancel?: (reason: any) => void | Promise; expectedLength?: (number | bigint); } interface Transformer { readableType?: string; writableType?: string; start?: (controller: TransformStreamDefaultController) => void | Promise; transform?: (chunk: I, controller: TransformStreamDefaultController) => void | Promise; flush?: (controller: TransformStreamDefaultController) => void | Promise; cancel?: (reason: any) => void | Promise; expectedLength?: number; } interface StreamPipeOptions { /** * Pipes this readable stream to a given writable stream destination. The way in which the piping process behaves under various error conditions can be customized with a number of passed options. It returns a promise that fulfills when the piping process completes successfully, or rejects if any errors were encountered. * * Piping a stream will lock it for the duration of the pipe, preventing any other consumer from acquiring a reader. * * Errors and closures of the source and destination streams propagate as follows: * * An error in this source readable stream will abort destination, unless preventAbort is truthy. The returned promise will be rejected with the source's error, or with any error that occurs during aborting the destination. * * An error in destination will cancel this source readable stream, unless preventCancel is truthy. The returned promise will be rejected with the destination's error, or with any error that occurs during canceling the source. * * When this source readable stream closes, destination will be closed, unless preventClose is truthy. The returned promise will be fulfilled once this process completes, unless an error is encountered while closing the destination, in which case it will be rejected with that error. * * If destination starts out closed or closing, this source readable stream will be canceled, unless preventCancel is true. The returned promise will be rejected with an error indicating piping to a closed stream failed, or with any error that occurs during canceling the source. * * The signal option can be set to an AbortSignal to allow aborting an ongoing pipe operation via the corresponding AbortController. In this case, this source readable stream will be canceled, and destination aborted, unless the respective options preventCancel or preventAbort are set. */ preventClose?: boolean; preventAbort?: boolean; preventCancel?: boolean; signal?: AbortSignal; } type ReadableStreamReadResult = { done: false; value: R; } | { done: true; value?: undefined; }; /** * This Streams API interface represents a readable stream of byte data. The Fetch API offers a concrete instance of a ReadableStream through the body property of a Response object. * * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableStream) */ interface ReadableStream { /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableStream/locked) */ get locked(): boolean; /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableStream/cancel) */ cancel(reason?: any): Promise; /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableStream/getReader) */ getReader(): ReadableStreamDefaultReader; /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableStream/getReader) */ getReader(options: ReadableStreamGetReaderOptions): ReadableStreamBYOBReader; /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableStream/pipeThrough) */ pipeThrough(transform: ReadableWritablePair, options?: StreamPipeOptions): ReadableStream; /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableStream/pipeTo) */ pipeTo(destination: WritableStream, options?: StreamPipeOptions): Promise; /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableStream/tee) */ tee(): [ ReadableStream, ReadableStream ]; values(options?: ReadableStreamValuesOptions): AsyncIterableIterator; [Symbol.asyncIterator](options?: ReadableStreamValuesOptions): AsyncIterableIterator; } /** * This Streams API interface represents a readable stream of byte data. The Fetch API offers a concrete instance of a ReadableStream through the body property of a Response object. * * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableStream) */ declare const ReadableStream: { prototype: ReadableStream; new (underlyingSource: UnderlyingByteSource, strategy?: QueuingStrategy): ReadableStream; new (underlyingSource?: UnderlyingSource, strategy?: QueuingStrategy): ReadableStream; }; /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableStreamDefaultReader) */ declare class ReadableStreamDefaultReader { constructor(stream: ReadableStream); get closed(): Promise; cancel(reason?: any): Promise; /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableStreamDefaultReader/read) */ read(): Promise>; /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableStreamDefaultReader/releaseLock) */ releaseLock(): void; } /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableStreamBYOBReader) */ declare class ReadableStreamBYOBReader { constructor(stream: ReadableStream); get closed(): Promise; cancel(reason?: any): Promise; /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableStreamBYOBReader/read) */ read(view: T): Promise>; /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableStreamBYOBReader/releaseLock) */ releaseLock(): void; readAtLeast(minElements: number, view: T): Promise>; } interface ReadableStreamBYOBReaderReadableStreamBYOBReaderReadOptions { min?: number; } interface ReadableStreamGetReaderOptions { /** * Creates a ReadableStreamBYOBReader and locks the stream to the new reader. * * This call behaves the same way as the no-argument variant, except that it only works on readable byte streams, i.e. streams which were constructed specifically with the ability to handle "bring your own buffer" reading. The returned BYOB reader provides the ability to directly read individual chunks from the stream via its read() method, into developer-supplied buffers, allowing more precise control over allocation. */ mode: "byob"; } /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableStreamBYOBRequest) */ declare abstract class ReadableStreamBYOBRequest { /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableStreamBYOBRequest/view) */ get view(): Uint8Array | null; /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableStreamBYOBRequest/respond) */ respond(bytesWritten: number): void; /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableStreamBYOBRequest/respondWithNewView) */ respondWithNewView(view: ArrayBuffer | ArrayBufferView): void; get atLeast(): number | null; } /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableStreamDefaultController) */ declare abstract class ReadableStreamDefaultController { /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableStreamDefaultController/desiredSize) */ get desiredSize(): number | null; /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableStreamDefaultController/close) */ close(): void; /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableStreamDefaultController/enqueue) */ enqueue(chunk?: R): void; /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableStreamDefaultController/error) */ error(reason: any): void; } /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableByteStreamController) */ declare abstract class ReadableByteStreamController { /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableByteStreamController/byobRequest) */ get byobRequest(): ReadableStreamBYOBRequest | null; /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableByteStreamController/desiredSize) */ get desiredSize(): number | null; /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableByteStreamController/close) */ close(): void; /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableByteStreamController/enqueue) */ enqueue(chunk: ArrayBuffer | ArrayBufferView): void; /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/ReadableByteStreamController/error) */ error(reason: any): void; } /** * This Streams API interface represents a controller allowing control of a WritableStream's state. When constructing a WritableStream, the underlying sink is given a corresponding WritableStreamDefaultController instance to manipulate. * * [MDN Reference](https://developer.mozilla.org/docs/Web/API/WritableStreamDefaultController) */ declare abstract class WritableStreamDefaultController { /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/WritableStreamDefaultController/signal) */ get signal(): AbortSignal; /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/WritableStreamDefaultController/error) */ error(reason?: any): void; } /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/TransformStreamDefaultController) */ declare abstract class TransformStreamDefaultController { /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/TransformStreamDefaultController/desiredSize) */ get desiredSize(): number | null; /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/TransformStreamDefaultController/enqueue) */ enqueue(chunk?: O): void; /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/TransformStreamDefaultController/error) */ error(reason: any): void; /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/TransformStreamDefaultController/terminate) */ terminate(): void; } interface ReadableWritablePair { /** * Provides a convenient, chainable way of piping this readable stream through a transform stream (or any other { writable, readable } pair). It simply pipes the stream into the writable side of the supplied pair, and returns the readable side for further use. * * Piping a stream will lock it for the duration of the pipe, preventing any other consumer from acquiring a reader. */ writable: WritableStream; readable: ReadableStream; } /** * This Streams API interface provides a standard abstraction for writing streaming data to a destination, known as a sink. This object comes with built-in backpressure and queuing. * * [MDN Reference](https://developer.mozilla.org/docs/Web/API/WritableStream) */ declare class WritableStream { constructor(underlyingSink?: UnderlyingSink, queuingStrategy?: QueuingStrategy); /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/WritableStream/locked) */ get locked(): boolean; /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/WritableStream/abort) */ abort(reason?: any): Promise; /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/WritableStream/close) */ close(): Promise; /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/WritableStream/getWriter) */ getWriter(): WritableStreamDefaultWriter; } /** * This Streams API interface is the object returned by WritableStream.getWriter() and once created locks the < writer to the WritableStream ensuring that no other streams can write to the underlying sink. * * [MDN Reference](https://developer.mozilla.org/docs/Web/API/WritableStreamDefaultWriter) */ declare class WritableStreamDefaultWriter { constructor(stream: WritableStream); /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/WritableStreamDefaultWriter/closed) */ get closed(): Promise; /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/WritableStreamDefaultWriter/ready) */ get ready(): Promise; /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/WritableStreamDefaultWriter/desiredSize) */ get desiredSize(): number | null; /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/WritableStreamDefaultWriter/abort) */ abort(reason?: any): Promise; /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/WritableStreamDefaultWriter/close) */ close(): Promise; /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/WritableStreamDefaultWriter/write) */ write(chunk?: W): Promise; /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/WritableStreamDefaultWriter/releaseLock) */ releaseLock(): void; } /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/TransformStream) */ declare class TransformStream { constructor(transformer?: Transformer, writableStrategy?: QueuingStrategy, readableStrategy?: QueuingStrategy); /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/TransformStream/readable) */ get readable(): ReadableStream; /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/TransformStream/writable) */ get writable(): WritableStream; } declare class FixedLengthStream extends IdentityTransformStream { constructor(expectedLength: number | bigint, queuingStrategy?: IdentityTransformStreamQueuingStrategy); } declare class IdentityTransformStream extends TransformStream { constructor(queuingStrategy?: IdentityTransformStreamQueuingStrategy); } interface IdentityTransformStreamQueuingStrategy { highWaterMark?: (number | bigint); } interface ReadableStreamValuesOptions { preventCancel?: boolean; } /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/CompressionStream) */ declare class CompressionStream extends TransformStream { constructor(format: "gzip" | "deflate" | "deflate-raw"); } /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/DecompressionStream) */ declare class DecompressionStream extends TransformStream { constructor(format: "gzip" | "deflate" | "deflate-raw"); } /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/TextEncoderStream) */ declare class TextEncoderStream extends TransformStream { constructor(); get encoding(): string; } /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/TextDecoderStream) */ declare class TextDecoderStream extends TransformStream { constructor(label?: string, options?: TextDecoderStreamTextDecoderStreamInit); get encoding(): string; get fatal(): boolean; get ignoreBOM(): boolean; } interface TextDecoderStreamTextDecoderStreamInit { fatal?: boolean; ignoreBOM?: boolean; } /** * This Streams API interface provides a built-in byte length queuing strategy that can be used when constructing streams. * * [MDN Reference](https://developer.mozilla.org/docs/Web/API/ByteLengthQueuingStrategy) */ declare class ByteLengthQueuingStrategy implements QueuingStrategy { constructor(init: QueuingStrategyInit); /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/ByteLengthQueuingStrategy/highWaterMark) */ get highWaterMark(): number; /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/ByteLengthQueuingStrategy/size) */ get size(): (chunk?: any) => number; } /** * This Streams API interface provides a built-in byte length queuing strategy that can be used when constructing streams. * * [MDN Reference](https://developer.mozilla.org/docs/Web/API/CountQueuingStrategy) */ declare class CountQueuingStrategy implements QueuingStrategy { constructor(init: QueuingStrategyInit); /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/CountQueuingStrategy/highWaterMark) */ get highWaterMark(): number; /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/CountQueuingStrategy/size) */ get size(): (chunk?: any) => number; } interface QueuingStrategyInit { /** * Creates a new ByteLengthQueuingStrategy with the provided high water mark. * * Note that the provided high water mark will not be validated ahead of time. Instead, if it is negative, NaN, or not a number, the resulting ByteLengthQueuingStrategy will cause the corresponding stream constructor to throw. */ highWaterMark: number; } interface ScriptVersion { id?: string; tag?: string; message?: string; } declare abstract class TailEvent extends ExtendableEvent { readonly events: TraceItem[]; readonly traces: TraceItem[]; } interface TraceItem { readonly event: (TraceItemFetchEventInfo | TraceItemJsRpcEventInfo | TraceItemScheduledEventInfo | TraceItemAlarmEventInfo | TraceItemQueueEventInfo | TraceItemEmailEventInfo | TraceItemTailEventInfo | TraceItemCustomEventInfo | TraceItemHibernatableWebSocketEventInfo) | null; readonly eventTimestamp: number | null; readonly logs: TraceLog[]; readonly exceptions: TraceException[]; readonly diagnosticsChannelEvents: TraceDiagnosticChannelEvent[]; readonly scriptName: string | null; readonly entrypoint?: string; readonly scriptVersion?: ScriptVersion; readonly dispatchNamespace?: string; readonly scriptTags?: string[]; readonly durableObjectId?: string; readonly outcome: string; readonly executionModel: string; readonly truncated: boolean; readonly cpuTime: number; readonly wallTime: number; } interface TraceItemAlarmEventInfo { readonly scheduledTime: Date; } interface TraceItemCustomEventInfo { } interface TraceItemScheduledEventInfo { readonly scheduledTime: number; readonly cron: string; } interface TraceItemQueueEventInfo { readonly queue: string; readonly batchSize: number; } interface TraceItemEmailEventInfo { readonly mailFrom: string; readonly rcptTo: string; readonly rawSize: number; } interface TraceItemTailEventInfo { readonly consumedEvents: TraceItemTailEventInfoTailItem[]; } interface TraceItemTailEventInfoTailItem { readonly scriptName: string | null; } interface TraceItemFetchEventInfo { readonly response?: TraceItemFetchEventInfoResponse; readonly request: TraceItemFetchEventInfoRequest; } interface TraceItemFetchEventInfoRequest { readonly cf?: any; readonly headers: Record; readonly method: string; readonly url: string; getUnredacted(): TraceItemFetchEventInfoRequest; } interface TraceItemFetchEventInfoResponse { readonly status: number; } interface TraceItemJsRpcEventInfo { readonly rpcMethod: string; } interface TraceItemHibernatableWebSocketEventInfo { readonly getWebSocketEvent: TraceItemHibernatableWebSocketEventInfoMessage | TraceItemHibernatableWebSocketEventInfoClose | TraceItemHibernatableWebSocketEventInfoError; } interface TraceItemHibernatableWebSocketEventInfoMessage { readonly webSocketEventType: string; } interface TraceItemHibernatableWebSocketEventInfoClose { readonly webSocketEventType: string; readonly code: number; readonly wasClean: boolean; } interface TraceItemHibernatableWebSocketEventInfoError { readonly webSocketEventType: string; } interface TraceLog { readonly timestamp: number; readonly level: string; readonly message: any; } interface TraceException { readonly timestamp: number; readonly message: string; readonly name: string; readonly stack?: string; } interface TraceDiagnosticChannelEvent { readonly timestamp: number; readonly channel: string; readonly message: any; } interface TraceMetrics { readonly cpuTime: number; readonly wallTime: number; } interface UnsafeTraceMetrics { fromTrace(item: TraceItem): TraceMetrics; } /** * The URL interface represents an object providing static methods used for creating object URLs. * * [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL) */ declare class URL { constructor(url: string | URL, base?: string | URL); /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/origin) */ get origin(): string; /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/href) */ get href(): string; /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/href) */ set href(value: string); /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/protocol) */ get protocol(): string; /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/protocol) */ set protocol(value: string); /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/username) */ get username(): string; /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/username) */ set username(value: string); /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/password) */ get password(): string; /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/password) */ set password(value: string); /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/host) */ get host(): string; /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/host) */ set host(value: string); /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/hostname) */ get hostname(): string; /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/hostname) */ set hostname(value: string); /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/port) */ get port(): string; /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/port) */ set port(value: string); /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/pathname) */ get pathname(): string; /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/pathname) */ set pathname(value: string); /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/search) */ get search(): string; /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/search) */ set search(value: string); /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/hash) */ get hash(): string; /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/hash) */ set hash(value: string); /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/searchParams) */ get searchParams(): URLSearchParams; /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/toJSON) */ toJSON(): string; /*function toString() { [native code] }*/ toString(): string; /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/canParse_static) */ static canParse(url: string, base?: string): boolean; /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/parse_static) */ static parse(url: string, base?: string): URL | null; /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/createObjectURL_static) */ static createObjectURL(object: File | Blob): string; /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/URL/revokeObjectURL_static) */ static revokeObjectURL(object_url: string): void; } /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/URLSearchParams) */ declare class URLSearchParams { constructor(init?: (Iterable> | Record | string)); /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/URLSearchParams/size) */ get size(): number; /** * Appends a specified key/value pair as a new search parameter. * * [MDN Reference](https://developer.mozilla.org/docs/Web/API/URLSearchParams/append) */ append(name: string, value: string): void; /** * Deletes the given search parameter, and its associated value, from the list of all search parameters. * * [MDN Reference](https://developer.mozilla.org/docs/Web/API/URLSearchParams/delete) */ delete(name: string, value?: string): void; /** * Returns the first value associated to the given search parameter. * * [MDN Reference](https://developer.mozilla.org/docs/Web/API/URLSearchParams/get) */ get(name: string): string | null; /** * Returns all the values association with a given search parameter. * * [MDN Reference](https://developer.mozilla.org/docs/Web/API/URLSearchParams/getAll) */ getAll(name: string): string[]; /** * Returns a Boolean indicating if such a search parameter exists. * * [MDN Reference](https://developer.mozilla.org/docs/Web/API/URLSearchParams/has) */ has(name: string, value?: string): boolean; /** * Sets the value associated to a given search parameter to the given value. If there were several values, delete the others. * * [MDN Reference](https://developer.mozilla.org/docs/Web/API/URLSearchParams/set) */ set(name: string, value: string): void; /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/URLSearchParams/sort) */ sort(): void; /* Returns an array of key, value pairs for every entry in the search params. */ entries(): IterableIterator<[ key: string, value: string ]>; /* Returns a list of keys in the search params. */ keys(): IterableIterator; /* Returns a list of values in the search params. */ values(): IterableIterator; forEach(callback: (this: This, value: string, key: string, parent: URLSearchParams) => void, thisArg?: This): void; /*function toString() { [native code] } Returns a string containing a query string suitable for use in a URL. Does not include the question mark. */ toString(): string; [Symbol.iterator](): IterableIterator<[ key: string, value: string ]>; } declare class URLPattern { constructor(input?: (string | URLPatternInit), baseURL?: (string | URLPatternOptions), patternOptions?: URLPatternOptions); get protocol(): string; get username(): string; get password(): string; get hostname(): string; get port(): string; get pathname(): string; get search(): string; get hash(): string; get hasRegExpGroups(): boolean; test(input?: (string | URLPatternInit), baseURL?: string): boolean; exec(input?: (string | URLPatternInit), baseURL?: string): URLPatternResult | null; } interface URLPatternInit { protocol?: string; username?: string; password?: string; hostname?: string; port?: string; pathname?: string; search?: string; hash?: string; baseURL?: string; } interface URLPatternComponentResult { input: string; groups: Record; } interface URLPatternResult { inputs: (string | URLPatternInit)[]; protocol: URLPatternComponentResult; username: URLPatternComponentResult; password: URLPatternComponentResult; hostname: URLPatternComponentResult; port: URLPatternComponentResult; pathname: URLPatternComponentResult; search: URLPatternComponentResult; hash: URLPatternComponentResult; } interface URLPatternOptions { ignoreCase?: boolean; } /** * A CloseEvent is sent to clients using WebSockets when the connection is closed. This is delivered to the listener indicated by the WebSocket object's onclose attribute. * * [MDN Reference](https://developer.mozilla.org/docs/Web/API/CloseEvent) */ declare class CloseEvent extends Event { constructor(type: string, initializer?: CloseEventInit); /** * Returns the WebSocket connection close code provided by the server. * * [MDN Reference](https://developer.mozilla.org/docs/Web/API/CloseEvent/code) */ readonly code: number; /** * Returns the WebSocket connection close reason provided by the server. * * [MDN Reference](https://developer.mozilla.org/docs/Web/API/CloseEvent/reason) */ readonly reason: string; /** * Returns true if the connection closed cleanly; false otherwise. * * [MDN Reference](https://developer.mozilla.org/docs/Web/API/CloseEvent/wasClean) */ readonly wasClean: boolean; } interface CloseEventInit { code?: number; reason?: string; wasClean?: boolean; } type WebSocketEventMap = { close: CloseEvent; message: MessageEvent; open: Event; error: ErrorEvent; }; /** * Provides the API for creating and managing a WebSocket connection to a server, as well as for sending and receiving data on the connection. * * [MDN Reference](https://developer.mozilla.org/docs/Web/API/WebSocket) */ declare var WebSocket: { prototype: WebSocket; new (url: string, protocols?: (string[] | string)): WebSocket; readonly READY_STATE_CONNECTING: number; readonly CONNECTING: number; readonly READY_STATE_OPEN: number; readonly OPEN: number; readonly READY_STATE_CLOSING: number; readonly CLOSING: number; readonly READY_STATE_CLOSED: number; readonly CLOSED: number; }; /** * Provides the API for creating and managing a WebSocket connection to a server, as well as for sending and receiving data on the connection. * * [MDN Reference](https://developer.mozilla.org/docs/Web/API/WebSocket) */ interface WebSocket extends EventTarget { accept(): void; /** * Transmits data using the WebSocket connection. data can be a string, a Blob, an ArrayBuffer, or an ArrayBufferView. * * [MDN Reference](https://developer.mozilla.org/docs/Web/API/WebSocket/send) */ send(message: (ArrayBuffer | ArrayBufferView) | string): void; /** * Closes the WebSocket connection, optionally using code as the the WebSocket connection close code and reason as the the WebSocket connection close reason. * * [MDN Reference](https://developer.mozilla.org/docs/Web/API/WebSocket/close) */ close(code?: number, reason?: string): void; serializeAttachment(attachment: any): void; deserializeAttachment(): any | null; /** * Returns the state of the WebSocket object's connection. It can have the values described below. * * [MDN Reference](https://developer.mozilla.org/docs/Web/API/WebSocket/readyState) */ readyState: number; /** * Returns the URL that was used to establish the WebSocket connection. * * [MDN Reference](https://developer.mozilla.org/docs/Web/API/WebSocket/url) */ url: string | null; /** * Returns the subprotocol selected by the server, if any. It can be used in conjunction with the array form of the constructor's second argument to perform subprotocol negotiation. * * [MDN Reference](https://developer.mozilla.org/docs/Web/API/WebSocket/protocol) */ protocol: string | null; /** * Returns the extensions selected by the server, if any. * * [MDN Reference](https://developer.mozilla.org/docs/Web/API/WebSocket/extensions) */ extensions: string | null; } declare const WebSocketPair: { new (): { 0: WebSocket; 1: WebSocket; }; }; interface SqlStorage { exec>(query: string, ...bindings: any[]): SqlStorageCursor; get databaseSize(): number; Cursor: typeof SqlStorageCursor; Statement: typeof SqlStorageStatement; } declare abstract class SqlStorageStatement { } type SqlStorageValue = ArrayBuffer | string | number | null; declare abstract class SqlStorageCursor> { next(): { done?: false; value: T; } | { done: true; value?: never; }; toArray(): T[]; one(): T; raw(): IterableIterator; columnNames: string[]; get rowsRead(): number; get rowsWritten(): number; [Symbol.iterator](): IterableIterator; } interface Socket { get readable(): ReadableStream; get writable(): WritableStream; get closed(): Promise; get opened(): Promise; get upgraded(): boolean; get secureTransport(): "on" | "off" | "starttls"; close(): Promise; startTls(options?: TlsOptions): Socket; } interface SocketOptions { secureTransport?: string; allowHalfOpen: boolean; highWaterMark?: (number | bigint); } interface SocketAddress { hostname: string; port: number; } interface TlsOptions { expectedServerHostname?: string; } interface SocketInfo { remoteAddress?: string; localAddress?: string; } /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/EventSource) */ declare class EventSource extends EventTarget { constructor(url: string, init?: EventSourceEventSourceInit); /** * Aborts any instances of the fetch algorithm started for this EventSource object, and sets the readyState attribute to CLOSED. * * [MDN Reference](https://developer.mozilla.org/docs/Web/API/EventSource/close) */ close(): void; /** * Returns the URL providing the event stream. * * [MDN Reference](https://developer.mozilla.org/docs/Web/API/EventSource/url) */ get url(): string; /** * Returns true if the credentials mode for connection requests to the URL providing the event stream is set to "include", and false otherwise. * * [MDN Reference](https://developer.mozilla.org/docs/Web/API/EventSource/withCredentials) */ get withCredentials(): boolean; /** * Returns the state of this EventSource object's connection. It can have the values described below. * * [MDN Reference](https://developer.mozilla.org/docs/Web/API/EventSource/readyState) */ get readyState(): number; /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/EventSource/open_event) */ get onopen(): any | null; /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/EventSource/open_event) */ set onopen(value: any | null); /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/EventSource/message_event) */ get onmessage(): any | null; /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/EventSource/message_event) */ set onmessage(value: any | null); /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/EventSource/error_event) */ get onerror(): any | null; /* [MDN Reference](https://developer.mozilla.org/docs/Web/API/EventSource/error_event) */ set onerror(value: any | null); static readonly CONNECTING: number; static readonly OPEN: number; static readonly CLOSED: number; static from(stream: ReadableStream): EventSource; } interface EventSourceEventSourceInit { withCredentials?: boolean; fetcher?: Fetcher; } interface Container { get running(): boolean; start(options?: ContainerStartupOptions): void; monitor(): Promise; destroy(error?: any): Promise; signal(signo: number): void; getTcpPort(port: number): Fetcher; } interface ContainerStartupOptions { entrypoint?: string[]; enableInternet: boolean; env?: Record; } /** * This Channel Messaging API interface represents one of the two ports of a MessageChannel, allowing messages to be sent from one port and listening out for them arriving at the other. * * [MDN Reference](https://developer.mozilla.org/docs/Web/API/MessagePort) */ declare abstract class MessagePort extends EventTarget { /** * Posts a message through the channel. Objects listed in transfer are transferred, not just cloned, meaning that they are no longer usable on the sending side. * * Throws a "DataCloneError" DOMException if transfer contains duplicate objects or port, or if message could not be cloned. * * [MDN Reference](https://developer.mozilla.org/docs/Web/API/MessagePort/postMessage) */ postMessage(data?: any, options?: (any[] | MessagePortPostMessageOptions)): void; /** * Disconnects the port, so that it is no longer active. * * [MDN Reference](https://developer.mozilla.org/docs/Web/API/MessagePort/close) */ close(): void; /** * Begins dispatching messages received on the port. * * [MDN Reference](https://developer.mozilla.org/docs/Web/API/MessagePort/start) */ start(): void; get onmessage(): any | null; set onmessage(value: any | null); } /** * This Channel Messaging API interface allows us to create a new message channel and send data through it via its two MessagePort properties. * * [MDN Reference](https://developer.mozilla.org/docs/Web/API/MessageChannel) */ declare class MessageChannel { constructor(); /** * Returns the first MessagePort object. * * [MDN Reference](https://developer.mozilla.org/docs/Web/API/MessageChannel/port1) */ readonly port1: MessagePort; /** * Returns the second MessagePort object. * * [MDN Reference](https://developer.mozilla.org/docs/Web/API/MessageChannel/port2) */ readonly port2: MessagePort; } interface MessagePortPostMessageOptions { transfer?: any[]; } type LoopbackForExport Rpc.EntrypointBranded) | ExportedHandler | undefined = undefined> = T extends new (...args: any[]) => Rpc.WorkerEntrypointBranded ? LoopbackServiceStub> : T extends new (...args: any[]) => Rpc.DurableObjectBranded ? LoopbackDurableObjectClass> : T extends ExportedHandler ? LoopbackServiceStub : undefined; type LoopbackServiceStub = Fetcher & (T extends CloudflareWorkersModule.WorkerEntrypoint ? (opts: { props?: Props; }) => Fetcher : (opts: { props?: any; }) => Fetcher); type LoopbackDurableObjectClass = DurableObjectClass & (T extends CloudflareWorkersModule.DurableObject ? (opts: { props?: Props; }) => DurableObjectClass : (opts: { props?: any; }) => DurableObjectClass); interface SyncKvStorage { get(key: string): T | undefined; list(options?: SyncKvListOptions): Iterable<[ string, T ]>; put(key: string, value: T): void; delete(key: string): boolean; } interface SyncKvListOptions { start?: string; startAfter?: string; end?: string; prefix?: string; reverse?: boolean; limit?: number; } interface WorkerStub { getEntrypoint(name?: string, options?: WorkerStubEntrypointOptions): Fetcher; } interface WorkerStubEntrypointOptions { props?: any; } interface WorkerLoader { get(name: string, getCode: () => WorkerLoaderWorkerCode | Promise): WorkerStub; } interface WorkerLoaderModule { js?: string; cjs?: string; text?: string; data?: ArrayBuffer; json?: any; py?: string; } interface WorkerLoaderWorkerCode { compatibilityDate: string; compatibilityFlags?: string[]; allowExperimental?: boolean; mainModule: string; modules: Record; env?: any; globalOutbound?: (Fetcher | null); tails?: Fetcher[]; streamingTails?: Fetcher[]; } /** * The Workers runtime supports a subset of the Performance API, used to measure timing and performance, * as well as timing of subrequests and other operations. * * [Cloudflare Docs Reference](https://developers.cloudflare.com/workers/runtime-apis/performance/) */ declare abstract class Performance { /* [Cloudflare Docs Reference](https://developers.cloudflare.com/workers/runtime-apis/performance/#performancetimeorigin) */ get timeOrigin(): number; /* [Cloudflare Docs Reference](https://developers.cloudflare.com/workers/runtime-apis/performance/#performancenow) */ now(): number; } type AiImageClassificationInput = { image: number[]; }; type AiImageClassificationOutput = { score?: number; label?: string; }[]; declare abstract class BaseAiImageClassification { inputs: AiImageClassificationInput; postProcessedOutputs: AiImageClassificationOutput; } type AiImageToTextInput = { image: number[]; prompt?: string; max_tokens?: number; temperature?: number; top_p?: number; top_k?: number; seed?: number; repetition_penalty?: number; frequency_penalty?: number; presence_penalty?: number; raw?: boolean; messages?: RoleScopedChatInput[]; }; type AiImageToTextOutput = { description: string; }; declare abstract class BaseAiImageToText { inputs: AiImageToTextInput; postProcessedOutputs: AiImageToTextOutput; } type AiImageTextToTextInput = { image: string; prompt?: string; max_tokens?: number; temperature?: number; ignore_eos?: boolean; top_p?: number; top_k?: number; seed?: number; repetition_penalty?: number; frequency_penalty?: number; presence_penalty?: number; raw?: boolean; messages?: RoleScopedChatInput[]; }; type AiImageTextToTextOutput = { description: string; }; declare abstract class BaseAiImageTextToText { inputs: AiImageTextToTextInput; postProcessedOutputs: AiImageTextToTextOutput; } type AiMultimodalEmbeddingsInput = { image: string; text: string[]; }; type AiIMultimodalEmbeddingsOutput = { data: number[][]; shape: number[]; }; declare abstract class BaseAiMultimodalEmbeddings { inputs: AiImageTextToTextInput; postProcessedOutputs: AiImageTextToTextOutput; } type AiObjectDetectionInput = { image: number[]; }; type AiObjectDetectionOutput = { score?: number; label?: string; }[]; declare abstract class BaseAiObjectDetection { inputs: AiObjectDetectionInput; postProcessedOutputs: AiObjectDetectionOutput; } type AiSentenceSimilarityInput = { source: string; sentences: string[]; }; type AiSentenceSimilarityOutput = number[]; declare abstract class BaseAiSentenceSimilarity { inputs: AiSentenceSimilarityInput; postProcessedOutputs: AiSentenceSimilarityOutput; } type AiAutomaticSpeechRecognitionInput = { audio: number[]; }; type AiAutomaticSpeechRecognitionOutput = { text?: string; words?: { word: string; start: number; end: number; }[]; vtt?: string; }; declare abstract class BaseAiAutomaticSpeechRecognition { inputs: AiAutomaticSpeechRecognitionInput; postProcessedOutputs: AiAutomaticSpeechRecognitionOutput; } type AiSummarizationInput = { input_text: string; max_length?: number; }; type AiSummarizationOutput = { summary: string; }; declare abstract class BaseAiSummarization { inputs: AiSummarizationInput; postProcessedOutputs: AiSummarizationOutput; } type AiTextClassificationInput = { text: string; }; type AiTextClassificationOutput = { score?: number; label?: string; }[]; declare abstract class BaseAiTextClassification { inputs: AiTextClassificationInput; postProcessedOutputs: AiTextClassificationOutput; } type AiTextEmbeddingsInput = { text: string | string[]; }; type AiTextEmbeddingsOutput = { shape: number[]; data: number[][]; }; declare abstract class BaseAiTextEmbeddings { inputs: AiTextEmbeddingsInput; postProcessedOutputs: AiTextEmbeddingsOutput; } type RoleScopedChatInput = { role: "user" | "assistant" | "system" | "tool" | (string & NonNullable); content: string; name?: string; }; type AiTextGenerationToolLegacyInput = { name: string; description: string; parameters?: { type: "object" | (string & NonNullable); properties: { [key: string]: { type: string; description?: string; }; }; required: string[]; }; }; type AiTextGenerationToolInput = { type: "function" | (string & NonNullable); function: { name: string; description: string; parameters?: { type: "object" | (string & NonNullable); properties: { [key: string]: { type: string; description?: string; }; }; required: string[]; }; }; }; type AiTextGenerationFunctionsInput = { name: string; code: string; }; type AiTextGenerationResponseFormat = { type: string; json_schema?: any; }; type AiTextGenerationInput = { prompt?: string; raw?: boolean; stream?: boolean; max_tokens?: number; temperature?: number; top_p?: number; top_k?: number; seed?: number; repetition_penalty?: number; frequency_penalty?: number; presence_penalty?: number; messages?: RoleScopedChatInput[]; response_format?: AiTextGenerationResponseFormat; tools?: AiTextGenerationToolInput[] | AiTextGenerationToolLegacyInput[] | (object & NonNullable); functions?: AiTextGenerationFunctionsInput[]; }; type AiTextGenerationToolLegacyOutput = { name: string; arguments: unknown; }; type AiTextGenerationToolOutput = { id: string; type: "function"; function: { name: string; arguments: string; }; }; type UsageTags = { prompt_tokens: number; completion_tokens: number; total_tokens: number; }; type AiTextGenerationOutput = { response?: string; tool_calls?: AiTextGenerationToolLegacyOutput[] & AiTextGenerationToolOutput[]; usage?: UsageTags; }; declare abstract class BaseAiTextGeneration { inputs: AiTextGenerationInput; postProcessedOutputs: AiTextGenerationOutput; } type AiTextToSpeechInput = { prompt: string; lang?: string; }; type AiTextToSpeechOutput = Uint8Array | { audio: string; }; declare abstract class BaseAiTextToSpeech { inputs: AiTextToSpeechInput; postProcessedOutputs: AiTextToSpeechOutput; } type AiTextToImageInput = { prompt: string; negative_prompt?: string; height?: number; width?: number; image?: number[]; image_b64?: string; mask?: number[]; num_steps?: number; strength?: number; guidance?: number; seed?: number; }; type AiTextToImageOutput = ReadableStream; declare abstract class BaseAiTextToImage { inputs: AiTextToImageInput; postProcessedOutputs: AiTextToImageOutput; } type AiTranslationInput = { text: string; target_lang: string; source_lang?: string; }; type AiTranslationOutput = { translated_text?: string; }; declare abstract class BaseAiTranslation { inputs: AiTranslationInput; postProcessedOutputs: AiTranslationOutput; } type Ai_Cf_Baai_Bge_Base_En_V1_5_Input = { text: string | string[]; /** * The pooling method used in the embedding process. `cls` pooling will generate more accurate embeddings on larger inputs - however, embeddings created with cls pooling are not compatible with embeddings generated with mean pooling. The default pooling method is `mean` in order for this to not be a breaking change, but we highly suggest using the new `cls` pooling for better accuracy. */ pooling?: "mean" | "cls"; } | { /** * Batch of the embeddings requests to run using async-queue */ requests: { text: string | string[]; /** * The pooling method used in the embedding process. `cls` pooling will generate more accurate embeddings on larger inputs - however, embeddings created with cls pooling are not compatible with embeddings generated with mean pooling. The default pooling method is `mean` in order for this to not be a breaking change, but we highly suggest using the new `cls` pooling for better accuracy. */ pooling?: "mean" | "cls"; }[]; }; type Ai_Cf_Baai_Bge_Base_En_V1_5_Output = { shape?: number[]; /** * Embeddings of the requested text values */ data?: number[][]; /** * The pooling method used in the embedding process. */ pooling?: "mean" | "cls"; } | AsyncResponse; interface AsyncResponse { /** * The async request id that can be used to obtain the results. */ request_id?: string; } declare abstract class Base_Ai_Cf_Baai_Bge_Base_En_V1_5 { inputs: Ai_Cf_Baai_Bge_Base_En_V1_5_Input; postProcessedOutputs: Ai_Cf_Baai_Bge_Base_En_V1_5_Output; } type Ai_Cf_Openai_Whisper_Input = string | { /** * An array of integers that represent the audio data constrained to 8-bit unsigned integer values */ audio: number[]; }; interface Ai_Cf_Openai_Whisper_Output { /** * The transcription */ text: string; word_count?: number; words?: { word?: string; /** * The second this word begins in the recording */ start?: number; /** * The ending second when the word completes */ end?: number; }[]; vtt?: string; } declare abstract class Base_Ai_Cf_Openai_Whisper { inputs: Ai_Cf_Openai_Whisper_Input; postProcessedOutputs: Ai_Cf_Openai_Whisper_Output; } type Ai_Cf_Meta_M2M100_1_2B_Input = { /** * The text to be translated */ text: string; /** * The language code of the source text (e.g., 'en' for English). Defaults to 'en' if not specified */ source_lang?: string; /** * The language code to translate the text into (e.g., 'es' for Spanish) */ target_lang: string; } | { /** * Batch of the embeddings requests to run using async-queue */ requests: { /** * The text to be translated */ text: string; /** * The language code of the source text (e.g., 'en' for English). Defaults to 'en' if not specified */ source_lang?: string; /** * The language code to translate the text into (e.g., 'es' for Spanish) */ target_lang: string; }[]; }; type Ai_Cf_Meta_M2M100_1_2B_Output = { /** * The translated text in the target language */ translated_text?: string; } | AsyncResponse; declare abstract class Base_Ai_Cf_Meta_M2M100_1_2B { inputs: Ai_Cf_Meta_M2M100_1_2B_Input; postProcessedOutputs: Ai_Cf_Meta_M2M100_1_2B_Output; } type Ai_Cf_Baai_Bge_Small_En_V1_5_Input = { text: string | string[]; /** * The pooling method used in the embedding process. `cls` pooling will generate more accurate embeddings on larger inputs - however, embeddings created with cls pooling are not compatible with embeddings generated with mean pooling. The default pooling method is `mean` in order for this to not be a breaking change, but we highly suggest using the new `cls` pooling for better accuracy. */ pooling?: "mean" | "cls"; } | { /** * Batch of the embeddings requests to run using async-queue */ requests: { text: string | string[]; /** * The pooling method used in the embedding process. `cls` pooling will generate more accurate embeddings on larger inputs - however, embeddings created with cls pooling are not compatible with embeddings generated with mean pooling. The default pooling method is `mean` in order for this to not be a breaking change, but we highly suggest using the new `cls` pooling for better accuracy. */ pooling?: "mean" | "cls"; }[]; }; type Ai_Cf_Baai_Bge_Small_En_V1_5_Output = { shape?: number[]; /** * Embeddings of the requested text values */ data?: number[][]; /** * The pooling method used in the embedding process. */ pooling?: "mean" | "cls"; } | AsyncResponse; declare abstract class Base_Ai_Cf_Baai_Bge_Small_En_V1_5 { inputs: Ai_Cf_Baai_Bge_Small_En_V1_5_Input; postProcessedOutputs: Ai_Cf_Baai_Bge_Small_En_V1_5_Output; } type Ai_Cf_Baai_Bge_Large_En_V1_5_Input = { text: string | string[]; /** * The pooling method used in the embedding process. `cls` pooling will generate more accurate embeddings on larger inputs - however, embeddings created with cls pooling are not compatible with embeddings generated with mean pooling. The default pooling method is `mean` in order for this to not be a breaking change, but we highly suggest using the new `cls` pooling for better accuracy. */ pooling?: "mean" | "cls"; } | { /** * Batch of the embeddings requests to run using async-queue */ requests: { text: string | string[]; /** * The pooling method used in the embedding process. `cls` pooling will generate more accurate embeddings on larger inputs - however, embeddings created with cls pooling are not compatible with embeddings generated with mean pooling. The default pooling method is `mean` in order for this to not be a breaking change, but we highly suggest using the new `cls` pooling for better accuracy. */ pooling?: "mean" | "cls"; }[]; }; type Ai_Cf_Baai_Bge_Large_En_V1_5_Output = { shape?: number[]; /** * Embeddings of the requested text values */ data?: number[][]; /** * The pooling method used in the embedding process. */ pooling?: "mean" | "cls"; } | AsyncResponse; declare abstract class Base_Ai_Cf_Baai_Bge_Large_En_V1_5 { inputs: Ai_Cf_Baai_Bge_Large_En_V1_5_Input; postProcessedOutputs: Ai_Cf_Baai_Bge_Large_En_V1_5_Output; } type Ai_Cf_Unum_Uform_Gen2_Qwen_500M_Input = string | { /** * The input text prompt for the model to generate a response. */ prompt?: string; /** * If true, a chat template is not applied and you must adhere to the specific model's expected formatting. */ raw?: boolean; /** * Controls the creativity of the AI's responses by adjusting how many possible words it considers. Lower values make outputs more predictable; higher values allow for more varied and creative responses. */ top_p?: number; /** * Limits the AI to choose from the top 'k' most probable words. Lower values make responses more focused; higher values introduce more variety and potential surprises. */ top_k?: number; /** * Random seed for reproducibility of the generation. */ seed?: number; /** * Penalty for repeated tokens; higher values discourage repetition. */ repetition_penalty?: number; /** * Decreases the likelihood of the model repeating the same lines verbatim. */ frequency_penalty?: number; /** * Increases the likelihood of the model introducing new topics. */ presence_penalty?: number; image: number[] | (string & NonNullable); /** * The maximum number of tokens to generate in the response. */ max_tokens?: number; }; interface Ai_Cf_Unum_Uform_Gen2_Qwen_500M_Output { description?: string; } declare abstract class Base_Ai_Cf_Unum_Uform_Gen2_Qwen_500M { inputs: Ai_Cf_Unum_Uform_Gen2_Qwen_500M_Input; postProcessedOutputs: Ai_Cf_Unum_Uform_Gen2_Qwen_500M_Output; } type Ai_Cf_Openai_Whisper_Tiny_En_Input = string | { /** * An array of integers that represent the audio data constrained to 8-bit unsigned integer values */ audio: number[]; }; interface Ai_Cf_Openai_Whisper_Tiny_En_Output { /** * The transcription */ text: string; word_count?: number; words?: { word?: string; /** * The second this word begins in the recording */ start?: number; /** * The ending second when the word completes */ end?: number; }[]; vtt?: string; } declare abstract class Base_Ai_Cf_Openai_Whisper_Tiny_En { inputs: Ai_Cf_Openai_Whisper_Tiny_En_Input; postProcessedOutputs: Ai_Cf_Openai_Whisper_Tiny_En_Output; } interface Ai_Cf_Openai_Whisper_Large_V3_Turbo_Input { /** * Base64 encoded value of the audio data. */ audio: string; /** * Supported tasks are 'translate' or 'transcribe'. */ task?: string; /** * The language of the audio being transcribed or translated. */ language?: string; /** * Preprocess the audio with a voice activity detection model. */ vad_filter?: boolean; /** * A text prompt to help provide context to the model on the contents of the audio. */ initial_prompt?: string; /** * The prefix it appended the the beginning of the output of the transcription and can guide the transcription result. */ prefix?: string; } interface Ai_Cf_Openai_Whisper_Large_V3_Turbo_Output { transcription_info?: { /** * The language of the audio being transcribed or translated. */ language?: string; /** * The confidence level or probability of the detected language being accurate, represented as a decimal between 0 and 1. */ language_probability?: number; /** * The total duration of the original audio file, in seconds. */ duration?: number; /** * The duration of the audio after applying Voice Activity Detection (VAD) to remove silent or irrelevant sections, in seconds. */ duration_after_vad?: number; }; /** * The complete transcription of the audio. */ text: string; /** * The total number of words in the transcription. */ word_count?: number; segments?: { /** * The starting time of the segment within the audio, in seconds. */ start?: number; /** * The ending time of the segment within the audio, in seconds. */ end?: number; /** * The transcription of the segment. */ text?: string; /** * The temperature used in the decoding process, controlling randomness in predictions. Lower values result in more deterministic outputs. */ temperature?: number; /** * The average log probability of the predictions for the words in this segment, indicating overall confidence. */ avg_logprob?: number; /** * The compression ratio of the input to the output, measuring how much the text was compressed during the transcription process. */ compression_ratio?: number; /** * The probability that the segment contains no speech, represented as a decimal between 0 and 1. */ no_speech_prob?: number; words?: { /** * The individual word transcribed from the audio. */ word?: string; /** * The starting time of the word within the audio, in seconds. */ start?: number; /** * The ending time of the word within the audio, in seconds. */ end?: number; }[]; }[]; /** * The transcription in WebVTT format, which includes timing and text information for use in subtitles. */ vtt?: string; } declare abstract class Base_Ai_Cf_Openai_Whisper_Large_V3_Turbo { inputs: Ai_Cf_Openai_Whisper_Large_V3_Turbo_Input; postProcessedOutputs: Ai_Cf_Openai_Whisper_Large_V3_Turbo_Output; } type Ai_Cf_Baai_Bge_M3_Input = BGEM3InputQueryAndContexts | BGEM3InputEmbedding | { /** * Batch of the embeddings requests to run using async-queue */ requests: (BGEM3InputQueryAndContexts1 | BGEM3InputEmbedding1)[]; }; interface BGEM3InputQueryAndContexts { /** * A query you wish to perform against the provided contexts. If no query is provided the model with respond with embeddings for contexts */ query?: string; /** * List of provided contexts. Note that the index in this array is important, as the response will refer to it. */ contexts: { /** * One of the provided context content */ text?: string; }[]; /** * When provided with too long context should the model error out or truncate the context to fit? */ truncate_inputs?: boolean; } interface BGEM3InputEmbedding { text: string | string[]; /** * When provided with too long context should the model error out or truncate the context to fit? */ truncate_inputs?: boolean; } interface BGEM3InputQueryAndContexts1 { /** * A query you wish to perform against the provided contexts. If no query is provided the model with respond with embeddings for contexts */ query?: string; /** * List of provided contexts. Note that the index in this array is important, as the response will refer to it. */ contexts: { /** * One of the provided context content */ text?: string; }[]; /** * When provided with too long context should the model error out or truncate the context to fit? */ truncate_inputs?: boolean; } interface BGEM3InputEmbedding1 { text: string | string[]; /** * When provided with too long context should the model error out or truncate the context to fit? */ truncate_inputs?: boolean; } type Ai_Cf_Baai_Bge_M3_Output = BGEM3OuputQuery | BGEM3OutputEmbeddingForContexts | BGEM3OuputEmbedding | AsyncResponse; interface BGEM3OuputQuery { response?: { /** * Index of the context in the request */ id?: number; /** * Score of the context under the index. */ score?: number; }[]; } interface BGEM3OutputEmbeddingForContexts { response?: number[][]; shape?: number[]; /** * The pooling method used in the embedding process. */ pooling?: "mean" | "cls"; } interface BGEM3OuputEmbedding { shape?: number[]; /** * Embeddings of the requested text values */ data?: number[][]; /** * The pooling method used in the embedding process. */ pooling?: "mean" | "cls"; } declare abstract class Base_Ai_Cf_Baai_Bge_M3 { inputs: Ai_Cf_Baai_Bge_M3_Input; postProcessedOutputs: Ai_Cf_Baai_Bge_M3_Output; } interface Ai_Cf_Black_Forest_Labs_Flux_1_Schnell_Input { /** * A text description of the image you want to generate. */ prompt: string; /** * The number of diffusion steps; higher values can improve quality but take longer. */ steps?: number; } interface Ai_Cf_Black_Forest_Labs_Flux_1_Schnell_Output { /** * The generated image in Base64 format. */ image?: string; } declare abstract class Base_Ai_Cf_Black_Forest_Labs_Flux_1_Schnell { inputs: Ai_Cf_Black_Forest_Labs_Flux_1_Schnell_Input; postProcessedOutputs: Ai_Cf_Black_Forest_Labs_Flux_1_Schnell_Output; } type Ai_Cf_Meta_Llama_3_2_11B_Vision_Instruct_Input = Prompt | Messages; interface Prompt { /** * The input text prompt for the model to generate a response. */ prompt: string; image?: number[] | (string & NonNullable); /** * If true, a chat template is not applied and you must adhere to the specific model's expected formatting. */ raw?: boolean; /** * If true, the response will be streamed back incrementally using SSE, Server Sent Events. */ stream?: boolean; /** * The maximum number of tokens to generate in the response. */ max_tokens?: number; /** * Controls the randomness of the output; higher values produce more random results. */ temperature?: number; /** * Adjusts the creativity of the AI's responses by controlling how many possible words it considers. Lower values make outputs more predictable; higher values allow for more varied and creative responses. */ top_p?: number; /** * Limits the AI to choose from the top 'k' most probable words. Lower values make responses more focused; higher values introduce more variety and potential surprises. */ top_k?: number; /** * Random seed for reproducibility of the generation. */ seed?: number; /** * Penalty for repeated tokens; higher values discourage repetition. */ repetition_penalty?: number; /** * Decreases the likelihood of the model repeating the same lines verbatim. */ frequency_penalty?: number; /** * Increases the likelihood of the model introducing new topics. */ presence_penalty?: number; /** * Name of the LoRA (Low-Rank Adaptation) model to fine-tune the base model. */ lora?: string; } interface Messages { /** * An array of message objects representing the conversation history. */ messages: { /** * The role of the message sender (e.g., 'user', 'assistant', 'system', 'tool'). */ role?: string; /** * The tool call id. Must be supplied for tool calls for Mistral-3. If you don't know what to put here you can fall back to 000000001 */ tool_call_id?: string; content?: string | { /** * Type of the content provided */ type?: string; text?: string; image_url?: { /** * image uri with data (e.g. data:image/jpeg;base64,/9j/...). HTTP URL will not be accepted */ url?: string; }; }[] | { /** * Type of the content provided */ type?: string; text?: string; image_url?: { /** * image uri with data (e.g. data:image/jpeg;base64,/9j/...). HTTP URL will not be accepted */ url?: string; }; }; }[]; image?: number[] | (string & NonNullable); functions?: { name: string; code: string; }[]; /** * A list of tools available for the assistant to use. */ tools?: ({ /** * The name of the tool. More descriptive the better. */ name: string; /** * A brief description of what the tool does. */ description: string; /** * Schema defining the parameters accepted by the tool. */ parameters: { /** * The type of the parameters object (usually 'object'). */ type: string; /** * List of required parameter names. */ required?: string[]; /** * Definitions of each parameter. */ properties: { [k: string]: { /** * The data type of the parameter. */ type: string; /** * A description of the expected parameter. */ description: string; }; }; }; } | { /** * Specifies the type of tool (e.g., 'function'). */ type: string; /** * Details of the function tool. */ function: { /** * The name of the function. */ name: string; /** * A brief description of what the function does. */ description: string; /** * Schema defining the parameters accepted by the function. */ parameters: { /** * The type of the parameters object (usually 'object'). */ type: string; /** * List of required parameter names. */ required?: string[]; /** * Definitions of each parameter. */ properties: { [k: string]: { /** * The data type of the parameter. */ type: string; /** * A description of the expected parameter. */ description: string; }; }; }; }; })[]; /** * If true, the response will be streamed back incrementally. */ stream?: boolean; /** * The maximum number of tokens to generate in the response. */ max_tokens?: number; /** * Controls the randomness of the output; higher values produce more random results. */ temperature?: number; /** * Controls the creativity of the AI's responses by adjusting how many possible words it considers. Lower values make outputs more predictable; higher values allow for more varied and creative responses. */ top_p?: number; /** * Limits the AI to choose from the top 'k' most probable words. Lower values make responses more focused; higher values introduce more variety and potential surprises. */ top_k?: number; /** * Random seed for reproducibility of the generation. */ seed?: number; /** * Penalty for repeated tokens; higher values discourage repetition. */ repetition_penalty?: number; /** * Decreases the likelihood of the model repeating the same lines verbatim. */ frequency_penalty?: number; /** * Increases the likelihood of the model introducing new topics. */ presence_penalty?: number; } type Ai_Cf_Meta_Llama_3_2_11B_Vision_Instruct_Output = { /** * The generated text response from the model */ response?: string; /** * An array of tool calls requests made during the response generation */ tool_calls?: { /** * The arguments passed to be passed to the tool call request */ arguments?: object; /** * The name of the tool to be called */ name?: string; }[]; }; declare abstract class Base_Ai_Cf_Meta_Llama_3_2_11B_Vision_Instruct { inputs: Ai_Cf_Meta_Llama_3_2_11B_Vision_Instruct_Input; postProcessedOutputs: Ai_Cf_Meta_Llama_3_2_11B_Vision_Instruct_Output; } type Ai_Cf_Meta_Llama_3_3_70B_Instruct_Fp8_Fast_Input = Meta_Llama_3_3_70B_Instruct_Fp8_Fast_Prompt | Meta_Llama_3_3_70B_Instruct_Fp8_Fast_Messages | AsyncBatch; interface Meta_Llama_3_3_70B_Instruct_Fp8_Fast_Prompt { /** * The input text prompt for the model to generate a response. */ prompt: string; /** * Name of the LoRA (Low-Rank Adaptation) model to fine-tune the base model. */ lora?: string; response_format?: JSONMode; /** * If true, a chat template is not applied and you must adhere to the specific model's expected formatting. */ raw?: boolean; /** * If true, the response will be streamed back incrementally using SSE, Server Sent Events. */ stream?: boolean; /** * The maximum number of tokens to generate in the response. */ max_tokens?: number; /** * Controls the randomness of the output; higher values produce more random results. */ temperature?: number; /** * Adjusts the creativity of the AI's responses by controlling how many possible words it considers. Lower values make outputs more predictable; higher values allow for more varied and creative responses. */ top_p?: number; /** * Limits the AI to choose from the top 'k' most probable words. Lower values make responses more focused; higher values introduce more variety and potential surprises. */ top_k?: number; /** * Random seed for reproducibility of the generation. */ seed?: number; /** * Penalty for repeated tokens; higher values discourage repetition. */ repetition_penalty?: number; /** * Decreases the likelihood of the model repeating the same lines verbatim. */ frequency_penalty?: number; /** * Increases the likelihood of the model introducing new topics. */ presence_penalty?: number; } interface JSONMode { type?: "json_object" | "json_schema"; json_schema?: unknown; } interface Meta_Llama_3_3_70B_Instruct_Fp8_Fast_Messages { /** * An array of message objects representing the conversation history. */ messages: { /** * The role of the message sender (e.g., 'user', 'assistant', 'system', 'tool'). */ role: string; /** * The content of the message as a string. */ content: string; }[]; functions?: { name: string; code: string; }[]; /** * A list of tools available for the assistant to use. */ tools?: ({ /** * The name of the tool. More descriptive the better. */ name: string; /** * A brief description of what the tool does. */ description: string; /** * Schema defining the parameters accepted by the tool. */ parameters: { /** * The type of the parameters object (usually 'object'). */ type: string; /** * List of required parameter names. */ required?: string[]; /** * Definitions of each parameter. */ properties: { [k: string]: { /** * The data type of the parameter. */ type: string; /** * A description of the expected parameter. */ description: string; }; }; }; } | { /** * Specifies the type of tool (e.g., 'function'). */ type: string; /** * Details of the function tool. */ function: { /** * The name of the function. */ name: string; /** * A brief description of what the function does. */ description: string; /** * Schema defining the parameters accepted by the function. */ parameters: { /** * The type of the parameters object (usually 'object'). */ type: string; /** * List of required parameter names. */ required?: string[]; /** * Definitions of each parameter. */ properties: { [k: string]: { /** * The data type of the parameter. */ type: string; /** * A description of the expected parameter. */ description: string; }; }; }; }; })[]; response_format?: JSONMode; /** * If true, a chat template is not applied and you must adhere to the specific model's expected formatting. */ raw?: boolean; /** * If true, the response will be streamed back incrementally using SSE, Server Sent Events. */ stream?: boolean; /** * The maximum number of tokens to generate in the response. */ max_tokens?: number; /** * Controls the randomness of the output; higher values produce more random results. */ temperature?: number; /** * Adjusts the creativity of the AI's responses by controlling how many possible words it considers. Lower values make outputs more predictable; higher values allow for more varied and creative responses. */ top_p?: number; /** * Limits the AI to choose from the top 'k' most probable words. Lower values make responses more focused; higher values introduce more variety and potential surprises. */ top_k?: number; /** * Random seed for reproducibility of the generation. */ seed?: number; /** * Penalty for repeated tokens; higher values discourage repetition. */ repetition_penalty?: number; /** * Decreases the likelihood of the model repeating the same lines verbatim. */ frequency_penalty?: number; /** * Increases the likelihood of the model introducing new topics. */ presence_penalty?: number; } interface AsyncBatch { requests?: { /** * User-supplied reference. This field will be present in the response as well it can be used to reference the request and response. It's NOT validated to be unique. */ external_reference?: string; /** * Prompt for the text generation model */ prompt?: string; /** * If true, the response will be streamed back incrementally using SSE, Server Sent Events. */ stream?: boolean; /** * The maximum number of tokens to generate in the response. */ max_tokens?: number; /** * Controls the randomness of the output; higher values produce more random results. */ temperature?: number; /** * Adjusts the creativity of the AI's responses by controlling how many possible words it considers. Lower values make outputs more predictable; higher values allow for more varied and creative responses. */ top_p?: number; /** * Random seed for reproducibility of the generation. */ seed?: number; /** * Penalty for repeated tokens; higher values discourage repetition. */ repetition_penalty?: number; /** * Decreases the likelihood of the model repeating the same lines verbatim. */ frequency_penalty?: number; /** * Increases the likelihood of the model introducing new topics. */ presence_penalty?: number; response_format?: JSONMode; }[]; } type Ai_Cf_Meta_Llama_3_3_70B_Instruct_Fp8_Fast_Output = { /** * The generated text response from the model */ response: string; /** * Usage statistics for the inference request */ usage?: { /** * Total number of tokens in input */ prompt_tokens?: number; /** * Total number of tokens in output */ completion_tokens?: number; /** * Total number of input and output tokens */ total_tokens?: number; }; /** * An array of tool calls requests made during the response generation */ tool_calls?: { /** * The arguments passed to be passed to the tool call request */ arguments?: object; /** * The name of the tool to be called */ name?: string; }[]; } | string | AsyncResponse; declare abstract class Base_Ai_Cf_Meta_Llama_3_3_70B_Instruct_Fp8_Fast { inputs: Ai_Cf_Meta_Llama_3_3_70B_Instruct_Fp8_Fast_Input; postProcessedOutputs: Ai_Cf_Meta_Llama_3_3_70B_Instruct_Fp8_Fast_Output; } interface Ai_Cf_Meta_Llama_Guard_3_8B_Input { /** * An array of message objects representing the conversation history. */ messages: { /** * The role of the message sender must alternate between 'user' and 'assistant'. */ role: "user" | "assistant"; /** * The content of the message as a string. */ content: string; }[]; /** * The maximum number of tokens to generate in the response. */ max_tokens?: number; /** * Controls the randomness of the output; higher values produce more random results. */ temperature?: number; /** * Dictate the output format of the generated response. */ response_format?: { /** * Set to json_object to process and output generated text as JSON. */ type?: string; }; } interface Ai_Cf_Meta_Llama_Guard_3_8B_Output { response?: string | { /** * Whether the conversation is safe or not. */ safe?: boolean; /** * A list of what hazard categories predicted for the conversation, if the conversation is deemed unsafe. */ categories?: string[]; }; /** * Usage statistics for the inference request */ usage?: { /** * Total number of tokens in input */ prompt_tokens?: number; /** * Total number of tokens in output */ completion_tokens?: number; /** * Total number of input and output tokens */ total_tokens?: number; }; } declare abstract class Base_Ai_Cf_Meta_Llama_Guard_3_8B { inputs: Ai_Cf_Meta_Llama_Guard_3_8B_Input; postProcessedOutputs: Ai_Cf_Meta_Llama_Guard_3_8B_Output; } interface Ai_Cf_Baai_Bge_Reranker_Base_Input { /** * A query you wish to perform against the provided contexts. */ /** * Number of returned results starting with the best score. */ top_k?: number; /** * List of provided contexts. Note that the index in this array is important, as the response will refer to it. */ contexts: { /** * One of the provided context content */ text?: string; }[]; } interface Ai_Cf_Baai_Bge_Reranker_Base_Output { response?: { /** * Index of the context in the request */ id?: number; /** * Score of the context under the index. */ score?: number; }[]; } declare abstract class Base_Ai_Cf_Baai_Bge_Reranker_Base { inputs: Ai_Cf_Baai_Bge_Reranker_Base_Input; postProcessedOutputs: Ai_Cf_Baai_Bge_Reranker_Base_Output; } type Ai_Cf_Qwen_Qwen2_5_Coder_32B_Instruct_Input = Qwen2_5_Coder_32B_Instruct_Prompt | Qwen2_5_Coder_32B_Instruct_Messages; interface Qwen2_5_Coder_32B_Instruct_Prompt { /** * The input text prompt for the model to generate a response. */ prompt: string; /** * Name of the LoRA (Low-Rank Adaptation) model to fine-tune the base model. */ lora?: string; response_format?: JSONMode; /** * If true, a chat template is not applied and you must adhere to the specific model's expected formatting. */ raw?: boolean; /** * If true, the response will be streamed back incrementally using SSE, Server Sent Events. */ stream?: boolean; /** * The maximum number of tokens to generate in the response. */ max_tokens?: number; /** * Controls the randomness of the output; higher values produce more random results. */ temperature?: number; /** * Adjusts the creativity of the AI's responses by controlling how many possible words it considers. Lower values make outputs more predictable; higher values allow for more varied and creative responses. */ top_p?: number; /** * Limits the AI to choose from the top 'k' most probable words. Lower values make responses more focused; higher values introduce more variety and potential surprises. */ top_k?: number; /** * Random seed for reproducibility of the generation. */ seed?: number; /** * Penalty for repeated tokens; higher values discourage repetition. */ repetition_penalty?: number; /** * Decreases the likelihood of the model repeating the same lines verbatim. */ frequency_penalty?: number; /** * Increases the likelihood of the model introducing new topics. */ presence_penalty?: number; } interface Qwen2_5_Coder_32B_Instruct_Messages { /** * An array of message objects representing the conversation history. */ messages: { /** * The role of the message sender (e.g., 'user', 'assistant', 'system', 'tool'). */ role: string; /** * The content of the message as a string. */ content: string; }[]; functions?: { name: string; code: string; }[]; /** * A list of tools available for the assistant to use. */ tools?: ({ /** * The name of the tool. More descriptive the better. */ name: string; /** * A brief description of what the tool does. */ description: string; /** * Schema defining the parameters accepted by the tool. */ parameters: { /** * The type of the parameters object (usually 'object'). */ type: string; /** * List of required parameter names. */ required?: string[]; /** * Definitions of each parameter. */ properties: { [k: string]: { /** * The data type of the parameter. */ type: string; /** * A description of the expected parameter. */ description: string; }; }; }; } | { /** * Specifies the type of tool (e.g., 'function'). */ type: string; /** * Details of the function tool. */ function: { /** * The name of the function. */ name: string; /** * A brief description of what the function does. */ description: string; /** * Schema defining the parameters accepted by the function. */ parameters: { /** * The type of the parameters object (usually 'object'). */ type: string; /** * List of required parameter names. */ required?: string[]; /** * Definitions of each parameter. */ properties: { [k: string]: { /** * The data type of the parameter. */ type: string; /** * A description of the expected parameter. */ description: string; }; }; }; }; })[]; response_format?: JSONMode; /** * If true, a chat template is not applied and you must adhere to the specific model's expected formatting. */ raw?: boolean; /** * If true, the response will be streamed back incrementally using SSE, Server Sent Events. */ stream?: boolean; /** * The maximum number of tokens to generate in the response. */ max_tokens?: number; /** * Controls the randomness of the output; higher values produce more random results. */ temperature?: number; /** * Adjusts the creativity of the AI's responses by controlling how many possible words it considers. Lower values make outputs more predictable; higher values allow for more varied and creative responses. */ top_p?: number; /** * Limits the AI to choose from the top 'k' most probable words. Lower values make responses more focused; higher values introduce more variety and potential surprises. */ top_k?: number; /** * Random seed for reproducibility of the generation. */ seed?: number; /** * Penalty for repeated tokens; higher values discourage repetition. */ repetition_penalty?: number; /** * Decreases the likelihood of the model repeating the same lines verbatim. */ frequency_penalty?: number; /** * Increases the likelihood of the model introducing new topics. */ presence_penalty?: number; } type Ai_Cf_Qwen_Qwen2_5_Coder_32B_Instruct_Output = { /** * The generated text response from the model */ response: string; /** * Usage statistics for the inference request */ usage?: { /** * Total number of tokens in input */ prompt_tokens?: number; /** * Total number of tokens in output */ completion_tokens?: number; /** * Total number of input and output tokens */ total_tokens?: number; }; /** * An array of tool calls requests made during the response generation */ tool_calls?: { /** * The arguments passed to be passed to the tool call request */ arguments?: object; /** * The name of the tool to be called */ name?: string; }[]; }; declare abstract class Base_Ai_Cf_Qwen_Qwen2_5_Coder_32B_Instruct { inputs: Ai_Cf_Qwen_Qwen2_5_Coder_32B_Instruct_Input; postProcessedOutputs: Ai_Cf_Qwen_Qwen2_5_Coder_32B_Instruct_Output; } type Ai_Cf_Qwen_Qwq_32B_Input = Qwen_Qwq_32B_Prompt | Qwen_Qwq_32B_Messages; interface Qwen_Qwq_32B_Prompt { /** * The input text prompt for the model to generate a response. */ prompt: string; /** * JSON schema that should be fulfilled for the response. */ guided_json?: object; /** * If true, a chat template is not applied and you must adhere to the specific model's expected formatting. */ raw?: boolean; /** * If true, the response will be streamed back incrementally using SSE, Server Sent Events. */ stream?: boolean; /** * The maximum number of tokens to generate in the response. */ max_tokens?: number; /** * Controls the randomness of the output; higher values produce more random results. */ temperature?: number; /** * Adjusts the creativity of the AI's responses by controlling how many possible words it considers. Lower values make outputs more predictable; higher values allow for more varied and creative responses. */ top_p?: number; /** * Limits the AI to choose from the top 'k' most probable words. Lower values make responses more focused; higher values introduce more variety and potential surprises. */ top_k?: number; /** * Random seed for reproducibility of the generation. */ seed?: number; /** * Penalty for repeated tokens; higher values discourage repetition. */ repetition_penalty?: number; /** * Decreases the likelihood of the model repeating the same lines verbatim. */ frequency_penalty?: number; /** * Increases the likelihood of the model introducing new topics. */ presence_penalty?: number; } interface Qwen_Qwq_32B_Messages { /** * An array of message objects representing the conversation history. */ messages: { /** * The role of the message sender (e.g., 'user', 'assistant', 'system', 'tool'). */ role?: string; /** * The tool call id. Must be supplied for tool calls for Mistral-3. If you don't know what to put here you can fall back to 000000001 */ tool_call_id?: string; content?: string | { /** * Type of the content provided */ type?: string; text?: string; image_url?: { /** * image uri with data (e.g. data:image/jpeg;base64,/9j/...). HTTP URL will not be accepted */ url?: string; }; }[] | { /** * Type of the content provided */ type?: string; text?: string; image_url?: { /** * image uri with data (e.g. data:image/jpeg;base64,/9j/...). HTTP URL will not be accepted */ url?: string; }; }; }[]; functions?: { name: string; code: string; }[]; /** * A list of tools available for the assistant to use. */ tools?: ({ /** * The name of the tool. More descriptive the better. */ name: string; /** * A brief description of what the tool does. */ description: string; /** * Schema defining the parameters accepted by the tool. */ parameters: { /** * The type of the parameters object (usually 'object'). */ type: string; /** * List of required parameter names. */ required?: string[]; /** * Definitions of each parameter. */ properties: { [k: string]: { /** * The data type of the parameter. */ type: string; /** * A description of the expected parameter. */ description: string; }; }; }; } | { /** * Specifies the type of tool (e.g., 'function'). */ type: string; /** * Details of the function tool. */ function: { /** * The name of the function. */ name: string; /** * A brief description of what the function does. */ description: string; /** * Schema defining the parameters accepted by the function. */ parameters: { /** * The type of the parameters object (usually 'object'). */ type: string; /** * List of required parameter names. */ required?: string[]; /** * Definitions of each parameter. */ properties: { [k: string]: { /** * The data type of the parameter. */ type: string; /** * A description of the expected parameter. */ description: string; }; }; }; }; })[]; /** * JSON schema that should be fufilled for the response. */ guided_json?: object; /** * If true, a chat template is not applied and you must adhere to the specific model's expected formatting. */ raw?: boolean; /** * If true, the response will be streamed back incrementally using SSE, Server Sent Events. */ stream?: boolean; /** * The maximum number of tokens to generate in the response. */ max_tokens?: number; /** * Controls the randomness of the output; higher values produce more random results. */ temperature?: number; /** * Adjusts the creativity of the AI's responses by controlling how many possible words it considers. Lower values make outputs more predictable; higher values allow for more varied and creative responses. */ top_p?: number; /** * Limits the AI to choose from the top 'k' most probable words. Lower values make responses more focused; higher values introduce more variety and potential surprises. */ top_k?: number; /** * Random seed for reproducibility of the generation. */ seed?: number; /** * Penalty for repeated tokens; higher values discourage repetition. */ repetition_penalty?: number; /** * Decreases the likelihood of the model repeating the same lines verbatim. */ frequency_penalty?: number; /** * Increases the likelihood of the model introducing new topics. */ presence_penalty?: number; } type Ai_Cf_Qwen_Qwq_32B_Output = { /** * The generated text response from the model */ response: string; /** * Usage statistics for the inference request */ usage?: { /** * Total number of tokens in input */ prompt_tokens?: number; /** * Total number of tokens in output */ completion_tokens?: number; /** * Total number of input and output tokens */ total_tokens?: number; }; /** * An array of tool calls requests made during the response generation */ tool_calls?: { /** * The arguments passed to be passed to the tool call request */ arguments?: object; /** * The name of the tool to be called */ name?: string; }[]; }; declare abstract class Base_Ai_Cf_Qwen_Qwq_32B { inputs: Ai_Cf_Qwen_Qwq_32B_Input; postProcessedOutputs: Ai_Cf_Qwen_Qwq_32B_Output; } type Ai_Cf_Mistralai_Mistral_Small_3_1_24B_Instruct_Input = Mistral_Small_3_1_24B_Instruct_Prompt | Mistral_Small_3_1_24B_Instruct_Messages; interface Mistral_Small_3_1_24B_Instruct_Prompt { /** * The input text prompt for the model to generate a response. */ prompt: string; /** * JSON schema that should be fulfilled for the response. */ guided_json?: object; /** * If true, a chat template is not applied and you must adhere to the specific model's expected formatting. */ raw?: boolean; /** * If true, the response will be streamed back incrementally using SSE, Server Sent Events. */ stream?: boolean; /** * The maximum number of tokens to generate in the response. */ max_tokens?: number; /** * Controls the randomness of the output; higher values produce more random results. */ temperature?: number; /** * Adjusts the creativity of the AI's responses by controlling how many possible words it considers. Lower values make outputs more predictable; higher values allow for more varied and creative responses. */ top_p?: number; /** * Limits the AI to choose from the top 'k' most probable words. Lower values make responses more focused; higher values introduce more variety and potential surprises. */ top_k?: number; /** * Random seed for reproducibility of the generation. */ seed?: number; /** * Penalty for repeated tokens; higher values discourage repetition. */ repetition_penalty?: number; /** * Decreases the likelihood of the model repeating the same lines verbatim. */ frequency_penalty?: number; /** * Increases the likelihood of the model introducing new topics. */ presence_penalty?: number; } interface Mistral_Small_3_1_24B_Instruct_Messages { /** * An array of message objects representing the conversation history. */ messages: { /** * The role of the message sender (e.g., 'user', 'assistant', 'system', 'tool'). */ role?: string; /** * The tool call id. Must be supplied for tool calls for Mistral-3. If you don't know what to put here you can fall back to 000000001 */ tool_call_id?: string; content?: string | { /** * Type of the content provided */ type?: string; text?: string; image_url?: { /** * image uri with data (e.g. data:image/jpeg;base64,/9j/...). HTTP URL will not be accepted */ url?: string; }; }[] | { /** * Type of the content provided */ type?: string; text?: string; image_url?: { /** * image uri with data (e.g. data:image/jpeg;base64,/9j/...). HTTP URL will not be accepted */ url?: string; }; }; }[]; functions?: { name: string; code: string; }[]; /** * A list of tools available for the assistant to use. */ tools?: ({ /** * The name of the tool. More descriptive the better. */ name: string; /** * A brief description of what the tool does. */ description: string; /** * Schema defining the parameters accepted by the tool. */ parameters: { /** * The type of the parameters object (usually 'object'). */ type: string; /** * List of required parameter names. */ required?: string[]; /** * Definitions of each parameter. */ properties: { [k: string]: { /** * The data type of the parameter. */ type: string; /** * A description of the expected parameter. */ description: string; }; }; }; } | { /** * Specifies the type of tool (e.g., 'function'). */ type: string; /** * Details of the function tool. */ function: { /** * The name of the function. */ name: string; /** * A brief description of what the function does. */ description: string; /** * Schema defining the parameters accepted by the function. */ parameters: { /** * The type of the parameters object (usually 'object'). */ type: string; /** * List of required parameter names. */ required?: string[]; /** * Definitions of each parameter. */ properties: { [k: string]: { /** * The data type of the parameter. */ type: string; /** * A description of the expected parameter. */ description: string; }; }; }; }; })[]; /** * JSON schema that should be fufilled for the response. */ guided_json?: object; /** * If true, a chat template is not applied and you must adhere to the specific model's expected formatting. */ raw?: boolean; /** * If true, the response will be streamed back incrementally using SSE, Server Sent Events. */ stream?: boolean; /** * The maximum number of tokens to generate in the response. */ max_tokens?: number; /** * Controls the randomness of the output; higher values produce more random results. */ temperature?: number; /** * Adjusts the creativity of the AI's responses by controlling how many possible words it considers. Lower values make outputs more predictable; higher values allow for more varied and creative responses. */ top_p?: number; /** * Limits the AI to choose from the top 'k' most probable words. Lower values make responses more focused; higher values introduce more variety and potential surprises. */ top_k?: number; /** * Random seed for reproducibility of the generation. */ seed?: number; /** * Penalty for repeated tokens; higher values discourage repetition. */ repetition_penalty?: number; /** * Decreases the likelihood of the model repeating the same lines verbatim. */ frequency_penalty?: number; /** * Increases the likelihood of the model introducing new topics. */ presence_penalty?: number; } type Ai_Cf_Mistralai_Mistral_Small_3_1_24B_Instruct_Output = { /** * The generated text response from the model */ response: string; /** * Usage statistics for the inference request */ usage?: { /** * Total number of tokens in input */ prompt_tokens?: number; /** * Total number of tokens in output */ completion_tokens?: number; /** * Total number of input and output tokens */ total_tokens?: number; }; /** * An array of tool calls requests made during the response generation */ tool_calls?: { /** * The arguments passed to be passed to the tool call request */ arguments?: object; /** * The name of the tool to be called */ name?: string; }[]; }; declare abstract class Base_Ai_Cf_Mistralai_Mistral_Small_3_1_24B_Instruct { inputs: Ai_Cf_Mistralai_Mistral_Small_3_1_24B_Instruct_Input; postProcessedOutputs: Ai_Cf_Mistralai_Mistral_Small_3_1_24B_Instruct_Output; } type Ai_Cf_Google_Gemma_3_12B_It_Input = Google_Gemma_3_12B_It_Prompt | Google_Gemma_3_12B_It_Messages; interface Google_Gemma_3_12B_It_Prompt { /** * The input text prompt for the model to generate a response. */ prompt: string; /** * JSON schema that should be fufilled for the response. */ guided_json?: object; /** * If true, a chat template is not applied and you must adhere to the specific model's expected formatting. */ raw?: boolean; /** * If true, the response will be streamed back incrementally using SSE, Server Sent Events. */ stream?: boolean; /** * The maximum number of tokens to generate in the response. */ max_tokens?: number; /** * Controls the randomness of the output; higher values produce more random results. */ temperature?: number; /** * Adjusts the creativity of the AI's responses by controlling how many possible words it considers. Lower values make outputs more predictable; higher values allow for more varied and creative responses. */ top_p?: number; /** * Limits the AI to choose from the top 'k' most probable words. Lower values make responses more focused; higher values introduce more variety and potential surprises. */ top_k?: number; /** * Random seed for reproducibility of the generation. */ seed?: number; /** * Penalty for repeated tokens; higher values discourage repetition. */ repetition_penalty?: number; /** * Decreases the likelihood of the model repeating the same lines verbatim. */ frequency_penalty?: number; /** * Increases the likelihood of the model introducing new topics. */ presence_penalty?: number; } interface Google_Gemma_3_12B_It_Messages { /** * An array of message objects representing the conversation history. */ messages: { /** * The role of the message sender (e.g., 'user', 'assistant', 'system', 'tool'). */ role?: string; content?: string | { /** * Type of the content provided */ type?: string; text?: string; image_url?: { /** * image uri with data (e.g. data:image/jpeg;base64,/9j/...). HTTP URL will not be accepted */ url?: string; }; }[] | { /** * Type of the content provided */ type?: string; text?: string; image_url?: { /** * image uri with data (e.g. data:image/jpeg;base64,/9j/...). HTTP URL will not be accepted */ url?: string; }; }; }[]; functions?: { name: string; code: string; }[]; /** * A list of tools available for the assistant to use. */ tools?: ({ /** * The name of the tool. More descriptive the better. */ name: string; /** * A brief description of what the tool does. */ description: string; /** * Schema defining the parameters accepted by the tool. */ parameters: { /** * The type of the parameters object (usually 'object'). */ type: string; /** * List of required parameter names. */ required?: string[]; /** * Definitions of each parameter. */ properties: { [k: string]: { /** * The data type of the parameter. */ type: string; /** * A description of the expected parameter. */ description: string; }; }; }; } | { /** * Specifies the type of tool (e.g., 'function'). */ type: string; /** * Details of the function tool. */ function: { /** * The name of the function. */ name: string; /** * A brief description of what the function does. */ description: string; /** * Schema defining the parameters accepted by the function. */ parameters: { /** * The type of the parameters object (usually 'object'). */ type: string; /** * List of required parameter names. */ required?: string[]; /** * Definitions of each parameter. */ properties: { [k: string]: { /** * The data type of the parameter. */ type: string; /** * A description of the expected parameter. */ description: string; }; }; }; }; })[]; /** * JSON schema that should be fufilled for the response. */ guided_json?: object; /** * If true, a chat template is not applied and you must adhere to the specific model's expected formatting. */ raw?: boolean; /** * If true, the response will be streamed back incrementally using SSE, Server Sent Events. */ stream?: boolean; /** * The maximum number of tokens to generate in the response. */ max_tokens?: number; /** * Controls the randomness of the output; higher values produce more random results. */ temperature?: number; /** * Adjusts the creativity of the AI's responses by controlling how many possible words it considers. Lower values make outputs more predictable; higher values allow for more varied and creative responses. */ top_p?: number; /** * Limits the AI to choose from the top 'k' most probable words. Lower values make responses more focused; higher values introduce more variety and potential surprises. */ top_k?: number; /** * Random seed for reproducibility of the generation. */ seed?: number; /** * Penalty for repeated tokens; higher values discourage repetition. */ repetition_penalty?: number; /** * Decreases the likelihood of the model repeating the same lines verbatim. */ frequency_penalty?: number; /** * Increases the likelihood of the model introducing new topics. */ presence_penalty?: number; } type Ai_Cf_Google_Gemma_3_12B_It_Output = { /** * The generated text response from the model */ response: string; /** * Usage statistics for the inference request */ usage?: { /** * Total number of tokens in input */ prompt_tokens?: number; /** * Total number of tokens in output */ completion_tokens?: number; /** * Total number of input and output tokens */ total_tokens?: number; }; /** * An array of tool calls requests made during the response generation */ tool_calls?: { /** * The arguments passed to be passed to the tool call request */ arguments?: object; /** * The name of the tool to be called */ name?: string; }[]; }; declare abstract class Base_Ai_Cf_Google_Gemma_3_12B_It { inputs: Ai_Cf_Google_Gemma_3_12B_It_Input; postProcessedOutputs: Ai_Cf_Google_Gemma_3_12B_It_Output; } type Ai_Cf_Meta_Llama_4_Scout_17B_16E_Instruct_Input = Ai_Cf_Meta_Llama_4_Prompt | Ai_Cf_Meta_Llama_4_Messages | Ai_Cf_Meta_Llama_4_Async_Batch; interface Ai_Cf_Meta_Llama_4_Prompt { /** * The input text prompt for the model to generate a response. */ prompt: string; /** * JSON schema that should be fulfilled for the response. */ guided_json?: object; response_format?: JSONMode; /** * If true, a chat template is not applied and you must adhere to the specific model's expected formatting. */ raw?: boolean; /** * If true, the response will be streamed back incrementally using SSE, Server Sent Events. */ stream?: boolean; /** * The maximum number of tokens to generate in the response. */ max_tokens?: number; /** * Controls the randomness of the output; higher values produce more random results. */ temperature?: number; /** * Adjusts the creativity of the AI's responses by controlling how many possible words it considers. Lower values make outputs more predictable; higher values allow for more varied and creative responses. */ top_p?: number; /** * Limits the AI to choose from the top 'k' most probable words. Lower values make responses more focused; higher values introduce more variety and potential surprises. */ top_k?: number; /** * Random seed for reproducibility of the generation. */ seed?: number; /** * Penalty for repeated tokens; higher values discourage repetition. */ repetition_penalty?: number; /** * Decreases the likelihood of the model repeating the same lines verbatim. */ frequency_penalty?: number; /** * Increases the likelihood of the model introducing new topics. */ presence_penalty?: number; } interface Ai_Cf_Meta_Llama_4_Messages { /** * An array of message objects representing the conversation history. */ messages: { /** * The role of the message sender (e.g., 'user', 'assistant', 'system', 'tool'). */ role?: string; /** * The tool call id. If you don't know what to put here you can fall back to 000000001 */ tool_call_id?: string; content?: string | { /** * Type of the content provided */ type?: string; text?: string; image_url?: { /** * image uri with data (e.g. data:image/jpeg;base64,/9j/...). HTTP URL will not be accepted */ url?: string; }; }[] | { /** * Type of the content provided */ type?: string; text?: string; image_url?: { /** * image uri with data (e.g. data:image/jpeg;base64,/9j/...). HTTP URL will not be accepted */ url?: string; }; }; }[]; functions?: { name: string; code: string; }[]; /** * A list of tools available for the assistant to use. */ tools?: ({ /** * The name of the tool. More descriptive the better. */ name: string; /** * A brief description of what the tool does. */ description: string; /** * Schema defining the parameters accepted by the tool. */ parameters: { /** * The type of the parameters object (usually 'object'). */ type: string; /** * List of required parameter names. */ required?: string[]; /** * Definitions of each parameter. */ properties: { [k: string]: { /** * The data type of the parameter. */ type: string; /** * A description of the expected parameter. */ description: string; }; }; }; } | { /** * Specifies the type of tool (e.g., 'function'). */ type: string; /** * Details of the function tool. */ function: { /** * The name of the function. */ name: string; /** * A brief description of what the function does. */ description: string; /** * Schema defining the parameters accepted by the function. */ parameters: { /** * The type of the parameters object (usually 'object'). */ type: string; /** * List of required parameter names. */ required?: string[]; /** * Definitions of each parameter. */ properties: { [k: string]: { /** * The data type of the parameter. */ type: string; /** * A description of the expected parameter. */ description: string; }; }; }; }; })[]; response_format?: JSONMode; /** * JSON schema that should be fufilled for the response. */ guided_json?: object; /** * If true, a chat template is not applied and you must adhere to the specific model's expected formatting. */ raw?: boolean; /** * If true, the response will be streamed back incrementally using SSE, Server Sent Events. */ stream?: boolean; /** * The maximum number of tokens to generate in the response. */ max_tokens?: number; /** * Controls the randomness of the output; higher values produce more random results. */ temperature?: number; /** * Adjusts the creativity of the AI's responses by controlling how many possible words it considers. Lower values make outputs more predictable; higher values allow for more varied and creative responses. */ top_p?: number; /** * Limits the AI to choose from the top 'k' most probable words. Lower values make responses more focused; higher values introduce more variety and potential surprises. */ top_k?: number; /** * Random seed for reproducibility of the generation. */ seed?: number; /** * Penalty for repeated tokens; higher values discourage repetition. */ repetition_penalty?: number; /** * Decreases the likelihood of the model repeating the same lines verbatim. */ frequency_penalty?: number; /** * Increases the likelihood of the model introducing new topics. */ presence_penalty?: number; } interface Ai_Cf_Meta_Llama_4_Async_Batch { requests: (Ai_Cf_Meta_Llama_4_Prompt_Inner | Ai_Cf_Meta_Llama_4_Messages_Inner)[]; } interface Ai_Cf_Meta_Llama_4_Prompt_Inner { /** * The input text prompt for the model to generate a response. */ prompt: string; /** * JSON schema that should be fulfilled for the response. */ guided_json?: object; response_format?: JSONMode; /** * If true, a chat template is not applied and you must adhere to the specific model's expected formatting. */ raw?: boolean; /** * If true, the response will be streamed back incrementally using SSE, Server Sent Events. */ stream?: boolean; /** * The maximum number of tokens to generate in the response. */ max_tokens?: number; /** * Controls the randomness of the output; higher values produce more random results. */ temperature?: number; /** * Adjusts the creativity of the AI's responses by controlling how many possible words it considers. Lower values make outputs more predictable; higher values allow for more varied and creative responses. */ top_p?: number; /** * Limits the AI to choose from the top 'k' most probable words. Lower values make responses more focused; higher values introduce more variety and potential surprises. */ top_k?: number; /** * Random seed for reproducibility of the generation. */ seed?: number; /** * Penalty for repeated tokens; higher values discourage repetition. */ repetition_penalty?: number; /** * Decreases the likelihood of the model repeating the same lines verbatim. */ frequency_penalty?: number; /** * Increases the likelihood of the model introducing new topics. */ presence_penalty?: number; } interface Ai_Cf_Meta_Llama_4_Messages_Inner { /** * An array of message objects representing the conversation history. */ messages: { /** * The role of the message sender (e.g., 'user', 'assistant', 'system', 'tool'). */ role?: string; /** * The tool call id. If you don't know what to put here you can fall back to 000000001 */ tool_call_id?: string; content?: string | { /** * Type of the content provided */ type?: string; text?: string; image_url?: { /** * image uri with data (e.g. data:image/jpeg;base64,/9j/...). HTTP URL will not be accepted */ url?: string; }; }[] | { /** * Type of the content provided */ type?: string; text?: string; image_url?: { /** * image uri with data (e.g. data:image/jpeg;base64,/9j/...). HTTP URL will not be accepted */ url?: string; }; }; }[]; functions?: { name: string; code: string; }[]; /** * A list of tools available for the assistant to use. */ tools?: ({ /** * The name of the tool. More descriptive the better. */ name: string; /** * A brief description of what the tool does. */ description: string; /** * Schema defining the parameters accepted by the tool. */ parameters: { /** * The type of the parameters object (usually 'object'). */ type: string; /** * List of required parameter names. */ required?: string[]; /** * Definitions of each parameter. */ properties: { [k: string]: { /** * The data type of the parameter. */ type: string; /** * A description of the expected parameter. */ description: string; }; }; }; } | { /** * Specifies the type of tool (e.g., 'function'). */ type: string; /** * Details of the function tool. */ function: { /** * The name of the function. */ name: string; /** * A brief description of what the function does. */ description: string; /** * Schema defining the parameters accepted by the function. */ parameters: { /** * The type of the parameters object (usually 'object'). */ type: string; /** * List of required parameter names. */ required?: string[]; /** * Definitions of each parameter. */ properties: { [k: string]: { /** * The data type of the parameter. */ type: string; /** * A description of the expected parameter. */ description: string; }; }; }; }; })[]; response_format?: JSONMode; /** * JSON schema that should be fufilled for the response. */ guided_json?: object; /** * If true, a chat template is not applied and you must adhere to the specific model's expected formatting. */ raw?: boolean; /** * If true, the response will be streamed back incrementally using SSE, Server Sent Events. */ stream?: boolean; /** * The maximum number of tokens to generate in the response. */ max_tokens?: number; /** * Controls the randomness of the output; higher values produce more random results. */ temperature?: number; /** * Adjusts the creativity of the AI's responses by controlling how many possible words it considers. Lower values make outputs more predictable; higher values allow for more varied and creative responses. */ top_p?: number; /** * Limits the AI to choose from the top 'k' most probable words. Lower values make responses more focused; higher values introduce more variety and potential surprises. */ top_k?: number; /** * Random seed for reproducibility of the generation. */ seed?: number; /** * Penalty for repeated tokens; higher values discourage repetition. */ repetition_penalty?: number; /** * Decreases the likelihood of the model repeating the same lines verbatim. */ frequency_penalty?: number; /** * Increases the likelihood of the model introducing new topics. */ presence_penalty?: number; } type Ai_Cf_Meta_Llama_4_Scout_17B_16E_Instruct_Output = { /** * The generated text response from the model */ response: string; /** * Usage statistics for the inference request */ usage?: { /** * Total number of tokens in input */ prompt_tokens?: number; /** * Total number of tokens in output */ completion_tokens?: number; /** * Total number of input and output tokens */ total_tokens?: number; }; /** * An array of tool calls requests made during the response generation */ tool_calls?: { /** * The tool call id. */ id?: string; /** * Specifies the type of tool (e.g., 'function'). */ type?: string; /** * Details of the function tool. */ function?: { /** * The name of the tool to be called */ name?: string; /** * The arguments passed to be passed to the tool call request */ arguments?: object; }; }[]; }; declare abstract class Base_Ai_Cf_Meta_Llama_4_Scout_17B_16E_Instruct { inputs: Ai_Cf_Meta_Llama_4_Scout_17B_16E_Instruct_Input; postProcessedOutputs: Ai_Cf_Meta_Llama_4_Scout_17B_16E_Instruct_Output; } interface Ai_Cf_Deepgram_Nova_3_Input { audio: { body: object; contentType: string; }; /** * Sets how the model will interpret strings submitted to the custom_topic param. When strict, the model will only return topics submitted using the custom_topic param. When extended, the model will return its own detected topics in addition to those submitted using the custom_topic param. */ custom_topic_mode?: "extended" | "strict"; /** * Custom topics you want the model to detect within your input audio or text if present Submit up to 100 */ custom_topic?: string; /** * Sets how the model will interpret intents submitted to the custom_intent param. When strict, the model will only return intents submitted using the custom_intent param. When extended, the model will return its own detected intents in addition those submitted using the custom_intents param */ custom_intent_mode?: "extended" | "strict"; /** * Custom intents you want the model to detect within your input audio if present */ custom_intent?: string; /** * Identifies and extracts key entities from content in submitted audio */ detect_entities?: boolean; /** * Identifies the dominant language spoken in submitted audio */ detect_language?: boolean; /** * Recognize speaker changes. Each word in the transcript will be assigned a speaker number starting at 0 */ diarize?: boolean; /** * Identify and extract key entities from content in submitted audio */ dictation?: boolean; /** * Specify the expected encoding of your submitted audio */ encoding?: "linear16" | "flac" | "mulaw" | "amr-nb" | "amr-wb" | "opus" | "speex" | "g729"; /** * Arbitrary key-value pairs that are attached to the API response for usage in downstream processing */ extra?: string; /** * Filler Words can help transcribe interruptions in your audio, like 'uh' and 'um' */ filler_words?: boolean; /** * Key term prompting can boost or suppress specialized terminology and brands. */ keyterm?: string; /** * Keywords can boost or suppress specialized terminology and brands. */ keywords?: string; /** * The BCP-47 language tag that hints at the primary spoken language. Depending on the Model and API endpoint you choose only certain languages are available. */ language?: string; /** * Spoken measurements will be converted to their corresponding abbreviations. */ measurements?: boolean; /** * Opts out requests from the Deepgram Model Improvement Program. Refer to our Docs for pricing impacts before setting this to true. https://dpgr.am/deepgram-mip. */ mip_opt_out?: boolean; /** * Mode of operation for the model representing broad area of topic that will be talked about in the supplied audio */ mode?: "general" | "medical" | "finance"; /** * Transcribe each audio channel independently. */ multichannel?: boolean; /** * Numerals converts numbers from written format to numerical format. */ numerals?: boolean; /** * Splits audio into paragraphs to improve transcript readability. */ paragraphs?: boolean; /** * Profanity Filter looks for recognized profanity and converts it to the nearest recognized non-profane word or removes it from the transcript completely. */ profanity_filter?: boolean; /** * Add punctuation and capitalization to the transcript. */ punctuate?: boolean; /** * Redaction removes sensitive information from your transcripts. */ redact?: string; /** * Search for terms or phrases in submitted audio and replaces them. */ replace?: string; /** * Search for terms or phrases in submitted audio. */ search?: string; /** * Recognizes the sentiment throughout a transcript or text. */ sentiment?: boolean; /** * Apply formatting to transcript output. When set to true, additional formatting will be applied to transcripts to improve readability. */ smart_format?: boolean; /** * Detect topics throughout a transcript or text. */ topics?: boolean; /** * Segments speech into meaningful semantic units. */ utterances?: boolean; /** * Seconds to wait before detecting a pause between words in submitted audio. */ utt_split?: number; /** * The number of channels in the submitted audio */ channels?: number; /** * Specifies whether the streaming endpoint should provide ongoing transcription updates as more audio is received. When set to true, the endpoint sends continuous updates, meaning transcription results may evolve over time. Note: Supported only for webosockets. */ interim_results?: boolean; /** * Indicates how long model will wait to detect whether a speaker has finished speaking or pauses for a significant period of time. When set to a value, the streaming endpoint immediately finalizes the transcription for the processed time range and returns the transcript with a speech_final parameter set to true. Can also be set to false to disable endpointing */ endpointing?: string; /** * Indicates that speech has started. You'll begin receiving Speech Started messages upon speech starting. Note: Supported only for webosockets. */ vad_events?: boolean; /** * Indicates how long model will wait to send an UtteranceEnd message after a word has been transcribed. Use with interim_results. Note: Supported only for webosockets. */ utterance_end_ms?: boolean; } interface Ai_Cf_Deepgram_Nova_3_Output { results?: { channels?: { alternatives?: { confidence?: number; transcript?: string; words?: { confidence?: number; end?: number; start?: number; word?: string; }[]; }[]; }[]; summary?: { result?: string; short?: string; }; sentiments?: { segments?: { text?: string; start_word?: number; end_word?: number; sentiment?: string; sentiment_score?: number; }[]; average?: { sentiment?: string; sentiment_score?: number; }; }; }; } declare abstract class Base_Ai_Cf_Deepgram_Nova_3 { inputs: Ai_Cf_Deepgram_Nova_3_Input; postProcessedOutputs: Ai_Cf_Deepgram_Nova_3_Output; } type Ai_Cf_Pipecat_Ai_Smart_Turn_V2_Input = { /** * readable stream with audio data and content-type specified for that data */ audio: { body: object; contentType: string; }; /** * type of data PCM data that's sent to the inference server as raw array */ dtype?: "uint8" | "float32" | "float64"; } | { /** * base64 encoded audio data */ audio: string; /** * type of data PCM data that's sent to the inference server as raw array */ dtype?: "uint8" | "float32" | "float64"; }; interface Ai_Cf_Pipecat_Ai_Smart_Turn_V2_Output { /** * if true, end-of-turn was detected */ is_complete?: boolean; /** * probability of the end-of-turn detection */ probability?: number; } declare abstract class Base_Ai_Cf_Pipecat_Ai_Smart_Turn_V2 { inputs: Ai_Cf_Pipecat_Ai_Smart_Turn_V2_Input; postProcessedOutputs: Ai_Cf_Pipecat_Ai_Smart_Turn_V2_Output; } type Ai_Cf_Openai_Gpt_Oss_120B_Input = GPT_OSS_120B_Responses | GPT_OSS_120B_Responses_Async; interface GPT_OSS_120B_Responses { /** * Responses API Input messages. Refer to OpenAI Responses API docs to learn more about supported content types */ input: string | unknown[]; reasoning?: { /** * Constrains effort on reasoning for reasoning models. Currently supported values are low, medium, and high. Reducing reasoning effort can result in faster responses and fewer tokens used on reasoning in a response. */ effort?: "low" | "medium" | "high"; /** * A summary of the reasoning performed by the model. This can be useful for debugging and understanding the model's reasoning process. One of auto, concise, or detailed. */ summary?: "auto" | "concise" | "detailed"; }; } interface GPT_OSS_120B_Responses_Async { requests: { /** * Responses API Input messages. Refer to OpenAI Responses API docs to learn more about supported content types */ input: string | unknown[]; reasoning?: { /** * Constrains effort on reasoning for reasoning models. Currently supported values are low, medium, and high. Reducing reasoning effort can result in faster responses and fewer tokens used on reasoning in a response. */ effort?: "low" | "medium" | "high"; /** * A summary of the reasoning performed by the model. This can be useful for debugging and understanding the model's reasoning process. One of auto, concise, or detailed. */ summary?: "auto" | "concise" | "detailed"; }; }[]; } type Ai_Cf_Openai_Gpt_Oss_120B_Output = {} | (string & NonNullable); declare abstract class Base_Ai_Cf_Openai_Gpt_Oss_120B { inputs: Ai_Cf_Openai_Gpt_Oss_120B_Input; postProcessedOutputs: Ai_Cf_Openai_Gpt_Oss_120B_Output; } type Ai_Cf_Openai_Gpt_Oss_20B_Input = GPT_OSS_20B_Responses | GPT_OSS_20B_Responses_Async; interface GPT_OSS_20B_Responses { /** * Responses API Input messages. Refer to OpenAI Responses API docs to learn more about supported content types */ input: string | unknown[]; reasoning?: { /** * Constrains effort on reasoning for reasoning models. Currently supported values are low, medium, and high. Reducing reasoning effort can result in faster responses and fewer tokens used on reasoning in a response. */ effort?: "low" | "medium" | "high"; /** * A summary of the reasoning performed by the model. This can be useful for debugging and understanding the model's reasoning process. One of auto, concise, or detailed. */ summary?: "auto" | "concise" | "detailed"; }; } interface GPT_OSS_20B_Responses_Async { requests: { /** * Responses API Input messages. Refer to OpenAI Responses API docs to learn more about supported content types */ input: string | unknown[]; reasoning?: { /** * Constrains effort on reasoning for reasoning models. Currently supported values are low, medium, and high. Reducing reasoning effort can result in faster responses and fewer tokens used on reasoning in a response. */ effort?: "low" | "medium" | "high"; /** * A summary of the reasoning performed by the model. This can be useful for debugging and understanding the model's reasoning process. One of auto, concise, or detailed. */ summary?: "auto" | "concise" | "detailed"; }; }[]; } type Ai_Cf_Openai_Gpt_Oss_20B_Output = {} | (string & NonNullable); declare abstract class Base_Ai_Cf_Openai_Gpt_Oss_20B { inputs: Ai_Cf_Openai_Gpt_Oss_20B_Input; postProcessedOutputs: Ai_Cf_Openai_Gpt_Oss_20B_Output; } interface Ai_Cf_Leonardo_Phoenix_1_0_Input { /** * A text description of the image you want to generate. */ prompt: string; /** * Controls how closely the generated image should adhere to the prompt; higher values make the image more aligned with the prompt */ guidance?: number; /** * Random seed for reproducibility of the image generation */ seed?: number; /** * The height of the generated image in pixels */ height?: number; /** * The width of the generated image in pixels */ width?: number; /** * The number of diffusion steps; higher values can improve quality but take longer */ num_steps?: number; /** * Specify what to exclude from the generated images */ negative_prompt?: string; } /** * The generated image in JPEG format */ type Ai_Cf_Leonardo_Phoenix_1_0_Output = string; declare abstract class Base_Ai_Cf_Leonardo_Phoenix_1_0 { inputs: Ai_Cf_Leonardo_Phoenix_1_0_Input; postProcessedOutputs: Ai_Cf_Leonardo_Phoenix_1_0_Output; } interface Ai_Cf_Leonardo_Lucid_Origin_Input { /** * A text description of the image you want to generate. */ prompt: string; /** * Controls how closely the generated image should adhere to the prompt; higher values make the image more aligned with the prompt */ guidance?: number; /** * Random seed for reproducibility of the image generation */ seed?: number; /** * The height of the generated image in pixels */ height?: number; /** * The width of the generated image in pixels */ width?: number; /** * The number of diffusion steps; higher values can improve quality but take longer */ num_steps?: number; /** * The number of diffusion steps; higher values can improve quality but take longer */ steps?: number; } interface Ai_Cf_Leonardo_Lucid_Origin_Output { /** * The generated image in Base64 format. */ image?: string; } declare abstract class Base_Ai_Cf_Leonardo_Lucid_Origin { inputs: Ai_Cf_Leonardo_Lucid_Origin_Input; postProcessedOutputs: Ai_Cf_Leonardo_Lucid_Origin_Output; } interface Ai_Cf_Deepgram_Aura_1_Input { /** * Speaker used to produce the audio. */ speaker?: "angus" | "asteria" | "arcas" | "orion" | "orpheus" | "athena" | "luna" | "zeus" | "perseus" | "helios" | "hera" | "stella"; /** * Encoding of the output audio. */ encoding?: "linear16" | "flac" | "mulaw" | "alaw" | "mp3" | "opus" | "aac"; /** * Container specifies the file format wrapper for the output audio. The available options depend on the encoding type.. */ container?: "none" | "wav" | "ogg"; /** * The text content to be converted to speech */ text: string; /** * Sample Rate specifies the sample rate for the output audio. Based on the encoding, different sample rates are supported. For some encodings, the sample rate is not configurable */ sample_rate?: number; /** * The bitrate of the audio in bits per second. Choose from predefined ranges or specific values based on the encoding type. */ bit_rate?: number; } /** * The generated audio in MP3 format */ type Ai_Cf_Deepgram_Aura_1_Output = string; declare abstract class Base_Ai_Cf_Deepgram_Aura_1 { inputs: Ai_Cf_Deepgram_Aura_1_Input; postProcessedOutputs: Ai_Cf_Deepgram_Aura_1_Output; } interface AiModels { "@cf/huggingface/distilbert-sst-2-int8": BaseAiTextClassification; "@cf/stabilityai/stable-diffusion-xl-base-1.0": BaseAiTextToImage; "@cf/runwayml/stable-diffusion-v1-5-inpainting": BaseAiTextToImage; "@cf/runwayml/stable-diffusion-v1-5-img2img": BaseAiTextToImage; "@cf/lykon/dreamshaper-8-lcm": BaseAiTextToImage; "@cf/bytedance/stable-diffusion-xl-lightning": BaseAiTextToImage; "@cf/myshell-ai/melotts": BaseAiTextToSpeech; "@cf/google/embeddinggemma-300m": BaseAiTextEmbeddings; "@cf/microsoft/resnet-50": BaseAiImageClassification; "@cf/meta/llama-2-7b-chat-int8": BaseAiTextGeneration; "@cf/mistral/mistral-7b-instruct-v0.1": BaseAiTextGeneration; "@cf/meta/llama-2-7b-chat-fp16": BaseAiTextGeneration; "@hf/thebloke/llama-2-13b-chat-awq": BaseAiTextGeneration; "@hf/thebloke/mistral-7b-instruct-v0.1-awq": BaseAiTextGeneration; "@hf/thebloke/zephyr-7b-beta-awq": BaseAiTextGeneration; "@hf/thebloke/openhermes-2.5-mistral-7b-awq": BaseAiTextGeneration; "@hf/thebloke/neural-chat-7b-v3-1-awq": BaseAiTextGeneration; "@hf/thebloke/llamaguard-7b-awq": BaseAiTextGeneration; "@hf/thebloke/deepseek-coder-6.7b-base-awq": BaseAiTextGeneration; "@hf/thebloke/deepseek-coder-6.7b-instruct-awq": BaseAiTextGeneration; "@cf/deepseek-ai/deepseek-math-7b-instruct": BaseAiTextGeneration; "@cf/defog/sqlcoder-7b-2": BaseAiTextGeneration; "@cf/openchat/openchat-3.5-0106": BaseAiTextGeneration; "@cf/tiiuae/falcon-7b-instruct": BaseAiTextGeneration; "@cf/thebloke/discolm-german-7b-v1-awq": BaseAiTextGeneration; "@cf/qwen/qwen1.5-0.5b-chat": BaseAiTextGeneration; "@cf/qwen/qwen1.5-7b-chat-awq": BaseAiTextGeneration; "@cf/qwen/qwen1.5-14b-chat-awq": BaseAiTextGeneration; "@cf/tinyllama/tinyllama-1.1b-chat-v1.0": BaseAiTextGeneration; "@cf/microsoft/phi-2": BaseAiTextGeneration; "@cf/qwen/qwen1.5-1.8b-chat": BaseAiTextGeneration; "@cf/mistral/mistral-7b-instruct-v0.2-lora": BaseAiTextGeneration; "@hf/nousresearch/hermes-2-pro-mistral-7b": BaseAiTextGeneration; "@hf/nexusflow/starling-lm-7b-beta": BaseAiTextGeneration; "@hf/google/gemma-7b-it": BaseAiTextGeneration; "@cf/meta-llama/llama-2-7b-chat-hf-lora": BaseAiTextGeneration; "@cf/google/gemma-2b-it-lora": BaseAiTextGeneration; "@cf/google/gemma-7b-it-lora": BaseAiTextGeneration; "@hf/mistral/mistral-7b-instruct-v0.2": BaseAiTextGeneration; "@cf/meta/llama-3-8b-instruct": BaseAiTextGeneration; "@cf/fblgit/una-cybertron-7b-v2-bf16": BaseAiTextGeneration; "@cf/meta/llama-3-8b-instruct-awq": BaseAiTextGeneration; "@hf/meta-llama/meta-llama-3-8b-instruct": BaseAiTextGeneration; "@cf/meta/llama-3.1-8b-instruct-fp8": BaseAiTextGeneration; "@cf/meta/llama-3.1-8b-instruct-awq": BaseAiTextGeneration; "@cf/meta/llama-3.2-3b-instruct": BaseAiTextGeneration; "@cf/meta/llama-3.2-1b-instruct": BaseAiTextGeneration; "@cf/deepseek-ai/deepseek-r1-distill-qwen-32b": BaseAiTextGeneration; "@cf/facebook/bart-large-cnn": BaseAiSummarization; "@cf/llava-hf/llava-1.5-7b-hf": BaseAiImageToText; "@cf/baai/bge-base-en-v1.5": Base_Ai_Cf_Baai_Bge_Base_En_V1_5; "@cf/openai/whisper": Base_Ai_Cf_Openai_Whisper; "@cf/meta/m2m100-1.2b": Base_Ai_Cf_Meta_M2M100_1_2B; "@cf/baai/bge-small-en-v1.5": Base_Ai_Cf_Baai_Bge_Small_En_V1_5; "@cf/baai/bge-large-en-v1.5": Base_Ai_Cf_Baai_Bge_Large_En_V1_5; "@cf/unum/uform-gen2-qwen-500m": Base_Ai_Cf_Unum_Uform_Gen2_Qwen_500M; "@cf/openai/whisper-tiny-en": Base_Ai_Cf_Openai_Whisper_Tiny_En; "@cf/openai/whisper-large-v3-turbo": Base_Ai_Cf_Openai_Whisper_Large_V3_Turbo; "@cf/baai/bge-m3": Base_Ai_Cf_Baai_Bge_M3; "@cf/black-forest-labs/flux-1-schnell": Base_Ai_Cf_Black_Forest_Labs_Flux_1_Schnell; "@cf/meta/llama-3.2-11b-vision-instruct": Base_Ai_Cf_Meta_Llama_3_2_11B_Vision_Instruct; "@cf/meta/llama-3.3-70b-instruct-fp8-fast": Base_Ai_Cf_Meta_Llama_3_3_70B_Instruct_Fp8_Fast; "@cf/meta/llama-guard-3-8b": Base_Ai_Cf_Meta_Llama_Guard_3_8B; "@cf/baai/bge-reranker-base": Base_Ai_Cf_Baai_Bge_Reranker_Base; "@cf/qwen/qwen2.5-coder-32b-instruct": Base_Ai_Cf_Qwen_Qwen2_5_Coder_32B_Instruct; "@cf/qwen/qwq-32b": Base_Ai_Cf_Qwen_Qwq_32B; "@cf/mistralai/mistral-small-3.1-24b-instruct": Base_Ai_Cf_Mistralai_Mistral_Small_3_1_24B_Instruct; "@cf/google/gemma-3-12b-it": Base_Ai_Cf_Google_Gemma_3_12B_It; "@cf/meta/llama-4-scout-17b-16e-instruct": Base_Ai_Cf_Meta_Llama_4_Scout_17B_16E_Instruct; "@cf/deepgram/nova-3": Base_Ai_Cf_Deepgram_Nova_3; "@cf/pipecat-ai/smart-turn-v2": Base_Ai_Cf_Pipecat_Ai_Smart_Turn_V2; "@cf/openai/gpt-oss-120b": Base_Ai_Cf_Openai_Gpt_Oss_120B; "@cf/openai/gpt-oss-20b": Base_Ai_Cf_Openai_Gpt_Oss_20B; "@cf/leonardo/phoenix-1.0": Base_Ai_Cf_Leonardo_Phoenix_1_0; "@cf/leonardo/lucid-origin": Base_Ai_Cf_Leonardo_Lucid_Origin; "@cf/deepgram/aura-1": Base_Ai_Cf_Deepgram_Aura_1; } type AiOptions = { /** * Send requests as an asynchronous batch job, only works for supported models * https://developers.cloudflare.com/workers-ai/features/batch-api */ queueRequest?: boolean; /** * Establish websocket connections, only works for supported models */ websocket?: boolean; gateway?: GatewayOptions; returnRawResponse?: boolean; prefix?: string; extraHeaders?: object; }; type ConversionResponse = { name: string; mimeType: string; format: "markdown"; tokens: number; data: string; }; type AiModelsSearchParams = { author?: string; hide_experimental?: boolean; page?: number; per_page?: number; search?: string; source?: number; task?: string; }; type AiModelsSearchObject = { id: string; source: number; name: string; description: string; task: { id: string; name: string; description: string; }; tags: string[]; properties: { property_id: string; value: string; }[]; }; interface InferenceUpstreamError extends Error { } interface AiInternalError extends Error { } type AiModelListType = Record; declare abstract class Ai { aiGatewayLogId: string | null; gateway(gatewayId: string): AiGateway; autorag(autoragId: string): AutoRAG; run(model: Name, inputs: InputOptions, options?: Options): Promise; models(params?: AiModelsSearchParams): Promise; toMarkdown(files: { name: string; blob: Blob; }[], options?: { gateway?: GatewayOptions; extraHeaders?: object; }): Promise; toMarkdown(files: { name: string; blob: Blob; }, options?: { gateway?: GatewayOptions; extraHeaders?: object; }): Promise; } type GatewayRetries = { maxAttempts?: 1 | 2 | 3 | 4 | 5; retryDelayMs?: number; backoff?: 'constant' | 'linear' | 'exponential'; }; type GatewayOptions = { id: string; cacheKey?: string; cacheTtl?: number; skipCache?: boolean; metadata?: Record; collectLog?: boolean; eventId?: string; requestTimeoutMs?: number; retries?: GatewayRetries; }; type UniversalGatewayOptions = Exclude & { /** ** @deprecated */ id?: string; }; type AiGatewayPatchLog = { score?: number | null; feedback?: -1 | 1 | null; metadata?: Record | null; }; type AiGatewayLog = { id: string; provider: string; model: string; model_type?: string; path: string; duration: number; request_type?: string; request_content_type?: string; status_code: number; response_content_type?: string; success: boolean; cached: boolean; tokens_in?: number; tokens_out?: number; metadata?: Record; step?: number; cost?: number; custom_cost?: boolean; request_size: number; request_head?: string; request_head_complete: boolean; response_size: number; response_head?: string; response_head_complete: boolean; created_at: Date; }; type AIGatewayProviders = 'workers-ai' | 'anthropic' | 'aws-bedrock' | 'azure-openai' | 'google-vertex-ai' | 'huggingface' | 'openai' | 'perplexity-ai' | 'replicate' | 'groq' | 'cohere' | 'google-ai-studio' | 'mistral' | 'grok' | 'openrouter' | 'deepseek' | 'cerebras' | 'cartesia' | 'elevenlabs' | 'adobe-firefly'; type AIGatewayHeaders = { 'cf-aig-metadata': Record | string; 'cf-aig-custom-cost': { per_token_in?: number; per_token_out?: number; } | { total_cost?: number; } | string; 'cf-aig-cache-ttl': number | string; 'cf-aig-skip-cache': boolean | string; 'cf-aig-cache-key': string; 'cf-aig-event-id': string; 'cf-aig-request-timeout': number | string; 'cf-aig-max-attempts': number | string; 'cf-aig-retry-delay': number | string; 'cf-aig-backoff': string; 'cf-aig-collect-log': boolean | string; Authorization: string; 'Content-Type': string; [key: string]: string | number | boolean | object; }; type AIGatewayUniversalRequest = { provider: AIGatewayProviders | string; // eslint-disable-line endpoint: string; headers: Partial; query: unknown; }; interface AiGatewayInternalError extends Error { } interface AiGatewayLogNotFound extends Error { } declare abstract class AiGateway { patchLog(logId: string, data: AiGatewayPatchLog): Promise; getLog(logId: string): Promise; run(data: AIGatewayUniversalRequest | AIGatewayUniversalRequest[], options?: { gateway?: UniversalGatewayOptions; extraHeaders?: object; }): Promise; getUrl(provider?: AIGatewayProviders | string): Promise; // eslint-disable-line } interface AutoRAGInternalError extends Error { } interface AutoRAGNotFoundError extends Error { } interface AutoRAGUnauthorizedError extends Error { } interface AutoRAGNameNotSetError extends Error { } type ComparisonFilter = { key: string; type: 'eq' | 'ne' | 'gt' | 'gte' | 'lt' | 'lte'; value: string | number | boolean; }; type CompoundFilter = { type: 'and' | 'or'; filters: ComparisonFilter[]; }; type AutoRagSearchRequest = { query: string; filters?: CompoundFilter | ComparisonFilter; max_num_results?: number; ranking_options?: { ranker?: string; score_threshold?: number; }; rewrite_query?: boolean; }; type AutoRagAiSearchRequest = AutoRagSearchRequest & { stream?: boolean; system_prompt?: string; }; type AutoRagAiSearchRequestStreaming = Omit & { stream: true; }; type AutoRagSearchResponse = { object: 'vector_store.search_results.page'; search_query: string; data: { file_id: string; filename: string; score: number; attributes: Record; content: { type: 'text'; text: string; }[]; }[]; has_more: boolean; next_page: string | null; }; type AutoRagListResponse = { id: string; enable: boolean; type: string; source: string; vectorize_name: string; paused: boolean; status: string; }[]; type AutoRagAiSearchResponse = AutoRagSearchResponse & { response: string; }; declare abstract class AutoRAG { list(): Promise; search(params: AutoRagSearchRequest): Promise; aiSearch(params: AutoRagAiSearchRequestStreaming): Promise; aiSearch(params: AutoRagAiSearchRequest): Promise; aiSearch(params: AutoRagAiSearchRequest): Promise; } interface BasicImageTransformations { /** * Maximum width in image pixels. The value must be an integer. */ width?: number; /** * Maximum height in image pixels. The value must be an integer. */ height?: number; /** * Resizing mode as a string. It affects interpretation of width and height * options: * - scale-down: Similar to contain, but the image is never enlarged. If * the image is larger than given width or height, it will be resized. * Otherwise its original size will be kept. * - contain: Resizes to maximum size that fits within the given width and * height. If only a single dimension is given (e.g. only width), the * image will be shrunk or enlarged to exactly match that dimension. * Aspect ratio is always preserved. * - cover: Resizes (shrinks or enlarges) to fill the entire area of width * and height. If the image has an aspect ratio different from the ratio * of width and height, it will be cropped to fit. * - crop: The image will be shrunk and cropped to fit within the area * specified by width and height. The image will not be enlarged. For images * smaller than the given dimensions it's the same as scale-down. For * images larger than the given dimensions, it's the same as cover. * See also trim. * - pad: Resizes to the maximum size that fits within the given width and * height, and then fills the remaining area with a background color * (white by default). Use of this mode is not recommended, as the same * effect can be more efficiently achieved with the contain mode and the * CSS object-fit: contain property. * - squeeze: Stretches and deforms to the width and height given, even if it * breaks aspect ratio */ fit?: "scale-down" | "contain" | "cover" | "crop" | "pad" | "squeeze"; /** * Image segmentation using artificial intelligence models. Sets pixels not * within selected segment area to transparent e.g "foreground" sets every * background pixel as transparent. */ segment?: "foreground"; /** * When cropping with fit: "cover", this defines the side or point that should * be left uncropped. The value is either a string * "left", "right", "top", "bottom", "auto", or "center" (the default), * or an object {x, y} containing focal point coordinates in the original * image expressed as fractions ranging from 0.0 (top or left) to 1.0 * (bottom or right), 0.5 being the center. {fit: "cover", gravity: "top"} will * crop bottom or left and right sides as necessary, but won’t crop anything * from the top. {fit: "cover", gravity: {x:0.5, y:0.2}} will crop each side to * preserve as much as possible around a point at 20% of the height of the * source image. */ gravity?: 'face' | 'left' | 'right' | 'top' | 'bottom' | 'center' | 'auto' | 'entropy' | BasicImageTransformationsGravityCoordinates; /** * Background color to add underneath the image. Applies only to images with * transparency (such as PNG). Accepts any CSS color (#RRGGBB, rgba(…), * hsl(…), etc.) */ background?: string; /** * Number of degrees (90, 180, 270) to rotate the image by. width and height * options refer to axes after rotation. */ rotate?: 0 | 90 | 180 | 270 | 360; } interface BasicImageTransformationsGravityCoordinates { x?: number; y?: number; mode?: 'remainder' | 'box-center'; } /** * In addition to the properties you can set in the RequestInit dict * that you pass as an argument to the Request constructor, you can * set certain properties of a `cf` object to control how Cloudflare * features are applied to that new Request. * * Note: Currently, these properties cannot be tested in the * playground. */ interface RequestInitCfProperties extends Record { cacheEverything?: boolean; /** * A request's cache key is what determines if two requests are * "the same" for caching purposes. If a request has the same cache key * as some previous request, then we can serve the same cached response for * both. (e.g. 'some-key') * * Only available for Enterprise customers. */ cacheKey?: string; /** * This allows you to append additional Cache-Tag response headers * to the origin response without modifications to the origin server. * This will allow for greater control over the Purge by Cache Tag feature * utilizing changes only in the Workers process. * * Only available for Enterprise customers. */ cacheTags?: string[]; /** * Force response to be cached for a given number of seconds. (e.g. 300) */ cacheTtl?: number; /** * Force response to be cached for a given number of seconds based on the Origin status code. * (e.g. { '200-299': 86400, '404': 1, '500-599': 0 }) */ cacheTtlByStatus?: Record; scrapeShield?: boolean; apps?: boolean; image?: RequestInitCfPropertiesImage; minify?: RequestInitCfPropertiesImageMinify; mirage?: boolean; polish?: "lossy" | "lossless" | "off"; r2?: RequestInitCfPropertiesR2; /** * Redirects the request to an alternate origin server. You can use this, * for example, to implement load balancing across several origins. * (e.g.us-east.example.com) * * Note - For security reasons, the hostname set in resolveOverride must * be proxied on the same Cloudflare zone of the incoming request. * Otherwise, the setting is ignored. CNAME hosts are allowed, so to * resolve to a host under a different domain or a DNS only domain first * declare a CNAME record within your own zone’s DNS mapping to the * external hostname, set proxy on Cloudflare, then set resolveOverride * to point to that CNAME record. */ resolveOverride?: string; } interface RequestInitCfPropertiesImageDraw extends BasicImageTransformations { /** * Absolute URL of the image file to use for the drawing. It can be any of * the supported file formats. For drawing of watermarks or non-rectangular * overlays we recommend using PNG or WebP images. */ url: string; /** * Floating-point number between 0 (transparent) and 1 (opaque). * For example, opacity: 0.5 makes overlay semitransparent. */ opacity?: number; /** * - If set to true, the overlay image will be tiled to cover the entire * area. This is useful for stock-photo-like watermarks. * - If set to "x", the overlay image will be tiled horizontally only * (form a line). * - If set to "y", the overlay image will be tiled vertically only * (form a line). */ repeat?: true | "x" | "y"; /** * Position of the overlay image relative to a given edge. Each property is * an offset in pixels. 0 aligns exactly to the edge. For example, left: 10 * positions left side of the overlay 10 pixels from the left edge of the * image it's drawn over. bottom: 0 aligns bottom of the overlay with bottom * of the background image. * * Setting both left & right, or both top & bottom is an error. * * If no position is specified, the image will be centered. */ top?: number; left?: number; bottom?: number; right?: number; } interface RequestInitCfPropertiesImage extends BasicImageTransformations { /** * Device Pixel Ratio. Default 1. Multiplier for width/height that makes it * easier to specify higher-DPI sizes in . */ dpr?: number; /** * Allows you to trim your image. Takes dpr into account and is performed before * resizing or rotation. * * It can be used as: * - left, top, right, bottom - it will specify the number of pixels to cut * off each side * - width, height - the width/height you'd like to end up with - can be used * in combination with the properties above * - border - this will automatically trim the surroundings of an image based on * it's color. It consists of three properties: * - color: rgb or hex representation of the color you wish to trim (todo: verify the rgba bit) * - tolerance: difference from color to treat as color * - keep: the number of pixels of border to keep */ trim?: "border" | { top?: number; bottom?: number; left?: number; right?: number; width?: number; height?: number; border?: boolean | { color?: string; tolerance?: number; keep?: number; }; }; /** * Quality setting from 1-100 (useful values are in 60-90 range). Lower values * make images look worse, but load faster. The default is 85. It applies only * to JPEG and WebP images. It doesn’t have any effect on PNG. */ quality?: number | "low" | "medium-low" | "medium-high" | "high"; /** * Output format to generate. It can be: * - avif: generate images in AVIF format. * - webp: generate images in Google WebP format. Set quality to 100 to get * the WebP-lossless format. * - json: instead of generating an image, outputs information about the * image, in JSON format. The JSON object will contain image size * (before and after resizing), source image’s MIME type, file size, etc. * - jpeg: generate images in JPEG format. * - png: generate images in PNG format. */ format?: "avif" | "webp" | "json" | "jpeg" | "png" | "baseline-jpeg" | "png-force" | "svg"; /** * Whether to preserve animation frames from input files. Default is true. * Setting it to false reduces animations to still images. This setting is * recommended when enlarging images or processing arbitrary user content, * because large GIF animations can weigh tens or even hundreds of megabytes. * It is also useful to set anim:false when using format:"json" to get the * response quicker without the number of frames. */ anim?: boolean; /** * What EXIF data should be preserved in the output image. Note that EXIF * rotation and embedded color profiles are always applied ("baked in" into * the image), and aren't affected by this option. Note that if the Polish * feature is enabled, all metadata may have been removed already and this * option may have no effect. * - keep: Preserve most of EXIF metadata, including GPS location if there's * any. * - copyright: Only keep the copyright tag, and discard everything else. * This is the default behavior for JPEG files. * - none: Discard all invisible EXIF metadata. Currently WebP and PNG * output formats always discard metadata. */ metadata?: "keep" | "copyright" | "none"; /** * Strength of sharpening filter to apply to the image. Floating-point * number between 0 (no sharpening, default) and 10 (maximum). 1.0 is a * recommended value for downscaled images. */ sharpen?: number; /** * Radius of a blur filter (approximate gaussian). Maximum supported radius * is 250. */ blur?: number; /** * Overlays are drawn in the order they appear in the array (last array * entry is the topmost layer). */ draw?: RequestInitCfPropertiesImageDraw[]; /** * Fetching image from authenticated origin. Setting this property will * pass authentication headers (Authorization, Cookie, etc.) through to * the origin. */ "origin-auth"?: "share-publicly"; /** * Adds a border around the image. The border is added after resizing. Border * width takes dpr into account, and can be specified either using a single * width property, or individually for each side. */ border?: { color: string; width: number; } | { color: string; top: number; right: number; bottom: number; left: number; }; /** * Increase brightness by a factor. A value of 1.0 equals no change, a value * of 0.5 equals half brightness, and a value of 2.0 equals twice as bright. * 0 is ignored. */ brightness?: number; /** * Increase contrast by a factor. A value of 1.0 equals no change, a value of * 0.5 equals low contrast, and a value of 2.0 equals high contrast. 0 is * ignored. */ contrast?: number; /** * Increase exposure by a factor. A value of 1.0 equals no change, a value of * 0.5 darkens the image, and a value of 2.0 lightens the image. 0 is ignored. */ gamma?: number; /** * Increase contrast by a factor. A value of 1.0 equals no change, a value of * 0.5 equals low contrast, and a value of 2.0 equals high contrast. 0 is * ignored. */ saturation?: number; /** * Flips the images horizontally, vertically, or both. Flipping is applied before * rotation, so if you apply flip=h,rotate=90 then the image will be flipped * horizontally, then rotated by 90 degrees. */ flip?: 'h' | 'v' | 'hv'; /** * Slightly reduces latency on a cache miss by selecting a * quickest-to-compress file format, at a cost of increased file size and * lower image quality. It will usually override the format option and choose * JPEG over WebP or AVIF. We do not recommend using this option, except in * unusual circumstances like resizing uncacheable dynamically-generated * images. */ compression?: "fast"; } interface RequestInitCfPropertiesImageMinify { javascript?: boolean; css?: boolean; html?: boolean; } interface RequestInitCfPropertiesR2 { /** * Colo id of bucket that an object is stored in */ bucketColoId?: number; } /** * Request metadata provided by Cloudflare's edge. */ type IncomingRequestCfProperties = IncomingRequestCfPropertiesBase & IncomingRequestCfPropertiesBotManagementEnterprise & IncomingRequestCfPropertiesCloudflareForSaaSEnterprise & IncomingRequestCfPropertiesGeographicInformation & IncomingRequestCfPropertiesCloudflareAccessOrApiShield; interface IncomingRequestCfPropertiesBase extends Record { /** * [ASN](https://www.iana.org/assignments/as-numbers/as-numbers.xhtml) of the incoming request. * * @example 395747 */ asn?: number; /** * The organization which owns the ASN of the incoming request. * * @example "Google Cloud" */ asOrganization?: string; /** * The original value of the `Accept-Encoding` header if Cloudflare modified it. * * @example "gzip, deflate, br" */ clientAcceptEncoding?: string; /** * The number of milliseconds it took for the request to reach your worker. * * @example 22 */ clientTcpRtt?: number; /** * The three-letter [IATA](https://en.wikipedia.org/wiki/IATA_airport_code) * airport code of the data center that the request hit. * * @example "DFW" */ colo: string; /** * Represents the upstream's response to a * [TCP `keepalive` message](https://tldp.org/HOWTO/TCP-Keepalive-HOWTO/overview.html) * from cloudflare. * * For workers with no upstream, this will always be `1`. * * @example 3 */ edgeRequestKeepAliveStatus: IncomingRequestCfPropertiesEdgeRequestKeepAliveStatus; /** * The HTTP Protocol the request used. * * @example "HTTP/2" */ httpProtocol: string; /** * The browser-requested prioritization information in the request object. * * If no information was set, defaults to the empty string `""` * * @example "weight=192;exclusive=0;group=3;group-weight=127" * @default "" */ requestPriority: string; /** * The TLS version of the connection to Cloudflare. * In requests served over plaintext (without TLS), this property is the empty string `""`. * * @example "TLSv1.3" */ tlsVersion: string; /** * The cipher for the connection to Cloudflare. * In requests served over plaintext (without TLS), this property is the empty string `""`. * * @example "AEAD-AES128-GCM-SHA256" */ tlsCipher: string; /** * Metadata containing the [`HELLO`](https://www.rfc-editor.org/rfc/rfc5246#section-7.4.1.2) and [`FINISHED`](https://www.rfc-editor.org/rfc/rfc5246#section-7.4.9) messages from this request's TLS handshake. * * If the incoming request was served over plaintext (without TLS) this field is undefined. */ tlsExportedAuthenticator?: IncomingRequestCfPropertiesExportedAuthenticatorMetadata; } interface IncomingRequestCfPropertiesBotManagementBase { /** * Cloudflare’s [level of certainty](https://developers.cloudflare.com/bots/concepts/bot-score/) that a request comes from a bot, * represented as an integer percentage between `1` (almost certainly a bot) and `99` (almost certainly human). * * @example 54 */ score: number; /** * A boolean value that is true if the request comes from a good bot, like Google or Bing. * Most customers choose to allow this traffic. For more details, see [Traffic from known bots](https://developers.cloudflare.com/firewall/known-issues-and-faq/#how-does-firewall-rules-handle-traffic-from-known-bots). */ verifiedBot: boolean; /** * A boolean value that is true if the request originates from a * Cloudflare-verified proxy service. */ corporateProxy: boolean; /** * A boolean value that's true if the request matches [file extensions](https://developers.cloudflare.com/bots/reference/static-resources/) for many types of static resources. */ staticResource: boolean; /** * List of IDs that correlate to the Bot Management heuristic detections made on a request (you can have multiple heuristic detections on the same request). */ detectionIds: number[]; } interface IncomingRequestCfPropertiesBotManagement { /** * Results of Cloudflare's Bot Management analysis */ botManagement: IncomingRequestCfPropertiesBotManagementBase; /** * Duplicate of `botManagement.score`. * * @deprecated */ clientTrustScore: number; } interface IncomingRequestCfPropertiesBotManagementEnterprise extends IncomingRequestCfPropertiesBotManagement { /** * Results of Cloudflare's Bot Management analysis */ botManagement: IncomingRequestCfPropertiesBotManagementBase & { /** * A [JA3 Fingerprint](https://developers.cloudflare.com/bots/concepts/ja3-fingerprint/) to help profile specific SSL/TLS clients * across different destination IPs, Ports, and X509 certificates. */ ja3Hash: string; }; } interface IncomingRequestCfPropertiesCloudflareForSaaSEnterprise { /** * Custom metadata set per-host in [Cloudflare for SaaS](https://developers.cloudflare.com/cloudflare-for-platforms/cloudflare-for-saas/). * * This field is only present if you have Cloudflare for SaaS enabled on your account * and you have followed the [required steps to enable it]((https://developers.cloudflare.com/cloudflare-for-platforms/cloudflare-for-saas/domain-support/custom-metadata/)). */ hostMetadata?: HostMetadata; } interface IncomingRequestCfPropertiesCloudflareAccessOrApiShield { /** * Information about the client certificate presented to Cloudflare. * * This is populated when the incoming request is served over TLS using * either Cloudflare Access or API Shield (mTLS) * and the presented SSL certificate has a valid * [Certificate Serial Number](https://ldapwiki.com/wiki/Certificate%20Serial%20Number) * (i.e., not `null` or `""`). * * Otherwise, a set of placeholder values are used. * * The property `certPresented` will be set to `"1"` when * the object is populated (i.e. the above conditions were met). */ tlsClientAuth: IncomingRequestCfPropertiesTLSClientAuth | IncomingRequestCfPropertiesTLSClientAuthPlaceholder; } /** * Metadata about the request's TLS handshake */ interface IncomingRequestCfPropertiesExportedAuthenticatorMetadata { /** * The client's [`HELLO` message](https://www.rfc-editor.org/rfc/rfc5246#section-7.4.1.2), encoded in hexadecimal * * @example "44372ba35fa1270921d318f34c12f155dc87b682cf36a790cfaa3ba8737a1b5d" */ clientHandshake: string; /** * The server's [`HELLO` message](https://www.rfc-editor.org/rfc/rfc5246#section-7.4.1.2), encoded in hexadecimal * * @example "44372ba35fa1270921d318f34c12f155dc87b682cf36a790cfaa3ba8737a1b5d" */ serverHandshake: string; /** * The client's [`FINISHED` message](https://www.rfc-editor.org/rfc/rfc5246#section-7.4.9), encoded in hexadecimal * * @example "084ee802fe1348f688220e2a6040a05b2199a761f33cf753abb1b006792d3f8b" */ clientFinished: string; /** * The server's [`FINISHED` message](https://www.rfc-editor.org/rfc/rfc5246#section-7.4.9), encoded in hexadecimal * * @example "084ee802fe1348f688220e2a6040a05b2199a761f33cf753abb1b006792d3f8b" */ serverFinished: string; } /** * Geographic data about the request's origin. */ interface IncomingRequestCfPropertiesGeographicInformation { /** * The [ISO 3166-1 Alpha 2](https://www.iso.org/iso-3166-country-codes.html) country code the request originated from. * * If your worker is [configured to accept TOR connections](https://support.cloudflare.com/hc/en-us/articles/203306930-Understanding-Cloudflare-Tor-support-and-Onion-Routing), this may also be `"T1"`, indicating a request that originated over TOR. * * If Cloudflare is unable to determine where the request originated this property is omitted. * * The country code `"T1"` is used for requests originating on TOR. * * @example "GB" */ country?: Iso3166Alpha2Code | "T1"; /** * If present, this property indicates that the request originated in the EU * * @example "1" */ isEUCountry?: "1"; /** * A two-letter code indicating the continent the request originated from. * * @example "AN" */ continent?: ContinentCode; /** * The city the request originated from * * @example "Austin" */ city?: string; /** * Postal code of the incoming request * * @example "78701" */ postalCode?: string; /** * Latitude of the incoming request * * @example "30.27130" */ latitude?: string; /** * Longitude of the incoming request * * @example "-97.74260" */ longitude?: string; /** * Timezone of the incoming request * * @example "America/Chicago" */ timezone?: string; /** * If known, the ISO 3166-2 name for the first level region associated with * the IP address of the incoming request * * @example "Texas" */ region?: string; /** * If known, the ISO 3166-2 code for the first-level region associated with * the IP address of the incoming request * * @example "TX" */ regionCode?: string; /** * Metro code (DMA) of the incoming request * * @example "635" */ metroCode?: string; } /** Data about the incoming request's TLS certificate */ interface IncomingRequestCfPropertiesTLSClientAuth { /** Always `"1"`, indicating that the certificate was presented */ certPresented: "1"; /** * Result of certificate verification. * * @example "FAILED:self signed certificate" */ certVerified: Exclude; /** The presented certificate's revokation status. * * - A value of `"1"` indicates the certificate has been revoked * - A value of `"0"` indicates the certificate has not been revoked */ certRevoked: "1" | "0"; /** * The certificate issuer's [distinguished name](https://knowledge.digicert.com/generalinformation/INFO1745.html) * * @example "CN=cloudflareaccess.com, C=US, ST=Texas, L=Austin, O=Cloudflare" */ certIssuerDN: string; /** * The certificate subject's [distinguished name](https://knowledge.digicert.com/generalinformation/INFO1745.html) * * @example "CN=*.cloudflareaccess.com, C=US, ST=Texas, L=Austin, O=Cloudflare" */ certSubjectDN: string; /** * The certificate issuer's [distinguished name](https://knowledge.digicert.com/generalinformation/INFO1745.html) ([RFC 2253](https://www.rfc-editor.org/rfc/rfc2253.html) formatted) * * @example "CN=cloudflareaccess.com, C=US, ST=Texas, L=Austin, O=Cloudflare" */ certIssuerDNRFC2253: string; /** * The certificate subject's [distinguished name](https://knowledge.digicert.com/generalinformation/INFO1745.html) ([RFC 2253](https://www.rfc-editor.org/rfc/rfc2253.html) formatted) * * @example "CN=*.cloudflareaccess.com, C=US, ST=Texas, L=Austin, O=Cloudflare" */ certSubjectDNRFC2253: string; /** The certificate issuer's distinguished name (legacy policies) */ certIssuerDNLegacy: string; /** The certificate subject's distinguished name (legacy policies) */ certSubjectDNLegacy: string; /** * The certificate's serial number * * @example "00936EACBE07F201DF" */ certSerial: string; /** * The certificate issuer's serial number * * @example "2489002934BDFEA34" */ certIssuerSerial: string; /** * The certificate's Subject Key Identifier * * @example "BB:AF:7E:02:3D:FA:A6:F1:3C:84:8E:AD:EE:38:98:EC:D9:32:32:D4" */ certSKI: string; /** * The certificate issuer's Subject Key Identifier * * @example "BB:AF:7E:02:3D:FA:A6:F1:3C:84:8E:AD:EE:38:98:EC:D9:32:32:D4" */ certIssuerSKI: string; /** * The certificate's SHA-1 fingerprint * * @example "6b9109f323999e52259cda7373ff0b4d26bd232e" */ certFingerprintSHA1: string; /** * The certificate's SHA-256 fingerprint * * @example "acf77cf37b4156a2708e34c4eb755f9b5dbbe5ebb55adfec8f11493438d19e6ad3f157f81fa3b98278453d5652b0c1fd1d71e5695ae4d709803a4d3f39de9dea" */ certFingerprintSHA256: string; /** * The effective starting date of the certificate * * @example "Dec 22 19:39:00 2018 GMT" */ certNotBefore: string; /** * The effective expiration date of the certificate * * @example "Dec 22 19:39:00 2018 GMT" */ certNotAfter: string; } /** Placeholder values for TLS Client Authorization */ interface IncomingRequestCfPropertiesTLSClientAuthPlaceholder { certPresented: "0"; certVerified: "NONE"; certRevoked: "0"; certIssuerDN: ""; certSubjectDN: ""; certIssuerDNRFC2253: ""; certSubjectDNRFC2253: ""; certIssuerDNLegacy: ""; certSubjectDNLegacy: ""; certSerial: ""; certIssuerSerial: ""; certSKI: ""; certIssuerSKI: ""; certFingerprintSHA1: ""; certFingerprintSHA256: ""; certNotBefore: ""; certNotAfter: ""; } /** Possible outcomes of TLS verification */ declare type CertVerificationStatus = /** Authentication succeeded */ "SUCCESS" /** No certificate was presented */ | "NONE" /** Failed because the certificate was self-signed */ | "FAILED:self signed certificate" /** Failed because the certificate failed a trust chain check */ | "FAILED:unable to verify the first certificate" /** Failed because the certificate not yet valid */ | "FAILED:certificate is not yet valid" /** Failed because the certificate is expired */ | "FAILED:certificate has expired" /** Failed for another unspecified reason */ | "FAILED"; /** * An upstream endpoint's response to a TCP `keepalive` message from Cloudflare. */ declare type IncomingRequestCfPropertiesEdgeRequestKeepAliveStatus = 0 /** Unknown */ | 1 /** no keepalives (not found) */ | 2 /** no connection re-use, opening keepalive connection failed */ | 3 /** no connection re-use, keepalive accepted and saved */ | 4 /** connection re-use, refused by the origin server (`TCP FIN`) */ | 5; /** connection re-use, accepted by the origin server */ /** ISO 3166-1 Alpha-2 codes */ declare type Iso3166Alpha2Code = "AD" | "AE" | "AF" | "AG" | "AI" | "AL" | "AM" | "AO" | "AQ" | "AR" | "AS" | "AT" | "AU" | "AW" | "AX" | "AZ" | "BA" | "BB" | "BD" | "BE" | "BF" | "BG" | "BH" | "BI" | "BJ" | "BL" | "BM" | "BN" | "BO" | "BQ" | "BR" | "BS" | "BT" | "BV" | "BW" | "BY" | "BZ" | "CA" | "CC" | "CD" | "CF" | "CG" | "CH" | "CI" | "CK" | "CL" | "CM" | "CN" | "CO" | "CR" | "CU" | "CV" | "CW" | "CX" | "CY" | "CZ" | "DE" | "DJ" | "DK" | "DM" | "DO" | "DZ" | "EC" | "EE" | "EG" | "EH" | "ER" | "ES" | "ET" | "FI" | "FJ" | "FK" | "FM" | "FO" | "FR" | "GA" | "GB" | "GD" | "GE" | "GF" | "GG" | "GH" | "GI" | "GL" | "GM" | "GN" | "GP" | "GQ" | "GR" | "GS" | "GT" | "GU" | "GW" | "GY" | "HK" | "HM" | "HN" | "HR" | "HT" | "HU" | "ID" | "IE" | "IL" | "IM" | "IN" | "IO" | "IQ" | "IR" | "IS" | "IT" | "JE" | "JM" | "JO" | "JP" | "KE" | "KG" | "KH" | "KI" | "KM" | "KN" | "KP" | "KR" | "KW" | "KY" | "KZ" | "LA" | "LB" | "LC" | "LI" | "LK" | "LR" | "LS" | "LT" | "LU" | "LV" | "LY" | "MA" | "MC" | "MD" | "ME" | "MF" | "MG" | "MH" | "MK" | "ML" | "MM" | "MN" | "MO" | "MP" | "MQ" | "MR" | "MS" | "MT" | "MU" | "MV" | "MW" | "MX" | "MY" | "MZ" | "NA" | "NC" | "NE" | "NF" | "NG" | "NI" | "NL" | "NO" | "NP" | "NR" | "NU" | "NZ" | "OM" | "PA" | "PE" | "PF" | "PG" | "PH" | "PK" | "PL" | "PM" | "PN" | "PR" | "PS" | "PT" | "PW" | "PY" | "QA" | "RE" | "RO" | "RS" | "RU" | "RW" | "SA" | "SB" | "SC" | "SD" | "SE" | "SG" | "SH" | "SI" | "SJ" | "SK" | "SL" | "SM" | "SN" | "SO" | "SR" | "SS" | "ST" | "SV" | "SX" | "SY" | "SZ" | "TC" | "TD" | "TF" | "TG" | "TH" | "TJ" | "TK" | "TL" | "TM" | "TN" | "TO" | "TR" | "TT" | "TV" | "TW" | "TZ" | "UA" | "UG" | "UM" | "US" | "UY" | "UZ" | "VA" | "VC" | "VE" | "VG" | "VI" | "VN" | "VU" | "WF" | "WS" | "YE" | "YT" | "ZA" | "ZM" | "ZW"; /** The 2-letter continent codes Cloudflare uses */ declare type ContinentCode = "AF" | "AN" | "AS" | "EU" | "NA" | "OC" | "SA"; type CfProperties = IncomingRequestCfProperties | RequestInitCfProperties; interface D1Meta { duration: number; size_after: number; rows_read: number; rows_written: number; last_row_id: number; changed_db: boolean; changes: number; /** * The region of the database instance that executed the query. */ served_by_region?: string; /** * True if-and-only-if the database instance that executed the query was the primary. */ served_by_primary?: boolean; timings?: { /** * The duration of the SQL query execution by the database instance. It doesn't include any network time. */ sql_duration_ms: number; }; /** * Number of total attempts to execute the query, due to automatic retries. * Note: All other fields in the response like `timings` only apply to the last attempt. */ total_attempts?: number; } interface D1Response { success: true; meta: D1Meta & Record; error?: never; } type D1Result = D1Response & { results: T[]; }; interface D1ExecResult { count: number; duration: number; } type D1SessionConstraint = // Indicates that the first query should go to the primary, and the rest queries // using the same D1DatabaseSession will go to any replica that is consistent with // the bookmark maintained by the session (returned by the first query). 'first-primary' // Indicates that the first query can go anywhere (primary or replica), and the rest queries // using the same D1DatabaseSession will go to any replica that is consistent with // the bookmark maintained by the session (returned by the first query). | 'first-unconstrained'; type D1SessionBookmark = string; declare abstract class D1Database { prepare(query: string): D1PreparedStatement; batch(statements: D1PreparedStatement[]): Promise[]>; exec(query: string): Promise; /** * Creates a new D1 Session anchored at the given constraint or the bookmark. * All queries executed using the created session will have sequential consistency, * meaning that all writes done through the session will be visible in subsequent reads. * * @param constraintOrBookmark Either the session constraint or the explicit bookmark to anchor the created session. */ withSession(constraintOrBookmark?: D1SessionBookmark | D1SessionConstraint): D1DatabaseSession; /** * @deprecated dump() will be removed soon, only applies to deprecated alpha v1 databases. */ dump(): Promise; } declare abstract class D1DatabaseSession { prepare(query: string): D1PreparedStatement; batch(statements: D1PreparedStatement[]): Promise[]>; /** * @returns The latest session bookmark across all executed queries on the session. * If no query has been executed yet, `null` is returned. */ getBookmark(): D1SessionBookmark | null; } declare abstract class D1PreparedStatement { bind(...values: unknown[]): D1PreparedStatement; first(colName: string): Promise; first>(): Promise; run>(): Promise>; all>(): Promise>; raw(options: { columnNames: true; }): Promise<[ string[], ...T[] ]>; raw(options?: { columnNames?: false; }): Promise; } // `Disposable` was added to TypeScript's standard lib types in version 5.2. // To support older TypeScript versions, define an empty `Disposable` interface. // Users won't be able to use `using`/`Symbol.dispose` without upgrading to 5.2, // but this will ensure type checking on older versions still passes. // TypeScript's interface merging will ensure our empty interface is effectively // ignored when `Disposable` is included in the standard lib. interface Disposable { } /** * An email message that can be sent from a Worker. */ interface EmailMessage { /** * Envelope From attribute of the email message. */ readonly from: string; /** * Envelope To attribute of the email message. */ readonly to: string; } /** * An email message that is sent to a consumer Worker and can be rejected/forwarded. */ interface ForwardableEmailMessage extends EmailMessage { /** * Stream of the email message content. */ readonly raw: ReadableStream; /** * An [Headers object](https://developer.mozilla.org/en-US/docs/Web/API/Headers). */ readonly headers: Headers; /** * Size of the email message content. */ readonly rawSize: number; /** * Reject this email message by returning a permanent SMTP error back to the connecting client including the given reason. * @param reason The reject reason. * @returns void */ setReject(reason: string): void; /** * Forward this email message to a verified destination address of the account. * @param rcptTo Verified destination address. * @param headers A [Headers object](https://developer.mozilla.org/en-US/docs/Web/API/Headers). * @returns A promise that resolves when the email message is forwarded. */ forward(rcptTo: string, headers?: Headers): Promise; /** * Reply to the sender of this email message with a new EmailMessage object. * @param message The reply message. * @returns A promise that resolves when the email message is replied. */ reply(message: EmailMessage): Promise; } /** * A binding that allows a Worker to send email messages. */ interface SendEmail { send(message: EmailMessage): Promise; } declare abstract class EmailEvent extends ExtendableEvent { readonly message: ForwardableEmailMessage; } declare type EmailExportedHandler = (message: ForwardableEmailMessage, env: Env, ctx: ExecutionContext) => void | Promise; declare module "cloudflare:email" { let _EmailMessage: { prototype: EmailMessage; new (from: string, to: string, raw: ReadableStream | string): EmailMessage; }; export { _EmailMessage as EmailMessage }; } /** * Hello World binding to serve as an explanatory example. DO NOT USE */ interface HelloWorldBinding { /** * Retrieve the current stored value */ get(): Promise<{ value: string; ms?: number; }>; /** * Set a new stored value */ set(value: string): Promise; } interface Hyperdrive { /** * Connect directly to Hyperdrive as if it's your database, returning a TCP socket. * * Calling this method returns an idential socket to if you call * `connect("host:port")` using the `host` and `port` fields from this object. * Pick whichever approach works better with your preferred DB client library. * * Note that this socket is not yet authenticated -- it's expected that your * code (or preferably, the client library of your choice) will authenticate * using the information in this class's readonly fields. */ connect(): Socket; /** * A valid DB connection string that can be passed straight into the typical * client library/driver/ORM. This will typically be the easiest way to use * Hyperdrive. */ readonly connectionString: string; /* * A randomly generated hostname that is only valid within the context of the * currently running Worker which, when passed into `connect()` function from * the "cloudflare:sockets" module, will connect to the Hyperdrive instance * for your database. */ readonly host: string; /* * The port that must be paired the the host field when connecting. */ readonly port: number; /* * The username to use when authenticating to your database via Hyperdrive. * Unlike the host and password, this will be the same every time */ readonly user: string; /* * The randomly generated password to use when authenticating to your * database via Hyperdrive. Like the host field, this password is only valid * within the context of the currently running Worker instance from which * it's read. */ readonly password: string; /* * The name of the database to connect to. */ readonly database: string; } // Copyright (c) 2024 Cloudflare, Inc. // Licensed under the Apache 2.0 license found in the LICENSE file or at: // https://opensource.org/licenses/Apache-2.0 type ImageInfoResponse = { format: 'image/svg+xml'; } | { format: string; fileSize: number; width: number; height: number; }; type ImageTransform = { width?: number; height?: number; background?: string; blur?: number; border?: { color?: string; width?: number; } | { top?: number; bottom?: number; left?: number; right?: number; }; brightness?: number; contrast?: number; fit?: 'scale-down' | 'contain' | 'pad' | 'squeeze' | 'cover' | 'crop'; flip?: 'h' | 'v' | 'hv'; gamma?: number; segment?: 'foreground'; gravity?: 'face' | 'left' | 'right' | 'top' | 'bottom' | 'center' | 'auto' | 'entropy' | { x?: number; y?: number; mode: 'remainder' | 'box-center'; }; rotate?: 0 | 90 | 180 | 270; saturation?: number; sharpen?: number; trim?: 'border' | { top?: number; bottom?: number; left?: number; right?: number; width?: number; height?: number; border?: boolean | { color?: string; tolerance?: number; keep?: number; }; }; }; type ImageDrawOptions = { opacity?: number; repeat?: boolean | string; top?: number; left?: number; bottom?: number; right?: number; }; type ImageInputOptions = { encoding?: 'base64'; }; type ImageOutputOptions = { format: 'image/jpeg' | 'image/png' | 'image/gif' | 'image/webp' | 'image/avif' | 'rgb' | 'rgba'; quality?: number; background?: string; anim?: boolean; }; interface ImagesBinding { /** * Get image metadata (type, width and height) * @throws {@link ImagesError} with code 9412 if input is not an image * @param stream The image bytes */ info(stream: ReadableStream, options?: ImageInputOptions): Promise; /** * Begin applying a series of transformations to an image * @param stream The image bytes * @returns A transform handle */ input(stream: ReadableStream, options?: ImageInputOptions): ImageTransformer; } interface ImageTransformer { /** * Apply transform next, returning a transform handle. * You can then apply more transformations, draw, or retrieve the output. * @param transform */ transform(transform: ImageTransform): ImageTransformer; /** * Draw an image on this transformer, returning a transform handle. * You can then apply more transformations, draw, or retrieve the output. * @param image The image (or transformer that will give the image) to draw * @param options The options configuring how to draw the image */ draw(image: ReadableStream | ImageTransformer, options?: ImageDrawOptions): ImageTransformer; /** * Retrieve the image that results from applying the transforms to the * provided input * @param options Options that apply to the output e.g. output format */ output(options: ImageOutputOptions): Promise; } type ImageTransformationOutputOptions = { encoding?: 'base64'; }; interface ImageTransformationResult { /** * The image as a response, ready to store in cache or return to users */ response(): Response; /** * The content type of the returned image */ contentType(): string; /** * The bytes of the response */ image(options?: ImageTransformationOutputOptions): ReadableStream; } interface ImagesError extends Error { readonly code: number; readonly message: string; readonly stack?: string; } /** * Media binding for transforming media streams. * Provides the entry point for media transformation operations. */ interface MediaBinding { /** * Creates a media transformer from an input stream. * @param media - The input media bytes * @returns A MediaTransformer instance for applying transformations */ input(media: ReadableStream): MediaTransformer; } /** * Media transformer for applying transformation operations to media content. * Handles sizing, fitting, and other input transformation parameters. */ interface MediaTransformer { /** * Applies transformation options to the media content. * @param transform - Configuration for how the media should be transformed * @returns A generator for producing the transformed media output */ transform(transform: MediaTransformationInputOptions): MediaTransformationGenerator; } /** * Generator for producing media transformation results. * Configures the output format and parameters for the transformed media. */ interface MediaTransformationGenerator { /** * Generates the final media output with specified options. * @param output - Configuration for the output format and parameters * @returns The final transformation result containing the transformed media */ output(output: MediaTransformationOutputOptions): MediaTransformationResult; } /** * Result of a media transformation operation. * Provides multiple ways to access the transformed media content. */ interface MediaTransformationResult { /** * Returns the transformed media as a readable stream of bytes. * @returns A stream containing the transformed media data */ media(): ReadableStream; /** * Returns the transformed media as an HTTP response object. * @returns The transformed media as a Response, ready to store in cache or return to users */ response(): Response; /** * Returns the MIME type of the transformed media. * @returns The content type string (e.g., 'image/jpeg', 'video/mp4') */ contentType(): string; } /** * Configuration options for transforming media input. * Controls how the media should be resized and fitted. */ type MediaTransformationInputOptions = { /** How the media should be resized to fit the specified dimensions */ fit?: 'contain' | 'cover' | 'scale-down'; /** Target width in pixels */ width?: number; /** Target height in pixels */ height?: number; }; /** * Configuration options for Media Transformations output. * Controls the format, timing, and type of the generated output. */ type MediaTransformationOutputOptions = { /** * Output mode determining the type of media to generate */ mode?: 'video' | 'spritesheet' | 'frame' | 'audio'; /** Whether to include audio in the output */ audio?: boolean; /** * Starting timestamp for frame extraction or start time for clips. (e.g. '2s'). */ time?: string; /** * Duration for video clips, audio extraction, and spritesheet generation (e.g. '5s'). */ duration?: string; /** * Output format for the generated media. */ format?: 'jpg' | 'png' | 'm4a'; }; /** * Error object for media transformation operations. * Extends the standard Error interface with additional media-specific information. */ interface MediaError extends Error { readonly code: number; readonly message: string; readonly stack?: string; } type Params

= Record; type EventContext = { request: Request>; functionPath: string; waitUntil: (promise: Promise) => void; passThroughOnException: () => void; next: (input?: Request | string, init?: RequestInit) => Promise; env: Env & { ASSETS: { fetch: typeof fetch; }; }; params: Params

; data: Data; }; type PagesFunction = Record> = (context: EventContext) => Response | Promise; type EventPluginContext = { request: Request>; functionPath: string; waitUntil: (promise: Promise) => void; passThroughOnException: () => void; next: (input?: Request | string, init?: RequestInit) => Promise; env: Env & { ASSETS: { fetch: typeof fetch; }; }; params: Params

; data: Data; pluginArgs: PluginArgs; }; type PagesPluginFunction = Record, PluginArgs = unknown> = (context: EventPluginContext) => Response | Promise; declare module "assets:*" { export const onRequest: PagesFunction; } // Copyright (c) 2022-2023 Cloudflare, Inc. // Licensed under the Apache 2.0 license found in the LICENSE file or at: // https://opensource.org/licenses/Apache-2.0 declare module "cloudflare:pipelines" { export abstract class PipelineTransformationEntrypoint { protected env: Env; protected ctx: ExecutionContext; constructor(ctx: ExecutionContext, env: Env); /** * run recieves an array of PipelineRecord which can be * transformed and returned to the pipeline * @param records Incoming records from the pipeline to be transformed * @param metadata Information about the specific pipeline calling the transformation entrypoint * @returns A promise containing the transformed PipelineRecord array */ public run(records: I[], metadata: PipelineBatchMetadata): Promise; } export type PipelineRecord = Record; export type PipelineBatchMetadata = { pipelineId: string; pipelineName: string; }; export interface Pipeline { /** * The Pipeline interface represents the type of a binding to a Pipeline * * @param records The records to send to the pipeline */ send(records: T[]): Promise; } } // PubSubMessage represents an incoming PubSub message. // The message includes metadata about the broker, the client, and the payload // itself. // https://developers.cloudflare.com/pub-sub/ interface PubSubMessage { // Message ID readonly mid: number; // MQTT broker FQDN in the form mqtts://BROKER.NAMESPACE.cloudflarepubsub.com:PORT readonly broker: string; // The MQTT topic the message was sent on. readonly topic: string; // The client ID of the client that published this message. readonly clientId: string; // The unique identifier (JWT ID) used by the client to authenticate, if token // auth was used. readonly jti?: string; // A Unix timestamp (seconds from Jan 1, 1970), set when the Pub/Sub Broker // received the message from the client. readonly receivedAt: number; // An (optional) string with the MIME type of the payload, if set by the // client. readonly contentType: string; // Set to 1 when the payload is a UTF-8 string // https://docs.oasis-open.org/mqtt/mqtt/v5.0/os/mqtt-v5.0-os.html#_Toc3901063 readonly payloadFormatIndicator: number; // Pub/Sub (MQTT) payloads can be UTF-8 strings, or byte arrays. // You can use payloadFormatIndicator to inspect this before decoding. payload: string | Uint8Array; } // JsonWebKey extended by kid parameter interface JsonWebKeyWithKid extends JsonWebKey { // Key Identifier of the JWK readonly kid: string; } interface RateLimitOptions { key: string; } interface RateLimitOutcome { success: boolean; } interface RateLimit { /** * Rate limit a request based on the provided options. * @see https://developers.cloudflare.com/workers/runtime-apis/bindings/rate-limit/ * @returns A promise that resolves with the outcome of the rate limit. */ limit(options: RateLimitOptions): Promise; } // Namespace for RPC utility types. Unfortunately, we can't use a `module` here as these types need // to referenced by `Fetcher`. This is included in the "importable" version of the types which // strips all `module` blocks. declare namespace Rpc { // Branded types for identifying `WorkerEntrypoint`/`DurableObject`/`Target`s. // TypeScript uses *structural* typing meaning anything with the same shape as type `T` is a `T`. // For the classes exported by `cloudflare:workers` we want *nominal* typing (i.e. we only want to // accept `WorkerEntrypoint` from `cloudflare:workers`, not any other class with the same shape) export const __RPC_STUB_BRAND: '__RPC_STUB_BRAND'; export const __RPC_TARGET_BRAND: '__RPC_TARGET_BRAND'; export const __WORKER_ENTRYPOINT_BRAND: '__WORKER_ENTRYPOINT_BRAND'; export const __DURABLE_OBJECT_BRAND: '__DURABLE_OBJECT_BRAND'; export const __WORKFLOW_ENTRYPOINT_BRAND: '__WORKFLOW_ENTRYPOINT_BRAND'; export interface RpcTargetBranded { [__RPC_TARGET_BRAND]: never; } export interface WorkerEntrypointBranded { [__WORKER_ENTRYPOINT_BRAND]: never; } export interface DurableObjectBranded { [__DURABLE_OBJECT_BRAND]: never; } export interface WorkflowEntrypointBranded { [__WORKFLOW_ENTRYPOINT_BRAND]: never; } export type EntrypointBranded = WorkerEntrypointBranded | DurableObjectBranded | WorkflowEntrypointBranded; // Types that can be used through `Stub`s export type Stubable = RpcTargetBranded | ((...args: any[]) => any); // Types that can be passed over RPC // The reason for using a generic type here is to build a serializable subset of structured // cloneable composite types. This allows types defined with the "interface" keyword to pass the // serializable check as well. Otherwise, only types defined with the "type" keyword would pass. type Serializable = // Structured cloneables BaseType // Structured cloneable composites | Map ? Serializable : never, T extends Map ? Serializable : never> | Set ? Serializable : never> | ReadonlyArray ? Serializable : never> | { [K in keyof T]: K extends number | string ? Serializable : never; } // Special types | Stub // Serialized as stubs, see `Stubify` | Stubable; // Base type for all RPC stubs, including common memory management methods. // `T` is used as a marker type for unwrapping `Stub`s later. interface StubBase extends Disposable { [__RPC_STUB_BRAND]: T; dup(): this; } export type Stub = Provider & StubBase; // This represents all the types that can be sent as-is over an RPC boundary type BaseType = void | undefined | null | boolean | number | bigint | string | TypedArray | ArrayBuffer | DataView | Date | Error | RegExp | ReadableStream | WritableStream | Request | Response | Headers; // Recursively rewrite all `Stubable` types with `Stub`s // prettier-ignore type Stubify = T extends Stubable ? Stub : T extends Map ? Map, Stubify> : T extends Set ? Set> : T extends Array ? Array> : T extends ReadonlyArray ? ReadonlyArray> : T extends BaseType ? T : T extends { [key: string | number]: any; } ? { [K in keyof T]: Stubify; } : T; // Recursively rewrite all `Stub`s with the corresponding `T`s. // Note we use `StubBase` instead of `Stub` here to avoid circular dependencies: // `Stub` depends on `Provider`, which depends on `Unstubify`, which would depend on `Stub`. // prettier-ignore type Unstubify = T extends StubBase ? V : T extends Map ? Map, Unstubify> : T extends Set ? Set> : T extends Array ? Array> : T extends ReadonlyArray ? ReadonlyArray> : T extends BaseType ? T : T extends { [key: string | number]: unknown; } ? { [K in keyof T]: Unstubify; } : T; type UnstubifyAll = { [I in keyof A]: Unstubify; }; // Utility type for adding `Provider`/`Disposable`s to `object` types only. // Note `unknown & T` is equivalent to `T`. type MaybeProvider = T extends object ? Provider : unknown; type MaybeDisposable = T extends object ? Disposable : unknown; // Type for method return or property on an RPC interface. // - Stubable types are replaced by stubs. // - Serializable types are passed by value, with stubable types replaced by stubs // and a top-level `Disposer`. // Everything else can't be passed over PRC. // Technically, we use custom thenables here, but they quack like `Promise`s. // Intersecting with `(Maybe)Provider` allows pipelining. // prettier-ignore type Result = R extends Stubable ? Promise> & Provider : R extends Serializable ? Promise & MaybeDisposable> & MaybeProvider : never; // Type for method or property on an RPC interface. // For methods, unwrap `Stub`s in parameters, and rewrite returns to be `Result`s. // Unwrapping `Stub`s allows calling with `Stubable` arguments. // For properties, rewrite types to be `Result`s. // In each case, unwrap `Promise`s. type MethodOrProperty = V extends (...args: infer P) => infer R ? (...args: UnstubifyAll

) => Result> : Result>; // Type for the callable part of an `Provider` if `T` is callable. // This is intersected with methods/properties. type MaybeCallableProvider = T extends (...args: any[]) => any ? MethodOrProperty : unknown; // Base type for all other types providing RPC-like interfaces. // Rewrites all methods/properties to be `MethodOrProperty`s, while preserving callable types. // `Reserved` names (e.g. stub method names like `dup()`) and symbols can't be accessed over RPC. export type Provider = MaybeCallableProvider & { [K in Exclude>]: MethodOrProperty; }; } declare namespace Cloudflare { // Type of `env`. // // The specific project can extend `Env` by redeclaring it in project-specific files. Typescript // will merge all declarations. // // You can use `wrangler types` to generate the `Env` type automatically. interface Env { } // Project-specific parameters used to inform types. // // This interface is, again, intended to be declared in project-specific files, and then that // declaration will be merged with this one. // // A project should have a declaration like this: // // interface GlobalProps { // // Declares the main module's exports. Used to populate Cloudflare.Exports aka the type // // of `ctx.exports`. // mainModule: typeof import("my-main-module"); // // // Declares which of the main module's exports are configured with durable storage, and // // thus should behave as Durable Object namsepace bindings. // durableNamespaces: "MyDurableObject" | "AnotherDurableObject"; // } // // You can use `wrangler types` to generate `GlobalProps` automatically. interface GlobalProps { } // Evaluates to the type of a property in GlobalProps, defaulting to `Default` if it is not // present. type GlobalProp = K extends keyof GlobalProps ? GlobalProps[K] : Default; // The type of the program's main module exports, if known. Requires `GlobalProps` to declare the // `mainModule` property. type MainModule = GlobalProp<"mainModule", {}>; // The type of ctx.exports, which contains loopback bindings for all top-level exports. type Exports = { [K in keyof MainModule]: LoopbackForExport // If the export is listed in `durableNamespaces`, then it is also a // DurableObjectNamespace. & (K extends GlobalProp<"durableNamespaces", never> ? MainModule[K] extends new (...args: any[]) => infer DoInstance ? DoInstance extends Rpc.DurableObjectBranded ? DurableObjectNamespace : DurableObjectNamespace : DurableObjectNamespace : {}); }; } declare module 'cloudflare:node' { export interface DefaultHandler { fetch?(request: Request): Response | Promise; tail?(events: TraceItem[]): void | Promise; trace?(traces: TraceItem[]): void | Promise; scheduled?(controller: ScheduledController): void | Promise; queue?(batch: MessageBatch): void | Promise; test?(controller: TestController): void | Promise; } export function httpServerHandler(options: { port: number; }, handlers?: Omit): DefaultHandler; } declare namespace CloudflareWorkersModule { export type RpcStub = Rpc.Stub; export const RpcStub: { new (value: T): Rpc.Stub; }; export abstract class RpcTarget implements Rpc.RpcTargetBranded { [Rpc.__RPC_TARGET_BRAND]: never; } // `protected` fields don't appear in `keyof`s, so can't be accessed over RPC export abstract class WorkerEntrypoint implements Rpc.WorkerEntrypointBranded { [Rpc.__WORKER_ENTRYPOINT_BRAND]: never; protected ctx: ExecutionContext; protected env: Env; constructor(ctx: ExecutionContext, env: Env); fetch?(request: Request): Response | Promise; tail?(events: TraceItem[]): void | Promise; trace?(traces: TraceItem[]): void | Promise; scheduled?(controller: ScheduledController): void | Promise; queue?(batch: MessageBatch): void | Promise; test?(controller: TestController): void | Promise; } export abstract class DurableObject implements Rpc.DurableObjectBranded { [Rpc.__DURABLE_OBJECT_BRAND]: never; protected ctx: DurableObjectState; protected env: Env; constructor(ctx: DurableObjectState, env: Env); fetch?(request: Request): Response | Promise; alarm?(alarmInfo?: AlarmInvocationInfo): void | Promise; webSocketMessage?(ws: WebSocket, message: string | ArrayBuffer): void | Promise; webSocketClose?(ws: WebSocket, code: number, reason: string, wasClean: boolean): void | Promise; webSocketError?(ws: WebSocket, error: unknown): void | Promise; } export type WorkflowDurationLabel = 'second' | 'minute' | 'hour' | 'day' | 'week' | 'month' | 'year'; export type WorkflowSleepDuration = `${number} ${WorkflowDurationLabel}${'s' | ''}` | number; export type WorkflowDelayDuration = WorkflowSleepDuration; export type WorkflowTimeoutDuration = WorkflowSleepDuration; export type WorkflowRetentionDuration = WorkflowSleepDuration; export type WorkflowBackoff = 'constant' | 'linear' | 'exponential'; export type WorkflowStepConfig = { retries?: { limit: number; delay: WorkflowDelayDuration | number; backoff?: WorkflowBackoff; }; timeout?: WorkflowTimeoutDuration | number; }; export type WorkflowEvent = { payload: Readonly; timestamp: Date; instanceId: string; }; export type WorkflowStepEvent = { payload: Readonly; timestamp: Date; type: string; }; export abstract class WorkflowStep { do>(name: string, callback: () => Promise): Promise; do>(name: string, config: WorkflowStepConfig, callback: () => Promise): Promise; sleep: (name: string, duration: WorkflowSleepDuration) => Promise; sleepUntil: (name: string, timestamp: Date | number) => Promise; waitForEvent>(name: string, options: { type: string; timeout?: WorkflowTimeoutDuration | number; }): Promise>; } export abstract class WorkflowEntrypoint | unknown = unknown> implements Rpc.WorkflowEntrypointBranded { [Rpc.__WORKFLOW_ENTRYPOINT_BRAND]: never; protected ctx: ExecutionContext; protected env: Env; constructor(ctx: ExecutionContext, env: Env); run(event: Readonly>, step: WorkflowStep): Promise; } export function waitUntil(promise: Promise): void; export const env: Cloudflare.Env; } declare module 'cloudflare:workers' { export = CloudflareWorkersModule; } interface SecretsStoreSecret { /** * Get a secret from the Secrets Store, returning a string of the secret value * if it exists, or throws an error if it does not exist */ get(): Promise; } declare module "cloudflare:sockets" { function _connect(address: string | SocketAddress, options?: SocketOptions): Socket; export { _connect as connect }; } declare namespace TailStream { interface Header { readonly name: string; readonly value: string; } interface FetchEventInfo { readonly type: "fetch"; readonly method: string; readonly url: string; readonly cfJson?: object; readonly headers: Header[]; } interface JsRpcEventInfo { readonly type: "jsrpc"; } interface ScheduledEventInfo { readonly type: "scheduled"; readonly scheduledTime: Date; readonly cron: string; } interface AlarmEventInfo { readonly type: "alarm"; readonly scheduledTime: Date; } interface QueueEventInfo { readonly type: "queue"; readonly queueName: string; readonly batchSize: number; } interface EmailEventInfo { readonly type: "email"; readonly mailFrom: string; readonly rcptTo: string; readonly rawSize: number; } interface TraceEventInfo { readonly type: "trace"; readonly traces: (string | null)[]; } interface HibernatableWebSocketEventInfoMessage { readonly type: "message"; } interface HibernatableWebSocketEventInfoError { readonly type: "error"; } interface HibernatableWebSocketEventInfoClose { readonly type: "close"; readonly code: number; readonly wasClean: boolean; } interface HibernatableWebSocketEventInfo { readonly type: "hibernatableWebSocket"; readonly info: HibernatableWebSocketEventInfoClose | HibernatableWebSocketEventInfoError | HibernatableWebSocketEventInfoMessage; } interface CustomEventInfo { readonly type: "custom"; } interface FetchResponseInfo { readonly type: "fetch"; readonly statusCode: number; } type EventOutcome = "ok" | "canceled" | "exception" | "unknown" | "killSwitch" | "daemonDown" | "exceededCpu" | "exceededMemory" | "loadShed" | "responseStreamDisconnected" | "scriptNotFound"; interface ScriptVersion { readonly id: string; readonly tag?: string; readonly message?: string; } interface Onset { readonly type: "onset"; readonly attributes: Attribute[]; // id for the span being opened by this Onset event. readonly spanId: string; readonly dispatchNamespace?: string; readonly entrypoint?: string; readonly executionModel: string; readonly scriptName?: string; readonly scriptTags?: string[]; readonly scriptVersion?: ScriptVersion; readonly info: FetchEventInfo | JsRpcEventInfo | ScheduledEventInfo | AlarmEventInfo | QueueEventInfo | EmailEventInfo | TraceEventInfo | HibernatableWebSocketEventInfo | CustomEventInfo; } interface Outcome { readonly type: "outcome"; readonly outcome: EventOutcome; readonly cpuTime: number; readonly wallTime: number; } interface SpanOpen { readonly type: "spanOpen"; readonly name: string; // id for the span being opened by this SpanOpen event. readonly spanId: string; readonly info?: FetchEventInfo | JsRpcEventInfo | Attributes; } interface SpanClose { readonly type: "spanClose"; readonly outcome: EventOutcome; } interface DiagnosticChannelEvent { readonly type: "diagnosticChannel"; readonly channel: string; readonly message: any; } interface Exception { readonly type: "exception"; readonly name: string; readonly message: string; readonly stack?: string; } interface Log { readonly type: "log"; readonly level: "debug" | "error" | "info" | "log" | "warn"; readonly message: object; } // This marks the worker handler return information. // This is separate from Outcome because the worker invocation can live for a long time after // returning. For example - Websockets that return an http upgrade response but then continue // streaming information or SSE http connections. interface Return { readonly type: "return"; readonly info?: FetchResponseInfo; } interface Attribute { readonly name: string; readonly value: string | string[] | boolean | boolean[] | number | number[] | bigint | bigint[]; } interface Attributes { readonly type: "attributes"; readonly info: Attribute[]; } type EventType = Onset | Outcome | SpanOpen | SpanClose | DiagnosticChannelEvent | Exception | Log | Return | Attributes; // Context in which this trace event lives. interface SpanContext { // Single id for the entire top-level invocation // This should be a new traceId for the first worker stage invoked in the eyeball request and then // same-account service-bindings should reuse the same traceId but cross-account service-bindings // should use a new traceId. readonly traceId: string; // spanId in which this event is handled // for Onset and SpanOpen events this would be the parent span id // for Outcome and SpanClose these this would be the span id of the opening Onset and SpanOpen events // For Hibernate and Mark this would be the span under which they were emitted. // spanId is not set ONLY if: // 1. This is an Onset event // 2. We are not inherting any SpanContext. (e.g. this is a cross-account service binding or a new top-level invocation) readonly spanId?: string; } interface TailEvent { // invocation id of the currently invoked worker stage. // invocation id will always be unique to every Onset event and will be the same until the Outcome event. readonly invocationId: string; // Inherited spanContext for this event. readonly spanContext: SpanContext; readonly timestamp: Date; readonly sequence: number; readonly event: Event; } type TailEventHandler = (event: TailEvent) => void | Promise; type TailEventHandlerObject = { outcome?: TailEventHandler; spanOpen?: TailEventHandler; spanClose?: TailEventHandler; diagnosticChannel?: TailEventHandler; exception?: TailEventHandler; log?: TailEventHandler; return?: TailEventHandler; attributes?: TailEventHandler; }; type TailEventHandlerType = TailEventHandler | TailEventHandlerObject; } // Copyright (c) 2022-2023 Cloudflare, Inc. // Licensed under the Apache 2.0 license found in the LICENSE file or at: // https://opensource.org/licenses/Apache-2.0 /** * Data types supported for holding vector metadata. */ type VectorizeVectorMetadataValue = string | number | boolean | string[]; /** * Additional information to associate with a vector. */ type VectorizeVectorMetadata = VectorizeVectorMetadataValue | Record; type VectorFloatArray = Float32Array | Float64Array; interface VectorizeError { code?: number; error: string; } /** * Comparison logic/operation to use for metadata filtering. * * This list is expected to grow as support for more operations are released. */ type VectorizeVectorMetadataFilterOp = "$eq" | "$ne"; /** * Filter criteria for vector metadata used to limit the retrieved query result set. */ type VectorizeVectorMetadataFilter = { [field: string]: Exclude | null | { [Op in VectorizeVectorMetadataFilterOp]?: Exclude | null; }; }; /** * Supported distance metrics for an index. * Distance metrics determine how other "similar" vectors are determined. */ type VectorizeDistanceMetric = "euclidean" | "cosine" | "dot-product"; /** * Metadata return levels for a Vectorize query. * * Default to "none". * * @property all Full metadata for the vector return set, including all fields (including those un-indexed) without truncation. This is a more expensive retrieval, as it requires additional fetching & reading of un-indexed data. * @property indexed Return all metadata fields configured for indexing in the vector return set. This level of retrieval is "free" in that no additional overhead is incurred returning this data. However, note that indexed metadata is subject to truncation (especially for larger strings). * @property none No indexed metadata will be returned. */ type VectorizeMetadataRetrievalLevel = "all" | "indexed" | "none"; interface VectorizeQueryOptions { topK?: number; namespace?: string; returnValues?: boolean; returnMetadata?: boolean | VectorizeMetadataRetrievalLevel; filter?: VectorizeVectorMetadataFilter; } /** * Information about the configuration of an index. */ type VectorizeIndexConfig = { dimensions: number; metric: VectorizeDistanceMetric; } | { preset: string; // keep this generic, as we'll be adding more presets in the future and this is only in a read capacity }; /** * Metadata about an existing index. * * This type is exclusively for the Vectorize **beta** and will be deprecated once Vectorize RC is released. * See {@link VectorizeIndexInfo} for its post-beta equivalent. */ interface VectorizeIndexDetails { /** The unique ID of the index */ readonly id: string; /** The name of the index. */ name: string; /** (optional) A human readable description for the index. */ description?: string; /** The index configuration, including the dimension size and distance metric. */ config: VectorizeIndexConfig; /** The number of records containing vectors within the index. */ vectorsCount: number; } /** * Metadata about an existing index. */ interface VectorizeIndexInfo { /** The number of records containing vectors within the index. */ vectorCount: number; /** Number of dimensions the index has been configured for. */ dimensions: number; /** ISO 8601 datetime of the last processed mutation on in the index. All changes before this mutation will be reflected in the index state. */ processedUpToDatetime: number; /** UUIDv4 of the last mutation processed by the index. All changes before this mutation will be reflected in the index state. */ processedUpToMutation: number; } /** * Represents a single vector value set along with its associated metadata. */ interface VectorizeVector { /** The ID for the vector. This can be user-defined, and must be unique. It should uniquely identify the object, and is best set based on the ID of what the vector represents. */ id: string; /** The vector values */ values: VectorFloatArray | number[]; /** The namespace this vector belongs to. */ namespace?: string; /** Metadata associated with the vector. Includes the values of other fields and potentially additional details. */ metadata?: Record; } /** * Represents a matched vector for a query along with its score and (if specified) the matching vector information. */ type VectorizeMatch = Pick, "values"> & Omit & { /** The score or rank for similarity, when returned as a result */ score: number; }; /** * A set of matching {@link VectorizeMatch} for a particular query. */ interface VectorizeMatches { matches: VectorizeMatch[]; count: number; } /** * Results of an operation that performed a mutation on a set of vectors. * Here, `ids` is a list of vectors that were successfully processed. * * This type is exclusively for the Vectorize **beta** and will be deprecated once Vectorize RC is released. * See {@link VectorizeAsyncMutation} for its post-beta equivalent. */ interface VectorizeVectorMutation { /* List of ids of vectors that were successfully processed. */ ids: string[]; /* Total count of the number of processed vectors. */ count: number; } /** * Result type indicating a mutation on the Vectorize Index. * Actual mutations are processed async where the `mutationId` is the unique identifier for the operation. */ interface VectorizeAsyncMutation { /** The unique identifier for the async mutation operation containing the changeset. */ mutationId: string; } /** * A Vectorize Vector Search Index for querying vectors/embeddings. * * This type is exclusively for the Vectorize **beta** and will be deprecated once Vectorize RC is released. * See {@link Vectorize} for its new implementation. */ declare abstract class VectorizeIndex { /** * Get information about the currently bound index. * @returns A promise that resolves with information about the current index. */ public describe(): Promise; /** * Use the provided vector to perform a similarity search across the index. * @param vector Input vector that will be used to drive the similarity search. * @param options Configuration options to massage the returned data. * @returns A promise that resolves with matched and scored vectors. */ public query(vector: VectorFloatArray | number[], options?: VectorizeQueryOptions): Promise; /** * Insert a list of vectors into the index dataset. If a provided id exists, an error will be thrown. * @param vectors List of vectors that will be inserted. * @returns A promise that resolves with the ids & count of records that were successfully processed. */ public insert(vectors: VectorizeVector[]): Promise; /** * Upsert a list of vectors into the index dataset. If a provided id exists, it will be replaced with the new values. * @param vectors List of vectors that will be upserted. * @returns A promise that resolves with the ids & count of records that were successfully processed. */ public upsert(vectors: VectorizeVector[]): Promise; /** * Delete a list of vectors with a matching id. * @param ids List of vector ids that should be deleted. * @returns A promise that resolves with the ids & count of records that were successfully processed (and thus deleted). */ public deleteByIds(ids: string[]): Promise; /** * Get a list of vectors with a matching id. * @param ids List of vector ids that should be returned. * @returns A promise that resolves with the raw unscored vectors matching the id set. */ public getByIds(ids: string[]): Promise; } /** * A Vectorize Vector Search Index for querying vectors/embeddings. * * Mutations in this version are async, returning a mutation id. */ declare abstract class Vectorize { /** * Get information about the currently bound index. * @returns A promise that resolves with information about the current index. */ public describe(): Promise; /** * Use the provided vector to perform a similarity search across the index. * @param vector Input vector that will be used to drive the similarity search. * @param options Configuration options to massage the returned data. * @returns A promise that resolves with matched and scored vectors. */ public query(vector: VectorFloatArray | number[], options?: VectorizeQueryOptions): Promise; /** * Use the provided vector-id to perform a similarity search across the index. * @param vectorId Id for a vector in the index against which the index should be queried. * @param options Configuration options to massage the returned data. * @returns A promise that resolves with matched and scored vectors. */ public queryById(vectorId: string, options?: VectorizeQueryOptions): Promise; /** * Insert a list of vectors into the index dataset. If a provided id exists, an error will be thrown. * @param vectors List of vectors that will be inserted. * @returns A promise that resolves with a unique identifier of a mutation containing the insert changeset. */ public insert(vectors: VectorizeVector[]): Promise; /** * Upsert a list of vectors into the index dataset. If a provided id exists, it will be replaced with the new values. * @param vectors List of vectors that will be upserted. * @returns A promise that resolves with a unique identifier of a mutation containing the upsert changeset. */ public upsert(vectors: VectorizeVector[]): Promise; /** * Delete a list of vectors with a matching id. * @param ids List of vector ids that should be deleted. * @returns A promise that resolves with a unique identifier of a mutation containing the delete changeset. */ public deleteByIds(ids: string[]): Promise; /** * Get a list of vectors with a matching id. * @param ids List of vector ids that should be returned. * @returns A promise that resolves with the raw unscored vectors matching the id set. */ public getByIds(ids: string[]): Promise; } /** * The interface for "version_metadata" binding * providing metadata about the Worker Version using this binding. */ type WorkerVersionMetadata = { /** The ID of the Worker Version using this binding */ id: string; /** The tag of the Worker Version using this binding */ tag: string; /** The timestamp of when the Worker Version was uploaded */ timestamp: string; }; interface DynamicDispatchLimits { /** * Limit CPU time in milliseconds. */ cpuMs?: number; /** * Limit number of subrequests. */ subRequests?: number; } interface DynamicDispatchOptions { /** * Limit resources of invoked Worker script. */ limits?: DynamicDispatchLimits; /** * Arguments for outbound Worker script, if configured. */ outbound?: { [key: string]: any; }; } interface DispatchNamespace { /** * @param name Name of the Worker script. * @param args Arguments to Worker script. * @param options Options for Dynamic Dispatch invocation. * @returns A Fetcher object that allows you to send requests to the Worker script. * @throws If the Worker script does not exist in this dispatch namespace, an error will be thrown. */ get(name: string, args?: { [key: string]: any; }, options?: DynamicDispatchOptions): Fetcher; } declare module 'cloudflare:workflows' { /** * NonRetryableError allows for a user to throw a fatal error * that makes a Workflow instance fail immediately without triggering a retry */ export class NonRetryableError extends Error { public constructor(message: string, name?: string); } } declare abstract class Workflow { /** * Get a handle to an existing instance of the Workflow. * @param id Id for the instance of this Workflow * @returns A promise that resolves with a handle for the Instance */ public get(id: string): Promise; /** * Create a new instance and return a handle to it. If a provided id exists, an error will be thrown. * @param options Options when creating an instance including id and params * @returns A promise that resolves with a handle for the Instance */ public create(options?: WorkflowInstanceCreateOptions): Promise; /** * Create a batch of instances and return handle for all of them. If a provided id exists, an error will be thrown. * `createBatch` is limited at 100 instances at a time or when the RPC limit for the batch (1MiB) is reached. * @param batch List of Options when creating an instance including name and params * @returns A promise that resolves with a list of handles for the created instances. */ public createBatch(batch: WorkflowInstanceCreateOptions[]): Promise; } type WorkflowDurationLabel = 'second' | 'minute' | 'hour' | 'day' | 'week' | 'month' | 'year'; type WorkflowSleepDuration = `${number} ${WorkflowDurationLabel}${'s' | ''}` | number; type WorkflowRetentionDuration = WorkflowSleepDuration; interface WorkflowInstanceCreateOptions { /** * An id for your Workflow instance. Must be unique within the Workflow. */ id?: string; /** * The event payload the Workflow instance is triggered with */ params?: PARAMS; /** * The retention policy for Workflow instance. * Defaults to the maximum retention period available for the owner's account. */ retention?: { successRetention?: WorkflowRetentionDuration; errorRetention?: WorkflowRetentionDuration; }; } type InstanceStatus = { status: 'queued' // means that instance is waiting to be started (see concurrency limits) | 'running' | 'paused' | 'errored' | 'terminated' // user terminated the instance while it was running | 'complete' | 'waiting' // instance is hibernating and waiting for sleep or event to finish | 'waitingForPause' // instance is finishing the current work to pause | 'unknown'; error?: string; output?: object; }; interface WorkflowError { code?: number; message: string; } declare abstract class WorkflowInstance { public id: string; /** * Pause the instance. */ public pause(): Promise; /** * Resume the instance. If it is already running, an error will be thrown. */ public resume(): Promise; /** * Terminate the instance. If it is errored, terminated or complete, an error will be thrown. */ public terminate(): Promise; /** * Restart the instance. */ public restart(): Promise; /** * Returns the current status of the instance. */ public status(): Promise; /** * Send an event to this instance. */ public sendEvent({ type, payload, }: { type: string; payload: unknown; }): Promise; } ================================================ FILE: test/cloudflare/wrangler.jsonc ================================================ /** * For more details on how to configure Wrangler, refer to: * https://developers.cloudflare.com/workers/wrangler/configuration/ */ { "$schema": "node_modules/wrangler/config-schema.json", "name": "elysia-cf", "main": "src/index.ts", "compatibility_date": "2025-09-06" } ================================================ FILE: test/cookie/explicit.test.ts ================================================ import { describe, expect, it } from 'bun:test' import { Cookie, createCookieJar } from '../../src/cookies' import type { Context } from '../../src' const create = () => { const set: Context['set'] = { cookie: {}, headers: {} } const cookie = createCookieJar(set, {}) return { cookie, set } } describe('Explicit Cookie', () => { it('create cookie', () => { const { cookie, set } = create() cookie.name.value = 'himari' expect(set.cookie?.name).toEqual({ value: 'himari' }) }) it('add cookie attribute', () => { const { cookie, set } = create() cookie.name.value = 'himari' cookie.name.update({ domain: 'millennium.sh' }) expect(set.cookie?.name).toEqual({ value: 'himari', domain: 'millennium.sh' }) }) it('add cookie attribute without overwrite entire property', () => { const { cookie, set } = create() cookie.name.value = 'himari' cookie.name.domain = 'millennium.sh' cookie.name.update({ httpOnly: true, path: '/' }) expect(set.cookie?.name).toEqual({ value: 'himari', domain: 'millennium.sh', httpOnly: true, path: '/' }) }) it('set cookie attribute', () => { const { cookie, set } = create() cookie.name.value = 'himari' cookie.name.domain = 'millennium.sh' cookie.name.set({ httpOnly: true, path: '/' }) expect(set.cookie?.name).toEqual({ httpOnly: true, path: '/', value: 'himari' }) }) it('add cookie overwrite attribute if duplicated', () => { const { cookie, set } = create() cookie.name.set({ value: 'aru', domain: 'millennium.sh', httpOnly: true }) cookie.name.update({ domain: 'gehenna.sh' }) expect(set.cookie?.name).toEqual({ value: 'aru', domain: 'gehenna.sh', httpOnly: true }) }) it('default undefined cookie with undefined', () => { const { cookie, set } = create() cookie.name expect(cookie?.name?.value).toEqual(undefined) }) it('overwrite existing cookie', () => { const { cookie, set } = create() cookie.name.value = 'aru' cookie.name.value = 'himari' expect(set.cookie?.name).toEqual({ value: 'himari' }) }) it('remove cookie', () => { const { cookie, set } = create() cookie.name.value = 'himari' cookie.name.remove() expect(set.cookie?.name.expires?.getTime()).toBeLessThanOrEqual( Date.now() ) }) }) ================================================ FILE: test/cookie/implicit.test.ts ================================================ import { describe, expect, it } from 'bun:test' import { createCookieJar } from '../../src/cookies' import type { Context } from '../../src' const create = () => { const set: Context['set'] = { cookie: {}, headers: {} } const cookie = createCookieJar(set, {}) return { cookie, set } } describe('Implicit Cookie', () => { it('create cookie using setter', () => { const { cookie: { name }, set } = create() name.value = 'himari' expect(set.cookie?.name).toEqual({ value: 'himari' }) }) it('create cookie set function', () => { const { cookie: { name }, set } = create() name.set({ value: 'himari' }) expect(set.cookie?.name).toEqual({ value: 'himari' }) }) it('add cookie attribute using setter', () => { const { cookie: { name }, set } = create() name.value = 'himari' name.domain = 'millennium.sh' expect(set.cookie?.name).toEqual({ value: 'himari', domain: 'millennium.sh' }) }) it('add cookie attribute using setter', () => { const { cookie: { name }, set } = create() name.value = 'himari' name.update({ domain: 'millennium.sh' }) expect(set.cookie?.name).toEqual({ value: 'himari', domain: 'millennium.sh' }) }) it('add cookie attribute without overwrite entire property', () => { const { cookie: { name }, set } = create() name.set({ value: 'himari', domain: 'millennium.sh' }).update({ httpOnly: true, path: '/' }) expect(set.cookie?.name).toEqual({ value: 'himari', domain: 'millennium.sh', httpOnly: true, path: '/' }) }) it('set cookie attribute', () => { const { cookie: { name }, set } = create() name.set({ value: 'himari', domain: 'millennium.sh' }).set({ httpOnly: true, path: '/' }) expect(set.cookie?.name).toEqual({ httpOnly: true, path: '/', value: 'himari', }) }) it('add cookie overwrite attribute if duplicated', () => { const { cookie: { name }, set } = create() name.set({ value: 'aru', domain: 'millennium.sh', httpOnly: true }).update({ domain: 'gehenna.sh' }) expect(set.cookie?.name).toEqual({ value: 'aru', domain: 'gehenna.sh', httpOnly: true }) }) it('create cookie with empty string', () => { const { cookie: { name }, set } = create() name.value = '' expect(set.cookie?.name).toEqual({ value: '' }) }) it('Remove cookie', () => { const { cookie: { name }, set } = create() name.value = 'himari' name.remove() expect(set.cookie?.name.expires?.getTime()).toBeLessThanOrEqual( Date.now() ) }) }) ================================================ FILE: test/cookie/response.test.ts ================================================ import { describe, expect, it } from 'bun:test' import { Elysia, t } from '../../src' import { req } from '../utils' import { signCookie } from '../../src/utils' const secrets = 'We long for the seven wailings. We bear the koan of Jericho.' const getCookies = (response: Response) => // @ts-ignore response.headers.getAll('Set-Cookie').map((x) => { const value = decodeURIComponent(x) return value }) const app = new Elysia() .get( '/council', ({ cookie: { council } }) => (council.value = [ { name: 'Rin', affilation: 'Administration' } ]), { cookie: t.Cookie({ council: t.Optional( t.Array( t.Object({ name: t.String(), affilation: t.String() }) ) ) }) } ) .get('/create', ({ cookie: { name } }) => (name.value = 'Himari')) .get('/multiple', ({ cookie: { name, president } }) => { name.value = 'Himari' president.value = 'Rio' return 'ok' }) .get( '/update', ({ cookie: { name } }) => { name.value = 'seminar: Himari' return name.value }, { cookie: t.Cookie( { name: t.Optional(t.String()) }, { secrets, sign: ['name'] } ) } ) .get('/remove', ({ cookie }) => { for (const self of Object.values(cookie)) self.remove() return 'Deleted' }) .get('/remove-with-options', ({ cookie }) => { for (const self of Object.values(cookie)) self.remove() return 'Deleted' }) .get('/set', ({ cookie: { session } }) => { session.value = 'rin' session.set({ path: '/' }) }) describe('Cookie Response', () => { it('set cookie', async () => { const response = await app.handle(req('/create')) expect(getCookies(response)).toEqual(['name=Himari; Path=/']) }) it('set multiple cookie', async () => { const response = await app.handle(req('/multiple')) expect(getCookies(response)).toEqual([ 'name=Himari; Path=/', 'president=Rio; Path=/' ]) }) it('set JSON cookie', async () => { const response = await app.handle(req('/council')) expect(getCookies(response)).toEqual([ 'council=[{"name":"Rin","affilation":"Administration"}]; Path=/' ]) }) it('write cookie on different value', async () => { const response = await app.handle( req('/council', { headers: { cookie: 'council=' + encodeURIComponent( JSON.stringify([ { name: 'Aoi', affilation: 'Financial' } ]) ) } }) ) expect(getCookies(response)).toEqual([ 'council=[{"name":"Rin","affilation":"Administration"}]; Path=/' ]) }) it('remove cookie', async () => { const response = await app.handle( req('/remove', { headers: { cookie: 'council=' + encodeURIComponent( JSON.stringify([ { name: 'Rin', affilation: 'Administration' } ]) ) } }) ) expect(getCookies(response)[0]).toInclude( `council=; Max-Age=0; Path=/; Expires=${new Date(0).toUTCString()}` ) }) it('sign cookie', async () => { const response = await app.handle(req('/update')) expect(getCookies(response)).toEqual([ `name=${await signCookie('seminar: Himari', secrets)}; Path=/` ]) }) it('sign/unsign cookie', async () => { const response = await app.handle( req('/update', { headers: { cookie: `name=${await signCookie( 'seminar: Himari', secrets )}` } }) ) expect(response.status).toBe(200) }) it('inherits cookie settings', async () => { const app = new Elysia({ cookie: { secrets, sign: ['name'] } }).get( '/update', ({ cookie: { name } }) => { if (!name.value) name.value = 'seminar: Himari' return name.value }, { cookie: t.Cookie({ name: t.Optional(t.String()) }) } ) const response = await app.handle( req('/update', { headers: { cookie: `name=${await signCookie( 'seminar: Himari', secrets )}` } }) ) expect(response.status).toBe(200) }) it('sign all cookie', async () => { const app = new Elysia({ cookie: { secrets, sign: true } }).get( '/update', ({ cookie: { name } }) => { if (!name.value) name.value = 'seminar: Himari' return name.value }, { cookie: t.Cookie({ name: t.Optional(t.String()) }) } ) const response = await app.handle( req('/update', { headers: { cookie: `name=${await signCookie( 'seminar: Himari', secrets )}` } }) ) expect(response.status).toBe(200) }) it('set cookie property from constructor', async () => { const app = new Elysia({ cookie: { httpOnly: true, path: '' } }).get('/create', ({ cookie: { name } }) => (name.value = 'Himari')) const response = await app.handle(req('/create')) expect(response.headers.getAll('Set-Cookie')).toEqual([ 'name=Himari; Path=/; HttpOnly' ]) }) it('retain cookie value when using set if not provided', async () => { const response = await app.handle(req('/set')) expect(response.headers.getAll('Set-Cookie')).toEqual([ 'session=rin; Path=/' ]) }) it('parse object cookie', async () => { const app = new Elysia().get( '/council', ({ cookie: { council } }) => council.value, { cookie: t.Cookie({ council: t.Object({ name: t.String(), affilation: t.String() }) }) } ) const expected = { name: 'Rin', affilation: 'Administration' } const response = await app.handle( req('/council', { headers: { cookie: 'council=' + JSON.stringify(expected) } }) ) expect(response.status).toBe(200) expect(await response.json()).toEqual(expected) }) // this is removed there's no way to accurately determine object on Standard Schema // it("don't parse cookie type unless specified", async () => { // let value: unknown // const app = new Elysia().get( // '/council', // ({ cookie: { council } }) => (value = council.value) // ) // const expected = { // name: 'Rin', // affilation: 'Administration' // } // const response = await app.handle( // req('/council', { // headers: { // cookie: // 'council=' + // encodeURIComponent(JSON.stringify(expected)) // } // }) // ) // expect(response.status).toBe(200) // expect(value).toEqual(JSON.stringify(expected)) // }) it('handle optional at root', async () => { const app = new Elysia().get('/', ({ cookie: { id } }) => id.value, { cookie: t.Optional( t.Object({ id: t.Numeric() }) ) }) const res = await Promise.all([ app.handle(req('/')).then((x) => x.text()), app .handle( req('/', { headers: { cookie: 'id=1' } }) ) .then((x) => x.text()) ]) expect(res).toEqual(['', '1']) }) it("don't set cookie if new value is undefined", async () => { const app = new Elysia().get('/', ({ cookie: { id } }) => { id.value = undefined return 'a' }) const res = app.handle(req('/')).then((x) => x.headers.toJSON()) // @ts-expect-error expect(res).toEqual({}) }) it('set cookie attribute before value', async () => { const date = new Date(Date.now() + 1000 * 60 * 60 * 24) const app = new Elysia().get('/', ({ cookie }) => { cookie.my_cookie.expires = date cookie.my_cookie.value = 'my_cookie_value' return 'HI' }) const setCookie = await app .handle(new Request('http://localhost')) .then((x) => x.headers.getSetCookie()) expect(setCookie).toEqual([ `my_cookie=my_cookie_value; Path=/; Expires=${date.toUTCString()}` ]) }) it('should not set if value is duplicated', async () => { const app = new Elysia() .derive(({ cookie: { test } }) => { if (!test.value) { test.value = 'Hello, world!' } return {} }) .get('/', () => 'Hello, world!') const res = await app .handle( new Request('http://localhost:3000/', { headers: { cookie: 'test=Hello, world!' } }) ) .then((x) => x.headers) expect(res.getSetCookie()).toEqual([]) }) }) ================================================ FILE: test/cookie/signature.test.ts ================================================ import { describe, expect, it } from 'bun:test' import { parseCookie, Cookie } from '../../src/cookies' import { signCookie } from '../../src/utils' describe('Parse Cookie', () => { it('handle empty cookie', async () => { const set = { headers: {}, cookie: {} } const cookieString = '' const result = await parseCookie(set, cookieString) expect(result).toEqual({}) }) it('create cookie jar from cookie string', async () => { const set = { headers: {}, cookie: {} } const cookieString = 'fischl=Princess; eula=Noble; amber=Knight' const result = await parseCookie(set, cookieString) expect(result).toEqual({ fischl: expect.any(Cookie), eula: expect.any(Cookie), amber: expect.any(Cookie) }) }) it('unsign cookie signature', async () => { const set = { headers: {}, cookie: {} } const secrets = 'Fischl von Luftschloss Narfidort' const fischl = await signCookie('fischl', secrets) const cookieString = `fischl=${fischl}` const result = await parseCookie(set, cookieString, { secrets, sign: ['fischl'] }) expect(result.fischl.value).toEqual('fischl') }) it('unsign multiple signature', async () => { const set = { headers: {}, cookie: {} } const secrets = 'Fischl von Luftschloss Narfidort' const fischl = await signCookie('fischl', secrets) const eula = await signCookie('eula', secrets) const cookieString = `fischl=${fischl}; eula=${eula}` const result = await parseCookie(set, cookieString, { secrets, sign: ['fischl', 'eula'] }) expect(result.fischl.value).toEqual('fischl') expect(result.eula.value).toEqual('eula') }) // it('parse JSON value', async () => { // const set = { // headers: {}, // cookie: {} // } // const value = { // eula: 'Vengeance will be mine' // } // const cookieString = `letter=${encodeURIComponent( // JSON.stringify(value) // )}` // const result = await parseCookie(set, cookieString) // expect(result.letter.value).toEqual(value) // }) // it('parse true', async () => { // const set = { // headers: {}, // cookie: {} // } // const cookieString = `letter=true` // const result = await parseCookie(set, cookieString) // expect(result.letter.value).toEqual(true) // }) // it('parse false', async () => { // const set = { // headers: {}, // cookie: {} // } // const cookieString = `letter=false` // const result = await parseCookie(set, cookieString) // expect(result.letter.value).toEqual(false) // }) // it('parse number', async () => { // const set = { // headers: {}, // cookie: {} // } // const cookieString = `letter=123` // const result = await parseCookie(set, cookieString) // expect(result.letter.value).toEqual(123) // }) it('Unsign signature via secret rotation', async () => { const set = { headers: {}, cookie: {} } const secret = 'Fischl von Luftschloss Narfidort' const fischl = await signCookie('fischl', secret) const cookieString = `fischl=${fischl}` const result = await parseCookie(set, cookieString, { secrets: ['New Secret', secret], sign: ['fischl'] }) expect(result.fischl.value).toEqual('fischl') }) }) ================================================ FILE: test/cookie/unchanged.test.ts ================================================ import { describe, it, expect } from 'bun:test' import { Elysia, t } from '../../src' describe('Cookie - Unchanged Values', () => { it('should not send set-cookie header when cookie is only read', async () => { const app = new Elysia() .guard({ cookie: t.Cookie({ value: t.Optional( t.Object({ a: t.String(), b: t.String() }) ) }) }) .get('/cookie', ({ cookie: { value } }) => value.value) .post('/cookie', ({ cookie: { value } }) => { value.value = { a: '1', b: '2' } return 'ok' }) // POST request should set cookie const postResponse = await app.handle( new Request('http://localhost/cookie', { method: 'POST' }) ) const setCookieHeaders = postResponse.headers.getAll('set-cookie') expect(setCookieHeaders.length).toBeGreaterThan(0) // GET request should NOT set cookie (only reading) const getResponse = await app.handle( new Request('http://localhost/cookie', { method: 'GET', headers: { cookie: setCookieHeaders[0].split(';')[0] } }) ) const getSetCookieHeaders = getResponse.headers.getAll('set-cookie') expect(getSetCookieHeaders.length).toBe(0) }) it('should not send set-cookie header when cookie value is accessed but not modified', async () => { const app = new Elysia() .get('/read', ({ cookie: { session } }) => { // Just reading the value const val = session.value return { read: val } }) .get('/write', ({ cookie: { session } }) => { // Writing the value session.value = 'test' return { written: true } }) // Read endpoint should not set cookie const readResponse = await app.handle( new Request('http://localhost/read') ) expect(readResponse.headers.getAll('set-cookie').length).toBe(0) // Write endpoint should set cookie const writeResponse = await app.handle( new Request('http://localhost/write') ) expect( writeResponse.headers.getAll('set-cookie').length ).toBeGreaterThan(0) }) // Fine by using FNV-1a hash // it('should send set-cookie header when setting same value', async () => { // const app = new Elysia().get('/same', ({ cookie: { session } }) => { // // Setting the same value that came from request // session.value = 'existing' // return 'ok' // }) // const response = await app.handle( // new Request('http://localhost/same', { // headers: { // cookie: 'session=existing' // } // }) // ) // expect(response.headers.getAll('set-cookie').length).toBeGreaterThan(0) // }) it('should send set-cookie header when value actually changes', async () => { const app = new Elysia().get('/change', ({ cookie: { session } }) => { session.value = 'new-value' return 'ok' }) const response = await app.handle( new Request('http://localhost/change', { headers: { cookie: 'session=old-value' } }) ) expect(response.headers.getAll('set-cookie').length).toBeGreaterThan(0) }) it('should not send set-cookie header when setting same object value as incoming cookie', async () => { const app = new Elysia().post('/update', ({ cookie: { data } }) => { // Set to same value as incoming cookie data.value = { id: 123, name: 'test' } return 'ok' }) // First request: set the cookie const firstRes = await app.handle( new Request('http://localhost/update', { method: 'POST' }) ) const setCookie = firstRes.headers.get('set-cookie') expect(setCookie).toBeTruthy() // Second request: send cookie back and set to same value const secondRes = await app.handle( new Request('http://localhost/update', { method: 'POST', headers: { cookie: setCookie!.split(';')[0] } }) ) // Should not send Set-Cookie since value didn't change expect(secondRes.headers.getAll('set-cookie').length).toBe(0) }) it('should not send set-cookie header for large unchanged object values', async () => { const large = { users: Array.from({ length: 100 }, (_, i) => ({ id: i, name: `User ${i}` })) } const app = new Elysia().post('/update', ({ cookie: { data } }) => { data.value = large return 'ok' }) // First request: set the cookie const firstRes = await app.handle( new Request('http://localhost/update', { method: 'POST' }) ) const setCookie = firstRes.headers.get('set-cookie') expect(setCookie).toBeTruthy() // Second request: send cookie back and set to same value const secondRes = await app.handle( new Request('http://localhost/update', { method: 'POST', headers: { cookie: setCookie!.split(';')[0] } }) ) // Should not send Set-Cookie since value didn't change expect(secondRes.headers.getAll('set-cookie').length).toBe(0) }) it('should optimize multiple assignments of same object in single request', async () => { const app = new Elysia().post('/multi', ({ cookie: { data } }) => { // Multiple assignments of the same value data.value = { id: 123, name: 'test' } data.value = { id: 123, name: 'test' } data.value = { id: 123, name: 'test' } return 'ok' }) const res = await app.handle( new Request('http://localhost/multi', { method: 'POST' }) ) // Should only produce one Set-Cookie header expect(res.headers.getAll('set-cookie').length).toBe(1) }) it('should invalidate hash cache when using update() method', async () => { const app = new Elysia().post('/cache-invalidation', ({ cookie: { data } }) => { // Set initial value data.value = { id: 1, name: 'first' } // Modify via update() - should invalidate cache data.update({ value: { id: 2, name: 'second' } }) // Set to the updated value again - should detect as unchanged data.value = { id: 2, name: 'second' } return 'ok' }) const res = await app.handle( new Request('http://localhost/cache-invalidation', { method: 'POST' }) ) // Should only have one Set-Cookie header (for final value) const setCookieHeaders = res.headers.getAll('set-cookie') expect(setCookieHeaders.length).toBe(1) expect(setCookieHeaders[0]).toContain('id') }) it('should invalidate hash cache when using set() method', async () => { const app = new Elysia().post('/cache-set', ({ cookie: { data } }) => { // Set initial value data.value = { id: 1 } // Modify via set() - should invalidate cache data.set({ value: { id: 2 } }) // Set to the updated value again - should detect as unchanged data.value = { id: 2 } return 'ok' }) const res = await app.handle( new Request('http://localhost/cache-set', { method: 'POST' }) ) // Should only have one Set-Cookie header expect(res.headers.getAll('set-cookie').length).toBe(1) }) }) ================================================ FILE: test/core/aot-strictpath.test.ts ================================================ import { describe, expect, it } from 'bun:test' import { Elysia } from '../../src' const req = (path: string) => new Request(`http://localhost${path}`) describe('AOT and strictPath interaction', () => { it('should respect default strictPath:false when aot:false', async () => { const app = new Elysia({ aot: false }) .get('/ping', () => 'pong') .group('/api', (app) => app.get('/ping', () => 'pong') ) // All these should return 200 with default strictPath: false expect(await app.handle(req('/ping')).then((x) => x.status)).toBe(200) expect(await app.handle(req('/ping/')).then((x) => x.status)).toBe(200) expect(await app.handle(req('/api/ping')).then((x) => x.status)).toBe(200) expect(await app.handle(req('/api/ping/')).then((x) => x.status)).toBe(200) }) it('should respect explicit strictPath:false when aot:false', async () => { const app = new Elysia({ aot: false, strictPath: false }) .get('/ping', () => 'pong') expect(await app.handle(req('/ping')).then((x) => x.status)).toBe(200) expect(await app.handle(req('/ping/')).then((x) => x.status)).toBe(200) }) it('should respect strictPath:true when aot:false', async () => { const app = new Elysia({ aot: false, strictPath: true }) .get('/ping', () => 'pong') expect(await app.handle(req('/ping')).then((x) => x.status)).toBe(200) expect(await app.handle(req('/ping/')).then((x) => x.status)).toBe(404) }) it('should handle group routes with default strictPath when aot:false', async () => { const app = new Elysia({ aot: false }).group('/api', (app) => app.get('/', () => 'Hello Elysia') ) // Both /api and /api/ should work with default strictPath expect(await app.handle(req('/api')).then((x) => x.status)).toBe(200) expect(await app.handle(req('/api/')).then((x) => x.status)).toBe(200) }) }) ================================================ FILE: test/core/as.test.ts ================================================ import { describe, it, expect } from 'bun:test' import { Elysia, t } from '../../src' import { req } from '../utils' describe('as', () => { it('scoped', async () => { const subPlugin1 = new Elysia() .derive(() => { return { hi: 'hi' } }) .as('scoped') const plugin = new Elysia() .use(subPlugin1) .get('/inner', ({ hi }) => hi) const app = new Elysia() .use(plugin) // @ts-ignore .get('/', ({ hi }) => hi ?? 'none') const res = await Promise.all([ app.handle(req('/')).then((x) => x.text()), app.handle(req('/inner')).then((x) => x.text()) ]) expect(res).toEqual(['none', 'hi']) }) it('global', async () => { const subPlugin1 = new Elysia() .derive(() => { return { hi: 'hi' } }) .as('global') const plugin = new Elysia() .use(subPlugin1) .get('/inner', ({ hi }) => hi) const app = new Elysia().use(plugin).get('/', ({ hi }) => hi ?? 'none') const res = await Promise.all([ app.handle(req('/')).then((x) => x.text()), app.handle(req('/inner')).then((x) => x.text()) ]) expect(res).toEqual(['hi', 'hi']) }) it('global on scoped event', async () => { const subPlugin1 = new Elysia() .derive({ as: 'scoped' }, () => { return { hi: 'hi' } }) .as('global') const plugin = new Elysia() .use(subPlugin1) .get('/inner', ({ hi }) => hi) const app = new Elysia().use(plugin).get('/', ({ hi }) => hi ?? 'none') const res = await Promise.all([ app.handle(req('/')).then((x) => x.text()), app.handle(req('/inner')).then((x) => x.text()) ]) expect(res).toEqual(['hi', 'hi']) }) it('handle as global', async () => { let called = 0 const inner = new Elysia() .guard({ response: t.Number(), }) .onBeforeHandle(() => { called++ }) // @ts-expect-error .get('/inner', () => 'a') .as('global') const plugin = new Elysia() .use(inner) // @ts-expect-error .get('/plugin', () => true) // @ts-expect-error const app = new Elysia().use(plugin).get('/', () => 'not a number') const response = await Promise.all([ app.handle(req('/inner')).then((x) => x.status), app.handle(req('/plugin')).then((x) => x.status), app.handle(req('/')).then((x) => x.status) ]) expect(called).toBe(3) expect(response).toEqual([422, 422, 422]) }) it('handle as global with local override', async () => { let called = 0 const inner = new Elysia() .guard({ response: t.Number() }) .onBeforeHandle(() => { called++ }) // @ts-expect-error .get('/inner', () => 'a') .as('global') const plugin = new Elysia() .use(inner) .guard({ response: t.Boolean() }) .onBeforeHandle(() => { called++ }) .get('/plugin', () => true) // @ts-expect-error const app = new Elysia().use(plugin).get('/', () => 'not a number') const response = await Promise.all([ app.handle(req('/inner')).then((x) => x.status), app.handle(req('/plugin')).then((x) => x.status), app.handle(req('/')).then((x) => x.status) ]) expect(called).toBe(4) expect(response).toEqual([422, 200, 422]) }) it('handle as global with scoped override', async () => { let called = 0 const inner = new Elysia() .guard({ response: t.Number() }) .onBeforeHandle(() => { called++ }) // @ts-expect-error .get('/inner', () => 'a') .as('global') const plugin = new Elysia() .use(inner) .guard({ as: 'scoped', response: t.String() }) .onBeforeHandle({ as: 'scoped' }, () => { called++ }) .get('/plugin', () => 'ok') const app = new Elysia().use(plugin).get('/', () => 'not a number') const response = await Promise.all([ app.handle(req('/inner')).then((x) => x.status), app.handle(req('/plugin')).then((x) => x.status), app.handle(req('/')).then((x) => x.status) ]) expect(called).toBe(5) expect(response).toEqual([422, 200, 200]) }) it('handle as scoped', async () => { let called = 0 const inner = new Elysia() .guard({ response: t.Number() }) .onBeforeHandle(() => { called++ }) // @ts-expect-error .get('/inner', () => 'a') .as('scoped') const plugin = new Elysia() .use(inner) // @ts-expect-error .get('/plugin', () => true) const app = new Elysia().use(plugin).get('/', () => 'not a number') const response = await Promise.all([ app.handle(req('/inner')).then((x) => x.status), app.handle(req('/plugin')).then((x) => x.status), app.handle(req('/')).then((x) => x.status) ]) expect(called).toBe(2) expect(response).toEqual([422, 422, 200]) }) it('handle as scoped twice', async () => { let called = 0 const inner = new Elysia() .guard({ response: t.Number() }) .onBeforeHandle(() => { called++ }) // @ts-expect-error .get('/inner', () => 'a') .as('scoped') const plugin = new Elysia() .use(inner) // @ts-expect-error .get('/plugin', () => true) .as('scoped') // @ts-expect-error const app = new Elysia().use(plugin).get('/', () => 'not a number') const response = await Promise.all([ app.handle(req('/inner')).then((x) => x.status), app.handle(req('/plugin')).then((x) => x.status), app.handle(req('/')).then((x) => x.status) ]) expect(called).toBe(3) expect(response).toEqual([422, 422, 422]) }) }) ================================================ FILE: test/core/before-handle-arrow.test.ts ================================================ import { describe, expect, it } from 'bun:test' import { Elysia } from '../../src' describe('beforeHandle with arrow functions', () => { it('should execute beforeHandle with arrow function expression', async () => { let beforeHandleCalled = false const app = new Elysia() .get('/test', () => 'ok', { // Arrow function expression (no braces) - this was broken with minified code beforeHandle: () => { beforeHandleCalled = true } }) const response = await app.handle(new Request('http://localhost/test')) expect(response.status).toBe(200) expect(beforeHandleCalled).toBe(true) }) it('should execute beforeHandle with arrow function returning value', async () => { const app = new Elysia() .get('/test', () => 'should not reach', { // Arrow function expression that returns early beforeHandle: () => 'intercepted' }) const response = await app.handle(new Request('http://localhost/test')) expect(await response.text()).toBe('intercepted') }) it('should execute async beforeHandle with arrow function expression', async () => { let beforeHandleCalled = false const app = new Elysia() .get('/test', () => 'ok', { // Async arrow function expression beforeHandle: async () => { beforeHandleCalled = true } }) const response = await app.handle(new Request('http://localhost/test')) expect(response.status).toBe(200) expect(beforeHandleCalled).toBe(true) }) it('should execute beforeHandle with complex arrow expression', async () => { let validatorCalled = false const validator = () => { validatorCalled = true } const app = new Elysia() .get('/test', () => 'ok', { // Complex arrow expression like: async ({status}) => requireSignature()({status}) // This pattern was broken when code is minified beforeHandle: () => validator() }) const response = await app.handle(new Request('http://localhost/test')) expect(response.status).toBe(200) expect(validatorCalled).toBe(true) }) it('should execute beforeHandle with arrow function block', async () => { let beforeHandleCalled = false const app = new Elysia() .get('/test', () => 'ok', { // Arrow function with block (this always worked) beforeHandle: () => { beforeHandleCalled = true return } }) const response = await app.handle(new Request('http://localhost/test')) expect(response.status).toBe(200) expect(beforeHandleCalled).toBe(true) }) it('should handle multiple beforeHandle hooks with arrow expressions', async () => { const callOrder: number[] = [] const app = new Elysia() .get('/test', () => 'ok', { beforeHandle: [ () => { callOrder.push(1) }, () => { callOrder.push(2) }, () => { callOrder.push(3) } ] }) const response = await app.handle(new Request('http://localhost/test')) expect(response.status).toBe(200) expect(callOrder).toEqual([1, 2, 3]) }) // Test with truly minified code (no spaces around arrow) // This simulates what happens when code is bundled and minified it('should execute beforeHandle with truly minified arrow function (no spaces)', async () => { // Construct a function with no spaces: async()=>'intercepted' // This is what real minifiers produce const minifiedHandler = Function("return async()=>'intercepted'")() const app = new Elysia().get('/test', () => 'should not reach', { beforeHandle: minifiedHandler }) const response = await app.handle(new Request('http://localhost/test')) expect(await response.text()).toBe('intercepted') }) // Test with actual HTTP server (not just app.handle) it('should work with live server', async () => { let beforeHandleCalled = false const app = new Elysia() .get('/test', () => 'ok', { beforeHandle: () => { beforeHandleCalled = true } }) .listen(0) try { const response = await fetch( `http://localhost:${app.server?.port}/test` ) expect(response.status).toBe(200) expect(beforeHandleCalled).toBe(true) } finally { await app.stop() } }) }) ================================================ FILE: test/core/compose.test.ts ================================================ import { describe, expect, it } from 'bun:test' import { Type } from '@sinclair/typebox' import { hasAdditionalProperties } from '../../src/schema' describe('hasAdditionalProperties', () => { it('should handle object schemas without properties key', () => { const schema = Type.Intersect([ Type.Object({ a: Type.String() }), // Record schemas does not have properties key, instead it has patternProperties Type.Record(Type.Number(), Type.String()) ]) expect(hasAdditionalProperties(schema)).toBe(false) }) }) ================================================ FILE: test/core/config.test.ts ================================================ import { describe, it, expect } from 'bun:test' import { Elysia, t } from '../../src' describe('config', () => { it('standard hostname', async () => { const app = new Elysia({ handler: { standardHostname: false } }).get( '/a', 'a' ) const response = await app .handle(new Request('http://a/a')) .then((x) => x.text()) expect(response).toBe('a') }) it('append prefix / if not provided', () => { const plugin = new Elysia({ prefix: 'v1' }).get('thing', 'thing') const app = new Elysia({ prefix: 'api' }).use(plugin) expect(app.routes[0].path).toBe('/api/v1/thing') // This should not error app['~Routes']?.api.v1.thing }) }) ================================================ FILE: test/core/context.test.ts ================================================ import { describe, expect, it } from 'bun:test' import { Elysia } from '../../src' import { req } from '../utils' describe('context', () => { describe('return route', () => { it('on aot=true', async () => { const app = new Elysia().get('/hi/:id', ({ route }) => route) const res = await app.handle(req('/hi/123')).then((x) => x.text()) expect(res).toBe('/hi/:id') }) it('on aot=false', async () => { const app = new Elysia({ aot: false }).get( '/hi/:id', ({ route }) => route ) const res = await app.handle(req('/hi/123')).then((x) => x.text()) expect(res).toBe('/hi/:id') }) }) describe('early return on macros with route data', () => { it('on aot=true', async () => { const app = new Elysia() .macro({ test: { beforeHandle({ route }) { return route } } }) .get('/hi/:id', () => 'should not returned', { test: true }) const res = await app.handle(req('/hi/123')).then((x) => x.text()) expect(res).toBe('/hi/:id') }) it('on aot=false', async () => { const app = new Elysia({ aot: false }) .macro({ test: { beforeHandle({ route }) { return route } } }) .get('/hi/:id', () => 'should not returned', { test: true }) const res = await app.handle(req('/hi/123')).then((x) => x.text()) expect(res).toBe('/hi/:id') }) }) }) ================================================ FILE: test/core/dynamic.test.ts ================================================ import { Elysia, NotFoundError, status, t } from '../../src' import { describe, expect, it } from 'bun:test' import { post, req } from '../utils' describe('Dynamic Mode', () => { it('handle path', async () => { const app = new Elysia({ aot: false }).get('/', () => 'Hi') const res = await app.handle(req('/')).then((x) => x.text()) expect(res).toBe('Hi') }) it('handle literal', async () => { const app = new Elysia({ aot: false }).get('/', 'Hi') const response = await app.handle(req('/')).then((x) => x.text()) expect(response).toBe('Hi') }) it('handle body', async () => { const app = new Elysia({ aot: false }).post('/', ({ body }) => body, { body: t.Object({ name: t.String() }) }) const body = { name: 'saltyaom' } as const const res = (await app .handle(post('/', body)) .then((x) => x.json())) as typeof body const invalid = await app.handle(post('/', {})).then((x) => x.status) expect(res.name).toBe(body.name) expect(invalid).toBe(422) }) it('handle dynamic all method', async () => { const app = new Elysia({ aot: false }).all('/all/*', () => 'ALL') const res = await app.handle(req('/all/world')).then((x) => x.text()) expect(res).toBe('ALL') }) it('inherits plugin', async () => { const plugin = new Elysia().decorate('hi', () => 'hi') const app = new Elysia({ aot: false }) .use(plugin) .get('/', ({ hi }) => hi()) const res = await app.handle(req('/')).then((r) => r.text()) expect(res).toBe('hi') }) it('use custom error', async () => { const res = await new Elysia({ aot: false }) .get('/', () => 'Hi') .onError(({ code }) => { if (code === 'NOT_FOUND') return new Response("I'm a teapot", { status: 418 }) }) .handle(req('/not-found')) expect(await res.text()).toBe("I'm a teapot") expect(res.status).toBe(418) }) it('inject headers to error', async () => { const app = new Elysia({ aot: false }) .onRequest(({ set }) => { set.headers['Access-Control-Allow-Origin'] = '*' }) .get('/', () => { throw new NotFoundError() }) const res = await app.handle(req('/')) expect(res.headers.get('Access-Control-Allow-Origin')).toBe('*') expect(res.status).toBe(404) }) it('transform any to error', async () => { const app = new Elysia({ aot: false }) .get('/', () => { throw new NotFoundError() }) .onError(async ({ set }) => { set.status = 418 return 'aw man' }) const res = await app.handle(req('/')) expect(await res.text()).toBe('aw man') expect(res.status).toBe(418) }) it('derive', async () => { const app = new Elysia({ aot: false }) .derive(() => { return { A: 'A' } }) .get('/', ({ A }) => A) const res = await app.handle(req('/')) expect(await res.text()).toBe('A') expect(res.status).toBe(200) }) it('resolve', async () => { const app = new Elysia({ aot: false }) .resolve(() => { return { hello: 'Sunday' } }) .get('/', ({ hello }) => hello) const res = await app.handle(req('/')) expect(await res.text()).toBe('Sunday') expect(res.status).toBe(200) }) it('validate', async () => { const app = new Elysia({ aot: false }).post( '/', ({ query: { id, arr } }) => `${id} - ${arr}`, { body: t.Object({ username: t.String(), password: t.String() }), query: t.Object({ id: t.String(), arr: t.Array(t.String()) }), response: { 200: t.String() } } ) const res = await app .handle( post('/?id=me&arr=v1&arr=v2', { username: 'username', password: 'password' }) ) .then((x) => x.text()) expect(res).toBe('me - v1,v2') }) it('default value', async () => { const app = new Elysia({ aot: false }).get( '/:propParams?', ({ params: { propParams }, headers: { propHeader }, query: { propQuery } }) => `${propParams} ${propHeader} ${propQuery}`, { params: t.Object({ propParams: t.String({ default: 'params-default' }) }), headers: t.Object({ propHeader: t.String({ default: 'header-default' }) }), query: t.Object({ propQuery: t.String({ default: 'query-default' }) }) } ) const response = await app .handle(new Request('http://localhost')) .then((x) => x.text()) expect(response).toBe('params-default header-default query-default') }) it('handle non query fallback', async () => { const app = new Elysia({ aot: false }).get('/', () => 'hi', { query: t.Object({ redirect_uri: t.Optional(t.String()) }) }) const res1 = await app.handle(req('/')) const res2 = await app.handle(req('/?')) const res3 = await app.handle(req('/?redirect_uri=a')) expect(res1.status).toBe(200) expect(res2.status).toBe(200) expect(res3.status).toBe(200) }) it('handle local parse event', async () => { const app = new Elysia({ aot: false }).post('/', (ctx) => ctx.body, { parse: (ctx, contentType) => { return contentType }, body: t.String() }) const res = await app.handle( new Request('http://localhost', { method: 'POST', body: 'yay', headers: { 'content-type': 'text/plain' } }) ) expect(await res.text()).toBe('text/plain') }) it('handle async resolve', async () => { const app = new Elysia({ aot: false }) .resolve(() => status(418, 'Chocominto yorimo anata!')) .post('/ruby-chan', () => 'Hai!') const res = await app.handle(post('/ruby-chan', 'nani ga suki!')) expect(await res.text()).toBe('Chocominto yorimo anata!') expect(res.status).toBe(418) }) it('set default header', async () => { const app = new Elysia({ aot: false }) .headers({ 'X-Powered-By': 'Elysia' }) .get('/', () => 'Hello') const res = await app.handle(req('/')) expect(res.headers.get('X-Powered-By')).toBe('Elysia') }) it('handle local cookie signing', async () => { const app = new Elysia({ aot: false }).get( '/', ({ cookie: { profile } }) => { profile.value = { id: 617, name: 'Summoning 101' } return profile.value }, { cookie: t.Cookie( { profile: t.Optional( t.Object({ id: t.Numeric(), name: t.String() }) ) }, { secrets: 'Fischl von Luftschloss Narfidort', sign: ['profile'] } ) } ) const response = await app.handle(req('/')).then((x) => x.json()) expect(response).toEqual({ id: 617, name: 'Summoning 101' }) }) it('handle global cookie signing', async () => { const app = new Elysia({ aot: false, cookie: { secrets: 'Fischl von Luftschloss Narfidort', sign: ['profile'] } }).get( '/', ({ cookie: { profile } }) => { profile.value = { id: 617, name: 'Summoning 101' } return profile.value }, { cookie: t.Cookie({ profile: t.Optional( t.Object({ id: t.Numeric(), name: t.String() }) ) }) } ) const response = await app.handle(req('/')).then((x) => x.json()) expect(response).toEqual({ id: 617, name: 'Summoning 101' }) }) it('handle optional cookie', async () => { const app = new Elysia({ aot: false }).get( '/', ({ cookie: { profile } }) => { profile.value = { id: 617, name: 'Summoning 101' } return profile.value }, { cookie: t.Optional( t.Cookie( { profile: t.Optional( t.Object({ id: t.Numeric(), name: t.String() }) ) }, { secrets: 'Fischl von Luftschloss Narfidort', sign: ['profile'] } ) ) } ) const response = await app.handle(req('/')).then((x) => x.json()) expect(response).toEqual({ id: 617, name: 'Summoning 101' }) }) it('use built-in name parser (text)', async () => { const app = new Elysia({ aot: false }).post( '/', ({ body }) => typeof body, { parse: 'text' } ) const response = await app .handle( new Request('http://localhost', { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ hello: 'world' }) }) ) .then((x) => x.text()) expect(response).toBe('string') }) it('use built-in name parser (text/plain)', async () => { const app = new Elysia({ aot: false }).post( '/', ({ body }) => typeof body, { parse: 'text/plain' } ) const response = await app .handle( new Request('http://localhost', { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ hello: 'world' }) }) ) .then((x) => x.text()) expect(response).toBe('string') }) it('use built-in name parser (json)', async () => { const app = new Elysia({ aot: false }).post( '/', ({ body }) => typeof body, { parse: 'json' } ) const response = await app .handle( new Request('http://localhost', { method: 'POST', headers: { 'content-type': 'text/plain' }, body: JSON.stringify({ hello: 'world' }) }) ) .then((x) => x.text()) expect(response).toBe('object') }) it('use custom name parser', async () => { const app = new Elysia({ aot: false }) .parser('thing', () => { return true }) .post('/', ({ body }) => typeof body, { parse: 'thing' }) const response = await app .handle( new Request('http://localhost', { method: 'POST', headers: { 'content-type': 'text/plain' }, body: JSON.stringify({ hello: 'world' }) }) ) .then((x) => x.text()) expect(response).toBe('boolean') }) it('validate response', async () => { const app = new Elysia({ aot: false }) // @ts-ignore .get('/invalid', () => ({ name: 'Jane Doe' }), { response: t.Object({ foo: t.String() }) }) .get( '/invalid-201', // @ts-ignore ({ status }) => status(201, { name: 'Jane Doe' }), { response: { 201: t.Object({ foo: t.String() }) } } ) .get('/valid', () => ({ foo: 'bar' }), { response: t.Object({ foo: t.String() }) }) .get('/valid-201', ({ status }) => status(201, { foo: 'bar' }), { response: { 201: t.Object({ foo: t.String() }) } }) const invalid = await app.handle(req('/invalid')).then((x) => x.status) const invalid201 = await app .handle(req('/invalid-201')) .then((x) => x.status) const valid = await app.handle(req('/valid')).then((x) => x.status) const valid201 = await app .handle(req('/valid-201')) .then((x) => x.status) expect(invalid).toBe(422) expect(invalid201).toBe(422) expect(valid).toBe(200) expect(valid201).toBe(201) }) it('clean response', async () => { const app = new Elysia({ aot: false }) // @ts-ignore .get('/invalid', () => ({ name: 'Jane Doe' }), { response: t.Object({ foo: t.String() }) }) .get('/valid', () => ({ foo: 'bar', a: 'b' }), { response: t.Object({ foo: t.String() }) }) const invalid = await app.handle(req('/invalid')).then((x) => x.status) const valid = await app.handle(req('/valid')).then((x) => x.json()) expect(invalid).toBe(422) expect(valid).toEqual({ foo: 'bar' }) }) it('validate after handle', async () => { const app = new Elysia({ aot: false }) // @ts-ignore .get('/invalid', () => '', { afterHandle: () => ({ name: 'Jane Doe' }), response: t.Object({ foo: t.String() }) }) .get( '/invalid-201', // @ts-ignore () => '', { // @ts-ignore afterHandle: ({ status }) => // @ts-ignore status(201, { name: 'Jane Doe' }), response: { 201: t.Object({ foo: t.String() }) } } ) // @ts-ignore .get('/valid', () => '', { afterHandle: () => ({ foo: 'bar' }), response: t.Object({ foo: t.String() }) }) .get('/valid-201', () => '', { afterHandle: ({ status }) => status(201, { foo: 'bar' }), response: { 201: t.Object({ foo: t.String() }) } }) const invalid = await app.handle(req('/invalid')).then((x) => x.status) const invalid201 = await app .handle(req('/invalid-201')) .then((x) => x.status) const valid = await app.handle(req('/valid')).then((x) => x.status) const valid201 = await app .handle(req('/valid-201')) .then((x) => x.status) expect(invalid).toBe(422) expect(invalid201).toBe(422) expect(valid).toBe(200) expect(valid201).toBe(201) }) it('clean afterHandle', async () => { const app = new Elysia({ aot: false }) // @ts-ignore .get('/invalid', () => '', { afterHandle: () => ({ name: 'Jane Doe' }), response: t.Object({ foo: t.String() }) }) // @ts-ignore .get('/valid', () => '', { afterHandle: () => ({ foo: 'bar', a: 'b' }), response: t.Object({ foo: t.String() }) }) const invalid = await app.handle(req('/invalid')).then((x) => x.status) const valid = await app.handle(req('/valid')).then((x) => x.json()) expect(invalid).toBe(422) expect(valid).toEqual({ foo: 'bar' }) }) it('handle single query array', async () => { const app = new Elysia({ aot: false }).get('/', ({ query }) => query, { query: t.Object({ name: t.String(), names: t.Array(t.String()) }) }) const data = await app .handle(req('/?name=neon&names=rapi')) .then((x) => x.json()) expect(data).toEqual({ name: 'neon', names: ['rapi'] }) }) it('handle multiple query array in nuqs format', async () => { const app = new Elysia({ aot: false }).get('/', ({ query }) => query, { query: t.Object({ name: t.String(), names: t.Array(t.String()) }) }) const data = await app .handle(req('/?name=neon&names=rapi,anis')) .then((x) => x.json()) expect(data).toEqual({ name: 'neon', names: ['rapi', 'anis'] }) }) it('handle multiple query array in nuqs format', async () => { const app = new Elysia({ aot: false }).get('/', ({ query }) => query, { query: t.Object({ name: t.String(), names: t.Array(t.String()) }) }) const data = await app .handle(req('/?name=neon&names=rapi,anis')) .then((x) => x.json()) expect(data).toEqual({ name: 'neon', names: ['rapi', 'anis'] }) }) it('call local afterResponse on aot: false', async () => { let called = false const app = new Elysia({ aot: false }) .guard( { afterResponse: () => { called = true } }, (app) => app.get('/test', () => 'afterResponse') ) .get('/', () => 'hi') const value = await app.handle(req('/test')).then((x) => x.text()) await Bun.sleep(6.7) expect(value).toBe('afterResponse') expect(called).toBeTrue() }) // it('handle query array reference in multiple reference format', async () => { // const IdsModel = new Elysia().model({ // name: t.Object({ // name: t.Array(t.String()) // }) // }) // const app = new Elysia({ aot: false }) // .use(IdsModel) // .get('/', ({ query }) => query, { // query: 'name' // }) // const data = await app // .handle(req('/?names=rapi&names=anis')) // .then((x) => x.json()) // expect(data).toEqual({ // names: ['rapi', 'anis'] // }) // }) // it('handle query array reference in multiple reference format', async () => { // const IdsModel = new Elysia().model({ // name: t.Object({ // name: t.Array(t.String()) // }) // }) // const app = new Elysia({ aot: false }) // .use(IdsModel) // .get('/', ({ query }) => query, { // query: 'name' // }) // const data = await app // .handle(req('/?names=rapi&names=anis')) // .then((x) => x.json()) // expect(data).toEqual({ // names: ['rapi', 'anis'] // }) // }) // Union schema test - verifies getSchemaProperties handles Union without crashing it('handle Union query schema', async () => { const app = new Elysia({ aot: false }).get( '/', ({ query }) => query, { query: t.Union([ t.Object({ search: t.String() }), t.Object({ id: t.Numeric() }) ]) } ) const response = await app .handle(req('/?search=test')) .then((x) => x.json()) expect(response.search).toBe('test') }) }) ================================================ FILE: test/core/elysia.test.ts ================================================ import { Elysia, t } from '../../src' import { describe, expect, it } from 'bun:test' import { req } from '../utils' import z from 'zod' describe('Edge Case', () => { it('handle state', async () => { const app = new Elysia() .state('a', 'a') .get('/', ({ store: { a } }) => a) const res = await app.handle(req('/')) expect(await res.text()).toBe('a') }) // https://github.com/oven-sh/bun/issues/1523 it("don't return HTTP 10", async () => { const app = new Elysia().get('/', ({ set }) => { set.headers.Server = 'Elysia' return 'hi' }) const res = await app.handle(req('/')) expect(res.status).toBe(200) }) it('has no side-effect', async () => { const app = new Elysia() .get('/1', ({ set }) => { set.headers['x-server'] = 'Elysia' return 'hi' }) .get('/2', () => 'hi') const res1 = await app.handle(req('/1')) const res2 = await app.handle(req('/2')) expect(res1.headers.get('x-server')).toBe('Elysia') expect(res2.headers.get('x-server')).toBe(null) }) it('return Promise', async () => { const app = new Elysia().get( '/', () => new Promise((resolve) => resolve('h')) ) const res = await app.handle(req('/')).then((x) => x.text()) expect(res).toBe('h') }) it('handle dynamic all method', async () => { const app = new Elysia().all('/all/*', () => 'ALL') const res = await app.handle(req('/all/world')).then((x) => x.text()) expect(res).toBe('ALL') }) // ? since different runtime expected to have different implementation of new Response // ? we can't handle all the case // it('handle object of class', async () => { // class SomeResponse { // constructor(public message: string) {} // } // const app = new Elysia().get( // '/', // () => new SomeResponse('Hello, world!') // ) // const res = await app.handle(req('/')).then((x) => x.json()) // expect(res).toStrictEqual({ // message: 'Hello, world!' // }) // }) // it('handle object of class (async)', async () => { // class SomeResponse { // constructor(public message: string) {} // } // const app = new Elysia().get('/', async () => { // await Bun.sleep(1) // return new SomeResponse('Hello, world!') // }) // const res = await app.handle(req('/')).then((x) => x.json()) // expect(res).toStrictEqual({ // message: 'Hello, world!' // }) // }) it('handle strict path and loose path', async () => { const loose = new Elysia().group('/a', (app) => app.get('/', () => 'Hi') ) expect(await loose.handle(req('/a')).then((x) => x.status)).toBe(200) expect(await loose.handle(req('/a/')).then((x) => x.status)).toBe(200) const strict = new Elysia({ strictPath: true }).group('/a', (app) => app.get('/', () => 'Hi')) expect(await strict.handle(req('/a')).then((x) => x.status)).toBe(404) expect(await strict.handle(req('/a/')).then((x) => x.status)).toBe(200) }) it('return cookie with file', async () => { const kyuukararin = Bun.file('test/kyuukurarin.mp4') const app = new Elysia().get('/', ({ cookie: { name } }) => { name.set({ value: 'Rikuhachima Aru', maxAge: new Date().setFullYear(new Date().getFullYear() + 1), httpOnly: true }) return kyuukararin }) const response = await app .handle(req('/')) .then((x) => x.headers.toJSON()) expect(response['set-cookie']).toHaveLength(1) expect(response['content-type']).toBe('video/mp4') }) it('return multiple cookies with file', async () => { const kyuukararin = Bun.file('test/kyuukurarin.mp4') const app = new Elysia().get('/', ({ cookie: { name, school } }) => { name.value = 'Rikuhachima Aru' school.value = 'Gehenna' return kyuukararin }) const response = await app.handle(req('/')) expect(response.headers.get('content-type')).toBe('video/mp4') expect(response.headers.getSetCookie()).toEqual([ 'name=Rikuhachima%20Aru; Path=/', 'school=Gehenna; Path=/' ]) }) it('preserve correct index order of routes if duplicated', () => { const app = new Elysia() .get('/0', () => '0') .get('/1', () => '1') .get('/2', () => '2') .get('/3', () => '3') .get('/1', () => '-') .get('/4', () => '4') // @ts-expect-error expect(app.routeTree['GET_/0']).toEqual(0) // @ts-expect-error expect(app.routeTree['GET_/4']).toEqual(4) }) it('preserve correct index order of routes if duplicated from plugin', () => { const plugin = new Elysia() .get('/3', () => '3') .get('/1', () => '-') .get('/4', () => '4') const app = new Elysia() .get('/0', () => '0') .get('/1', () => '1') .get('/2', () => '2') .use(plugin) // @ts-expect-error expect(app.routeTree['GET_/0']).toEqual(0) // @ts-expect-error expect(app.routeTree['GET_/4']).toEqual(4) }) it('get getGlobalRoutes', () => { const plugin = new Elysia().get('/', () => 'hello') const main = new Elysia().use(plugin).get('/2', () => 'hi') // @ts-expect-error private property expect(main.getGlobalRoutes().length).toBe(2) }) describe('value returned from transform has priority over the default value from schema', () => { const route = new Elysia().get( '/:propParams?', ({ params: { propParams } }) => propParams, { params: t.Object({ propParams: t.String({ default: 'params-default' }) }), transform({ params }) { params.propParams = 'params-transform' } } ) it('aot is on', async () => { const app = new Elysia().use(route) const response = await app .handle(new Request('http://localhost')) .then((x) => x.text()) expect(response).toBe('params-transform') }) it('aot is off', async () => { const app = new Elysia({ aot: false }).use(route) const response = await app .handle(new Request('http://localhost')) .then((x) => x.text()) expect(response).toBe('params-transform') }) }) it('handle duplicated static route may cause index conflict correctly', async () => { const Path = new Elysia({ name: 'auth' }) .mount('/AB', (request) => new Response('AB')) .mount('/BA', (request) => new Response('BA')) const Module = new Elysia().use(Path) const app = new Elysia({ name: 'main' }).use(Path).use(Module) const responses = await Promise.all([ app.handle(req('/AB')).then((x) => x.text()), app.handle(req('/BA')).then((x) => x.text()) ]) expect(responses).toEqual(['AB', 'BA']) }) it('handle complex union with json exact mirror, and sanitize', async () => { const app = new Elysia({ sanitize: (v) => v && 'Elysia' }).get( '/', // @ts-ignore () => ({ type: 'ok', data: [ { type: 'cool', data: null }, { type: 'yea', data: { type: 'aight', data: null } } ] }), { response: t.Recursive((This) => t.Object({ type: t.String(), data: t.Union([t.Nullable(This), t.Array(This)]) }) ) } ) const response = await app.handle(req('/')).then((x) => x.json()) expect(response).toEqual({ type: 'Elysia', data: [ { type: 'Elysia', data: null }, { type: 'Elysia', data: { type: 'Elysia', data: null } } ] }) }) it('clone hooks before mapping it to usable function while compose', async () => { const group = new Elysia() .macro({ user: (enabled: true) => ({ resolve() { if (!enabled) return return { user: 'a' } } }) }) .get( '/', ({ user, status }) => { if (!user) return status(401) return { hello: 'hanabi' } }, { user: true } ) const app = new Elysia({ precompile: true }).group('/group', (app) => app.use(group)) const response = await app.handle(req('/group')).then((x) => x.json()) expect(response).toEqual({ hello: 'hanabi' }) }) it('decode URI of path parameter', async () => { const api = new Elysia().get('/:id', ({ params }) => params, { params: t.Object({ id: t.String() }) }) const value = await api .handle(new Request('http://localhost:3000/hello world')) .then((response) => response.json()) expect(value).toEqual({ id: 'hello world' }) }) it('clean non-root additionalProperties', async () => { const app = new Elysia().get( '/', () => ({ keys: [{ a: 1, b: 2 }], extra: true }), { response: t.Object( { keys: t.Array(t.Object({ a: t.Number() })) }, { additionalProperties: true } ) } ) const value = await app.handle(req('/')).then((x) => x.json()) expect(value).toEqual({ keys: [{ a: 1 }], extra: true }) }) it('prevent side-effect from guard merge', async () => { const app = new Elysia().guard( { response: { 403: t.String() } }, (app) => app .get('/foo', () => 'bar', { response: { 200: t.String() } }) .get('/bar', () => 12, { response: { 200: t.Integer() } }) ) const response = await app.handle(req('/foo')) expect(response.status).toBe(200) }) it('automatically handle HEAD request for GET static path', async () => { const app = new Elysia().get('/', () => 'hello world') const response = await app.handle( new Request('http://localhost', { method: 'HEAD' }) ) expect(response.status).toBe(200) expect(response.headers.toJSON()).toEqual({ 'content-length': '11' }) }) it('automatically handle HEAD request for GET dynamic path', async () => { const app = new Elysia().get('/:id', () => 'hello world') const response = await app.handle( new Request('http://localhost/1', { method: 'HEAD' }) ) expect(response.status).toBe(200) expect(response.headers.toJSON()).toEqual({ 'content-length': '11' }) }) it('handle arbitrary code execution from cookie', async () => { const app = new Elysia({ cookie: { secrets: `\` + console.log(c.q='pwn') + \``, domain: process.env.COOKIE_DOMAIN || 'localhost' } }).get( '/', (c) => // @ts-ignore c.q ?? 'safe', { cookie: t.Cookie({ foo: t.Optional(t.Any()) }) } ) const response = await app .handle(req('/?name=saltyaom')) .then((x) => x.text()) expect(response).toBe('safe') }) it('prototype pollution from input', () => { const app = new Elysia() .guard({ schema: 'standalone', body: z.object({ data: z.any() }) }) .post( '/', ({ body }) => ({ body, win: // @ts-ignore {}.foo }), { body: z.object({ data: z.object({ messageId: z.string('pollute-me') }) }) } ) app.handle( new Request('http://localhost:3000/', { method: 'POST', headers: { 'content-type': 'application/json' }, body: `{ "data": { "messageId": "pollute-me", "__proto__": { "foo": "bar" } } }` }) ).then((x) => x.json()) }) }) ================================================ FILE: test/core/formdata.test.ts ================================================ import { Elysia, t, form, file } from '../../src' import { describe, expect, it } from 'bun:test' import { req } from '../utils' describe('Form Data', () => { it('return Bun.file', async () => { const app = new Elysia().get('/', () => form({ a: 'hello', b: Bun.file('test/kyuukurarin.mp4') }) ) const contentType = await app .handle(req('/')) .then((x) => x.headers.get('content-type')) expect(contentType).toStartWith('multipart/form-data') }) it('return Elysia.file', async () => { const app = new Elysia().get('/', () => form({ a: 'hello', b: file('test/kyuukurarin.mp4') }) ) const contentType = await app .handle(req('/')) .then((x) => x.headers.get('content-type')) expect(contentType).toStartWith('multipart/form-data') }) it('return Elysia.file', async () => { const app = new Elysia().get('/', () => form({ a: 'hello', b: file('test/kyuukurarin.mp4') }) ) const contentType = await app .handle(req('/')) .then((x) => x.headers.get('content-type')) expect(contentType).toStartWith('multipart/form-data') }) it('validate formdata', async () => { const app = new Elysia().get( '/', () => form({ a: 'hello', b: file('test/kyuukurarin.mp4') }), { response: t.Form({ a: t.String(), b: t.File() }) } ) const response = await app.handle(req('/')) expect(response.status).toBe(200) expect(response.headers.get('content-type')).toStartWith( 'multipart/form-data' ) }) it('return single file', async () => { const app = new Elysia().get('/', () => file('test/kyuukurarin.mp4')) const response = await app.handle(req('/')) expect(response.status).toBe(200) expect(response.headers.get('content-type')).toStartWith('video/mp4') }) it('inline single file', async () => { const app = new Elysia().get('/', file('test/kyuukurarin.mp4')) const response = await app.handle(req('/')) expect(response.status).toBe(200) expect(response.headers.get('content-type')).toStartWith('video/mp4') }) }) ================================================ FILE: test/core/handle-error.test.ts ================================================ import { Elysia, InternalServerError, NotFoundError, status, t } from '../../src' import { describe, expect, it } from 'bun:test' import { req } from '../utils' const request = new Request('http://localhost:8080') describe('Handle Error', () => { it('handle NOT_FOUND', async () => { const res = await new Elysia() .get('/', () => 'Hi') // @ts-expect-error private .handleError( { request, set: { headers: {} } }, new NotFoundError() ) expect(await res.text()).toBe('NOT_FOUND') expect(res.status).toBe(404) }) it('handle INTERNAL_SERVER_ERROR', async () => { const res = await new Elysia() .get('/', () => 'Hi') // @ts-expect-error private .handleError( { request, set: { headers: {} } }, new InternalServerError() ) expect(await res.text()).toBe('INTERNAL_SERVER_ERROR') expect(res.status).toBe(500) }) it('handle VALIDATION', async () => { const res = await new Elysia() .get('/', () => 'Hi', { query: t.Object({ name: t.String() }) }) .handle(req('/')) expect(res.status).toBe(422) }) it('use custom error', async () => { const res = await new Elysia() .get('/', () => 'Hi') .onError(({ code }) => { if (code === 'NOT_FOUND') return new Response("I'm a teapot", { status: 418 }) }) .handle(req('/not-found')) expect(await res.text()).toBe("I'm a teapot") expect(res.status).toBe(418) }) it('inject headers to error', async () => { const app = new Elysia() .onRequest(({ set }) => { set.headers['Access-Control-Allow-Origin'] = '*' }) .get('/', () => { throw new NotFoundError() }) const res = await app.handle(req('/')) expect(res.headers.get('Access-Control-Allow-Origin')).toBe('*') expect(res.status).toBe(404) }) it('transform any to error', async () => { const app = new Elysia() .onError(async ({ set }) => { set.status = 418 return 'aw man' }) .get('/', () => { throw new NotFoundError() }) const res = await app.handle(req('/')) expect(await res.text()).toBe('aw man') expect(res.status).toBe(418) }) it('handle error in group', async () => { const authenticate = new Elysia().group('/group', (group) => group .get('/inner', () => { throw new Error('A') }) .onError(() => { return 'handled' }) ) const app = new Elysia().use(authenticate) const response = await app.handle(req('/group/inner')) expect(await response.text()).toEqual('handled') expect(response.status).toEqual(500) }) it('handle error status in group', async () => { const authenticate = new Elysia().group('/group', (group) => group .get('/inner', ({ set }) => { set.status = 418 throw new Error('A') }) .onError(() => { return 'handled' }) ) const app = new Elysia().use(authenticate) const response = await app.handle(req('/group/inner')) expect(await response.text()).toEqual('handled') expect(response.status).toEqual(418) }) it('handle thrown error function', async () => { const app = new Elysia().get('/', ({ status }) => { throw status(404, 'Not Found :(') }) const response = await app.handle(req('/')) expect(await response.text()).toEqual('Not Found :(') expect(response.status).toEqual(404) }) it('handle thrown Response', async () => { const app = new Elysia().get('/', ({ status }) => { throw status(404, 'Not Found :(') }) const response = await app.handle(req('/')) expect(await response.text()).toEqual('Not Found :(') expect(response.status).toEqual(404) }) it('handle error code in request', async () => { class APIError extends Error { public readonly message: string public readonly status: number constructor( status: number, message: string, options?: ErrorOptions ) { super(message, options) this.status = status this.message = message this.name = 'APIError' Object.setPrototypeOf(this, APIError.prototype) Error.captureStackTrace(this) } } const errors = new Elysia() .error({ APIError }) .onError({ as: 'global' }, ({ code }) => { return code }) const requestHandler = new Elysia() .onTransform(() => { throw new APIError(403, 'Not authorized') }) .get('/', () => 'a') const app = new Elysia().use(errors).use(requestHandler) expect(await app.handle(req('/')).then((req) => req.text())).toBe( 'APIError' ) }) it('parse headers', async () => { const headers = await new Elysia() .get('/', ({ headers }) => headers) .handle( new Request('http://localhost:3000', { headers: { 'Content-Type': 'application/json', 'X-Test': 'Nagi' } }) ) .then((x) => x.json()) expect(headers).toEqual({ 'content-type': 'application/json', 'x-test': 'Nagi' }) }) it('handle error in Transform', async () => { const route = new Elysia().get('/', ({ query: { aid } }) => aid, { query: t.Object({ aid: t .Transform(t.String()) .Decode((value) => { throw new NotFoundError('foo') }) .Encode((value) => `1`) }) }) let response = await new Elysia({ aot: false }) .use(route) .handle(req('/?aid=a')) expect(response.status).toEqual(404) expect(await response.text()).toEqual('foo') response = await new Elysia({ aot: true }) .use(route) .handle(req('/?aid=a')) expect(response.status).toEqual(404) expect(await response.text()).toEqual('foo') }) it('map status error to response', async () => { const value = { message: 'meow!' } const response: Response = await new Elysia() .get('/', () => 'Hello', { beforeHandle({ status }) { throw status("I'm a teapot", { message: 'meow!' }) } }) // @ts-expect-error private property .handleError( { request: new Request('http://localhost/'), set: { headers: {} } }, status(422, value) as any ) expect(await response.json()).toEqual(value) expect(response.headers.get('content-type')).toStartWith( 'application/json' ) expect(response.status).toEqual(422) }) it('map status error with custom mapResponse', async () => { const value = { message: 'meow!' } const response: Response = await new Elysia() .mapResponse(({ responseValue }) => { if (typeof responseValue === 'object') return new Response('Don Quixote', { headers: { 'content-type': 'text/plain' } }) }) .get('/', () => 'Hello', { beforeHandle({ status }) { throw status("I'm a teapot", { message: 'meow!' }) } }) // @ts-expect-error private property .handleError( { request: new Request('http://localhost/'), set: { headers: {} } }, status(422, value) as any ) expect(await response.text()).toBe('Don Quixote') expect(response.headers.get('content-type')).toStartWith('text/plain') expect(response.status).toEqual(422) }) it('handle generic error', async () => { const res = await new Elysia() .get('/', () => 'Hi') // @ts-expect-error private .handleError( { request, set: { headers: {} } }, // https://youtube.com/shorts/PbIWVPKHfrQ new Error('a') ) expect(await res.text()).toBe('a') expect(res.status).toBe(500) }) it('handle generic error when thrown in handler', async () => { const app = new Elysia().get('/', () => { throw new Error('a') }) const res = await app.handle(req('/')) expect(await res.text()).toBe('a') expect(res.status).toBe(500) }) it('handle Error with toResponse() when returned', async () => { class ErrorA extends Error { toResponse() { return Response.json({ error: 'hello' }, { status: 418 }) } } const app = new Elysia().get('/A', () => { return new ErrorA() }) const res = await app.handle(req('/A')) expect(await res.json()).toEqual({ error: 'hello' }) expect(res.status).toBe(418) }) it('handle Error with toResponse() when thrown', async () => { class ErrorA extends Error { toResponse() { return Response.json({ error: 'hello' }, { status: 418 }) } } const app = new Elysia().get('/A', () => { throw new ErrorA() }) const res = await app.handle(req('/A')) expect(await res.json()).toEqual({ error: 'hello' }) expect(res.status).toBe(418) }) it('handle non-Error with toResponse() when returned', async () => { class ErrorB { toResponse() { return Response.json({ error: 'hello' }, { status: 418 }) } } const app = new Elysia().get('/B', () => { return new ErrorB() }) const res = await app.handle(req('/B')) expect(await res.json()).toEqual({ error: 'hello' }) expect(res.status).toBe(418) }) it('handle non-Error with toResponse() when thrown', async () => { class ErrorB { toResponse() { return Response.json({ error: 'hello' }, { status: 418 }) } } const app = new Elysia().get('/B', () => { throw new ErrorB() }) const res = await app.handle(req('/B')) expect(await res.json()).toEqual({ error: 'hello' }) expect(res.status).toBe(418) }) it('handle Error with toResponse() that includes custom headers', async () => { class ErrorWithHeaders extends Error { toResponse() { return Response.json( { error: 'custom error' }, { status: 418, headers: { 'X-Custom-Header': 'custom-value', 'Content-Type': 'application/json; charset=utf-8' } } ) } } const app = new Elysia().get('/', () => { throw new ErrorWithHeaders() }) const res = await app.handle(req('/')) expect(await res.json()).toEqual({ error: 'custom error' }) expect(res.status).toBe(418) expect(res.headers.get('X-Custom-Header')).toBe('custom-value') }) it('handle async toResponse() when thrown', async () => { class AsyncError extends Error { async toResponse() { // Simulate async operation await new Promise(resolve => setTimeout(resolve, 10)) return Response.json({ error: 'async error' }, { status: 418 }) } } const app = new Elysia().get('/', () => { throw new AsyncError() }) const res = await app.handle(req('/')) expect(await res.json()).toEqual({ error: 'async error' }) expect(res.status).toBe(418) }) it('handle async toResponse() when returned', async () => { class AsyncError extends Error { async toResponse() { // Simulate async operation await new Promise(resolve => setTimeout(resolve, 10)) return Response.json({ error: 'async error' }, { status: 418 }) } } const app = new Elysia().get('/', () => { return new AsyncError() }) const res = await app.handle(req('/')) expect(await res.json()).toEqual({ error: 'async error' }) expect(res.status).toBe(418) }) it('handle async toResponse() with custom headers', async () => { class AsyncErrorWithHeaders extends Error { async toResponse() { await new Promise(resolve => setTimeout(resolve, 10)) return Response.json( { error: 'async with headers' }, { status: 419, headers: { 'X-Async-Header': 'async-value' } } ) } } const app = new Elysia().get('/', () => { throw new AsyncErrorWithHeaders() }) const res = await app.handle(req('/')) expect(await res.json()).toEqual({ error: 'async with headers' }) expect(res.status).toBe(419) expect(res.headers.get('X-Async-Header')).toBe('async-value') }) it('handle non-Error with async toResponse()', async () => { class AsyncNonError { async toResponse() { await new Promise(resolve => setTimeout(resolve, 10)) return Response.json({ error: 'non-error async' }, { status: 418 }) } } const app = new Elysia().get('/', () => { throw new AsyncNonError() }) const res = await app.handle(req('/')) expect(await res.json()).toEqual({ error: 'non-error async' }) expect(res.status).toBe(418) }) it('handle toResponse() that throws an error', async () => { class BrokenError extends Error { toResponse() { throw new Error('toResponse failed') } } const app = new Elysia().get('/', () => { throw new BrokenError('original error') }) const res = await app.handle(req('/')) expect(res.status).toBe(500) expect(await res.text()).toBe('original error') }) it('handle async toResponse() that throws an error', async () => { class BrokenAsyncError extends Error { async toResponse() { throw new Error('async toResponse failed') } } const app = new Elysia().get('/', () => { throw new BrokenAsyncError('original error') }) const res = await app.handle(req('/')) expect(res.status).toBe(500) expect(await res.text()).toBe('original error') }) it('send set-cookie header when error is thrown', async () => { const app = new Elysia().get('/', ({ cookie }) => { cookie.session.value = 'test-session-id' throw new Error('test error') }) const res = await app.handle(req('/')) expect(res.status).toBe(500) expect(res.headers.get('set-cookie')).toContain('session=test-session-id') }) it('send set-cookie header when response validation error occurs', async () => { const app = new Elysia().get('/', ({ cookie }) => { cookie.session.value = 'test-session-id' return 'invalid response' }, { response: t.Number() }) const res = await app.handle(req('/')) expect(res.status).toBe(422) expect(res.headers.get('set-cookie')).toContain('session=test-session-id') }) it('send set-cookie header when error is thrown with onError hook', async () => { const app = new Elysia() .onError(({ error }) => { return error.message }) .get('/', ({ cookie }) => { cookie.session.value = 'test-session-id' throw new Error('custom error') }) const res = await app.handle(req('/')) expect(res.status).toBe(500) expect(await res.text()).toBe('custom error') expect(res.headers.get('set-cookie')).toContain('session=test-session-id') }) it('send set-cookie header when NotFoundError is thrown', async () => { const app = new Elysia().get('/', ({ cookie }) => { cookie.session.value = 'test-session-id' throw new NotFoundError() }) const res = await app.handle(req('/')) expect(res.status).toBe(404) expect(res.headers.get('set-cookie')).toContain('session=test-session-id') }) it('send set-cookie header when InternalServerError is thrown', async () => { const app = new Elysia().get('/', ({ cookie }) => { cookie.session.value = 'test-session-id' throw new InternalServerError() }) const res = await app.handle(req('/')) expect(res.status).toBe(500) expect(res.headers.get('set-cookie')).toContain('session=test-session-id') }) it('send set-cookie header in AOT mode when error is thrown', async () => { const app = new Elysia({ aot: true }).get('/', ({ cookie }) => { cookie.session.value = 'test-session-id' throw new Error('test error') }) const res = await app.handle(req('/')) expect(res.status).toBe(500) expect(res.headers.get('set-cookie')).toContain('session=test-session-id') }) it('preserve multiple cookies when error is thrown', async () => { const app = new Elysia().get('/', ({ cookie }) => { cookie.session.value = 'session-123' cookie.user.value = 'user-456' throw new Error('test error') }) const res = await app.handle(req('/')) expect(res.status).toBe(500) const setCookie = res.headers.get('set-cookie') expect(setCookie).toContain('session=session-123') expect(setCookie).toContain('user=user-456') }) it('send set-cookie header when error has custom headers', async () => { const app = new Elysia().get('/', ({ cookie, set }) => { cookie.session.value = 'test-session-id' set.headers['x-custom'] = 'value' throw new Error('test error') }) const res = await app.handle(req('/')) expect(res.headers.get('set-cookie')).toContain('session=test-session-id') expect(res.headers.get('x-custom')).toBe('value') }) }) ================================================ FILE: test/core/macro-lifecycle.test.ts ================================================ import { describe, expect, it } from 'bun:test' import { Elysia } from '../../src' describe('macro beforeHandle lifecycle order', () => { it('should run macro beforeHandle before nested plugin resolve', async () => { const executionOrder: string[] = [] // Auth service with resolve + macro const authService = new Elysia({ name: 'auth-service' }) .resolve(() => { executionOrder.push('authService.resolve') return { userId: undefined } // Simulating no auth }) .macro({ isSignedIn: { beforeHandle({ userId }) { executionOrder.push('isSignedIn.beforeHandle') if (!userId) throw new Error('Unauthorized') } } }) .as('scoped') // DB client that requires userId const dbClient = new Elysia({ name: 'db-client' }) .resolve((ctx) => { executionOrder.push('dbClient.resolve') const userId = (ctx as { userId?: string }).userId if (!userId) throw new Error('User ID is required') return { db: { userId } } }) .as('scoped') // Feature module using dbClient const feature = new Elysia({ name: 'feature' }) .use(dbClient) .get('/', ({ db }) => `Hello ${db.userId}`) // Main app const app = new Elysia() .use(authService) .group('/v1', { isSignedIn: true }, (app) => app.use(feature)) const response = await app.handle(new Request('http://localhost/v1/')) // The macro's beforeHandle should run BEFORE dbClient's resolve // So we should get "Unauthorized" error, not "User ID is required" const body = await response.text() // console.log('Execution order:', executionOrder) // console.log('Response:', body) // Expected order: authService.resolve -> isSignedIn.beforeHandle (throws) // dbClient.resolve should NOT run because beforeHandle throws first expect(executionOrder).toContain('authService.resolve') expect(executionOrder).toContain('isSignedIn.beforeHandle') expect(executionOrder).not.toContain('dbClient.resolve') expect(body).toContain('Unauthorized') }) it('should run hooks in registration order within same queue', async () => { const executionOrder: string[] = [] const app = new Elysia() .resolve(() => { executionOrder.push('resolve1') return { val1: 1 } }) .onBeforeHandle(() => { executionOrder.push('beforeHandle1') }) .resolve(() => { executionOrder.push('resolve2') return { val2: 2 } }) .onBeforeHandle(() => { executionOrder.push('beforeHandle2') }) .get('/', () => 'ok') await app.handle(new Request('http://localhost/')) // console.log('Execution order:', executionOrder) // According to docs, resolve and beforeHandle share the same queue // Order should be: resolve1 -> beforeHandle1 -> resolve2 -> beforeHandle2 expect(executionOrder).toEqual([ 'resolve1', 'beforeHandle1', 'resolve2', 'beforeHandle2' ]) }) it('should demonstrate the issue with macro in group', async () => { const executionOrder: string[] = [] let errorMessage = '' const authPlugin = new Elysia({ name: 'auth' }) .resolve(() => { executionOrder.push('auth.resolve') return { userId: 'user123' } // Auth succeeds }) .macro({ requireAuth: { beforeHandle({ userId }) { executionOrder.push('requireAuth.beforeHandle') if (!userId) throw new Error('Unauthorized') } } }) .as('scoped') const dataPlugin = new Elysia({ name: 'data' }) .resolve((ctx) => { executionOrder.push('data.resolve') return { data: 'some data' } }) .as('scoped') const app = new Elysia() .use(authPlugin) .onError(({ error }) => { // @ts-ignore errorMessage = error.message return errorMessage }) .group('/api', { requireAuth: true }, (app) => app.use(dataPlugin).get('/', ({ data }) => data) ) const response = await app.handle(new Request('http://localhost/api/')) // console.log('Execution order:', executionOrder) // console.log('Error:', errorMessage) // With auth succeeding (userId = 'user123'), all should run // Expected: auth.resolve -> requireAuth.beforeHandle -> data.resolve expect(executionOrder).toEqual([ 'auth.resolve', 'requireAuth.beforeHandle', 'data.resolve' ]) }) it('should preserve macro schema when merging with nested plugin hooks', async () => { const { t } = await import('../../src') // Macro that adds both beforeHandle and body schema const validatedMacro = new Elysia({ name: 'validated-macro' }) .macro({ validatePayload: { body: t.Object({ name: t.String() }), beforeHandle() { // Macro's beforeHandle } } }) .as('scoped') const nestedPlugin = new Elysia({ name: 'nested' }) .resolve(() => ({ nested: true })) .as('scoped') const app = new Elysia() .use(validatedMacro) .group('/api', { validatePayload: true }, (app) => app.use(nestedPlugin).post('/', ({ body }) => body.name) ) // Valid request - should pass validation const validResponse = await app.handle( new Request('http://localhost/api/', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ name: 'test' }) }) ) expect(validResponse.status).toBe(200) expect(await validResponse.text()).toBe('test') // Invalid request - should fail validation (macro schema should be preserved) const invalidResponse = await app.handle( new Request('http://localhost/api/', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ invalid: 'data' }) }) ) // If macro schema is lost, this would be 200 instead of 422 expect(invalidResponse.status).toBe(422) }) }) ================================================ FILE: test/core/modules.test.ts ================================================ /* eslint-disable @typescript-eslint/no-unused-vars */ import { Elysia } from '../../src' import { describe, expect, it } from 'bun:test' import { req } from '../utils' import { sleep } from 'bun' const asyncPlugin = async (app: Elysia) => app.get('/async', () => 'async') const lazyPlugin = import('../modules') const lazyNamed = lazyPlugin.then((x) => x.lazy) describe('Modules', () => { it('inline async', async () => { const app = new Elysia().use(async (app) => app.get('/async', () => 'async') ) await app.modules const res = await app.handle(req('/async')).then((r) => r.text()) expect(res).toBe('async') }) it('async', async () => { const app = new Elysia().use(asyncPlugin) await app.modules const res = await app.handle(req('/async')).then((r) => r.text()) expect(res).toBe('async') }) it('inline import', async () => { const app = new Elysia().use(import('../modules')) await app.modules const res = await app.handle(req('/lazy')).then((r) => r.text()) expect(res).toBe('lazy') }) it('import', async () => { const app = new Elysia().use(lazyPlugin) await app.modules const res = await app.handle(req('/lazy')).then((r) => r.text()) expect(res).toBe('lazy') }) it('import non default', async () => { const app = new Elysia().use(lazyNamed) await app.modules const res = await app.handle(req('/lazy')).then((r) => r.text()) expect(res).toBe('lazy') }) it('inline import non default', async () => { const app = new Elysia().use(import('../modules')) await app.modules const res = await app.handle(req('/lazy')).then((r) => r.text()) expect(res).toBe('lazy') }) it('register async and lazy path', async () => { const app = new Elysia() .use(import('../modules')) .use(asyncPlugin) .get('/', () => 'hi') await app.modules const res = await app.handle(req('/async')) expect(res.status).toEqual(200) }) it('handle other routes while lazy load', async () => { const app = new Elysia().use(import('../timeout')).get('/', () => 'hi') const res = await app.handle(req('/')).then((r) => r.text()) expect(res).toBe('hi') }) it('handle deferred import', async () => { const app = new Elysia().use(import('../modules')) await app.modules const res = await app.handle(req('/lazy')).then((x) => x.text()) expect(res).toBe('lazy') }) it('re-compile on async plugin', async () => { const app = new Elysia().use(async (app) => { await new Promise((resolve) => setTimeout(resolve, 1)) return app.get('/', () => 'hi') }) await app.modules const res = await app.handle(req('/')).then((x) => x.text()) expect(res).toBe('hi') }) it('handle async plugin', async () => { const a = (config = {}) => async (app: Elysia) => { await sleep(0) return app.derive(() => ({ derived: 'async' })) } const app = new Elysia().use(a()).get('/', ({ derived }) => derived) await app.modules const resRoot = await app.handle(req('/')).then((r) => r.text()) expect(resRoot).toBe('async') }) it('do not duplicate functional async plugin lifecycle', async () => { const plugin = async (app: Elysia) => app.get('/', () => 'yay') let fired = 0 const app = new Elysia() .use(plugin) .onRequest(() => { fired++ }) .compile() await app.modules await app.handle(req('/')) expect(fired).toBe(1) }) it('do not duplicate instance async plugin lifecycle', async () => { const plugin = async () => new Elysia().get('/', () => 'yay') let fired = 0 const app = new Elysia() .use(plugin()) .onRequest(() => { fired++ }) .compile() await app.modules await app.handle(req('/')) expect(fired).toBe(1) }) it('handle nested async plugin', async () => { const yay = async () => { await Bun.sleep(2) return new Elysia({ name: 'yay' }).get('/yay', 'yay') } const wrapper = new Elysia({ name: 'wrapper' }).use(yay()) const app = new Elysia().use(wrapper) await app.modules const response = await app.handle(req('/yay')) expect(response.status).toBe(200) }) it('handle recursive nested async plugins', async () => { const delay = any>( callback: T, ms = 617 ): Promise> => Bun.sleep(ms).then(() => callback()) const yay = () => delay(() => new Elysia().get('/nested', 'hi!'), 1) const yay2 = () => delay(() => new Elysia().use(yay), 5) const yay3 = () => delay(() => new Elysia().use(yay2), 10) const wrapper = new Elysia().use(async () => delay(() => yay3(), 6.17)) const app = new Elysia().use(wrapper) await app.modules const response = await app.handle(req('/nested')) expect(response.status).toBe(200) }) it('recompile nested async plugin once registered', async () => { const asyncPlugin = Promise.resolve(new Elysia({ name: 'AsyncPlugin' })) const plugin = new Elysia({ name: 'Plugin' }) .use(asyncPlugin) .get('/plugin', () => 'GET /plugin') const app = new Elysia({ name: 'App' }) .use(plugin) .get('/foo', () => 'GET /foo') const response = await app .handle(new Request('http://localhost/plugin')) .then((x) => x.text()) // https://github.com/elysiajs/elysia/issues/1067 // If the plugin doesn't recompile, route index // would be pointed to /foo instead of /plugin expect(response).toEqual('GET /plugin') }) it('recompile not nested async plugin once registered', async () => { const asyncPlugin = Promise.resolve(new Elysia({ name: 'AsyncPlugin' })) const plugin = new Elysia({ name: 'Plugin' }) .use(asyncPlugin) .get('/plugin', () => 'GET /plugin') const app = new Elysia({ name: 'App' }) .use(plugin) .get('/foo', () => 'GET /foo') const response = await app.handle( new Request('http://localhost/plugin') ) const text = await response.text() expect(text).toEqual('GET /plugin') }) it('register dynamic import routes inside guard', async () => { const app = new Elysia().guard( {}, (app) => app.use(import('../modules').then((m) => m.lazyInstance)) ) await app.modules const res = await app.handle(req('/lazy-instance')) expect(res.status).toBe(200) expect(await res.text()).toBe('lazy-instance') }) it('register multiple dynamic import routes inside guard', async () => { const lazyA = Promise.resolve(new Elysia().get('/a', () => 'a')) const lazyB = Promise.resolve(new Elysia().get('/b', () => 'b')) let hookCalls = 0 const app = new Elysia().guard( { beforeHandle: () => { hookCalls++ } }, (app) => app.use(lazyA).use(lazyB) ) await app.modules expect((await app.handle(req('/a'))).status).toBe(200) expect((await app.handle(req('/b'))).status).toBe(200) expect(hookCalls).toBe(2) }) it('register dynamic import routes inside guard with hook', async () => { let called = false const app = new Elysia().guard( { beforeHandle: () => { called = true } }, (app) => app.use(import('../modules').then((m) => m.lazyInstance)) ) await app.modules await app.handle(req('/lazy-instance')) expect(called).toBe(true) }) }) ================================================ FILE: test/core/mount.test.ts ================================================ import { describe, expect, it } from 'bun:test' import { Elysia } from '../../src' describe('Mount', () => { it('preserve request URL', async () => { const plugin = new Elysia().get('/', ({ request }) => request.url) const app = new Elysia().mount('/mount', plugin.handle) expect( await app .handle(new Request('http://elysiajs.com/mount/')) .then((x) => x.text()) ).toBe('http://elysiajs.com/') }) it('preserve request URL with query', async () => { const plugin = new Elysia().get('/', ({ request }) => request.url) const app = new Elysia().mount('/mount', plugin.handle) expect( await app .handle(new Request('http://elysiajs.com/mount/?a=1')) .then((x) => x.text()) ).toBe('http://elysiajs.com/?a=1') }) it('preserve body', async () => { const handler = async (req: Request) => { return new Response(await req.text()) } const app = new Elysia() .mount('/mount', (req) => handler(req)) .post('/not-mount', ({ body }) => body) const options = { method: 'POST', headers: { 'content-type': 'text/plain' }, body: 'sucrose' } const res = await Promise.all([ app .handle(new Request('http://elysiajs.com/mount', options)) .then((x) => x.text()), app .handle(new Request('http://elysiajs.com/not-mount', options)) .then((x) => x.text()) ]) expect(res).toEqual(['sucrose', 'sucrose']) }) it('remove wildcard path', async () => { const app = new Elysia().mount('/v1/*', (request) => { return Response.json({ path: request.url }) }) const response = await app .handle(new Request('http://localhost/v1/hello')) .then((x) => x.json()) expect(response).toEqual({ path: 'http://localhost/hello' }) }) it('preserve method', async () => { const app = new Elysia().mount((request) => { return Response.json({ method: request.method, path: request.url }) }) const response = await app .handle( new Request('http://localhost/v1/hello', { method: 'PUT' }) ) .then((x) => x.json()) expect(response).toEqual({ method: 'PUT', path: 'http://localhost/v1/hello' }) }) // https://github.com/elysiajs/elysia/issues/1070 it('preserve method with prefix', async () => { const app = new Elysia().mount('/v1/*', (request) => { return Response.json({ method: request.method, path: request.url }) }) const response = await app .handle( new Request('http://localhost/v1/hello', { method: 'PUT' }) ) .then((x) => x.json()) expect(response).toEqual({ method: 'PUT', path: 'http://localhost/hello' }) }) it('preserve headers', async () => { const app = new Elysia().mount((request) => { return Response.json(request.headers.toJSON()) }) const response = await app .handle( new Request('http://localhost/v1/hello', { method: 'PUT', headers: { 'x-test': 'test' } }) ) .then((x) => x.json()) expect(response).toEqual({ 'x-test': 'test' }) }) it('without prefix - strips mount path', async () => { const app = new Elysia().mount('/sdk/problems-domain', (request) => { return Response.json({ path: new URL(request.url).pathname }) }) const response = await app .handle( new Request('http://localhost/sdk/problems-domain/problems') ) .then((x) => x.json() as Promise<{ path: string }>) expect(response.path).toBe('/problems') }) it('with prefix - should strip both prefix and mount path', async () => { const sdkApp = new Elysia({ prefix: '/sdk' }).mount( '/problems-domain', (request) => { return Response.json({ path: new URL(request.url).pathname }) } ) const app = new Elysia().use(sdkApp) const response = await app .handle( new Request('http://localhost/sdk/problems-domain/problems') ) .then((x) => x.json() as Promise<{ path: string }>) expect(response.path).toBe('/problems') }) it('handle body in aot: false', async () => { const app = new Elysia({ aot: false }).mount('/api', async (request) => Response.json(await request.json()) ) const response = await app .handle( new Request('http://localhost/api', { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ message: 'hello world' }) }) ) .then((x) => x.json()) expect(response).toEqual({ message: 'hello world' }) }) it('preserve set-cookie headers from Response with CORS', async () => { const handler = async () => { const headers = new Headers() headers.set('location', '/redirect') headers.append('set-cookie', 'session=abc123; Path=/; HttpOnly') headers.append('set-cookie', 'token=xyz789; Path=/; Secure') return new Response('OK', { status: 302, headers }) } const app = new Elysia() .use((app) => app.onBeforeHandle(({ set }) => { set.headers['access-control-allow-origin'] = '*' }) ) .mount('/auth', handler) const response = await app.handle( new Request('http://localhost/auth/login', { method: 'POST' }) ) const cookies = response.headers.getSetCookie() expect(cookies).toHaveLength(2) expect(cookies).toContain('session=abc123; Path=/; HttpOnly') expect(cookies).toContain('token=xyz789; Path=/; Secure') expect(response.status).toBe(302) expect(response.headers.get('location')).toBe('/redirect') }) }) ================================================ FILE: test/core/native-static.test.ts ================================================ // @ts-nocheck import { Elysia } from '../../src' import { describe, expect, it } from 'bun:test' describe('Native Static Response', () => { it('work', async () => { const app = new Elysia().get('/', 'Static Content') expect(app.router.response['/'].GET).toBeInstanceOf(Response) expect(await app.router.response['/'].GET.text()).toEqual('Static Content') }) it('handle plugin', async () => { const plugin = new Elysia().get('/plugin', 'Plugin') const app = new Elysia().use(plugin).get('/', 'Static Content') expect(app.router.response['/'].GET).toBeInstanceOf(Response) expect(await app.router.response['/'].GET.text()).toEqual('Static Content') expect(app.router.response['/plugin'].GET).toBeInstanceOf(Response) expect(await app.router.response['/plugin'].GET.text()).toEqual('Plugin') }) it('handle default header', async () => { const plugin = new Elysia().get('/plugin', 'Plugin') const app = new Elysia() .headers({ server: 'Elysia' }) .use(plugin) .get('/', 'Static Content') expect(app.router.response['/'].GET).toBeInstanceOf(Response) expect(app.router.response['/'].GET.headers.get('server')).toBe('Elysia') expect(await app.router.response['/'].GET.text()).toEqual('Static Content') expect(app.router.response['/plugin'].GET).toBeInstanceOf(Response) expect(app.router.response['/plugin'].GET.headers.get('server')).toBe('Elysia') expect(await app.router.response['/plugin'].GET.text()).toEqual('Plugin') }) it('turn off by config', async () => { const app = new Elysia({ nativeStaticResponse: false }).get( '/', 'Static Content' ) expect(app.router.response).not.toHaveProperty('/') }) it('handle loose path', async () => { const plugin = new Elysia().get('/plugin', 'Plugin') const app = new Elysia().use(plugin).get('/', 'Static Content') expect(app.router.response['/'].GET).toBeInstanceOf(Response) expect(await app.router.response['/'].GET.text()).toEqual('Static Content') expect(app.router.response[''].GET).toBeInstanceOf(Response) expect(await app.router.response[''].GET.text()).toEqual('Static Content') expect(app.router.response['/plugin'].GET).toBeInstanceOf(Response) expect(await app.router.response['/plugin'].GET.text()).toEqual('Plugin') expect(app.router.response['/plugin/'].GET).toBeInstanceOf(Response) expect(await app.router.response['/plugin/'].GET.text()).toEqual('Plugin') const strict = new Elysia({ strictPath: true }) .use(plugin) .get('/', 'Static Content') expect(strict.router.response['/'].GET).toBeInstanceOf(Response) expect(await strict.router.response['/'].GET.text()).toEqual( 'Static Content' ) expect(strict.router.response).not.toHaveProperty('') expect(strict.router.response['/plugin'].GET).toBeInstanceOf(Response) expect(await strict.router.response['/plugin'].GET.text()).toEqual('Plugin') expect(strict.router.response).not.toHaveProperty('/plugin/') }) }) ================================================ FILE: test/core/normalize.test.ts ================================================ import { describe, it, expect } from 'bun:test' import { Elysia, t } from '../../src' import { post, req } from '../utils' describe('Normalize', () => { it('normalize response', async () => { const app = new Elysia().get( '/', () => { return { hello: 'world', a: 'b' } }, { response: t.Object({ hello: t.String() }) } ) const response = await app.handle(req('/')).then((x) => x.json()) expect(response).toEqual({ hello: 'world' }) }) it('normalize optional response', async () => { const app = new Elysia().get( '/', () => { return { hello: 'world', a: 'b' } }, { response: t.Optional( t.Object({ hello: t.String() }) ) } ) const response = await app.handle(req('/')).then((x) => x.json()) expect(response).toEqual({ hello: 'world' }) }) it('strictly validate response if not normalize', async () => { const app = new Elysia({ normalize: false }).get( '/', () => { return { hello: 'world', a: 'b' } }, { response: t.Object({ hello: t.String() }) } ) const response = await app.handle(req('/')) expect(response.status).toEqual(422) }) it('normalize multiple response', async () => { const app = new Elysia().get( '/', // @ts-ignore ({ status }) => status(418, { name: 'Nagisa', hifumi: 'daisuki' }), { response: { 200: t.Object({ hello: t.String() }), 418: t.Object({ name: t.Literal('Nagisa') }) } } ) const response = await app.handle(req('/')).then((x) => x.json()) expect(response).toEqual({ name: 'Nagisa' }) }) it('strictly validate multiple response', async () => { const app = new Elysia({ normalize: false }).get( '/', // @ts-ignore ({ status }) => status(418, { name: 'Nagisa', hifumi: 'daisuki' }), { response: { 200: t.Object({ hello: t.String() }), 418: t.Object({ name: t.Literal('Nagisa') }) } } ) const response = await app.handle(req('/')) expect(response.status).toEqual(422) }) it('normalize multiple response using 200', async () => { const app = new Elysia().get( '/', () => { return { hello: 'Nagisa', hifumi: 'daisuki' } }, { response: { 200: t.Object({ hello: t.String() }), 418: t.Object({ name: t.Literal('Nagisa') }) } } ) const response = await app.handle(req('/')).then((x) => x.json()) expect(response).toEqual({ hello: 'Nagisa' }) }) it('strictly validate multiple response using 200 if not normalize', async () => { const app = new Elysia({ normalize: false }).get( '/', () => { return { hello: 'Nagisa', hifumi: 'daisuki' } }, { response: { 200: t.Object({ hello: t.String() }), 418: t.Object({ name: t.Literal('Nagisa') }) } } ) const response = await app.handle(req('/')) expect(response.status).toEqual(422) }) it('do not normalize response when allowing additional properties', async () => { const app = new Elysia().get( '/', () => { return { hello: 'world', a: 'b' } }, { response: t.Object( { hello: t.String() }, { additionalProperties: true } ) } ) const response = await app.handle(req('/')).then((x) => x.json()) expect(response).toEqual({ hello: 'world', a: 'b' }) }) it('normalize body', async () => { const app = new Elysia().post('/', ({ body }) => body, { body: t.Object({ name: t.String() }) }) const response = await app .handle( post('/', { name: 'nagisa', hifumi: 'daisuki' }) ) .then((x) => x.json()) expect(response).toEqual({ name: 'nagisa' }) }) it('normalize optional body', async () => { const app = new Elysia().post('/', ({ body }) => body, { body: t.Optional( t.Object({ name: t.String() }) ) }) const response = await app .handle( post('/', { name: 'nagisa', hifumi: 'daisuki' }) ) .then((x) => x.json()) expect(response).toEqual({ name: 'nagisa' }) }) it('strictly validate body if not normalize', async () => { const app = new Elysia({ normalize: false }).post( '/', ({ body }) => body, { body: t.Object({ name: t.String() }) } ) const response = await app.handle( post('/', { name: 'nagisa', hifumi: 'daisuki' }) ) expect(response.status).toBe(422) }) it('loosely validate body if not normalize and has additionalProperties', async () => { const app = new Elysia({ normalize: false }).post( '/', ({ body }) => body, { body: t.Object( { name: t.String() }, { additionalProperties: true } ) } ) const response = await app.handle( post('/', { name: 'nagisa', hifumi: 'daisuki' }) ) expect(response.status).toBe(200) expect(await response.json()).toEqual({ name: 'nagisa', hifumi: 'daisuki' }) }) it('normalize query', async () => { const app = new Elysia().get('/', ({ query }) => query, { query: t.Object({ name: t.String() }) }) const response = await app .handle(req('/?name=nagisa&hifumi=daisuki')) .then((x) => x.json()) expect(response).toEqual({ name: 'nagisa' }) }) it("don't normalize query on additionalProperties", async () => { const app = new Elysia().get('/', ({ query }) => query, { query: t.Object( { name: t.String() }, { additionalProperties: true } ) }) const response = await app .handle(req('/?name=nagisa&hifumi=daisuki')) .then((x) => x.json()) expect(response).toEqual({ name: 'nagisa', hifumi: 'daisuki' }) }) it('normalize based on property when normalized is disabled', async () => { const app = new Elysia({ normalize: false }).get( '/', ({ query }) => query, { query: t.Object( { name: t.String() }, { additionalProperties: true } ) } ) const response = await app .handle(req('/?name=nagisa&hifumi=daisuki')) .then((x) => x.json()) expect(response).toEqual({ name: 'nagisa', hifumi: 'daisuki' }) }) it('normalize headers', async () => { const app = new Elysia().get('/', ({ headers }) => headers, { headers: t.Object({ name: t.String() }) }) const response = await app .handle( req('/', { headers: { name: 'nagisa', hifumi: 'daisuki' } }) ) .then((x) => x.json()) expect(response).toEqual({ name: 'nagisa' }) }) it('loosely validate headers by default if not normalized', async () => { const app = new Elysia({ normalize: false }).get( '/', ({ headers }) => headers, { headers: t.Object({ name: t.String() }) } ) const headers = { name: 'sucrose', job: 'alchemist' } const res = await app.handle( req('/', { headers }) ) expect(await res.json()).toEqual(headers) expect(res.status).toBe(200) }) it('strictly validate headers if not normalized and additionalProperties is false', async () => { const app = new Elysia({ normalize: false }).get( '/', ({ headers }) => headers, { headers: t.Object( { name: t.String() }, { additionalProperties: false } ) } ) const response = await app.handle( req('/', { headers: { name: 'nagisa', hifumi: 'daisuki' } }) ) expect(response.status).toBe(422) }) it('response normalization does not mutate', async () => { // Long-lived object has a `token` property const service = { name: 'nagisa', status: 'online', token: 'secret' } // ...but this property is hidden by the response schema const responseSchema = t.Object({ name: t.String(), status: t.String() }) const app = new Elysia({ normalize: true }).get('/test', () => service, { response: responseSchema }) expect(service).toHaveProperty('token') const origService = structuredClone(service) const response = await app.handle(new Request('http://localhost/test')) expect(response.body).not.toHaveProperty('token') // Expect the `token` property to remain present after `service` object was used in a response expect(service).toHaveProperty('token') // In fact, expect the `service` to not be mutated at all expect(service).toEqual(origService) }) it('normalize nested schema', async () => { const type = t.Array( t.Object({ id: t.String(), date: t.Date(), name: t.String() }) ) const date = new Date('2025-07-11T00:00:00.000Z') const app = new Elysia().get( '/', () => { return [ { id: 'testId', date, name: 'testName', needNormalize: 'yes' } ] }, { response: { 200: type } } ) const response = (await app .handle(new Request('http://localhost:3000/')) .then((x) => x.json())) as typeof type.static expect(response).toEqual([ { id: 'testId', // @ts-ignore date is normalized to ISO string by default date: date.toISOString(), name: 'testName' } ]) }) it('normalize encodeSchema with Transform', async () => { const app = new Elysia().get( '/', () => ({ hasMore: true, total: 1, offset: 0, totalPages: 1, currentPage: 1, items: [{ username: 'Bob', secret: 'shhh' }] }), { // I don't know why but it must be this exact shape response: t.Object({ hasMore: t.Boolean(), items: t.Array( t.Object({ username: t.String() }) ), total: t .Transform(t.Number()) .Decode((x) => x) .Encode((x) => x), offset: t.Number({ minimum: 0 }), totalPages: t.Number(), currentPage: t.Number({ minimum: 1 }) }) } ) const data = await app.handle(req('/')).then((x) => x.json()) expect(data).toEqual({ hasMore: true, items: [ { username: 'Bob' } ], total: 1, offset: 0, totalPages: 1, currentPage: 1 }) }) // it('normalize response with getter fields on class', async () => { // const app = new Elysia({ // normalize: true // }).get( // '/', // () => { // class MyTest { // constructor(hello: string) { // this.one = hello // this.two = hello // } // public one: string // public two: string // get oneGet() { // return this.one // } // get twoGet() { // return this.two // } // } // const res = new MyTest('world') // return res // }, // { // response: t.Object( // { // one: t.String(), // oneGet: t.String() // }, // { additionalProperties: false } // ) // } // ) // const response = await app.handle(req('/')).then((x) => x.json()) // expect(response).toEqual({ // one: 'world', // oneGet: 'world' // }) // }) // it('normalize response with getter fields on simple object', async () => { // const app = new Elysia({ // normalize: true // }).get( // '/', // () => { // return { // one: 'world', // get oneGet() { // return 'world' // }, // two: 'world', // get twoGet() { // return 'world' // } // } // }, // { // response: t.Object( // { // one: t.String(), // oneGet: t.String() // }, // { additionalProperties: false } // ) // } // ) // const response = await app.handle(req('/')).then((x) => x.json()) // expect(response).toEqual({ // one: 'world', // oneGet: 'world' // }) // }) // it('normalize response with getter fields on class array', async () => { // const app = new Elysia({ // normalize: true // }).get( // '/', // () => { // class MyTest { // constructor(hello: string) { // this.one = hello // this.two = hello // } // public one: string // public two: string // get oneGet() { // return this.one // } // get twoGet() { // return this.two // } // } // const res = new MyTest('world') // return [res] // }, // { // response: t.Array( // t.Object( // { // one: t.String(), // oneGet: t.String() // }, // { additionalProperties: false } // ) // ) // } // ) // const response = await app.handle(req('/')).then((x) => x.json()) // expect(response).toEqual([ // { // one: 'world', // oneGet: 'world' // } // ]) // }) // it('normalize response with getter fields on simple object array', async () => { // const app = new Elysia({ // normalize: true // }).get( // '/', // () => { // return [ // { // one: 'world', // get oneGet() { // return 'world' // }, // two: 'world', // get twoGet() { // return 'world' // } // } // ] // }, // { // response: t.Array( // t.Object( // { // one: t.String(), // oneGet: t.String() // }, // { additionalProperties: false } // ) // ) // } // ) // const response = await app.handle(req('/')).then((x) => x.json()) // expect(response).toEqual([ // { // one: 'world', // oneGet: 'world' // } // ]) // }) // it('normalize response with getter fields on class array with nested arrays', async () => { // const app = new Elysia({ // normalize: true // }).get( // '/', // () => { // class MyTest { // constructor(hello: string) { // this.one = hello // this.two = hello // } // public one: string // public two: string // get oneGet() { // return this.one // } // get twoGet() { // return this.two // } // } // class MyTest2 { // constructor(hello: string) { // this.one = hello // this.two = hello // this.three = [new MyTest(hello)] // this.four = [new MyTest(hello)] // } // public one: string // public two: string // public three: MyTest[] // public four: MyTest[] // get oneGet() { // return this.one // } // get twoGet() { // return this.two // } // get threeGet() { // return this.three // } // get fourGet() { // return this.four // } // } // const res = new MyTest2('world') // return [res] // }, // { // response: t.Array( // t.Object( // { // one: t.String(), // oneGet: t.String(), // three: t.Array( // t.Object( // { // one: t.String(), // oneGet: t.String() // }, // { additionalProperties: false } // ) // ), // threeGet: t.Array( // t.Object( // { // one: t.String(), // oneGet: t.String() // }, // { additionalProperties: false } // ) // ) // }, // { additionalProperties: false } // ) // ) // } // ) // const response = await app.handle(req('/')).then((x) => x.json()) // expect(response).toEqual([ // { // one: 'world', // oneGet: 'world', // three: [ // { // one: 'world', // oneGet: 'world' // } // ], // threeGet: [ // { // one: 'world', // oneGet: 'world' // } // ] // } // ]) // }) // it('normalize response with getter fields on simple object array with nested arrays', async () => { // const app = new Elysia({ // normalize: true // }).get( // '/', // () => { // const o = [ // { // one: 'world', // get oneGet() { // return 'world' // }, // two: 'world', // get twoGet() { // return 'world' // } // } // ] // return [ // { // one: 'world', // get oneGet() { // return 'world' // }, // two: 'world', // get twoGet() { // return 'world' // }, // three: o, // get threeGet() { // return o // }, // four: o, // get fourGet() { // return o // } // } // ] // }, // { // response: t.Array( // t.Object( // { // one: t.String(), // oneGet: t.String(), // three: t.Array( // t.Object( // { // one: t.String(), // oneGet: t.String() // }, // { additionalProperties: false } // ) // ), // threeGet: t.Array( // t.Object( // { // one: t.String(), // oneGet: t.String() // }, // { additionalProperties: false } // ) // ) // }, // { additionalProperties: false } // ) // ) // } // ) // const response = await app.handle(req('/')).then((x) => x.json()) // expect(response).toEqual([ // { // one: 'world', // oneGet: 'world', // three: [ // { // one: 'world', // oneGet: 'world' // } // ], // threeGet: [ // { // one: 'world', // oneGet: 'world' // } // ] // } // ]) // }) // it('normalize getter field', async () => { // class Example { // field1: string // field3?: string // constructor( // private field2: string, // field1: string, // field3?: string // ) { // this.field1 = field1 // this.field3 = field3 // } // get getterField() { // return this.field2 // } // } // const app = new Elysia().get( // '/', // () => new Example('field2', 'field1'), // { // response: t.Object({ // field1: t.String(), // field3: t.Optional(t.String()), // getterField: t.String() // }) // } // ) // const response = await app.handle(req('/')).then((x) => x.json()) // expect(response).toEqual({ // field1: 'field1', // getterField: 'field2' // }) // }) }) ================================================ FILE: test/core/path.test.ts ================================================ import { Elysia, t } from '../../src' import { describe, expect, it } from 'bun:test' describe('handle path with spaces', () => { it('AOT on: static path', async () => { const PATH = '/y a y' const app = new Elysia().get( PATH, () => 'result from a path wirh spaces' ) const response = await app.handle( new Request(`http://localhost${PATH}`) ) expect(response.status).toBe(200) }) it('AOT off: static path', async () => { const PATH = '/y a y' const app = new Elysia({ aot: false }).get( PATH, () => 'result from a path wirh spaces' ) const response = await app.handle( new Request(`http://localhost${PATH}`) ) expect(response.status).toBe(200) }) it('AOT on: dynamic path', async () => { const app = new Elysia().get('/y a y/:id', ({ params: { id } }) => id) const response = await app.handle( new Request(`http://localhost/y a y/1`) ) expect(response.status).toBe(200) expect(await response.text()).toBe('1') }) it('AOT off: dynamic path', async () => { const app = new Elysia({ aot: false }).get( '/y a y/:id', ({ params: { id } }) => id ) const response = await app.handle( new Request(`http://localhost/y a y/1`) ) expect(response.status).toBe(200) expect(await response.text()).toBe('1') }) it('AOT on: optional dynamic path', async () => { const app = new Elysia().get( '/y a y/:id?', ({ params: { id } }) => id ?? 0 ) const response = await Promise.all( [ new Request(`http://localhost/y a y`), new Request(`http://localhost/y a y/1`) ].map(app.handle) ) expect(response[0].status).toBe(200) expect(response[1].status).toBe(200) const value = await Promise.all(response.map((x) => x.text())) expect(value[0]).toBe('0') expect(value[1]).toBe('1') }) it('AOT off: optional dynamic path', async () => { const app = new Elysia({ aot: false }).get( '/y a y/:id?', ({ params: { id } }) => id ?? 0 ) const response = await Promise.all( [ new Request(`http://localhost/y a y`), new Request(`http://localhost/y a y/1`) ].map(app.handle) ) expect(response[0].status).toBe(200) expect(response[1].status).toBe(200) const value = await Promise.all(response.map((x) => x.text())) expect(value[0]).toBe('0') expect(value[1]).toBe('1') }) it('handle optional path parameters after required', async () => { const app = new Elysia().get( '/api/:required/:optional?', ({ params }) => params.required ) const required = await app.handle( new Request('http://localhost/api/yay') ) expect(required.status).toBe(200) expect(await required.text()).toEqual('yay') const optional = await app.handle( new Request('http://localhost/api/yay/ok') ) expect(optional.status).toBe(200) expect(await optional.text()).toEqual('yay') }) }) ================================================ FILE: test/core/redirect.test.ts ================================================ import { describe, expect, it } from 'bun:test' import { Elysia } from '../../src' import { req } from '../utils' describe('Redirect', () => { it('handles redirect without explicit status', async () => { const app = new Elysia().get('/', ({ set, redirect }) => redirect('/hello') ) const res = await app.handle(req('/')) expect(res.status).toBe(302) expect(res.headers.get('location')).toBe('/hello') }) it('handles redirect with explicit status', async () => { const app = new Elysia().get('/', ({ set, redirect }) => redirect('/hello', 303) ) const res = await app.handle(req('/')) expect(res.status).toBe(303) expect(res.headers.get('location')).toBe('/hello') }) }) ================================================ FILE: test/core/sanitize.test.ts ================================================ import { describe, expect, it } from 'bun:test' import { Elysia, t } from '../../src' import { post } from '../utils' describe('Sanitize', () => { it('handle single sanitize', async () => { const app = new Elysia({ sanitize: (v) => (v === 'a' ? 'ok' : v) }).post('/', ({ body }) => body, { body: t.Object({ a: t.String(), b: t.String(), c: t.String() }) }) const response = await app .handle( post('/', { a: 'a', b: 'b', c: 'c' }) ) .then((x) => x.json()) expect(response).toEqual({ a: 'ok', b: 'b', c: 'c' }) }) it('multiple sanitize', async () => { const app = new Elysia({ sanitize: [ (v) => (v === 'a' ? 'ok' : v), (v) => (v === 'b' ? 'ok' : v) ] }).post('/', ({ body }) => body, { body: t.Object({ a: t.String(), b: t.String(), c: t.String() }) }) const response = await app .handle( post('/', { a: 'a', b: 'b', c: 'c' }) ) .then((x) => x.json()) expect(response).toEqual({ a: 'ok', b: 'ok', c: 'c' }) }) it('handle sanitize in plugin from main', async () => { const plugin = new Elysia().post('/', ({ body }) => body, { body: t.Object({ a: t.String(), b: t.String(), c: t.String() }) }) const app = new Elysia({ sanitize: (v) => (v === 'a' ? 'ok' : v) }).use(plugin) const response = await app .handle( post('/', { a: 'a', b: 'b', c: 'c' }) ) .then((x) => x.json()) expect(response).toEqual({ a: 'ok', b: 'b', c: 'c' }) }) it('handle top-level string', async () => { const app = new Elysia({ sanitize: (v) => (v === 'a' ? 'ok' : v) }).post('/', ({ body }) => body, { body: t.String() }) const response = await app .handle( new Request('http://localhost', { method: 'POST', headers: { 'Content-Type': 'text/plain' }, body: 'a' }) ) .then((x) => x.text()) expect(response).toBe('ok') }) }) ================================================ FILE: test/core/status.test.ts ================================================ import { describe, it, expect } from 'bun:test' import Elysia, { t } from '../../src' import { req } from '../utils' describe('Status', () => { it('work', async () => { const app = new Elysia().get('/', ({ status }) => status(201)) const response = await app.handle(req('/')) expect(response.status).toBe(201) expect(await response.text()).toBe('Created') }) // Bun support 101 or >= 200 status it('ignore response body of 101', async () => { const app = new Elysia().get('/', ({ status }) => status(101)) const response = await app.handle(req('/')) expect(response.status).toBe(101) expect(await response.text()).toBe('') }) it('ignore explicit response body of 101', async () => { const app = new Elysia().get('/', ({ status }) => status(101, 'Hello')) const response = await app.handle(req('/')) expect(response.status).toBe(101) expect(await response.text()).toBe('') }) it('ignore response body of 204', async () => { const app = new Elysia().get('/', ({ status }) => status(204)) const response = await app.handle(req('/')) expect(response.status).toBe(204) expect(await response.text()).toBe('') }) it('ignore explicit response body of 204', async () => { const app = new Elysia().get('/', ({ status }) => status(204, 'Hello')) const response = await app.handle(req('/')) expect(response.status).toBe(204) expect(await response.text()).toBe('') }) it('ignore response body of 205', async () => { const app = new Elysia().get('/', ({ status }) => status(205)) const response = await app.handle(req('/')) expect(response.status).toBe(205) expect(await response.text()).toBe('') }) it('ignore explicit response body of 205', async () => { const app = new Elysia().get('/', ({ status }) => status(205, 'Hello')) const response = await app.handle(req('/')) expect(response.status).toBe(205) expect(await response.text()).toBe('') }) it('ignore response body of 304', async () => { const app = new Elysia().get('/', ({ status }) => status(304)) const response = await app.handle(req('/')) expect(response.status).toBe(304) expect(await response.text()).toBe('') }) it('ignore explicit response body of 304', async () => { const app = new Elysia().get('/', ({ status }) => status(304, 'Hello')) const response = await app.handle(req('/')) expect(response.status).toBe(304) expect(await response.text()).toBe('') }) it('ignore response body of 307', async () => { const app = new Elysia().get('/', ({ status }) => status(307)) const response = await app.handle(req('/')) expect(response.status).toBe(307) expect(await response.text()).toBe('') }) it('ignore explicit response body of 307', async () => { const app = new Elysia().get('/', ({ status }) => status(307, 'Hello')) const response = await app.handle(req('/')) expect(response.status).toBe(307) expect(await response.text()).toBe('') }) it('ignore response body of 308', async () => { const app = new Elysia().get('/', ({ status }) => status(308)) const response = await app.handle(req('/')) expect(response.status).toBe(308) expect(await response.text()).toBe('') }) it('ignore explicit response body of 308', async () => { const app = new Elysia().get('/', ({ status }) => status(308, 'Hello')) const response = await app.handle(req('/')) expect(response.status).toBe(308) expect(await response.text()).toBe('') }) }) ================================================ FILE: test/core/stop.test.ts ================================================ import { describe, expect, it } from 'bun:test' import { Elysia } from '../../src' describe('Stop', () => { it('shuts down the server when stop(true) is called', async () => { const app = new Elysia() app.get('/health', 'hi') const port = 8080 const server = app.listen(port) await fetch(`http://localhost:${port}/health`) await server.stop(true) // Check if the server is still running try { await fetch(`http://localhost:${port}/health`) throw new Error('Server is still running after teardown') } catch (error) { expect((error as Error).message).toContain('Unable to connect') } }) it('does not shut down the server when stop(false) is called', async () => { const app = new Elysia() app.get('/health', 'hi') const port = 8081 const server = app.listen(port) await fetch(`http://localhost:${port}/health`) await server.stop(false) // Check if the server is still running try { const response = await fetch(`http://localhost:${port}/health`) expect(response.status).toBe(200) expect(await response.text()).toBe('hi') } catch (error) { throw new Error('Server unexpectedly shut down') } }) }) ================================================ FILE: test/extends/decorators.test.ts ================================================ /* eslint-disable @typescript-eslint/no-unused-vars */ import { describe, it, expect } from 'bun:test' import { Elysia } from '../../src' import { req } from '../utils' describe('Decorate', () => { it('decorate primitive', async () => { const app = new Elysia() .decorate('name', 'Ina') .decorate('name', 'Tako') expect(app.decorator.name).toBe('Ina') }) it('decorate multiple', async () => { const app = new Elysia() .decorate('name', 'Ina') .decorate('job', 'artist') expect(app.decorator).toEqual({ name: 'Ina', job: 'artist' }) }) it('decorate object', async () => { const app = new Elysia() .decorate({ name: 'Ina', job: 'artist' }) .decorate({ as: 'override' }, { name: 'Fubuki' }) expect(app.decorator).toEqual({ name: 'Fubuki', job: 'artist' }) }) it('remap object', async () => { const app = new Elysia() .decorate({ name: 'Ina', job: 'artist' }) .decorate(({ job, ...rest }) => ({ ...rest, job: 'streamer' })) expect(app.decorator).toEqual({ name: 'Ina', job: 'streamer' }) }) it('inherits functional plugin', async () => { const plugin = () => (app: Elysia) => app.decorate('hi', () => 'hi') const app = new Elysia().use(plugin()).get('/', ({ hi }) => hi()) const res = await app.handle(req('/')).then((r) => r.text()) expect(res).toBe('hi') }) it('inherits instance plugin', async () => { const plugin = new Elysia().decorate('hi', () => 'hi') const app = new Elysia().use(plugin).get('/', ({ hi }) => hi()) const res = await app.handle(req('/')).then((r) => r.text()) expect(res).toBe('hi') }) it('accepts any type', async () => { const app = new Elysia().decorate('hi', { there: { hello: 'world' } }) expect(app.decorator.hi.there.hello).toBe('world') }) it('remap', async () => { const app = new Elysia() .decorate('job', 'artist') .decorate('name', 'Ina') .decorate(({ job, ...decorators }) => ({ ...decorators, job: 'vtuber' })) expect(app.decorator.job).toBe('vtuber') }) it('handle class deduplication', async () => { let _i = 0 class A { public i: number constructor() { this.i = _i++ } } const app = new Elysia().decorate('a', new A()).decorate('a', new A()) expect(app.decorator.a.i).toBe(0) }) it('handle nested object deduplication', async () => { const app = new Elysia() .decorate('a', { hello: { world: 'Tako' } }) .decorate('a', { hello: { world: 'Ina', cookie: 'wah!' } }) expect(app.decorator).toEqual({ a: { hello: { world: 'Tako', cookie: 'wah!' } } }) }) it('override primitive', async () => { const app = new Elysia() .decorate('name', 'Ina') .decorate({ as: 'override' }, 'name', 'Tako') expect(app.decorator.name).toBe('Tako') }) it('override object', async () => { const app = new Elysia() .decorate({ name: 'Ina', job: 'artist' }) .decorate( { as: 'override' }, { name: 'Fubuki' } ) expect(app.decorator).toEqual({ name: 'Fubuki', job: 'artist' }) }) it('override handle class', async () => { let _i = 0 class A { public i: number constructor() { this.i = _i++ } } const app = new Elysia() .decorate('a', new A()) .decorate({ as: 'override' }, 'a', new A()) expect(app.decorator.a.i).toBe(1) }) it('override nested object deduplication using name', async () => { const app = new Elysia() .decorate('a', { hello: { world: 'Tako' } }) .decorate({ as: 'override' }, 'a', { hello: { world: 'Ina', cookie: 'wah!' } }) expect(app.decorator.a.hello).toEqual({ world: 'Ina', cookie: 'wah!' }) }) it('override nested object deduplication using value', async () => { const app = new Elysia() .decorate({ hello: { world: 'Tako' } }) .decorate( { as: 'override' }, { hello: { world: 'Ina', cookie: 'wah!' } } ) expect(app.decorator.hello).toEqual({ world: 'Ina', cookie: 'wah!' }) }) it('handle escaped name', async () => { const app = new Elysia() .decorate('name ina', 'Ina') expect(app.decorator['name ina']).toBe('Ina') }) }) ================================================ FILE: test/extends/error.test.ts ================================================ /* eslint-disable @typescript-eslint/no-unused-vars */ import { Elysia, t } from '../../src' import { describe, expect, it } from 'bun:test' import { post, req } from '../utils' import z from 'zod' class CustomError extends Error { constructor() { super() } } const getErrors = (app: Elysia) => // @ts-ignore Object.keys(app.definitions.error) describe('Error', () => { it('add single', async () => { const app = new Elysia().error('CUSTOM_ERROR', CustomError) expect(getErrors(app)).toEqual(['CUSTOM_ERROR']) }) it('add multiple', async () => { const app = new Elysia() .error('CUSTOM_ERROR', CustomError) .error('CUSTOM_ERROR_2', CustomError) expect(getErrors(app)).toEqual(['CUSTOM_ERROR', 'CUSTOM_ERROR_2']) }) it('add object', async () => { const app = new Elysia().error({ CUSTOM_ERROR: CustomError, CUSTOM_ERROR_2: CustomError }) expect(getErrors(app)).toEqual(['CUSTOM_ERROR', 'CUSTOM_ERROR_2']) }) // it('remap error', async () => { // const app = new Elysia() // .error({ // CUSTOM_ERROR: CustomError, // CUSTOM_ERROR_2: CustomError // }) // .error(({ CUSTOM_ERROR, ...rest }) => ({ // ...rest, // CUSTOM_ERROR_3: CustomError // })) // expect(getErrors(app)).toEqual(['CUSTOM_ERROR', 'CUSTOM_ERROR_2']) // }) it('inherits functional plugin', async () => { const plugin = (app: Elysia) => app.error('CUSTOM_ERROR', CustomError) const app = new Elysia().use(plugin) expect(getErrors(app)).toEqual(['CUSTOM_ERROR']) }) it('inherits instance plugin', async () => { const plugin = new Elysia().error('CUSTOM_ERROR', CustomError) const app = new Elysia().use(plugin) expect(getErrors(app)).toEqual(['CUSTOM_ERROR']) }) it('preserve status code base on error if not set', async () => { const app = new Elysia().onError(({ code }) => { if (code === 'NOT_FOUND') return 'UwU' }) const response = await app.handle(req('/not/found')) expect(await response.text()).toBe('UwU') expect(response.status).toBe(404) }) it('validation error should be application/json', async () => { // @ts-expect-error const app = new Elysia().get('/', () => '1', { response: t.Null() }) const response = await app.handle(req('/')) expect(response.status).toBe(422) expect(response.headers.get('content-type')).toBe('application/json') }) it('validation error should handle Standard Schema with error.detail', async () => { const sendOtpEmailSchema = z.object({ channel: z.literal('email'), otpTo: z.email({ error: 'Must be a valid email address' }) }) const sendOtpSmsSchema = z.object({ channel: z.literal('sms'), otpTo: z.e164({ error: 'Must be a valid phone number with country code' }) }) const sendOtpSchema = z.discriminatedUnion('channel', [ sendOtpEmailSchema, sendOtpSmsSchema ]) const app = new Elysia() .onError(({ code, error, status }) => { switch (code) { case 'VALIDATION': return error.detail(error.message) } }) .post('/', ({ body, set }) => 'ok', { body: sendOtpSchema }) const response = await app.handle(post('/', {})) expect(response.status).toBe(422) }) }) ================================================ FILE: test/extends/models.test.ts ================================================ /* eslint-disable @typescript-eslint/no-unused-vars */ import { Elysia, t } from '../../src' import { describe, expect, it } from 'bun:test' import { post, req } from '../utils' describe('Model', () => { it('add single', async () => { const app = new Elysia() .model('string', t.String()) // @ts-ignore .route('GET', '/', (context) => Object.keys(context.defs), { config: { allowMeta: true } }) const res = await app.handle(req('/')).then((r) => r.json()) expect(res).toEqual(['string']) }) it('add multiple', async () => { const app = new Elysia() .model('string', t.String()) .model('number', t.Number()) // @ts-ignore .route('GET', '/', (context) => Object.keys(context.defs), { config: { allowMeta: true } }) const res = await app.handle(req('/')).then((r) => r.json()) expect(res).toEqual(['string', 'number']) }) it('add object', async () => { const app = new Elysia() .model({ string: t.String(), number: t.Number() }) // @ts-ignore .route('GET', '/', (context) => Object.keys(context.defs), { config: { allowMeta: true } }) const res = await app.handle(req('/')).then((r) => r.json()) expect(res).toEqual(['string', 'number']) }) it('add object', async () => { const app = new Elysia() .model({ string: t.String(), number: t.Number() }) .model(({ number, ...rest }) => ({ ...rest, boolean: t.Boolean() })) // @ts-ignore .route('GET', '/', (context) => Object.keys(context.defs), { config: { allowMeta: true } }) const res = await app.handle(req('/')).then((r) => r.json()) expect(res).toEqual(['string', 'boolean']) }) it('inherits functional plugin', async () => { const plugin = () => (app: Elysia) => app.model('string', t.String()) const app = new Elysia() .use(plugin()) // @ts-ignore .route('GET', '/', (context) => Object.keys(context.defs), { config: { allowMeta: true } }) const res = await app.handle(req('/')).then((r) => r.json()) expect(res).toEqual(['string']) }) it('inherits instance plugin', async () => { const plugin = () => (app: Elysia) => app.model('string', t.String()) const app = new Elysia() .use(plugin()) // @ts-ignore .route('GET', '/', (context) => Object.keys(context.defs), { config: { allowMeta: true } }) const res = await app.handle(req('/')).then((r) => r.json()) expect(res).toEqual(['string']) }) it('inherits instance plugin', async () => { const plugin = new Elysia().decorate('hi', () => 'hi') const app = new Elysia().use(plugin).get('/', ({ hi }) => hi()) const res = await app.handle(req('/')).then((r) => r.text()) expect(res).toBe('hi') }) it('validate reference model', async () => { const app = new Elysia() .model({ number: t.Number() }) .post('/', ({ body: { data } }) => data, { response: 'number', body: t.Object({ data: t.Number() }) }) .post('/arr', ({ body }) => body, { response: 'number', body: 'number' }) const correct = await app.handle( new Request('http://localhost/', { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ data: 1 }) }) ) expect(correct.status).toBe(200) const correctArr = await app.handle( new Request('http://localhost/arr', { method: 'POST', headers: { 'content-type': 'application/json' }, body: 1 }) ) expect(correctArr.status).toBe(200) const wrong = await app.handle( new Request('http://localhost/', { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ data: true }) }) ) expect(wrong.status).toBe(422) const wrongArr = await app.handle( new Request('http://localhost/arr', { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ data: true }) }) ) expect(wrongArr.status).toBe(422) }) it('remap', async () => { const app = new Elysia() .model('string', t.String()) .model('number', t.Number()) .model(({ number, ...rest }) => ({ ...rest, numba: number })) // @ts-ignore .route('GET', '/', (context) => Object.keys(context.defs), { config: { allowMeta: true } }) const res = await app.handle(req('/')).then((r) => r.json()) expect(res).toEqual(['string', 'numba']) }) it('use reference model', async () => { const app = new Elysia() .model({ string: t.Object({ data: t.String() }) }) .post('/', ({ body }) => body, { body: 'string' }) const error = await app.handle(post('/')) expect(error.status).toBe(422) const correct = await app.handle( post('/', { data: 'hi' }) ) expect(correct.status).toBe(200) }) // it('use coerce with reference model', async () => { // const app = new Elysia() // .model({ // number: t.Number(), // optionalNumber: t.Optional(t.Ref('number')) // }) // .post('/', ({ body }) => body, { // body: 'optionalNumber' // }) // const error = await app.handle(post('/')) // expect(error.status).toBe(422) // const correct = await app.handle( // new Request('http://localhost/', { // method: 'POST', // headers: { // 'content-type': 'text/plain; charset=utf-8' // }, // body: '0' // }) // ) // expect(correct.status).toBe(200) // }) it('create default value with nested reference model', async () => { const app = new Elysia() .model({ number: t.Numeric({ default: 0 }), optionalNumber: t.Optional(t.Ref('number')) }) .post('/', ({ body }) => body, { body: t.Optional(t.Ref('number')) }) const result = await app.handle(post('/')).then((x) => x.text()) expect(result).toBe('0') }) it('create default value with reference model', async () => { const app = new Elysia() .model({ number: t.Number({ default: 0 }), optionalNumber: t.Optional(t.Ref('number')) }) .post('/', ({ body }) => body, { body: 'optionalNumber' }) const result = await app.handle(post('/')).then((x) => x.text()) expect(result).toBe('0') }) it('use reference model with cookie', async () => { const app = new Elysia() .model({ session: t.Cookie({ token: t.Numeric() }), optionalSession: t.Optional(t.Ref('session')) }) .get('/', () => 'Hello Elysia', { cookie: 'optionalSession' }) const error = await app.handle( new Request('http://localhost/', { headers: { cookie: 'token=string' } }) ) expect(error.status).toBe(422) const correct = await app.handle( new Request('http://localhost/', { headers: { cookie: 'token=1' } }) ) expect(correct.status).toBe(200) }) it('use reference model with response', async () => { const app = new Elysia() .model({ res: t.String() }) .get('/correct', () => 'Hello Elysia', { response: 'res' }) // @ts-expect-error .get('/error', () => 1, { response: 'res' }) const error = await app.handle(req('/error')) expect(error.status).toBe(422) const correct = await app.handle(req('/correct')) expect(correct.status).toBe(200) }) it('use reference model with response per status', async () => { const app = new Elysia() .model({ res: t.String() }) .get('/correct', () => 'Hello Elysia', { response: { 200: 'res', 400: 'res' } }) .get('/400', ({ status }) => status(400, 'ok'), { response: { 200: 'res', 400: 'res' } }) // @ts-expect-error .get('/error', ({ status }) => status(400, 1), { response: { 200: 'res', 400: 'res' } }) const error = await app.handle(req('/error')) expect(error.status).toBe(422) const correct = await app.handle(req('/correct')) expect(correct.status).toBe(200) const correct400 = await app.handle(req('/400')) expect(correct400.status).toBe(400) }) it("coerce model when there's no reference", async () => { const app = new Elysia() .model({ idParam: t.Object({ id: t.Union([ t.String({ format: 'uuid' }), t.Number({ minimum: 1, maximum: Number.MAX_SAFE_INTEGER }) ]) }), response200: t.Object({ message: t.String(), content: t.Array( t.Object({ id: t.Union([t.String(), t.Number()]) }) ) }) }) .get( '/:id', ({ params: { id } }) => ({ message: 'ok', content: [{ id }] }), { params: 'idParam', response: 'response200' } ) const value = await app .handle(new Request('http://localhost/3')) .then((x) => x.json()) expect(value).toEqual({ message: 'ok', content: [{ id: 3 }] }) }) }) ================================================ FILE: test/extends/store.test.ts ================================================ /* eslint-disable @typescript-eslint/no-unused-vars */ import { describe, it, expect } from 'bun:test' import { Elysia } from '../../src' import { req } from '../utils' describe('State', () => { it('decorate primitive', async () => { const app = new Elysia().state('name', 'Ina').state('name', 'Tako') expect(app.store.name).toBe('Ina') }) it('decorate multiple', async () => { const app = new Elysia().state('name', 'Ina').state('job', 'artist') expect(app.store).toEqual({ name: 'Ina', job: 'artist' }) }) it('decorate object', async () => { const app = new Elysia() .state({ name: 'Ina', job: 'artist' }) .state({ name: 'Fubuki' }) expect(app.store).toEqual({ name: 'Ina', job: 'artist' }) }) it('remap object', async () => { const app = new Elysia() .state({ name: 'Ina', job: 'artist' }) .state(({ job, ...rest }) => ({ ...rest, job: 'streamer' })) expect(app.store).toEqual({ name: 'Ina', job: 'streamer' }) }) it('inherits functional plugin', async () => { const plugin = () => (app: Elysia) => app.state('hi', () => 'hi') const app = new Elysia() .use(plugin()) .get('/', ({ store: { hi } }) => hi()) const res = await app.handle(req('/')).then((r) => r.text()) expect(res).toBe('hi') }) it('inherits instance plugin', async () => { const plugin = new Elysia().state('hi', () => 'hi') const app = new Elysia() .use(plugin) .get('/', ({ store: { hi } }) => hi()) const res = await app.handle(req('/')).then((r) => r.text()) expect(res).toBe('hi') }) it('accepts any type', async () => { const app = new Elysia().state('hi', { there: { hello: 'world' } }) expect(app.store.hi.there.hello).toBe('world') }) it('remap', async () => { const app = new Elysia() .state('job', 'artist') .state('name', 'Ina') .state(({ job, ...decorators }) => ({ ...decorators, job: 'vtuber' })) expect(app.store.job).toBe('vtuber') }) it('handle class deduplication', async () => { let _i = 0 class A { public i: number constructor() { this.i = _i++ } } const app = new Elysia().state('a', new A()).state('a', new A()) expect(app.store.a.i).toBe(0) }) it('handle nested object deduplication', async () => { const app = new Elysia() .state('a', { hello: { world: 'Tako' } }) .state('a', { hello: { world: 'Ina', cookie: 'wah!' } }) expect(app.store).toEqual({ a: { hello: { world: 'Tako', cookie: 'wah!' } } }) }) it('override primitive', async () => { const app = new Elysia() .state('name', 'Ina') .state({ as: 'override' }, 'name', 'Tako') expect(app.store.name).toBe('Tako') }) it('override object', async () => { const app = new Elysia() .state({ name: 'Ina', job: 'artist' }) .state( { as: 'override' }, { name: 'Fubuki' } ) expect(app.store).toEqual({ name: 'Fubuki', job: 'artist' }) }) it('override handle class', async () => { let _i = 0 class A { public i: number constructor() { this.i = _i++ } } const app = new Elysia() .state('a', new A()) .state({ as: 'override' }, 'a', new A()) expect(app.store.a.i).toBe(1) }) it('override nested object deduplication using name', async () => { const app = new Elysia() .state('a', { hello: { world: 'Tako' } }) .state({ as: 'override' }, 'a', { hello: { world: 'Ina', cookie: 'wah!' } }) expect(app.store.a.hello).toEqual({ world: 'Ina', cookie: 'wah!' }) }) it('override nested object deduplication using value', async () => { const app = new Elysia() .state({ hello: { world: 'Tako' } }) .state( { as: 'override' }, { hello: { world: 'Ina', cookie: 'wah!' } } ) expect(app.store.hello).toEqual({ world: 'Ina', cookie: 'wah!' }) }) }) ================================================ FILE: test/hoc/index.test.ts ================================================ /* eslint-disable @typescript-eslint/no-unused-vars */ import { describe, it, expect } from 'bun:test' import { Elysia } from '../../src' import { req } from '../utils' describe('HOC', () => { it('work', async () => { let called = 0 const app = new Elysia() .wrap((fn) => { called++ return fn }) .get('/', () => 'ok') await app.handle(req('/')) expect(called).toBe(1) }) it('deduplicate', async () => { const plugin = new Elysia().wrap((fn) => fn) const plugin2 = new Elysia({ name: 'plugin2' }).wrap((fn) => fn) const app = new Elysia() .use(plugin) .use(plugin) .use(plugin) .use(plugin2) .use(plugin2) .get('/', () => 'ok') // @ts-expect-error expect(app.extender.higherOrderFunctions.length).toBe(2) }) }) ================================================ FILE: test/lifecycle/after-handle.test.ts ================================================ import { Elysia } from '../../src' import { describe, expect, it } from 'bun:test' import { req } from '../utils' describe('After Handle', () => { it('work global', async () => { const app = new Elysia().onAfterHandle(() => 'A').get('/', () => 'NOOP') const res = await app.handle(req('/')).then((x) => x.text()) expect(res).toBe('A') }) it('work local', async () => { const app = new Elysia().get('/', () => 'NOOP', { afterHandle() { return 'A' } }) const res = await app.handle(req('/')).then((x) => x.text()) expect(res).toBe('A') }) it('inherits from plugin', async () => { const transformType = new Elysia().onAfterHandle( { as: 'global' }, ({ response }) => { if (response === 'string') return 'number' } ) const app = new Elysia() .use(transformType) .get('/id/:id', ({ params: { id } }) => typeof id) const res = await app.handle(req('/id/1')) expect(await res.text()).toBe('number') }) it('not inherits plugin on local', async () => { const transformType = new Elysia().onAfterHandle(({ response }) => { if (response === 'string') return 'number' }) const app = new Elysia() .use(transformType) .get('/id/:id', ({ params: { id } }) => typeof id) const res = await app.handle(req('/id/1')) expect(await res.text()).toBe('string') }) it('register using on', async () => { const app = new Elysia() .on('transform', (request) => { if (request.params?.id) request.params.id = +request.params.id }) .get('/id/:id', ({ params: { id } }) => typeof id) const res = await app.handle(req('/id/1')) expect(await res.text()).toBe('number') }) it('after handle in order', async () => { let order = [] const app = new Elysia() .onAfterHandle(() => { order.push('A') }) .onAfterHandle(() => { order.push('B') }) .get('/', () => '') await app.handle(req('/')) expect(order).toEqual(['A', 'B']) }) it('accept response', async () => { const app = new Elysia().get('/', () => 'NOOP', { afterHandle({ response }) { return response }, mapResponse() { } }) const res = await app.handle(req('/')).then((x) => x.text()) expect(res).toBe('NOOP') }) it('accept responseValue', async () => { const app = new Elysia().get('/', () => 'NOOP', { afterHandle({ responseValue }) { return responseValue }, mapResponse() { } }) const res = await app.handle(req('/')).then((x) => x.text()) expect(res).toBe('NOOP') }) it('as global', async () => { const called = [] const plugin = new Elysia() .onAfterHandle({ as: 'global' }, ({ path }) => { called.push(path) }) .get('/inner', () => 'NOOP') const app = new Elysia().use(plugin).get('/outer', () => 'NOOP') const res = await Promise.all([ app.handle(req('/inner')), app.handle(req('/outer')) ]) expect(called).toEqual(['/inner', '/outer']) }) it('as local', async () => { const called = [] const plugin = new Elysia() .onAfterHandle({ as: 'local' }, ({ path }) => { called.push(path) }) .get('/inner', () => 'NOOP') const app = new Elysia().use(plugin).get('/outer', () => 'NOOP') const res = await Promise.all([ app.handle(req('/inner')), app.handle(req('/outer')) ]) expect(called).toEqual(['/inner']) }) it('support array', async () => { let total = 0 const app = new Elysia() .onAfterHandle([ () => { total++ }, () => { total++ } ]) .get('/', () => 'NOOP') const res = await app.handle(req('/')) expect(total).toEqual(2) }) }) ================================================ FILE: test/lifecycle/after-response.test.ts ================================================ import { Elysia } from '../../src' import { describe, expect, it } from 'bun:test' import { req } from '../utils' describe('After Handle', () => { it('set response status', async () => { let status: number const app = new Elysia() .onAfterResponse(({ set }) => { status = set.status as number }) .get('/error', (c) => c.status(401, { error: 'Unauthorized' })) const res = await app.handle(req('/error')).then((x) => x.json()) expect(res).toEqual({ error: 'Unauthorized' }) await Bun.sleep(1) expect(status!).toBe(401) }) it('set response status for default 404', async () => { let status: number const app = new Elysia().onAfterResponse(({ set }) => { status = set.status as number }) const res = await app.handle(req('/error')) expect(res.status).toEqual(404) await Bun.sleep(1) expect(status!).toBe(404) }) }) ================================================ FILE: test/lifecycle/before-handle.test.ts ================================================ import { Elysia } from '../../src' import { describe, expect, it } from 'bun:test' import { delay, req } from '../utils' describe('Before Handle', () => { it('globally skip main handler', async () => { const app = new Elysia() .onBeforeHandle<{ params: { name?: string } }>(({ params: { name } }) => { if (name === 'Fubuki') return 'Cat' }) .get('/name/:name', ({ params: { name } }) => name) const res = await app.handle(req('/name/Fubuki')) expect(await res.text()).toBe('Cat') }) it('locally skip main handler', async () => { const app = new Elysia().get( '/name/:name', ({ params: { name } }) => name, { beforeHandle: ({ params: { name } }) => { if (name === 'Fubuki') return 'Cat' } } ) const res = await app.handle(req('/name/Fubuki')) expect(await res.text()).toBe('Cat') }) it('group before handler', async () => { const app = new Elysia() .group('/type', (app) => app .onBeforeHandle<{ params: { name?: string } }>(({ params: { name } }) => { if (name === 'fubuki') return 'cat' }) .get('/name/:name', ({ params: { name } }) => name) ) .get('/name/:name', ({ params: { name } }) => name) const base = await app.handle(req('/name/fubuki')) const scoped = await app.handle(req('/type/name/fubuki')) expect(await base.text()).toBe('fubuki') expect(await scoped.text()).toBe('cat') }) it('inherits from plugin', async () => { const transformId = new Elysia().onBeforeHandle( { as: 'global' }, ({ params: { name } }) => { if (name === 'Fubuki') return 'Cat' } ) const app = new Elysia() .use(transformId) .get('/name/:name', ({ params: { name } }) => name) const res = await app.handle(req('/name/Fubuki')) expect(await res.text()).toBe('Cat') }) it('not inherits plugin on local', async () => { const beforeHandle = new Elysia().onBeforeHandle( ({ params: { name } }) => { if (name === 'Fubuki') return 'Cat' } ) const app = new Elysia() .use(beforeHandle) .get('/name/:name', ({ params: { name } }) => name) const res = await app.handle(req('/name/Fubuki')) expect(await res.text()).toBe('Fubuki') }) it('before handle in order', async () => { let order = [] const app = new Elysia() .onBeforeHandle(() => { order.push('A') }) .onBeforeHandle(() => { order.push('B') }) .get('/', () => '') await app.handle(req('/')) expect(order).toEqual(['A', 'B']) }) it('globally and locally before handle', async () => { const app = new Elysia() .onBeforeHandle<{ params: { name?: string } }>(({ params: { name } }) => { if (name === 'fubuki') return 'cat' }) .get('/name/:name', ({ params: { name } }) => name, { beforeHandle: ({ params: { name } }) => { if (name === 'korone') return 'dog' } }) const fubuki = await app.handle(req('/name/fubuki')) const korone = await app.handle(req('/name/korone')) expect(await fubuki.text()).toBe('cat') expect(await korone.text()).toBe('dog') }) it('accept multiple before handler', async () => { const app = new Elysia() .onBeforeHandle<{ params: { name?: string } }>(({ params: { name } }) => { if (name === 'fubuki') return 'cat' }) .onBeforeHandle<{ params: { name?: string } }>(({ params: { name } }) => { if (name === 'korone') return 'dog' }) .get('/name/:name', ({ params: { name } }) => name) const fubuki = await app.handle(req('/name/fubuki')) const korone = await app.handle(req('/name/korone')) expect(await fubuki.text()).toBe('cat') expect(await korone.text()).toBe('dog') }) it('handle async', async () => { const app = new Elysia().get( '/name/:name', ({ params: { name } }) => name, { beforeHandle: async ({ params: { name } }) => { await delay(5) if (name === 'Watame') return 'Warukunai yo ne' } } ) const res = await app.handle(req('/name/Watame')) expect(await res.text()).toBe('Warukunai yo ne') }) it("handle on('beforeHandle')", async () => { const app = new Elysia() .on('beforeHandle', async ({ params: { name } }) => { await new Promise((resolve) => setTimeout(() => { resolve() }, 1) ) if (name === 'Watame') return 'Warukunai yo ne' }) .get('/name/:name', ({ params: { name } }) => name) const res = await app.handle(req('/name/Watame')) expect(await res.text()).toBe('Warukunai yo ne') }) it('execute afterHandle', async () => { const app = new Elysia() .onBeforeHandle<{ params: { name?: string } }>(({ params: { name } }) => { if (name === 'Fubuki') return 'Cat' }) .onAfterHandle((context) => { if (context.response === 'Cat') return 'Not cat' }) .get('/name/:name', ({ params: { name } }) => name) const res = await app.handle(req('/name/Fubuki')) expect(await res.text()).toBe('Not cat') }) it('as global', async () => { const called = [] const plugin = new Elysia() .onBeforeHandle({ as: 'global' }, ({ path }) => { called.push(path) }) .get('/inner', () => 'NOOP') const app = new Elysia().use(plugin).get('/outer', () => 'NOOP') const res = await Promise.all([ app.handle(req('/inner')), app.handle(req('/outer')) ]) expect(called).toEqual(['/inner', '/outer']) }) it('as local', async () => { const called = [] const plugin = new Elysia() .onBeforeHandle({ as: 'local' }, ({ path }) => { called.push(path) }) .get('/inner', () => 'NOOP') const app = new Elysia().use(plugin).get('/outer', () => 'NOOP') const res = await Promise.all([ app.handle(req('/inner')), app.handle(req('/outer')) ]) expect(called).toEqual(['/inner']) }) it('support array', async () => { let total = 0 const app = new Elysia() .onAfterHandle([ () => { total++ }, () => { total++ } ]) .get('/', () => 'NOOP') const res = await app.handle(req('/')) expect(total).toEqual(2) }) it('add responseValue to afterHandle, and afterResponse when beforeHandle returns a value', async () => { let hasAfterHandleResponse = false let hasAfterResponseResponse = false const app = new Elysia().get( '/handler', ({ status }) => { return status(401, 'unauthorized handler') }, { afterHandle: ({ responseValue }) => { hasAfterHandleResponse = !!responseValue }, beforeHandle: ({ status }) => status(401, 'unauthorized beforeHandle'), afterResponse: ({ responseValue }) => { hasAfterResponseResponse = !!responseValue } } ) await app.handle(req('/handler')) await delay(10) expect(hasAfterHandleResponse).toBe(true) expect(hasAfterResponseResponse).toBe(true) }) }) ================================================ FILE: test/lifecycle/derive.test.ts ================================================ import { Elysia } from '../../src' import { describe, expect, it } from 'bun:test' import { req } from '../utils' describe('derive', () => { it('work', async () => { const app = new Elysia() .derive(() => ({ hi: () => 'hi' })) .get('/', ({ hi }) => hi()) const res = await app.handle(req('/')).then((t) => t.text()) expect(res).toBe('hi') }) it('inherits plugin', async () => { const plugin = new Elysia().derive({ as: 'global' }, () => ({ hi: () => 'hi' })) const app = new Elysia().use(plugin).get('/', ({ hi }) => hi()) const res = await app.handle(req('/')).then((t) => t.text()) expect(res).toBe('hi') }) it('inherits plugin on local', async () => { const plugin = new Elysia().derive(() => ({ hi: () => 'hi' })) const app = new Elysia() .use(plugin) // @ts-expect-error .get('/', ({ hi }) => typeof hi === 'undefined') const res = await app.handle(req('/')).then((t) => t.text()) expect(res).toBe('true') }) it('derive in order', async () => { let order = [] const app = new Elysia() .derive(() => { order.push('A') return {} }) .derive(() => { order.push('B') return {} }) .get('/', () => '') await app.handle(req('/')) expect(order).toEqual(['A', 'B']) }) it('can mutate store', async () => { const app = new Elysia() .state('counter', 1) .derive(({ store }) => ({ increase: () => store.counter++ })) .get('/', ({ store, increase }) => { increase() return store.counter }) const res = await app.handle(req('/')).then((t) => t.text()) expect(res).toBe('2') }) it('derive with static analysis', async () => { const app = new Elysia() .derive(({ headers: { name } }) => ({ name })) .get('/', ({ name }) => name) const res = await app .handle( new Request('http://localhost/', { headers: { name: 'Elysia' } }) ) .then((t) => t.text()) expect(res).toBe('Elysia') }) it('as global', async () => { const called = [] const plugin = new Elysia() .derive({ as: 'global' }, ({ path }) => { called.push(path) return {} }) .get('/inner', () => 'NOOP') const app = new Elysia().use(plugin).get('/outer', () => 'NOOP') const res = await Promise.all([ app.handle(req('/inner')), app.handle(req('/outer')) ]) expect(called).toEqual(['/inner', '/outer']) }) it('as local', async () => { const called = [] const plugin = new Elysia() .derive({ as: 'local' }, ({ path }) => { called.push(path) return {} }) .get('/inner', () => 'NOOP') const app = new Elysia().use(plugin).get('/outer', () => 'NOOP') const res = await Promise.all([ app.handle(req('/inner')), app.handle(req('/outer')) ]) expect(called).toEqual(['/inner']) }) it('as scoped', async () => { const called = [] const plugin = new Elysia() .derive({ as: 'scoped' }, ({ path }) => { called.push(path) return {} }) .get('/inner', () => 'NOOP') const middle = new Elysia().use(plugin).get('/middle', () => 'NOOP') const app = new Elysia().use(middle).get('/outer', () => 'NOOP') const res = await Promise.all([ app.handle(req('/inner')), app.handle(req('/middle')), app.handle(req('/outer')) ]) expect(called).toEqual(['/inner', '/middle']) }) it('support array', async () => { let total = 0 const app = new Elysia() .onAfterHandle([ () => { total++ }, () => { total++ } ]) .get('/', () => 'NOOP') const res = await app.handle(req('/')) expect(total).toEqual(2) }) it('handle error', async () => { const app = new Elysia() .derive(({ status }) => status(418)) .get('/', () => '') const res = await app.handle(req('/')).then((x) => x.text()) expect(res).toEqual("I'm a teapot") }) // it('work inline', async () => { // const app = new Elysia().get('/', ({ hi }) => hi(), { // derive: () => ({ // hi: () => 'hi' // }) // }) // const res = await app.handle(req('/')).then((t) => t.text()) // expect(res).toBe('hi') // }) // it('work inline array', async () => { // const app = new Elysia().get( // '/', // ({ first, last }) => [last, first].join(' '), // { // derive: [ // () => ({ // first: 'himari' // }), // () => ({ last: 'akeboshi' }) // ] // } // ) // const res = await app.handle(req('/')).then((t) => t.text()) // expect(res).toBe('akeboshi himari') // }) // it('work group guard', async () => { // const app = new Elysia() // .guard( // { // derive: () => ({ hi: () => 'hi' }) // }, // (app) => app.get('/', ({ hi }) => hi()) // ) // // @ts-expect-error // .get('/nope', ({ hi }) => hi?.() ?? 'nope') // const res = await app.handle(req('/')).then((t) => t.text()) // expect(res).toBe('hi') // const nope = await app.handle(req('/nope')).then((t) => t.text()) // expect(nope).toBe('nope') // }) // it('work group array guard', async () => { // const app = new Elysia() // .guard( // { // derive: [ // () => ({ first: 'himari' }), // () => ({ last: 'akeboshi' }) // ] // }, // (app) => // app.get('/', ({ first, last }) => [last, first].join(' ')) // ) // // @ts-expect-error // .get('/nope', ({ first, last }) => [last, first].join('')) // const res = await app.handle(req('/')).then((t) => t.text()) // expect(res).toBe('akeboshi himari') // const nope = await app.handle(req('/nope')).then((t) => t.text()) // expect(nope).toBe('') // }) // it('work local guard', async () => { // const app = new Elysia() // .guard({ // derive: () => ({ hi: () => 'hi' }) // }) // .get('/', ({ hi }) => hi()) // const res = await app.handle(req('/')).then((t) => t.text()) // expect(res).toBe('hi') // }) // it('work local array guard', async () => { // const app = new Elysia() // .guard({ // derive: [ // () => ({ // first: 'himari' // }), // () => ({ last: 'akeboshi' }) // ] // }) // .get('/', ({ first, last }) => [last, first].join(' ')) // const res = await app.handle(req('/')).then((t) => t.text()) // expect(res).toBe('akeboshi himari') // }) // it('work scoped guard', async () => { // const plugin = new Elysia().guard({ // as: 'scoped', // derive: () => ({ hi: () => 'hi' }) // }) // const app = new Elysia().use(plugin).get('/', ({ hi }) => hi()) // const root = new Elysia() // .use(app) // // @ts-expect-error // .get('/root', ({ hi }) => hi?.() ?? 'nope') // const res = await app.handle(req('/')).then((t) => t.text()) // expect(res).toBe('hi') // const res2 = await root.handle(req('/root')).then((t) => t.text()) // expect(res2).toBe('nope') // }) // it('work global guard', async () => { // const plugin = new Elysia().guard({ // as: 'global', // derive: () => ({ hi: () => 'hi' }) // }) // const app = new Elysia().use(plugin).get('/', ({ hi }) => hi()) // const root = new Elysia() // .use(app) // .get('/root', ({ hi }) => hi?.() ?? 'nope') // const res = await app.handle(req('/')).then((t) => t.text()) // expect(res).toBe('hi') // const res2 = await root.handle(req('/root')).then((t) => t.text()) // expect(res2).toBe('hi') // }) it('handle return derive without throw', async () => { let isOnErrorCalled = false const app = new Elysia() .onError(() => { isOnErrorCalled = true }) .derive(({ status }) => status(418)) .get('/', () => '') await app.handle(req('/')) expect(isOnErrorCalled).toBe(false) }) }) ================================================ FILE: test/lifecycle/error.test.ts ================================================ /* eslint-disable @typescript-eslint/no-unused-vars */ import { Elysia, InternalServerError, ParseError, ValidationError, t, validationDetail } from '../../src' import { describe, expect, it } from 'bun:test' import { post, req } from '../utils' import * as z from 'zod' describe('error', () => { it('use custom 404', async () => { const app = new Elysia() .get('/', () => 'hello') .onError(({ code, set }) => { if (code === 'NOT_FOUND') { set.status = 404 return 'UwU' } }) const root = await app.handle(req('/')).then((x) => x.text()) const notFound = await app .handle(req('/not/found')) .then((x) => x.text()) expect(root).toBe('hello') expect(notFound).toBe('UwU') }) it('handle parse error', async () => { const app = new Elysia() .onError(({ code }) => { if (code === 'PARSE') return 'Why you no proper type' }) .post('/', () => { throw new ParseError() }) const root = await app.handle( new Request('http://localhost/', { method: 'POST', body: 'A', headers: { 'content-type': 'application/json' } }) ) expect(await root.text()).toBe('Why you no proper type') expect(root.status).toBe(400) }) it('custom validation error', async () => { const app = new Elysia() .onError(({ code, error, set }) => { if (code === 'VALIDATION') { set.status = 400 return error.all.map((i) => i.summary ? { filed: i.path.slice(1) || 'root', reason: i.message } : {} ) } }) .post('/login', ({ body }) => body, { body: t.Object({ username: t.String(), password: t.String() }) }) const res = await app.handle(post('/login', {})) const data = await res.json() expect(data).toBeArray() expect(res.status).toBe(400) }) it('inherits plugin', async () => { const plugin = new Elysia().onError({ as: 'global' }, () => 'hi') const app = new Elysia().use(plugin).get('/', () => { throw new Error('') }) const res = await app.handle(req('/')).then((t) => t.text()) expect(res).toBe('hi') }) it('not inherits plugin on local', async () => { const plugin = new Elysia().onError(() => 'hi') const app = new Elysia().use(plugin).get('/', () => { throw new Error('') }) const res = await app.handle(req('/')).then((t) => t.text()) expect(res).not.toBe('hi') }) it('custom 500', async () => { const app = new Elysia() .onError(({ code }) => { if (code === 'INTERNAL_SERVER_ERROR') { return 'UwU' } }) .get('/', () => { throw new InternalServerError() }) const response = await app.handle(req('/')) expect(await response.text()).toBe('UwU') expect(response.status).toBe(500) }) it.each([true, false])( 'return correct number status on error function with aot: %p', async (aot) => { const app = new Elysia({ aot }).get('/', ({ status }) => status(418, 'I am a teapot') ) const response = await app.handle(req('/')) expect(response.status).toBe(418) } ) it.each([true, false])( 'return correct named status on error function with aot: %p', async (aot) => { const app = new Elysia({ aot }).get('/', ({ status }) => status("I'm a teapot", 'I am a teapot') ) const response = await app.handle(req('/')) expect(response.status).toBe(418) } ) it.each([true, false])( 'return correct number status without value on error function with aot: %p', async (aot) => { const app = new Elysia({ aot }).get('/', ({ status }) => status(418)) const response = await app.handle(req('/')) expect(response.status).toBe(418) expect(await response.text()).toBe("I'm a teapot") } ) it.each([true, false])( 'return correct named status without value on error function with aot: %p', async (aot) => { const app = new Elysia({ aot }).get('/', ({ status }) => status("I'm a teapot") ) const response = await app.handle(req('/')) expect(response.status).toBe(418) expect(await response.text()).toBe("I'm a teapot") } ) it('handle error in order', async () => { let order = [] const app = new Elysia() .onError(() => { order.push('A') }) .onError(() => { order.push('B') }) .get('/', () => { throw new Error('A') }) await app.handle(req('/')) expect(order).toEqual(['A', 'B']) }) it('as global', async () => { const called = [] const plugin = new Elysia() .onError({ as: 'global' }, ({ path }) => { called.push(path) return {} }) .get('/inner', () => { throw new Error('A') }) const app = new Elysia().use(plugin).get('/outer', () => { throw new Error('A') }) const res = await Promise.all([ app.handle(req('/inner')), app.handle(req('/outer')) ]) expect(called).toEqual(['/inner', '/outer']) }) it('as local', async () => { const called = [] const plugin = new Elysia() .onError({ as: 'local' }, ({ path }) => { called.push(path) return {} }) .get('/inner', () => { throw new Error('A') }) const app = new Elysia().use(plugin).get('/outer', () => { throw new Error('A') }) const res = await Promise.all([ app.handle(req('/inner')), app.handle(req('/outer')) ]) expect(called).toEqual(['/inner']) }) it('support array', async () => { let total = 0 const app = new Elysia() .onAfterHandle([ () => { total++ }, () => { total++ } ]) .get('/', () => 'NOOP') const res = await app.handle(req('/')) expect(total).toEqual(2) }) it('handle custom error thrown in onRequest', async () => { class SomeCustomError extends Error { asJSON() { return JSON.stringify({ somePretty: 'json' }) } } const app = new Elysia() .onError(({ error }) => { if (error instanceof SomeCustomError) return error.asJSON() }) .onRequest(() => { throw new SomeCustomError() }) .get('/', () => '') const body = await app .handle(new Request('https://localhost/')) .then((x) => x.json()) expect(body).toEqual({ somePretty: 'json' }) }) it('handle cookie signature error', async () => { const app = new Elysia({ cookie: { secrets: 'secrets', sign: ['session'] } }) .onError(({ code, error }) => { if (code === 'INVALID_COOKIE_SIGNATURE') return 'Where is the signature?' }) .get('/', ({ cookie: { session } }) => '') const root = await app.handle( new Request('http://localhost/', { headers: { Cookie: 'session=1234' } }) ) expect(await root.text()).toBe('Where is the signature?') expect(root.status).toBe(400) }) it("don't duplicate error from plugin", async () => { let i = 0 const plugin = new Elysia() .onError(() => { i++ }) .get('/', ({ status }) => { throw status(401) }) const app = new Elysia().use(plugin) const response = await app.handle(req('/')) expect(response.status).toBe(401) expect(await response.text()).toBe('Unauthorized') expect(i).toBe(1) }) it('404 should parse query if infer', async () => { const app = new Elysia().onError(({ query }) => query) const response = await app.handle( new Request('http://localhost?hello=world') ) expect(response.status).toBe(404) expect(await response.json()).toEqual({ hello: 'world' }) }) it('handle inline custom error message', async () => { const app = new Elysia().post('/', () => 'Hello World!', { body: t.Object({ x: t.Number({ error: 'x must be a number' }) }) }) const response = await app.handle( new Request('http://localhost', { method: 'POST', body: JSON.stringify({ x: 'hi!' }), headers: { 'Content-Type': 'application/json' } }) ) expect(response.status).toBe(422) const value = await response.text() expect(value).toBe('x must be a number') }) it('handle inline custom error message with validationDetail', async () => { const app = new Elysia().post('/', () => 'Hello World!', { body: t.Object({ x: t.Number({ error: validationDetail('x must be a number') }) }) }) const response = await app.handle( new Request('http://localhost', { method: 'POST', body: JSON.stringify({ x: 'hi!' }), headers: { 'Content-Type': 'application/json' } }) ) expect(response.status).toBe(422) const value = (await response.json()) as Record expect(value.type).toBe('validation') expect(value.message).toBe('x must be a number') }) it('handle custom error message globally', async () => { const app = new Elysia() .onError(({ error, code }) => { if (code === 'VALIDATION') return error.detail(error.message) }) .post('/', () => 'Hello World!', { body: t.Object({ x: t.Number({ error: 'x must be a number' }) }) }) const response = await app.handle( new Request('http://localhost', { method: 'POST', body: JSON.stringify({ x: 'hi!' }), headers: { 'Content-Type': 'application/json' } }) ) expect(response.status).toBe(422) const value = (await response.json()) as Record expect(value.type).toBe('validation') expect(value.message).toBe('x must be a number') }) it('ValidationError.detail only handle custom error', async () => { const app = new Elysia() .onError(({ error, code }) => { if (code === 'VALIDATION') return error.detail(error.message) }) .post('/', () => 'Hello World!', { body: t.Object({ x: t.Number() }) }) const response = await app.handle( new Request('http://localhost', { method: 'POST', body: JSON.stringify({ x: 'hi!' }), headers: { 'Content-Type': 'application/json' } }) ) expect(response.status).toBe(422) const value = (await response.json()) as Record expect(value.type).toBe('validation') expect(value.message).not.toStartWith('{') }) it('ValidationError.all works with Zod validators', async () => { const app = new Elysia() .onError(({ error, code }) => { if (error instanceof ValidationError) { const errors = error.all return { message: 'Validation failed', errors: errors } } }) .post('/login', ({ body }) => body, { body: z.object({ username: z.string(), password: z.string() }) }) const res = await app.handle(post('/login', {})) const data = (await res.json()) as any expect(data).toHaveProperty('message', 'Validation failed') expect(data).toHaveProperty('errors') expect(data.errors).toBeArray() expect(data.errors.length).toBeGreaterThan(0) expect(res.status).toBe(422) }) it('ValidationError.all provides error details with Zod validators', async () => { const app = new Elysia() .onError(({ error, code }) => { expect(code).toBe('VALIDATION') if (error instanceof ValidationError) { const errors = error.all return { message: 'Validation failed', errors: errors.map((e: any) => ({ path: e.path, message: e.message, summary: e.summary })) } } }) .post('/user', ({ body }) => body, { body: z.object({ name: z.string().min(3), email: z.string(), age: z.number().min(18) }) }) const res = await app.handle( post('/user', { name: 'ab', email: 'invalid', age: 10 }) ) const data = (await res.json()) as any expect(data).toHaveProperty('message', 'Validation failed') expect(data).toHaveProperty('errors') expect(data.errors).toBeArray() expect(data.errors.length).toBeGreaterThan(0) for (const error of data.errors) { expect(error).toHaveProperty('path') expect(error).toHaveProperty('message') expect(error).toHaveProperty('summary') } expect(res.status).toBe(422) }) }) ================================================ FILE: test/lifecycle/hook-types.test.ts ================================================ import { Elysia } from '../../src' import { req } from '../../test/utils' import { describe, it, expect } from 'bun:test' // ? Some hooks type are already tested in there respective lifecycle tests // ? This files is to verify weird behavior of hook type in general describe('Hook Types', () => { it('should clone function to prevent hookType link', async () => { const plugin = new Elysia({ name: 'plugin' }).derive( { as: 'scoped' }, () => { return { id: 1 } } ) expect(plugin.event.transform?.[0].scope).toBe('scoped') const a = new Elysia().use(plugin).get('/foo', ({ id }) => { return { id, name: 'foo' } }) expect(plugin.event.transform?.[0].scope).toBe('scoped') const b = new Elysia().use(plugin).get('/bar', ({ id }) => { return { id, name: 'bar' } }) expect(plugin.event.transform?.[0].scope).toBe('scoped') const [res1, res2] = await Promise.all([ a.handle(req('/foo')).then((x) => x.json()), b.handle(req('/bar')).then((x) => x.json()) ]) expect(res1).toEqual({ id: 1, name: 'foo' }) expect(res2).toEqual({ id: 1, name: 'bar' }) }) }) ================================================ FILE: test/lifecycle/map-derive.test.ts ================================================ import { Elysia } from '../../src' import { describe, expect, it } from 'bun:test' import { req } from '../utils' describe('map derive', () => { it('work', async () => { const app = new Elysia() .derive(() => ({ hi: () => 'hi' })) .mapDerive((derivatives) => ({ ...derivatives, hi2: () => 'hi' })) .get('/', ({ hi }) => hi()) .get('/h2', ({ hi2 }) => hi2()) const res = await app.handle(req('/')).then((t) => t.text()) const res2 = await app.handle(req('/h2')).then((t) => t.text()) expect(res).toBe('hi') expect(res2).toBe('hi') }) it('inherits plugin', async () => { const plugin = new Elysia() .derive({ as: 'global' }, () => ({ hi: () => 'hi' })) .mapDerive((derivatives) => ({ ...derivatives, hi2: () => 'hi' })) .get('/h2', ({ hi2 }) => hi2()) const app = new Elysia().use(plugin).get('/', ({ hi }) => hi()) const res = await app.handle(req('/')).then((t) => t.text()) const res2 = await app.handle(req('/h2')).then((t) => t.text()) expect(res).toBe('hi') expect(res2).toBe('hi') }) it('not inherits plugin on local', async () => { const plugin = new Elysia() .derive(() => ({ hi: () => 'hi' })) .mapDerive((derivatives) => ({ ...derivatives, hi2: () => 'hi' })) const app = new Elysia() .use(plugin) // @ts-expect-error .get('/', ({ hi2 }) => typeof hi2 === 'undefined') const res = await app.handle(req('/')).then((t) => t.text()) expect(res).toBe('true') }) it('can mutate store', async () => { const app = new Elysia() .state('counter', 1) .mapDerive(({ store }) => ({ increase: () => store.counter++ })) .get('/', ({ store, increase }) => { increase() return store.counter }) const res = await app.handle(req('/')).then((t) => t.text()) expect(res).toBe('2') }) it('derive with static analysis', async () => { const app = new Elysia() .mapDerive(({ headers: { name } }) => ({ name })) .get('/', ({ name }) => name) const res = await app .handle( new Request('http://localhost/', { headers: { name: 'Elysia' } }) ) .then((t) => t.text()) expect(res).toBe('Elysia') }) it('store in the same stack as transform', async () => { const stack: number[] = [] const app = new Elysia() .onTransform(() => { stack.push(1) }) .mapDerive(() => { stack.push(2) return { name: 'Ina' } }) .get('/', ({ name }) => name, { transform() { stack.push(3) } }) await app.handle( new Request('http://localhost/', { headers: { name: 'Elysia' } }) ) expect(stack).toEqual([1, 2, 3]) }) it('map derive in order', async () => { let order = [] const app = new Elysia() .mapDerive(() => { order.push('A') return {} }) .mapDerive(() => { order.push('B') return {} }) .get('/', () => '') await app.handle(req('/')) expect(order).toEqual(['A', 'B']) }) it('as local', async () => { const called = [] const plugin = new Elysia() .mapDerive({ as: 'local' }, ({ path }) => { called.push(path) return {} }) .get('/inner', () => 'NOOP') const app = new Elysia().use(plugin).get('/outer', () => 'NOOP') const res = await Promise.all([ app.handle(req('/inner')), app.handle(req('/outer')) ]) expect(called).toEqual(['/inner']) }) it('as global', async () => { const called = [] const plugin = new Elysia() .mapDerive({ as: 'global' }, ({ path }) => { called.push(path) return {} }) .get('/inner', () => 'NOOP') const app = new Elysia().use(plugin).get('/outer', () => 'NOOP') const res = await Promise.all([ app.handle(req('/inner')), app.handle(req('/outer')) ]) expect(called).toEqual(['/inner', '/outer']) }) }) ================================================ FILE: test/lifecycle/map-resolve.test.ts ================================================ import { Elysia } from '../../src' import { describe, expect, it } from 'bun:test' import { req } from '../utils' describe('map resolve', () => { it('work', async () => { const app = new Elysia() .resolve(() => ({ hi: () => 'hi' })) .mapResolve((resolvers) => ({ ...resolvers, hi2: () => 'hi' })) .get('/', ({ hi }) => hi()) .get('/h2', ({ hi2 }) => hi2()) const res = await app.handle(req('/')).then((t) => t.text()) const res2 = await app.handle(req('/h2')).then((t) => t.text()) expect(res).toBe('hi') expect(res2).toBe('hi') }) it('inherits plugin', async () => { const plugin = new Elysia() .resolve({ as: 'global' }, () => ({ hi: () => 'hi' })) // .mapResolve((resolvers) => ({ // ...resolvers, // hi2: () => 'hi' // })) // .get('/h2', ({ hi2 }) => hi2()) const app = new Elysia().use(plugin).get('/', ({ hi }) => hi()) const res = await app.handle(req('/')).then((t) => t.text()) // const res2 = await app.handle(req('/h2')).then((t) => t.text()) expect(res).toBe('hi') // expect(res2).toBe('hi') }) it('not inherits plugin', async () => { const plugin = new Elysia() .resolve(() => ({ hi: () => 'hi' })) .mapResolve((resolvers) => ({ ...resolvers, hi2: () => 'hi' })) .get('/h2', ({ hi2 }) => hi2()) const app = new Elysia() .use(plugin) // @ts-expect-error .get('/', ({ hi2 }) => typeof hi2 === "undefined") const res = await app.handle(req('/')).then((t) => t.text()) const res2 = await app.handle(req('/h2')).then((t) => t.text()) expect(res).toBe('true') expect(res2).toBe('hi') }) it('map resolve in order', async () => { let order = [] const app = new Elysia() .mapResolve(() => { order.push('A') return {} }) .mapResolve(() => { order.push('B') return {} }) .get('/', () => '') await app.handle(req('/')) expect(order).toEqual(['A', 'B']) }) it('can mutate store', async () => { const app = new Elysia() .state('counter', 1) .mapResolve(({ store }) => ({ increase: () => store.counter++ })) .get('/', ({ store, increase }) => { increase() return store.counter }) const res = await app.handle(req('/')).then((t) => t.text()) expect(res).toBe('2') }) it('resolve with static analysis', async () => { const app = new Elysia() .mapResolve(({ headers: { name } }) => ({ name })) .get('/', ({ name }) => name) const res = await app .handle( new Request('http://localhost/', { headers: { name: 'Elysia' } }) ) .then((t) => t.text()) expect(res).toBe('Elysia') }) it('store in the same stack as before handle', async () => { const stack: number[] = [] const app = new Elysia() .onBeforeHandle(() => { stack.push(1) }) .mapResolve(() => { stack.push(2) return { name: 'Ina' } }) .get('/', ({ name }) => name, { beforeHandle() { stack.push(3) } }) await app.handle( new Request('http://localhost/', { headers: { name: 'Elysia' } }) ) expect(stack).toEqual([1, 2, 3]) }) it('as global', async () => { const called = [] const plugin = new Elysia() .mapResolve({ as: 'global' }, ({ path }) => { called.push(path) return {} }) .get('/inner', () => 'NOOP') const app = new Elysia().use(plugin).get('/outer', () => 'NOOP') const res = await Promise.all([ app.handle(req('/inner')), app.handle(req('/outer')) ]) expect(called).toEqual(['/inner', '/outer']) }) it('as local', async () => { const called = [] const plugin = new Elysia() .mapResolve({ as: 'local' }, ({ path }) => { called.push(path) return {} }) .get('/inner', () => 'NOOP') const app = new Elysia().use(plugin).get('/outer', () => 'NOOP') const res = await Promise.all([ app.handle(req('/inner')), app.handle(req('/outer')) ]) expect(called).toEqual(['/inner']) }) }) ================================================ FILE: test/lifecycle/map-response.test.ts ================================================ import { Elysia, form } from '../../src' import { describe, expect, it } from 'bun:test' import { req } from '../utils' describe('Map Response', () => { it('work global', async () => { const app = new Elysia() .mapResponse(() => new Response('A')) .get('/', () => 'Hutao') const res = await app.handle(req('/')).then((x) => x.text()) expect(res).toBe('A') }) it('work local', async () => { const app = new Elysia().get('/', () => 'Hutao', { mapResponse() { return new Response('A') } }) const res = await app.handle(req('/')).then((x) => x.text()) expect(res).toBe('A') }) it('set header', async () => { const app = new Elysia().get( '/', ({ set }) => { set.headers['X-Powered-By'] = 'Elysia' return 'a' }, { mapResponse() { return new Response('A', { headers: { 'X-Test': 'OK' } }) } } ) const headers = await app.handle(req('/')).then((x) => x.headers) expect(headers.get('X-Test')).toContain('OK') expect(headers.get('X-Powered-By')).toContain('Elysia') }) it('inherits plugin', async () => { const plugin = new Elysia().mapResponse( { as: 'global' }, () => new Response('Fubuki') ) const app = new Elysia().use(plugin).get('/', () => 'a') const res = await app.handle(req('/')).then((t) => t.text()) expect(res).toBe('Fubuki') }) it('not inherits plugin on local', async () => { const plugin = new Elysia().mapResponse(() => new Response('Fubuki')) const app = new Elysia().use(plugin).get('/', () => 'a') const res = await app.handle(req('/')).then((t) => t.text()) expect(res).toBe('a') }) it('map response only once', async () => { const app = new Elysia().get('/', () => 'Hutao', { mapResponse: [ () => {}, () => { return new Response('A') }, () => { return new Response('B') } ] }) const res = await app.handle(req('/')).then((x) => x.text()) expect(res).toBe('A') }) it('inherit response', async () => { const app = new Elysia().get('/', () => 'Hu', { mapResponse({ response }) { if (typeof response === 'string') return new Response(response + 'tao') } }) const res = await app.handle(req('/')).then((x) => x.text()) expect(res).toBe('Hutao') }) it('inherit response using responseValue', async () => { const app = new Elysia().get('/', () => 'Hu', { mapResponse({ responseValue }) { if (typeof responseValue === 'string') return new Response(responseValue + 'tao') } }) const res = await app.handle(req('/')).then((x) => x.text()) expect(res).toBe('Hutao') }) it('inherit set', async () => { const app = new Elysia().get('/', () => 'Hu', { mapResponse({ response, set }) { set.headers['X-Powered-By'] = 'Elysia' if (typeof response === 'string') return new Response(response + 'tao', { headers: { 'X-Series': 'Genshin' } }) } }) const res = await app.handle(req('/')).then((x) => x.headers) expect(res.get('X-Powered-By')).toBe('Elysia') expect(res.get('X-Series')).toBe('Genshin') }) it('inherit set using responseValue', async () => { const app = new Elysia().get('/', () => 'Hu', { mapResponse({ responseValue, set }) { set.headers['X-Powered-By'] = 'Elysia' if (typeof responseValue === 'string') return new Response(responseValue + 'tao', { headers: { 'X-Series': 'Genshin' } }) } }) const res = await app.handle(req('/')).then((x) => x.headers) expect(res.get('X-Powered-By')).toBe('Elysia') expect(res.get('X-Series')).toBe('Genshin') }) it('return async', async () => { const app = new Elysia() .mapResponse(async () => new Response('A')) .get('/', () => 'Hutao') const res = await app.handle(req('/')).then((x) => x.text()) expect(res).toBe('A') }) it('skip async', async () => { const app = new Elysia() .mapResponse(async () => {}) .get('/', () => 'Hutao') const res = await app.handle(req('/')).then((x) => x.text()) expect(res).toBe('Hutao') }) it('map response in order', async () => { let order = [] const app = new Elysia() .mapResponse(() => { order.push('A') }) .mapResponse(() => { order.push('B') }) .get('/', () => '') await app.handle(req('/')) expect(order).toEqual(['A', 'B']) }) it('as global', async () => { const called = [] const plugin = new Elysia() .mapResponse({ as: 'global' }, ({ path }) => { called.push(path) }) .get('/inner', () => 'NOOP') const app = new Elysia().use(plugin).get('/outer', () => 'NOOP') const res = await Promise.all([ app.handle(req('/inner')), app.handle(req('/outer')) ]) expect(called).toEqual(['/inner', '/outer']) }) it('as local', async () => { const called = [] const plugin = new Elysia() .mapResponse({ as: 'local' }, ({ path }) => { called.push(path) }) .get('/inner', () => 'NOOP') const app = new Elysia().use(plugin).get('/outer', () => 'NOOP') const res = await Promise.all([ app.handle(req('/inner')), app.handle(req('/outer')) ]) expect(called).toEqual(['/inner']) }) it('support array', async () => { let total = 0 const app = new Elysia() .mapResponse([ () => { total++ }, () => { total++ } ]) .get('/', () => 'NOOP') const res = await app.handle(req('/')) expect(total).toEqual(2) }) it('mapResponse in error', async () => { class CustomClass { constructor(public name: string) {} } const app = new Elysia() .trace(() => {}) .onError(() => new CustomClass('aru')) .mapResponse(({ response }) => { if (response instanceof CustomClass) return new Response(response.name) }) .get('/', () => { throw new Error('Hello') }) const response = await app.handle(req('/')).then((x) => x.text()) expect(response).toBe('aru') }) it('mapResponse in error using responseValue', async () => { class CustomClass { constructor(public name: string) {} } const app = new Elysia() .trace(() => {}) .onError(() => new CustomClass('aru')) .mapResponse(({ responseValue }) => { if (responseValue instanceof CustomClass) return new Response(responseValue.name) }) .get('/', () => { throw new Error('Hello') }) const response = await app.handle(req('/')).then((x) => x.text()) expect(response).toBe('aru') }) // https://github.com/elysiajs/elysia/issues/965 it('mapResponse with after handle', async () => { const app = new Elysia() .onAfterHandle(() => {}) .mapResponse((context) => { return new Response(context.response + '') }) .get('/', async () => 'aru') const response = await app.handle(req('/')).then((x) => x.text()) expect(response).toBe('aru') }) it('mapResponse with after handle using responseValue', async () => { const app = new Elysia() .onAfterHandle(() => {}) .mapResponse((context) => { return new Response(context.responseValue + '') }) .get('/', async () => 'aru') const response = await app.handle(req('/')).then((x) => x.text()) expect(response).toBe('aru') }) it('mapResponse with onError', async () => { const app = new Elysia() .onError(() => {}) .mapResponse(() => {}) .get('/', () => 'ok') const response = await app.handle(req('/')).then((x) => x.text()) expect(response).toBe('ok') }) it('handle set in mapResonse', async () => { const app = new Elysia() .mapResponse(({ set }) => { set.headers['x-powered-by'] = 'Elysia' }) .get('/', new Response('ok')) const response = await app.handle(req('/')) const value = await response.text() expect(value).toBe('ok') expect(response.headers.get('x-powered-by')).toBe('Elysia') }) }) ================================================ FILE: test/lifecycle/parser.test.ts ================================================ import { Elysia, t } from '../../src' import { describe, expect, it } from 'bun:test' import { post } from '../utils' describe('Parser', () => { it('handle onParse', async () => { const app = new Elysia() .onParse((context, contentType) => { switch (contentType) { case 'application/Elysia': return 'A' } }) .post('/', ({ body }) => body) const res = await app.handle( new Request('http://localhost/', { method: 'POST', body: ':D', headers: { 'content-type': 'application/Elysia', 'content-length': '2' } }) ) expect(await res.text()).toBe('A') }) it('register using on', async () => { const app = new Elysia() .on('parse', (context, contentType) => { switch (contentType) { case 'application/Elysia': return context.request.text() } }) .post('/', ({ body }) => body) const res = await app.handle( new Request('http://localhost/', { method: 'POST', body: ':D', headers: { 'content-type': 'application/Elysia', 'content-length': '2' } }) ) expect(await res.text()).toBe(':D') }) it('overwrite default parser', async () => { const app = new Elysia() .onParse((context, contentType) => { switch (contentType) { case 'text/plain': return 'Overwrited' } }) .post('/', ({ body }) => body) const res = await app.handle( new Request('http://localhost/', { method: 'POST', body: ':D', headers: { 'content-type': 'text/plain', 'content-length': '2' } }) ) expect(await res.text()).toBe('Overwrited') }) it('parse x-www-form-urlencoded', async () => { const app = new Elysia().post('/', ({ body }) => body) const body = { username: 'salty aom', password: '12345678' } const res = await app.handle( new Request('http://localhost/', { method: 'POST', body: `username=${body.username}&password=${body.password}`, headers: { 'content-type': 'application/x-www-form-urlencoded' } }) ) expect(await res.json()).toEqual(body) }) it('parse with extra content-type attribute', async () => { const app = new Elysia().post('/', ({ body }) => body) const body = { username: 'salty aom', password: '12345678' } const res = await app.handle( new Request('http://localhost/', { method: 'POST', body: JSON.stringify(body), headers: { 'content-type': 'application/json;charset=utf-8' } }) ) expect(await res.json()).toEqual(body) }) it('inline parse', async () => { const app = new Elysia().post('/', ({ body }) => body, { parse({ request }) { return request.text().then(() => 'hi') } }) const res = await app .handle( new Request('http://localhost/', { method: 'POST', body: 'ok', headers: { 'Content-Type': 'application/json' } }) ) .then((x) => x.text()) expect(res).toBe('hi') }) it('map parser in order', async () => { let order = [] const app = new Elysia() .onParse({ as: 'global' }, ({ path }) => { order.push('A') }) .onParse({ as: 'global' }, ({ path }) => { order.push('B') }) .post('/', ({ body }) => 'NOOP') const res = await app.handle(post('/', {})) expect(order).toEqual(['A', 'B']) }) it('inherits plugin', async () => { const plugin = new Elysia().onParse({ as: 'global' }, () => 'Kozeki Ui') const app = new Elysia().use(plugin).post('/', ({ body }) => body) const res = await app.handle(post('/', {})).then((t) => t.text()) expect(res).toBe('Kozeki Ui') }) it('not inherits plugin on local', async () => { const plugin = new Elysia().onParse(() => 'Kozeki Ui') const app = new Elysia().use(plugin).post('/', ({ body }) => body) const res = await app .handle(post('/', { name: 'Kozeki Ui' })) .then((t) => t.json()) expect(res).toEqual({ name: 'Kozeki Ui' }) }) it('as global', async () => { const called = [] const plugin = new Elysia() .onParse({ as: 'global' }, ({ path }) => { called.push(path) }) .post('/inner', () => 'NOOP') const app = new Elysia().use(plugin).post('/outer', () => 'NOOP') const res = await Promise.all([ app.handle(post('/inner', {})), app.handle(post('/outer', {})) ]) expect(called).toEqual(['/inner', '/outer']) }) it('as local', async () => { const called = [] const plugin = new Elysia() .onParse({ as: 'local' }, ({ path }) => { called.push(path) }) .post('/inner', () => 'NOOP') const app = new Elysia().use(plugin).post('/outer', () => 'NOOP') const res = await Promise.all([ app.handle(post('/inner', {})), app.handle(post('/outer', {})) ]) expect(called).toEqual(['/inner']) }) it('support array', async () => { let total = 0 const app = new Elysia() .onParse([ () => { total++ }, () => { total++ } ]) .post('/', ({ body }) => 'NOOP') const res = await app.handle(post('/', {})) expect(total).toEqual(2) }) it('handle type with validator with custom parse', async () => { const app = new Elysia().post('/json', ({ body: { name } }) => name, { body: t.Object({ name: t.String() }), parse: [ ({ contentType }) => { if (contentType === 'custom') return { name: 'Mutsuki' } }, 'json' ] }) const [correct, incorrect, custom] = await Promise.all([ app.handle(post('/json', { name: 'Aru' })).then((x) => x.text()), app .handle(post('/json', { school: 'Gehenna' })) .then((x) => x.status), app .handle( new Request('http://localhost/json', { method: 'POST', body: JSON.stringify({ name: 'Aru' }), headers: { 'content-type': 'custom' } }) ) .then((x) => x.text()) ]) expect(correct).toBe('Aru') expect(incorrect).toBe(422) expect(custom).toBe('Mutsuki') }) it('handle name parser', async () => { const app = new Elysia().post('/json', ({ body }) => body, { parse: ['json'] }) const response = await app .handle( new Request('http://localhost:3000/json', { method: 'POST', body: JSON.stringify({ name: 'Aru' }) }) ) .then((x) => x.json()) expect(response).toEqual({ name: 'Aru' }) }) it('handle custom parser then fallback to named default', async () => { const app = new Elysia() .parser('custom', ({ contentType, request }) => { if (contentType.startsWith('application/x-elysia')) return { name: 'Eden' } }) .post('/json', ({ body }) => body, { parse: ['custom', 'json'] }) const response = await Promise.all([ app .handle( new Request('http://localhost:3000/json', { method: 'POST', body: JSON.stringify({ name: 'Aru' }) }) ) .then((x) => x.json()), app .handle( new Request('http://localhost:3000/json', { method: 'POST', headers: { 'content-type': 'application/x-elysia' }, body: JSON.stringify({ name: 'Aru' }) }) ) .then((x) => x.json()) ]) expect(response).toEqual([{ name: 'Aru' }, { name: 'Eden' }]) }) it('handle custom parser then fallback to unknown', async () => { const app = new Elysia() .parser('custom', ({ contentType, request }) => { if (contentType.startsWith('application/x-elysia')) return { name: 'Eden' } }) .post('/json', ({ body }) => body, { parse: ['custom'] }) const response = await Promise.all([ app .handle( new Request('http://localhost:3000/json', { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ name: 'Aru' }) }) ) .then((x) => x.json()), app .handle( new Request('http://localhost:3000/json', { method: 'POST', headers: { 'content-type': 'application/x-elysia' }, body: JSON.stringify({ name: 'Aru' }) }) ) .then((x) => x.json()) ]) expect(response).toEqual([{ name: 'Aru' }, { name: 'Eden' }]) }) it('handle parser from plugin', async () => { const plugin = new Elysia().parser( 'custom', ({ contentType, request }) => { if (contentType === 'application/x-elysia') return { name: 'Eden' } } ) const app = new Elysia() .use(plugin) .parser('custom2', ({ contentType, request }) => { if (contentType === 'application/x-elysia-2') return { name: 'Pardofelis' } }) .post('/json', ({ body }) => body, { parse: ['custom', 'custom2'] }) const response = await Promise.all([ app .handle( new Request('http://localhost:3000/json', { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ name: 'Aru' }) }) ) .then((x) => x.json()), app .handle( new Request('http://localhost:3000/json', { method: 'POST', headers: { 'content-type': 'application/x-elysia' }, body: JSON.stringify({ name: 'Aru' }) }) ) .then((x) => x.json()), app .handle( new Request('http://localhost:3000/json', { method: 'POST', headers: { 'content-type': 'application/x-elysia-2' }, body: JSON.stringify({ name: 'Aru' }) }) ) .then((x) => x.json()) ]) expect(response).toEqual([ { name: 'Aru' }, { name: 'Eden' }, { name: 'Pardofelis' } ]) }) it('should get parse error', async () => { let code: string | undefined const app = new Elysia() .onError((ctx) => { // @ts-ignore code = ctx.code }) .post('/', () => '', { body: t.Object({ test: t.String() }) }) await app.modules const response = await app.handle( new Request(`http://localhost`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: '' }) ) expect(code).toBe('PARSE') expect(response.status).toBe(400) }) }) ================================================ FILE: test/lifecycle/request.test.ts ================================================ import { Elysia } from '../../src' import { describe, expect, it } from 'bun:test' import { req, delay } from '../utils' describe('On Request', () => { it('inject headers to response', async () => { const app = new Elysia() .onRequest(({ set }) => { set.headers['Access-Control-Allow-Origin'] = '*' }) .get('/', () => 'hi') const res = await app.handle(req('/')) expect(res.headers.get('Access-Control-Allow-Origin')).toBe('*') }) it('handle async', async () => { const app = new Elysia() .onRequest(async ({ set }) => { await delay(5) set.headers.name = 'llama' }) .get('/', () => 'hi') const res = await app.handle(req('/')) expect(res.headers.get('name')).toBe('llama') }) it('early return', async () => { const app = new Elysia() .onRequest(({ set }) => { set.status = 401 return 'Unauthorized' }) .get('/', () => { console.log("This shouldn't be run") return "You shouldn't see this" }) const res = await app.handle(req('/')) expect(await res.text()).toBe('Unauthorized') expect(res.status).toBe(401) }) it('support array', async () => { let total = 0 const app = new Elysia() .onRequest([ () => { total++ }, () => { total++ } ]) .get('/', () => 'NOOP') const res = await app.handle(req('/')) expect(total).toEqual(2) }) it('request in order', async () => { let order = [] const app = new Elysia() .onRequest(() => { order.push('A') }) .onRequest(() => { order.push('B') }) .get('/', () => '') await app.handle(req('/')) expect(order).toEqual(['A', 'B']) }) it('has qi', async () => { let queryIndex const app = new Elysia() // @ts-ignore .onRequest(({ qi }) => { queryIndex = qi }) .get('/', () => 'ok') .listen(0) await fetch(`http://localhost:${app.server?.port}`) expect(queryIndex).toBeTypeOf('number') }) }) ================================================ FILE: test/lifecycle/resolve.test.ts ================================================ import { Elysia } from '../../src' import { describe, expect, it } from 'bun:test' import { req } from '../utils' describe('resolve', () => { it('work', async () => { const app = new Elysia() .resolve(() => ({ hi: () => 'hi' })) .get('/', ({ hi }) => hi()) const res = await app.handle(req('/')).then((t) => t.text()) expect(res).toBe('hi') }) it('inherits plugin', async () => { const plugin = new Elysia().resolve({ as: 'global' }, () => ({ hi: () => 'hi' })) const app = new Elysia().use(plugin).get('/', ({ hi }) => hi()) const res = await app.handle(req('/')).then((t) => t.text()) expect(res).toBe('hi') }) it('not inherits plugin on local', async () => { const plugin = new Elysia().resolve(() => ({ hi: () => 'hi' })) const app = new Elysia() .use(plugin) // @ts-expect-error .get('/', ({ hi }) => typeof hi === 'undefined') const res = await app.handle(req('/')).then((t) => t.text()) expect(res).toBe('true') }) it('can mutate store', async () => { const app = new Elysia() .state('counter', 1) .resolve(({ store }) => ({ increase: () => store.counter++ })) .get('/', ({ store, increase }) => { increase() return store.counter }) const res = await app.handle(req('/')).then((t) => t.text()) expect(res).toBe('2') }) it('derive with static analysis', async () => { const app = new Elysia() .resolve(({ headers: { name } }) => ({ name })) .get('/', ({ name }) => name) const res = await app .handle( new Request('http://localhost/', { headers: { name: 'Elysia' } }) ) .then((t) => t.text()) expect(res).toBe('Elysia') }) it('store in the same stack as before handle', async () => { const stack: number[] = [] const app = new Elysia() .onBeforeHandle(() => { stack.push(1) }) .resolve(() => { stack.push(2) return { name: 'Ina' } }) .get('/', ({ name }) => name, { beforeHandle() { stack.push(3) } }) await app.handle( new Request('http://localhost/', { headers: { name: 'Elysia' } }) ) expect(stack).toEqual([1, 2, 3]) }) it('resolve in order', async () => { let order = [] const app = new Elysia() .resolve(() => { order.push('A') return {} }) .resolve(() => { order.push('B') return {} }) .get('/', () => '') await app.handle(req('/')) expect(order).toEqual(['A', 'B']) }) it('as global', async () => { const called = [] const plugin = new Elysia() .resolve({ as: 'global' }, ({ path }) => { called.push(path) return {} }) .get('/inner', () => 'NOOP') const app = new Elysia().use(plugin).get('/outer', () => 'NOOP') const res = await Promise.all([ app.handle(req('/inner')), app.handle(req('/outer')) ]) expect(called).toEqual(['/inner', '/outer']) }) it('as scoped', async () => { const called = [] const plugin = new Elysia() .resolve({ as: 'scoped' }, ({ path }) => { called.push(path) return {} }) .get('/inner', () => 'NOOP') const middle = new Elysia().use(plugin).get('/middle', () => 'NOOP') const app = new Elysia().use(middle).get('/outer', () => 'NOOP') const res = await Promise.all([ app.handle(req('/inner')), app.handle(req('/middle')), app.handle(req('/outer')) ]) expect(called).toEqual(['/inner', '/middle']) }) it('as local', async () => { const called = [] const plugin = new Elysia() .resolve({ as: 'local' }, ({ path }) => { called.push(path) return {} }) .get('/inner', () => 'NOOP') const app = new Elysia().use(plugin).get('/outer', () => 'NOOP') const res = await Promise.all([ app.handle(req('/inner')), app.handle(req('/outer')) ]) expect(called).toEqual(['/inner']) }) it('support array', async () => { let total = 0 const app = new Elysia() .onAfterHandle([ () => { total++ }, () => { total++ } ]) .get('/', () => 'NOOP') const res = await app.handle(req('/')) expect(total).toEqual(2) }) it('handle error', async () => { const route = new Elysia() .resolve(({ status }) => { return status(418) }) .get('/', () => '') const res = await new Elysia({ aot: true }).use(route).handle(req('/')) expect(await res.status).toEqual(418) expect(await res.text()).toEqual("I'm a teapot") const res2 = await new Elysia({ aot: false }) .use(route) .handle(req('/')) expect(await res2.status).toEqual(418) expect(await res2.text()).toEqual("I'm a teapot") }) /** These work but there's no support for type it('work inline', async () => { const app = new Elysia().get('/', ({ hi }) => hi(), { resolve: () => ({ hi: () => 'hi' }) }) const res = await app.handle(req('/')).then((t) => t.text()) expect(res).toBe('hi') }) it('work inline array', async () => { const app = new Elysia().get( '/', ({ first, last }) => [last, first].join(' '), { resolve: [ () => ({ first: 'himari' }), () => ({ last: 'akeboshi' }) ] } ) const res = await app.handle(req('/')).then((t) => t.text()) expect(res).toBe('akeboshi himari') }) it('work group guard', async () => { const app = new Elysia() .guard( { resolve: () => ({ hi: () => 'hi' }) }, (app) => app.get('/', ({ hi }) => hi()) ) // @ts-expect-error .get('/nope', ({ hi }) => hi?.() ?? 'nope') const res = await app.handle(req('/')).then((t) => t.text()) expect(res).toBe('hi') const nope = await app.handle(req('/nope')).then((t) => t.text()) expect(nope).toBe('nope') }) it('work group array guard', async () => { const app = new Elysia() .guard( { resolve: [ () => ({ first: 'himari' }), () => ({ last: 'akeboshi' }) ] }, (app) => app.get('/', ({ first, last }) => [last, first].join(' ')) ) // @ts-expect-error .get('/nope', ({ first, last }) => [last, first].join('')) const res = await app.handle(req('/')).then((t) => t.text()) expect(res).toBe('akeboshi himari') const nope = await app.handle(req('/nope')).then((t) => t.text()) expect(nope).toBe('') }) it('work local guard', async () => { const app = new Elysia() .guard({ resolve: () => ({ hi: () => 'hi' }) }) .get('/', ({ hi }) => hi()) const res = await app.handle(req('/')).then((t) => t.text()) expect(res).toBe('hi') }) it('work local array guard', async () => { const app = new Elysia() .guard({ resolve: [ () => ({ first: 'himari' }), () => ({ last: 'akeboshi' }) ] }) .get('/', ({ first, last }) => [last, first].join(' ')) const res = await app.handle(req('/')).then((t) => t.text()) expect(res).toBe('akeboshi himari') }) it('work scoped guard', async () => { const plugin = new Elysia().guard({ as: 'scoped', resolve: () => ({ hi: () => 'hi' }) }) const app = new Elysia().use(plugin).get('/', ({ hi }) => hi()) const root = new Elysia() .use(app) // @ts-expect-error .get('/root', ({ hi }) => hi?.() ?? 'nope') const res = await app.handle(req('/')).then((t) => t.text()) expect(res).toBe('hi') const res2 = await root.handle(req('/root')).then((t) => t.text()) expect(res2).toBe('nope') }) it('work global guard', async () => { const plugin = new Elysia().guard({ as: 'global', resolve: () => ({ hi: () => 'hi' }) }) const app = new Elysia().use(plugin).get('/', ({ hi }) => hi()) const root = new Elysia() .use(app) .get('/root', ({ hi }) => hi?.() ?? 'nope') const res = await app.handle(req('/')).then((t) => t.text()) expect(res).toBe('hi') const res2 = await root.handle(req('/root')).then((t) => t.text()) expect(res2).toBe('hi') }) */ it('handle return resolve without throw', async () => { let isOnErrorCalled = false const app = new Elysia() .onError(() => { isOnErrorCalled = true }) .resolve(({ status }) => status(418)) .get('/', () => '') await app.handle(req('/')) expect(isOnErrorCalled).toBe(false) }) }) ================================================ FILE: test/lifecycle/response.test.ts ================================================ import { Elysia, InternalServerError, t } from '../../src' import { beforeEach, describe, expect, it } from 'bun:test' import { req } from '../utils' describe('On After Response', () => { it('call after response in error', async () => { let isAfterResponseCalled = false const app = new Elysia() .onAfterResponse(() => { isAfterResponseCalled = true }) .onError(() => { return new Response('a', { status: 401, headers: { awd: 'b' } }) }) await app.handle(req('/')) // wait for next tick await Bun.sleep(1) expect(isAfterResponseCalled).toBeTrue() }) it('call after response on not found without error handler', async () => { let isAfterResponseCalled = false const app = new Elysia().onAfterResponse(() => { isAfterResponseCalled = true }) await app.handle(req('/')) // wait for next tick await Bun.sleep(1) expect(isAfterResponseCalled).toBeTrue() }) it('response in order', async () => { let order = [] const app = new Elysia() .onAfterResponse(() => { order.push('A') }) .onAfterResponse(() => { order.push('B') }) .get('/', () => '') await app.handle(req('/')) // wait for next tick await Bun.sleep(1) expect(order).toEqual(['A', 'B']) }) it('inherits from plugin', async () => { let type = '' const afterResponse = new Elysia().onAfterResponse( { as: 'global' }, ({ response }) => { type = typeof response } ) const app = new Elysia() .use(afterResponse) .get('/id/:id', ({ params: { id } }) => id, { params: t.Object({ id: t.Number() }) }) await app.handle(req('/id/1')) // wait for next tick await Bun.sleep(1) expect(type).toBe('number') }) it('inherits from plugin using responseValue', async () => { let type = '' const afterResponse = new Elysia().onAfterResponse( { as: 'global' }, ({ responseValue }) => { type = typeof responseValue } ) const app = new Elysia() .use(afterResponse) .get('/id/:id', ({ params: { id } }) => id, { params: t.Object({ id: t.Number() }) }) await app.handle(req('/id/1')) // wait for next tick await Bun.sleep(1) expect(type).toBe('number') }) it('as global', async () => { const called = [] const plugin = new Elysia() .onAfterResponse({ as: 'global' }, ({ path }) => { called.push(path) }) .get('/inner', () => 'NOOP') const app = new Elysia().use(plugin).get('/outer', () => 'NOOP') await Promise.all([ app.handle(req('/inner')), app.handle(req('/outer')) ]) // wait for next tick await Bun.sleep(1) expect(called).toEqual(['/inner', '/outer']) }) it('as local', async () => { const called = [] const plugin = new Elysia() .onAfterResponse({ as: 'local' }, ({ path }) => { called.push(path) }) .get('/inner', () => 'NOOP') const app = new Elysia().use(plugin).get('/outer', () => 'NOOP') await Promise.all([ app.handle(req('/inner')), app.handle(req('/outer')) ]) // wait for next tick await Bun.sleep(1) expect(called).toEqual(['/inner']) }) it('support array', async () => { let total = 0 const app = new Elysia() .onAfterHandle([ () => { total++ }, () => { total++ } ]) .get('/', () => 'NOOP') const res = await app.handle(req('/')) // wait for next tick await Bun.sleep(1) expect(total).toEqual(2) }) }) describe('On After Response Error', () => { const newReq = (params?: { path?: string headers?: Record method?: string body?: string }) => new Request(`http://localhost${params?.path ?? '/'}`, params) class CustomError extends Error {} let isOnResponseCalled: boolean let onResponseCalledCounter = 0 beforeEach(() => { isOnResponseCalled = false onResponseCalledCounter = 0 }) const app = new Elysia() .onAfterResponse(() => { isOnResponseCalled = true onResponseCalledCounter++ }) .post('/', () => 'yay', { body: t.Object({ test: t.String() }) }) .get('/customError', () => { throw new CustomError('whelp') }) .get('/internalError', () => { throw new InternalServerError('whelp') }) it.each([ ['NotFoundError', newReq({ path: '/notFound' })], [ 'ParseError', newReq({ method: 'POST', headers: { 'Content-Type': 'application/json' }, body: '' }) ], [ 'ValidationError', newReq({ method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({}) }) ], ['CustomError', newReq({ path: '/customError' })], ['InternalServerError', newReq({ path: '/internalError' })] ])('%s should call onResponse', async (_name, request) => { expect(isOnResponseCalled).toBeFalse() await app.handle(request) // wait for next tick await Bun.sleep(1) expect(isOnResponseCalled).toBeTrue() expect(onResponseCalledCounter).toBe(1) }) it.each([ { aot: true, withOnError: true }, { aot: true, withOnError: false }, { aot: false, withOnError: true }, { aot: false, withOnError: false } ])( 'should execute onAfterResponse once during NotFoundError aot=$aot,\twithOnError=$withOnError', async ({ aot, withOnError }) => { let counter = 0 const app = new Elysia({ aot }).onAfterResponse(() => { counter++ }) if (withOnError) app.onError(() => {}) const req = new Request('http://localhost/notFound') await app.handle(req) await Bun.sleep(1) expect(counter).toBe(1) } ) it.each([ { aot: true, onErrorReturnsValue: "error handled" }, { aot: false, onErrorReturnsValue: "error handled" }, { aot: true, onErrorReturnsValue: { message: "error handled" } }, { aot: false, onErrorReturnsValue: { message: "error handled" } }, ])('should execute onAfterResponse when onError returns a value aot=$aot,\tonErrorReturnsValue=$onErrorReturnsValue', async ({ aot, onErrorReturnsValue }) => { let counter = 0 const app = new Elysia({ aot }) .onError(() => { return onErrorReturnsValue }) .onAfterResponse(() => { counter++ }) .get('/error', () => { throw new Error('test error') }) expect(counter).toBe(0) const req = new Request('http://localhost/error') const res = await app.handle(req) const text = await res.text() expect(text).toStrictEqual( typeof onErrorReturnsValue === 'string' ? onErrorReturnsValue : JSON.stringify(onErrorReturnsValue) ) await Bun.sleep(1) expect(counter).toBe(1) }) }) ================================================ FILE: test/lifecycle/transform.test.ts ================================================ import { Elysia, t } from '../../src' import { describe, expect, it } from 'bun:test' import { req } from '../utils' describe('Transform', () => { it('globally Transform', async () => { const app = new Elysia() .onTransform<{ params: { id: number } | null }>((request) => { if (request.params?.id) request.params.id = +request.params.id }) .get('/id/:id', ({ params: { id } }) => typeof id) const res = await app.handle(req('/id/1')) expect(await res.text()).toBe('number') }) it('locally transform', async () => { const app = new Elysia().get( '/id/:id', ({ params: { id } }) => typeof id, { transform: (request) => { if (request.params?.id) request.params.id = +request.params.id }, params: t.Object({ id: t.Number() }) } ) const res = await app.handle(req('/id/1')) expect(await res.text()).toBe('number') }) it('group transform', async () => { const app = new Elysia() .group('/scoped/id/:id', (app) => app .onTransform(({ params }) => { // @ts-ignore if (params.id) params.id = +params.id }) .get('', ({ params: { id } }) => typeof id) ) .get('/id/:id', ({ params: { id } }) => typeof id) const base = await app.handle(req('/id/1')) const scoped = await app.handle(req('/scoped/id/1')) expect(await base.text()).toBe('string') expect(await scoped.text()).toBe('number') }) it('transform from plugin', async () => { const transformId = new Elysia().onTransform< { params: { id: number } | null }, 'global' >({ as: 'global' }, (request) => { // @ts-ignore if (request.params?.id) request.params.id = +request.params.id }) const app = new Elysia() .use(transformId) .get('/id/:id', ({ params: { id } }) => typeof id) const res = await app.handle(req('/id/1')) expect(await res.text()).toBe('number') }) it('transform from on', async () => { const app = new Elysia() .on('transform', (request) => { if (request.params?.id) request.params.id = +request.params.id }) .get('/id/:id', ({ params: { id } }) => typeof id) const res = await app.handle(req('/id/1')) expect(await res.text()).toBe('number') }) it('transform in order', async () => { let order = [] const app = new Elysia() .onTransform(() => { order.push('A') }) .onTransform(() => { order.push('B') }) .get('/', () => '') await app.handle(req('/')) expect(order).toEqual(['A', 'B']) }) it('globally and locally pre handle', async () => { const app = new Elysia() .onTransform<{ params: { id: number } | null }>((request) => { if (request.params?.id) request.params.id = +request.params.id }) .get('/id/:id', ({ params: { id } }) => id, { params: t.Object({ id: t.Number() }), transform: (request) => { if ( request.params?.id && typeof request.params?.id === 'number' ) request.params.id = request.params.id + 1 } }) const res = await app.handle(req('/id/1')) expect(await res.text()).toBe('2') }) it('accept multiple transform', async () => { const app = new Elysia() .onTransform<{ params: { id: number } | null }>((request) => { if (request.params?.id) request.params.id = +request.params.id }) .onTransform<{ params: { id: number } | null }>((request) => { if ( request.params?.id && typeof request.params?.id === 'number' ) request.params.id = request.params.id + 1 }) .get('/id/:id', ({ params: { id } }) => id) const res = await app.handle(req('/id/1')) expect(await res.text()).toBe('2') }) it('transform async', async () => { const app = new Elysia().get( '/id/:id', ({ params: { id } }) => typeof id, { params: t.Object({ id: t.Number() }), transform: async ({ params }) => { await new Promise((resolve) => setTimeout(() => { resolve() }, 1) ) if (params?.id) params.id = +params.id } } ) const res = await app.handle(req('/id/1')) expect(await res.text()).toBe('number') }) it('map returned value', async () => { const app = new Elysia() .onTransform<{ params: { id: number } | null }>((request) => { if (request.params?.id) request.params.id = +request.params.id }) .get('/id/:id', ({ params: { id } }) => typeof id) const res = await app.handle(req('/id/1')) expect(await res.text()).toBe('number') }) it('validate property', async () => { const app = new Elysia().get('/id/:id', ({ params: { id } }) => id, { params: t.Object({ id: t.Numeric({ minimum: 0 }) }) }) const correct = await app.handle(req('/id/1')).then((x) => x.status) expect(correct).toBe(200) const invalid = await app.handle(req('/id/-1')).then((x) => x.status) expect(invalid).toBe(422) }) it('inherits from plugin', async () => { const transformId = new Elysia().onTransform< { params: { name: string } | null }, 'global' >({ as: 'global' }, ({ params }) => { if (params?.name === 'Fubuki') params.name = 'Cat' }) const app = new Elysia() .use(transformId) .get('/name/:name', ({ params: { name } }) => name) const res = await app.handle(req('/name/Fubuki')) expect(await res.text()).toBe('Cat') }) it('not inherits plugin on local', async () => { const transformId = new Elysia().onTransform<{ params: { name: string } | null }>(({ params }) => { if (params?.name === 'Fubuki') params.name = 'Cat' }) const app = new Elysia() .use(transformId) .get('/name/:name', ({ params: { name } }) => name) const res = await app.handle(req('/name/Fubuki')) expect(await res.text()).toBe('Fubuki') }) it('global true', async () => { const called = [] const plugin = new Elysia() .onTransform({ as: 'global' }, ({ path }) => { called.push(path) }) .get('/inner', () => 'NOOP') const app = new Elysia().use(plugin).get('/outer', () => 'NOOP') const res = await Promise.all([ app.handle(req('/inner')), app.handle(req('/outer')) ]) expect(called).toEqual(['/inner', '/outer']) }) it('global false', async () => { const called = [] const plugin = new Elysia() .onTransform({ as: 'local' }, ({ path }) => { called.push(path) }) .get('/inner', () => 'NOOP') const app = new Elysia().use(plugin).get('/outer', () => 'NOOP') const res = await Promise.all([ app.handle(req('/inner')), app.handle(req('/outer')) ]) expect(called).toEqual(['/inner']) }) it('support array', async () => { let total = 0 const app = new Elysia() .onTransform([ () => { total++ }, () => { total++ } ]) .get('/', () => 'NOOP') const res = await app.handle(req('/')) expect(total).toEqual(2) }) }) ================================================ FILE: test/macro/macro.test.ts ================================================ /* eslint-disable @typescript-eslint/no-unused-vars */ import { describe, it, expect } from 'bun:test' import { Elysia, t, status } from '../../src' import { post, req } from '../utils' describe('Macro', () => { it('trace back', async () => { let answer: string | undefined const app = new Elysia() .macro({ hi(config: string) { answer = config } }) .get('/', () => 'Hello World', { hi: 'Hello World' }) await app.handle(req('/')) expect(answer).toBe('Hello World') }) it('work', async () => { const app = new Elysia() .macro({ hi(beforeHandle: () => any) { return { beforeHandle } } }) .get('/', () => 'Hello World', { hi: () => 'Hello World' }) const response = await app.handle(req('/')).then((x) => x.text()) expect(response).toBe('Hello World') }) it('appends parse', async () => { const app = new Elysia() .macro({ hi(fn: () => any) { return { parse: fn } } }) .get('/', () => 'Hello World', { hi: () => {} }) expect(app.router.history[0].hooks.parse?.length).toEqual(1) }) it('appends parse array', async () => { const app = new Elysia() .macro({ hi(fn: () => any) { return { parse: [fn, () => {}] } } }) .get('/', () => 'Hello World', { hi: () => {} }) expect(app.router.history[0].hooks.parse?.length).toEqual(2) }) it('appends transform', async () => { const app = new Elysia() .macro({ hi(fn: () => any) { return { transform: fn } } }) .get('/', () => 'Hello World', { hi: () => {} }) expect(app.router.history[0].hooks.transform?.length).toEqual(1) }) it('appends transform array', async () => { const app = new Elysia() .macro({ hi(fn: () => any) { return { transform: [fn, () => {}] } } }) .get('/', () => 'Hello World', { hi: () => {} }) expect(app.router.history[0].hooks.transform?.length).toEqual(2) }) it('appends beforeHandle', async () => { const app = new Elysia() .macro({ hi(fn: () => any) { return { beforeHandle: fn } } }) .get('/', () => 'Hello World', { hi: () => {} }) expect(app.router.history[0].hooks.beforeHandle?.length).toEqual(1) }) it('appends beforeHandle array', async () => { const app = new Elysia() .macro({ hi(fn: () => any) { return { beforeHandle: [fn, () => {}] } } }) .get('/', () => 'Hello World', { hi: () => {} }) expect(app.router.history[0].hooks.beforeHandle?.length).toEqual(2) }) it('appends afterHandle', async () => { const app = new Elysia() .macro({ hi(fn: () => any) { return { afterHandle: fn } } }) .get('/', () => 'Hello World', { hi: () => {} }) expect(app.router.history[0].hooks.afterHandle?.length).toEqual(1) }) it('appends afterHandle array', async () => { const app = new Elysia() .macro({ hi(fn: () => any) { return { afterHandle: [fn, () => {}] } } }) .get('/', () => 'Hello World', { hi: () => {} }) expect(app.router.history[0].hooks.afterHandle?.length).toEqual(2) }) it('appends error', async () => { const app = new Elysia() .macro({ hi(fn: () => any) { return { error: fn } } }) .get('/', () => 'Hello World', { hi: () => {} }) expect(app.router.history[0].hooks.error?.length).toEqual(1) }) it('appends error array', async () => { const app = new Elysia() .macro({ hi(fn: () => any) { return { error: [fn, () => {}] } } }) .get('/', () => 'Hello World', { hi: () => {} }) expect(app.router.history[0].hooks.error?.length).toEqual(2) }) it('appends afterResponse', async () => { const app = new Elysia() .macro({ hi(fn: () => any) { return { afterResponse: fn } } }) .get('/', () => 'Hello World', { hi: () => {} }) expect(app.router.history[0].hooks.afterResponse?.length).toEqual(1) }) it('appends afterResponse array', async () => { const app = new Elysia() .macro({ hi(fn: () => any) { return { afterResponse: [fn, () => {}] } } }) .get('/', () => 'Hello World', { hi: () => {} }) expect(app.router.history[0].hooks.afterResponse?.length).toEqual(2) }) it('handle deduplication', async () => { let call = 0 const a = new Elysia({ name: 'a', seed: 'awdawd' }).macro({ a: { beforeHandle() { call++ } } }) const b = new Elysia({ name: 'b', seed: 'add' }) .use(a) .decorate('b', 'b') const app = new Elysia() .use(a) .use(b) .get('/', () => 'Hello World', { a: true }) await app.handle(req('/')) expect(call).toBe(1) }) it('propagate macro without inaccurate deduplication in guard', async () => { let call = 0 const base = new Elysia({ name: 'base' }).macro({ auth(role: 'teacher' | 'student' | 'admin' | 'noLogin') { return { beforeHandle() { call++ } } } }) const app = new Elysia() // ? Deduplication check .use(base) .use(base) .use(base) .use(base) .guard({ auth: 'admin' }, (route) => route .get('/test1', () => 'test1') .get('/test2', () => 'test2') .get('/test3', () => 'hello test3') ) .get('/hello', () => 'hello', { auth: 'teacher' }) await Promise.all( ['/test1', '/test2', '/test3'].map((x) => app.handle(req(x))) ) expect(call).toBe(3) }) it('inherits macro from plugin without name', async () => { let called = 0 const plugin = new Elysia().macro({ hi(_: string) { called++ } }) const app = new Elysia() .use(plugin) .use(plugin) .use(plugin) .get('/', () => 'Hello World', { hi: 'Hello World' }) await app.handle(req('/')) expect(called).toBe(1) }) it('handle macro from plugin', async () => { const authGuard = new Elysia().macro({ requiredUser(value: boolean) { return { beforeHandle: async () => { if (value) return status(401, { code: 'S000002', message: 'Unauthorized' }) } } } }) const testRoute = new Elysia({ prefix: '/test', name: 'testRoute' }) .use(authGuard) .guard({ requiredUser: true }) .get('/', () => 'Ely') const app = new Elysia().use(testRoute).get('/', () => 'Ely') const ok = await app.handle(req('/')).then((t) => t.text()) const err = await app.handle(req('/test')).then((t) => t.text()) expect(ok).toBe('Ely') expect(err).not.toBe('Ely') expect(err).not.toBe('NOT_FOUND') }) it("don't duplicate call on as plugin", async () => { let called = 0 const plugin = new Elysia() .macro({ count(_: boolean) { return { beforeHandle(ctx) { called++ } } } }) .get('/', () => 'hi', { count: true }) const app = new Elysia().use(plugin).get('/foo', () => 'foo', { count: true }) await app.handle(req('/')) }) it('inherits macro in group', async () => { const authGuard = new Elysia().macro({ isAuth(shouldAuth: boolean) { if (shouldAuth) return { beforeHandle({ cookie: { session }, status }) { if (!session.value) return status(418) } } } }) const app = new Elysia().use(authGuard).group('/posts', (app) => app.get('/', () => 'a', { isAuth: true }) ) const status = await app.handle(req('/posts')).then((x) => x.status) expect(status).toBe(418) }) it('inherits macro in guard', async () => { const authGuard = new Elysia().macro({ isAuth(shouldAuth: boolean) { if (shouldAuth) return { beforeHandle({ cookie: { session }, status }) { if (!session.value) return status(418) } } } }) const app = new Elysia().use(authGuard).guard({}, (app) => app.get('/posts', () => 'a', { isAuth: true }) ) const status = await app.handle(req('/posts')).then((x) => x.status) expect(status).toBe(418) }) it('inherits macro from plugin', async () => { const authGuard = new Elysia().macro({ isAuth(shouldAuth: boolean) { if (shouldAuth) return { beforeHandle({ cookie: { session }, status }) { if (!session.value) return status(418) } } } }) const app = new Elysia().use(authGuard).use((app) => app.get('/posts', () => 'a', { isAuth: true }) ) const status = await app.handle(req('/posts')).then((x) => x.status) expect(status).toBe(418) }) it("don't inherits macro to plugin without type reference", () => { const called = [] const plugin = new Elysia().get('/hello', () => 'hello', { // @ts-ignore hello: 'nagisa' }) new Elysia() .macro({ hello(a: string) { called.push(a) } }) .use(plugin) .get('/', () => 'a', { hello: 'hifumi' }) expect(called).toEqual(['nagisa', 'hifumi']) }) it("don't duplicate macro call", async () => { let registered = 0 let called = 0 const a = new Elysia({ name: 'a' }).macro({ isSignIn() { registered++ return { beforeHandle() { called++ } } } }) const b = new Elysia({ name: 'b' }).use(a) const c = new Elysia().use(b).get('/', () => 'ok', { isSignIn: true }) const app = new Elysia().use(c) await app.handle(req('/')) expect(registered).toBe(1) expect(called).toBe(1) }) it('accept resolve', async () => { const app = new Elysia() .macro({ user: (enabled: boolean) => ({ resolve: ({ query: { name = 'anon' } }) => ({ user: { name } }) }) }) .get('/', ({ user }) => user, { user: true }) const [a, b] = await Promise.all([ app.handle(req('/')).then((x) => x.json()), app.handle(req('/?name=hoshino')).then((x) => x.json()) ]) expect(a).toEqual({ name: 'anon' }) expect(b).toEqual({ name: 'hoshino' }) }) it('accept async resolve', async () => { const app = new Elysia() .macro({ user: (enabled: boolean) => ({ resolve: async ({ query: { name = 'anon' } }) => ({ user: { name } }) }) }) .get('/', ({ user }) => user, { user: true }) const [a, b] = await Promise.all([ app.handle(req('/')).then((x) => x.json()), app.handle(req('/?name=hoshino')).then((x) => x.json()) ]) expect(a).toEqual({ name: 'anon' }) expect(b).toEqual({ name: 'hoshino' }) }) it('guard handle resolve macro', async () => { const plugin = new Elysia() .macro({ account: (a: boolean) => ({ resolve: () => ({ account: 'A' }) }) }) .guard({ account: true }) .get('/local', ({ account }) => account === 'A') const parent = new Elysia() .use(plugin) .get('/plugin', (context) => !('account' in context)) const app = new Elysia() .use(parent) .get('/global', (context) => !('account' in context)) expect( await Promise.all( ['/local', '/plugin', '/global'].map((path) => app .handle(req(path)) .then((x) => x.text()) .then((x) => x === 'true') ) ) ).toEqual([true, true, true]) }) it('guard handle resolve macro with scoped', async () => { const plugin = new Elysia() .macro({ account: (a: boolean) => ({ resolve: () => ({ account: 'A' }) }) }) .guard({ as: 'scoped', account: true }) .get('/local', ({ account }) => account === 'A') const parent = new Elysia() .use(plugin) .get('/plugin', ({ account }) => account === 'A') const app = new Elysia() .use(parent) .get('/global', (context) => !('account' in context)) expect( await Promise.all( ['/local', '/plugin', '/global'].map((path) => app .handle(req(path)) .then((x) => x.text()) .then((x) => x === 'true') ) ) ).toEqual([true, true, true]) }) it('guard handle resolve macro with global', async () => { const plugin = new Elysia() .macro({ account: (a: boolean) => ({ resolve: () => ({ account: 'A' }) }) }) .guard({ as: 'global', account: true }) .get('/local', ({ account }) => account === 'A') const parent = new Elysia() .use(plugin) .get('/plugin', ({ account }) => account === 'A') const app = new Elysia() .use(parent) .get('/global', ({ account }) => account === 'A') expect( await Promise.all( ['/local', '/plugin', '/global'].map((path) => app .handle(req(path)) .then((x) => x.text()) .then((x) => x === 'true') ) ) ).toEqual([true, true, true]) }) it('guard handle resolve macro with local', async () => { const plugin = new Elysia() .macro({ account: (a: boolean) => ({ resolve: () => ({ account: 'A' }) }) }) .guard({ as: 'local', account: true }) .get('/local', ({ account }) => account === 'A') const parent = new Elysia() .use(plugin) .get('/plugin', (context) => !('account' in context)) const app = new Elysia() .use(parent) .get('/global', (context) => !('account' in context)) expect( await Promise.all( ['/local', '/plugin', '/global'].map((path) => app .handle(req(path)) .then((x) => x.text()) .then((x) => x === 'true') ) ) ).toEqual([true, true, true]) }) it('guard handle resolve macro with error', async () => { const plugin = new Elysia() .macro({ account: (a: boolean) => ({ resolve: () => { if (Math.random() > 2) return status(401) return { account: 'A' } } }) }) .guard({ account: true }) .get('/local', ({ account }) => account === 'A') const parent = new Elysia() .use(plugin) .get('/plugin', (context) => !('account' in context)) const app = new Elysia() .use(parent) .get('/global', (context) => !('account' in context)) expect( await Promise.all( ['/local', '/plugin', '/global'].map((path) => app .handle(req(path)) .then((x) => x.text()) .then((x) => x === 'true') ) ) ).toEqual([true, true, true]) }) it('guard handle resolve macro with async', async () => { const plugin = new Elysia() .macro({ account: (a: boolean) => ({ resolve: async () => { if (Math.random() > 2) return status(401) return { account: 'A' } } }) }) .guard({ as: 'scoped', account: true }) .get('/local', ({ account }) => account === 'A') const parent = new Elysia() .use(plugin) .get('/plugin', ({ account }) => account === 'A') const app = new Elysia() .use(parent) .get('/global', (context) => !('account' in context)) expect( await Promise.all( ['/local', '/plugin', '/global'].map((path) => app .handle(req(path)) .then((x) => x.text()) .then((x) => x === 'true') ) ) ).toEqual([true, true, true]) }) // It may look duplicate to the test case above, but it occurs for some reason it('handle macro resolve', async () => { const app = new Elysia() .macro({ user: (enabled: true) => ({ resolve() { if (!enabled) return return { user: 'a' } } }) }) .get( '/', ({ user, status }) => { if (!user) return status(401) return { hello: 'hanabi' } }, { user: true } ) const response = await app.handle(req('/')).then((x) => x.json()) expect(response).toEqual({ hello: 'hanabi' }) }) it('handle function macro shorthand property', async () => { const app = new Elysia() .macro({ user: { resolve: ({ query: { name = 'anon' } }) => ({ user: { name } }) } }) .get('/', ({ user }) => user, { user: true }) // @ts-expect-error .get('/no-macro', (context) => context?.user ?? { name: 'none' }, { user: false }) const [a, b, c, d] = await Promise.all([ app.handle(req('/')).then((x) => x.json()), app.handle(req('/?name=hoshino')).then((x) => x.json()), app.handle(req('/no-macro')).then((x) => x.json()), app.handle(req('/no-macro?name=hoshino')).then((x) => x.json()) ]) expect(a).toEqual({ name: 'anon' }) expect(b).toEqual({ name: 'hoshino' }) expect(c).toEqual({ name: 'none' }) expect(d).toEqual({ name: 'none' }) }) it('handle multiple macros in a route handler', async () => { const app = new Elysia() .macro({ a: { resolve: () => ({ a: 'a' as const }) }, b: { resolve: () => ({ b: 'b' as const }) }, c: (n: number) => ({ resolve: () => ({ c: n }) }) }) .get('/a', ({ a }) => ({ a }), { a: true, response: t.Object({ a: t.Literal('a') }) }) .get('/b', ({ b }) => ({ b }), { b: true, response: t.Object({ b: t.Literal('b') }) }) .get('/c', ({ a, b }) => ({ a, b }), { a: true, b: true, response: t.Object({ a: t.Literal('a'), b: t.Literal('b') }) }) .get( '/d', ({ a, // @ts-expect-error Property `b` does not exist b }) => ({ a, b }), { a: true, b: false, response: t.Object({ a: t.Literal('a'), b: t.Undefined() }) } ) .get( '/e', ({ // @ts-expect-error Property `a` does not exist a, b, // @ts-expect-error Property `c` does not exist c }) => ({ a, b, c }), { b: true, c: 10, response: t.Object({ a: t.Undefined(), b: t.Literal('b'), c: t.Number() }) } ) const [a, b, c, d, e] = await Promise.all([ app.handle(req('/a')).then((x) => x.json()), app.handle(req('/b')).then((x) => x.json()), app.handle(req('/c')).then((x) => x.json()), app.handle(req('/d')).then((x) => x.json()), app.handle(req('/e')).then((x) => x.json()) ]) expect(a).toEqual({ a: 'a' }) expect(b).toEqual({ b: 'b' }) expect(c).toEqual({ a: 'a', b: 'b' }) expect(d).toEqual({ a: 'a', b: undefined }) expect(e).toEqual({ a: undefined, b: 'b', c: 10 }) }) it('validate', async () => { const app = new Elysia() .macro({ sartre: { params: t.Object({ sartre: t.Literal('Sartre') }) }, focou: { query: t.Object({ focou: t.Literal('Focou') }) }, lilith: { body: t.Object({ lilith: t.Literal('Lilith') }) } }) .post('/:sartre', ({ body }) => body, { sartre: true, focou: true, lilith: true }) expect(app.routes[0].hooks.standaloneValidator.length).toBe(1) const valid = await app.handle( post('/Sartre?focou=Focou', { lilith: 'Lilith' }) ) expect(valid.status).toBe(200) expect(await valid.json()).toEqual({ lilith: 'Lilith' }) const invalid1 = await app.handle( post('/Sartre?focou=Focou', { lilith: 'Not Lilith' }) ) expect(invalid1.status).toBe(422) const invalid2 = await app.handle( post('/Not Sartre?focou=Focou', { lilith: 'Lilith' }) ) expect(invalid2.status).toBe(422) const invalid3 = await app.handle( post('/Sartre?focou=Not Focou', { lilith: 'Lilith' }) ) expect(invalid3.status).toBe(422) }) it('merge validation', async () => { const app = new Elysia() .macro({ sartre: { body: t.Object({ sartre: t.Literal('Sartre') }) }, focou: { body: t.Object({ focou: t.Literal('Focou') }) }, lilith: { body: t.Object({ lilith: t.Literal('Lilith') }) } }) .post('/', ({ body }) => body, { sartre: true, focou: true, lilith: true }) expect(app.routes[0].hooks.standaloneValidator.length).toBe(3) const response = await app.handle( post('/', { sartre: 'Sartre', focou: 'Focou', lilith: 'Lilith' }) ) expect(response.status).toBe(200) expect(await response.json()).toEqual({ sartre: 'Sartre', focou: 'Focou', lilith: 'Lilith' }) const invalid1 = await app.handle( post('/', { sartre: 'Not Sartre', focou: 'Focou', lilith: 'Lilith' }) ) expect(invalid1.status).toBe(422) const invalid2 = await app.handle( post('/', { sartre: 'Sartre', focou: 'Not Focou', lilith: 'Lilith' }) ) expect(invalid2.status).toBe(422) const invalid3 = await app.handle( post('/', { sartre: 'Sartre', focou: 'Focou', lilith: 'Not Lilith' }) ) expect(invalid3.status).toBe(422) }) it('extends', async () => { const app = new Elysia() .macro({ sartre: { body: t.Object({ sartre: t.Literal('Sartre') }) }, focou: { body: t.Object({ focou: t.Literal('Focou') }) }, lilith: { sartre: true, focou: true, body: t.Object({ lilith: t.Literal('Lilith') }) } }) .post('/', ({ body }) => body, { lilith: true }) expect(app.routes[0].hooks.standaloneValidator.length).toBe(3) const response = await app.handle( post('/', { sartre: 'Sartre', focou: 'Focou', lilith: 'Lilith' }) ) expect(response.status).toBe(200) expect(await response.json()).toEqual({ sartre: 'Sartre', focou: 'Focou', lilith: 'Lilith' }) const invalid1 = await app.handle( post('/', { sartre: 'Not Sartre', focou: 'Focou', lilith: 'Lilith' }) ) expect(invalid1.status).toBe(422) const invalid2 = await app.handle( post('/', { sartre: 'Sartre', focou: 'Not Focou', lilith: 'Lilith' }) ) expect(invalid2.status).toBe(422) const invalid3 = await app.handle( post('/', { sartre: 'Sartre', focou: 'Focou', lilith: 'Not Lilith' }) ) expect(invalid3.status).toBe(422) }) it('create detail if not exists', () => { const app = new Elysia() .macro({ lilith: { detail: { summary: 'Lilith', description: 'Lilith description' } } }) .post('/', ({ body }) => body, { lilith: true }) const route = app.routes[0] expect(route.hooks.detail).toEqual({ summary: 'Lilith', description: 'Lilith description' }) }) it('modify detail', () => { const app = new Elysia() .macro({ lilith: { detail: { summary: 'Lilith' } } }) .post('/', ({ body }) => body, { lilith: true, detail: { description: 'Lilith description' } }) const route = app.routes[0] expect(route.hooks.detail).toEqual({ summary: 'Lilith', description: 'Lilith description' }) }) it('deduplicate static object default', () => { const app = new Elysia() .macro({ sartre: { body: t.Object({ sartre: t.Literal('Sartre') }) }, focou: { sartre: true, body: t.Object({ focou: t.Literal('Focou') }) }, lilith: { sartre: true, focou: true, body: t.Object({ lilith: t.Literal('Lilith') }) } }) .post('/', ({ body }) => body, { lilith: true }) const route = app.routes[0] expect(route.hooks.standaloneValidator.length).toBe(3) }) it('deduplicate function macro by default', () => { const app = new Elysia() .macro({ sartre(enabled: boolean) { return { body: t.Object({ sartre: t.Literal('Sartre') }) } }, focou: { sartre: true, body: t.Object({ focou: t.Literal('Focou') }) }, lilith: { sartre: true, focou: true, body: t.Object({ lilith: t.Literal('Lilith') }) } }) .post('/', ({ body }) => body, { lilith: true, sartre: false }) const route = app.routes[0] // This is 4 because // 1. lilith // 2. focou // 3. sartre from focou // 4. sartre with false flag expect(route.hooks.standaloneValidator.length).toBe(4) }) it('deduplicate function macro when argument is similar', () => { const app = new Elysia() .macro({ sartre(enabled: boolean) { return { body: t.Object({ sartre: t.Literal('Sartre') }) } }, focou: { sartre: true, body: t.Object({ focou: t.Literal('Focou') }) }, lilith: { sartre: true, focou: true, body: t.Object({ lilith: t.Literal('Lilith') }) } }) .post('/', ({ body }) => body, { lilith: true, sartre: true }) const route = app.routes[0] // This is 4 because // 1. lilith // 2. focou // 3. sartre from focou expect(route.hooks.standaloneValidator.length).toBe(3) }) it('deduplicate programmatically', () => { const app = new Elysia() .macro({ sartre(tag: string) { return { seed: tag, body: t.Object({ sartre: t.Literal('Sartre') }), detail: { tags: [tag] } } }, focou: { sartre: 'npc', body: t.Object({ focou: t.Literal('Focou') }) }, lilith: { sartre: 'philosopher', focou: true, body: t.Object({ lilith: t.Literal('Lilith') }) } }) .post('/', ({ body }) => body, { lilith: true }) const route = app.routes[0] expect(route.hooks.standaloneValidator.length).toBe(4) expect(route.hooks.detail).toEqual({ tags: ['philosopher', 'npc'] }) }) it('handle macro name', async () => { const app = new Elysia() .macro('sartre', { params: t.Object({ sartre: t.Literal('Sartre') }) }) .macro({ focou: { query: t.Object({ focou: t.Literal('Focou') }) }, lilith: { body: t.Object({ lilith: t.Literal('Lilith') }) } }) .post('/:sartre', ({ body }) => body, { sartre: true, focou: true, lilith: true }) expect(app.routes[0].hooks.standaloneValidator.length).toBe(1) const valid = await app.handle( post('/Sartre?focou=Focou', { lilith: 'Lilith' }) ) expect(valid.status).toBe(200) expect(await valid.json()).toEqual({ lilith: 'Lilith' }) const invalid1 = await app.handle( post('/Sartre?focou=Focou', { lilith: 'Not Lilith' }) ) expect(invalid1.status).toBe(422) const invalid2 = await app.handle( post('/Not Sartre?focou=Focou', { lilith: 'Lilith' }) ) expect(invalid2.status).toBe(422) const invalid3 = await app.handle( post('/Sartre?focou=Not Focou', { lilith: 'Lilith' }) ) expect(invalid3.status).toBe(422) }) it('handle macro name with function', async () => { const app = new Elysia() .macro('sartre', (_: boolean) => ({ params: t.Object({ sartre: t.Literal('Sartre') }) })) .macro({ focou: { query: t.Object({ focou: t.Literal('Focou') }) }, lilith: { body: t.Object({ lilith: t.Literal('Lilith') }) } }) .post('/:sartre', ({ body }) => body, { sartre: true, focou: true, lilith: true }) expect(app.routes[0].hooks.standaloneValidator.length).toBe(1) const valid = await app.handle( post('/Sartre?focou=Focou', { lilith: 'Lilith' }) ) expect(valid.status).toBe(200) expect(await valid.json()).toEqual({ lilith: 'Lilith' }) const invalid1 = await app.handle( post('/Sartre?focou=Focou', { lilith: 'Not Lilith' }) ) expect(invalid1.status).toBe(422) const invalid2 = await app.handle( post('/Not Sartre?focou=Focou', { lilith: 'Lilith' }) ) expect(invalid2.status).toBe(422) const invalid3 = await app.handle( post('/Sartre?focou=Not Focou', { lilith: 'Lilith' }) ) expect(invalid3.status).toBe(422) }) it('handle macro name extends', async () => { const app = new Elysia() .macro('sartre', { body: t.Object({ sartre: t.Literal('Sartre') }) }) .macro({ focou: { sartre: true, body: t.Object({ focou: t.Literal('Focou') }) }, lilith: { focou: true, body: t.Object({ lilith: t.Literal('Lilith') }) } }) .post('/', ({ body }) => body, { lilith: true }) expect(app.routes[0].hooks.standaloneValidator.length).toBe(3) const response = await app.handle( post('/', { sartre: 'Sartre', focou: 'Focou', lilith: 'Lilith' }) ) expect(response.status).toBe(200) expect(await response.json()).toEqual({ sartre: 'Sartre', focou: 'Focou', lilith: 'Lilith' }) const invalid1 = await app.handle( post('/', { sartre: 'Not Sartre', focou: 'Focou', lilith: 'Lilith' }) ) expect(invalid1.status).toBe(422) const invalid2 = await app.handle( post('/', { sartre: 'Sartre', focou: 'Not Focou', lilith: 'Lilith' }) ) expect(invalid2.status).toBe(422) const invalid3 = await app.handle( post('/', { sartre: 'Sartre', focou: 'Focou', lilith: 'Not Lilith' }) ) expect(invalid3.status).toBe(422) }) }) ================================================ FILE: test/modules.ts ================================================ import { Elysia } from '../src' export const lazy = async (app: Elysia) => app.get('/lazy', () => 'lazy') export const lazyInstance = new Elysia().get('/lazy-instance', () => 'lazy-instance') export default lazy ================================================ FILE: test/node/.gitignore ================================================ node_modules/ package-lock.json ================================================ FILE: test/node/cjs/index.js ================================================ if ('Bun' in globalThis) { throw new Error('❌ Use Node.js to run this test!') } setTimeout(() => { console.log('❌ CJS Node.js timed out') process.exit(1) }, 5000) const { Elysia, t } = require('elysia') const app = new Elysia().get('/', () => 'Node.js', { response: t.String() }) const main = async () => { const response = await app.handle(new Request('http://localhost')) if ((await response.text()) !== 'Node.js') { throw new Error('❌ CommonJS Node.js failed') } console.log('✅ CommonJS Node.js works!') process.exit() } main() ================================================ FILE: test/node/cjs/package.json ================================================ { "type": "commonjs", "dependencies": { "elysia": "../../.." } } ================================================ FILE: test/node/esm/index.js ================================================ import { Elysia, t } from 'elysia' if ('Bun' in globalThis) { throw new Error('❌ Use Node.js to run this test!') } setTimeout(() => { console.log('❌ ESM Node.js timed out') process.exit(1) }, 5000) const app = new Elysia().get('/', () => 'Node.js', { response: t.String() }) const response = await app.handle(new Request('http://localhost')) if ((await response.text()) !== 'Node.js') { throw new Error('❌ ESM Node.js failed') } console.log('✅ ESM Node.js works!') process.exit() ================================================ FILE: test/node/esm/package.json ================================================ { "type": "module", "dependencies": { "elysia": "../../.." } } ================================================ FILE: test/path/group.test.ts ================================================ import { describe, it, expect } from 'bun:test' import { Elysia, t } from '../../src' import { post, req } from '../utils' describe('group', () => { it('delegate onRequest', async () => { const app = new Elysia() .get('/', () => 'A') .group('/counter', (app) => app .state('counter', 0) .onRequest(({ store }) => { store.counter++ }) .get('', ({ store: { counter } }) => counter) ) await app.handle(req('/')) const res = await app.handle(req('/counter')).then((r) => r.text()) expect(res).toBe('2') }) it('decorate group', async () => { const app = new Elysia().group('/v1', (app) => app.decorate('a', 'b').get('/', ({ a }) => a) ) const res = await app.handle(req('/v1/')).then((x) => x.text()) expect(res).toBe('b') }) it('validate headers', async () => { const app = new Elysia().group( '/v1', { headers: t.Object({ authorization: t.String() }) }, (app) => app.get('', () => 'Hello') ) const error = await app.handle(req('/v1')) const correct = await app.handle( new Request('http://localhost/v1', { headers: { authorization: 'Bearer' } }) ) expect(correct.status).toBe(200) expect(error.status).toBe(422) }) it('validate params', async () => { const app = new Elysia().group( '/v1', { transform({ params }) { if (!+Number.isNaN(params.id)) params.id = +params.id }, params: t.Object({ id: t.Number() }) }, (app) => app.get('/id/:id', () => 'Hello') ) const error = await app.handle(req('/v1/id/a')) const correct = await app.handle(req('/v1/id/1')) expect(correct.status).toBe(200) expect(error.status).toBe(422) }) it('validate query', async () => { const app = new Elysia().group( '/v1', { query: t.Object({ name: t.String() }) }, (app) => app.get('', () => 'Hello') ) const error = await app.handle(req('/v1?id=1')) const correct = await app.handle(req('/v1?name=a')) expect(correct.status).toBe(200) expect(error.status).toBe(422) }) it('validate body', async () => { const app = new Elysia().group( '/v1', { body: t.Object({ name: t.String() }) }, (app) => app.post('', ({ body }) => body) ) const error = await app.handle( post('/v1', { id: 'hi' }) ) const correct = await app.handle( post('/v1', { name: 'hi' }) ) expect(correct.status).toBe(200) expect(error.status).toBe(422) }) it('validate response', async () => { const app = new Elysia().group( '/v1', { response: t.String() }, (app) => // @ts-ignore app .get('/correct', () => 'Hello') // @ts-ignore .get('/error', () => 1) ) const error = await app.handle(req('/v1/error')) const correct = await app.handle(req('/v1/correct')) expect(correct.status).toBe(200) expect(error.status).toBe(422) }) it('validate request with prefix', async () => { const app = new Elysia({ prefix: '/api' }).group('/v1', (app) => app.get('', () => 'Hello') ) const res = await app.handle(req('/api/v1')) expect(res.status).toBe(200) }) it('handle nested prefix with group', () => { const plugin = new Elysia({ prefix: '/v1' }).group('/course', (app) => app .get('', () => '') .put('/new', () => '') .group( '/id/:courseId', { params: t.Object({ courseId: t.Numeric() }) }, (app) => app.group('/chapter', (app) => app.get( '/hello', ({ params: { courseId } }) => courseId ) ) ) ) const app = new Elysia().use(plugin) expect(app.router.history.map((x) => x.path)).toEqual([ '/v1/course', '/v1/course/new', '/v1/course/id/:courseId/chapter/hello' ]) }) it("skip don't duplicate prefix on group with hooks", () => { const a = new Elysia({ prefix: '/course' }).group( '/id/:courseId', { params: t.Object({ courseId: t.Numeric() }) }, (app) => app.get('/b', () => 'A') ) const b = new Elysia({ prefix: '/test' }).group( '/id/:courseId', (app) => app.get('/b', () => 'A') ) const app = new Elysia() .use(a) .use(b) .get('/', () => 'a') expect(app.router.history.map((x) => x.path)).toEqual([ '/course/id/:courseId/b', '/test/id/:courseId/b', '/' ]) }) it('inherits singleton / definitions and re-meregd on main', async () => { const app = new Elysia() .decorate({ a: 'a' }) .state({ a: 'a' }) .model('a', t.String()) .error('a', Error) .group('/posts', (app) => { // @ts-expect-error expect(Object.keys(app.singleton.decorator)).toEqual(['a']) // @ts-expect-error expect(Object.keys(app.singleton.store)).toEqual(['a']) // @ts-expect-error expect(Object.keys(app.definitions.type)).toEqual(['a']) // @ts-expect-error expect(Object.keys(app.definitions.error)).toEqual(['a']) return app .decorate({ b: 'b' }) .state({ b: 'b' }) .model('b', t.String()) .error('b', Error) .get('/', ({ a }) => a ?? 'Aint no response') }) // @ts-expect-error expect(Object.keys(app.singleton.decorator)).toEqual(['a', 'b']) // @ts-expect-error expect(Object.keys(app.singleton.store)).toEqual(['a', 'b']) // @ts-expect-error expect(Object.keys(app.definitions.type)).toEqual(['a', 'b']) // @ts-expect-error expect(Object.keys(app.definitions.error)).toEqual(['a', 'b']) const response = await app.handle(req('/posts')).then((x) => x.text()) expect(response).toEqual('a') }) it('cast callback function schema to standaloneValidator', async () => { const app = new Elysia().group( '/group/:id', { params: t.Object({ id: t.Number() }) }, (app) => app.get('/:name', ({ params }) => params, { params: t.Object({ name: t.String() }) }) ) const valid = app.handle(req('/group/1/saltyaom')).then((x) => x.json()) const invalid = app .handle(req('/group/a/saltyaom')) .then((x) => x.status) expect(await valid).toEqual({ id: 1, name: 'saltyaom' }) expect(await invalid).toBe(422) }) it('handle multiple nested guard group', async () => { const app = new Elysia().group('', {}, (app) => app.group('', {}, (app) => app.get('/', ({ query }) => query, { query: t.Object({ playing: t.Boolean(), limit: t.Number() }) }) ) ) const value = await app .handle(req('/?playing=true&limit=10')) .then((x) => x.json()) expect(value).toEqual({ playing: true, limit: 10 }) }) it('handle multiple nested guard with schema', async () => { const app = new Elysia().group( '', { query: t.Object({ name: t.Literal('lilith') }) }, (app) => app.group( '', { query: t.Object({ limit: t.Number() }) }, (app) => app.get('/', ({ query }) => query, { query: t.Object({ playing: t.Boolean() }) }) ) ) const value = await app .handle(req('/?name=lilith&playing=true&limit=10')) .then((x) => x.json()) expect(value).toEqual({ name: 'lilith', playing: true, limit: 10 }) const error = await app .handle(req('/?name=lilith&playing=true')) .then((x) => x.status) expect(error).toBe(422) }) }) ================================================ FILE: test/path/guard.test.ts ================================================ import { Elysia, t } from '../../src' import { describe, expect, it } from 'bun:test' import { post, req } from '../utils' describe('guard', () => { it('inherits global', async () => { const app = new Elysia().state('counter', 0).guard( { transform: ({ store }) => { store.counter++ } }, (app) => app.get('/', ({ store: { counter } }) => counter, { transform: ({ store }) => { store.counter++ } }) ) const valid = await app.handle(req('/')) expect(await valid.text()).toBe('2') }) it('delegate onRequest', async () => { const app = new Elysia() .get('/', () => 'A') .guard({}, (app) => app .state('counter', 0) .onRequest(({ store }) => { store.counter++ }) .get('/counter', ({ store: { counter } }) => counter) ) await app.handle(req('/')) const res = await app.handle(req('/counter')).then((r) => r.text()) expect(res).toBe('2') }) it('decorate guard', async () => { const app = new Elysia().guard({}, (app) => app.decorate('a', 'b').get('/', ({ a }) => a) ) const res = await app.handle(req('/')).then((x) => x.text()) expect(res).toBe('b') }) it('validate headers', async () => { const app = new Elysia().guard( { headers: t.Object({ authorization: t.String() }) }, (app) => app.get('/', () => 'Hello') ) const error = await app.handle(req('/')) const correct = await app.handle( new Request('http://localhost/', { headers: { authorization: 'Bearer' } }) ) expect(correct.status).toBe(200) expect(error.status).toBe(422) }) it('validate params', async () => { const app = new Elysia().guard( { transform({ params }) { if (!+Number.isNaN(params.id)) params.id = +params.id }, params: t.Object({ id: t.Number() }) }, (app) => app.get('/id/:id', () => 'Hello') ) const error = await app.handle(req('/id/a')) const correct = await app.handle(req('/id/1')) expect(correct.status).toBe(200) expect(error.status).toBe(422) }) it('validate query', async () => { const app = new Elysia().guard( { query: t.Object({ name: t.String() }) }, (app) => app.get('/', () => 'Hello') ) const error = await app.handle(req('/?id=1')) const correct = await app.handle(req('/?name=a')) expect(correct.status).toBe(200) expect(error.status).toBe(422) }) it('validate body', async () => { const app = new Elysia().guard( { body: t.Object({ name: t.String() }) }, (app) => app.post('/', ({ body }) => body) ) const error = await app.handle( post('/', { id: 'hi' }) ) const correct = await app.handle( post('/', { name: 'hi' }) ) expect(correct.status).toBe(200) expect(error.status).toBe(422) }) it('validate response', async () => { const app = new Elysia().guard( { response: t.String() }, (app) => // @ts-ignore app.get('/correct', () => 'Hello').get('/error', () => 1) ) const error = await app.handle(req('/error')) const correct = await app.handle(req('/correct')) expect(correct.status).toBe(200) expect(error.status).toBe(422) }) it('apply guard globally', async () => { // @ts-ignore const app = new Elysia({ precompile: false }) .guard({ response: t.String() }) .get('/correct', () => 'Hello') // @ts-expect-error .get('/error', () => 1) const error = await app.handle(req('/error')) const correct = await app.handle(req('/correct')) expect(correct.status).toBe(200) expect(error.status).toBe(422) }) it('inherits singleton / definitions and re-meregd on main', async () => { const app = new Elysia() .decorate({ a: 'a' }) .state({ a: 'a' }) .model('a', t.String()) .error('a', Error) .group('/posts', (app) => { // @ts-expect-error expect(Object.keys(app.singleton.decorator)).toEqual(['a']) // @ts-expect-error expect(Object.keys(app.singleton.store)).toEqual(['a']) // @ts-expect-error expect(Object.keys(app.definitions.type)).toEqual(['a']) // @ts-expect-error expect(Object.keys(app.definitions.error)).toEqual(['a']) return app .decorate({ b: 'b' }) .state({ b: 'b' }) .model('b', t.String()) .error('b', Error) .get('/', ({ a }) => a ?? 'Aint no response') }) // @ts-expect-error expect(Object.keys(app.singleton.decorator)).toEqual(['a', 'b']) // @ts-expect-error expect(Object.keys(app.singleton.store)).toEqual(['a', 'b']) // @ts-expect-error expect(Object.keys(app.definitions.type)).toEqual(['a', 'b']) // @ts-expect-error expect(Object.keys(app.definitions.error)).toEqual(['a', 'b']) const response = await app.handle(req('/posts')).then((x) => x.text()) expect(response).toEqual('a') }) it('handle as global', async () => { let called = 0 const inner = new Elysia() .guard({ as: 'global', response: t.Number(), transform() { called++ } }) // @ts-expect-error .get('/inner', () => 'a') const plugin = new Elysia() .use(inner) // @ts-expect-error .get('/plugin', () => true) // @ts-expect-error const app = new Elysia().use(plugin).get('/', () => 'not a number') const response = await Promise.all([ app.handle(req('/inner')).then((x) => x.status), app.handle(req('/plugin')).then((x) => x.status), app.handle(req('/')).then((x) => x.status) ]) expect(called).toBe(3) expect(response).toEqual([422, 422, 422]) }) it('handle as global with local override', async () => { let called = 0 const inner = new Elysia() .guard({ as: 'global', response: t.Number(), transform() { called++ } }) // @ts-expect-error .get('/inner', () => 'a') const plugin = new Elysia() .use(inner) .guard({ response: t.Boolean(), transform() { called++ } }) .get('/plugin', () => true) // @ts-expect-error const app = new Elysia().use(plugin).get('/', () => 'not a number') const response = await Promise.all([ app.handle(req('/inner')).then((x) => x.status), app.handle(req('/plugin')).then((x) => x.status), app.handle(req('/')).then((x) => x.status) ]) expect(called).toBe(4) expect(response).toEqual([422, 200, 422]) }) it('handle as global with scoped override', async () => { let called = 0 const inner = new Elysia() .guard({ as: 'global', response: t.Number(), transform() { called++ } }) // @ts-expect-error .get('/inner', () => 'a') const plugin = new Elysia() .use(inner) .guard({ as: 'scoped', response: t.String(), transform() { called++ } }) .get('/plugin', () => 'ok') const app = new Elysia().use(plugin).get('/', () => 'not a number') const response = await Promise.all([ app.handle(req('/inner')).then((x) => x.status), app.handle(req('/plugin')).then((x) => x.status), app.handle(req('/')).then((x) => x.status) ]) expect(called).toBe(5) expect(response).toEqual([422, 200, 200]) }) it('handle as scoped', async () => { let called = 0 const inner = new Elysia() .guard({ as: 'scoped', response: t.Number(), transform() { called++ } }) // @ts-expect-error .get('/inner', () => 'a') const plugin = new Elysia() .use(inner) // @ts-expect-error .get('/plugin', () => true) const app = new Elysia().use(plugin).get('/', () => 'not a number') const response = await Promise.all([ app.handle(req('/inner')).then((x) => x.status), app.handle(req('/plugin')).then((x) => x.status), app.handle(req('/')).then((x) => x.status) ]) expect(called).toBe(2) expect(response).toEqual([422, 422, 200]) }) it('handle as local', async () => { let called = 0 const inner = new Elysia() .guard({ as: 'local', response: t.Number(), transform() { called++ } }) // @ts-expect-error .get('/inner', () => 'a') const plugin = new Elysia().use(inner).get('/plugin', () => true) const app = new Elysia().use(plugin).get('/', () => 'not a number') const response = await Promise.all([ app.handle(req('/inner')).then((x) => x.status), app.handle(req('/plugin')).then((x) => x.status), app.handle(req('/')).then((x) => x.status) ]) expect(called).toBe(1) expect(response).toEqual([422, 200, 200]) }) it('only cast guard', async () => { let called = 0 const plugin = new Elysia() .guard({ as: 'scoped', response: t.Number(), transform() { called++ } }) .onTransform(() => { called++ }) // @ts-expect-error .get('/inner', () => 'a') const app = new Elysia().use(plugin).get('/', () => 1) const response = await Promise.all([ app.handle(req('/inner')).then((x) => x.status), app.handle(req('/')).then((x) => x.status) ]) expect(called).toBe(3) expect(response).toEqual([422, 200]) }) it('handle merge guard and hook on non-specified responses status', () => { const app = new Elysia() .guard({ response: { 400: t.String(), 500: t.String() } }) .get('/', () => '', { response: t.String() }) expect(Object.keys(app.routes[0].hooks.response)).toEqual([ '200', '400', '500' ]) }) it('cast callback function schema to standaloneValidator', async () => { const app = new Elysia().guard( { params: t.Object({ id: t.Number() }) }, (app) => app.get('/guard/:id/:name', ({ params }) => params, { params: t.Object({ name: t.String() }) }) ) const valid = app.handle(req('/guard/1/saltyaom')).then((x) => x.json()) const invalid = app .handle(req('/guard/a/saltyaom')) .then((x) => x.status) expect(await valid).toEqual({ id: 1, name: 'saltyaom' }) expect(await invalid).toBe(422) }) it('handle multiple nested guard with schema', async () => { const app = new Elysia().guard( { query: t.Object({ name: t.Literal('lilith') }) }, (app) => app.guard( { query: t.Object({ limit: t.Number() }) }, (app) => app.get('/', ({ query }) => query, { query: t.Object({ playing: t.Boolean() }) }) ) ) const value = await app .handle(req('/?name=lilith&playing=true&limit=10')) .then((x) => x.json()) expect(value).toEqual({ name: 'lilith', playing: true, limit: 10 }) const error = await app .handle(req('/?name=lilith&playing=true')) .then((x) => x.status) expect(error).toBe(422) }) }) ================================================ FILE: test/path/path.test.ts ================================================ import { Elysia, t } from '../../src' import { describe, expect, it } from 'bun:test' import { post, req } from '../utils' describe('Path', () => { it('handle root', async () => { const app = new Elysia().get('/', () => 'Hi') const res = await app.handle(req('/')) expect(await res.text()).toBe('Hi') }) it('handle multiple level', async () => { const app = new Elysia().get('/this/is/my/deep/nested/root', () => 'Ok') const res = await app.handle(req('/this/is/my/deep/nested/root')) expect(await res.text()).toBe('Ok') }) it('return boolean', async () => { const app = new Elysia().get('/', () => true) const res = await app.handle(req('/')) expect(await res.text()).toBe('true') }) it('return number', async () => { const app = new Elysia().get('/', () => 617) const res = await app.handle(req('/')) expect(await res.text()).toBe('617') }) it('return json', async () => { const app = new Elysia().get('/', () => ({ name: 'takodachi' })) const res = await app.handle(req('/')) expect(JSON.stringify(await res.json())).toBe( JSON.stringify({ name: 'takodachi' }) ) expect(res.headers.get('content-type')).toContain('application/json') }) it('return response', async () => { const app = new Elysia().get( '/', () => new Response('Shuba Shuba', { headers: { duck: 'shuba duck' }, status: 418 }) ) const res = await app.handle(req('/')) expect(await res.text()).toBe('Shuba Shuba') expect(res.status).toBe(418) expect(res.headers.get('duck')).toBe('shuba duck') }) it('parse single param', async () => { const app = new Elysia().get('/id/:id', ({ params: { id } }) => id) const res = await app.handle(req('/id/123')) expect(await res.text()).toBe('123') }) it('parse multiple params', async () => { const app = new Elysia().get( '/id/:id/:name', ({ params: { id, name } }) => `${id}/${name}` ) const res = await app.handle(req('/id/fubuki/Elysia')) expect(await res.text()).toBe('fubuki/Elysia') }) it('parse optional params', async () => { const app = new Elysia().get('/id/:id?', ({ params: { id } }) => id) const res = await Promise.all([ app.handle(req('/id')).then((x) => x.text()), app.handle(req('/id/fubuki')).then((x) => x.text()) ]) expect(res).toEqual(['', 'fubuki']) }) it('parse multiple optional params', async () => { const app = new Elysia().get( '/id/:id?/:name?', ({ params: { id = '', name = '' } }) => `${id}/${name}` ) const res = await Promise.all([ app.handle(req('/id')).then((x) => x.text()), app.handle(req('/id/fubuki')).then((x) => x.text()), app.handle(req('/id/fubuki/shirakami')).then((x) => x.text()) ]) expect(res).toEqual(['/', 'fubuki/', 'fubuki/shirakami']) }) it('accept wildcard', async () => { const app = new Elysia().get('/wildcard/*', () => 'Wildcard') const res = await app.handle(req('/wildcard/okayu')) expect(await res.text()).toBe('Wildcard') }) it('custom error', async () => { const app = new Elysia().onError((error) => { if (error.code === 'NOT_FOUND') return new Response('Not Stonk :(', { status: 404 }) }) const res = await app.handle(req('/wildcard/okayu')) expect(await res.text()).toBe('Not Stonk :(') expect(res.status).toBe(404) }) it('parse a querystring', async () => { const app = new Elysia().get('/', ({ query: { id } }) => id) const res = await app.handle(req('/?id=123')) expect(await res.text()).toBe('123') }) it('parse multiple querystrings', async () => { const app = new Elysia().get( '/', ({ query: { first, last } }) => `${last} ${first}`, { query: t.Object({ first: t.String(), last: t.String() }) } ) const res = await app.handle(req('/?first=Fubuki&last=Shirakami')) expect(await res.text()).toBe('Shirakami Fubuki') }) it('parse a querystring with a space', async () => { const app = new Elysia().get('/', ({ query: { id } }) => id) const res = await app.handle(req('/?id=test+123%2B')) expect(await res.text()).toBe('test 123+') }) it('handle body', async () => { const app = new Elysia().post('/', ({ body }) => body, { body: t.String() }) const body = 'Botan' const res = await app.handle( new Request('http://localhost/', { method: 'POST', body, headers: { 'content-type': 'text/plain', 'content-length': body.length.toString() } }) ) expect(await res.text()).toBe('Botan') }) it('parse JSON body', async () => { const body = JSON.stringify({ name: 'Okayu' }) const app = new Elysia().post('/', ({ body }) => body, { body: t.Object({ name: t.String() }) }) const res = await app.handle( new Request('http://localhost/', { method: 'POST', body, headers: { 'content-type': 'application/json', 'content-length': body.length.toString() } }) ) expect(JSON.stringify(await res.json())).toBe(body) }) it('parse headers', async () => { const app = new Elysia().post('/', ({ request }) => request.headers.get('x-powered-by') ) const res = await app.handle( new Request('http://localhost/', { method: 'POST', headers: { 'x-powered-by': 'Elysia' } }) ) expect(await res.text()).toBe('Elysia') }) it('handle group', async () => { const app = new Elysia().group('/gamer', (app) => app.get('/korone', () => 'Yubi Yubi!') ) const res = await app.handle(req('/gamer/korone')).then((r) => r.text()) expect(await res).toBe('Yubi Yubi!') }) it('handle plugin', async () => { const plugin = (app: Elysia) => app.get('/korone', () => 'Yubi Yubi!') const app = new Elysia().use(plugin) const res = await app.handle(req('/korone')) expect(await res.text()).toBe('Yubi Yubi!') }) it('handle error', async () => { const error = 'Pardun?' const plugin = (app: Elysia) => app.get('/error', () => new Error(error)) const app = new Elysia().use(plugin) const res = await app.handle(req('/error')) const { message } = (await res.json()) as unknown as { message: string } expect(message).toBe(error) }) it('handle async', async () => { const app = new Elysia().get('/async', async () => { await new Promise((resolve) => setTimeout(() => { resolve() }, 1) ) return 'Hi' }) const res = await app.handle(req('/async')) expect(await res.text()).toBe('Hi') }) it('handle absolute path', async () => { const app = new Elysia().get('/', () => 'Hi') const res = await app.handle(req('/')) expect(await res.text()).toBe('Hi') }) it('handle route which start with same letter', async () => { const app = new Elysia() .get('/aa', () => 'route 1') .get('/ab', () => 'route 2') const response = await app.handle(req('/ab')) const text = await response.text() expect(text).toBe('route 2') }) it('handle route which start with same letter', async () => { const app = new Elysia() .get('/aa', () => 'route 1') .get('/ab', () => 'route 2') const response = await app.handle(req('/ab')) const text = await response.text() expect(text).toBe('route 2') }) it('return file', async () => { const app = new Elysia().get('/', ({ set }) => { set.headers.server = 'Elysia' return Bun.file('./example/takodachi.png') }) const res = await app.handle(req('/')) expect((await res.text()).length).toBe( (await Bun.file('./example/takodachi.png').text()).length ) expect(res.headers.get('Server')).toBe('Elysia') }) it('return web api\'s File', async () => { const app = new Elysia().get('/', () => new File(['Hello'], 'hello.txt', { type: 'text/plain' })) const res = await app.handle(req('/')) expect(res.headers.get('content-type')).toBe('text/plain;charset=utf-8') expect(await res.text()).toBe('Hello') expect(res.status).toBe(200) expect(res.headers.get('accept-ranges')).toBe('bytes') expect(res.headers.get('content-range')).toBe('bytes 0-4/5') }) it('handle *', async () => { const app = new Elysia().get('/*', () => 'Hi') const get = await app.handle(req('/')).then((r) => r.text()) const post = await app .handle(req('/anything/should/match')) .then((r) => r.text()) expect(get).toBe('Hi') expect(post).toBe('Hi') }) it('handle * on multiple methods', async () => { const app = new Elysia() .get('/part', () => 'Part') .options('*', () => 'Hi') const get = await app.handle(req('/part')).then((r) => r.text()) const options = await app .handle( new Request('http://localhost/part', { method: 'OPTIONS' }) ) .then((r) => r.text()) expect(get).toBe('Part') expect(options).toBe('Hi') }) it('decode uri', async () => { const app = new Elysia().get('/', ({ query }) => query) const res = await app .handle(req('/?name=a%20b&c=d%20e')) .then((r) => r.json()) expect(res).toEqual({ name: 'a b', c: 'd e' }) }) it('handle all method', async () => { const app = new Elysia().all('/', () => 'Hi') const res1 = await app.handle(req('/')).then((res) => res.text()) const res2 = await app.handle(post('/', {})).then((res) => res.text()) expect(res1).toBe('Hi') expect(res2).toBe('Hi') }) it('add path if onRequest is used', async () => { const app = new Elysia() .onRequest(() => {}) .onAfterHandle(({ path }) => { return path }) .get('/', () => 'Hi') const res = await app.handle(req('/')).then((res) => res.text()) expect(res).toBe('/') }) // it('handle array route - GET', async () => { // const paths = ['/', '/test', '/other/nested'] // const app = new Elysia().get(paths, ({ path }) => { // return path // }) // for (const path of paths) { // const res = await app.handle(req(path)) // expect(await res.text()).toBe(path) // } // }) // it('handle array route - POST', async () => { // const paths = ['/', '/test', '/other/nested'] // const app = new Elysia().post(paths, ({ path }) => { // return path // }) // for (const path of paths) { // const res = await app.handle( // new Request('http://localhost' + path, { // method: 'POST' // }) // ) // expect(await res.text()).toBe(path) // } // }) // it('handle array route - PUT', async () => { // const paths = ['/', '/test', '/other/nested'] // const app = new Elysia().put(paths, ({ path }) => { // return path // }) // for (const path of paths) { // const res = await app.handle( // new Request('http://localhost' + path, { // method: 'PUT' // }) // ) // expect(await res.text()).toBe(path) // } // }) // it('handle array route - DELETE', async () => { // const paths = ['/', '/test', '/other/nested'] // const app = new Elysia().delete(paths, ({ path }) => { // return path // }) // for (const path of paths) { // const res = await app.handle( // new Request('http://localhost' + path, { // method: 'DELETE' // }) // ) // expect(await res.text()).toBe(path) // } // }) // it('handle array route - PATCH', async () => { // const paths = ['/', '/test', '/other/nested'] // const app = new Elysia().patch(paths, ({ path }) => { // return path // }) // for (const path of paths) { // const res = await app.handle( // new Request('http://localhost' + path, { // method: 'PATCH' // }) // ) // expect(await res.text()).toBe(path) // } // }) // it('handle array route - HEAD', async () => { // const paths = ['/', '/test', '/other/nested'] // const app = new Elysia().head(paths, ({ path }) => { // return path // }) // for (const path of paths) { // const res = await app.handle( // new Request('http://localhost' + path, { // method: 'HEAD' // }) // ) // expect(await res.text()).toBe(path) // } // }) // it('handle array route - OPTIONS', async () => { // const paths = ['/', '/test', '/other/nested'] // const app = new Elysia().options(paths, ({ path }) => { // return path // }) // for (const path of paths) { // const res = await app.handle( // new Request('http://localhost' + path, { // method: 'OPTIONS' // }) // ) // expect(await res.text()).toBe(path) // } // }) // it('handle array route - CONNECT', async () => { // const paths = ['/', '/test', '/other/nested'] // const app = new Elysia().connect(paths, ({ path }) => { // return path // }) // for (const path of paths) { // const res = await app.handle( // new Request('http://localhost' + path, { // method: 'CONNECT' // }) // ) // expect(await res.text()).toBe(path) // } // }) // it('handle array route - all', async () => { // const paths = ['/', '/test', '/other/nested'] as const // const app = new Elysia().all(paths, ({ path }) => { // return path // }) // for (const path of paths) { // const getRes = await app.handle(req(path)) // const postRes = await app.handle( // new Request('http://localhost' + path, { // method: 'POST' // }) // ) // const putRes = await app.handle( // new Request('http://localhost' + path, { // method: 'PUT' // }) // ) // const deleteRes = await app.handle( // new Request('http://localhost' + path, { // method: 'DELETE' // }) // ) // const patchRes = await app.handle( // new Request('http://localhost' + path, { // method: 'PATCH' // }) // ) // const headRes = await app.handle( // new Request('http://localhost' + path, { // method: 'HEAD' // }) // ) // expect(await getRes.text()).toBe(path) // expect(await postRes.text()).toBe(path) // expect(await putRes.text()).toBe(path) // expect(await deleteRes.text()).toBe(path) // expect(await patchRes.text()).toBe(path) // expect(await headRes.text()).toBe(path) // } // }) // it('handle array route - custom method', async () => { // const paths = ['/', '/test', '/other/nested'] as const // // @ts-ignore // const app = new Elysia().route('NOTIFY', paths, ({ path }) => { // return path // }) // for (const path of paths) { // const res = await app.handle( // new Request('http://localhost' + path, { // method: 'NOTIFY' // }) // ) // expect(await res.text()).toBe(path) // } // }) }) ================================================ FILE: test/plugins/affix.test.ts ================================================ // @ts-nocheck import { Elysia, t } from '../../src' import { describe, it, expect } from 'bun:test' const setup = new Elysia() .decorate('decorate', 'decorate') .state('state', 'state') .model('model', t.String()) .error('error', Error) describe('affix', () => { it('should add prefix to all decorators, states, models, and errors', () => { const app = new Elysia().use(setup).affix('prefix', 'all', 'p') expect(app.singleton.decorator).toHaveProperty('pDecorate') expect(app.singleton.store).toHaveProperty('pState') expect(app.definitions.type).toHaveProperty('pModel') expect(app.definitions.error).toHaveProperty('pError') }) it('should add suffix to all decorators, states, models, and errors', () => { const app = new Elysia().use(setup).affix('suffix', 'all', 'p') expect(app.singleton.decorator).toHaveProperty('decorateP') expect(app.singleton.store).toHaveProperty('stateP') expect(app.definitions.type).toHaveProperty('modelP') expect(app.definitions.error).toHaveProperty('errorP') }) it('should add suffix to all states', () => { const app = new Elysia().use(setup).suffix('state', 'p') expect(app.singleton.store).toHaveProperty('stateP') }) it('should add prefix to all decorators and errors', () => { const app = new Elysia() .use(setup) .prefix('decorator', 'p') .prefix('error', 'p') expect(app.singleton.decorator).toHaveProperty('pDecorate') expect(app.definitions.error).toHaveProperty('pError') }) it('should add suffix to all decorators and errors', () => { const app = new Elysia() .use(setup) .suffix('decorator', 'p') .suffix('error', 'p') expect(app.singleton.decorator).toHaveProperty('decorateP') expect(app.definitions.error).toHaveProperty('errorP') }) it('should add prefix to all models', () => { const app = new Elysia().use(setup).prefix('model', 'p') expect(app.definitions.type).toHaveProperty('pModel') }) it('should add suffix to all models', () => { const app = new Elysia().use(setup).affix('suffix', 'model', 'p') expect(app.definitions.type).toHaveProperty('modelP') }) it('should skip on empty', () => { const app = new Elysia().use(setup).suffix('all', '') expect(app.singleton.decorator).toHaveProperty('decorate') expect(app.singleton.store).toHaveProperty('state') expect(app.definitions.type).toHaveProperty('model') expect(app.definitions.error).toHaveProperty('error') }) }) ================================================ FILE: test/plugins/checksum.test.ts ================================================ import { Elysia, t } from '../../src' import { describe, expect, it } from 'bun:test' import { req } from '../utils' describe('Checksum', () => { it('deduplicate plugin', async () => { const cookie = (options?: Record) => new Elysia({ name: '@elysiajs/cookie', seed: options }).onTransform({ as: 'global' }, () => {}) const group = new Elysia().use(cookie({})).get('/a', () => 'Hi') const app = new Elysia() .use(cookie({})) .use(group) .get('/cookie', () => 'Hi') const [a, b] = app.router.history expect(a.hooks.transform!.length).toBe(1) expect(b.hooks.transform!.length).toBe(1) }) it('Set default checksum if not provided when name is set', async () => { const cookie = (options?: Record) => new Elysia({ name: '@elysiajs/cookie', seed: options }).onTransform({ as: 'global' }, () => {}) const group = new Elysia().use(cookie()).get('/a', () => 'Hi') const app = new Elysia() .use(cookie()) .use(group) .get('/cookie', () => 'Hi') const [a, b] = app.router.history expect(a.hooks.transform!.length).toBe(1) expect(b.hooks.transform!.length).toBe(1) }) it('Accept plugin when on different different', async () => { const cookie = (options?: Record) => new Elysia({ name: '@elysiajs/cookie', seed: options }).onTransform({ as: 'global' }, () => {}) const group = new Elysia().use(cookie({})).get('/a', () => 'Hi') const app = new Elysia() .use(group) .use( cookie({ hello: 'world' }) ) .get('/cookie', () => 'Hi') const [a, b] = app.router.history expect( Math.abs(a.hooks.transform!.length - b.hooks.transform!.length) ).toBe(1) }) it('Deduplicate global hook on use', async () => { const cookie = (options?: Record) => new Elysia({ name: '@elysiajs/cookie', seed: options }).onTransform({ as: 'global' }, () => {}) const group = new Elysia().use(cookie()).get('/a', () => 'Hi') const app = new Elysia() .use(cookie()) .use(group) .get('/cookie', () => 'Hi') const [a, b] = app.router.history expect( Math.abs(a.hooks.transform!.length - b.hooks.transform!.length) ).toBe(0) }) it('Filter inline hook', async () => { const cookie = (options?: Record) => new Elysia({ name: '@elysiajs/cookie', seed: options }).onTransform({ as: 'global' }, () => {}) const group = new Elysia().use(cookie()).get('/a', () => 'Hi', { transform() {} }) const app = new Elysia() .use(cookie()) .use(group) .get('/cookie', () => 'Hi') const [a, b] = app.router.history expect( Math.abs(a.hooks.transform!.length - b.hooks.transform!.length) ).toBe(1) }) it('Merge global hook', async () => { let count = 0 const cookie = (options?: Record) => new Elysia({ name: '@elysiajs/cookie', seed: options }).onTransform({ as: 'global' }, () => {}) const group = new Elysia() .use(cookie()) .onTransform({ as: 'global' }, () => { count++ }) .get('/a', () => 'Hi') const app = new Elysia() .use(cookie()) .use(group) .get('/cookie', () => 'Hi') await Promise.all(['/a', '/cookie'].map((x) => app.handle(req(x)))) expect(count).toBe(2) }) it('Deduplicate guard hook', async () => { const guard = new Elysia({ prefix: '/guard' }).guard( { params: t.Object({ id: t.Number() }), transform({ params }) { const id = +params.id if (!Number.isNaN(id)) params.id = id } }, (app) => app.get('/id/:id', ({ params: { id } }) => id) ) const app = new Elysia().use(guard) const res = await app.handle(req('/guard/id/123')) expect(res.status).toBe(200) }) it('deduplicate in new instance', async () => { const cookie = (options?: Record) => new Elysia({ name: '@elysiajs/cookie', seed: options }).derive({ as: 'global' }, () => { return { cookie: 'mock' } }) const plugin = new Elysia({ prefix: '/v1' }) .use(cookie()) .get('/plugin', ({ cookie }) => cookie) const plugin2 = new Elysia({ prefix: '/v2' }) .use(cookie()) .get('/plugin', ({ cookie }) => cookie) const app = new Elysia() .use(cookie()) .use(plugin) .use(plugin2) .get('/root', ({ cookie }) => cookie) const res1 = await app.handle(req('/v1/plugin')).then((x) => x.text()) expect(res1).toBe('mock') const res2 = await app.handle(req('/v1/plugin')).then((x) => x.text()) expect(res2).toBe('mock') const root = await app.handle(req('/root')).then((x) => x.text()) expect(root).toBe('mock') }) it('Filter global event', async () => { let x = 0 let a = 0 let b = 0 const plugin = new Elysia() .onBeforeHandle({ as: 'global' }, () => { x++ }) .group('/v1', (app) => app .onBeforeHandle(() => { a++ }) .get('', () => 'A') .group('/v1', (app) => app .onBeforeHandle(() => { b++ }) .get('/', () => 'B') ) ) const app = new Elysia().use(plugin).get('/', () => 'A') await Promise.all( ['/v1', '/v1/v1', '/'].map((path) => app.handle(req(path))) ) expect(x).toBe(3) expect(a).toBe(2) expect(b).toBe(1) }) it('invalidate non-root lifecycle', async () => { let a = 0 let b = 0 let c = 0 const plugin = new Elysia() .use( new Elysia() .derive({ as: 'global' }, () => { a++ return {} }) .get('/1', () => 'asdf') ) .use( new Elysia() .derive({ as: 'global' }, () => { b++ return { test: 'test' } }) .get('/2', ({ test }) => test) .use( new Elysia() .derive({ as: 'global' }, () => { c++ return { test: 'test' } }) .get('/3', ({ test }) => test) ) ) const app = new Elysia() .get('/root', () => 'A') .use(plugin) .get('/all', () => 'A') await Promise.all( ['/root', '/1', '/2', '/3', '/all'].map((path) => app.handle(req(path)) ) ) expect(a).toBe(4) expect(b).toBe(3) expect(c).toBe(2) }) it('read lifecylce top-down', async () => { let i = 0 const plugin = new Elysia() .use(new Elysia({ prefix: '/not-call' }).get('/', () => 'asdf')) .use( new Elysia({ prefix: '/call' }) .derive({ as: 'global' }, () => { i++ // <-- should not be called, when requesting /asdf return { test: 'test' } }) .get('/', ({ test }) => test) ) const app = new Elysia().use(plugin) await Promise.all( ['/not-call', '/call'].map((path) => app.handle(req(path))) ) expect(i).toBe(1) }) it('scope plugin', async () => { let i = 0 const plugin = new Elysia().use( new Elysia({ prefix: '/call' }) .derive(() => { i++ // <-- should not be called, when requesting /asdf return { test: 'test' } }) .get('/', ({ test }) => test) .use(new Elysia({ prefix: '/not-call' }).get('/', () => 'asdf')) ) const app = new Elysia().use(plugin) await Promise.all( ['/not-call', '/call'].map((path) => app.handle(req(path))) ) expect(i).toBe(1) }) it('handle reference parent-child', async () => { const parent = new Elysia({ name: 'parent' }).derive( { as: 'global' }, () => ({ bye: () => 'bye' }) ) const child = new Elysia({ name: 'child' }) .use(parent) .derive({ as: 'global' }, ({ bye }) => ({ hi: () => `hi + ${bye()}` })) const app = new Elysia() .use(parent) .use(child) .get('/', ({ hi }) => hi()) const response = await app.handle(req('/')).then((res) => res.text()) expect(response).toBe('hi + bye') }) it('deduplicate local handler from global event', () => { const ip = new Elysia({ name: 'ip', seed: 'ip' }) .derive({ as: 'global' }, ({ server, request }) => { return { ip: server?.requestIP(request) } }) .onBeforeHandle(() => { console.log('11') }) .get('/ip', ({ ip }) => ip) const router1 = new Elysia({ name: 'ip1', seed: 'ip1' }) .use(ip) .get('/ip-1', ({ ip }) => ip) const router2 = new Elysia({ name: 'ip2', seed: 'ip2' }) .use(ip) .get('/ip-2', ({ ip }) => ip) const router3 = new Elysia({ name: 'ip2', seed: 'ip2' }) .use(ip) .get('/ip-3', ({ ip }) => ip) const server = new Elysia({ name: 'server' }).use(router1).use(router2) expect( server.routes.find((x) => x.path === '/ip')?.hooks.transform ).toHaveLength(1) }) }) ================================================ FILE: test/plugins/error-propagation.test.ts ================================================ import { describe, expect, it } from 'bun:test' import { Elysia } from '../../src' import { req } from '../utils' describe('Error correctly passed to outer elysia instance', () => { it('Global error handler is run', async () => { let globalHandlerRun = false const mainApp = new Elysia().onError(() => { globalHandlerRun = true return 'Fail' }) const plugin = new Elysia().get('/foo', () => { throw new Error('Error') }) mainApp.use(plugin) const res = await (await mainApp.handle(req('/foo'))).text() expect(res).toBe('Fail') expect(globalHandlerRun).toBeTrue() }) it('Plugin global handler is executed before plugin handler', async () => { //I would expect the plugin error handler to be executed let globalHandlerRun = false let localHandlerRun = false const plugin = new Elysia({ prefix: '/a' }) .onError({ as: 'global' }, () => { localHandlerRun = true return 'FailPlugin' }) .get('/foo', () => { throw new Error('Error') }) const mainApp = new Elysia() .onError(() => { globalHandlerRun = true return 'Fail' }) .use(plugin) const res = await mainApp.handle(req('/a/foo')).then((x) => x.text()) expect(res).toBe('Fail') expect(localHandlerRun).toBeFalse() expect(globalHandlerRun).toBeTrue() }) }) ================================================ FILE: test/plugins/plugin.test.ts ================================================ import { Elysia } from '../../src' import { describe, expect, it } from 'bun:test' import { req } from '../utils' describe('Plugin', () => { it('await async nested plugin', async () => { const yay = async () => { await Bun.sleep(2) return new Elysia({ name: 'yay' }).get('/yay', 'yay') } const wrapper = new Elysia({ name: 'wrapper' }).use(yay()) const app = new Elysia().use(wrapper) await app.modules const response = await app.handle(req('/yay')) expect(response.status).toBe(200) }) }) ================================================ FILE: test/production/index.test.ts ================================================ import { Elysia, t } from '../../src' import { describe, it, expect, beforeEach } from 'bun:test' describe('NODE_ENV=production', () => { beforeEach(() => { process.env.NODE_ENV = 'production' }) it('omit error summary', async () => { const app = new Elysia() .post('/', () => 'yay', { body: t.Object({ name: t.String() }) }) const response = await app.handle( new Request('http://localhost/', { method: 'POST', body: '' }) ) const text = await response.text() expect(text).not.toEqual( 'Right side of assignment cannot be destructured' ) }) }) ================================================ FILE: test/response/custom-response.test.ts ================================================ import { Elysia } from '../../src' import { describe, expect, it } from 'bun:test' import { req } from '../utils' class CustomResponse extends Response { } describe('Custom Response Type', () => { it('returns custom response when set headers is not empty', async () => { const app = new Elysia() .get('/', ({ set }) => { set.headers['X-POWERED-BY'] = 'Elysia' return new CustomResponse('Shuba Shuba', { headers: { duck: 'shuba duck' }, status: 418 }) }) const response = await app.handle(req('/')) expect(await response.text()).toBe('Shuba Shuba') expect(response.headers.get('duck')).toBe('shuba duck') expect(response.headers.get('X-POWERED-BY')).toBe('Elysia') expect(response.status).toBe(418) }) it('returns custom response when set headers is empty', async () => { const app = new Elysia() .get('/', () => { return new CustomResponse('Shuba Shuba') }) const response = await app.handle(req('/')) expect(await response.text()).toBe('Shuba Shuba') }) it('Response headers take precedence, set.headers merge non-conflicting', async () => { const app = new Elysia() .onRequest(({ set }) => { set.headers['Content-Type'] = 'application/json' set.headers['X-Framework'] = 'Elysia' }) .get('/', () => { return new Response('{"message":"hello"}', { headers: { 'Content-Type': 'text/plain', 'X-Custom': 'custom-value' } }) }) const response = await app.handle(req('/')) // Response's Content-Type takes precedence expect(response.headers.get('Content-Type')).toBe('text/plain') // set.headers adds non-conflicting headers expect(response.headers.get('X-Framework')).toBe('Elysia') // Response's own headers are preserved expect(response.headers.get('X-Custom')).toBe('custom-value') }) }) ================================================ FILE: test/response/headers.test.ts ================================================ import { Elysia } from '../../src' import { describe, expect, it } from 'bun:test' import { req } from '../utils' describe('Response Headers', () => { it('add response headers', async () => { const app = new Elysia().get('/', ({ set }) => { set.headers['x-powered-by'] = 'Elysia' return 'Hi' }) const res = await app.handle(req('/')) expect(res.headers.get('x-powered-by')).toBe('Elysia') }) it('add headers from hook', async () => { const app = new Elysia() .onTransform(({ set }) => { set.headers['x-powered-by'] = 'Elysia' }) .get('/', () => 'Hi') const res = await app.handle(req('/')) expect(res.headers.get('x-powered-by')).toBe('Elysia') }) it('add headers from plugin', async () => { const plugin = (app: Elysia) => app.onTransform(({ set }) => { set.headers['x-powered-by'] = 'Elysia' }) const app = new Elysia().use(plugin).get('/', () => 'Hi') const res = await app.handle(req('/')) expect(res.headers.get('x-powered-by')).toBe('Elysia') }) it('add headers to Response', async () => { const app = new Elysia() .onTransform(({ set }) => { set.headers['x-powered-by'] = 'Elysia' }) .get('/', () => new Response('Hi')) const res = await app.handle(req('/')) expect(res.headers.get('x-powered-by')).toBe('Elysia') }) it('add status to Response', async () => { const app = new Elysia().get('/', ({ set }) => { set.status = 401 return 'Hi' }) const res = await app.handle(req('/')) expect(await res.text()).toBe('Hi') expect(res.status).toBe(401) }) it('create static header', async () => { const app = new Elysia() .headers({ 'x-powered-by': 'Elysia' }) .get('/', () => 'hi') const headers = await app.handle(req('/')).then((x) => x.headers) expect(headers.get('x-powered-by')).toBe('Elysia') }) it('accept header from plugin', async () => { const plugin = new Elysia().headers({ 'x-powered-by': 'Elysia' }) const app = new Elysia().use(plugin).get('/', () => 'hi') const headers = await app.handle(req('/')).then((x) => x.headers) expect(headers.get('x-powered-by')).toBe('Elysia') }) }) ================================================ FILE: test/response/range.test.ts ================================================ import { describe, expect, it } from 'bun:test' import { Elysia } from '../../src' import { req } from '../utils' // Regression test for https://github.com/elysiajs/elysia/issues/1790 // Range header was ignored; always returned bytes 0-N/N instead of the requested slice. describe('Range header', () => { const content = '12345' const app = new Elysia().get('/file', () => new Blob([content])) it('returns full file without Range header', async () => { const res = await app.handle(req('/file')) expect(res.status).toBe(200) expect(await res.text()).toBe(content) }) it('handles bytes=start- (open-ended range)', async () => { const res = await app.handle( req('/file', { headers: { range: 'bytes=3-' } }) ) expect(res.status).toBe(206) expect(res.headers.get('content-range')).toBe('bytes 3-4/5') expect(res.headers.get('content-length')).toBe('2') expect(await res.text()).toBe('45') }) it('handles bytes=start-end (bounded range)', async () => { const res = await app.handle( req('/file', { headers: { range: 'bytes=1-3' } }) ) expect(res.status).toBe(206) expect(res.headers.get('content-range')).toBe('bytes 1-3/5') expect(res.headers.get('content-length')).toBe('3') expect(await res.text()).toBe('234') }) it('handles bytes=-suffix (last N bytes)', async () => { const res = await app.handle( req('/file', { headers: { range: 'bytes=-2' } }) ) expect(res.status).toBe(206) expect(res.headers.get('content-range')).toBe('bytes 3-4/5') expect(res.headers.get('content-length')).toBe('2') expect(await res.text()).toBe('45') }) it('clamps end beyond file size to last byte', async () => { const res = await app.handle( req('/file', { headers: { range: 'bytes=2-999' } }) ) expect(res.status).toBe(206) expect(res.headers.get('content-range')).toBe('bytes 2-4/5') expect(await res.text()).toBe('345') }) it('returns 416 when start is out of range', async () => { const res = await app.handle( req('/file', { headers: { range: 'bytes=99-' } }) ) expect(res.status).toBe(416) expect(res.headers.get('content-range')).toBe('bytes */5') }) it('returns 416 for invalid "bytes=-" (both positions empty)', async () => { const res = await app.handle( req('/file', { headers: { range: 'bytes=-' } }) ) expect(res.status).toBe(416) expect(res.headers.get('content-range')).toBe('bytes */5') }) it('ignores subsequent ranges in multi-range requests, uses first range only', async () => { // Multi-range (e.g. bytes=0-1,3-4) is not supported; only the first range is applied. const res = await app.handle( req('/file', { headers: { range: 'bytes=0-1,3-4' } }) ) expect(res.status).toBe(206) expect(res.headers.get('content-range')).toBe('bytes 0-1/5') expect(await res.text()).toBe('12') }) }) ================================================ FILE: test/response/redirect.test.ts ================================================ import { Elysia } from '../../src' import { describe, expect, it } from 'bun:test' import { req } from '../utils' describe('Response Redirect', () => { it('handle redirect', async () => { const app = new Elysia().get('/', ({ redirect }) => redirect('/skadi')) const { headers, status } = await app.handle(req('/')) expect(status).toBe(302) expect(headers.toJSON()).toEqual({ location: '/skadi' }) }) it('handle redirect status', async () => { const app = new Elysia().get('/', ({ redirect }) => redirect('/skadi', 301) ) const { headers, status } = await app.handle(req('/')) expect(status).toBe(301) expect(headers.toJSON()).toEqual({ location: '/skadi' }) }) it('add set.headers to redirect', async () => { const app = new Elysia().get('/', ({ redirect, set }) => { set.headers.alias = 'Abyssal Hunter' return redirect('/skadi') }) const { headers, status } = await app.handle(req('/')) expect(status).toBe(302) expect(headers.toJSON()).toEqual({ location: '/skadi', alias: 'Abyssal Hunter' }) }) it('set multiple cookie on redirect', async () => { const app = new Elysia().get( '/', ({ cookie: { name, name2 }, redirect }) => { name.value = 'a' name2.value = 'b' return redirect('/skadi') } ) const { headers, status } = await app.handle(req('/')) expect(status).toBe(302) // @ts-expect-error expect(headers.toJSON()).toEqual({ location: '/skadi', 'set-cookie': ['name=a; Path=/', 'name2=b; Path=/'] }) }) }) ================================================ FILE: test/response/sse-double-wrap.test.ts ================================================ import { describe, it, expect } from 'bun:test' import { Elysia } from '../../src' describe('SSE - Response Double Wrapping', () => { it('should not double-wrap SSE data when returning pre-formatted Response', async () => { const app = new Elysia().get('/', ({ set }) => { set.headers.hello = 'world' return new Response('data: hello\n\ndata: world\n\n', { headers: { 'content-type': 'text/event-stream', 'transfer-encoding': 'chunked' }, status: 200 }) }) const response = await app.handle(new Request('http://localhost/')).then(r => r.text()) // Should NOT double-wrap with "data: data:" expect(response).toBe('data: hello\n\ndata: world\n\n') expect(response).not.toContain('data: data:') }) it('should not double-wrap SSE when using set.headers with pre-formatted content', async () => { const app = new Elysia().get('/', ({ set }) => { set.headers['x-custom'] = 'test' set.headers['content-type'] = 'text/event-stream' return new Response('data: message1\n\ndata: message2\n\n', { headers: { 'transfer-encoding': 'chunked' } }) }) const response = await app.handle(new Request('http://localhost/')).then(r => r.text()) expect(response).toBe('data: message1\n\ndata: message2\n\n') expect(response).not.toContain('data: data:') }) it('should properly format SSE for generator functions', async () => { const app = new Elysia().get('/', function* () { yield 'hello' yield 'world' }) const response = await app .handle(new Request('http://localhost/')) .then((r) => r.text()) // Generator without explicit SSE should format as plain text expect(response).toContain('hello') expect(response).toContain('world') // Verify it's NOT SSE formatted expect(response).not.toContain('data: hello') expect(response).not.toContain('data: world') }) it('should format SSE correctly for generators with explicit SSE configuration', async () => { const { sse } = await import('../../src') const app = new Elysia().get('/', ({ set }) => { set.headers['content-type'] = 'text/event-stream' return (async function* () { yield sse({ data: 'first message' }) yield sse({ data: 'second message' }) })() }) const response = await app .handle(new Request('http://localhost/')) .then((r) => r.text()) // Generator WITH explicit SSE markers should get properly formatted expect(response).toContain('data: first message\n\n') expect(response).toContain('data: second message\n\n') // Should NOT double-wrap expect(response).not.toContain('data: data:') }) }) ================================================ FILE: test/response/static.test.ts ================================================ import { describe, expect, it } from 'bun:test' import { Elysia } from '../../src' import { req } from '../utils' describe('Static Content', () => { it('work', async () => { const app = new Elysia().get('/', 'Static Content') const response = await app.handle(req('/')).then((x) => x.text()) expect(response).toBe('Static Content') }) it('handle onRequest', async () => { const app = new Elysia() .onRequest(() => 'request') .get('/', 'Static Content') const response = await app.handle(req('/')).then((x) => x.text()) expect(response).toBe('request') }) it('inline life-cycle', async () => { const app = new Elysia().get('/', 'Static Content', { beforeHandle() { return 'beforeHandle' } }) const response = await app.handle(req('/')).then((x) => x.text()) expect(response).toBe('beforeHandle') }) it('mutate context', async () => { const app = new Elysia().get('/', 'Static Content', { beforeHandle({ set }) { set.headers['X-Powered-By'] = 'Elysia' } }) const headers = await app.handle(req('/')).then((x) => x.headers) expect(headers.get('X-Powered-By')).toBe('Elysia') }) it('set default header', async () => { const app = new Elysia() .headers({ 'X-Powered-By': 'Elysia' }) .get('/', 'Static Content') const headers = await app.handle(req('/')).then((x) => x.headers) expect(headers.get('X-Powered-By')).toBe('Elysia') }) it('handle errror after routing', async () => { const app = new Elysia().get('/', 'Static Content', { beforeHandle() { throw new Error('error') }, error() { return 'handled' } }) const response = await app.handle(req('/')).then((x) => x.text()) expect(response).toBe('handled') }) it('handle errror after routing', async () => { const app = new Elysia() .onError(() => 'handled') .onRequest(() => { throw new Error('error') }) .get('/', 'Static Content') const response = await app.handle(req('/')).then((x) => x.text()) expect(response).toBe('handled') }) it('clone content', async () => { const app = new Elysia().get('/', 'Static Content', { beforeHandle({ set }) { set.headers['X-Powered-By'] = 'Elysia' } }) await app.handle(req('/')) await app.handle(req('/')) const headers = await app.handle(req('/')).then((x) => x.headers) expect(headers.get('X-Powered-By')).toBe('Elysia') }) }) ================================================ FILE: test/response/stream.test.ts ================================================ import { describe, expect, it } from 'bun:test' import { req } from '../utils' import { Elysia, sse } from '../../src' import { streamResponse } from '../../src/adapter/utils' import { randomId } from '../../src/utils' describe('Stream', () => { it('handle stream', async () => { const expected = ['a', 'b', 'c'] const app = new Elysia().get('/', async function* () { yield 'a' await Bun.sleep(10) yield 'b' await Bun.sleep(10) yield 'c' }) const response = await app .handle(req('/')) .then((x) => x.body) .then((x) => { if (!x) return const reader = x?.getReader() let acc = '' const { promise, resolve } = Promise.withResolvers() reader.read().then(function pump({ done, value }): unknown { if (done) return resolve(acc) expect(value.toString()).toBe(expected.shift()!) acc += value.toString() return reader.read().then(pump) }) return promise }) expect(expected).toHaveLength(0) expect(response).toBe('abc') }) it('stop stream on canceled request', async () => { const expected = ['a', 'b'] const app = new Elysia().get('/', async function* () { yield 'a' await Bun.sleep(10) yield 'b' await Bun.sleep(10) yield 'c' }) const controller = new AbortController() setTimeout(() => { controller.abort() }, 15) const response = await app .handle( new Request('http://e.ly', { signal: controller.signal }) ) .then((x) => x.body) .then((x) => { if (!x) return const reader = x?.getReader() let acc = '' const { promise, resolve } = Promise.withResolvers() reader.read().then(function pump({ done, value }): unknown { if (done) return resolve(acc) expect(value.toString()).toBe(expected.shift()!) acc += value.toString() return reader.read().then(pump) }) return promise }) expect(expected).toHaveLength(0) expect(response).toBe('ab') }) it('include multiple set-cookie headers in streamed response', async () => { const app = new Elysia().get('/', async function* (context) { context.cookie['cookie1'].set({ value: 'value1' }) context.cookie['cookie2'].set({ value: 'value2' }) yield sse({ event: 'test', data: { count: 1 } }) yield sse({ event: 'test', data: { count: 2 } }) }) const response = await app.handle(req('/')) const cookieHeaders = response.headers.getSetCookie() expect(cookieHeaders).toHaveLength(2) expect(cookieHeaders.some((h) => h.includes('cookie1=value1'))).toBe( true ) expect(cookieHeaders.some((h) => h.includes('cookie2=value2'))).toBe( true ) }) it('mutate set before yield is called', async () => { const expected = ['a', 'b', 'c'] const app = new Elysia().get('/', function* ({ set }) { set.headers['access-control-allow-origin'] = 'http://saltyaom.com' yield 'a' yield 'b' yield 'c' }) const response = await app.handle(req('/')).then((x) => x.headers) expect(response.get('access-control-allow-origin')).toBe( 'http://saltyaom.com' ) }) it('mutate set before yield is called', async () => { const expected = ['a', 'b', 'c'] const app = new Elysia().get('/', function* ({ set }) { set.headers['access-control-allow-origin'] = 'http://saltyaom.com' yield 'a' yield 'b' yield 'c' }) const response = await app.handle(req('/')).then((x) => x.headers) expect(response.get('access-control-allow-origin')).toBe( 'http://saltyaom.com' ) }) it('async mutate set before yield is called', async () => { const expected = ['a', 'b', 'c'] const app = new Elysia().get('/', async function* ({ set }) { set.headers['access-control-allow-origin'] = 'http://saltyaom.com' yield 'a' yield 'b' yield 'c' }) const response = await app.handle(req('/')).then((x) => x.headers) expect(response.get('access-control-allow-origin')).toBe( 'http://saltyaom.com' ) }) it('return value if not yield', async () => { const app = new Elysia() .get('/', function* ({ set }) { return 'hello' }) .get('/json', function* ({ set }) { return { hello: 'world' } }) const response = await Promise.all([ app.handle(req('/')), app.handle(req('/json')) ]) expect(await response[0].text()).toBe('hello') expect(await response[1].json()).toEqual({ hello: 'world' }) }) it('return async value if not yield', async () => { const app = new Elysia() .get('/', function* ({ set }) { return 'hello' }) .get('/json', function* ({ set }) { return { hello: 'world' } }) const response = await Promise.all([ app.handle(req('/')), app.handle(req('/json')) ]) expect(await response[0].text()).toBe('hello') expect(await response[1].json()).toEqual({ hello: 'world' }) }) it('handle object and array', async () => { const expected = [{ a: 'b' }, ['a'], ['a', 1, { a: 'b' }]] const expectedResponse = JSON.stringify([...expected]) let i = 0 const app = new Elysia().get('/', async function* () { yield expected[0] await Bun.sleep(10) yield expected[1] await Bun.sleep(10) yield expected[2] }) app.handle(req('/')) .then((x) => x.body) .then((x) => { if (!x) return const reader = x?.getReader() const { promise, resolve } = Promise.withResolvers() reader.read().then(function pump({ done, value }): unknown { if (done) return resolve() expect(value.toString()).toBe(JSON.stringify(expected[i++])) return reader.read().then(pump) }) return promise }) }) it('proxy fetch stream', async () => { const expected = ['a', 'b', 'c'] let i = 0 const app = new Elysia().get('/', async function* () { yield 'a' await Bun.sleep(10) yield 'b' await Bun.sleep(10) yield 'c' }) const proxy = new Elysia().get('/', () => app.handle(new Request('http://e.ly')) ) proxy .handle(req('/')) .then((x) => x.body) .then((x) => { if (!x) return const reader = x?.getReader() const { promise, resolve } = Promise.withResolvers() reader.read().then(function pump({ done, value }): unknown { if (done) return resolve() expect(value.toString()).toBe(expected[i++]) return reader.read().then(pump) }) return promise }) }) it('handle sse with id', () => { const app = new Elysia().get('/sse', async function* () { for (let i = 0; i < 3; i++) { yield sse({ id: randomId(), data: `message ${i}` }) await Bun.sleep(10) } }) return app .handle(req('/sse')) .then((x) => x.body) .then((x) => { if (!x) return const reader = x?.getReader() let acc = '' const { promise, resolve } = Promise.withResolvers() reader.read().then(function pump({ done, value }): unknown { if (done) return resolve(acc) acc += value.toString() return reader.read().then(pump) }) return promise }) .then((response) => { if (typeof response !== 'string') return void expect(response).toBeTypeOf('string') response .split('\n\n') .filter(Boolean) .forEach((x, i) => { expect(x).toInclude(`data: message ${i}`) expect(x).toInclude('id: ') }) }) }) it('handle sse with event, retry and custom id', () => { const app = new Elysia().get('/sse', async function* () { for (let i = 0; i < 3; i++) { yield sse({ data: `message ${i}`, event: 'message', retry: 1000, id: i }) await Bun.sleep(10) } }) return app .handle(req('/sse')) .then((x) => x.body) .then((x) => { if (!x) return const reader = x?.getReader() let acc = '' const { promise, resolve } = Promise.withResolvers() reader.read().then(function pump({ done, value }): unknown { if (done) return resolve(acc) acc += value.toString() return reader.read().then(pump) }) return promise }) .then((response) => { if (typeof response !== 'string') return void expect(response).toBeTypeOf('string') response .split('\n\n') .filter(Boolean) .forEach((x, i) => { expect(x).toInclude(`data: message ${i}`) expect(x).toInclude('event: message') expect(x).toInclude('retry: 1000') expect(x).toInclude(`id: ${i}`) }) }) }) it('handle sse without id', () => { const app = new Elysia().get('/sse', async function* () { for (let i = 0; i < 3; i++) { yield sse({ id: null, data: `message ${i}` }) await Bun.sleep(10) } }) return app .handle(req('/sse')) .then((x) => x.body) .then((x) => { if (!x) return const reader = x?.getReader() let acc = '' const { promise, resolve } = Promise.withResolvers() reader.read().then(function pump({ done, value }): unknown { if (done) return resolve(acc) acc += value.toString() return reader.read().then(pump) }) return promise }) .then((response) => { expect(response).toBe( 'data: message 0\n\ndata: message 1\n\ndata: message 2\n\n' ) }) }) it('handle sse short-form', () => { const app = new Elysia().get('/sse', async function* () { for (let i = 0; i < 3; i++) { yield sse(`message ${i}`) await Bun.sleep(10) } }) return app .handle(req('/sse')) .then((x) => x.body) .then((x) => { if (!x) return const reader = x?.getReader() let acc = '' const { promise, resolve } = Promise.withResolvers() reader.read().then(function pump({ done, value }): unknown { if (done) return resolve(acc) acc += value.toString() return reader.read().then(pump) }) return promise }) .then((response) => { if (typeof response !== 'string') return void expect(response).toBeTypeOf('string') response .split('\n\n') .filter(Boolean) .forEach((x, i) => { expect(x).toInclude(`data: message ${i}`) }) }) }) it('stream ReadableStream', async () => { const app = new Elysia().get('/', function () { return new ReadableStream({ async start(controller) { controller.enqueue('Elysia') await Bun.sleep(1) controller.enqueue('Eden') await Bun.sleep(1) controller.close() } }) }) const response = await app.handle(req('/')) const result = [] for await (const a of streamResponse(response)) result.push(a) expect(result).toEqual(['Elysia', 'Eden']) }) it('stream ReadableStream return from generator function', async () => { const app = new Elysia().get('/', function* () { return new ReadableStream({ async start(controller) { controller.enqueue('Elysia') await Bun.sleep(1) controller.enqueue('Eden') await Bun.sleep(1) controller.close() } }) }) const response = await app.handle(req('/')) const result = [] for await (const a of streamResponse(response)) result.push(a) expect(result).toEqual(['Elysia', 'Eden']) }) it('stream ReadableStream return from async generator function', async () => { const app = new Elysia().get('/', async function* () { return new ReadableStream({ async start(controller) { controller.enqueue('Elysia') await Bun.sleep(1) controller.enqueue('Eden') await Bun.sleep(1) controller.close() } }) }) const response = await app.handle(req('/')) const result = [] for await (const a of streamResponse(response)) result.push(a) expect(result).toEqual(['Elysia', 'Eden']) }) it('stream ReadableStream with sse', async () => { const app = new Elysia().get('/', async function* () { return sse( new ReadableStream({ async start(controller) { controller.enqueue('Elysia') await Bun.sleep(1) controller.enqueue('Eden') await Bun.sleep(1) controller.close() } }) ) }) const response = await app.handle(req('/')) const result = [] for await (const a of streamResponse(response)) result.push(a) expect(result).toEqual(['Elysia', 'Eden'].map((x) => `data: ${x}\n\n`)) expect(response.headers.get('content-type')).toBe('text/event-stream') }) // Issue #1677: Throwing from AsyncGenerator should preserve headers it('should preserve headers when throwing from async generator', async () => { const { status: statusFn } = await import('../../src') const app = new Elysia().get('/', async function* ({ set }) { set.headers['access-control-allow-origin'] = '*' set.headers['x-custom-header'] = 'test-value' // Throw before yielding - this is the bug scenario from #1677 if (true) throw statusFn(500) yield 'unreachable' }) const response = await app.handle(req('/')) expect(response.status).toBe(500) expect(response.headers.get('access-control-allow-origin')).toBe('*') expect(response.headers.get('x-custom-header')).toBe('test-value') }) // Issue #1677: onError hook should be called when throwing from generator it('should call onError hook when throwing from async generator', async () => { const { status: statusFn } = await import('../../src') let onErrorCalled = false let errorCode: string | number | undefined const app = new Elysia() .onError(({ code }) => { onErrorCalled = true errorCode = code }) .get('/', async function* ({ set }) { set.headers['x-custom-header'] = 'test-value' // Throw before yielding - this is the bug scenario from #1677 if (true) throw statusFn(500) yield 'unreachable' }) const response = await app.handle(req('/')) expect(response.status).toBe(500) expect(onErrorCalled).toBe(true) expect(errorCode).toBe(500) expect(response.headers.get('x-custom-header')).toBe('test-value') }) it('handle sse with plugin global hooks and trace', async () => { const PluginA = () => new Elysia({ name: 'PluginA' }) .onBeforeHandle(() => {}) .onAfterHandle(() => {}) .onParse(() => {}) .onTransform(() => {}) .onError(() => {}) .onAfterResponse(() => {}) .onStart(() => {}) .onStop(() => {}) .onRequest(() => {}) .trace(() => {}) .as('global') const app = new Elysia().use(PluginA()).get('/sse', async function* () { yield sse({ event: 'message', data: { meow: '1' } }) yield sse({ event: 'message', data: { meow: '2' } }) yield sse({ event: 'message', data: { meow: '3' } }) }) const response = await app.handle(req('/sse')) expect(response.headers.get('content-type')).toBe('text/event-stream') const result = [] for await (const chunk of streamResponse(response)) result.push(chunk) expect(result).toHaveLength(3) expect(result).toEqual([ 'event: message\ndata: {"meow":"1"}\n\n', 'event: message\ndata: {"meow":"2"}\n\n', 'event: message\ndata: {"meow":"3"}\n\n' ]) }) // Regression: proxying a large upstream SSE stream caused OOM (#1801). // The upstream generator must not be drained ahead of the slow consumer. it('does not buffer unboundedly when proxying a slow-consuming SSE stream', async () => { const TOTAL = 50 let produced = 0 const upstream = new Elysia().get('/', async function* () { for (let i = 0; i < TOTAL; i++) { produced++ yield sse(`message ${i}`) } }) // Simulate the OOM scenario: one Elysia instance re-streams another's // SSE response while the consumer reads slowly. const proxy = new Elysia().get('/', () => upstream.handle(new Request('http://e.ly')) ) const response = await proxy.handle(req('/')) const reader = response.body!.getReader() // Slow consumer: read 3 chunks with pauses between each await reader.read() await Bun.sleep(20) await reader.read() await Bun.sleep(20) await reader.read() // The upstream generator must not have raced ahead and produced all // TOTAL chunks. A small prefetch buffer is fine, but nowhere near 50. expect(produced).toBeLessThan(TOTAL) reader.cancel() }) // Regression test for https://github.com/elysiajs/elysia/issues/1801 // The generator must not be drained ahead of the consumer (backpressure). it('does not eagerly drain generator ahead of consumer', async () => { let nextCallCount = 0 async function* lazyGenerator() { for (let i = 0; i < 10; i++) { nextCallCount++ yield String(i) } } const app = new Elysia().get('/', lazyGenerator) const response = await app.handle(req('/')) const reader = response.body!.getReader() // Read only the first 3 chunks await reader.read() await reader.read() await reader.read() // With pull()-based backpressure the generator should not have // been advanced far beyond what was consumed. Allow a small buffer // (ReadableStream may prefetch one extra chunk via pull()) but it // must not have drained all 10. expect(nextCallCount).toBeLessThan(10) reader.cancel() }) it('stream ReadableStream binary chunks', async () => { const payload = new Uint8Array(128 * 1024) for (let i = 0; i < payload.length; i++) payload[i] = i % 251 const app = new Elysia().get( '/', () => new ReadableStream({ start(controller) { controller.enqueue(payload.subarray(0, 32768)) controller.enqueue(payload.subarray(32768, 65536)) controller.enqueue(payload.subarray(65536, 98304)) controller.enqueue(payload.subarray(98304)) controller.close() } }) ) const response = await app.handle(req('/')) const result = new Uint8Array(await response.arrayBuffer()) expect(result.byteLength).toBe(payload.byteLength) expect(result).toEqual(payload) }) it('stream ReadableStream binary views and blob chunks', async () => { const source = new Uint8Array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]) const expected = new Uint8Array([6, 7, 8, 9, 2, 3, 4, 5, 10, 11, 12]) const app = new Elysia().get( '/', () => new ReadableStream({ start(controller) { controller.enqueue(source.subarray(6, 10)) controller.enqueue(new DataView(source.buffer, 2, 4)) controller.enqueue( new Blob([new Uint8Array([10, 11, 12])]) ) controller.close() } }) ) const response = await app.handle(req('/')) const result = new Uint8Array(await response.arrayBuffer()) expect(result).toEqual(expected) }) it('stream generator Uint8Array chunks as binary', async () => { const app = new Elysia().get('/', async function* () { yield new Uint8Array([1, 2]) await Bun.sleep(1) yield new Uint8Array([3, 4]) }) const response = await app.handle(req('/')) const result = new Uint8Array(await response.arrayBuffer()) // expect(result).toEqual(result.toBase64()) expect(result).toEqual(new Uint8Array([1, 2, 3, 4])) }) }) ================================================ FILE: test/schema/schema-utils.test.ts ================================================ import { describe, it, expect } from 'bun:test' import { t } from '../../src' import { hasProperty, getSchemaProperties } from '../../src/schema' describe('getSchemaProperties', () => { it('returns properties for Object schema', () => { const schema = t.Object({ name: t.String(), age: t.Number() }) const props = getSchemaProperties(schema) expect(props).toBeDefined() expect(Object.keys(props!)).toEqual(['name', 'age']) }) it('returns undefined for non-object schema', () => { expect(getSchemaProperties(t.String())).toBeUndefined() expect(getSchemaProperties(t.Number())).toBeUndefined() expect(getSchemaProperties(t.Array(t.String()))).toBeUndefined() }) it('returns undefined for undefined/null', () => { expect(getSchemaProperties(undefined)).toBeUndefined() expect(getSchemaProperties(null as any)).toBeUndefined() }) it('returns combined properties for Union schema', () => { const schema = t.Union([ t.Object({ name: t.String() }), t.Object({ age: t.Number() }) ]) const props = getSchemaProperties(schema) expect(props).toBeDefined() expect(Object.keys(props!).sort()).toEqual(['age', 'name']) }) it('returns combined properties for Intersect schema', () => { const schema = t.Intersect([ t.Object({ name: t.String() }), t.Object({ age: t.Number() }) ]) const props = getSchemaProperties(schema) expect(props).toBeDefined() expect(Object.keys(props!).sort()).toEqual(['age', 'name']) }) it('returns Object properties when property value is Union', () => { const schema = t.Object({ data: t.Union([t.String(), t.Number()]) }) const props = getSchemaProperties(schema) expect(props).toBeDefined() expect(Object.keys(props!)).toEqual(['data']) }) it('handles nested Union/Intersect', () => { const schema = t.Union([ t.Intersect([ t.Object({ a: t.String() }), t.Object({ b: t.Number() }) ]), t.Object({ c: t.Boolean() }) ]) const props = getSchemaProperties(schema) expect(props).toBeDefined() expect(Object.keys(props!).sort()).toEqual(['a', 'b', 'c']) }) it('handles nested Intersect/Union', () => { const schema = t.Intersect([ t.Union([t.Object({ a: t.String() }), t.Object({ b: t.Number() })]), t.Object({ c: t.Boolean() }) ]) const props = getSchemaProperties(schema) expect(props).toBeDefined() expect(Object.keys(props!).sort()).toEqual(['a', 'b', 'c']) }) it('returns undefined for empty Union or Intersect', () => { expect(getSchemaProperties({ anyOf: [] } as any)).toBeUndefined() expect(getSchemaProperties({ allOf: [] } as any)).toBeUndefined() }) it('handles Union with non-object members', () => { const schema = t.Union([t.Object({ name: t.String() }), t.String()]) const props = getSchemaProperties(schema) expect(props).toBeDefined() expect(Object.keys(props!)).toEqual(['name']) }) }) describe('hasProperty', () => { it('finds property in Object schema', () => { const schema = t.Object({ name: t.String({ default: 'test' }) }) expect(hasProperty('default', schema)).toBe(true) expect(hasProperty('minimum', schema)).toBe(false) }) it('finds property in Union schema', () => { const schema = t.Union([ t.Object({ name: t.String({ default: 'test' }) }), t.Object({ name: t.String() }) ]) expect(hasProperty('default', schema)).toBe(true) }) it('finds property in Intersect schema', () => { const schema = t.Intersect([ t.Object({ name: t.String({ default: 'test' }) }), t.Object({ age: t.Number() }) ]) expect(hasProperty('default', schema)).toBe(true) }) it('returns false when property not in any Union member', () => { const schema = t.Union([ t.Object({ name: t.String() }), t.Object({ age: t.Number() }) ]) expect(hasProperty('default', schema)).toBe(false) }) it('finds property in nested Union within Object', () => { const schema = t.Object({ data: t.Union([t.String({ default: 'hello' }), t.Number()]) }) expect(hasProperty('default', schema)).toBe(true) }) it('finds property in oneOf schema', () => { const schema = { oneOf: [ t.Object({ name: t.String({ default: 'test' }) }), t.Object({ name: t.String() }) ] } expect(hasProperty('default', schema as any)).toBe(true) }) it('returns false for undefined schema', () => { expect(hasProperty('default', undefined as any)).toBe(false) }) it('handles deeply nested structures', () => { const schema = t.Union([ t.Intersect([ t.Object({ config: t.Object({ value: t.String({ default: 'nested' }) }) }) ]) ]) expect(hasProperty('default', schema)).toBe(true) }) }) ================================================ FILE: test/standard-schema/reference.test.ts ================================================ import { Elysia } from '../../src' import { describe, it, expect } from 'bun:test' import { z } from 'zod' import { post, req } from '../utils' describe('Standard Schema Validate', () => { it('validate body', async () => { const app = new Elysia() .model({ body: z.object({ id: z.number() }) }) .post('/', ({ body }) => body, { body: 'body' }) const value = await app .handle( post('/', { id: 1 }) ) .then((x) => x.json()) expect(value).toEqual({ id: 1 }) const invalid = await app.handle( post('/', { id: '1' }) ) expect(invalid.status).toBe(422) }) it('validate query', async () => { const app = new Elysia() .model({ query: z.object({ id: z.coerce.number() }) }) .get('/', ({ query }) => query, { query: 'query' }) const value = await app.handle(req('/?id=1')).then((x) => x.json()) expect(value).toEqual({ id: 1 }) const invalid = await app.handle(req('/?id=a')) expect(invalid.status).toBe(422) }) it('validate params', async () => { const app = new Elysia() .model({ params: z.object({ id: z.coerce.number() }) }) .get('/user/:id', ({ params }) => params, { params: 'params' }) const value = await app.handle(req('/user/1')).then((x) => x.json()) expect(value).toEqual({ id: 1 }) const invalid = await app.handle(req('/user/a')) expect(invalid.status).toBe(422) }) it('validate headers', async () => { const app = new Elysia() .model({ headers: z.object({ id: z.coerce.number() }) }) .get('/', ({ headers }) => headers, { headers: 'headers' }) const value = await app .handle( req('/', { headers: { id: '1' } }) ) .then((x) => x.json()) expect(value).toEqual({ id: 1 }) const invalid = await app.handle(req('/', {})) expect(invalid.status).toBe(422) }) it('validate single response', async () => { const app = new Elysia() .model({ response: z.boolean() }) .get( '/:name', // @ts-expect-error ({ params: { name } }) => name === 'lilith' ? undefined : true, { response: 'response' } ) const exists = await app.handle(req('/fouco')) const nonExists = await app.handle(req('/lilith')) expect(exists.status).toBe(200) expect(nonExists.status).toBe(422) }) it('validate multiple response', async () => { const app = new Elysia() .model({ 'response.404': z.literal('lilith'), 'response.418': z.literal('fouco') }) .get( '/:name', ({ params: { name }, status }) => name === 'lilith' ? status(404, 'lilith') : status(418, name as any), { response: { 404: 'response.404', 418: 'response.418' } } ) const exists = await app.handle(req('/fouco')) const nonExists = await app.handle(req('/lilith')) expect(exists.status).toBe(418) expect(nonExists.status).toBe(404) const invalid = await app.handle(req('/unknown')) expect(invalid.status).toBe(422) }) it('validate multiple schema together', async () => { const app = new Elysia() .model({ body: z.object({ id: z.number() }), query: z.object({ limit: z.coerce.number() }), params: z.object({ name: z.literal('fouco').or(z.literal('lilith')) }), 'response.404': z.literal('lilith'), 'response.418': z.literal('fouco') }) .post( '/:name', ({ params: { name }, status }) => name === 'lilith' ? status(404, 'lilith') : status(418, name as any), { body: 'body', query: 'query', params: 'params', response: { 404: 'response.404', 418: 'response.418' } } ) const responses = await Promise.all( [ post('/lilith?limit=1', { id: 1 }), post('/fouco?limit=10', { id: 2 }), post('/unknown?limit=10', { id: 2 }), post('/fouco?limit=a', { id: 2 }), post('/fouco?limit=10', { id: '2' }), post('/fouco', {}) ].map((x) => app.handle(x).then((x) => x.status)) ) expect(responses[0]).toEqual(404) expect(responses[1]).toEqual(418) expect(responses[2]).toEqual(422) expect(responses[3]).toEqual(422) expect(responses[4]).toEqual(422) expect(responses[5]).toEqual(422) }) it('merge guard', async () => { const app = new Elysia() .model({ body: z.object({ id: z.number() }), query: z.object({ limit: z.coerce.number() }), params: z.object({ name: z.literal('fouco').or(z.literal('lilith')) }), 'response.404': z.literal('lilith'), 'response.418': z.literal('fouco') }) .guard({ body: 'body', query: 'query', response: { 404: 'response.404' } }) .post( '/:name', ({ params: { name }, status }) => name === 'lilith' ? status(404, 'lilith') : status(418, name as any), { params: 'params', response: { 418: 'response.418' } } ) const responses = await Promise.all( [ post('/lilith?limit=1', { id: 1 }), post('/fouco?limit=10', { id: 2 }), post('/unknown?limit=10', { id: 2 }), post('/fouco?limit=a', { id: 2 }), post('/fouco?limit=10', { id: '2' }), post('/fouco', {}) ].map((x) => app.handle(x).then((x) => x.status)) ) expect(responses[0]).toEqual(404) expect(responses[1]).toEqual(418) expect(responses[2]).toEqual(422) expect(responses[3]).toEqual(422) expect(responses[4]).toEqual(422) expect(responses[5]).toEqual(422) }) it('merge plugin', async () => { const plugin = new Elysia() .model({ body: z.object({ id: z.number() }), query: z.object({ limit: z.coerce.number() }), params: z.object({ name: z.literal('fouco').or(z.literal('lilith')) }), 'response.404': z.literal('lilith'), 'response.418': z.literal('fouco') }) .guard({ as: 'scoped', body: 'body', query: 'query', response: { 404: 'response.404' } }) const app = new Elysia() .use(plugin) .post( '/:name', ({ params: { name }, status }) => name === 'lilith' ? status(404, 'lilith') : status(418, name as any), { params: z.object({ name: z.literal('fouco').or(z.literal('lilith')) }), response: { 418: 'response.418' } } ) const responses = await Promise.all( [ post('/lilith?limit=1', { id: 1 }), post('/fouco?limit=10', { id: 2 }), post('/unknown?limit=10', { id: 2 }), post('/fouco?limit=a', { id: 2 }), post('/fouco?limit=10', { id: '2' }), post('/fouco', {}) ].map((x) => app.handle(x).then((x) => x.status)) ) expect(responses[0]).toEqual(404) expect(responses[1]).toEqual(418) expect(responses[2]).toEqual(422) expect(responses[3]).toEqual(422) expect(responses[4]).toEqual(422) expect(responses[5]).toEqual(422) }) }) ================================================ FILE: test/standard-schema/standalone.test.ts ================================================ import { Elysia, t } from '../../src' import { describe, it, expect } from 'bun:test' import { z } from 'zod' import * as v from 'valibot' import { type } from 'arktype' import { post, req } from '../utils' describe('Standard Schema Standalone', () => { it('validate and normalize body', async () => { const app = new Elysia() .guard({ schema: 'standalone', body: z.object({ id: z.number() }) }) .post('/', ({ body }) => body, { body: t.Object({ name: t.Literal('lilith') }) }) const value = await app .handle( post('/', { id: 1, name: 'lilith', extra: false }) ) .then((x) => x.json()) expect(value).toEqual({ id: 1, name: 'lilith' }) const invalid = await app.handle( post('/', { id: '1', name: 'fouco', extra: false }) ) expect(invalid.status).toBe(422) }) it('validate query', async () => { const app = new Elysia() .guard({ schema: 'standalone', query: z.object({ id: z.coerce.number() }) }) .get('/', ({ query }) => query, { query: t.Object({ name: t.Literal('lilith') }) }) const value = await app .handle(req('/?id=1&name=lilith&extra=true')) .then((x) => x.json()) expect(value).toEqual({ id: 1, name: 'lilith' }) const invalid = await app.handle(req('/?id=a&name=fouco')) expect(invalid.status).toBe(422) }) it('validate and normalize params', async () => { const app = new Elysia() .guard({ schema: 'standalone', params: z.object({ id: z.coerce.number() }) }) .get('/:name/:id', ({ params }) => params, { params: t.Object({ name: t.Literal('lilith') }) }) const value = await app.handle(req('/lilith/1')).then((x) => x.json()) expect(value).toEqual({ id: 1, name: 'lilith' }) const invalid = await app.handle(req('/user/a?name=fouco')) expect(invalid.status).toBe(422) }) it('validate and normalize headers', async () => { const app = new Elysia() .guard({ schema: 'standalone', headers: z.object({ id: z.coerce.number() }) }) .get('/', ({ headers }) => headers, { headers: t.Object({ name: t.Literal('lilith') }) }) const value = await app .handle( req('/', { headers: { id: '1', name: 'lilith', extra: 'false' } }) ) .then((x) => x.json()) expect(value).toEqual({ id: 1, name: 'lilith' }) const invalid = await app.handle( req('/', { headers: { id: 'a', name: 'fouco' } }) ) expect(invalid.status).toBe(422) }) it('validate and normalize single response', async () => { const app = new Elysia() .guard({ schema: 'standalone', response: z.object({ id: z.number() }) }) .get( '/:name', // @ts-expect-error ({ params: { name } }) => ({ name, id: name !== 'lilith' ? undefined : 1, extra: false }), { response: t.Object({ name: t.Literal('lilith') }) } ) const valid = await app.handle(req('/lilith')).then((x) => x.json()) expect(valid).toEqual({ id: 1, name: 'lilith' }) const invalid = await app.handle(req('/focou')) expect(invalid.status).toBe(422) }) it('validate and normalize multiple response', async () => { const app = new Elysia() .guard({ schema: 'standalone', response: { 404: z.object({ id: z.number() }), 418: z.object({ id: z.number() }) } }) .get( '/:name', ({ params: { name }, status }) => name === 'lilith' ? status(404, { name, id: 1, extra: false }) : status(418, { // @ts-expect-error name, id: 2, extra: false }), { response: { 404: t.Object({ name: t.Literal('lilith') }), 418: t.Object({ name: t.Literal('fouco') }) } } ) const lilith = await app.handle(req('/lilith')).then((x) => x.json()) const fouco = await app.handle(req('/fouco')).then((x) => x.json()) expect(lilith).toEqual({ id: 1, name: 'lilith' }) expect(fouco).toEqual({ id: 2, name: 'fouco' }) const invalid = await app.handle(req('/unknown')) expect(invalid.status).toBe(422) }) it('validate multiple schema together', async () => { const app = new Elysia() .onError(({ error, code }) => { if (code !== 'VALIDATION') console.log(error) }) .guard({ schema: 'standalone', body: z.object({ name: z.string() }), query: z.object({ id: z.coerce.number() }), params: z.object({ id: z.coerce.number() }), response: { 404: z.object({ id: z.number() }), 418: z.object({ id: z.number() }) } }) .post( '/:name/:id', ({ params: { name, id }, status }) => name === 'lilith' ? status(404, { name, id, extra: true }) : status(418, { name, id, extra: true }), { body: t.Object({ id: t.Number() }), query: t.Object({ limit: t.Number() }), params: t.Object({ name: t.UnionEnum(['fouco', 'lilith']) }), response: { 404: z.object({ name: z.literal('lilith') }), 418: z.object({ name: z.literal('fouco') }) } } ) const responses = await Promise.all( [ post('/lilith/1?limit=1&id=1', { id: 1, name: 'lilith' }), post('/fouco/2?limit=10&id=2', { id: 2, name: 'fouco' }), post('/unknown/2?limit=10&id=2', { id: 2, name: 'fouco' }), post('/fouco/2?limit=a&id=2', { id: 2, name: 'fouco' }), post('/fouco/2?limit=10&id=2', { id: '2', name: 'fouco' }), post('/fouco/2', { id: 2, name: 'fouco' }), post('/fouco/a?limit=10&id=2', {}) ].map((x) => app.handle(x).then((x) => x.status)) ) expect(responses[0]).toEqual(404) expect(responses[1]).toEqual(418) expect(responses[2]).toEqual(422) expect(responses[3]).toEqual(422) expect(responses[4]).toEqual(422) expect(responses[5]).toEqual(422) expect(responses[6]).toEqual(422) }) it('merge plugin', async () => { const plugin = new Elysia().guard({ as: 'scoped', schema: 'standalone', response: { 404: z.object({ id: z.number() }), 418: z.object({ id: z.number() }) } }) const app = new Elysia().use(plugin).get( '/:name', ({ params: { name }, status }) => name === 'lilith' ? status(404, { name, id: 1, extra: false }) : status(418, { // @ts-expect-error name, id: 2, extra: false }), { response: { 404: t.Object({ name: t.Literal('lilith') }), 418: t.Object({ name: t.Literal('fouco') }) } } ) const lilith = await app.handle(req('/lilith')).then((x) => x.json()) const fouco = await app.handle(req('/fouco')).then((x) => x.json()) expect(lilith).toEqual({ id: 1, name: 'lilith' }) expect(fouco).toEqual({ id: 2, name: 'fouco' }) const invalid = await app.handle(req('/unknown')) expect(invalid.status).toBe(422) }) it('validate non-typebox schema', async () => { const app = new Elysia() .guard({ schema: 'standalone', body: z.object({ name: z.string() }), query: z.object({ id: z.coerce.number() }), params: z.object({ id: z.coerce.number() }), response: { 404: z.object({ id: z.number() }), 418: z.object({ id: z.number() }) } }) .post( '/:name/:id', ({ params: { name, id }, status }) => name === 'lilith' ? status(404, { name, id, extra: true }) : status(418, { name, id, extra: true }), { body: v.object({ id: v.number() }), query: v.object({ limit: v.pipe( v.string(), v.transform(Number), v.number() ) }), params: v.object({ name: v.union([v.literal('fouco'), v.literal('lilith')]) }), response: { 404: v.object({ name: v.literal('lilith') }), 418: v.object({ name: v.literal('fouco') }) } } ) const responses = await Promise.all( [ post('/lilith/1?limit=1&id=1', { id: 1, name: 'lilith' }), post('/fouco/2?limit=10&id=2', { id: 2, name: 'fouco' }), post('/unknown/2?limit=10&id=2', { id: 2, name: 'fouco' }), post('/fouco/2?limit=a&id=2', { id: 2, name: 'fouco' }), post('/fouco/2?limit=10&id=2', { id: '2', name: 'fouco' }), post('/fouco/2', { id: 2, name: 'fouco' }), post('/fouco/a?limit=10&id=2', {}) ].map((x) => app.handle(x).then((x) => x.status)) ) expect(responses[0]).toEqual(404) expect(responses[1]).toEqual(418) expect(responses[2]).toEqual(422) expect(responses[3]).toEqual(422) expect(responses[4]).toEqual(422) expect(responses[5]).toEqual(422) expect(responses[6]).toEqual(422) }) it('validate 3 schema validators without TypeBox', async () => { const app = new Elysia() .guard({ schema: 'standalone', body: z.object({ name: z.string() }), query: z.object({ id: z.coerce.number() }), params: z.object({ id: z.coerce.number() }), response: { 404: z.object({ id: z.number() }), 418: z.object({ id: z.number() }) } }) .guard({ schema: 'standalone', body: type({ world: '"fantasy"' }), query: type({ world: '"fantasy"' }), response: { 404: type({ world: '"fantasy"' }), 418: type({ world: '"fantasy"' }) } }) .post( '/:name/:id', ({ params: { name, id }, status }) => name === 'lilith' ? status(404, { name, id, world: 'fantasy' }) : status(418, { name, id, world: 'fantasy' }), { body: v.object({ id: v.number() }), query: v.object({ limit: v.pipe( v.string(), v.transform(Number), v.number() ) }), params: v.object({ name: v.union([v.literal('fouco'), v.literal('lilith')]) }), response: { 404: v.object({ name: v.literal('lilith') }), 418: v.object({ name: v.literal('fouco') }) } } ) const responses = await Promise.all( [ post('/lilith/1?limit=1&id=1&world=fantasy', { id: 1, name: 'lilith', world: 'fantasy' }), post('/fouco/2?limit=10&id=2&world=fantasy', { id: 2, name: 'fouco', world: 'fantasy' }), post('/unknown/2?limit=10&id=2&world=fantasy', { id: 2, name: 'fouco', world: 'fantasy' }), post('/unknown/2?limit=10&id=2', { id: 2, name: 'fouco' }), post('/fouco/2?limit=a&id=2', { id: 2, name: 'fouco' }), post('/fouco/2?limit=10&id=2', { id: '2', name: 'fouco' }), post('/fouco/2', { id: 2, name: 'fouco' }), post('/fouco/a?limit=10&id=2', {}) ].map((x) => app.handle(x).then((x) => x.status)) ) expect(responses[0]).toEqual(404) expect(responses[1]).toEqual(418) expect(responses[2]).toEqual(422) expect(responses[3]).toEqual(422) expect(responses[4]).toEqual(422) expect(responses[5]).toEqual(422) expect(responses[6]).toEqual(422) expect(responses[7]).toEqual(422) }) }) ================================================ FILE: test/standard-schema/validate.test.ts ================================================ import { Elysia } from '../../src' import { describe, it, expect } from 'bun:test' import { z } from 'zod' import { post, req } from '../utils' describe('Standard Schema Validate', () => { it('validate body', async () => { const app = new Elysia().post('/', ({ body }) => body, { body: z.object({ id: z.number() }) }) const value = await app .handle( post('/', { id: 1 }) ) .then((x) => x.json()) expect(value).toEqual({ id: 1 }) const invalid = await app.handle( post('/', { id: '1' }) ) expect(invalid.status).toBe(422) }) it('validate query', async () => { const app = new Elysia().get('/', ({ query }) => query, { query: z.object({ id: z.coerce.number() }) }) const value = await app.handle(req('/?id=1')).then((x) => x.json()) expect(value).toEqual({ id: 1 }) const invalid = await app.handle(req('/?id=a')) expect(invalid.status).toBe(422) }) it('validate params', async () => { const app = new Elysia().get('/user/:id', ({ params }) => params, { params: z.object({ id: z.coerce.number() }) }) const value = await app.handle(req('/user/1')).then((x) => x.json()) expect(value).toEqual({ id: 1 }) const invalid = await app.handle(req('/user/a')) expect(invalid.status).toBe(422) }) it('validate headers', async () => { const app = new Elysia().get('/', ({ headers }) => headers, { headers: z.object({ id: z.coerce.number() }) }) const value = await app .handle( req('/', { headers: { id: '1' } }) ) .then((x) => x.json()) expect(value).toEqual({ id: 1 }) const invalid = await app.handle(req('/', {})) expect(invalid.status).toBe(422) }) it('validate single response', async () => { const app = new Elysia().get( '/:name', // @ts-expect-error ({ params: { name } }) => (name === 'lilith' ? undefined : true), { response: z.boolean() } ) const exists = await app.handle(req('/fouco')) const nonExists = await app.handle(req('/lilith')) expect(exists.status).toBe(200) expect(nonExists.status).toBe(422) }) it('validate multiple response', async () => { const app = new Elysia().get( '/:name', ({ params: { name }, status }) => name === 'lilith' ? status(404, 'lilith') : status(418, name as any), { response: { 404: z.literal('lilith'), 418: z.literal('fouco') } } ) const exists = await app.handle(req('/fouco')) const nonExists = await app.handle(req('/lilith')) expect(exists.status).toBe(418) expect(nonExists.status).toBe(404) const invalid = await app.handle(req('/unknown')) expect(invalid.status).toBe(422) }) it('validate multiple schema together', async () => { const app = new Elysia().post( '/:name', ({ params: { name }, status }) => name === 'lilith' ? status(404, 'lilith') : status(418, name as any), { body: z.object({ id: z.number() }), query: z.object({ limit: z.coerce.number() }), params: z.object({ name: z.literal('fouco').or(z.literal('lilith')) }), response: { 404: z.literal('lilith'), 418: z.literal('fouco') } } ) const responses = await Promise.all( [ post('/lilith?limit=1', { id: 1 }), post('/fouco?limit=10', { id: 2 }), post('/unknown?limit=10', { id: 2 }), post('/fouco?limit=a', { id: 2 }), post('/fouco?limit=10', { id: '2' }), post('/fouco', {}) ].map((x) => app.handle(x).then((x) => x.status)) ) expect(responses[0]).toEqual(404) expect(responses[1]).toEqual(418) expect(responses[2]).toEqual(422) expect(responses[3]).toEqual(422) expect(responses[4]).toEqual(422) expect(responses[5]).toEqual(422) }) it('merge guard', async () => { const app = new Elysia() .guard({ body: z.object({ id: z.number() }), query: z.object({ limit: z.coerce.number() }), response: { 404: z.literal('lilith') } }) .post( '/:name', ({ params: { name }, status }) => name === 'lilith' ? status(404, 'lilith') : status(418, name as any), { params: z.object({ name: z.literal('fouco').or(z.literal('lilith')) }), response: { 418: z.literal('fouco') } } ) const responses = await Promise.all( [ post('/lilith?limit=1', { id: 1 }), post('/fouco?limit=10', { id: 2 }), post('/unknown?limit=10', { id: 2 }), post('/fouco?limit=a', { id: 2 }), post('/fouco?limit=10', { id: '2' }), post('/fouco', {}) ].map((x) => app.handle(x).then((x) => x.status)) ) expect(responses[0]).toEqual(404) expect(responses[1]).toEqual(418) expect(responses[2]).toEqual(422) expect(responses[3]).toEqual(422) expect(responses[4]).toEqual(422) expect(responses[5]).toEqual(422) }) it('merge plugin', async () => { const plugin = new Elysia().guard({ as: 'scoped', body: z.object({ id: z.number() }), query: z.object({ limit: z.coerce.number() }), response: { 404: z.literal('lilith') } }) const app = new Elysia() .use(plugin) .post( '/:name', ({ params: { name }, status }) => name === 'lilith' ? status(404, 'lilith') : status(418, name as any), { params: z.object({ name: z.literal('fouco').or(z.literal('lilith')) }), response: { 418: z.literal('fouco') } } ) const responses = await Promise.all( [ post('/lilith?limit=1', { id: 1 }), post('/fouco?limit=10', { id: 2 }), post('/unknown?limit=10', { id: 2 }), post('/fouco?limit=a', { id: 2 }), post('/fouco?limit=10', { id: '2' }), post('/fouco', {}) ].map((x) => app.handle(x).then((x) => x.status)) ) expect(responses[0]).toEqual(404) expect(responses[1]).toEqual(418) expect(responses[2]).toEqual(422) expect(responses[3]).toEqual(422) expect(responses[4]).toEqual(422) expect(responses[5]).toEqual(422) }) it('handle cookie', async () => { const app = new Elysia().get( 'test', ({ cookie: { test } }) => typeof test.value, { cookie: z.object({ test: z.coerce.number() }) } ) const value = await app .handle( new Request('http://localhost:3000/test', { headers: { cookie: 'test=123' } }) ) .then((x) => x.text()) expect(value).toBe('number') }) }) ================================================ FILE: test/sucrose/bracket-pair-range-reverse.test.ts ================================================ import { describe, expect, it } from 'bun:test' import { bracketPairRangeReverse } from '../../src/sucrose' describe('bracket pair range reverse', () => { it('return the correct range when given a string with a single bracket pair', () => { const parameter = 'hello: { world: { a } }, elysia' const result = bracketPairRangeReverse(parameter) expect(result).toEqual([7, 23]) }) it('return the correct range when given a string with nested bracket pairs', () => { const parameter = 'hello: { world: { a } }, elysia' const result = bracketPairRangeReverse(parameter) expect(result).toEqual([7, 23]) }) it('return [-1, 0] when given a string without any bracket pairs', () => { const parameter = 'hello, elysia' const result = bracketPairRangeReverse(parameter) expect(result).toEqual([-1, 0]) }) it('return [0, 1] when given a string with a single opening bracket at the beginning', () => { const parameter = '{hello, elysia' const result = bracketPairRangeReverse(parameter) expect(result).toEqual([-1, 0]) }) it('return [parameter.length - 1, parameter.length] when given a string with a single closing bracket at the end', () => { const parameter = 'hello, elysia}' const result = bracketPairRangeReverse(parameter) expect(result).toEqual([-1, 0]) }) it('return [-1, 0] when given an empty string', () => { const parameter = '' const result = bracketPairRangeReverse(parameter) expect(result).toEqual([-1, 0]) }) it('return the correct range when given a string with multiple bracket pairs', () => { const parameter = 'hello: { world: { a } }, elysia' const result = bracketPairRangeReverse(parameter) expect(result).toEqual([7, 23]) }) it('return the correct range when given a string with an opening bracket but no closing bracket', () => { const parameter = 'hello: { world: { a }, elysia' const result = bracketPairRangeReverse(parameter) expect(result).toEqual([16, 21]) }) it('return the correct range when given a string with brackets inside quotes', () => { const parameter = 'hello: { world: { a } }, elysia' const result = bracketPairRangeReverse(parameter) expect(result).toEqual([7, 23]) }) it('return the correct range when given a string with nested bracket pairs', () => { const parameter = 'hello: { world: { a } }, elysia' const result = bracketPairRangeReverse(parameter) expect(result).toEqual([7, 23]) }) it('return the correct range when given a string with non-bracket characters', () => { const parameter = 'hello: { world: { a } }, elysia' const result = bracketPairRangeReverse(parameter) expect(result).toEqual([7, 23]) }) }) ================================================ FILE: test/sucrose/bracket-pair-range.test.ts ================================================ import { describe, expect, it } from 'bun:test' import { bracketPairRange } from '../../src/sucrose' describe('bracket pair range', () => { it('return the correct range when given a string with a single bracket pair', () => { const parameter = 'hello: { world: { a } }, elysia' const result = bracketPairRange(parameter) expect(result).toEqual([7, 23]) }) it('return the correct range when given a string with nested bracket pairs', () => { const parameter = 'hello: { world: { a } }, elysia' const result = bracketPairRange(parameter) expect(result).toEqual([7, 23]) }) it('return [-1, 0] when given a string without any bracket pairs', () => { const parameter = 'hello, elysia' const result = bracketPairRange(parameter) expect(result).toEqual([-1, 0]) }) it('return [0, 1] when given a string with a single opening bracket at the beginning', () => { const parameter = '{hello, elysia' const result = bracketPairRange(parameter) expect(result).toEqual([0, 14]) }) it('return [parameter.length - 1, parameter.length] when given a string with a single closing bracket at the end', () => { const parameter = 'hello, elysia}' const result = bracketPairRange(parameter) expect(result).toEqual([-1, 0]) }) it('return [-1, 0] when given an empty string', () => { const parameter = '' const result = bracketPairRange(parameter) expect(result).toEqual([-1, 0]) }) it('return the correct range when given a string with multiple bracket pairs', () => { const parameter = 'hello: { world: { a } }, elysia' const result = bracketPairRange(parameter) expect(result).toEqual([7, 23]) }) it('return the correct range when given a string with an opening bracket but no closing bracket', () => { const parameter = 'hello: { world: { a }, elysia' const result = bracketPairRange(parameter) expect(result).toEqual([0, parameter.length]) }) it('return the correct range when given a string with brackets inside quotes', () => { const parameter = 'hello: { world: { a } }, elysia' const result = bracketPairRange(parameter) expect(result).toEqual([7, 23]) }) it('return the correct range when given a string with nested bracket pairs', () => { const parameter = 'hello: { world: { a } }, elysia' const result = bracketPairRange(parameter) expect(result).toEqual([7, 23]) }) it('return the correct range when given a string with non-bracket characters', () => { const parameter = 'hello: { world: { a } }, elysia' const result = bracketPairRange(parameter) expect(result).toEqual([7, 23]) }) }) ================================================ FILE: test/sucrose/extract-main-parameter.test.ts ================================================ import { describe, it, expect } from 'bun:test' import { extractMainParameter } from '../../src/sucrose' describe('extract main parameter', () => { it('extract main parameter when there is no spread operator and only one parameter', () => { const parameter = 'param1' const result = extractMainParameter(parameter) expect(result).toBe('param1') }) it('extract main parameter when there is a spread operator and only one parameter', () => { const parameter = '{ ...param1 }' const result = extractMainParameter(parameter) expect(result).toBe('param1') }) it('extract main parameter when there are multiple parameters and a spread operator', () => { const parameter = '{ param1, param2, ...param3 }' const result = extractMainParameter(parameter) expect(result).toBe('param3') }) it('return undefined when parameter is an empty string', () => { const parameter = '' const result = extractMainParameter(parameter) expect(result).toBeUndefined() }) it('return undefined when parameter is undefined', () => { const parameter = undefined // @ts-expect-error const result = extractMainParameter(parameter) expect(result).toBeUndefined() }) it('return undefined when parameter is null', () => { const parameter = null // @ts-expect-error const result = extractMainParameter(parameter) expect(result).toBeUndefined() }) }) ================================================ FILE: test/sucrose/find-alias.test.ts ================================================ import { describe, it, expect } from 'bun:test' import { findAlias } from '../../src/sucrose' describe('find alias', () => { it('find aliases of a variable in a simple function body', () => { const type = 'body' const body = '{ const a = body, b = body }' const aliases = findAlias(type, body) expect(aliases).toEqual(['a', 'b']) }) it('find aliases of a variable in a function body with multiple assignments', () => { const type = 'body' const body = `{ const a = body; const b = body }` const aliases = findAlias(type, body) expect(aliases).toEqual(['a', 'b']) }) it('find aliases of a variable in a function body with object destructuring as-is', () => { const type = 'body' const body = '{ const { a, b } = body }' const aliases = findAlias(type, body) expect(aliases).toEqual(['{ a, b }']) }) it('return an empty array when the variable is not found in the function body', () => { const type = 'body' const body = '{ const a = otherVariable }' const aliases = findAlias(type, body) expect(aliases).toEqual([]) }) it('handle a function body with no content', () => { const type = 'body' const body = '' const aliases = findAlias(type, body) expect(aliases).toEqual([]) }) it('handle a function body with only one line', () => { const type = 'body' const body = 'const a = body' const aliases = findAlias(type, body) expect(aliases).toEqual(['a']) }) it('find aliases of a variable in a nested function body', () => { const type = 'body' const body = '{ const a = body, b = { const c = body, d = body } }' const aliases = findAlias(type, body) expect(aliases).toEqual(['a', 'c', 'd']) }) it('find aliases of a variable in a function body with comments', () => { const type = 'body' const body = '{ const a = body, b = body }' const aliases = findAlias(type, body) expect(aliases).toEqual(['a', 'b']) }) it('find aliases of a variable in a function body with multiple lines', () => { const type = 'body' const body = '{ const a = body,\n b = body }' const aliases = findAlias(type, body) expect(aliases).toEqual(['a', 'b']) }) it('find aliases of a variable in a nested function body', () => { const type = 'body' const body = '{ const a = body, b = { const c = body, d = body } }' const aliases = findAlias(type, body) expect(aliases).toEqual(['a', 'c', 'd']) }) it('find aliases of a variable in a function body with mixed quotes', () => { const type = 'body' const body = '{ const a = body, b = body }' const aliases = findAlias(type, body) expect(aliases).toEqual(['a', 'b']) }) it('handle aliases of a variable in a function body with mixed spaces and tabs', () => { const type = 'body' const body = '{ const a = body,\tb = body }' const aliases = findAlias(type, body) expect(aliases).toEqual(['a', 'b']) }) it('find aliases of a variable in a simple function body', () => { const type = 'body' const body = '{ const a = body, b = body }' const aliases = findAlias(type, body) expect(aliases).toEqual(['a', 'b']) }) it('find aliases of a variable in a simple function body', () => { const type = 'body' const body = '{ const a = body, b = body }' const aliases = findAlias(type, body) expect(aliases).toEqual(['a', 'b']) }) it('find aliases of a variable in a function body with a variable name that starts with an underscore', () => { const type = '_body' const body = '{ const _a = _body, _b = _body }' const aliases = findAlias(type, body) expect(aliases).toEqual(['_a', '_b']) }) it('find aliases of a variable in a function body with a variable name that starts with a number', () => { const type = 'body' const body = '{ const 1a = body, b = body }' const aliases = findAlias(type, body) expect(aliases).toEqual(['1a', 'b']) }) it('find aliases of a variable in a function body with a variable name that contains a dot', () => { const type = 'body' const body = '{ const a.b = body, b = body }' const aliases = findAlias(type, body) expect(aliases).toEqual(['a.b', 'b']) }) it('find aliases of a variable in a function body with a hyphenated variable name', () => { const type = 'body' const body = '{ const a-b = body, b = body }' const aliases = findAlias(type, body) expect(aliases).toEqual(['a-b', 'b']) }) it('find aliases of a variable in a function body with a variable name that contains a dollar sign', () => { const type = 'body' const body = '{ const $a = body, b = $a }' const aliases = findAlias(type, body) expect(aliases).toEqual(['$a', 'b']) }) }) ================================================ FILE: test/sucrose/infer-body-reference.test.ts ================================================ import { describe, it, expect } from 'bun:test' import { inferBodyReference, Sucrose } from '../../src/sucrose' describe('infer body reference', () => { it('infer dot notation', () => { const code = 'context.body.a' const aliases = ['context'] const inference = { query: false, headers: false, body: false, cookie: false, set: false, server: false, path: false, route: false, url: false } satisfies Sucrose.Inference inferBodyReference(code, aliases, inference) expect(inference.body as boolean).toBe(true) }) it('infer property access', () => { const code = 'context["body"]["a"]' const aliases = ['context'] const inference = { query: false, headers: false, body: false, cookie: false, set: false, server: false, path: false, route: false, url: false } satisfies Sucrose.Inference inferBodyReference(code, aliases, inference) expect(inference.body as boolean).toBe(true) }) it('infer multiple query', () => { const code = `{ console.log({ a: query.quack }, { b: query.duck }); const b = query.bark; return "a"; }` const aliases = ['query'] const inference = { body: false, cookie: false, headers: false, query: true, set: true, server: false, path: false, route: false, url: false } satisfies Sucrose.Inference inferBodyReference(code, aliases, inference) expect(inference).toEqual({ body: false, cookie: false, headers: false, query: true, set: true, server: false, path: false, route: false, url: false }) }) // This is not use in Bun // it('infer single quote query', () => { // const code = `{ // const b = query['quack']; // return "a"; // }` // const aliases = ['query'] // const inference = { // body: false, // cookie: false, // headers: false, // queries: [], // query: true, // set: true, // unknownQueries: false // } // inferBodyReference(code, aliases, inference) // expect(inference).toEqual({ // body: false, // cookie: false, // headers: false, // queries: ['quack'], // query: true, // set: true, // unknownQueries: false // }) // }) // it('infer double quote query', () => { // const code = `{ // const b = query["quack"]; // return "a"; // }` // const aliases = ['query'] // const inference = { // body: false, // cookie: false, // headers: false, // queries: [], // query: true, // set: true, // unknownQueries: false // } // inferBodyReference(code, aliases, inference) // expect(inference).toEqual({ // body: false, // cookie: false, // headers: false, // queries: ['quack'], // query: true, // set: true, // unknownQueries: false // }) // }) it('skip dynamic quote query', () => { const code = `{ const b = query[quack]; return "a"; }` const aliases = ['query'] const inference = { body: false, cookie: false, headers: false, query: true, set: true, server: false, path: false, route: false, url: false } satisfies Sucrose.Inference inferBodyReference(code, aliases, inference) expect(inference).toEqual({ body: false, cookie: false, headers: false, query: true, set: true, server: false, path: false, route: false, url: false }) }) it('infer dot notation', () => { const code = ` context.server?.upgrade(request) ` const aliases = ['context'] const inference = { query: false, headers: false, body: false, cookie: false, set: false, server: false, path: false, route: false, url: false } satisfies Sucrose.Inference inferBodyReference(code, aliases, inference) expect(inference.server as boolean).toBe(true) }) }) ================================================ FILE: test/sucrose/integration.test.ts ================================================ import { describe, expect, it } from 'bun:test' describe('Integration', () => { it( 'Allows process to finish', async () => { const res = Bun.spawn({ cmd: [ 'bun', '-e', "import { Elysia } from './src'; new Elysia().onBeforeHandle(() => { });" ] }) const status = await res.exited; expect(status).toEqual(0); }, { timeout: 500 } ) }) ================================================ FILE: test/sucrose/query.test.ts ================================================ import { Elysia } from '../../src' import { describe, expect, it } from 'bun:test' const req = (path: string = '/?name=sucrose') => new Request(`http://localhost${path}`) describe('Query', () => { it('access all using property name', async () => { const app = new Elysia().get('/', (ctx) => ctx.query) const response = await app.handle(req()) expect(await response.json()).toEqual({ name: 'sucrose' }) }) it('access all using destructuring', async () => { const app = new Elysia().get('/', ({ query }) => query) const response = await app.handle(req()) expect(await response.json()).toEqual({ name: 'sucrose' }) }) it('access single param using property name', async () => { const app = new Elysia().get('/', (ctx) => ctx.query.name) const response = await app.handle(req()) expect(await response.text()).toEqual('sucrose') }) it('access single param using destructuring', async () => { const app = new Elysia().get('/', ({ query: { name } }) => name) const response = await app.handle(req()) expect(await response.text()).toEqual('sucrose') }) it('access all using destructuring assignment', async () => { const app = new Elysia().get('/', (ctx) => { const { query } = ctx return query }) const response = await app.handle(req()) expect(await response.json()).toEqual({ name: 'sucrose' }) }) it('access all using destructuring assignment within derive', async () => { const app = new Elysia() .derive((ctx) => { const { query } = ctx return { yay() { return query } } }) .get('/', (ctx) => ctx.yay()) const response = await app.handle(req()) expect(await response.json()).toEqual({ name: 'sucrose' }) }) it('access all using property name within derive', async () => { const app = new Elysia() .derive((ctx) => { return { yay() { return ctx.query } } }) .get('/', (ctx) => ctx.yay()) const response = await app.handle(req()) expect(await response.json()).toEqual({ name: 'sucrose' }) }) it('destructured encoded & (%26) query string', async () => { const app = new Elysia() .get('/unknown', ({ query }) => query) .get('/named', ({ query: { name } }) => name) const unknown = await app .handle(req('/unknown?name=sucrose%26albedo&alias=achemist')) .then((x) => x.json()) const named = await app .handle(req('/named?name=sucrose%26albedo&alias=achemist')) .then((x) => x.text()) expect(unknown).toEqual({ name: 'sucrose&albedo', alias: 'achemist' }) expect(named).toEqual('sucrose&albedo') }) }) ================================================ FILE: test/sucrose/remove-colon-alias.test.ts ================================================ import { describe, it, expect } from 'bun:test' import { inferBodyReference, removeColonAlias } from '../../src/sucrose' describe('remove colon alias', () => { it('remove aliased name', () => { const code = '{ headers: rs }' expect(removeColonAlias(code)).toBe(`{ headers }`) }) it('remove aliased with part of keyword', () => { const code = '{ headers: reqHeaders }' expect(removeColonAlias(code)).toBe(`{ headers }`) }) it('remove multiple aliased', () => { const code = '{ headers: rs, query: q }' expect(removeColonAlias(code)).toBe(`{ headers, query }`) }) it('maintain same value if no aliased found', () => { const code = '{ headers, query }' expect(removeColonAlias(code)).toBe(`{ headers, query }`) }) }) ================================================ FILE: test/sucrose/remove-default-parameter.test.ts ================================================ import { describe, it, expect } from 'bun:test' import { removeDefaultParameter } from '../../src/sucrose' describe('removeDefaultParameter', () => { // The function removes default parameter values from a string parameter. it('should remove default parameter values from a string parameter', () => { const parameter = 'a=1, b=2, c=3' const result = removeDefaultParameter(parameter) expect(result).toEqual('a, b, c') }) // The function returns the modified string parameter. it('should return the modified string parameter', () => { const parameter = 'a=1, b=2, c=3' const result = removeDefaultParameter(parameter) expect(result).toEqual('a, b, c') }) // The function handles a string parameter with no default parameter values. it('should handle a string parameter with no default parameter values', () => { const parameter = 'a, b, c' const result = removeDefaultParameter(parameter) expect(result).toEqual('a, b, c') }) // The function handles a string parameter with no equals sign. it('should handle a string parameter with no equals sign', () => { const parameter = 'a, b, c' const result = removeDefaultParameter(parameter) expect(result).toEqual('a, b, c') }) // The function handles a string parameter with an equals sign but no comma or closing bracket. it('should handle a string parameter with an equals sign but no comma or closing bracket', () => { const parameter = 'a=1' const result = removeDefaultParameter(parameter) expect(result).toEqual('a') }) // The function handles a string parameter with an equals sign and a comma but no closing bracket. it('should handle a string parameter with an equals sign and a comma but no closing bracket', () => { const parameter = 'a=1, b=2, c' const result = removeDefaultParameter(parameter) expect(result).toEqual('a, b, c') }) // The function handles a string parameter with one default parameter value. it('should remove default parameter values from a string parameter', () => { const parameter = 'a=1, b=2, c=3' const result = removeDefaultParameter(parameter) expect(result).toEqual('a, b, c') }) // The function handles a string parameter with multiple default parameter values. it('should remove default parameter values from a string parameter', () => { const parameter = 'a=1, b=2, c=3' const result = removeDefaultParameter(parameter) expect(result).toEqual('a, b, c') }) // The function handles a string parameter with default parameter values in different locations. it('should remove default parameter values from a string parameter', () => { const parameter = 'a=1, b=2, c=3' const result = removeDefaultParameter(parameter) expect(result).toEqual('a, b, c') }) // The function handles a string parameter with default parameter values in nested brackets. it('should remove default parameter values from a string parameter', () => { const parameter = 'a=1, b=2, c=3' const result = removeDefaultParameter(parameter) expect(result).toEqual('a, b, c') }) // The function handles a string parameter with an equals sign and a closing bracket but no comma. it('should remove default parameter values from a string parameter when there is an equals sign and a closing bracket but no comma', () => { const parameter = 'a=1, b=2, c=3' const result = removeDefaultParameter(parameter) expect(result).toEqual('a, b, c') }) // The function handles a string parameter with an equals sign, a comma, and a closing bracket in the wrong order. it('should remove default parameter values from a string parameter', () => { const parameter = 'a=1, b=2, c=3' const result = removeDefaultParameter(parameter) expect(result).toEqual('a, b, c') }) // The function is case-sensitive and does not remove default parameter values with different capitalization. it('should not remove default parameter values with different capitalization', () => { const parameter = 'a=1, B=2, c=3' const result = removeDefaultParameter(parameter) expect(result).toEqual('a, B, c') }) // The function does not modify the original string parameter and returns a new string. it('should remove default parameter values from a string parameter', () => { const parameter = 'a=1, b=2, c=3' const result = removeDefaultParameter(parameter) expect(result).toEqual('a, b, c') }) // The function can handle whitespace characters around the equals sign. it('should remove default parameter values when there are whitespace characters around the equals sign', () => { const parameter = 'a = 1, b = 2, c = 3' const result = removeDefaultParameter(parameter) expect(result).toEqual('a, b, c') }) // The function can handle default parameter values that contain equals signs. it('should remove default parameter values from a string parameter', () => { const parameter = 'a=1, b=2, c=3' const result = removeDefaultParameter(parameter) expect(result).toEqual('a, b, c') }) // The function can handle default parameter values that contain commas. it('should remove default parameter values from a string parameter', () => { const parameter = 'a=1, b=2, c=3' const result = removeDefaultParameter(parameter) expect(result).toEqual('a, b, c') }) }) ================================================ FILE: test/sucrose/retrieve-root-parameters.test.ts ================================================ import { describe, it, expect } from 'bun:test' import { retrieveRootparameters } from '../../src/sucrose' describe('retrieve root parameter', () => { it('return an empty string when given an empty string', () => { const parameter = '' const result = retrieveRootparameters(parameter) expect(result).toEqual({ parameters: {}, hasParenthesis: false }) }) // Doesn't make sense as JavaScript will panic // it('return the same string when there are no brackets in the string', () => { // const parameter = 'hello world' // const result = retrieveRootparameters(parameter) // expect(result).toEqual({ // parameters: ['hello world'], // hasParenthesis: false // }) // }) it('remove brackets and their contents when they are at the root level', () => { const parameter = '({ hello: { world: { a } }, elysia })' const result = retrieveRootparameters(parameter) expect(result).toEqual({ parameters: { hello: true, elysia: true }, hasParenthesis: true }) }) it('return an empty string when given only brackets', () => { const parameter = '()' const result = retrieveRootparameters(parameter) expect(result).toEqual({ parameters: {}, hasParenthesis: false }) }) it('return an empty string when given only one bracket', () => { const parameter = '(' const result = retrieveRootparameters(parameter) expect(result).toEqual({ parameters: {}, hasParenthesis: false }) }) // Doesn't make sense as JavaScript will panic // it('return even if bracket is unbalanced', () => { // const parameter = '({ hello: { world: { a } })' // const result = retrieveRootparameters(parameter) // expect(result).toEqual({ // parameters: ['hello'], // hasParenthesis: true // }) // }) it('return the root parameters when given a string with spaces between brackets', () => { const parameter = '({ hello: { world: { a } }, elysia })' const result = retrieveRootparameters(parameter) expect(result).toEqual({ parameters: { hello: true, elysia: true }, hasParenthesis: true }) }) it('return parameter on minified bracket', () => { const parameter = '({ hello, path })' const result = retrieveRootparameters(parameter) expect(result).toEqual({ parameters: { hello: true, path: true }, hasParenthesis: true }) }) it('handle tab and new line', () => { const parameter = '({ hello: { world: { a } }, \nelysia, \teden })' const result = retrieveRootparameters(parameter) expect(result).toEqual({ parameters: { hello: true, elysia: true, eden: true }, hasParenthesis: true }) }) it('handle last parameter destructuring', () => { const parameter = '{ set, cookie: { auth } }' const result = retrieveRootparameters(parameter) expect(result).toEqual({ hasParenthesis: true, parameters: { set: true, cookie: true } }) }) }) ================================================ FILE: test/sucrose/separate-function.test.ts ================================================ import { describe, expect, it } from 'bun:test' import { separateFunction } from '../../src/sucrose' describe('Sucrose: separateFunction', () => { it('separate arrowParam', () => { const arrowParam = ({ sucrose, amber }: any) => { return 'sucrose' } expect(separateFunction(arrowParam.toString())).toEqual([ '{ sucrose, amber }', '{\n return "sucrose";\n }', { isArrowReturn: false } ]) }) it('separate arrowNoParam', () => { const arrowNoParam = () => 'sucrose' expect(separateFunction(arrowNoParam.toString())).toEqual([ '', '"sucrose"', { isArrowReturn: true } ]) }) it('separate arrowAsync', () => { const arrowAsync = async (sucrose: any) => 'sucrose' expect(separateFunction(arrowAsync.toString())).toEqual([ 'sucrose', '"sucrose"', { isArrowReturn: true } ]) }) it('separate fnParam', () => { function fnParam({ sucrose, amber }: any) { return 'sucrose' } expect(separateFunction(fnParam.toString())).toEqual([ '{ sucrose, amber }', '{\n return "sucrose";\n }', { isArrowReturn: false } ]) }) it('separate fnNoParam', () => { function fnNoParam() { return 'sucrose' } expect(separateFunction(fnNoParam.toString())).toEqual([ '', '{\n return "sucrose";\n }', { isArrowReturn: false } ]) }) it('separate fnAsync', () => { async function fnAsync(sucrose: any) { return 'sucrose' } expect(separateFunction(fnAsync.toString())).toEqual([ 'sucrose', '{\n return "sucrose";\n }', { isArrowReturn: false } ]) }) it('separate minifed arrow param', () => { const arrowParam = `({ sucrose, amber })=>{return "sucrose"}` expect(separateFunction(arrowParam.toString())).toEqual([ '{ sucrose, amber }', '{return "sucrose"}', { isArrowReturn: false } ]) }) it('separate minified arrow without whitespace in the beginning', () => { const arrowParam = `({sucrose, amber })=>{return "sucrose"}` expect(separateFunction(arrowParam.toString())).toEqual([ '{sucrose, amber }', '{return "sucrose"}', { isArrowReturn: false } ]) }) }) ================================================ FILE: test/sucrose/sucrose.test.ts ================================================ // @ts-nocheck import { describe, expect, it } from 'bun:test' import { Elysia } from '../../src' import { separateFunction, sucrose } from '../../src/sucrose' import { req } from '../utils' describe('sucrose', () => { it('common 1', () => { expect( sucrose({ handler: function ({ query }) { query.a }, afterHandle: [], beforeHandle: [], error: [ function a({ query, query: { a, c: d }, headers: { hello }, ...rest }) { query.b rest.query.e }, ({ query: { f } }) => {} ], mapResponse: [], afterResponse: [], parse: [], request: [], start: [], stop: [], trace: [], transform: [] }) ).toEqual({ query: true, headers: true, body: false, cookie: false, set: false, server: false, path: false, url: false, route: false }) }) it('common 2', async () => { expect( sucrose({ handler: ({ set, cookie: { auth } }) => { console.log(auth.value) return '' }, afterHandle: [], beforeHandle: [], error: [], mapResponse: [], afterResponse: [], parse: [], request: [], start: [], stop: [], trace: [], transform: [] }) ).toEqual({ query: false, headers: false, body: false, cookie: true, set: true, server: false, path: false, url: false, route: false }) }) it('integration 1', async () => { const path = 'a' const app = new Elysia() // ✅ easy to perform inference .get('/1', ({ query: { a } }) => a) .get('/2', ({ query }) => query.a) .get('/3', (c) => c.query.a) .get('/4', ({ query }) => query[path]) .get('/5', (c) => c.query[path]) new Array(5).fill(0).map(async (_, i) => { const result = await app .handle(req(`/${i + 1}?a=a&b=b`)) .then((x) => x.text()) expect(result).toBe('a') }) }) it('inherits inference from plugin', () => { const plugin = new Elysia().derive(({ headers: { authorization } }) => { return { get auth() { return authorization } } }) const main = new Elysia().use(plugin) // @ts-expect-error expect(main.inference.headers).toBe(true) }) it("don't link inference", async () => { const app = new Elysia({ cookie: { secrets: 'Zero Exception', sign: true } }) .get('/', () => 'hello') .onBeforeHandle(({ cookie: { session }, error }) => { if (!session.value) return error(401, 'Unauthorized') }) const status = await app.handle(req('/')).then((x) => x.status) expect(status).toBe(200) }) it('mix up chain properties as query', () => { expect( sucrose({ handler: async (c) => { const id = c.query.id const cookie = c.cookie return { cookie, id } }, afterHandle: [], beforeHandle: [], error: [], mapResponse: [], onResponse: [], parse: [], request: [], start: [], stop: [], trace: [], transform: [] }) ).toEqual({ body: false, cookie: true, headers: false, query: true, set: false, server: false, path: false, url: false, route: false }) }) it('infer all inferences if context is passed to function', () => { expect( sucrose({ handler: function (context) { console.log(context) }, afterHandle: [], beforeHandle: [], error: [], mapResponse: [], onResponse: [], parse: [], request: [], start: [], stop: [], trace: [], transform: [] }) ).toEqual({ query: true, headers: true, body: true, cookie: true, set: true, server: true, path: true, url: true, route: true }) }) it('infer all inferences if context is passed to function', () => { expect( sucrose({ handler: function ({ ...context }) { console.log(context) }, afterHandle: [], beforeHandle: [], error: [], mapResponse: [], onResponse: [], parse: [], request: [], start: [], stop: [], trace: [], transform: [] }) ).toEqual({ query: true, headers: true, body: true, cookie: true, set: true, server: true, path: true, url: true, route: true }) }) it('infer single object destructure property', () => { expect( sucrose({ handler: function ({ server }) { console.log(server) }, afterHandle: [], beforeHandle: [], error: [], mapResponse: [], onResponse: [], parse: [], request: [], start: [], stop: [], trace: [], transform: [] }) ).toEqual({ query: false, headers: false, body: false, cookie: false, set: false, server: true, path: false, url: false, route: false }) }) it('infer server', async () => { const app = new Elysia({ precompile: true }) .onRequest(({ server }) => {}) .get('/', () => 'Hello, World!') const response = await app.handle(new Request('http://localhost:3000')) expect(response.status).toBe(200) }) it('not death lock on empty', async () => { const app = new Elysia({ precompile: true }) .onRequest((c) => {}) .get('/', () => 'Hello, World!') const response = await app.handle(new Request('http://localhost:3000')) expect(response.status).toBe(200) }) it('access route, url, path', () => { expect( sucrose({ handler: function (context) { console.log(context.url, context.path, context.route) }, afterHandle: [], beforeHandle: [], error: [], mapResponse: [], onResponse: [], parse: [], request: [], start: [], stop: [], trace: [], transform: [] }) ).toEqual({ query: false, headers: false, body: false, cookie: false, set: false, server: false, path: true, url: true, route: true }) }) it('handle context pass to function with sub context', () => { expect( sucrose({ handler: (context) => { console.log('path >>> ', context.path) console.log(context) } }) ).toEqual({ query: true, headers: true, body: true, cookie: true, set: true, server: true, path: true, url: true, route: true }) }) }) ================================================ FILE: test/timeout.ts ================================================ import { Elysia } from '../src' const largePlugin = async (app: Elysia) => { await new Promise((resolve) => setTimeout(resolve, 1000)) return app.get('/large', () => 'Hi') } export default largePlugin ================================================ FILE: test/tracer/aot.test.ts ================================================ import { Context, Elysia } from '../../src' import { describe, expect, it } from 'bun:test' import { req } from '../utils' describe('Trace AoT', async () => { // it('inject request report', async () => { // const app = new Elysia().trace(async () => {}).get('/', () => '') // expect(app.compile().fetch.toString()).toInclude( // `reporter.emit('event',{id,event:'request'` // ) // }) it('try-catch edge case', async () => { class Controller { static async handle(ctx: Context) { try { // @ts-ignore const { token } = ctx.body return token } catch { return 'nope' } } } const app = new Elysia().post('/', Controller.handle) const response = await app.handle( new Request('http://localhost/', { method: 'POST', body: JSON.stringify({ token: 'yay' }), headers: { 'Content-Type': 'application/json' } }) ) expect(await response.text()).toEqual('yay') }) // ! Fix me: uncomment when 1.0.0 is released // it('inject response report', async () => { // const app = new Elysia().trace(async () => {}).get('/', () => '') // expect(app.router.history[0].composed?.toString()).toInclude( // `reporter.emit('event',{id,event:'response'` // ) // }) it('handle scope', async () => { let called = 0 const plugin = new Elysia() .trace(({ onHandle }) => { onHandle(() => { called++ }) }) .get('/plugin', () => 'ok') const app = new Elysia().use(plugin).get('/main', () => 'ok') await Promise.all([ app.handle(req('/plugin')), app.handle(req('/main')) ]) expect(called).toBe(1) }) }) ================================================ FILE: test/tracer/detail.test.ts ================================================ import { Elysia } from '../../src' import { describe, expect, it } from 'bun:test' import { post, req } from '../utils' describe('Trace Detail', async () => { it('report parse units name', async () => { const app = new Elysia() .trace(({ onParse, set }) => { onParse(({ onEvent, onStop }) => { const names = [] onEvent(({ name }) => { names.push(name) }) onStop(() => { set.headers.name = names.join(', ') }) }) }) .onParse(function luna() {}) .post('/', ({ body }) => body, { parse: [function kindred() {}] }) const { headers } = await app.handle(post('/', {})) expect(headers.get('name')).toBe('luna, kindred') }) it('report transform units name', async () => { const app = new Elysia() .trace(({ onTransform, set }) => { onTransform(({ onEvent, onStop }) => { const names = [] onEvent(({ name }) => { names.push(name) }) onStop(() => { set.headers.name = names.join(', ') }) }) }) .onTransform(function luna() {}) .get('/', () => 'a', { transform: [function kindred() {}] }) const { headers } = await app.handle(req('/')) expect(headers.get('name')).toBe('luna, kindred') }) it('report beforeHandle units name', async () => { const app = new Elysia() .trace(({ onBeforeHandle, set }) => { onBeforeHandle(({ onEvent, onStop }) => { const names = [] onEvent(({ name }) => { names.push(name) }) onStop(() => { set.headers.name = names.join(', ') }) }) }) .onBeforeHandle(function luna() {}) .get('/', () => 'a', { beforeHandle: [function kindred() {}] }) const { headers } = await app.handle(req('/')) expect(headers.get('name')).toBe('luna, kindred') }) it('report afterHandle units name', async () => { const app = new Elysia() .trace(({ onAfterHandle, set }) => { onAfterHandle(({ onEvent, onStop }) => { const names = [] onEvent(({ name }) => { names.push(name) }) onStop(() => { set.headers.name = names.join(', ') }) }) }) .onAfterHandle(function luna() {}) .get('/', () => 'a', { afterHandle: [function kindred() {}] }) const { headers } = await app.handle(req('/')) expect(headers.get('name')).toBe('luna, kindred') }) it('report mapResponse units name', async () => { const app = new Elysia() .trace(({ onMapResponse, set }) => { onMapResponse(({ onEvent, onStop }) => { const names = [] onEvent(({ name }) => { names.push(name) }) onStop(() => { set.headers.name = names.join(', ') }) }) }) .mapResponse(function luna() {}) .get('/', () => 'a', { mapResponse: [function kindred() {}] }) const { headers } = await app.handle(req('/')) expect(headers.get('name')).toBe('luna, kindred') }) it('report afterResponse units name', async () => { const app = new Elysia() .trace(({ onAfterResponse, set }) => { onAfterResponse(({ onEvent, onStop }) => { const names = [] onEvent(({ name }) => { names.push(name) }) onStop(() => { expect(names.join(', ')).toBe('luna, kindred') }) }) }) .onAfterResponse(function luna() {}) .get('/', () => 'a', { afterResponse: [function kindred() {}] }) app.handle(req('/')) }) }) ================================================ FILE: test/tracer/timing.test.ts ================================================ /* eslint-disable @typescript-eslint/no-unused-vars */ import { Elysia } from '../../src' import { describe, expect, it } from 'bun:test' import { post, req } from '../utils' const delay = (delay = 7) => new Promise((resolve) => setTimeout(resolve, delay)) describe('Trace Timing', async () => { it('handle', async () => { const app = new Elysia() .trace(({ onHandle, set }) => { onHandle(({ onStop }) => { onStop(({ elapsed }) => { set.headers.time = elapsed.toString() }) }) }) .get('/', async () => { await delay() return 'a' }) const { headers } = await app.handle(req('/')) expect(+(headers.get('time') ?? 0)).toBeGreaterThan(5) }) it('request', async () => { const app = new Elysia() .trace(({ onRequest, set }) => { onRequest(({ onStop }) => { onStop(({ elapsed }) => { set.headers.time = elapsed.toString() }) }) }) .onRequest(async () => { await delay() }) .get('/', () => 'a') const { headers } = await app.handle(req('/')) expect(+(headers.get('time') ?? 0)).toBeGreaterThan(5) }) it('parse', async () => { const app = new Elysia() .trace(({ onParse, set }) => { onParse(({ onStop }) => { onStop(({ elapsed }) => { set.headers.time = elapsed.toString() }) }) }) .onParse(async () => { await delay() }) .post('/', ({ body }) => 'a') const { headers } = await app.handle(post('/', {})) expect(+(headers.get('time') ?? 0)).toBeGreaterThan(5) }) it('transform', async () => { const app = new Elysia() .trace(({ onTransform, set }) => { onTransform(({ onStop }) => { onStop(({ elapsed }) => { set.headers.time = elapsed.toString() }) }) }) .onTransform(async () => { await delay() }) .get('/', () => 'a') const { headers } = await app.handle(req('/')) expect(+(headers.get('time') ?? 0)).toBeGreaterThan(5) }) it('beforeHandle', async () => { const app = new Elysia() .trace(({ onBeforeHandle, set }) => { onBeforeHandle(({ onStop }) => { onStop(({ elapsed }) => { set.headers.time = elapsed.toString() }) }) }) .onBeforeHandle(async () => { await delay() }) .get('/', () => 'a') const { headers } = await app.handle(req('/')) expect(+(headers.get('time') ?? 0)).toBeGreaterThan(5) }) it('afterHandle', async () => { const app = new Elysia() .trace(({ onAfterHandle, set }) => { onAfterHandle(({ onStop }) => { onStop(({ elapsed }) => { set.headers.time = elapsed.toString() }) }) }) .onAfterHandle(async () => { await delay() }) .get('/', () => 'a') const { headers } = await app.handle(req('/')) expect(+(headers.get('time') ?? 0)).toBeGreaterThan(5) }) it('mapResponse', async () => { const app = new Elysia() .trace(({ onMapResponse, set }) => { onMapResponse(({ onStop }) => { onStop(({ elapsed }) => { set.headers.time = elapsed.toString() }) }) }) .mapResponse(async () => { await delay() }) .get('/', () => 'a') const { headers } = await app.handle(req('/')) expect(+(headers.get('time') ?? 0)).toBeGreaterThan(5) }) it('afterResponse', async () => { const app = new Elysia() .trace(({ onAfterResponse, set }) => { onAfterResponse(({ onStop }) => { onStop(({ elapsed }) => { expect(elapsed).toBeGreaterThan(5) }) }) }) .onAfterResponse(async () => { await delay() }) .get('/', () => 'a') app.handle(req('/')) }) it('inline parse', async () => { const app = new Elysia() .trace(({ onParse, set }) => { onParse(({ onStop }) => { onStop(({ elapsed }) => { set.headers.time = elapsed.toString() }) }) }) .post('/', ({ body }) => 'a', { async parse() { await delay() } }) const { headers } = await app.handle(post('/', {})) expect(+(headers.get('time') ?? 0)).toBeGreaterThan(5) }) it('inline transform', async () => { const app = new Elysia() .trace(({ onTransform, set }) => { onTransform(({ onStop }) => { onStop(({ elapsed }) => { set.headers.time = elapsed.toString() }) }) }) .get('/', () => 'a', { async transform() { await delay() } }) const { headers } = await app.handle(req('/')) expect(+(headers.get('time') ?? 0)).toBeGreaterThan(5) }) it('inline beforeHandle', async () => { const app = new Elysia() .trace(({ onBeforeHandle, set }) => { onBeforeHandle(({ onStop }) => { onStop(({ elapsed }) => { set.headers.time = elapsed.toString() }) }) }) .get('/', () => 'a', { async beforeHandle() { await delay() } }) const { headers } = await app.handle(req('/')) expect(+(headers.get('time') ?? 0)).toBeGreaterThan(5) }) it('inline afterHandle', async () => { const app = new Elysia() .trace(({ onAfterHandle, set }) => { onAfterHandle(({ onStop }) => { onStop(({ elapsed }) => { set.headers.time = elapsed.toString() }) }) }) .get('/', () => 'a', { async afterHandle() { await delay() } }) const { headers } = await app.handle(req('/')) expect(+(headers.get('time') ?? 0)).toBeGreaterThan(5) }) it('inline mapResponse', async () => { const app = new Elysia() .trace(({ onMapResponse, set }) => { onMapResponse(({ onStop }) => { onStop(({ elapsed }) => { set.headers.time = elapsed.toString() }) }) }) .get('/', () => 'a', { async mapResponse() { await delay() } }) const { headers } = await app.handle(req('/')) expect(+(headers.get('time') ?? 0)).toBeGreaterThan(5) }) it('inline afterResponse', async () => { const app = new Elysia() .trace(({ onAfterResponse, set }) => { onAfterResponse(({ onStop }) => { onStop(({ elapsed }) => { expect(elapsed).toBeGreaterThan(5) }) }) }) .get('/', () => 'a', { async afterResponse() { await delay() } }) app.handle(req('/')) }) it('parse unit', async () => { const app = new Elysia() .trace(({ onParse, set }) => { onParse(({ onStop, onEvent }) => { let total = 0 onEvent(({ begin }) => { onStop(({ elapsed }) => { total += elapsed }) }) onStop(({ elapsed }) => { set.headers.time = total.toString() }) }) }) .onParse(async function luna() { await delay(6) }) .post('/', ({ body }) => body, { parse: [ async function kindred() { await delay(6) } ] }) const { headers } = await app.handle(post('/', {})) expect(+(headers.get('time') ?? 0)).toBeGreaterThan(5) }) it('transform unit', async () => { const app = new Elysia() .trace(({ onTransform, set }) => { onTransform(({ onStop, onEvent }) => { let total = 0 onEvent(({ onStop }) => { onStop(({ elapsed }) => { total += elapsed }) }) onStop(({ elapsed }) => { set.headers.time = total.toString() }) }) }) .onTransform(async function luna() { await delay(6) }) .get('/', () => 'a', { transform: [ async function kindred() { await delay(6) } ] }) const { headers } = await app.handle(req('/')) expect(+(headers.get('time') ?? 0)).toBeGreaterThan(5) }) it('beforeHandle unit', async () => { const app = new Elysia() .trace(({ onBeforeHandle, set }) => { onBeforeHandle(({ onStop, onEvent }) => { let total = 0 onEvent(({ onStop }) => { onStop(({ elapsed }) => { total += elapsed }) }) onStop(({ elapsed }) => { set.headers.time = total.toString() }) }) }) .onBeforeHandle(async function luna() { await delay(6) }) .get('/', () => 'a', { beforeHandle: [ async function kindred() { await delay(6) } ] }) const { headers } = await app.handle(req('/')) expect(+(headers.get('time') ?? 0)).toBeGreaterThan(5) }) it('beforeHandle units', async () => { const app = new Elysia() .trace(({ onBeforeHandle, set }) => { onBeforeHandle(({ onStop, onEvent }) => { let total = 0 onEvent(({ onStop }) => { onStop(({ elapsed }) => { total += elapsed }) }) onStop(({ elapsed }) => { set.headers.time = total.toString() }) }) }) .onBeforeHandle(async function luna() { await delay(6.25) }) .get('/', () => 'a', { beforeHandle: [ async function kindred() { await delay(6.25) } ] }) const { headers } = await app.handle(req('/')) expect(+(headers.get('time') ?? 0)).toBeGreaterThan(5) }) it('afterHandle unit', async () => { const app = new Elysia() .trace(({ onAfterHandle, set }) => { onAfterHandle(({ onStop, onEvent }) => { let total = 0 onEvent(({ onStop }) => { onStop(({ elapsed }) => { total += elapsed }) }) onStop(({ elapsed }) => { set.headers.time = total.toString() }) }) }) .onAfterHandle(async function luna() { await delay(6) }) .get('/', () => 'a', { afterHandle: [ async function kindred() { await delay(6) } ] }) const { headers } = await app.handle(req('/')) expect(+(headers.get('time') ?? 0)).toBeGreaterThan(5) }) it('mapResponse unit', async () => { const app = new Elysia() .trace(({ onMapResponse, set }) => { onMapResponse(({ onStop, onEvent }) => { let total = 0 onEvent(({ onStop }) => { onStop(({ elapsed }) => { total += elapsed }) }) onStop(({ elapsed }) => { set.headers.time = total.toString() }) }) }) .mapResponse(async function luna() { await delay(6) }) .get('/', () => 'a', { mapResponse: [ async function kindred() { await delay(6) } ] }) const { headers } = await app.handle(req('/')) expect(+(headers.get('time') ?? 0)).toBeGreaterThan(5) }) it('afterResponse unit', async () => { const app = new Elysia() .trace(({ onAfterResponse, set }) => { onAfterResponse(({ onStop, onEvent }) => { let total = 0 onEvent(({ onStop }) => { onStop(({ elapsed }) => { total += elapsed }) }) onStop(({ elapsed }) => { set.headers.time = total.toString() }) }) }) .onAfterResponse(async function luna() { await delay(6) }) .get('/', () => 'a', { afterResponse: [ async function kindred() { await delay(6) } ] }) app.handle(req('/')) }) }) ================================================ FILE: test/tracer/trace.test.ts ================================================ import { describe, expect, it } from 'bun:test' import { Elysia, type TraceProcess, type TraceEvent } from '../../src' import { delay, req } from '../utils' describe('trace', () => { it('inherits plugin', async () => { const timeout = setTimeout(() => { throw new Error('Trace stuck') }, 1000) const a = new Elysia().trace({ as: 'global' }, async ({ set }) => { set.headers['X-Powered-By'] = 'elysia' clearTimeout(timeout) }) const app = new Elysia().use(a).get('/', () => 'hi') const response = await app.handle(req('/')) expect(response.headers.get('X-Powered-By')).toBe('elysia') expect(response.status).toBe(200) }) it('handle scoped instance', async () => { const timeout = setTimeout(() => { throw new Error('Trace stuck') }, 1000) const a = new Elysia().trace({ as: 'global' }, async ({ set }) => { set.headers['X-Powered-By'] = 'elysia' clearTimeout(timeout) }) const b = new Elysia().get('/scoped', () => 'hi') const app = new Elysia() .use(a) .use(b) .get('/', () => 'hi') const response = await app.handle(req('/scoped')) expect(response.headers.get('X-Powered-By')).toBe('elysia') expect(response.status).toBe(200) }) it('call all trace', async () => { const called = [] const detectEvent = (event: TraceEvent) => ({ onStop }: TraceProcess<'begin'>) => { onStop(() => { called.push(event) }) } const plugin = new Elysia().trace( { as: 'scoped' }, ({ onRequest, onParse, onTransform, onBeforeHandle, onHandle, onAfterHandle, onMapResponse, onAfterResponse }) => { onRequest(detectEvent('request')) onParse(detectEvent('parse')) onTransform(detectEvent('transform')) onBeforeHandle(detectEvent('beforeHandle')) onHandle(detectEvent('handle')) onAfterHandle(detectEvent('afterHandle')) onMapResponse(detectEvent('mapResponse')) onAfterResponse(detectEvent('afterResponse')) onAfterResponse(() => { expect(called).toEqual([ 'request', 'parse', 'transform', 'beforeHandle', 'handle', 'afterHandle', 'mapResponse' // afterResponse is being called so we can't check it yet ]) }) } ) const app = new Elysia().use(plugin).get('/', 'hi') await app.handle(req('/')) // wait for next tick await Bun.sleep(1) expect(called).toEqual([ 'request', 'parse', 'transform', 'beforeHandle', 'handle', 'afterHandle', 'mapResponse', 'afterResponse' ]) }) it("don't crash on composer", async () => { const called = [] const detectEvent = (event: TraceEvent) => ({ onStop }: TraceProcess<'begin'>) => { onStop(() => { called.push(event) }) } const plugin = new Elysia() .onRequest(() => {}) .onTransform(() => {}) .onError(() => {}) .trace( { as: 'scoped' }, ({ onRequest, onParse, onTransform, onBeforeHandle, onHandle, onAfterHandle, onMapResponse, onAfterResponse }) => { onRequest(detectEvent('request')) onParse(detectEvent('parse')) onTransform(detectEvent('transform')) onBeforeHandle(detectEvent('beforeHandle')) onHandle(detectEvent('handle')) onAfterHandle(detectEvent('afterHandle')) onMapResponse(detectEvent('mapResponse')) onAfterResponse(detectEvent('afterResponse')) onAfterResponse(() => { expect(called).toEqual([ 'request', 'parse', 'transform', 'beforeHandle', 'handle', 'afterHandle', 'mapResponse' // afterResponse is being called so we can't check it yet ]) }) } ) const app = new Elysia().use(plugin).get('/', 'hi') await app.handle(req('/')) // wait for next tick await Bun.sleep(1) expect(called).toEqual([ 'request', 'parse', 'transform', 'beforeHandle', 'handle', 'afterHandle', 'mapResponse', 'afterResponse' ]) }) it('handle local scope', async () => { let called = false const plugin = new Elysia().trace(() => { called = true }) const parent = new Elysia().use(plugin) const main = new Elysia().use(parent).get('/', () => 'h') await main.handle(req('/')) expect(called).toBe(false) await parent.handle(req('/')) expect(called).toBe(false) await plugin.handle(req('/')) expect(called).toBe(true) }) it('handle scoped scope', async () => { let called = false const plugin = new Elysia().trace({ as: 'scoped' }, () => { called = true }) const parent = new Elysia().use(plugin) const main = new Elysia().use(parent).get('/', () => 'h') await main.handle(req('/')) expect(called).toBe(false) await parent.handle(req('/')) expect(called).toBe(true) await plugin.handle(req('/')) expect(called).toBe(true) }) it('handle global scope', async () => { let called = false const plugin = new Elysia().trace({ as: 'global' }, () => { called = true }) const parent = new Elysia().use(plugin) const main = new Elysia().use(parent).get('/', () => 'h') await main.handle(req('/')) expect(called).toBe(true) await parent.handle(req('/')) expect(called).toBe(true) await plugin.handle(req('/')) expect(called).toBe(true) }) it('handle as cast', async () => { let called = false const plugin = new Elysia().trace({ as: 'scoped' }, () => { called = true }) const parent = new Elysia().use(plugin).as('scoped') const main = new Elysia().use(parent).get('/', () => 'h') await main.handle(req('/')) expect(called).toBe(true) await parent.handle(req('/')) expect(called).toBe(true) await plugin.handle(req('/')) expect(called).toBe(true) }) it('deduplicate plugin when name is provided', () => { const a = new Elysia({ name: 'a' }).trace({ as: 'global' }, () => {}) const b = new Elysia().use(a) const app = new Elysia() .use(a) .use(a) .use(b) .use(b) .get('/', () => 'ok') expect(app.routes[0].hooks.trace.length).toBe(1) }) it('report error thrown in lifecycle event', async () => { let isCalled = false const app = new Elysia() .trace(({ onBeforeHandle }) => { onBeforeHandle(({ onEvent }) => { onEvent(({ onStop }) => { onStop(({ error }) => { if (error) isCalled = true expect(error).toBeInstanceOf(Error) }) }) }) }) .get('/', () => 'ok', { beforeHandle() { throw new Error('A') } }) await app.handle(req('/')) expect(isCalled).toBeTrue() }) it('report error return in lifecycle event', async () => { let isCalled = false const app = new Elysia() .trace(({ onBeforeHandle }) => { onBeforeHandle(({ onEvent }) => { onEvent(({ onStop }) => { onStop(({ error }) => { if (error) isCalled = true expect(error).toBeInstanceOf(Error) }) }) }) }) .get('/', () => 'ok', { beforeHandle() { return new Error('A') } }) await app.handle(req('/')) expect(isCalled).toBeTrue() }) it('report error to parent group', async () => { let isCalled = false const app = new Elysia() .trace(({ onBeforeHandle }) => { onBeforeHandle(({ onStop }) => { onStop(({ error }) => { if (error) isCalled = true expect(error).toBeInstanceOf(Error) }) }) }) .get('/', () => 'ok', { beforeHandle() { return new Error('A') } }) await app.handle(req('/')) expect(isCalled).toBeTrue() }) it('report resolve parent error', async () => { let isCalled = false const app = new Elysia() .trace(({ onBeforeHandle }) => { onBeforeHandle(async ({ error: err }) => { const error = await err if (error) isCalled = true expect(error).toBeInstanceOf(Error) }) }) .get('/', () => 'ok', { beforeHandle() { return new Error('A') } }) await app.handle(req('/')) expect(isCalled).toBeTrue() }) it('report error return in lifecycle event', async () => { let isCalled = false const app = new Elysia() .trace(({ onBeforeHandle }) => { onBeforeHandle(({ onStop }) => { onStop(({ error }) => { if (error) isCalled = true expect(error).toBeInstanceOf(Error) }) }) }) .get('/', () => 'ok', { beforeHandle() { return new Error('A') } }) await app.handle(req('/')) expect(isCalled).toBeTrue() }) it('report error throw in handle', async () => { let isCalled = false const app = new Elysia() .trace(({ onHandle }) => { onHandle(({ onStop }) => { onStop(({ error }) => { if (error) isCalled = true expect(error).toBeInstanceOf(Error) }) }) }) .get('/', () => { throw new Error('A') }) await app.handle(req('/')) expect(isCalled).toBeTrue() }) it('report error return in handle', async () => { let isCalled = false const app = new Elysia() .trace(({ onHandle }) => { onHandle(({ onStop }) => { onStop(({ error }) => { if (error) isCalled = true expect(error).toBeInstanceOf(Error) }) }) }) .get('/', () => { return new Error('A') }) await app.handle(req('/')) expect(isCalled).toBeTrue() }) it('report error throw in handle', async () => { let isCalled = false const app = new Elysia() .trace(({ onHandle }) => { onHandle(({ onStop }) => { onStop(({ error }) => { if (error) isCalled = true expect(error).toBeInstanceOf(Error) }) }) }) .get('/', () => { throw new Error('A') }) await app.handle(req('/')) expect(isCalled).toBeTrue() }) it('report route when using trace', async () => { let route: string | undefined const app = new Elysia() .trace(({ onHandle, context }) => { onHandle(({ onStop }) => { onStop(({ error }) => { route = context.route expect(error).toBeInstanceOf(Error) }) }) }) .get('/id/:id', () => { throw new Error('A') }) await app.handle(req('/id/1')) expect(route).toBe('/id/:id') }) it('defers stream for onHandle, and onAfterResponse', async () => { const order = [] const app = new Elysia() .trace(({ onHandle, onAfterResponse }) => { onHandle(({ onStop }) => { onStop(({ error }) => { order.push('HANDLE') }) }) onAfterResponse(() => { order.push('AFTER') }) }) .get('/', async function* () { for (let i = 0; i < 5; i++) { yield `${i}` await delay(1) } }) expect(order).toEqual([]) const response = await app.handle(req('/')) expect(order).toEqual([]) await response.text() expect(order).toEqual(['HANDLE', 'AFTER']) }) }) ================================================ FILE: test/type-system/array-buffer.test.ts ================================================ import Elysia, { t } from '../../src' import { describe, expect, it } from 'bun:test' import { Value } from '@sinclair/typebox/value' import { TypeBoxError } from '@sinclair/typebox' describe('TypeSystem - ArrayBuffer', () => { it('Create', () => { // @ts-expect-error expect(Value.Create(t.ArrayBuffer())).toEqual([1, 2, 3]) expect( Value.Create( t.ArrayString(t.Any(), { default: '[]' }) ) // @ts-expect-error ).toBe('[]') }) it('Check', () => { const schema = t.ArrayBuffer() expect(Value.Check(schema, new ArrayBuffer())).toBe(true) expect(Value.Check(schema, [1, 2, 3])).toBe(false) }) it('Encode', () => { const schema = t.ArrayBuffer() expect(() => Value.Encode(schema, [1, 2, 3])).toThrow(TypeBoxError) expect(() => Value.Encode(schema, 'test')).toThrow(TypeBoxError) expect(() => Value.Encode(schema, 123)).toThrow(TypeBoxError) expect(() => Value.Encode(schema, true)).toThrow(TypeBoxError) expect(() => Value.Encode(schema, null)).toThrow(TypeBoxError) expect(() => Value.Encode(schema, undefined)).toThrow(TypeBoxError) }) it('Decode', () => { const schema = t.ArrayBuffer() expect(Value.Decode(schema, new ArrayBuffer())).toEqual( new ArrayBuffer() ) expect(() => Value.Decode(schema, [1, 2, 3])).toThrow(TypeBoxError) expect(() => Value.Decode(schema, 'test')).toThrow(TypeBoxError) expect(() => Value.Decode(schema, 123)).toThrow(TypeBoxError) expect(() => Value.Decode(schema, true)).toThrow(TypeBoxError) expect(() => Value.Decode(schema, null)).toThrow(TypeBoxError) expect(() => Value.Decode(schema, undefined)).toThrow(TypeBoxError) }) it('Integrate', async () => { const app = new Elysia().post('/', ({ body }) => body, { body: t.ArrayBuffer(), response: t.ArrayBuffer() }) const response = await app.handle( new Request('http://localhost', { method: 'POST', body: new TextEncoder().encode('可愛くてごめん'), headers: { 'content-type': 'application/octet-stream' } }) ) expect(await response.text()).toBe('可愛くてごめん') }) }) ================================================ FILE: test/type-system/array-string.test.ts ================================================ import Elysia, { t } from '../../src' import { describe, expect, it } from 'bun:test' import { Value } from '@sinclair/typebox/value' import { TBoolean, TString, TypeBoxError } from '@sinclair/typebox' import { req } from '../utils' describe('TypeSystem - ArrayString', () => { it('Create', () => { // @ts-expect-error expect(Value.Create(t.ArrayString())).toBe(undefined) expect( Value.Create( t.ArrayString(t.Any(), { default: '[]' }) ) // @ts-expect-error ).toBe('[]') }) it('Check', () => { const schema = t.ArrayString(t.Number()) expect(Value.Check(schema, [1])).toBe(true) }) it('Encode', () => { const schema = t.ArrayString(t.Number()) expect(Value.Encode(schema, [1])).toBe( JSON.stringify([1]) ) expect(Value.Encode(schema, [1])).toBe( JSON.stringify([1]) ) }) it('Decode', () => { const schema = t.ArrayString(t.Number()) expect(Value.Decode(schema, '[1]')).toEqual([1]) expect(() => Value.Decode(schema, '1')).toThrow() }) it('Integrate', async () => { const app = new Elysia().post('/', ({ body }) => body, { body: t.Object({ id: t.ArrayString(t.Number()) }) }) const res1 = await app.handle( new Request('http://localhost', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ id: JSON.stringify([1, 2, 3]) }) }) ) expect(res1.status).toBe(200) const res2 = await app.handle( new Request('http://localhost', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ id: [1, 2, 3] }) }) ) expect(res2.status).toBe(200) const res3 = await app.handle( new Request('http://localhost', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ id: ['a', 2, 3] }) }) ) expect(res3.status).toBe(422) }) }) ================================================ FILE: test/type-system/boolean-string.test.ts ================================================ import Elysia, { t } from '../../src' import { describe, expect, it } from 'bun:test' import { Value } from '@sinclair/typebox/value' import { TBoolean, TypeBoxError } from '@sinclair/typebox' import { req } from '../utils' describe('TypeSystem - BooleanString', () => { it('Create', () => { expect(Value.Create(t.BooleanString())).toBe(false) expect(Value.Create(t.BooleanString({ default: true }))).toBe(true) }) it('Check', () => { const schema = t.BooleanString() expect(Value.Check(schema, true)).toBe(true) expect(Value.Check(schema, 'true')).toBe(true) expect(Value.Check(schema, false)).toBe(true) expect(Value.Check(schema, 'false')).toBe(true) expect(Value.Check(schema, 'yay')).toBe(false) expect(Value.Check(schema, 42)).toBe(false) expect(Value.Check(schema, {})).toBe(false) expect(Value.Check(schema, undefined)).toBe(false) expect(Value.Check(schema, null)).toBe(false) }) it('Encode', () => { const schema = t.BooleanString() expect(Value.Encode(schema, true)).toBe(true) expect(Value.Encode(schema, 'true')).toBe('true') expect(Value.Encode(schema, false)).toBe(false) expect(Value.Encode(schema, 'false')).toBe('false') const error = new TypeBoxError( 'The encoded value does not match the expected schema' ) expect(() => Value.Encode(schema, 'yay')).toThrow(error) expect(() => Value.Encode(schema, 42)).toThrow(error) expect(() => Value.Encode(schema, {})).toThrow(error) expect(() => Value.Encode(schema, undefined)).toThrow(error) expect(() => Value.Encode(schema, null)).toThrow(error) }) it('Decode', () => { const schema = t.BooleanString() expect(Value.Decode(schema, true)).toBe(true) expect(Value.Decode(schema, 'true')).toBe(true) expect(Value.Decode(schema, false)).toBe(false) expect(Value.Decode(schema, 'false')).toBe(false) const error = new TypeBoxError( 'Unable to decode value as it does not match the expected schema' ) expect(() => Value.Decode(schema, 'yay')).toThrow(error) expect(() => Value.Decode(schema, 42)).toThrow(error) expect(() => Value.Decode(schema, {})).toThrow(error) expect(() => Value.Decode(schema, undefined)).toThrow(error) expect(() => Value.Decode(schema, null)).toThrow(error) }) // it('Convert', () => { // expect(Value.Convert(t.BooleanString(), 'true')).toBe(true) // expect(Value.Convert(t.BooleanString(), 'false')).toBe(false) // }) it('Integrate', async () => { const app = new Elysia().get('/', ({ query }) => query, { query: t.Object({ value: t.BooleanString() }) }) const res1 = await app.handle(req('/?value=true')) expect(res1.status).toBe(200) const res2 = await app.handle(req('/?value=false')) expect(res2.status).toBe(200) const res3 = await app.handle(req('/?value=aight')) expect(res3.status).toBe(422) }) }) ================================================ FILE: test/type-system/coercion-number.test.ts ================================================ import { Elysia, t } from '../../src' import { describe, it, expect } from 'bun:test' describe('Coercion - Numeric -> Number', () => { it('work', async () => { const app = new Elysia().get( '/:entityType', ({ params: { entityType } }) => entityType, { params: t.Object({ entityType: t.Number() }) } ) const response = await app.handle(new Request('http://localhost/999')) expect(response.status).toBe(200) }) it('handle property', async () => { const numberApp = new Elysia() .onError(({ code }) => code) .get('/:entityType', ({ params: { entityType } }) => entityType, { params: t.Object({ entityType: t.Number({ minimum: 0, maximum: 3, multipleOf: 1 }) }) }) const numericApp = new Elysia() .onError(({ code }) => code) .get('/:entityType', ({ params: { entityType } }) => entityType, { params: t.Object({ entityType: t.Numeric({ minimum: 0, maximum: 3, multipleOf: 1 }) }) }) async function expectValidResponse(response: Response) { expect(response.status).toBe(422) const body = await response.text() expect(body).not.toBe('999') expect(body).toBe('VALIDATION') } await expectValidResponse( await numberApp.handle(new Request('http://localhost/999')) ) await expectValidResponse( await numericApp.handle(new Request('http://localhost/999')) ) }) }) ================================================ FILE: test/type-system/date.test.ts ================================================ import Elysia, { t } from '../../src' import { describe, expect, it } from 'bun:test' import { Value } from '@sinclair/typebox/value' import { TBoolean, TDate, TUnion, TypeBoxError } from '@sinclair/typebox' import { post } from '../utils' describe('TypeSystem - Date', () => { it('Create', () => { expect(Value.Create(t.Date())).toBeInstanceOf(Date) }) it('No default date provided', () => { const schema = t.Date() expect(schema.default).toBeUndefined() const unionSchema = schema as unknown as TUnion for (const type of unionSchema.anyOf) { expect(type.default).toBeUndefined() } }) it('Default date provided', () => { const given = new Date('2025-01-01T00:00:00.000Z') const schema = t.Date({ default: given }) expect(schema.default).toEqual(given) const unionSchema = schema as unknown as TUnion for (const type of unionSchema.anyOf) { expect(new Date(type.default)).toEqual(given) } }) it('Check', () => { const schema = t.Date() expect(Value.Check(schema, new Date())).toEqual(true) expect(Value.Check(schema, '2021/1/1')).toEqual(true) expect(Value.Check(schema, 'yay')).toEqual(false) expect(Value.Check(schema, 42)).toEqual(true) expect(Value.Check(schema, {})).toEqual(false) expect(Value.Check(schema, undefined)).toEqual(false) expect(Value.Check(schema, null)).toEqual(false) }) it('Encode', () => { const schema = t.Date() const date = new Date() expect(Value.Encode(schema, date)).toBe( date.toISOString() ) expect(() => Value.Encode(schema, 'yay')).toThrowError() expect(() => Value.Encode(schema, Value.Decode(schema, 42)) ).not.toThrowError() expect(() => new Date().toISOString()).not.toThrowError() expect(() => Value.Encode(schema, {})).toThrowError() expect(() => Value.Encode(schema, undefined)).toThrowError() expect(() => Value.Encode(schema, null)).toThrowError() }) it('Decode', () => { const schema = t.Date() expect(Value.Decode(schema, new Date())).toBeInstanceOf( Date ) expect(Value.Decode(schema, '2021/1/1')).toBeInstanceOf( Date ) const error = new TypeBoxError( 'Unable to decode value as it does not match the expected schema' ) expect(() => Value.Decode(schema, 'yay')).toThrow(error) expect(() => Value.Decode(schema, 42)).not.toThrow(error) expect(() => Value.Decode(schema, {})).toThrow(error) expect(() => Value.Decode(schema, undefined)).toThrow(error) expect(() => Value.Decode(schema, null)).toThrow(error) }) it('Integrate', async () => { const app = new Elysia().post('/', ({ body: { date } }) => date, { body: t.Object({ date: t.Date() }) }) const res1 = await app.handle( post('/', { date: new Date() }) ) expect(res1.status).toBe(200) const res2 = await app.handle( post('/', { date: '2021/1/1' }) ) expect(res2.status).toBe(200) const res3 = await app.handle( post('/', { date: 'Skibidi dom dom yes yes' }) ) expect(res3.status).toBe(422) }) }) ================================================ FILE: test/type-system/files.test.ts ================================================ import { Elysia, t, ValidationError } from '../../src' import { describe, expect, it } from 'bun:test' import { post, upload } from '../utils' describe('Files', () => { it('validate minItems, maxItems', async () => { const app = new Elysia().post('/', () => 'ok', { body: t.Object({ file: t.Files({ minItems: 2, maxItems: 2 }) }) }) // case 1 fail: less than 2 files { const body = new FormData() body.append('file', Bun.file('test/images/millenium.jpg')) const response = await app.handle( new Request('http://localhost/', { method: 'POST', body }) ) expect(response.status).toBe(422) } // case 2 pass: all valid images { const body = new FormData() body.append('file', Bun.file('test/images/millenium.jpg')) body.append('file', Bun.file('test/images/kozeki-ui.webp')) const response = await app.handle( new Request('http://localhost/', { method: 'POST', body }) ) expect(response.status).toBe(200) } // case 3 fail: more than 2 files { const body = new FormData() body.append('file', Bun.file('test/images/millenium.jpg')) body.append('file', Bun.file('test/images/kozeki-ui.webp')) body.append('file', Bun.file('test/images/midori.png')) const response = await app.handle( new Request('http://localhost/', { method: 'POST', body }) ) expect(response.status).toBe(422) } }) // Union schema tests - testing that getSchemaProperties handles Union correctly it('handle file in Union schema', async () => { const app = new Elysia().post('/', ({ body }) => 'ok', { body: t.Union([ t.Object({ avatar: t.File(), type: t.Literal('image') }), t.Object({ document: t.File(), type: t.Literal('doc') }) ]) }) const body = new FormData() body.append('avatar', Bun.file('test/images/millenium.jpg')) body.append('type', 'image') const response = await app.handle( new Request('http://localhost/', { method: 'POST', body }) ) expect(response.status).toBe(200) }) it('handle multiple files in Union schema', async () => { const app = new Elysia().post('/', ({ body }) => 'ok', { body: t.Union([ t.Object({ images: t.Files(), category: t.Literal('gallery') }), t.Object({ documents: t.Files(), category: t.Literal('archive') }) ]) }) const body = new FormData() body.append('images', Bun.file('test/images/millenium.jpg')) body.append('images', Bun.file('test/images/kozeki-ui.webp')) body.append('category', 'gallery') const response = await app.handle( new Request('http://localhost/', { method: 'POST', body }) ) expect(response.status).toBe(200) }) }) ================================================ FILE: test/type-system/form.test.ts ================================================ import { Elysia, file, form, t } from '../../src' import { describe, expect, it } from 'bun:test' import { Value } from '@sinclair/typebox/value' import { req } from '../utils' describe('TypeSystem - Form', () => { it('Create', () => { expect(Value.Create(t.Form({}))).toEqual(form({})) expect( Value.Create( t.Form( {}, { default: form({ name: 'saltyaom' }) } ) ) ).toEqual( form({ name: 'saltyaom' }) ) }) it('Check', () => { const schema = t.Form({ name: t.String(), age: t.Number() }) expect( Value.Check( schema, form({ name: 'saltyaom', age: 20 }) ) ).toBe(true) try { Value.Check( schema, form({ name: 'saltyaom' }) ) expect(true).toBe(false) } catch { expect(true).toBe(true) } }) it('Integrate', async () => { const app = new Elysia() .get( '/form/:name', ({ params: { name } }) => form({ name: name as any }), { response: t.Form({ name: t.Literal('saltyaom') }) } ) .get( '/file', () => form({ teapot: file('example/teapot.webp') }), { response: t.Form({ teapot: t.File() }) } ) const res1 = await app.handle(req('/form/saltyaom')) expect(res1.status).toBe(200) const res2 = await app.handle(req('/form/felis')) expect(res2.status).toBe(422) const res3 = await app.handle(req('/file')) expect(res3.status).toBe(200) }) }) ================================================ FILE: test/type-system/formdata.test.ts ================================================ import { describe, expect, it } from 'bun:test' import { z } from 'zod' import { Elysia, fileType, t, type ValidationError } from '../../src' const variantObject = t.Object({ price: t.Number({ minimum: 0 }), weight: t.Number({ minimum: 0 }) }) const metadataObject = { category: t.String(), tags: t.Array(t.String()), inStock: t.Boolean() } const postProductModel = t.Object({ name: t.String(), variants: t.Array(variantObject), metadata: t.Object(metadataObject), image: t.File({ type: 'image' }) }) type postProductModel = typeof postProductModel.static const patchProductModel = t.Object({ name: t.Optional(t.String()), variants: t.Optional(t.Array(variantObject)), metadata: t.Optional(t.Object(metadataObject)), image: t.Optional(t.File({ type: 'image' })) }) type patchProductModel = typeof patchProductModel.static const postProductModelComplex = t.Object({ name: t.String(), variants: t.ArrayString(variantObject), metadata: t.ObjectString(metadataObject), image: t.File({ type: 'image' }) }) type postProductModelComplex = typeof postProductModelComplex.static const patchProductModelComplex = t.Object({ name: t.Optional(t.String()), variants: t.Optional(t.ArrayString(variantObject)), metadata: t.Optional(t.ObjectString(metadataObject)), image: t.Optional(t.File({ type: 'image' })) }) type patchProductModelComplex = typeof patchProductModelComplex.static const app = new Elysia() .post('/product', async ({ body, status }) => status('Created', body), { body: postProductModel }) .patch( '/product/:id', ({ body, params }) => ({ id: params.id, ...body }), { body: patchProductModel } ) .post( '/product-complex', async ({ body, status }) => status('Created', body), { body: postProductModelComplex } ) .patch( '/product-complex/:id', ({ body, params }) => ({ id: params.id, ...body }), { body: patchProductModelComplex } ) describe('Nested FormData with mandatory bunFile (post operation)', async () => { const bunFilePath1 = `${import.meta.dir}/../images/aris-yuzu.jpg` const bunFile = Bun.file(bunFilePath1) as File const newProduct: postProductModel = { name: 'Test Product', variants: [ { price: 10, weight: 100 }, { price: 2.7, weight: 32 } ], metadata: { category: 'Electronics', tags: ['new', 'featured', 'sale'], inStock: true }, image: bunFile } it('should create a product', async () => { const stringifiedVariants = JSON.stringify(newProduct.variants) const stringifiedMetadata = JSON.stringify(newProduct.metadata) const body = new FormData() body.append('name', newProduct.name) body.append('variants', stringifiedVariants) body.append('metadata', stringifiedMetadata) body.append('image', bunFile) const response = await app.handle( new Request('http://localhost/product', { method: 'POST', body }) ) expect(response.status).toBe(201) const data = await response.json() expect(data).toEqual(newProduct) }) it('should return validation error on nested ArrayString', async () => { const stringifiedVariants = JSON.stringify([ { price: 23, waighTypo: '' } ]) const stringifiedMetadata = JSON.stringify(newProduct.metadata) const body = new FormData() body.append('name', newProduct.name) body.append('variants', stringifiedVariants) body.append('metadata', stringifiedMetadata) body.append('image', bunFile) const response = await app.handle( new Request('http://localhost/product', { method: 'POST', body }) ) const data = (await response.json()) as ValidationError expect(response.status).toBe(422) expect(data.type).toBe('validation') }) it('should return validation error on nested ObjectString', async () => { const stringifiedVariants = JSON.stringify(newProduct.variants) const stringifiedMetadata = JSON.stringify({ categoryTypo: 'Electronics', tags: ['new', 'featured', 'sale'], inStock: true }) const body = new FormData() body.append('name', newProduct.name) body.append('variants', stringifiedVariants) body.append('metadata', stringifiedMetadata) body.append('image', bunFile) const response = await app.handle( new Request('http://localhost/product', { method: 'POST', body }) ) const data = (await response.json()) as ValidationError expect(response.status).toBe(422) expect(data.type).toBe('validation') }) }) describe('Nested FormData with optionnal file (patch operation)', async () => { const bunFilePath2 = `${import.meta.dir}/../images/aris-yuzu.jpg` const bunFile = Bun.file(bunFilePath2) as File it('PATCH with bunFile and omitted optional t.ObjectString', async () => { const body = new FormData() body.append('name', 'Updated Product') body.append('image', bunFile) // metadata and variants fields are omitted (should be OK since they're optional) const response = await app.handle( new Request('http://localhost/product/123', { method: 'PATCH', body }) ) expect(response.status).toBe(200) const data = (await response.json()) as patchProductModel expect(data).not.toBeNull() expect(data?.name).toBe('Updated Product') expect(data?.metadata).toBeUndefined() expect(data?.variants).toBeUndefined() }) it('PATCH with file and valid t.ObjectString and t.ArrayString data', async () => { const body = new FormData() body.append('name', 'Updated Product') body.append('image', bunFile) body.append( 'metadata', JSON.stringify({ category: 'Electronics', tags: ['sale', 'new'], inStock: true }) ) body.append( 'variants', JSON.stringify([ { price: 15, weight: 200 } ]) ) const response = await app.handle( new Request('http://localhost/product/123', { method: 'PATCH', body }) ) expect(response.status).toBe(200) const data = (await response.json()) as patchProductModel expect(data).not.toBeNull() expect(data?.name).toBe('Updated Product') expect(data?.metadata).toEqual({ category: 'Electronics', tags: ['sale', 'new'], inStock: true }) expect(data?.variants).toEqual([ { price: 15, weight: 200 } ]) }) it('PATCH without file and omitted optional t.ObjectString and optional t.ArrayString', async () => { const response = await app.handle( new Request('http://localhost/product/123', { method: 'PATCH', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ name: 'Updated Product' }) }) ) expect(response.status).toBe(200) const data = (await response.json()) as patchProductModel expect(data).not.toBeNull() expect(data?.name).toBe('Updated Product') expect(data?.image).toBeUndefined() expect(data?.metadata).toBeUndefined() expect(data?.variants).toBeUndefined() }) it('PATCH should return validation error on invalid ObjectString', async () => { const body = new FormData() body.append('name', 'Updated Product') body.append('image', bunFile) body.append( 'metadata', JSON.stringify({ categoryTypo: 'Electronics', // Wrong property name tags: ['sale'], inStock: true }) ) const response = await app.handle( new Request('http://localhost/product/123', { method: 'PATCH', body }) ) expect(response.status).toBe(422) const data = (await response.json()) as ValidationError expect(data.type).toBe('validation') }) it('PATCH should return validation error on invalid ArrayString', async () => { const body = new FormData() body.append('name', 'Updated Product') body.append('image', bunFile) body.append( 'variants', JSON.stringify([ { priceTypo: 15, // Wrong property name weight: 200 } ]) ) const response = await app.handle( new Request('http://localhost/product/123', { method: 'PATCH', body }) ) expect(response.status).toBe(422) const data = (await response.json()) as ValidationError expect(data.type).toBe('validation') }) }) describe('Nested FormData with t.ArrayString and t.ObjectString (POST operation)', async () => { const bunFilePath3 = `${import.meta.dir}/../images/aris-yuzu.jpg` const bunFile = Bun.file(bunFilePath3) as File const newProductComplex: postProductModelComplex = { name: 'Test Product Complex', variants: [ { price: 10, weight: 100 }, { price: 2.7, weight: 32 } ], metadata: { category: 'Electronics', tags: ['new', 'featured', 'sale'], inStock: true }, image: bunFile } it('should create a product with t.ArrayString and t.ObjectString', async () => { const stringifiedVariants = JSON.stringify(newProductComplex.variants) const stringifiedMetadata = JSON.stringify(newProductComplex.metadata) const body = new FormData() body.append('name', newProductComplex.name) body.append('variants', stringifiedVariants) body.append('metadata', stringifiedMetadata) body.append('image', bunFile) const response = await app.handle( new Request('http://localhost/product-complex', { method: 'POST', body }) ) expect(response.status).toBe(201) const data = await response.json() expect(data).toEqual(newProductComplex) }) it('should return validation error on invalid t.ArrayString nested structure', async () => { const stringifiedVariants = JSON.stringify([ { price: 23, weightTypo: 100 // Wrong property name } ]) const stringifiedMetadata = JSON.stringify(newProductComplex.metadata) const body = new FormData() body.append('name', newProductComplex.name) body.append('variants', stringifiedVariants) body.append('metadata', stringifiedMetadata) body.append('image', bunFile) const response = await app.handle( new Request('http://localhost/product-complex', { method: 'POST', body }) ) const data = (await response.json()) as ValidationError expect(response.status).toBe(422) expect(data.type).toBe('validation') }) it('should return validation error on invalid t.ObjectString nested structure', async () => { const stringifiedVariants = JSON.stringify(newProductComplex.variants) const stringifiedMetadata = JSON.stringify({ categoryTypo: 'Electronics', // Wrong property name tags: ['new', 'featured', 'sale'], inStock: true }) const body = new FormData() body.append('name', newProductComplex.name) body.append('variants', stringifiedVariants) body.append('metadata', stringifiedMetadata) body.append('image', bunFile) const response = await app.handle( new Request('http://localhost/product-complex', { method: 'POST', body }) ) const data = (await response.json()) as ValidationError expect(response.status).toBe(422) expect(data.type).toBe('validation') }) it('should return validation error when variants is not a valid JSON string', async () => { const stringifiedMetadata = JSON.stringify(newProductComplex.metadata) const body = new FormData() body.append('name', newProductComplex.name) body.append('variants', 'not-valid-json') body.append('metadata', stringifiedMetadata) body.append('image', bunFile) const response = await app.handle( new Request('http://localhost/product-complex', { method: 'POST', body }) ) const data = (await response.json()) as ValidationError expect(response.status).toBe(422) expect(data.type).toBe('validation') }) it('should return validation error when metadata is not a valid JSON string', async () => { const stringifiedVariants = JSON.stringify(newProductComplex.variants) const body = new FormData() body.append('name', newProductComplex.name) body.append('variants', stringifiedVariants) body.append('metadata', 'not-valid-json') body.append('image', bunFile) const response = await app.handle( new Request('http://localhost/product-complex', { method: 'POST', body }) ) const data = (await response.json()) as ValidationError expect(response.status).toBe(422) expect(data.type).toBe('validation') }) }) describe('Nested FormData with optional t.ArrayString and t.ObjectString (PATCH operation)', async () => { const bunFilePath4 = `${import.meta.dir}/../images/aris-yuzu.jpg` const bunFile = Bun.file(bunFilePath4) as File it('PATCH with bunFile and omitted optional t.ObjectString and t.ArrayString', async () => { const body = new FormData() body.append('name', 'Updated Product Complex') body.append('image', bunFile) // metadata and variants fields are omitted (should be OK since they're optional) const response = await app.handle( new Request('http://localhost/product-complex/456', { method: 'PATCH', body }) ) expect(response.status).toBe(200) const data = (await response.json()) as patchProductModelComplex expect(data).not.toBeNull() expect(data?.name).toBe('Updated Product Complex') expect(data?.metadata).toBeUndefined() expect(data?.variants).toBeUndefined() }) it('PATCH with file and valid t.ObjectString and t.ArrayString data', async () => { const body = new FormData() body.append('name', 'Updated Product Complex') body.append('image', bunFile) body.append( 'metadata', JSON.stringify({ category: 'Electronics', tags: ['sale', 'new'], inStock: true }) ) body.append( 'variants', JSON.stringify([ { price: 15, weight: 200 } ]) ) const response = await app.handle( new Request('http://localhost/product-complex/456', { method: 'PATCH', body }) ) expect(response.status).toBe(200) const data = (await response.json()) as patchProductModelComplex expect(data).not.toBeNull() expect(data?.name).toBe('Updated Product Complex') expect(data?.metadata).toEqual({ category: 'Electronics', tags: ['sale', 'new'], inStock: true }) expect(data?.variants).toEqual([ { price: 15, weight: 200 } ]) }) it('PATCH without file and omitted optional fields', async () => { const response = await app.handle( new Request('http://localhost/product-complex/456', { method: 'PATCH', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ name: 'Updated Product Complex' }) }) ) expect(response.status).toBe(200) const data = (await response.json()) as patchProductModelComplex expect(data).not.toBeNull() expect(data?.name).toBe('Updated Product Complex') expect(data?.image).toBeUndefined() expect(data?.metadata).toBeUndefined() expect(data?.variants).toBeUndefined() }) it('PATCH should return validation error on invalid t.ObjectString', async () => { const body = new FormData() body.append('name', 'Updated Product Complex') body.append('image', bunFile) body.append( 'metadata', JSON.stringify({ categoryTypo: 'Electronics', // Wrong property name tags: ['sale'], inStock: true }) ) const response = await app.handle( new Request('http://localhost/product-complex/456', { method: 'PATCH', body }) ) expect(response.status).toBe(422) const data = (await response.json()) as ValidationError expect(data.type).toBe('validation') }) it('PATCH should return validation error on invalid t.ArrayString', async () => { const body = new FormData() body.append('name', 'Updated Product Complex') body.append('image', bunFile) body.append( 'variants', JSON.stringify([ { priceTypo: 15, // Wrong property name weight: 200 } ]) ) const response = await app.handle( new Request('http://localhost/product-complex/456', { method: 'PATCH', body }) ) expect(response.status).toBe(422) const data = (await response.json()) as ValidationError expect(data.type).toBe('validation') }) it('PATCH should return validation error when metadata is not valid JSON', async () => { const body = new FormData() body.append('name', 'Updated Product Complex') body.append('image', bunFile) body.append('metadata', 'invalid-json') const response = await app.handle( new Request('http://localhost/product-complex/456', { method: 'PATCH', body }) ) expect(response.status).toBe(422) const data = (await response.json()) as ValidationError expect(data.type).toBe('validation') }) it('PATCH should return validation error when variants is not valid JSON', async () => { const body = new FormData() body.append('name', 'Updated Product Complex') body.append('image', bunFile) body.append('variants', 'invalid-json') const response = await app.handle( new Request('http://localhost/product-complex/456', { method: 'PATCH', body }) ) expect(response.status).toBe(422) const data = (await response.json()) as ValidationError expect(data.type).toBe('validation') }) }) describe('Model reference with File and nested Object', () => { const bunFilePath5 = `${import.meta.dir}/../images/aris-yuzu.jpg` const bunFile = Bun.file(bunFilePath5) as File it('should coerce nested Object to ObjectString when using model reference', async () => { const app = new Elysia() .model( 'userWithAvatar', t.Object({ name: t.String(), avatar: t.File(), metadata: t.Object({ age: t.Number() }) }) ) .post('/user', ({ body }) => body, { body: 'userWithAvatar' }) const formData = new FormData() formData.append('name', 'John') formData.append('avatar', bunFile) formData.append('metadata', JSON.stringify({ age: 25 })) const response = await app.handle( new Request('http://localhost/user', { method: 'POST', body: formData }) ) expect(response.status).toBe(200) const result = await response.json() expect(result).toMatchObject({ name: 'John', metadata: { age: 25 } }) }) }) describe('Zod (for standard schema) with File and nested Object', () => { const bunFilePath6 = `test/images/aris-yuzu.jpg` const bunFile = Bun.file(bunFilePath6) as File it('should handle Zod schema with File and nested object (without manual coercion)', async () => { const app = new Elysia().post('/upload', ({ body }) => body, { body: z.object({ name: z.string(), file: z.file().refine((file) => fileType(file, 'image/jpeg')), metadata: z.object({ age: z.coerce.number() }) }) }) const formData = new FormData() formData.append('name', 'John') formData.append('file', bunFile) formData.append('metadata', JSON.stringify({ age: '25' })) const response = await app.handle( new Request('http://localhost/upload', { method: 'POST', body: formData }) ) const result = await response.json() expect(response.status).toBe(200) expect(result).toMatchObject({ name: 'John', metadata: { age: 25 } }) }) it('should handle array JSON strings in FormData', async () => { const app = new Elysia().post('/upload', ({ body }) => body, { body: z.object({ file: z.file().refine((file) => fileType(file, 'image/jpeg')), tags: z.array(z.string()) }) }) const formData = new FormData() formData.append('file', bunFile) formData.append('tags', 'tag1') formData.append('tags', 'tag2') formData.append('tags', 'tag3') const response = await app.handle( new Request('http://localhost/upload', { method: 'POST', body: formData }) ) const result = await response.json() expect(response.status).toBe(200) expect(result).toMatchObject({ tags: ['tag1', 'tag2', 'tag3'] }) }) it('should keep invalid JSON as string', async () => { const app = new Elysia().post('/upload', ({ body }) => body, { body: z.object({ file: z.file().refine((file) => fileType(file, 'image/jpeg')), description: z.string() }) }) const formData = new FormData() formData.append('file', bunFile) formData.append('description', '{invalid json}') const response = await app.handle( new Request('http://localhost/upload', { method: 'POST', body: formData }) ) const result = await response.json() expect(response.status).toBe(200) expect(result).toMatchObject({ description: '{invalid json}' }) }) it('should keep plain strings that are not JSON', async () => { const app = new Elysia().post('/upload', ({ body }) => body, { body: z.object({ file: z.file().refine((file) => fileType(file, 'image/jpeg')), comment: z.string() }) }) const formData = new FormData() formData.append('file', bunFile) formData.append('comment', 'This is a plain comment') const response = await app.handle( new Request('http://localhost/upload', { method: 'POST', body: formData }) ) const result = await response.json() expect(response.status).toBe(200) expect(result).toMatchObject({ comment: 'This is a plain comment' }) }) it('should handle nested objects in JSON', async () => { const app = new Elysia().post('/upload', ({ body }) => body, { body: z.object({ file: z.file().refine((file) => fileType(file, 'image/jpeg')), profile: z.object({ user: z.object({ name: z.string(), age: z.coerce.number() }), settings: z.object({ notifications: z.coerce.boolean() }) }) }) }) const formData = new FormData() formData.append('file', bunFile) formData.append( 'profile', JSON.stringify({ user: { name: 'Alice', age: 30 }, settings: { notifications: true } }) ) const response = await app.handle( new Request('http://localhost/upload', { method: 'POST', body: formData }) ) const result = await response.json() expect(response.status).toBe(200) expect(result).toMatchObject({ profile: { user: { name: 'Alice', age: 30 }, settings: { notifications: true } } }) }) it('should handle Zod schema with optional fields', async () => { const app = new Elysia().post('/upload', ({ body }) => body, { body: z.object({ file: z.file().refine((file) => fileType(file, 'image/jpeg')), name: z.string(), description: z.string().optional(), metadata: z .object({ category: z.string(), tags: z.array(z.string()).optional(), featured: z.boolean().optional() }) .optional() }) }) const formData = new FormData() formData.append('file', bunFile) formData.append('name', 'Test Product') // Omit optional fields const response = await app.handle( new Request('http://localhost/upload', { method: 'POST', body: formData }) ) const result = await response.json() expect(response.status).toBe(200) expect(result).toMatchObject({ name: 'Test Product' }) expect(result).not.toHaveProperty('description') expect(result).not.toHaveProperty('metadata') }) it('should handle Zod schema with optional fields provided', async () => { const app = new Elysia().post('/upload', ({ body }) => body, { body: z.object({ file: z.file().refine((file) => fileType(file, 'image/jpeg')), name: z.string(), description: z.string().optional(), metadata: z .object({ category: z.string(), tags: z.array(z.string()).optional(), featured: z.coerce.boolean().optional() }) .optional() }) }) const formData = new FormData() formData.append('file', bunFile) formData.append('name', 'Test Product') formData.append('description', 'A test description') formData.append( 'metadata', JSON.stringify({ category: 'electronics', tags: ['phone', 'mobile'], featured: true }) ) const response = await app.handle( new Request('http://localhost/upload', { method: 'POST', body: formData }) ) const result = await response.json() expect(response.status).toBe(200) expect(result).toMatchObject({ name: 'Test Product', description: 'A test description', metadata: { category: 'electronics', tags: ['phone', 'mobile'], featured: true } }) }) }) ================================================ FILE: test/type-system/import.ts ================================================ try { const { TypeSystem } = await import('../../src/type-system') console.log(TypeSystem && `✅ TypeSystem import works!`) } catch (cause) { throw new Error('❌ TypeSystem import failed', { cause }) } export {} ================================================ FILE: test/type-system/object-string.test.ts ================================================ import Elysia, { t } from '../../src' import { describe, expect, it } from 'bun:test' import { Value } from '@sinclair/typebox/value' import { req } from '../utils' describe('TypeSystem - ObjectString', () => { it('Create', () => { expect(Value.Create(t.ObjectString({}))).toBeUndefined() expect( Value.Create( t.ObjectString({}, { default: '{}' }) ) ).toBe('{}') }) it('Check', () => { const schema = t.ObjectString({ pageIndex: t.Number(), pageLimit: t.Number() }) expect(Value.Check(schema, { pageIndex: 1, pageLimit: 1 })).toBe(true) }) it('Encode', () => { const schema = t.ObjectString({ pageIndex: t.Number(), pageLimit: t.Number() }) expect( Value.Encode(schema, { pageIndex: 1, pageLimit: 1 }) ).toBe(JSON.stringify({ pageIndex: 1, pageLimit: 1 })) expect( Value.Encode(schema, { pageIndex: 1, pageLimit: 1 }) ).toBe(JSON.stringify({ pageIndex: 1, pageLimit: 1 })) }) it('Decode', () => { const schema = t.ObjectString({ pageIndex: t.Number(), pageLimit: t.Number() }) expect( Value.Decode( schema, JSON.stringify({ pageIndex: 1, pageLimit: 1 }) ) ).toEqual({ pageIndex: 1, pageLimit: 1 }) expect(() => Value.Decode( schema, JSON.stringify({ pageLimit: 1 }) ) ).toThrow() }) it('Integrate', async () => { const app = new Elysia().get('/', ({ query }) => query, { query: t.Object({ pagination: t.ObjectString({ pageIndex: t.Number(), pageLimit: t.Number() }) }) }) const res1 = await app.handle( req('/?pagination={"pageIndex":1,"pageLimit":1}') ) expect(res1.status).toBe(200) const res2 = await app.handle(req('/?pagination={"pageLimit":1}')) expect(res2.status).toBe(422) }) it('Optional', async () => { const schema = t.Object({ name: t.String(), metadata: t.Optional(t.ObjectString({ pageIndex: t.Number(), pageLimit: t.Number() })) }) expect(Value.Check(schema, { name: 'test' })).toBe(true) expect(Value.Create(schema).metadata).toBeUndefined() expect(Value.Check(schema, { name: 'test', metadata: { pageIndex: 1, pageLimit: 10 } })).toBe(true) expect(Value.Check(schema, { name: 'test', metadata: {} })).toBe(false) }) it('Default value', async () => { const schema = t.ObjectString({ pageIndex: t.Number(), pageLimit: t.Number() }, { default: { pageIndex: 0, pageLimit: 10 } }) expect(Value.Create(schema)).toEqual({ pageIndex: 0, pageLimit: 10 }) expect(Value.Check(schema, { pageIndex: 1, pageLimit: 20 })).toBe(true) expect(Value.Check(schema, { pageIndex: 0, pageLimit: 10 })).toBe(true) expect(Value.Check(schema, JSON.stringify({ pageIndex: 1, pageLimit: 20 }))).toBe(true) expect(Value.Check(schema, JSON.stringify({ pageIndex: 0, pageLimit: 10 }))).toBe(true) expect(Value.Check(schema, {})).toBe(false) expect(Value.Check(schema, { pageIndex: 1 })).toBe(false) expect(Value.Check(schema, undefined)).toBe(false) }) }) ================================================ FILE: test/type-system/string-format.test.ts ================================================ import Elysia, { t } from '../../src' import { describe, expect, it } from 'bun:test' import { Value } from '@sinclair/typebox/value' import { TBoolean, TString, TypeBoxError } from '@sinclair/typebox' import { req } from '../utils' describe('TypeSystem - ObjectString', () => { it('Format email', async () => { const testString = "foo@example.com"; const app = new Elysia().get( '/', ({ query }) => query, { query: t.Object({ email: t.String({ format: 'email' }) }) } ) const res1 = await app.handle(req(`/?email=${testString}`)) expect(res1.status).toBe(200); expect(await (res1).json()).toEqual({ email: testString }) }) it('Format hostname', async () => { const testString = "www"; const app = new Elysia().get( '/', ({ query }) => query, { query: t.Object({ host: t.String({ format: 'hostname' }) }) } ) const res1 = await app.handle(req(`/?host=${testString}`)) expect(res1.status).toBe(200); expect(await (res1).json()).toEqual({ host: testString }) }) it('Format date', async () => { const testString = "2024-01-01"; const app = new Elysia().get( '/', ({ query }) => query, { query: t.Object({ date: t.String({ format: 'date' }) }) } ) const res1 = await app.handle(req(`/?date=${testString}`)) expect(res1.status).toBe(200); expect(await (res1).json()).toEqual({ date: testString }) }) }) ================================================ FILE: test/type-system/uint8array.test.ts ================================================ import Elysia, { t } from '../../src' import { describe, expect, it } from 'bun:test' import { Value } from '@sinclair/typebox/value' import { TypeBoxError } from '@sinclair/typebox' describe('TypeSystem - Uint8Array', () => { // it('Create', () => { // // @ts-expect-error // expect(Value.Create(t.Uint8Array())).toEqual([1, 2, 3]) // expect( // Value.Create( // t.ArrayString(t.Any(), { // default: '[]' // }) // ) // // @ts-expect-error // ).toBe('[]') // }) // it('Check', () => { // const schema = t.Uint8Array() // expect(Value.Check(schema, new ArrayBuffer())).toBe(true) // expect(Value.Check(schema, new TextEncoder().encode('hello!'))).toBe( // true // ) // expect(Value.Check(schema, [1, 2, 3])).toBe(false) // expect( // Value.Check( // t.Uint8Array({ // maxByteLength: 2 // }), // new TextEncoder().encode('hello!') // ) // ).toBe(false) // }) // it('Encode', () => { // const schema = t.Uint8Array() // expect(() => Value.Encode(schema, [1, 2, 3])).toThrow(TypeBoxError) // expect(() => Value.Encode(schema, 'test')).toThrow(TypeBoxError) // expect(() => Value.Encode(schema, 123)).toThrow(TypeBoxError) // expect(() => Value.Encode(schema, true)).toThrow(TypeBoxError) // expect(() => Value.Encode(schema, null)).toThrow(TypeBoxError) // expect(() => Value.Encode(schema, undefined)).toThrow(TypeBoxError) // }) // it('Decode', () => { // const schema = t.Uint8Array() // expect(Value.Decode(schema, new ArrayBuffer())).toEqual( // new Uint8Array() // ) // expect(() => Value.Decode(schema, [1, 2, 3])).toThrow(TypeBoxError) // expect(() => Value.Decode(schema, 'test')).toThrow(TypeBoxError) // expect(() => Value.Decode(schema, 123)).toThrow(TypeBoxError) // expect(() => Value.Decode(schema, true)).toThrow(TypeBoxError) // expect(() => Value.Decode(schema, null)).toThrow(TypeBoxError) // expect(() => Value.Decode(schema, undefined)).toThrow(TypeBoxError) // }) it('Integrate', async () => { const app = new Elysia().post('/', ({ body }) => body, { body: t.Uint8Array(), response: t.Uint8Array() }) const response = await app.handle( new Request('http://localhost', { method: 'POST', body: new TextEncoder().encode('可愛くてごめん'), headers: { 'content-type': 'application/octet-stream' } }) ) expect(await response.text()).toBe('可愛くてごめん') }) }) ================================================ FILE: test/type-system/union-enum.test.ts ================================================ import Elysia, { t } from '../../src' import { describe, expect, it } from 'bun:test' import { Value } from '@sinclair/typebox/value' import { post } from '../utils' describe('TypeSystem - UnionEnum', () => { it('Create', () => { expect(Value.Create(t.UnionEnum(['some', 'data']))).toEqual('some') }) it('Allows readonly', () => { const readonlyArray = ['some', 'data'] as const expect(Value.Create(t.UnionEnum(readonlyArray))).toEqual('some') }) it('Check', () => { const schema = t.UnionEnum(['some', 'data']) expect(Value.Check(schema, 'some')).toBe(true) expect(Value.Check(schema, 'data')).toBe(true) expect(Value.Check(schema, { deep: 2 })).toBe(false) expect(Value.Check(schema, 'yay')).toBe(false) expect(Value.Check(schema, 42)).toBe(false) expect(Value.Check(schema, {})).toBe(false) expect(Value.Check(schema, undefined)).toBe(false) }) it('JSON schema', () => { expect(t.UnionEnum(['some', 'data'])).toMatchObject({ type: 'string', enum: ['some', 'data'] }) expect(t.UnionEnum(['some', 1]).type).toBeUndefined() expect(t.UnionEnum([2, 1])).toMatchObject({ type: 'number', enum: [2, 1] }) }) it('Integrate', async () => { const app = new Elysia().post('/', ({ body }) => body, { body: t.Object({ value: t.UnionEnum(['some', 1]) }) }) const res1 = await app.handle(post('/', { value: 1 })) expect(res1.status).toBe(200) const res2 = await app.handle(post('/', { value: 'some' })) expect(res2.status).toBe(200) const res3 = await app.handle(post('/', { value: 'data' })) expect(res3.status).toBe(422) }) }) ================================================ FILE: test/types/async-modules.ts ================================================ import { expectTypeOf } from 'expect-type' import { Elysia } from '../../src' // ? async plugin should mark as partial { const serviceA = async () => { await Bun.sleep(1) return new Elysia() .decorate('decoratorA', 'decoratorA') .state('storeA', 'storeA' as const) .derive(() => ({ deriveA: 'deriveA' })) .resolve(() => ({ resolveA: 'resolveA' })) } new Elysia() .use(serviceA) .decorate((v) => { expectTypeOf(v.decoratorA).toEqualTypeOf() return v }) .state((v) => { expectTypeOf(v.storeA).toEqualTypeOf<'storeA' | undefined>() return v }) .derive((v) => { expectTypeOf(v.deriveA).toEqualTypeOf<'deriveA' | undefined>() return v }) .resolve((v) => { expectTypeOf(v.resolveA).toEqualTypeOf<'resolveA' | undefined>() return v }) } // ? inline async should mark plugin as partial { const serviceA = new Elysia().use(async (app) => { await Bun.sleep(1) return app .decorate('decoratorA', 'decoratorA') .state('storeA', 'storeA' as const) .derive(() => ({ deriveA: 'deriveA' })) .resolve(() => ({ resolveA: 'resolveA' })) .as('scoped') }) new Elysia() .use(serviceA) .decorate((v) => { expectTypeOf(v.decoratorA).toEqualTypeOf() return v }) .state((v) => { expectTypeOf(v.storeA).toEqualTypeOf<'storeA' | undefined>() return v }) .derive((v) => { expectTypeOf(v.deriveA).toEqualTypeOf<'deriveA' | undefined>() return v }) .resolve((v) => { expectTypeOf(v.resolveA).toEqualTypeOf<'resolveA' | undefined>() return v }) } // ? async plugin should mark plugin as partial { const serviceA = async () => { await Bun.sleep(1) return new Elysia() .decorate('decoratorA', 'decoratorA') .state('storeA', 'storeA' as const) .derive(() => ({ deriveA: 'deriveA' })) .resolve(() => ({ resolveA: 'resolveA' })) .as('scoped') } new Elysia() .use(serviceA) .decorate((v) => { expectTypeOf(v.decoratorA).toEqualTypeOf() return v }) .state((v) => { expectTypeOf(v.storeA).toEqualTypeOf<'storeA' | undefined>() return v }) .derive((v) => { expectTypeOf(v.deriveA).toEqualTypeOf<'deriveA' | undefined>() return v }) .resolve((v) => { expectTypeOf(v.resolveA).toEqualTypeOf<'resolveA' | undefined>() return v }) } // ? inline async should mark plugin as partial { const serviceA = new Elysia().use(async (app) => { await Bun.sleep(1) return app .decorate('decoratorA', 'decoratorA') .state('storeA', 'storeA' as const) .derive(() => ({ deriveA: 'deriveA' })) .resolve(() => ({ resolveA: 'resolveA' })) .as('scoped') }) new Elysia() .use(serviceA) .decorate((v) => { expectTypeOf(v.decoratorA).toEqualTypeOf() return v }) .state((v) => { expectTypeOf(v.storeA).toEqualTypeOf<'storeA' | undefined>() return v }) .derive((v) => { expectTypeOf(v.deriveA).toEqualTypeOf<'deriveA' | undefined>() return v }) .resolve((v) => { expectTypeOf(v.resolveA).toEqualTypeOf<'resolveA' | undefined>() return v }) } // ? async plugin should mark global as partial { const serviceA = async () => { await Bun.sleep(1) return new Elysia() .decorate('decoratorA', 'decoratorA') .state('storeA', 'storeA' as const) .derive(() => ({ deriveA: 'deriveA' })) .resolve(() => ({ resolveA: 'resolveA' })) .as('global') } new Elysia() .use(serviceA) .decorate((v) => { expectTypeOf(v.decoratorA).toEqualTypeOf() return v }) .state((v) => { expectTypeOf(v.storeA).toEqualTypeOf<'storeA' | undefined>() return v }) .derive((v) => { expectTypeOf(v.deriveA).toEqualTypeOf<'deriveA' | undefined>() return v }) .resolve((v) => { expectTypeOf(v.resolveA).toEqualTypeOf<'resolveA' | undefined>() return v }) } // ? inline async should mark global as partial { const serviceA = new Elysia().use(async (app) => { await Bun.sleep(1) return app .decorate('decoratorA', 'decoratorA') .state('storeA', 'storeA' as const) .derive(() => ({ deriveA: 'deriveA' })) .resolve(() => ({ resolveA: 'resolveA' })) .as('global') }) new Elysia() .use(serviceA) .decorate((v) => { expectTypeOf(v.decoratorA).toEqualTypeOf() return v }) .state((v) => { expectTypeOf(v.storeA).toEqualTypeOf<'storeA' | undefined>() return v }) .derive((v) => { expectTypeOf(v.deriveA).toEqualTypeOf<'deriveA' | undefined>() return v }) .resolve((v) => { expectTypeOf(v.resolveA).toEqualTypeOf<'resolveA' | undefined>() return v }) } { // ? inherits lazy loading plugin type new Elysia().use(import('./plugins')).get( '/', ({ body, decorate, store: { state } }) => { expectTypeOf().toBeString() expectTypeOf().toBeString() expectTypeOf().toBeString() }, { body: 'string' } ) } ================================================ FILE: test/types/documentation.ts ================================================ import { Elysia, t } from '../../src' // handle error property { new Elysia() .post('/', ({ body }) => body, { body: t.Object({ name: t.String(), age: t.Number() }), error({ code, error }) { switch (code) { case 'VALIDATION': console.log(error.all) // Find a specific error name (path is OpenAPI Schema compliance) const name = error.all.find( (x) => x.summary && 'path' in x && x.path === '/name' ) // If there is a validation error, then log it if (name) console.log(name) } } }) } ================================================ FILE: test/types/index.ts ================================================ /* eslint-disable @typescript-eslint/no-unused-vars */ import { type Cookie, Elysia, file, form, SSEPayload, sse, status, t } from '../../src' import { expectTypeOf } from 'expect-type' const app = new Elysia() // ? default value of context app.get('/', ({ headers, query, params, body, store }) => { // ? default keyof params should be never expectTypeOf().toEqualTypeOf<{}>() // ? default headers should be Record expectTypeOf().toEqualTypeOf< Record >() // ? default query should be Record expectTypeOf().toEqualTypeOf>() // ? default body should be unknown expectTypeOf().toBeUnknown() // ? default store should be empty expectTypeOf().toEqualTypeOf<{}>() }) app.model({ t: t.Object({ username: t.String(), password: t.String() }) }).get( '/', ({ headers, query, params, body, cookie }) => { // ? unwrap body type expectTypeOf<{ username: string password: string }>().toEqualTypeOf() // ? unwrap body type expectTypeOf<{ username: string password: string }>().toEqualTypeOf() // ? unwrap body type expectTypeOf<{ username: string password: string }>().toEqualTypeOf() // ? unwrap body type expectTypeOf<{ username: string password: string }>().toEqualTypeOf() // ? unwrap cookie expectTypeOf< Record> & { username: Cookie password: Cookie } >().toEqualTypeOf() return body }, { body: 't', params: 't', query: 't', headers: 't', response: 't', cookie: 't' } ) app.model({ t: t.Object({ username: t.String(), password: t.String() }) }).get( '/', ({ body }) => { // ? unwrap body type expectTypeOf<{ username: string password: string }>().toEqualTypeOf() return body }, { body: 't', response: 't' } ) app.get('/id/:id', ({ params }) => { // ? infer params name expectTypeOf<{ id: string }>().toEqualTypeOf() }) app.get('/id/:id/name/:name', ({ params }) => { // ? infer multiple params name expectTypeOf<{ id: string name: string }>().toEqualTypeOf() }) // ? support unioned response app.get('/', () => '1', { response: { 200: t.String(), 400: t.Number() } }).get('/', () => 1, { response: { 200: t.String(), 400: t.Number() } }) // ? support pre-defined schema app.guard({ body: t.String() }).get('/', ({ body }) => { expectTypeOf().not.toBeUnknown() expectTypeOf().toBeString() }) // ? override schema app.guard({ body: t.String() }).get( '/', ({ body }) => { expectTypeOf().not.toBeUnknown() expectTypeOf().toBeNumber() }, { body: t.Number() } ) // ? override schema app.model({ string: t.String() }).guard( { body: t.String() }, (app) => app // ? Inherits guard type .get('/', ({ body }) => { expectTypeOf().not.toBeUnknown() expectTypeOf().toBeString() }) // // ? override guard type .get( '/', ({ body }) => { expectTypeOf().not.toBeUnknown() expectTypeOf().toBeNumber() }, { body: t.Number() } ) // ? Merge schema and inherits typed .get( '/', ({ body, query }) => { expectTypeOf().not.toEqualTypeOf< Record >() expectTypeOf().toEqualTypeOf<{ a: string }>() expectTypeOf().not.toBeUnknown() expectTypeOf().toBeString() }, { query: t.Object({ a: t.String() }) } ) // ? Inherits schema reference .get( '/', ({ body }) => { expectTypeOf().not.toBeUnknown() expectTypeOf().toEqualTypeOf() }, { body: 'string' } ) .get( '/', ({ body }) => { expectTypeOf().not.toBeUnknown() expectTypeOf().toEqualTypeOf() }, { body: 'string' } ) .model({ authorization: t.Object({ authorization: t.String() }) }) // ? Merge inherited schema .get( '/', ({ body, headers }) => { expectTypeOf().not.toBeUnknown() expectTypeOf().not.toEqualTypeOf< Record >() expectTypeOf().toEqualTypeOf<{ authorization: string }>() }, { headers: 'authorization' } ) .guard( { headers: 'authorization' }, (app) => // ? To reconcilate multiple level of schema app.get('/', ({ body, headers }) => { expectTypeOf().not.toBeUnknown() expectTypeOf().toEqualTypeOf() expectTypeOf().not.toBeUnknown() expectTypeOf().toEqualTypeOf<{ authorization: string }>() }) ) ) app.state('a', 'b') // ? Infer state .get('/', ({ store }) => { expectTypeOf().toEqualTypeOf<{ a: string }>() }) .state('b', 'c') // ? Merge state .get('/', ({ store }) => { expectTypeOf().toEqualTypeOf<{ a: string b: string }>() }) .state({ c: 'd', d: 'e' }) // ? Use multiple state .get('/', ({ store }) => { expectTypeOf().toEqualTypeOf<{ a: string b: string c: string d: string }>() }) app.decorate('a', 'b') // ? Infer state .get('/', ({ a }) => { expectTypeOf().toBeString() }) .decorate('b', 'c') // ? Merge state .get('/', ({ a, b }) => { expectTypeOf().toBeString() expectTypeOf().toBeString() }) .decorate({ c: 'd', d: 'e' }) // ? Use multiple decorate .get('/', ({ a, b, c, d }) => { expectTypeOf<{ a: typeof a b: typeof b c: typeof c d: typeof d }>().toEqualTypeOf<{ a: string b: string c: string d: string }>() }) // ? Reconcile deep using name { const app = new Elysia() .decorate('a', { hello: { world: 'Tako' } }) .decorate('a', { hello: { world: 'Ina', cookie: 'wah!' } }) expectTypeOf<(typeof app)['decorator']['a']>().toEqualTypeOf< { hello: { world: string } } & { hello: { world: string cookie: string } } >() } // ? Reconcile deep using value { const app = new Elysia() .decorate({ hello: { world: 'Tako' } }) .decorate( { as: 'override' }, { hello: { world: 'Ina', cookie: 'wah!' } } ) expectTypeOf().toEqualTypeOf<{ world: string cookie: string }>() } // ? Reconcile deep using name { const app = new Elysia() .state('a', { hello: { world: 'Tako' } }) .state('a', { hello: { world: 'Ina', cookie: 'wah!' } }) expectTypeOf<(typeof app)['store']['a']>().toEqualTypeOf< { hello: { world: string } } & { hello: { world: string cookie: string } } >() } // ? Reconcile deep using value { const app = new Elysia() .state({ hello: { world: 'Tako' } }) .state( { as: 'override' }, { hello: { world: 'Ina', cookie: 'wah!' } } ) expectTypeOf().toEqualTypeOf<{ world: string cookie: string }>() } const b = app .model('a', t.Literal('a')) // ? Infer label model .post( '/', ({ body }) => { expectTypeOf().toEqualTypeOf<'a'>() }, { body: 'a', transform() {} } ) // ? Infer multiple model .model({ b: t.Literal('b'), c: t.Literal('c') }) .post( '/', ({ body }) => { expectTypeOf().toEqualTypeOf<'b'>() }, { body: 'b' } ) .post( '/', ({ body }) => { expectTypeOf().toEqualTypeOf<'c'>() }, { body: 'c' } ) // ? It derive void { app.derive(({ headers }) => { if (Math.random() > 0.5) return { stuff: 'a' } }).get('/', ({ stuff }) => { expectTypeOf().not.toBeUnknown() expectTypeOf().toEqualTypeOf<'a' | undefined>() }) } // ? It resolve void { app.resolve(async ({ headers }) => { if (Math.random() > 0.5) return { stuff: 'a' } }).get('/', ({ stuff }) => { expectTypeOf().not.toBeUnknown() expectTypeOf().toEqualTypeOf<'a' | undefined>() }) } app.derive(({ headers }) => { return { authorization: headers.authorization as string } }) .get('/', ({ authorization }) => { // ? infers derive type expectTypeOf().toBeString() }) .decorate('a', 'b') .derive(({ a }) => { // ? derive from current context expectTypeOf().toBeString() return { b: a } }) .get('/', ({ a, b }) => { // ? save previous derivation expectTypeOf().toBeString() // ? derive from context expectTypeOf().toBeString() }) // ? Resolve should not include in onRequest .onRequest((context) => { expectTypeOf< 'b' extends keyof typeof context ? true : false >().toEqualTypeOf() }) // ? Resolve should not include in onTransform .onTransform((context) => { expectTypeOf< 'b' extends keyof typeof context ? true : false >().toEqualTypeOf() }) const plugin = (app: Elysia) => app.decorate('decorate', 'a').state('state', 'a').model({ string: t.String() }) // ? inherits plugin type app.use(plugin) .get( '/', ({ body, decorate, store: { state } }) => { expectTypeOf().toBeString() expectTypeOf().toBeString() expectTypeOf().toBeString() }, { body: 'string' } ) .get( '/', ({ body, decorate, store: { state } }) => { expectTypeOf().toBeString() expectTypeOf().toBeString() expectTypeOf().toEqualTypeOf() }, { body: 'string' } ) export const asyncPlugin = async (app: Elysia) => app.decorate('decorate', 'a').state('state', 'a').model({ string: t.String() }) // ? group inherits type app.use(plugin).group('/', (app) => app.get( '/', ({ body, decorate, store: { state } }) => { expectTypeOf().toBeString() expectTypeOf().toBeString() expectTypeOf().toBeString() }, { body: 'string' } ) ) // ? guard inherits type app.use(plugin).guard({}, (app) => app.get( '/', ({ body, decorate, store: { state } }) => { expectTypeOf().toBeString() expectTypeOf().toBeString() expectTypeOf().toBeString() }, { body: 'string' } ) ) // ? guarded group inherits type app.use(plugin).group( '/', { query: t.Object({ username: t.String() }) }, (app) => { app['~Metadata'].schema return app.get( '/', ({ query, body, decorate, store: { state } }) => { expectTypeOf().toEqualTypeOf<{ username: string }>() expectTypeOf().toBeString() expectTypeOf().toBeString() expectTypeOf().toBeString() }, { body: 'string' } ) } ) // ? It inherits group type to Eden { const server = app .group( '/v1', { query: t.Object({ name: t.String() }) }, (app) => app.guard( { headers: t.Object({ authorization: t.String() }) }, (app) => app.get('/a', () => 1, { body: t.String() }) ) ) .get('/', () => 1) type App = (typeof server)['~Routes'] type Route = App['v1']['a']['get'] expectTypeOf().toEqualTypeOf<{ headers: { authorization: string } body: string query: { name: string } params: Record response: { 200: number 422: { type: 'validation' on: string summary?: string message?: string found?: unknown property?: string expected?: string } } }>() } // ? It doesn't exposed guard type to external route { const server = app .guard( { query: t.Object({ name: t.String() }) }, (app) => app ) .get('/', () => 1) type App = (typeof server)['~Routes'] type Route = App['get'] expectTypeOf().toEqualTypeOf<{ body: unknown params: {} query: unknown headers: unknown response: { 200: number } }>() } // ? Register websocket { const server = app.group( '/v1', { query: t.Object({ name: t.String() }) }, (app) => app.guard( { headers: t.Object({ authorization: t.String() }) }, (app) => app.ws('/a', { message(ws, message) { message ws.data.params }, body: t.String() }) ) ) type App = (typeof server)['~Routes'] type Route = App['v1']['a']['subscribe'] expectTypeOf().toEqualTypeOf<{ body: string params: {} query: { name: string } headers: { authorization: string } response: { 422: { type: 'validation' on: string summary?: string message?: string found?: unknown property?: string expected?: string } } }>() } // ? Register empty model { const server = app.get('/', () => 'Hello').get('/a', () => 'hi') type App = (typeof server)['~Routes'] type Route = App['get'] expectTypeOf().toEqualTypeOf<{ body: unknown params: {} query: unknown headers: unknown response: { 200: string } }>() } // ? Register wildcard as params app.get('/*', ({ params }) => { expectTypeOf().toEqualTypeOf<{ '*': string }>() return 'hello' }).get('/id/:id/*', ({ params }) => { expectTypeOf().toEqualTypeOf<{ id: string '*': string }>() return 'hello' }) // ? Handle recursive path typing app.group( '/:a', { body: t.Object({}), beforeHandle({ params, params: { a } }) { expectTypeOf().toEqualTypeOf<{ a: string }>() return a } }, (app) => app .get('/', ({ params, params: { a } }) => { expectTypeOf().toEqualTypeOf<{ a: string }>() return a }) .group('/:b', (app) => app.get('/', ({ params, params: { a, b } }) => { expectTypeOf().toEqualTypeOf<{ a: string b: string }>() return b }) ) .group( '/:c', { beforeHandle({ params, params: { a, c } }) { expectTypeOf().toEqualTypeOf<{ a: string c: string }>() return a } }, (app) => app.get('/', ({ params, params: { a, c } }) => { expectTypeOf().toEqualTypeOf<{ a: string c: string }>() return c }) ) ) // ? Handle recursive schema collision causing infinite type app.group( '/:a', { body: t.Object({ username: t.String() }), query: t.Object({ user: t.String() }), beforeHandle: ({ body }) => { expectTypeOf().toEqualTypeOf<{ username: string }>() } }, (app) => app.group( '/:c', { beforeHandle({ body, query }) { expectTypeOf().toEqualTypeOf<{ username: string password: string }>() expectTypeOf().toEqualTypeOf<{ user: string }>() return body }, body: t.Object({ password: t.String() }) }, (app) => app.get('/', ({ body }) => { expectTypeOf().toEqualTypeOf<{ username: string password: string }>() return body }) ) ) // ? Reconcilation on state // { // const a = app.state('a', 'a' as const) // const b = a.state('a', 'b' as const) // expectTypeOf<(typeof a)['store']>().toEqualTypeOf<{ // a: 'a' // }>() // expectTypeOf<(typeof b)['store']>().toEqualTypeOf<{ // a: 'b' // }>() // } // // ? Reconcilation on decorator // { // const a = app.decorate('a', 'a' as const) // const b = a.decorate('a', 'b' as const) // expectTypeOf<(typeof a)['decorators']>().toEqualTypeOf<{ // a: 'a' // }>() // expectTypeOf<(typeof b)['decorators']>().toEqualTypeOf<{ // a: 'b' // }>() // } // // ? Reconcilation on model // { // const a = app.model('a', t.String()) // const b = a.model('a', t.Number()) // expectTypeOf<(typeof a)['definitions']['type']>().toEqualTypeOf<{ // a: string // }>() // expectTypeOf<(typeof b)['definitions']['type']>().toEqualTypeOf<{ // a: number // }>() // } // // ? Reconcilation on use // { // const a = app // .state('a', 'a' as const) // .model('a', t.String()) // .decorate('a', 'b' as const) // .use((app) => // app // .state('a', 'b' as const) // .model('a', t.Number()) // .decorate('a', 'b' as const) // ) // expectTypeOf<(typeof a)['store']>().toEqualTypeOf<{ // a: 'b' // }>() // expectTypeOf<(typeof a)['decorators']>().toEqualTypeOf<{ // a: 'b' // }>() // expectTypeOf<(typeof a)['definitions']['type']>().toEqualTypeOf<{ // a: number // }>() // } // ? Inherits plugin instance path { const plugin = new Elysia().get('/', () => 'hello') const server = app.use(plugin) type App = (typeof server)['~Routes'] type Route = App['get'] expectTypeOf().toEqualTypeOf<{ body: unknown params: {} query: unknown headers: unknown response: { 200: string } }>() } // ? Inherits plugin instance prefix path { const pluginPrefixApp = new Elysia({ prefix: '/app' }).get( '/test', () => 'hello' ) const appWithArrayOfPlugin = new Elysia({ prefix: '/api' }).use([ pluginPrefixApp ]) const appWithPlugin = new Elysia({ prefix: '/api' }).use(pluginPrefixApp) expectTypeOf<(typeof appWithArrayOfPlugin)['~Routes']>().toEqualTypeOf<{ api: { app: { test: { get: { body: unknown params: {} query: unknown headers: unknown response: { 200: string } } } } } }>() expectTypeOf<(typeof appWithArrayOfPlugin)['~Routes']>().toEqualTypeOf< (typeof appWithPlugin)['~Routes'] >() } // ? Inlining function callback don't repeat prefix { const test = (app: Elysia) => app.group('/app', (group) => group.get('/test', () => 'test')) const app = new Elysia().use(test) type App = (typeof app)['~Routes'] type Routes = keyof App['app']['test']['get'] expectTypeOf().not.toBeUnknown() } // ? Merging identical plugin type { const cookie = new Elysia({ prefix: '/' }).derive({ as: 'global' }, () => { return { customCookie: 'A' } }) const controller = new Elysia().use(cookie).get('/', () => 'A') const app = new Elysia() .use(cookie) .use(controller) .get('/', ({ customCookie }) => { expectTypeOf().toBeString() }) } // ? Prefer local schema over parent schema for nesting { new Elysia().group( '/id/:id', { params: t.Object({ id: t.Number() }), beforeHandle({ params }) { expectTypeOf().toEqualTypeOf<{ id: number }>() } }, (app) => app .get('/awd', ({ params }) => { expectTypeOf().toEqualTypeOf<{ id: number }>() }) .group( '/name/:name', { params: t.Object({ id: t.Numeric(), name: t.String() }) }, (app) => app.get('/awd', ({ params }) => { expectTypeOf().toEqualTypeOf<{ id: number name: string }>() }) ) ) } // ? Inherits route for scoped instance { const child = new Elysia() .decorate('b', 'b') .model('b', t.String()) .get('/child', () => 'Hello from child route') const main = new Elysia().use(child) type App = (typeof main)['~Routes'] expectTypeOf().toEqualTypeOf<'child'>() expectTypeOf< keyof (typeof main)['~Singleton']['decorator'] >().not.toEqualTypeOf<{ request: { b: 'b' } store: {} }>() expectTypeOf().not.toEqualTypeOf<{ type: { b: string } error: {} }>() } // ? WebSocket infers params { new Elysia() .ws('/:id', { open(ws) { expectTypeOf().toEqualTypeOf<{ id: string }>() } }) .ws('/:id', { params: t.Object({ id: t.Number() }), open(ws) { expectTypeOf().toEqualTypeOf<{ id: number }>() } }) } const a = app .resolve(({ headers }) => { return { authorization: headers.authorization as string } }) // .get('/', ({ authorization }) => { // // ? infers derive type // expectTypeOf().toBeString() // }) .decorate('a', 'b') .resolve(({ a }) => { // ? derive from current context expectTypeOf().toBeString() return { b: a } }) .get('/', ({ a, b }) => { // ? save previous derivation expectTypeOf().toBeString() // ? derive from context expectTypeOf().toBeString() }) // ? Resolve should not include in onTransform .onTransform((context) => { expectTypeOf< 'b' extends keyof typeof context ? true : false >().toEqualTypeOf() }) // ? Resolve should not include in onBeforeHandle .onBeforeHandle((context) => { expectTypeOf< 'b' extends keyof typeof context ? true : false >().toEqualTypeOf() }) { app.macro({ a(a: string) {} }) .get('/', () => {}, { // ? Should contains macro a: 'a' }) .get('/', () => {}, { // ? Should have error // @ts-expect-error a: 1 }) .macro({ b(a: number) {} }) .get('/', () => {}, { // ? Should merge macro a: 'a', b: 2 }) .guard( { // ? Should contains macro a: 'a', b: 2 }, (app) => app.get('/', () => {}, { // ? Should contains macro a: 'a', b: 2 }) ) } // ? Join Eden path correctly { const testController = new Elysia({ name: 'testController', prefix: '/test' }) .get('/could-be-error/right', () => ({ couldBeError: true })) .ws('/deep/ws', { message() {} }) const app = new Elysia().group('/api', (app) => app.use(testController)) expectTypeOf< (typeof app)['~Routes']['api']['test']['could-be-error']['right']['get'] >().toEqualTypeOf<{ body: unknown params: {} query: unknown headers: unknown response: { 200: { couldBeError: boolean } } }>() expectTypeOf< (typeof app)['~Routes']['api']['test']['deep']['ws']['subscribe'] >().toEqualTypeOf<{ body: {} params: {} query: {} headers: {} response: {} }>() } // ? Handle error status { const a = new Elysia() .get('/', ({ status }) => status(418, 'a'), { response: { 200: t.String(), 418: t.Literal('a') } }) .get('/', ({ status }) => status(418, 'b' as any), { response: { 200: t.String(), 418: t.Literal('a') } }) } // ? Get response type correctly { const app = new Elysia() .get('', () => 'a') .get('/true', () => true) .post('', () => 'a', { response: { 201: t.String() } }) .post('/true', () => true, { response: { 202: t.Boolean() } }) .get('/error', ({ status }) => status("I'm a teapot", 'a')) .post('/mirror', ({ body }) => body) .get('/immutable', '1') .get('/immutable-error', ({ status }) => status("I'm a teapot", 'a')) .get('/async', async ({ status }) => { if (Math.random() > 0.5) return status("I'm a teapot", 'Nagisa') return 'Hifumi' }) .get('/default-error-code', ({ status }) => { if (Math.random() > 0.5) return status(418, 'Nagisa') if (Math.random() > 0.5) return status(401) return 'Hifumi' }) type app = (typeof app)['~Routes'] expectTypeOf().toEqualTypeOf<{ 200: string }>() expectTypeOf().toEqualTypeOf<{ 200: string 201: string 422: { type: 'validation' on: string summary?: string message?: string found?: unknown property?: string expected?: string } }>() expectTypeOf().toEqualTypeOf<{ 200: boolean }>() expectTypeOf().toEqualTypeOf<{ 200: boolean 202: boolean 422: { type: 'validation' on: string summary?: string message?: string found?: unknown property?: string expected?: string } }>() expectTypeOf().toEqualTypeOf<{ 418: 'a' }>() expectTypeOf().toEqualTypeOf<{}>() expectTypeOf().toEqualTypeOf<{ 200: '1' }>() expectTypeOf().toEqualTypeOf<{ 418: 'a' }>() expectTypeOf().toEqualTypeOf<{ 200: 'Hifumi' 418: 'Nagisa' }>() expectTypeOf().toEqualTypeOf<{ 200: 'Hifumi' 401: 'Unauthorized' 418: 'Nagisa' }>() } app.get('/', ({ set }) => { // ? Able to set literal type to set.status set.status = "I'm a teapot" // ? Able to number to set.status set.status = 418 }) // ? Ephemeral and Current type { const child = new Elysia() .derive({ as: 'scoped' }, () => { return { hello: 'world' } }) .get('/', ({ hello }) => { expectTypeOf().toEqualTypeOf<'world'>() return 'hello' }) const current = new Elysia().use(child).get('/', ({ hello }) => { expectTypeOf().toEqualTypeOf<'world'>() return 'hello' }) const parrent = new Elysia().use(current).get('/', (context) => { expectTypeOf().not.toHaveProperty('hello') return 'hello' }) } // ? Return file with File Schema { const child = new Elysia().get( '/', () => { return file('test/kyuukurarin.mp4') }, { response: t.File() } ) } // ? Return file with Object File Schema { const child = new Elysia().get( '/', () => { return form({ a: file('test/kyuukurarin.mp4') }) }, { response: t.Form({ a: t.File() }) } ) } // ? Accept file with Object File Schema { const child = new Elysia().get( '/', ({ body: { file } }) => { expectTypeOf().toEqualTypeOf() return file }, { body: t.Object({ file: t.File() }), response: t.File() } ) } type a = keyof {} // It handle optional params { new Elysia() .get('/:id?', ({ params }) => { expectTypeOf().toEqualTypeOf<{ id?: string }>() }) .get('/:id/:name?', ({ params }) => { expectTypeOf().toEqualTypeOf<{ id: string name?: string }>() }) } // ? Elysia.as { // ? handle as global' { const inner = new Elysia() .guard({ response: t.Number() }) // @ts-expect-error .get('/inner', () => 'a') .as('global') const plugin = new Elysia() .use(inner) // @ts-expect-error .get('/plugin', () => true) // @ts-expect-error const app = new Elysia().use(plugin).get('/', () => 'not a number') } // handle as global with local override { const inner = new Elysia() .guard({ response: t.Number() }) // @ts-expect-error .get('/inner', () => 'a') .as('global') const plugin = new Elysia() .use(inner) .guard({ response: t.Boolean() }) .get('/plugin', () => true) // @ts-expect-error const app = new Elysia().use(plugin).get('/', () => 'not a number') } // handle as global with scoped override' { const inner = new Elysia() .guard({ response: t.Number() }) // @ts-expect-error .get('/inner', () => 'a') .as('global') const plugin = new Elysia() .use(inner) .guard({ as: 'scoped', response: t.String() }) .get('/plugin', () => 'ok') const app = new Elysia().use(plugin).get('/', () => 'not a number') } // ? handle as global' { const inner = new Elysia() .guard({ response: t.Number() }) // @ts-expect-error .get('/inner', () => 'a') .as('global') const plugin = new Elysia() .use(inner) // @ts-expect-error .get('/plugin', () => true) // @ts-expect-error const app = new Elysia().use(plugin).get('/', () => 'not a number') } // ? handle as global with local override { const inner = new Elysia() .guard({ response: t.Number() }) // @ts-expect-error .get('/inner', () => 'a') .as('global') const plugin = new Elysia() .use(inner) .guard({ response: t.Boolean() }) .get('/plugin', () => true) // @ts-expect-error const app = new Elysia().use(plugin).get('/', () => 'not a number') } // handle as global with scoped override { const inner = new Elysia() .guard({ response: t.Number() }) // @ts-expect-error .get('/inner', () => 'a') .as('global') const plugin = new Elysia() .use(inner) .guard({ as: 'scoped', response: t.String() }) .get('/plugin', () => 'ok') const app = new Elysia().use(plugin).get('/', () => 'not a number') } // ? handle as plugin { const inner = new Elysia() .guard({ response: t.Number() }) // @ts-expect-error .get('/inner', () => 'a') .as('scoped') const plugin = new Elysia() .use(inner) // @ts-expect-error .get('/plugin', () => true) const app = new Elysia().use(plugin).get('/', () => 'not a number') } // ? handle as propagate twice { const inner = new Elysia() .guard({ response: t.Number() }) // @ts-expect-error .get('/inner', () => 'a') .as('scoped') const plugin = new Elysia() .use(inner) // @ts-expect-error .get('/plugin', () => true) .as('scoped') // @ts-expect-error const app = new Elysia().use(plugin).get('/', () => 'not a number') } // ? Reconcile status { const inner = new Elysia() .guard({ response: { 401: t.Number(), 402: t.Number() } }) .get('/inner', () => '') .as('global') const plugin = new Elysia() .use(inner) .guard({ response: { 401: t.Boolean() } }) .get('/plugin', ({ status }) => { status('Payment Required', 20) return status(401, true) }) const app = new Elysia().use(plugin).get('/', () => 'ok') } // ? Reconcile inline handle { const inner = new Elysia() .guard({ response: { 401: t.Number(), 402: t.Number() } }) .get('/inner', '') .as('global') const plugin = new Elysia() .use(inner) .guard({ response: { 401: t.Boolean() } }) .get('/plugin', status(401, true)) const app = new Elysia().use(plugin).get('/', 'ok') } } // ? Guard as // handle as global { const inner = new Elysia() .guard({ as: 'global', response: t.Number() }) // @ts-expect-error .get('/inner', () => 'a') const plugin = new Elysia() .use(inner) // @ts-expect-error .get('/plugin', () => true) // @ts-expect-error const app = new Elysia().use(plugin).get('/', () => 'not a number') } // ? handle as global with local override { const inner = new Elysia() .guard({ as: 'global', response: t.Number() }) // @ts-expect-error .get('/inner', () => 'a') const plugin = new Elysia() .use(inner) .guard({ response: t.Boolean() }) .get('/plugin', () => true) // @ts-expect-error const app = new Elysia().use(plugin).get('/', () => 'not a number') } // handle as global with scoped override { const inner = new Elysia() .guard({ as: 'global', response: t.Number() }) // @ts-expect-error .get('/inner', () => 'a') const plugin = new Elysia() .use(inner) .guard({ as: 'scoped', response: t.String() }) .get('/plugin', () => 'ok') const app = new Elysia().use(plugin).get('/', () => 'not a number') } // handle as global { const inner = new Elysia() .guard({ as: 'global', response: t.Number() }) // @ts-expect-error .get('/inner', () => 'a') const plugin = new Elysia() .use(inner) // @ts-expect-error .get('/plugin', () => true) // @ts-expect-error const app = new Elysia().use(plugin).get('/', () => 'not a number') } // handle as global with local override { const inner = new Elysia() .guard({ as: 'global', response: t.Number() }) // @ts-expect-error .get('/inner', () => 'a') const plugin = new Elysia() .use(inner) .guard({ response: t.Boolean() }) .get('/plugin', () => true) // @ts-expect-error const app = new Elysia().use(plugin).get('/', () => 'not a number') } // ? handle as global with scoped override { const inner = new Elysia() .guard({ as: 'global', response: t.Number() }) // @ts-expect-error .get('/inner', () => 'a') const plugin = new Elysia() .use(inner) .guard({ as: 'scoped', response: t.String() }) .get('/plugin', () => 'ok') const app = new Elysia().use(plugin).get('/', () => 'not a number') } // handle as scoped { const inner = new Elysia() .guard({ as: 'scoped', response: t.Number() }) // @ts-expect-error .get('/inner', () => 'a') const plugin = new Elysia() .use(inner) // @ts-expect-error .get('/plugin', () => true) const app = new Elysia().use(plugin).get('/', () => 'not a number') } // handle as local { const inner = new Elysia() .guard({ as: 'local', response: t.Number() }) // @ts-expect-error .get('/inner', () => 'a') const plugin = new Elysia().use(inner).get('/plugin', () => true) const app = new Elysia().use(plugin).get('/', () => 'not a number') } // ? Nested guard { new Elysia() .state('name', 'salt') .get('/', ({ store: { name } }) => `Hi ${name}`, { query: t.Object({ name: t.String() }) }) // If query 'name' is not preset, skip the whole handler .guard( { query: t.Object({ name: t.String() }) }, (app) => app // Query type is inherited from guard .get('/profile', ({ query }) => `Hi`) // Store is inherited .post( '/name', ({ store, body, query }) => { expectTypeOf().toEqualTypeOf<{ name: string }>() expectTypeOf().toEqualTypeOf<{ name: string }>() expectTypeOf().toEqualTypeOf<{ id: number username: string profile: { name: string } }>() }, { body: t.Object({ id: t.Number({ minimum: 5 }), username: t.String(), profile: t.Object({ name: t.String() }) }) } ) ) // ? Reconcile status { const inner = new Elysia() .guard({ as: 'global', response: { 401: t.Number(), 402: t.Number() } }) .get('/inner', () => '') const plugin = new Elysia() .use(inner) .guard({ response: { 401: t.Boolean() } }) .get('/plugin', ({ status }) => { status('Payment Required', 20) return status(401, true) }) const app = new Elysia().use(plugin).get('/', () => 'ok') } // ? Reconcile inline handle { const inner = new Elysia() .guard({ as: 'global', response: { 401: t.Number(), 402: t.Number() } }) .get('/inner', '') const plugin = new Elysia() .use(inner) .guard({ response: { 401: t.Boolean() } }) .get('/plugin', status(401, true)) const app = new Elysia().use(plugin).get('/', 'ok') } } // Derive guard { new Elysia() .guard({ query: t.Object({ id: t.Number() }) }) .derive(({ query }) => { expectTypeOf().toEqualTypeOf<{ id: number }>() }) .resolve(({ query }) => { expectTypeOf().toEqualTypeOf<{ id: number }>() }) } // ? As cast shouldn't resolve derive as any key { const plugin = new Elysia() .derive(() => ({ pluginMethod() { console.log('pluginMethod') } })) .derive(({ pluginMethod, ...rest }) => ({ myPluginMethod: pluginMethod, ...rest })) .as('scoped') expectTypeOf<(typeof plugin)['~Ephemeral']['derive']>().toHaveProperty( 'pluginMethod' ) } // ? afterResponse type { const app = new Elysia().get( '/', () => { return { duration: 200 } }, { response: { 200: t.Object({ duration: t.Number() }), 400: t.Object({ stuff: t.Number() }) }, afterResponse({ response }) { // expectTypeOf().toEqualTypeOf< // | { // duration: number // } // | { // stuff: number // } // >() // return undefined as any } } ) } // ? params in local lifecycle should follow path prefix { new Elysia() .onParse(({ params }) => { expectTypeOf().toEqualTypeOf< Record >() }) .derive(({ params }) => { expectTypeOf().toEqualTypeOf< Record >() return {} }) .resolve(({ params }) => { expectTypeOf().toEqualTypeOf() return {} }) .onTransform(({ params }) => { expectTypeOf().toEqualTypeOf<{}>() }) .onBeforeHandle(({ params }) => { expectTypeOf().toEqualTypeOf< Record >() }) .onAfterHandle(({ params }) => { expectTypeOf().toEqualTypeOf< Record >() }) .mapResponse(({ params }) => { expectTypeOf().toEqualTypeOf< Record >() }) .onAfterResponse(({ params }) => { expectTypeOf().toEqualTypeOf< Record >() }) } // ? params in local lifecycle should follow path prefix { new Elysia({ prefix: '/:id' }) .onParse(({ params }) => { expectTypeOf().toEqualTypeOf<{ id: string }>() }) .derive(({ params }) => { expectTypeOf().toEqualTypeOf<{ id: string }>() return {} }) .resolve(({ params }) => { expectTypeOf().toEqualTypeOf<{ id: string }>() return {} }) .onTransform(({ params }) => { expectTypeOf().toEqualTypeOf<{ id: string }>() }) .onBeforeHandle(({ params }) => { expectTypeOf().toEqualTypeOf<{ id: string }>() }) .onAfterHandle(({ params }) => { expectTypeOf().toEqualTypeOf<{ id: string }>() }) .mapResponse(({ params }) => { expectTypeOf().toEqualTypeOf<{ id: string }>() }) .onAfterResponse(({ params }) => { expectTypeOf().toEqualTypeOf<{ id: string }>() }) } // ? params in local lifecycle should respect global scope { new Elysia({ prefix: '/:id' }) .onParse({ as: 'global' }, ({ params }) => { expectTypeOf().toEqualTypeOf<{ [name: string]: string | undefined }>() }) .derive({ as: 'global' }, ({ params }) => { expectTypeOf().toEqualTypeOf<{ [name: string]: string | undefined }>() return {} }) .resolve({ as: 'global' }, ({ params }) => { expectTypeOf().toEqualTypeOf<{ [name: string]: string | undefined }>() return {} }) .onTransform({ as: 'global' }, ({ params }) => { expectTypeOf().toEqualTypeOf<{ [name: string]: string | undefined }>() }) .onBeforeHandle({ as: 'global' }, ({ params }) => { expectTypeOf().toEqualTypeOf<{ [name: string]: string | undefined }>() }) .onAfterHandle({ as: 'global' }, ({ params }) => { expectTypeOf().toEqualTypeOf<{ [name: string]: string | undefined }>() }) .mapResponse({ as: 'global' }, ({ params }) => { expectTypeOf().toEqualTypeOf<{ [name: string]: string | undefined }>() }) .onAfterResponse({ as: 'global' }, ({ params }) => { expectTypeOf().toEqualTypeOf<{ [name: string]: string | undefined }>() }) } // ? params in local lifecycle should respect scoped scope { new Elysia({ prefix: '/:id' }) .onParse({ as: 'scoped' }, ({ params }) => { expectTypeOf().toEqualTypeOf<{ [name: string]: string | undefined }>() }) .derive({ as: 'scoped' }, ({ params }) => { expectTypeOf().toEqualTypeOf<{ [name: string]: string | undefined }>() return {} }) .resolve({ as: 'scoped' }, ({ params }) => { expectTypeOf().toEqualTypeOf<{ [name: string]: string | undefined }>() return {} }) .onTransform({ as: 'scoped' }, ({ params }) => { expectTypeOf().toEqualTypeOf<{ [name: string]: string | undefined }>() }) .onBeforeHandle({ as: 'scoped' }, ({ params }) => { expectTypeOf().toEqualTypeOf<{ [name: string]: string | undefined }>() }) .onAfterHandle({ as: 'scoped' }, ({ params }) => { expectTypeOf().toEqualTypeOf<{ [name: string]: string | undefined }>() }) .mapResponse({ as: 'scoped' }, ({ params }) => { expectTypeOf().toEqualTypeOf<{ [name: string]: string | undefined }>() }) .onAfterResponse({ as: 'scoped' }, ({ params }) => { expectTypeOf().toEqualTypeOf<{ [name: string]: string | undefined }>() }) } // ? onAfterResponse should have derivative { new Elysia() .derive(() => { return { startTime: performance.now() } }) .onAfterResponse((ctx) => { expectTypeOf().not.toBeNever() expectTypeOf<(typeof ctx)['startTime']>().toBeNumber() }) } // ? Websocket Response { new Elysia().ws('/', { open: (ws) => { ws.publish('channel', 'hello') }, response: t.String() }) } // ? Macro resolve { const app = new Elysia() .macro({ user: (enabled: boolean) => ({ resolve: async ({ query: { name = 'anon' } }) => ({ user: { name, async: false } as const }) }), asyncUser: (enabled: boolean) => ({ resolve: async ({ query: { name = 'anon' } }) => ({ user: { name, async: true } as const }) }) }) .get( '/', ({ user }) => { expectTypeOf().toEqualTypeOf<{ readonly name: string readonly async: false }>() }, { user: true } ) .get( '/', ({ user }) => { expectTypeOf().toEqualTypeOf<{ readonly name: string readonly async: true }>() }, { asyncUser: true } ) } // Macro { const userService = new Elysia({ name: 'user/service' }) .state({ user: {} as Record, session: {} as Record }) .model({ signIn: t.Object({ username: t.String({ minLength: 1 }), password: t.String({ minLength: 8 }) }), session: t.Cookie( { token: t.Number() }, { secrets: 'seia' } ), optionalSession: t.Optional(t.Ref('session')) }) .macro({ isSignIn(enabled: boolean) { if (!enabled) return {} return { beforeHandle({ status, cookie: { token }, store: { session } }) { if (!token.value) return status(401, { success: false, message: 'Unauthorized' }) expectTypeOf().toEqualTypeOf< Record >() const username = session[token.value as unknown as number] if (!username) return status(401, { success: false, message: 'Unauthorized' }) } } } }) } // Use validation response instead of return type { const app = new Elysia().get( '/', () => { return { name: 'a', a: 'b' } }, { response: { 200: t.Object({ name: t.String() }), 400: t.Object({ name: t.String() }) } } ) expectTypeOf< (typeof app)['~Routes']['get']['response'][200] >().toEqualTypeOf<{ name: string }>() } // Use return type when validation is not provided { const app = new Elysia().get( '/', () => { return { name: 'a', a: 'b' } }, { response: { 400: t.Object({ name: t.String() }) } } ) expectTypeOf< (typeof app)['~Routes']['get']['response'][200] >().toEqualTypeOf<{ name: string a: string }>() } // ? cookie sample { const app = new Elysia() .get( '/council', ({ cookie: { council } }) => (council.value = [ { name: 'Rin', affilation: 'Administration' } ]), { cookie: t.Cookie({ council: t.Optional( t.Array( t.Object({ name: t.String(), affilation: t.String() }) ) ) }) } ) .get('/create', ({ cookie: { name } }) => (name.value = 'Himari')) .get('/multiple', ({ cookie: { name, president } }) => { name.value = 'Himari' president.value = 'Rio' return 'ok' }) .get( '/update', ({ cookie: { name } }) => { name.value = 'seminar: Himari' return name.value }, { cookie: t.Cookie( { name: t.Optional(t.String()) }, { secrets: 'a', sign: ['name'] } ) } ) .get('/remove', ({ cookie }) => { for (const self of Object.values(cookie)) self.remove() return 'Deleted' }) .get('/remove-with-options', ({ cookie }) => { for (const self of Object.values(cookie)) self.remove() return 'Deleted' }) .get('/set', ({ cookie: { session } }) => { session.value = 'rin' session.set({ path: '/' }) }) } // Handle macro with function { const app = new Elysia() .macro({ a: { resolve: () => ({ a: 'a' }) } }) .get( '/a', ({ a }) => { expectTypeOf().toEqualTypeOf() }, { a: true // beforeHandle: (c) => {} } ) .ws('/', { a: true, message({ data: { a } }) { expectTypeOf().toEqualTypeOf() } }) } // Type AfterHandler according to known schema { new Elysia().get('/', () => 'yay', { afterResponse({ responseValue }) { expectTypeOf().toEqualTypeOf< string | number >() }, response: { 200: t.String(), 400: t.Number() } }) } // Handle Prefix { const app = new Elysia().group('/users', (app) => app .post('/', async ({ body }) => { // Create user endpoint return { success: true, userId: 1 } }) .get('/:id', async ({ params }) => { // Get user by ID endpoint return { id: params.id, name: 'John Doe' } }) ) expectTypeOf().toEqualTypeOf< 'post' | ':id' >() } // onError should have status { new Elysia().onError(({ status }) => { status(200) }) } // onAfterHandle should have response { new Elysia().onAfterHandle( { as: 'scoped' }, ({ responseValue }) => responseValue ) } /* Neither `a` or `b` exist at the type level, even though they do exist at runtime */ { new Elysia() .macro({ a: { body: t.Object({ a: t.String() }), resolve: () => ({ a: 'a' as const }) }, b: { body: t.Object({ b: t.String() }), resolve: () => ({ b: 'b' as const }) } }) .get( '/test', ({ a, b, body }) => { expectTypeOf().toEqualTypeOf<{ a: string b: string }>() expectTypeOf().toEqualTypeOf<'a'>() expectTypeOf().toEqualTypeOf<'b'>() return { a, b } }, { a: true, b: true } ) } // append prefix / if not provided { const plugin = new Elysia({ prefix: 'v1' }).get('thing', 'thing') const app = new Elysia({ prefix: 'api' }).use(plugin) // This should not error app['~Routes']?.api.v1.thing } // handle status in afterResponse { new Elysia().get('/', () => '', { afterHandle: ({ status }) => status(201, { foo: 'bar' }), response: { 201: t.Object({ foo: t.String() }) } }) const route = new Elysia().get('/', () => ({ foo: 'a' }), { // @ts-expect-error afterHandle: () => ({ q: 'a' }), response: t.Object({ foo: t.String() }) }) } // infer SSE type correctly { const app = new Elysia().get('/', function* () { yield sse('a') yield sse({ event: 'a', data: 'b' }) }) expectTypeOf< (typeof app)['~Routes']['get']['response'][200] >().toEqualTypeOf< Generator< | { readonly data: 'a' } | { readonly event: 'a' readonly data: 'b' }, void, unknown > >() } // return generator SSE type correctly { function* a() { yield 'a' yield 'b' } const app = new Elysia().get('/', () => sse(a())) expectTypeOf< (typeof app)['~Routes']['get']['response'][200] >().toEqualTypeOf< Generator< | { readonly data: 'a' } | { readonly data: 'b' }, void, unknown > >() } // return async generator SSE type correctly { async function* a() { yield 'a' yield 'b' } const app = new Elysia().get('/', () => sse(a())) expectTypeOf< (typeof app)['~Routes']['get']['response'][200] >().toEqualTypeOf< AsyncGenerator< | { readonly data: 'a' } | { readonly data: 'b' }, void, unknown > >() } // return ReadableStream SSE type correctly { async function* a() { yield 'a' yield 'b' } const app = new Elysia().get('/', () => sse(undefined as any as ReadableStream<'a'>) ) expectTypeOf< (typeof app)['~Routes']['get']['response'][200] >().toEqualTypeOf< ReadableStream<{ readonly data: 'a' }> >() } // infer ReadableStream to Iterable { const app = new Elysia() .get('/', () => undefined as any as ReadableStream<'a'>) .listen(3000) expectTypeOf< (typeof app)['~Routes']['get']['response'][200] >().toEqualTypeOf>() } // Inline Elysia file { new Elysia().get('/file', file('public/takodachi.png')) } // derive should add property union correctly { const app = new Elysia() .derive(({ request, status }) => { const apiKey = request.headers.get('x-api-key') if (!apiKey) return { auth: null } if (Math.random() > 0.5) return status(401) return { auth: { id: 1 } } }) .onBeforeHandle(({ auth }) => { expectTypeOf().toEqualTypeOf<{ readonly id: 1 } | null>() }) } // resolve should add property union correctly { const app = new Elysia() .resolve(({ request, status }) => { const apiKey = request.headers.get('x-api-key') if (!apiKey) return { auth: null } if (Math.random() > 0.5) return status(401) return { auth: { id: 1 } } }) .onBeforeHandle(({ auth }) => { expectTypeOf().toEqualTypeOf<{ readonly id: 1 } | null>() }) } // transform shouldn't inherit schema type { new Elysia() .guard({ body: t.Object({ a: t.String() }), params: t.Object({ id: t.String() }) }) .onTransform(({ params, body }) => { expectTypeOf().toEqualTypeOf<{}>() expectTypeOf().toBeUnknown() }) } // transform should cast params to unknown when scope is over local { new Elysia({ prefix: '/:id' }) .onTransform(({ params }) => { expectTypeOf().toEqualTypeOf<{ id: string }>() }) .onTransform({ as: 'scoped' }, ({ params }) => { expectTypeOf().toEqualTypeOf<{ [name: string]: string | undefined }>() }) } // Enforce return type in OptionalHandler { new Elysia().get( '/', ({ status }) => { return status(401, { error: 'Unauthorized' }) }, { beforeHandle: ({ status }) => { if (Math.random() > 0.5) { // @ts-expect-error return status(401, { a: 'Unauthorized' }) } return status(401, { error: 'Unauthorized' }) }, response: { 401: t.Object({ error: t.String() }) } } ) } // Enforce return type of Generator { const message = t.Object({ event: t.String(), data: t.Object({ message: t.String(), timestamp: t.String() }) }) new Elysia().get( '/sse', function* () { yield sse({ event: 'message', data: { message: 'This is a message', timestamp: new Date().toISOString() } }) }, { response: { 200: message } } ) } // Enforce return type of AsyncGenerator { const message = t.Object({ event: t.String(), data: t.Object({ message: t.String(), timestamp: t.String() }) }) new Elysia().get( '/sse', async function* () { yield sse({ event: 'message', data: { message: 'This is a message', timestamp: new Date().toISOString() } }) }, { response: { 200: message } } ) } // Strict status response { new Elysia().post( '/mirror', async ({ status, body }) => { if (Math.random() > 0.5) // @ts-expect-error - should reject extra 'body' property return status(201, { body, success: false }) // @ts-expect-error if (Math.random() > 0.5) return status(200, { success: false }) // @ts-expect-error if (Math.random() > 0.5) return status(201, { success: true }) if (Math.random() > 0.5) return status(200, { success: true }) return status(201, { success: false }) }, { body: t.Object({ code: t.String() }), response: { 200: t.Object({ success: t.Literal(true) }), 201: t.Object({ success: t.Literal(false) }) } } ) } // Status code 200 type inference (issue #1584) { const app = new Elysia().get( '/', () => ({ message: 'Hello Elysia' as const }), { response: { 200: t.Object({ message: t.Literal('Hello Elysia') }) } } ) type AppResponse = (typeof app)['~Routes']['get']['response'] // Should properly infer the 200 response type, not [x: string]: any const _typeTest: AppResponse extends { 200: { message: 'Hello Elysia' } } ? true : false = true // Test with multiple status codes including 200 const app2 = new Elysia().post( '/test', ({ status }) => { if (Math.random() > 0.5) { return status(200, { message: 'Hello Elysia' as const }) } return status(422, { error: 'Validation error' }) }, { response: { 200: t.Object({ message: t.Literal('Hello Elysia') }), 422: t.Object({ error: t.String() }) } } ) type App2Response = (typeof app2)['~Routes']['test']['post']['response'] const _typeTest2: App2Response extends { 200: { message: 'Hello Elysia' } 422: { error: string } } ? true : false = true } // group empty prefix { const app = new Elysia() .group('', (app) => { return app.get('/ok', () => 'Hello World') }) .listen(3000) type Routes = keyof (typeof app)['~Routes'] expectTypeOf().toEqualTypeOf<'ok'>() } // override group prefix type { new Elysia().group('/:example', (app) => app.get( '/', ({ params: { example } }) => { expectTypeOf().toBeNumber() }, { params: t.Object({ example: t.Numeric() }) } ) ) } // ? params with model { new Elysia() .model({ 'character.name': t.String(), 'character.thing': t.Object({ name: t.String() }) }) .get('/id/:id/name/:name', ({ params }) => { expectTypeOf().toEqualTypeOf<{ id: string name: string }>() }) } // ? Promise { async function handler() { return new Response(JSON.stringify({ text: 'hello' }), { status: 200, headers: { 'Content-Type': 'application/json' } }) } new Elysia().get('/hello', () => handler(), { response: { 200: t.Object({ text: t.String() }) } }) } ================================================ FILE: test/types/lifecycle/derive.ts ================================================ // /* eslint-disable @typescript-eslint/no-unused-vars */ // import { expect } from 'bun:test' // import { t, Elysia, RouteSchema, Cookie, error } from '../../../src' // import { expectTypeOf } from 'expect-type' // // Inline Derive // { // new Elysia().get( // '/', // ({ name }) => { // expectTypeOf().toEqualTypeOf<'hare'>() // }, // { // derive: () => { // return { name: 'hare' as const } // } // } // ) // } // // Inline Derive Array // { // new Elysia().get( // '/', // ({ first, last }) => { // expectTypeOf().toEqualTypeOf<'hare'>() // expectTypeOf().toEqualTypeOf<'omagari'>() // }, // { // derive: [ // () => { // return { first: 'hare' as const } // }, // () => { // return { last: 'omagari' as const } // } // ] // } // ) // } // // Inline Derive Array // { // new Elysia().get( // '/', // ({ first, last }) => { // expectTypeOf().toEqualTypeOf<'hare'>() // expectTypeOf().toEqualTypeOf<'omagari'>() // }, // { // derive: [ // () => { // return { first: 'hare' as const } // }, // () => { // return { last: 'omagari' as const } // } // ] // } // ) // } // // ? Group Derive // { // new Elysia() // .guard( // { // derive: () => ({ hi: 'hare' as const }) // }, // (app) => // app.get('/', ({ hi }) => { // expectTypeOf().toEqualTypeOf<'hare'>() // }) // ) // .get('/nope', (context) => { // expectTypeOf().not.toHaveProperty('hi') // }) // } // // ? Group Derive // { // new Elysia() // .guard( // { // derive: [ // () => ({ first: 'hare' as const }), // () => ({ last: 'omagari' as const }) // ] // }, // (app) => // app.get('/', ({ first, last }) => { // expectTypeOf().toEqualTypeOf<'hare'>() // expectTypeOf().toEqualTypeOf<'omagari'>() // }) // ) // .get('/nope', (context) => { // expectTypeOf().not.toHaveProperty('first') // expectTypeOf().not.toHaveProperty('last') // }) // } // // ? Guard Derive // { // const plugin = new Elysia() // .guard({ // derive: () => ({ name: 'hare' as const }) // }) // .get('/', ({ name }) => { // expectTypeOf().toEqualTypeOf<'hare'>() // }) // new Elysia().use(plugin).get('/', (context) => { // expectTypeOf().not.toHaveProperty('name') // }) // } // // ? Guard Derive Array // { // new Elysia() // .guard({ // derive: [ // () => ({ first: 'hare' as const }), // () => ({ last: 'omagari' as const }) // ] // }) // .get('/', ({ first, last }) => { // expectTypeOf().toEqualTypeOf<'hare'>() // expectTypeOf().toEqualTypeOf<'omagari'>() // }) // } // // ? Scoped Derive // { // const plugin = new Elysia() // .guard({ // as: 'scoped', // derive: () => ({ name: 'hare' as const }) // }) // .get('/', ({ name }) => { // expectTypeOf().toEqualTypeOf<'hare'>() // }) // const app = new Elysia().use(plugin).get('/', ({ name }) => { // expectTypeOf().toEqualTypeOf<'hare'>() // }) // const root = new Elysia().use(app).get('/', (context) => { // expectTypeOf().not.toHaveProperty('name') // }) // } // // ? Global Derive // { // const plugin = new Elysia() // .guard({ // as: 'global', // derive: () => ({ name: 'hare' as const }) // }) // .get('/', ({ name }) => { // expectTypeOf().toEqualTypeOf<'hare'>() // }) // const app = new Elysia().use(plugin).get('/', ({ name }) => { // expectTypeOf().toEqualTypeOf<'hare'>() // }) // new Elysia().use(app).get('/', ({ name }) => { // expectTypeOf().toEqualTypeOf<'hare'>() // }) // } ================================================ FILE: test/types/lifecycle/resolve.ts ================================================ // /* eslint-disable @typescript-eslint/no-unused-vars */ // import { expect } from 'bun:test' // import { t, Elysia, RouteSchema, Cookie, error } from '../../../src' // import { expectTypeOf } from 'expect-type' // // Inline Resolve // { // new Elysia().get( // '/', // ({ name }) => { // expectTypeOf().toEqualTypeOf<'hare'>() // }, // { // resolve: () => { // return { name: 'hare' as const } // } // } // ) // } // // Inline Resolve Array // { // new Elysia().get( // '/', // ({ first, last }) => { // expectTypeOf().toEqualTypeOf<'hare'>() // expectTypeOf().toEqualTypeOf<'omagari'>() // }, // { // resolve: [ // () => { // return { first: 'hare' as const } // }, // () => { // return { last: 'omagari' as const } // } // ] // } // ) // } // // Inline Resolve Array // { // new Elysia().get( // '/', // ({ first, last }) => { // expectTypeOf().toEqualTypeOf<'hare'>() // expectTypeOf().toEqualTypeOf<'omagari'>() // }, // { // resolve: [ // () => { // return { first: 'hare' as const } // }, // () => { // return { last: 'omagari' as const } // } // ] // } // ) // } // // ? Group Resolve // { // new Elysia() // .guard( // { // resolve: () => ({ hi: 'hare' as const }) // }, // (app) => // app.get('/', ({ hi }) => { // expectTypeOf().toEqualTypeOf<'hare'>() // }) // ) // .get('/nope', (context) => { // expectTypeOf().not.toHaveProperty('hi') // }) // } // // ? Group Resolve // { // new Elysia() // .guard( // { // resolve: [ // () => ({ first: 'hare' as const }), // () => ({ last: 'omagari' as const }) // ] // }, // (app) => // app.get('/', ({ first, last }) => { // expectTypeOf().toEqualTypeOf<'hare'>() // expectTypeOf().toEqualTypeOf<'omagari'>() // }) // ) // .get('/nope', (context) => { // expectTypeOf().not.toHaveProperty('first') // expectTypeOf().not.toHaveProperty('last') // }) // } // // ? Guard Resolve // { // const plugin = new Elysia() // .guard({ // resolve: () => ({ name: 'hare' as const }) // }) // .get('/', ({ name }) => { // expectTypeOf().toEqualTypeOf<'hare'>() // }) // new Elysia().use(plugin).get('/', (context) => { // expectTypeOf().not.toHaveProperty('name') // }) // } // // ? Guard Resolve Array // { // new Elysia() // .guard({ // resolve: [ // () => ({ first: 'hare' as const }), // () => ({ last: 'omagari' as const }) // ] // }) // .get('/', ({ first, last }) => { // expectTypeOf().toEqualTypeOf<'hare'>() // expectTypeOf().toEqualTypeOf<'omagari'>() // }) // } // // ? Scoped Resolve // { // const plugin = new Elysia() // .guard({ // as: 'scoped', // resolve: () => ({ name: 'hare' as const }) // }) // .get('/', ({ name }) => { // expectTypeOf().toEqualTypeOf<'hare'>() // }) // const app = new Elysia().use(plugin).get('/', ({ name }) => { // expectTypeOf().toEqualTypeOf<'hare'>() // }) // const root = new Elysia().use(app).get('/', (context) => { // expectTypeOf().not.toHaveProperty('name') // }) // } // // ? Global Resolve // { // const plugin = new Elysia() // .guard({ // as: 'global', // resolve: () => ({ name: 'hare' as const }) // }) // .get('/', ({ name }) => { // expectTypeOf().toEqualTypeOf<'hare'>() // }) // const app = new Elysia().use(plugin).get('/', ({ name }) => { // expectTypeOf().toEqualTypeOf<'hare'>() // }) // new Elysia().use(app).get('/', ({ name }) => { // expectTypeOf().toEqualTypeOf<'hare'>() // }) // } // // ? Macro resolve // { // const macro = new Elysia() // .macro({ // a: () => ({ // resolve: () => ({ // message: 'hello' as const // }) // }) // }) // .get( // '/', // ({ message }) => { // expectTypeOf().toEqualTypeOf<'hello'>() // }, // { // a: true // } // ) // const main = new Elysia().use(macro).get( // '/', // ({ message }) => { // expectTypeOf().toEqualTypeOf<'hello'>() // }, // { // a: true // } // ) // } ================================================ FILE: test/types/lifecycle/soundness.ts ================================================ import { Cookie, Elysia, t } from '../../../src' import { expectTypeOf } from 'expect-type' import { Prettify } from '../../../src/types' // Handle resolve property correctly { const app = new Elysia().resolve(({ status }) => { if (Math.random() > 0.05) return status(401) return { name: 'mokou' } }) type Resolve = (typeof app)['~Volatile']['resolve'] expectTypeOf().toEqualTypeOf<{ name: 'mokou' }> } // Handle resolve property without any data { const app = new Elysia().resolve(({ status }) => { if (Math.random() > 0.05) return status(401) }) type Resolve = (typeof app)['~Volatile']['resolve'] expectTypeOf().toEqualTypeOf<{}> } // Type soundness of lifecycle event in local { const app = new Elysia() .onError(({ status }) => { if (Math.random() > 0.05) return status(400) }) .resolve(({ status }) => { if (Math.random() > 0.05) return status(401) }) .onBeforeHandle([ ({ status }) => { if (Math.random() > 0.05) return status(402) }, ({ status }) => { if (Math.random() > 0.05) return status(403) } ]) .guard({ beforeHandle: [ ({ status }) => { if (Math.random() > 0.05) return status(405) }, ({ status }) => { if (Math.random() > 0.05) return status(406) } ], afterHandle({ status }) { if (Math.random() > 0.05) return status(407) }, error({ status }) { if (Math.random() > 0.05) return status(408) } }) .get('/', ({ body, status }) => Math.random() > 0.05 ? status(409) : ('Hello World' as const) ) type Lifecycle = Prettify<(typeof app)['~Volatile']['response']> expectTypeOf().toEqualTypeOf<{ 400: 'Bad Request' 401: 'Unauthorized' 402: 'Payment Required' 403: 'Forbidden' 405: 'Method Not Allowed' 406: 'Not Acceptable' 407: 'Proxy Authentication Required' 408: 'Request Timeout' }>() type Route = Prettify<(typeof app)['~Routes']['get']['response']> expectTypeOf().toEqualTypeOf<{ 200: 'Hello World' 400: 'Bad Request' 401: 'Unauthorized' 402: 'Payment Required' 403: 'Forbidden' 405: 'Method Not Allowed' 406: 'Not Acceptable' 407: 'Proxy Authentication Required' 408: 'Request Timeout' 409: 'Conflict' }> } // Type soundness of lifecycle event in scoped { const app = new Elysia() .onError(({ status }) => { if (Math.random() > 0.05) return status(400) }) .resolve(({ status }) => { if (Math.random() > 0.05) return status(401) }) .onBeforeHandle([ ({ status }) => { if (Math.random() > 0.05) return status(402) }, ({ status }) => { if (Math.random() > 0.05) return status(403) } ]) .guard({ beforeHandle: [ ({ status }) => { if (Math.random() > 0.05) return status(405) }, ({ status }) => { if (Math.random() > 0.05) return status(406) } ], afterHandle({ status }) { if (Math.random() > 0.05) return status(407) }, error({ status }) { if (Math.random() > 0.05) return status(408) } }) .as('scoped') .get('/', ({ body, status }) => Math.random() > 0.05 ? status(409) : ('Hello World' as const) ) type Lifecycle = Prettify<(typeof app)['~Ephemeral']['response']> expectTypeOf().toEqualTypeOf<{ 400: 'Bad Request' 401: 'Unauthorized' 402: 'Payment Required' 403: 'Forbidden' 405: 'Method Not Allowed' 406: 'Not Acceptable' 407: 'Proxy Authentication Required' 408: 'Request Timeout' }>() type Route = Prettify<(typeof app)['~Routes']['get']['response']> expectTypeOf().toEqualTypeOf<{ 200: 'Hello World' 400: 'Bad Request' 401: 'Unauthorized' 402: 'Payment Required' 403: 'Forbidden' 405: 'Method Not Allowed' 406: 'Not Acceptable' 407: 'Proxy Authentication Required' 408: 'Request Timeout' 409: 'Conflict' }> } // Type soundness of lifecycle event in global { const app = new Elysia() .onError(({ status }) => { if (Math.random() > 0.05) return status(400) }) .resolve(({ status }) => { if (Math.random() > 0.05) return status(401) }) .onBeforeHandle([ ({ status }) => { if (Math.random() > 0.05) return status(402) }, ({ status }) => { if (Math.random() > 0.05) return status(403) } ]) .guard({ beforeHandle: [ ({ status }) => { if (Math.random() > 0.05) return status(405) }, ({ status }) => { if (Math.random() > 0.05) return status(406) } ], afterHandle({ status }) { if (Math.random() > 0.05) return status(407) }, error({ status }) { if (Math.random() > 0.05) return status(408) } }) .as('global') .get('/', ({ body, status }) => Math.random() > 0.05 ? status(409) : ('Hello World' as const) ) type Lifecycle = Prettify<(typeof app)['~Metadata']['response']> expectTypeOf().toEqualTypeOf<{ 400: 'Bad Request' 401: 'Unauthorized' 402: 'Payment Required' 403: 'Forbidden' 405: 'Method Not Allowed' 406: 'Not Acceptable' 407: 'Proxy Authentication Required' 408: 'Request Timeout' }>() type Route = Prettify<(typeof app)['~Routes']['get']['response']> expectTypeOf().toEqualTypeOf<{ 200: 'Hello World' 400: 'Bad Request' 401: 'Unauthorized' 402: 'Payment Required' 403: 'Forbidden' 405: 'Method Not Allowed' 406: 'Not Acceptable' 407: 'Proxy Authentication Required' 408: 'Request Timeout' 409: 'Conflict' }> } // All together now { const app = new Elysia() .macro({ auth: { response: { 409: t.Literal('Conflict') }, beforeHandle({ status }) { if (Math.random() < 0.05) return status(410) }, resolve: () => ({ a: 'a' as const }) } }) .onError(({ status }) => { if (Math.random() < 0.05) return status(400) }) .resolve(({ status }) => { if (Math.random() < 0.05) return status(401) return { b: 'b' as const } }) .onBeforeHandle([ ({ status }) => { if (Math.random() < 0.05) return status(402) }, ({ status }) => { if (Math.random() < 0.05) return status(403) } ]) .guard({ beforeHandle: [ ({ status }) => { if (Math.random() < 0.05) return status(405) }, ({ status }) => { if (Math.random() < 0.05) return status(406) } ], afterHandle({ status }) { if (Math.random() < 0.05) return status(407) }, error({ status }) { if (Math.random() < 0.05) return status(408) } }) .post( '/', ({ status, a, b }) => { if (Math.random() < 0.05) return status(409, 'Conflict') expectTypeOf().toEqualTypeOf<'a'>() expectTypeOf().toEqualTypeOf<'b'>() return 'Type Soundness' }, { auth: true, response: { 411: t.Literal('Length Required') } } ) type Lifecycle = (typeof app)['~Routes']['post']['response'] expectTypeOf().toEqualTypeOf<{ 200: 'Type Soundness' 400: 'Bad Request' 401: 'Unauthorized' 402: 'Payment Required' 403: 'Forbidden' 405: 'Method Not Allowed' 406: 'Not Acceptable' 407: 'Proxy Authentication Required' 408: 'Request Timeout' 409: 'Conflict' 410: 'Gone' 411: 'Length Required' 422: { type: 'validation' on: string summary?: string message?: string found?: unknown property?: string expected?: string } }>() } // Macro without schema should not have 422 { const app = new Elysia() .macro({ auth: { beforeHandle({ status }) { if (Math.random() < 0.05) return status(410) } } }) .get('/', () => 'Hello World' as const, { auth: true }) type Route = (typeof app)['~Routes']['get']['response'] expectTypeOf().toEqualTypeOf<{ 200: 'Hello World' 410: 'Gone' }>() } // Macro with schema should have 422 { const app = new Elysia() .macro({ auth: { response: { 401: t.Literal('Unauthorized') }, beforeHandle({ status }) { if (Math.random() < 0.05) return status(410) } } }) .get('/', () => 'Hello World' as const, { auth: true }) type Route = (typeof app)['~Routes']['get']['response'] expectTypeOf().toEqualTypeOf<{ 200: 'Hello World' 401: 'Unauthorized' 410: 'Gone' 422: { type: 'validation' on: string summary?: string message?: string found?: unknown property?: string expected?: string } }>() } // Macro should inject schema { const app = new Elysia() .macro({ auth: { body: t.Object({ name: t.Literal('lilith') }), query: t.Object({ name: t.Literal('lilith') }), headers: t.Object({ name: t.Literal('lilith') }), params: t.Object({ name: t.Literal('lilith') }), cookie: t.Object({ name: t.Literal('lilith') }), response: { 401: t.Literal('Unauthorized') }, beforeHandle({ status }) { if (Math.random() < 0.05) return status(410) } } }) .get( '/', ({ headers, body, cookie, params, query, status }) => { expectTypeOf().toEqualTypeOf<{ name: 'lilith' }>() expectTypeOf().toEqualTypeOf<{ name: 'lilith' }>() expectTypeOf().toEqualTypeOf< Record> & { name: Cookie<'lilith'> } >() expectTypeOf().toEqualTypeOf<{ name: 'lilith' }>() expectTypeOf().toEqualTypeOf<{ name: 'lilith' }>() if (Math.random() > 0.5) return status(401, 'Unauthorized') if (Math.random() > 0.5) // @ts-expect-error return status(401, 'Unauthorize') return 'Hello World' as const }, { auth: true } ) type Route = (typeof app)['~Routes']['get']['response'] expectTypeOf().toEqualTypeOf<{ 200: 'Hello World' 401: 'Unauthorized' 410: 'Gone' 422: { type: 'validation' on: string summary?: string message?: string found?: unknown property?: string expected?: string } }>() } // Macro should inject schema to guard { const app = new Elysia() .macro({ auth: { body: t.Object({ name: t.Literal('lilith') }), query: t.Object({ name: t.Literal('lilith') }), headers: t.Object({ name: t.Literal('lilith') }), params: t.Object({ name: t.Literal('lilith') }), cookie: t.Object({ name: t.Literal('lilith') }), response: { 401: t.Literal('Unauthorized') }, beforeHandle({ status }) { if (Math.random() < 0.05) return status(410) } } }) .guard({ auth: true }) .get('/', ({ headers, body, cookie, params, query, status }) => { expectTypeOf().toEqualTypeOf<{ name: 'lilith' }>() expectTypeOf().toEqualTypeOf<{ name: 'lilith' }>() expectTypeOf().toEqualTypeOf< Record> & { name: Cookie<'lilith'> } >() expectTypeOf().toEqualTypeOf<{ name: 'lilith' }>() expectTypeOf().toEqualTypeOf<{ name: 'lilith' }>() if (Math.random() > 0.5) return status(401, 'Unauthorized') if (Math.random() > 0.5) // @ts-expect-error return status(401, 'Unauthorize') return 'Hello World' as const }) app['~Volatile']['standaloneSchema']['response']['401'] type Route = (typeof app)['~Routes']['get']['response'] expectTypeOf().toEqualTypeOf<{ 200: 'Hello World' 401: 'Unauthorized' 410: 'Gone' 422: { type: 'validation' on: string summary?: string message?: string found?: unknown property?: string expected?: string } }>() } // Guard should extract possible status 1 { const app = new Elysia().guard({ beforeHandle({ status }) { if (Math.random() < 0.05) return status(410) } }) expectTypeOf<(typeof app)['~Volatile']['response']>().toEqualTypeOf<{ 410: 'Gone' }>() } // Guard should extract possible status 2 { const app = new Elysia().guard({ afterHandle({ status }) { return status(411) } }) expectTypeOf<(typeof app)['~Volatile']['response']>().toEqualTypeOf<{ 411: 'Length Required' }>() } // Guard should extract possible status 3 { const app = new Elysia().guard({ error: [ ({ status }) => { return status(412) }, ({ status }) => { if (Math.random() > 0.5) return status(413) } ] }) expectTypeOf<(typeof app)['~Volatile']['response']>().toEqualTypeOf<{ 412: 'Precondition Failed' 413: 'Payload Too Large' }>() } // Guard should extract possible status 4 { const app = new Elysia().guard({ beforeHandle({ status }) { if (Math.random() < 0.05) return status(410) }, error: [ ({ status }) => { return status(412) }, ({ status }) => { if (Math.random() > 0.5) return status(413) } ] }) expectTypeOf< Prettify<(typeof app)['~Volatile']['response']> >().toEqualTypeOf<{ 410: 'Gone' 412: 'Precondition Failed' 413: 'Payload Too Large' }>() } // Guard should extract possible status 5 { const app = new Elysia() .macro({ a: { resolve() { return { a: 'a' } }, beforeHandle({ status }) { return status(409) } } }) .guard({ a: true, beforeHandle({ status }) { if (Math.random() < 0.05) return status(410) }, error: [ ({ status }) => { return status(412) }, ({ status }) => { if (Math.random() > 0.5) return status(413) } ] }) expectTypeOf< Prettify<(typeof app)['~Volatile']['response']> >().toEqualTypeOf<{ 409: 'Conflict' 410: 'Gone' 412: 'Precondition Failed' 413: 'Payload Too Large' }>() } // Macro should extract possible status 1 { const app = new Elysia() .macro({ a: { beforeHandle({ status }) { if (Math.random() < 0.05) return status(410) } } }) .guard({ a: true }) expectTypeOf<(typeof app)['~Volatile']['response']>().toEqualTypeOf<{ 410: 'Gone' }>() } // Macro should extract possible status 2 { const app = new Elysia() .macro({ a: { afterHandle({ status }) { return status(411) } } }) .guard({ a: true }) expectTypeOf<(typeof app)['~Volatile']['response']>().toEqualTypeOf<{ 411: 'Length Required' }>() } // Macro should extract possible status 3 { const app = new Elysia() .macro({ a: { error({ status }) { if (Math.random() > 0.5) return status(412) } } }) .guard({ a: true }) expectTypeOf<(typeof app)['~Volatile']['response']>().toEqualTypeOf<{ 412: 'Precondition Failed' }>() } // Macro should extract possible status 4 { const app = new Elysia() .macro({ a: { beforeHandle({ status }) { if (Math.random() > 0.5) return status(410) }, afterHandle({ status }) { if (Math.random() > 0.5) return status(411) } }, b: { error({ status }) { if (Math.random() > 0.5) return status(412) } } }) .guard({ a: true, b: true }) expectTypeOf< Prettify<(typeof app)['~Volatile']['response']> >().toEqualTypeOf<{ 410: 'Gone' 411: 'Length Required' 412: 'Precondition Failed' }>() } // Guard should cast to scoped { const app = new Elysia() .macro({ a: { beforeHandle({ status }) { if (Math.random() > 0.5) return status(410) }, afterHandle({ status }) { if (Math.random() > 0.5) return status(411) } }, b: { error({ status }) { if (Math.random() > 0.5) return status(412) } } }) .guard({ as: 'scoped', a: true, b: true }) expectTypeOf< Prettify<(typeof app)['~Ephemeral']['response']> >().toEqualTypeOf<{ 410: 'Gone' 411: 'Length Required' 412: 'Precondition Failed' }>() } // Guard should cast to global { const app = new Elysia() .macro({ a: { beforeHandle({ status }) { if (Math.random() > 0.5) return status(410) }, afterHandle({ status }) { if (Math.random() > 0.5) return status(411) } }, b: { error({ status }) { if (Math.random() > 0.5) return status(412) } } }) .guard({ as: 'global', a: true, b: true }) expectTypeOf< Prettify<(typeof app)['~Metadata']['response']> >().toEqualTypeOf<{ 410: 'Gone' 411: 'Length Required' 412: 'Precondition Failed' }>() } // Unwrap ElysiaCustomStatusResponse value in resolve macro automatically { const app = new Elysia() .macro({ auth: { resolve({ status }) { if (Math.random() > 0.5) return status(401) return { user: 'saltyaom' } as const } } }) .get('/', ({ user }) => user, { auth: true }) expectTypeOf<(typeof app)['~Routes']['get']['response']>().toEqualTypeOf<{ 200: 'saltyaom' 401: 'Unauthorized' }>() } // Unwrap beforeHandle 200 status { const app = new Elysia() .macro({ auth: { beforeHandle({ status }) { if (Math.random() > 0.5) return status(401) if (Math.random() > 0.5) return 'lilith' } } }) .get('/', () => 'fouco' as const, { auth: true }) expectTypeOf<(typeof app)['~Routes']['get']['response']>().toEqualTypeOf<{ 200: 'lilith' | 'fouco' 401: 'Unauthorized' }>() } // Reconcile response { const app = new Elysia() .onBeforeHandle(({ status }) => Math.random() > 0.5 ? status(404, 'lilith') : 'lilith' ) .get('/', ({ status }) => Math.random() > 0.5 ? status(404, 'fouco') : 'fouco' ) expectTypeOf<(typeof app)['~Routes']['get']['response']>().toEqualTypeOf<{ 200: 'lilith' | 'fouco' 404: 'lilith' | 'fouco' }>() } // onBeforeHandle { const app = new Elysia() .onBeforeHandle(({ status }) => Math.random() > 0.5 ? status(404, 'lilith') : 'lilith' ) .onBeforeHandle([ ({ status }) => Math.random() > 0.5 ? status(401, 'fouco') : 'fouco', ({ status }) => Math.random() > 0.5 ? status(418, 'sartre') : 'sartre' ]) expectTypeOf<(typeof app)['~Volatile']['response']>().toEqualTypeOf<{ 200: 'fouco' | 'sartre' | 'lilith' 401: 'fouco' 404: 'lilith' 418: 'sartre' }>() } // onBeforeHandle scoped { const app = new Elysia() .onBeforeHandle({ as: 'scoped' }, ({ status }) => Math.random() > 0.5 ? status(404, 'lilith') : 'lilith' ) .onBeforeHandle({ as: 'scoped' }, [ ({ status }) => Math.random() > 0.5 ? status(401, 'fouco') : 'fouco', ({ status }) => Math.random() > 0.5 ? status(418, 'sartre') : 'sartre' ]) expectTypeOf<(typeof app)['~Ephemeral']['response']>().toEqualTypeOf<{ 200: 'fouco' | 'sartre' | 'lilith' 401: 'fouco' 404: 'lilith' 418: 'sartre' }>() } // onBeforeHandle global { const app = new Elysia() .onBeforeHandle({ as: 'global' }, ({ status }) => Math.random() > 0.5 ? status(404, 'lilith') : 'lilith' ) .onBeforeHandle({ as: 'global' }, [ ({ status }) => Math.random() > 0.5 ? status(401, 'fouco') : 'fouco', ({ status }) => Math.random() > 0.5 ? status(418, 'sartre') : 'sartre' ]) expectTypeOf<(typeof app)['~Metadata']['response']>().toEqualTypeOf<{ 200: 'fouco' | 'sartre' | 'lilith' 401: 'fouco' 404: 'lilith' 418: 'sartre' }>() } // onAfterHandle local { const app = new Elysia() .onAfterHandle(({ status }) => Math.random() > 0.5 ? status(404, 'lilith') : 'lilith' ) .onAfterHandle([ ({ status }) => Math.random() > 0.5 ? status(401, 'fouco') : 'fouco', ({ status }) => Math.random() > 0.5 ? status(418, 'sartre') : 'sartre' ]) expectTypeOf<(typeof app)['~Volatile']['response']>().toEqualTypeOf<{ 200: 'fouco' | 'sartre' | 'lilith' 401: 'fouco' 404: 'lilith' 418: 'sartre' }>() } // onAfterHandle scoped { const app = new Elysia() .onAfterHandle({ as: 'scoped' }, ({ status }) => Math.random() > 0.5 ? status(404, 'lilith') : 'lilith' ) .onAfterHandle({ as: 'scoped' }, [ ({ status }) => Math.random() > 0.5 ? status(401, 'fouco') : 'fouco', ({ status }) => Math.random() > 0.5 ? status(418, 'sartre') : 'sartre' ]) expectTypeOf<(typeof app)['~Ephemeral']['response']>().toEqualTypeOf<{ 200: 'fouco' | 'sartre' | 'lilith' 401: 'fouco' 404: 'lilith' 418: 'sartre' }>() } // onAfterHandle global { const app = new Elysia() .onAfterHandle({ as: 'global' }, ({ status }) => Math.random() > 0.5 ? status(404, 'lilith') : 'lilith' ) .onAfterHandle({ as: 'global' }, [ ({ status }) => Math.random() > 0.5 ? status(401, 'fouco') : 'fouco', ({ status }) => Math.random() > 0.5 ? status(418, 'sartre') : 'sartre' ]) expectTypeOf<(typeof app)['~Metadata']['response']>().toEqualTypeOf<{ 200: 'fouco' | 'sartre' | 'lilith' 401: 'fouco' 404: 'lilith' 418: 'sartre' }>() } // onError local { const app = new Elysia() .onError(({ status }) => Math.random() > 0.5 ? status(404, 'lilith') : 'lilith' ) .onError([ ({ status }) => Math.random() > 0.5 ? status(401, 'fouco') : 'fouco', ({ status }) => Math.random() > 0.5 ? status(418, 'sartre') : 'sartre' ]) expectTypeOf<(typeof app)['~Volatile']['response']>().toEqualTypeOf<{ 200: 'fouco' | 'sartre' | 'lilith' 401: 'fouco' 404: 'lilith' 418: 'sartre' }>() } // onError scoped { const app = new Elysia() .onError({ as: 'scoped' }, ({ status }) => Math.random() > 0.5 ? status(404, 'lilith') : 'lilith' ) .onError({ as: 'scoped' }, [ ({ status }) => Math.random() > 0.5 ? status(401, 'fouco') : 'fouco', ({ status }) => Math.random() > 0.5 ? status(418, 'sartre') : 'sartre' ]) expectTypeOf<(typeof app)['~Ephemeral']['response']>().toEqualTypeOf<{ 200: 'fouco' | 'sartre' | 'lilith' 401: 'fouco' 404: 'lilith' 418: 'sartre' }>() } // onError global { const app = new Elysia() .onError({ as: 'global' }, ({ status }) => Math.random() > 0.5 ? status(404, 'lilith') : 'lilith' ) .onError({ as: 'global' }, [ ({ status }) => Math.random() > 0.5 ? status(401, 'fouco') : 'fouco', ({ status }) => Math.random() > 0.5 ? status(418, 'sartre') : 'sartre' ]) expectTypeOf<(typeof app)['~Metadata']['response']>().toEqualTypeOf<{ 200: 'fouco' | 'sartre' | 'lilith' 401: 'fouco' 404: 'lilith' 418: 'sartre' }>() } // resolve local { const app = new Elysia() .resolve(({ status }) => Math.random() > 0.5 ? status(401, 'sartre') : { friends: ['lilith'] } ) .resolve(({ status }) => Math.random() > 0.5 ? status(401, 'fouco') : { friends: ['lilith'] } ) .get('/', ({ friends, status }) => { if (Math.random() > 0.5) return status(401, friends[0]) return 'NOexistenceN' }) expectTypeOf<(typeof app)['~Volatile']['resolve']>().toEqualTypeOf<{ readonly friends: readonly ['lilith'] }>() expectTypeOf<(typeof app)['~Volatile']['response']>().toEqualTypeOf<{ 401: 'sartre' | 'fouco' }>() expectTypeOf<(typeof app)['~Routes']['get']['response']>().toEqualTypeOf<{ 200: 'NOexistenceN' 401: 'sartre' | 'fouco' | 'lilith' }>() } // resolve scoped { const app = new Elysia() .resolve({ as: 'scoped' }, ({ status }) => Math.random() > 0.5 ? status(401, 'sartre') : { friends: ['lilith'] } ) .resolve({ as: 'scoped' }, ({ status }) => Math.random() > 0.5 ? status(401, 'fouco') : { friends: ['lilith'] } ) .get('/', ({ friends, status }) => { if (Math.random() > 0.5) return status(401, friends[0]) return 'NOexistenceN' }) expectTypeOf<(typeof app)['~Ephemeral']['resolve']>().toEqualTypeOf<{ readonly friends: readonly ['lilith'] }>() expectTypeOf<(typeof app)['~Ephemeral']['response']>().toEqualTypeOf<{ 401: 'sartre' | 'fouco' }>() expectTypeOf<(typeof app)['~Routes']['get']['response']>().toEqualTypeOf<{ 200: 'NOexistenceN' 401: 'sartre' | 'fouco' | 'lilith' }>() } // resolve global { const app = new Elysia() .resolve({ as: 'global' }, ({ status }) => Math.random() > 0.5 ? status(401, 'sartre') : { friends: ['lilith'] } ) .resolve({ as: 'global' }, ({ status }) => Math.random() > 0.5 ? status(401, 'fouco') : { friends: ['lilith'] } ) .get('/', ({ friends, status }) => { if (Math.random() > 0.5) return status(401, friends[0]) return 'NOexistenceN' }) expectTypeOf<(typeof app)['~Singleton']['resolve']>().toEqualTypeOf<{ readonly friends: readonly ['lilith'] }>() expectTypeOf<(typeof app)['~Metadata']['response']>().toEqualTypeOf<{ 401: 'sartre' | 'fouco' }>() expectTypeOf<(typeof app)['~Routes']['get']['response']>().toEqualTypeOf<{ 200: 'NOexistenceN' 401: 'sartre' | 'fouco' | 'lilith' }>() } // mapResolve local { const app = new Elysia() .mapResolve(({ status }) => Math.random() > 0.5 ? status(401, 'sartre') : { friends: ['lilith'] } ) .mapResolve(({ status }) => Math.random() > 0.5 ? status(401, 'fouco') : { friends: ['lilith'] } ) .get('/', ({ friends, status }) => { if (Math.random() > 0.5) return status(401, friends[0]) return 'NOexistenceN' }) expectTypeOf<(typeof app)['~Volatile']['resolve']>().toEqualTypeOf<{ readonly friends: readonly ['lilith'] }>() expectTypeOf<(typeof app)['~Volatile']['response']>().toEqualTypeOf<{ 401: 'sartre' | 'fouco' }>() expectTypeOf<(typeof app)['~Routes']['get']['response']>().toEqualTypeOf<{ 200: 'NOexistenceN' 401: 'sartre' | 'fouco' | 'lilith' }>() } // mapResolve scoped { const app = new Elysia() .mapResolve({ as: 'scoped' }, ({ status }) => Math.random() > 0.5 ? status(401, 'sartre') : { friends: ['lilith'] } ) .mapResolve({ as: 'scoped' }, ({ status }) => Math.random() > 0.5 ? status(401, 'fouco') : { friends: ['lilith'] } ) .get('/', ({ friends, status }) => { if (Math.random() > 0.5) return status(401, friends[0]) return 'NOexistenceN' }) expectTypeOf<(typeof app)['~Ephemeral']['resolve']>().toEqualTypeOf<{ readonly friends: readonly ['lilith'] }>() expectTypeOf<(typeof app)['~Ephemeral']['response']>().toEqualTypeOf<{ 401: 'sartre' | 'fouco' }>() expectTypeOf<(typeof app)['~Routes']['get']['response']>().toEqualTypeOf<{ 200: 'NOexistenceN' 401: 'sartre' | 'fouco' | 'lilith' }>() } // mapResolve global { const app = new Elysia() .mapResolve({ as: 'global' }, ({ status }) => Math.random() > 0.5 ? status(401, 'sartre') : { friends: ['lilith'] } ) .mapResolve({ as: 'global' }, ({ status }) => Math.random() > 0.5 ? status(401, 'fouco') : { friends: ['lilith'] } ) .get('/', ({ friends, status }) => { if (Math.random() > 0.5) return status(401, friends[0]) return 'NOexistenceN' }) expectTypeOf<(typeof app)['~Singleton']['resolve']>().toEqualTypeOf<{ readonly friends: readonly ['lilith'] }>() expectTypeOf<(typeof app)['~Metadata']['response']>().toEqualTypeOf<{ 401: 'sartre' | 'fouco' }>() expectTypeOf<(typeof app)['~Routes']['get']['response']>().toEqualTypeOf<{ 200: 'NOexistenceN' 401: 'sartre' | 'fouco' | 'lilith' }>() } // derive local { const app = new Elysia() .derive(({ status }) => Math.random() > 0.5 ? status(401, 'sartre') : { friends: ['lilith'] } ) .derive(({ status }) => Math.random() > 0.5 ? status(401, 'fouco') : { friends: ['lilith'] } ) .get('/', ({ friends, status }) => { if (Math.random() > 0.5) return status(401, friends[0]) return 'NOexistenceN' }) expectTypeOf<(typeof app)['~Volatile']['derive']>().toEqualTypeOf<{ readonly friends: readonly ['lilith'] }>() expectTypeOf<(typeof app)['~Volatile']['response']>().toEqualTypeOf<{ 401: 'sartre' | 'fouco' }>() expectTypeOf<(typeof app)['~Routes']['get']['response']>().toEqualTypeOf<{ 200: 'NOexistenceN' 401: 'sartre' | 'fouco' | 'lilith' }>() } // derive scoped { const app = new Elysia() .derive({ as: 'scoped' }, ({ status }) => Math.random() > 0.5 ? status(401, 'sartre') : { friends: ['lilith'] } ) .derive({ as: 'scoped' }, ({ status }) => Math.random() > 0.5 ? status(401, 'fouco') : { friends: ['lilith'] } ) .get('/', ({ friends, status }) => { if (Math.random() > 0.5) return status(401, friends[0]) return 'NOexistenceN' }) expectTypeOf<(typeof app)['~Ephemeral']['derive']>().toEqualTypeOf<{ readonly friends: readonly ['lilith'] }>() expectTypeOf<(typeof app)['~Ephemeral']['response']>().toEqualTypeOf<{ 401: 'sartre' | 'fouco' }>() expectTypeOf<(typeof app)['~Routes']['get']['response']>().toEqualTypeOf<{ 200: 'NOexistenceN' 401: 'sartre' | 'fouco' | 'lilith' }>() } // derive global { const app = new Elysia() .derive({ as: 'global' }, ({ status }) => Math.random() > 0.5 ? status(401, 'sartre') : { friends: ['lilith'] } ) .derive({ as: 'global' }, ({ status }) => Math.random() > 0.5 ? status(401, 'fouco') : { friends: ['lilith'] } ) .get('/', ({ friends, status }) => { if (Math.random() > 0.5) return status(401, friends[0]) return 'NOexistenceN' }) expectTypeOf<(typeof app)['~Singleton']['derive']>().toEqualTypeOf<{ readonly friends: readonly ['lilith'] }>() expectTypeOf<(typeof app)['~Metadata']['response']>().toEqualTypeOf<{ 401: 'sartre' | 'fouco' }>() expectTypeOf<(typeof app)['~Routes']['get']['response']>().toEqualTypeOf<{ 200: 'NOexistenceN' 401: 'sartre' | 'fouco' | 'lilith' }>() } // mapDerive local { const app = new Elysia() .mapDerive(({ status }) => Math.random() > 0.5 ? status(401, 'sartre') : { friends: ['lilith'] } ) .mapDerive(({ status }) => Math.random() > 0.5 ? status(401, 'fouco') : { friends: ['lilith'] } ) .get('/', ({ friends, status }) => { if (Math.random() > 0.5) return status(401, friends[0]) return 'NOexistenceN' }) expectTypeOf<(typeof app)['~Volatile']['derive']>().toEqualTypeOf<{ readonly friends: readonly ['lilith'] }>() expectTypeOf<(typeof app)['~Volatile']['response']>().toEqualTypeOf<{ 401: 'sartre' | 'fouco' }>() expectTypeOf<(typeof app)['~Routes']['get']['response']>().toEqualTypeOf<{ 200: 'NOexistenceN' 401: 'sartre' | 'fouco' | 'lilith' }>() } // mapDerive scoped { const app = new Elysia() .mapDerive({ as: 'scoped' }, ({ status }) => Math.random() > 0.5 ? status(401, 'sartre') : { friends: ['lilith'] } ) .mapDerive({ as: 'scoped' }, ({ status }) => Math.random() > 0.5 ? status(401, 'fouco') : { friends: ['lilith'] } ) .get('/', ({ friends, status }) => { if (Math.random() > 0.5) return status(401, friends[0]) return 'NOexistenceN' }) expectTypeOf<(typeof app)['~Ephemeral']['derive']>().toEqualTypeOf<{ readonly friends: readonly ['lilith'] }>() expectTypeOf<(typeof app)['~Ephemeral']['response']>().toEqualTypeOf<{ 401: 'sartre' | 'fouco' }>() expectTypeOf<(typeof app)['~Routes']['get']['response']>().toEqualTypeOf<{ 200: 'NOexistenceN' 401: 'sartre' | 'fouco' | 'lilith' }>() } // mapDerive global { const app = new Elysia() .mapDerive({ as: 'global' }, ({ status }) => Math.random() > 0.5 ? status(401, 'sartre') : { friends: ['lilith'] } ) .mapDerive({ as: 'global' }, ({ status }) => Math.random() > 0.5 ? status(401, 'fouco') : { friends: ['lilith'] } ) .get('/', ({ friends, status }) => { if (Math.random() > 0.5) return status(401, friends[0]) return 'NOexistenceN' }) expectTypeOf<(typeof app)['~Singleton']['derive']>().toEqualTypeOf<{ readonly friends: readonly ['lilith'] }>() expectTypeOf<(typeof app)['~Metadata']['response']>().toEqualTypeOf<{ 401: 'sartre' | 'fouco' }>() expectTypeOf<(typeof app)['~Routes']['get']['response']>().toEqualTypeOf<{ 200: 'NOexistenceN' 401: 'sartre' | 'fouco' | 'lilith' }>() } // Guard local { const app = new Elysia() .macro({ q: { beforeHandle: [ ({ status }) => { if (Math.random() > 0.05) return status(401) }, ({ status }) => { if (Math.random() > 0.05) return status(402) } ], afterHandle({ status }) { if (Math.random() > 0.05) return status(403) }, error({ status }) { if (Math.random() > 0.05) return status(404, 'lilith') } } }) .guard({ q: true, beforeHandle: [ ({ status }) => { if (Math.random() > 0.05) return status(405) }, ({ status }) => { if (Math.random() > 0.05) return status(406) } ], afterHandle({ status }) { if (Math.random() > 0.05) return status(407) }, error({ status }) { if (Math.random() > 0.05) return status(408) } }) .get('/', () => 'NOexistenceN' as const) expectTypeOf< Prettify<(typeof app)['~Volatile']['response']> >().toEqualTypeOf<{ 401: 'Unauthorized' 402: 'Payment Required' 403: 'Forbidden' 404: 'lilith' 405: 'Method Not Allowed' 406: 'Not Acceptable' 407: 'Proxy Authentication Required' 408: 'Request Timeout' }>() expectTypeOf< Prettify<(typeof app)['~Routes']['get']['response']> >().toEqualTypeOf<{ 200: 'NOexistenceN' 401: 'Unauthorized' 402: 'Payment Required' 403: 'Forbidden' 404: 'lilith' 405: 'Method Not Allowed' 406: 'Not Acceptable' 407: 'Proxy Authentication Required' 408: 'Request Timeout' }>() } // Guard scoped { const app = new Elysia() .macro({ q: { beforeHandle: [ ({ status }) => { if (Math.random() > 0.05) return status(401) }, ({ status }) => { if (Math.random() > 0.05) return status(402) } ], afterHandle({ status }) { if (Math.random() > 0.05) return status(403) }, error({ status }) { if (Math.random() > 0.05) return status(404, 'lilith') } } }) .guard({ as: 'scoped', q: true, beforeHandle: [ ({ status }) => { if (Math.random() > 0.05) return status(405) }, ({ status }) => { if (Math.random() > 0.05) return status(406) } ], afterHandle({ status }) { if (Math.random() > 0.05) return status(407) }, error({ status }) { if (Math.random() > 0.05) return status(408) } }) .get('/', () => 'NOexistenceN' as const) expectTypeOf< Prettify<(typeof app)['~Ephemeral']['response']> >().toEqualTypeOf<{ 401: 'Unauthorized' 402: 'Payment Required' 403: 'Forbidden' 404: 'lilith' 405: 'Method Not Allowed' 406: 'Not Acceptable' 407: 'Proxy Authentication Required' 408: 'Request Timeout' }>() type A = keyof (typeof app)['~Routes']['get']['response'] expectTypeOf< Prettify<(typeof app)['~Routes']['get']['response']> >().toEqualTypeOf<{ 200: 'NOexistenceN' 401: 'Unauthorized' 402: 'Payment Required' 403: 'Forbidden' 404: 'lilith' 405: 'Method Not Allowed' 406: 'Not Acceptable' 407: 'Proxy Authentication Required' 408: 'Request Timeout' }>() } // Guard global { const app = new Elysia() .macro({ q: { beforeHandle: [ ({ status }) => { if (Math.random() > 0.05) return status(401) }, ({ status }) => { if (Math.random() > 0.05) return status(402) } ], afterHandle({ status }) { if (Math.random() > 0.05) return status(403) }, error({ status }) { if (Math.random() > 0.05) return status(404, 'lilith') } } }) .guard({ as: 'global', q: true, beforeHandle: [ ({ status }) => { if (Math.random() > 0.05) return status(405) }, ({ status }) => { if (Math.random() > 0.05) return status(406) } ], afterHandle({ status }) { if (Math.random() > 0.05) return status(407) }, error({ status }) { if (Math.random() > 0.05) return status(408) } }) .get('/', () => 'NOexistenceN' as const) expectTypeOf< Prettify<(typeof app)['~Metadata']['response']> >().toEqualTypeOf<{ 401: 'Unauthorized' 402: 'Payment Required' 403: 'Forbidden' 404: 'lilith' 405: 'Method Not Allowed' 406: 'Not Acceptable' 407: 'Proxy Authentication Required' 408: 'Request Timeout' }>() type A = keyof (typeof app)['~Routes']['get']['response'] expectTypeOf<(typeof app)['~Routes']['get']['response']>().toEqualTypeOf<{ 200: 'NOexistenceN' 401: 'Unauthorized' 402: 'Payment Required' 403: 'Forbidden' 404: 'lilith' 405: 'Method Not Allowed' 406: 'Not Acceptable' 407: 'Proxy Authentication Required' 408: 'Request Timeout' }>() } // Multiple macro { const app = new Elysia() .macro({ q: { beforeHandle: [ ({ status }) => { if (Math.random() > 0.05) return status(401) }, ({ status }) => { if (Math.random() > 0.05) return status(402) } ], afterHandle({ status }) { if (Math.random() > 0.05) return status(403) }, error({ status }) { if (Math.random() > 0.05) return status(404, 'lilith') } }, a: { beforeHandle: ({ status }) => { if (Math.random() > 0.05) return status(405) }, afterHandle: [ ({ status }) => { if (Math.random() > 0.05) return status(406) }, ({ status }) => { if (Math.random() > 0.05) return status(407) } ], error({ status }) { if (Math.random() > 0.05) return status(408) } } }) .guard({ q: true, a: true }) .get('/', () => 'NOexistenceN' as const) expectTypeOf< Prettify<(typeof app)['~Volatile']['response']> >().toEqualTypeOf<{ 401: 'Unauthorized' 402: 'Payment Required' 403: 'Forbidden' 404: 'lilith' 405: 'Method Not Allowed' 406: 'Not Acceptable' 407: 'Proxy Authentication Required' 408: 'Request Timeout' }>() type A = keyof (typeof app)['~Routes']['get']['response'] expectTypeOf<(typeof app)['~Routes']['get']['response']>().toEqualTypeOf<{ 200: 'NOexistenceN' 401: 'Unauthorized' 402: 'Payment Required' 403: 'Forbidden' 404: 'lilith' 405: 'Method Not Allowed' 406: 'Not Acceptable' 407: 'Proxy Authentication Required' 408: 'Request Timeout' }>() } // merge possible path { const app = new Elysia() .onBeforeHandle(({ status }) => { if (Math.random() > 0.05) return 'fouco' as const if (Math.random() > 0.05) return 'sartre' as const if (Math.random() > 0.05) return status(404, 'lilith') }) .get('/', () => 'lilith' as const) expectTypeOf<(typeof app)['~Volatile']['response']>().toEqualTypeOf<{ 200: 'fouco' | 'sartre' 404: 'lilith' }> expectTypeOf<(typeof app)['~Routes']['get']['response']>().toEqualTypeOf<{ 200: 'fouco' | 'sartre' | 'lilith' 404: 'lilith' }>() } // Macro Context should add to output declaration { const app = new Elysia() .macro({ a: { query: t.Object({ name: t.Literal('lilith') }), cookie: t.Object({ name: t.Literal('lilith') }), params: t.Object({ name: t.Literal('lilith') }), body: t.Object({ name: t.Literal('lilith') }), headers: t.Object({ name: t.Literal('lilith') }), response: { 403: t.Object({ name: t.Literal('lilith') }) } } }) .post('/', ({ body }) => 'b' as const, { a: true, beforeHandle({ body }) { expectTypeOf(body).toEqualTypeOf<{ name: 'lilith' }>() } }) expectTypeOf<(typeof app)['~Routes']['post']>().toEqualTypeOf<{ body: { name: 'lilith' } params: { name: 'lilith' } query: { name: 'lilith' } headers: { name: 'lilith' } response: { 200: 'b' 403: { name: 'lilith' } 422: { type: 'validation' on: string summary?: string message?: string found?: unknown property?: string expected?: string } } }>() } // Macro Context schema and inline schema works together even in inline lifecycle { const app = new Elysia() .macro({ withFriends: { body: t.Object({ friends: t.Tuple([t.Literal('Sartre'), t.Literal('Fouco')]) }) } }) .post( '/', ({ body }) => { expectTypeOf(body).toEqualTypeOf<{ name: 'Lilith' friends: ['Sartre', 'Fouco'] }>() return body }, { body: t.Object({ name: t.Literal('Lilith') }), withFriends: true, response: { 418: t.Literal('Teapot') }, beforeHandle({ body }) { expectTypeOf(body).toEqualTypeOf<{ name: 'Lilith' friends: ['Sartre', 'Fouco'] }>() } } ) expectTypeOf<(typeof app)['~Routes']['post']>().toEqualTypeOf<{ body: { name: 'Lilith' friends: ['Sartre', 'Fouco'] } params: {} query: {} headers: {} response: { 200: { name: 'Lilith' friends: ['Sartre', 'Fouco'] } 418: 'Teapot' 422: { type: 'validation' on: string summary?: string message?: string found?: unknown property?: string expected?: string } } }>() } // resolve for lifecycle event { new Elysia() .macro('auth', { headers: t.Object({ authorization: t.String() }), resolve: ({ status }) => Math.random() > 0.5 ? { role: 'user' } : status(401, 'not authorized') }) .post('/', ({ role }) => role, { auth: true, beforeHandle: ({ role }) => {} }) } // handle macro with arguments { new Elysia() .macro({ role: (role: 'user' | 'admin') => ({ resolve({ status, headers: { authorization } }) { const user = { role: Math.random() > 0.5 ? 'user' : 'admin' } as { role: 'user' | 'admin' } if (user.role !== role) return status(401) return { user } } }) }) .get( '/token', ({ user }) => { expectTypeOf(user).toEqualTypeOf<{ role: 'admin' | 'user' }>() }, { role: 'admin' } ) } // Get schema in GET { new Elysia() .guard({ schema: 'standalone', body: t.Object({ id: t.Number() }) }) .get('/user/:id', ({ body }) => body, { body: t.Object({ name: t.Literal('lilith') }) }) } // Merge multiple guard schema { const app = new Elysia().guard( { query: t.Object({ name: t.Literal('lilith') }), beforeHandle({ status }) { if (Math.random() > 0.5) return status(401) } }, (app) => app.guard( { query: t.Object({ limit: t.Number() }), beforeHandle({ status }) { if (Math.random() > 0.5) return status(400) } }, (app) => app.get( '/', ({ query }) => { expectTypeOf(query).toEqualTypeOf<{ playing: boolean name: 'lilith' limit: number }>() return query }, { query: t.Object({ playing: t.Boolean() }) } ) ) ) } // Merge multiple group schema { const app = new Elysia().guard( { query: t.Object({ name: t.Literal('lilith') }), beforeHandle({ status }) { if (Math.random() > 0.5) return status(401) } }, (app) => app.guard( { query: t.Object({ limit: t.Number() }), beforeHandle({ status }) { if (Math.random() > 0.5) return status(400) } }, (app) => app.get( '/', ({ query }) => { expectTypeOf(query).toEqualTypeOf<{ playing: boolean name: 'lilith' limit: number }>() return query }, { query: t.Object({ playing: t.Boolean() }) } ) ) ) expectTypeOf<(typeof app)['~Routes']['get']['response']>().toEqualTypeOf<{ 200: { playing: boolean name: 'lilith' limit: number } 400: 'Bad Request' 401: 'Unauthorized' 422: { type: 'validation' on: string summary?: string message?: string found?: unknown property?: string expected?: string } }>() } { const app = new Elysia().guard( { query: t.Object({ name: t.Literal('lilith') }), beforeHandle({ status }) { if (Math.random() > 0.5) return status(401) } }, (app) => app.guard( { query: t.Object({ limit: t.Number() }), beforeHandle({ status }) { if (Math.random() > 0.5) return status(400) } }, (app) => app.get( '/', ({ query }) => { expectTypeOf(query).toEqualTypeOf<{ playing: boolean name: 'lilith' limit: number }>() return query }, { query: t.Object({ playing: t.Boolean() }) } ) ) ) expectTypeOf<(typeof app)['~Routes']['get']['response']>().toEqualTypeOf<{ 200: { playing: boolean name: 'lilith' limit: number } 400: 'Bad Request' 401: 'Unauthorized' 422: { type: 'validation' on: string summary?: string message?: string found?: unknown property?: string expected?: string } }>() } // Inherit macro context { new Elysia() .macro('guestOrUser', { resolve: () => { return { user: 'Lilith' as const } } }) .macro('user', { guestOrUser: true, body: t.String(), resolve: ({ user }) => { expectTypeOf(user).toEqualTypeOf<'Lilith'>() } }) } // Handle 200 status for inline status { new Elysia().get( '/test', ({ status }) => { if (Math.random() > 0.1) return status(200, { key: 1, id: 1 }) if (Math.random() > 0.1) return status(200, { // @ts-expect-error key: 'a', id: 1 }) return status(200, { key2: 's', id: 2 }) }, { response: { 200: t.Union([ t.Object({ key2: t.String(), id: t.Literal(2) }), t.Object({ key: t.Number(), id: t.Literal(1) }) ]) } } ) } // coerce union status value and return type { new Elysia().get( '/test', ({ status }) => { return status(200, { key2: 's', id: 2 }) }, { response: { 200: t.Union([ t.Object({ key2: t.String(), id: t.Literal(2) }), t.Object({ key: t.Number(), id: t.Literal(1) }) ]) } } ) } // Macro should inherit schema type { new Elysia({ name: 'my-middleware-1' }) .guard({ as: 'scoped', headers: t.Object({ role: t.UnionEnum(['admin', 'user']) }), body: t.Object({ foo: t.String() }) }) .macro({ auth: { resolve: ({ headers, body }) => { expectTypeOf(headers).toEqualTypeOf<{ role: 'admin' | 'user' }>() expectTypeOf(body).toEqualTypeOf<{ foo: string }>() } } }) } // intersect multiple resolve macro response { const app = new Elysia() .macro({ multiple: { resolve({ status }) { if (Math.random() > 0.5) return status(401) return status(403) } } }) .get('/multiple', () => 'Ok', { multiple: true }) expectTypeOf< (typeof app)['~Routes']['multiple']['get']['response'] >().toEqualTypeOf<{ 200: string 401: 'Unauthorized' 403: 'Forbidden' }>() } // intersect multiple resolve macro response { const app = new Elysia() .macro('multiple', { resolve({ status }) { if (Math.random() > 0.5) return status(401) return status(403) } }) .get('/multiple', () => 'Ok', { multiple: true }) expectTypeOf< (typeof app)['~Routes']['multiple']['get']['response'] >().toEqualTypeOf<{ 200: string 401: 'Unauthorized' 403: 'Forbidden' }>() } // intersect multiple resolve macro response { const app = new Elysia() .macro('multiple', { resolve({ status }) { if (Math.random() > 0.5) return status(401) return status(403) } }) .get('/multiple', () => 'Ok', { multiple: true }) expectTypeOf< (typeof app)['~Routes']['multiple']['get']['response'] >().toEqualTypeOf<{ 200: string 401: 'Unauthorized' 403: 'Forbidden' }>() expectTypeOf< (typeof app)['~Routes']['multiple']['get']['response'] >().toEqualTypeOf<{ 200: string 401: 'Unauthorized' 403: 'Forbidden' }>() } // Macro with conflict value per status { const app = new Elysia() .macro({ a: { beforeHandle({ status }) { if (Math.random()) return status(400, 'a') if (Math.random()) return status(401, 'a') return 'a' } } }) .get('/', () => 'ok', { a: true }) type Route = (typeof app)['~Routes']['get']['response'] expectTypeOf().toEqualTypeOf<{ 200: string 400: 'a' 401: 'a' }> } // Recursive macro with conflict value per status { const app = new Elysia() .macro({ a: { beforeHandle({ status }) { if (Math.random()) return status(400, 'a') if (Math.random()) return status(401, 'a') return 'before handler' as const } }, b: { beforeHandle({ status }) { if (Math.random()) return status(401, 'b') }, a: true } }) .get('/', () => 'handler' as const, { b: true }) type Routes = (typeof app)['~Routes']['get']['response'] expectTypeOf().toEqualTypeOf<{ 200: 'before handler' | 'handler' 400: 'a' 401: 'a' | 'b' }> } // Recursive macro with conflict value per status { const app = new Elysia() .macro({ a: { resolve({ status }) { if (Math.random()) return status(400, 'a') if (Math.random()) return status(401, 'b') if (Math.random()) return status(401, 'c') return { a: 'a' } } }, b: { resolve({ status }) { if (Math.random()) return status(400, 'x') if (Math.random()) return status(401, 'y') if (Math.random()) return status(401, 'z') return { b: 'b' } }, a: true } }) .get('/', () => 'handler' as const, { b: true }) type Routes = (typeof app)['~Routes']['get']['response'] expectTypeOf().toEqualTypeOf<{ 200: 'handler' 400: 'a' | 'x' 401: 'b' | 'c' | 'y' | 'z' }> } // Separate Box from literal status-like response in macro { const app = new Elysia() .macro({ a: { beforeHandle({ status }) { if (Math.random()) return status(400, 'a') if (Math.random()) return status(401, 'a') if (Math.random()) return status(401, 'b') if (Math.random()) return status(402) if (Math.random()) // Test status-like response but literal not box return { status: 401, response: 'c' } as const return 'a' } } }) .get('/', () => 'ok', { a: true }) type Route = (typeof app)['~Routes']['get']['response'] expectTypeOf().toEqualTypeOf<{ 200: string | { readonly status: 401; readonly response: 'c' } 400: 'a' 401: 'a' | 'b' 402: 'Payment Required' }> } ================================================ FILE: test/types/macro.ts ================================================ import { Elysia, t } from '../../src' import { expectTypeOf } from 'expect-type' // guard handle resolve macro { const plugin = new Elysia() .macro({ account: (a: boolean) => ({ resolve: () => ({ account: 'A' }) }) }) .guard({ account: true }) .get('/', ({ account }) => { expectTypeOf(account).toEqualTypeOf() }) const parent = new Elysia().use(plugin).get('/', (context) => { expectTypeOf(context).not.toHaveProperty('account') }) const app = new Elysia().use(parent).get('/', (context) => { expectTypeOf(context).not.toHaveProperty('account') }) } // guard handle resolve macro with scoped { const plugin = new Elysia() .macro({ account: (a: boolean) => ({ resolve: () => ({ account: 'A' }) }) }) .guard({ as: 'scoped', account: true }) .get('/', ({ account }) => { expectTypeOf(account).toEqualTypeOf() }) const parent = new Elysia().use(plugin).get('/', (context) => { expectTypeOf(context).toHaveProperty('account') expectTypeOf(context.account).toEqualTypeOf() }) const app = new Elysia().use(parent).get('/', (context) => { expectTypeOf(context).not.toHaveProperty('account') }) } // guard handle resolve macro with global { const plugin = new Elysia() .macro({ account: (a: boolean) => ({ resolve: () => ({ account: 'A' }) }) }) .guard({ as: 'global', account: true }) .get('/', ({ account }) => { expectTypeOf(account).toEqualTypeOf() }) const parent = new Elysia().use(plugin).get('/', (context) => { expectTypeOf(context).toHaveProperty('account') expectTypeOf(context.account).toEqualTypeOf() }) const app = new Elysia().use(parent).get('/', (context) => { expectTypeOf(context).toHaveProperty('account') expectTypeOf(context.account).toEqualTypeOf() }) } // guard handle resolve macro with local { const plugin = new Elysia() .macro({ account: (a: boolean) => ({ resolve: () => ({ account: 'A' }) }) }) .guard({ as: 'local', account: true }) .get('/', ({ account }) => { expectTypeOf(account).toEqualTypeOf() }) const parent = new Elysia().use(plugin).get('/', (context) => { expectTypeOf(context).not.toHaveProperty('account') }) const app = new Elysia().use(parent).get('/', (context) => { expectTypeOf(context).not.toHaveProperty('account') }) } // guard handle resolve macro with error { const plugin = new Elysia() .macro({ account: (a: boolean) => ({ resolve: ({ status }) => { if (Math.random() > 0.5) return status(401) return { account: 'A' } } }) }) .guard({ account: true }) .get('/', ({ account }) => { expectTypeOf(account).toEqualTypeOf() }) const parent = new Elysia().use(plugin).get('/', (context) => { expectTypeOf(context).not.toHaveProperty('account') }) const app = new Elysia().use(parent).get('/', (context) => { expectTypeOf(context).not.toHaveProperty('account') }) } // guard handle resolve macro with async { const plugin = new Elysia() .macro({ account: (a: boolean) => ({ resolve: async ({ status }) => { if (Math.random() > 0.5) return status(401) return { account: 'A' } } }) }) .guard({ as: 'scoped', account: true }) .get('/', ({ account }) => { expectTypeOf(account).toEqualTypeOf() }) const parent = new Elysia().use(plugin).get('/', (context) => { expectTypeOf(context).toHaveProperty('account') expectTypeOf(context.account).toEqualTypeOf() }) const app = new Elysia().use(parent).get('/', (context) => { expectTypeOf(context).not.toHaveProperty('account') }) } // Handle ephemeral and volatile property { const app = new Elysia() .resolve(() => { return { hello: 'world' } }) .macro({ user: (enabled: boolean) => ({ resolve: ({ hello, query: { name = 'anon' } }) => { expectTypeOf(hello).toEqualTypeOf<'world' | undefined>() return { user: { name } } } }) }) .get('/', ({ user }) => user, { user: true }) } // Handle shorthand function macro { const app = new Elysia() .macro({ user: { resolve: ({ query: { name = 'anon' } }) => ({ user: { name } }) } }) .get( '/', ({ user }) => { expectTypeOf(user).toEqualTypeOf<{ name: string }>() }, { user: true } ) .get( '/no', (context) => { expectTypeOf(context).not.toHaveProperty('user') }, { user: false } ) } // resolve with custom status { const app = new Elysia() .macro({ auth: { resolve: [ ({ status }) => { if (Math.random() > 0.5) return status(401) return { user: 'saltyaom' } as const } ] } }) .get('/', ({ user }) => user, { auth: true }) } // retrieve resolve conditionally const app = new Elysia() .macro({ user: (enabled: true) => ({ resolve() { if (!enabled) return return { user: 'a' } } }) }) .get( '/', ({ user, status }) => { if (!user) return status(401) return { hello: 'hanabi' } }, { user: true } ) // Macro name extends macro { new Elysia() .macro('a', { body: t.Object({ a: t.Literal('A') }), beforeHandle({ body }) { expectTypeOf(body).toEqualTypeOf<{ a: 'A' }>() } }) .macro('b', { a: true, body: t.Object({ b: t.Literal('B') }), beforeHandle({ body }) { expectTypeOf(body).toEqualTypeOf<{ a: 'A' b: 'B' }>() } }) } // handle function { new Elysia() .macro('a', (a: 'a') => ({ resolve: () => ({ a: 'a' as const }) })) .get( '/', ({ a }) => { expectTypeOf(a).toEqualTypeOf<'a'>() return a }, { a: 'a' } ) .get('/', 'ok', { // @ts-expect-error a: 'b' }) .listen(3000) } ================================================ FILE: test/types/plugins.ts ================================================ import { Elysia, t } from '../../src' const plugin = async (app: Elysia) => app.decorate('decorate', 'a').state('state', 'a').model({ string: t.String() }) export default plugin ================================================ FILE: test/types/schema-standalone.ts ================================================ import { Cookie, Elysia, t } from '../../src' import { expectTypeOf } from 'expect-type' // local { // Handle standalone { new Elysia() .guard({ schema: 'standalone', body: t.Object({ id: t.Number() }), response: t.Object({ success: t.Boolean() }) }) // @ts-expect-error .post('/', ({ body }) => { expectTypeOf().toEqualTypeOf<{ id: number }>() return { success: 'a' } }) } // Handle with local schema standalone { new Elysia() .guard({ schema: 'standalone', body: t.Object({ id: t.Number() }), response: t.Object({ success: t.Boolean() }) }) .post( '/', ({ body }) => { expectTypeOf().toEqualTypeOf<{ id: number name: string }>() return { success: true, ...body } }, { body: t.Object({ name: t.String() }), response: t.Object({ name: t.String() }) } ) } // Handle with multiple standalone with local schema { new Elysia() .guard({ schema: 'standalone', body: t.Object({ id: t.Number() }), response: t.Object({ success: t.Boolean() }) }) .guard({ schema: 'standalone', body: t.Object({ separated: t.Literal(true) }) }) .post( '/', ({ body }) => { expectTypeOf().toEqualTypeOf<{ id: number separated: true name: string }>() return { success: true, ...body } }, { body: t.Object({ name: t.String() }), response: t.Object({ name: t.String() }) } ) } // Handle standalone after override guard { new Elysia() .guard({ schema: 'standalone', body: t.Object({ id: t.Number() }), response: t.Object({ success: t.Boolean() }) }) .guard({ body: t.Object({ separated: t.Literal(true) }) }) .post( '/', ({ body }) => { expectTypeOf().toEqualTypeOf<{ id: number name: 'saltyaom' }>() return { success: true, ...body } }, { body: t.Object({ name: t.Literal('saltyaom') }), response: t.Object({ id: t.Number() }) } ) } // Handle standalone { new Elysia() .guard({ schema: 'standalone', body: t.Object({ id: t.Number() }), response: t.Object({ success: t.Boolean() }) }) // @ts-expect-error .post('/', ({ body }) => { expectTypeOf().toEqualTypeOf<{ id: number }>() return { success: 'a' } }) } // Handle with local schema standalone { new Elysia() .guard({ schema: 'standalone', body: t.Object({ id: t.Number() }), response: t.Object({ success: t.Boolean() }) }) .post( '/', ({ body }) => { expectTypeOf().toEqualTypeOf<{ id: number name: string }>() return { success: true, ...body } }, { body: t.Object({ name: t.String() }), response: t.Object({ name: t.String() }) } ) } // Handle with multiple standalone with local schema { new Elysia() .guard({ schema: 'standalone', body: t.Object({ id: t.Number() }), response: t.Object({ success: t.Boolean() }) }) .guard({ schema: 'standalone', body: t.Object({ separated: t.Literal(true) }) }) .post( '/', ({ body }) => { expectTypeOf().toEqualTypeOf<{ id: number separated: true name: string }>() return { success: true, ...body } }, { body: t.Object({ name: t.String() }), response: t.Object({ name: t.String() }) } ) } // Handle standalone after override guard { const local = new Elysia() .guard({ schema: 'standalone', body: t.Object({ id: t.Number() }), response: t.Object({ success: t.Boolean() }) }) .guard({ schema: 'standalone', body: t.Object({ separated: t.Literal(true) }) }) .post( '/', ({ body }) => { expectTypeOf().toEqualTypeOf<{ name: 'saltyaom' id: number separated: true }>() return { success: true, ...body } }, { body: t.Object({ name: t.Literal('saltyaom') }), response: t.Object({ id: t.Number() }) } ) const main = new Elysia().use(local).post( '/', ({ body }) => { expectTypeOf().toEqualTypeOf<{ name: 'saltyaom' }>() return { id: 1, name: body.name } }, { body: t.Object({ name: t.Literal('saltyaom') }), response: t.Object({ id: t.Number() }) } ) } } // scoped { // Handle standalone { new Elysia() .guard({ as: 'scoped', schema: 'standalone', body: t.Object({ id: t.Number() }), response: t.Object({ success: t.Boolean() }) }) // @ts-expect-error .post('/', ({ body }) => { expectTypeOf().toEqualTypeOf<{ id: number }>() return { success: 'a' } }) } // Handle with local schema standalone { new Elysia() .guard({ as: 'scoped', schema: 'standalone', body: t.Object({ id: t.Number() }), response: t.Object({ success: t.Boolean() }) }) .post( '/', ({ body }) => { expectTypeOf().toEqualTypeOf<{ id: number name: string }>() return { success: true, ...body } }, { body: t.Object({ name: t.String() }), response: t.Object({ name: t.String() }) } ) } // Handle with multiple standalone with local schema { new Elysia() .guard({ as: 'scoped', schema: 'standalone', body: t.Object({ id: t.Number() }), response: t.Object({ success: t.Boolean() }) }) .guard({ as: 'scoped', schema: 'standalone', body: t.Object({ separated: t.Literal(true) }) }) .post( '/', ({ body }) => { expectTypeOf().toEqualTypeOf<{ id: number separated: true name: string }>() return { success: true, ...body } }, { body: t.Object({ name: t.String() }), response: t.Object({ name: t.String() }) } ) } // Handle standalone after override guard { new Elysia() .guard({ as: 'scoped', schema: 'standalone', body: t.Object({ id: t.Number() }), response: t.Object({ success: t.Boolean() }) }) .guard({ as: 'scoped', body: t.Object({ separated: t.Literal(true) }) }) .post( '/', ({ body }) => { expectTypeOf().toEqualTypeOf<{ id: number name: 'saltyaom' }>() return { success: true, ...body } }, { body: t.Object({ name: t.Literal('saltyaom') }), response: t.Object({ id: t.Number() }) } ) } // Handle standalone { new Elysia() .guard({ as: 'scoped', schema: 'standalone', body: t.Object({ id: t.Number() }), response: t.Object({ success: t.Boolean() }) }) // @ts-expect-error .post('/', ({ body }) => { expectTypeOf().toEqualTypeOf<{ id: number }>() return { success: 'a' } }) } // Handle with local schema standalone { new Elysia() .guard({ as: 'scoped', schema: 'standalone', body: t.Object({ id: t.Number() }), response: t.Object({ success: t.Boolean() }) }) .post( '/', ({ body }) => { expectTypeOf().toEqualTypeOf<{ id: number name: string }>() return { success: true, ...body } }, { body: t.Object({ name: t.String() }), response: t.Object({ name: t.String() }) } ) } // Handle with multiple standalone with local schema { new Elysia() .guard({ as: 'scoped', schema: 'standalone', body: t.Object({ id: t.Number() }), response: t.Object({ success: t.Boolean() }) }) .guard({ schema: 'standalone', body: t.Object({ separated: t.Literal(true) }) }) .post( '/', ({ body }) => { expectTypeOf().toEqualTypeOf<{ id: number separated: true name: string }>() return { success: true, ...body } }, { body: t.Object({ name: t.String() }), response: t.Object({ name: t.String() }) } ) } // Handle standalone after override guard { const local = new Elysia() .guard({ as: 'scoped', schema: 'standalone', body: t.Object({ id: t.Number() }), response: t.Object({ success: t.Boolean() }) }) .guard({ as: 'scoped', schema: 'standalone', body: t.Object({ separated: t.Literal(true) }) }) .post( '/', ({ body }) => { expectTypeOf().toEqualTypeOf<{ id: number separated: true name: 'saltyaom' }>() return { success: true, ...body } }, { body: t.Object({ name: t.Literal('saltyaom') }), response: t.Object({ id: t.Number() }) } ) const parent = new Elysia().use(local).post( '/', ({ body }) => { expectTypeOf().toEqualTypeOf<{ id: number separated: true name: 'saltyaom' }>() return { success: true, id: 1, name: body.name } }, { body: t.Object({ name: t.Literal('saltyaom') }), response: t.Object({ id: t.Number() }) } ) const main = new Elysia().use(parent).post( '/', ({ body }) => { expectTypeOf().toEqualTypeOf<{ name: 'saltyaom' }>() return { id: 1, name: body.name } }, { body: t.Object({ name: t.Literal('saltyaom') }), response: t.Object({ id: t.Number() }) } ) const propagated = new Elysia().use(parent.as('scoped')).post( '/', ({ body }) => { expectTypeOf().toEqualTypeOf<{ name: 'saltyaom' id: number separated: true }>() return { success: true, id: 1, name: body.name } }, { body: t.Object({ name: t.Literal('saltyaom') }), response: t.Object({ id: t.Number() }) } ) } } // global { // Handle standalone { new Elysia() .guard({ as: 'global', schema: 'standalone', body: t.Object({ id: t.Number() }), response: t.Object({ success: t.Boolean() }) }) // @ts-expect-error .post('/', ({ body }) => { expectTypeOf().toEqualTypeOf<{ id: number }>() return { success: 'a' } }) } // Handle with local schema standalone { new Elysia() .guard({ as: 'global', schema: 'standalone', body: t.Object({ id: t.Number() }), response: t.Object({ success: t.Boolean() }) }) .post( '/', ({ body }) => { expectTypeOf().toEqualTypeOf<{ id: number name: string }>() return { success: true, ...body } }, { body: t.Object({ name: t.String() }), response: t.Object({ name: t.String() }) } ) } // Handle with multiple standalone with local schema { new Elysia() .guard({ as: 'global', schema: 'standalone', body: t.Object({ id: t.Number() }), response: t.Object({ success: t.Boolean() }) }) .guard({ as: 'global', schema: 'standalone', body: t.Object({ separated: t.Literal(true) }) }) .post( '/', ({ body }) => { expectTypeOf().toEqualTypeOf<{ id: number separated: true name: string }>() return { success: true, ...body } }, { body: t.Object({ name: t.String() }), response: t.Object({ name: t.String() }) } ) } // Handle standalone after override guard { new Elysia() .guard({ as: 'global', schema: 'standalone', body: t.Object({ id: t.Number() }), response: t.Object({ success: t.Boolean() }) }) .guard({ as: 'global', body: t.Object({ separated: t.Literal(true) }) }) .post( '/', ({ body }) => { expectTypeOf().toEqualTypeOf<{ id: number name: 'saltyaom' }>() return { success: true, ...body } }, { body: t.Object({ name: t.Literal('saltyaom') }), response: t.Object({ id: t.Number() }) } ) } // Handle standalone { new Elysia() .guard({ as: 'global', schema: 'standalone', body: t.Object({ id: t.Number() }), response: t.Object({ success: t.Boolean() }) }) // @ts-expect-error .post('/', ({ body }) => { expectTypeOf().toEqualTypeOf<{ id: number }>() return { success: 'a' } }) } // Handle with local schema standalone { new Elysia() .guard({ as: 'global', schema: 'standalone', body: t.Object({ id: t.Number() }), response: t.Object({ success: t.Boolean() }) }) .post( '/', ({ body }) => { expectTypeOf().toEqualTypeOf<{ id: number name: string }>() return { success: true, ...body } }, { body: t.Object({ name: t.String() }), response: t.Object({ name: t.String() }) } ) } // Handle with multiple standalone with local schema { new Elysia() .guard({ as: 'global', schema: 'standalone', body: t.Object({ id: t.Number() }), response: t.Object({ success: t.Boolean() }) }) .guard({ as: 'scoped', schema: 'standalone', body: t.Object({ separated: t.Literal(true) }) }) .post( '/', ({ body }) => { expectTypeOf().toEqualTypeOf<{ id: number separated: true name: string }>() return { success: true, ...body } }, { body: t.Object({ name: t.String() }), response: t.Object({ name: t.String() }) } ) } // Handle standalone after override guard { const local = new Elysia() .guard({ as: 'global', schema: 'standalone', body: t.Object({ id: t.Number() }), response: t.Object({ success: t.Boolean() }) }) .guard({ as: 'global', schema: 'standalone', body: t.Object({ separated: t.Literal(true) }) }) .post( '/', ({ body }) => { expectTypeOf().toEqualTypeOf<{ id: number separated: true name: 'saltyaom' }>() return { success: true, ...body } }, { body: t.Object({ name: t.Literal('saltyaom') }), response: t.Object({ id: t.Number() }) } ) const parent = new Elysia().use(local).post( '/', ({ body }) => { expectTypeOf().toEqualTypeOf<{ id: number separated: true name: 'saltyaom' }>() return { success: true, id: 1, name: body.name } }, { body: t.Object({ name: t.Literal('saltyaom') }), response: t.Object({ id: t.Number() }) } ) const main = new Elysia().use(parent).post( '/', ({ body }) => { expectTypeOf().toEqualTypeOf<{ name: 'saltyaom' separated: true id: number }>() return { success: true, id: 1, name: body.name } }, { body: t.Object({ name: t.Literal('saltyaom') }), response: t.Object({ id: t.Number() }) } ) } } // handle every schema type on local { // ? handle local { const local = new Elysia() .guard({ schema: 'standalone', body: t.Object({ family: t.String() }), headers: t.Object({ family: t.String() }), query: t.Object({ family: t.String() }), params: t.Object({ family: t.String() }), response: t.Object({ family: t.String() }), cookie: t.Object({ family: t.String() }) }) .post( '/:family', ({ body, query, params, headers, cookie }) => { expectTypeOf().toEqualTypeOf<{ family: string name: string }>() expectTypeOf().toEqualTypeOf<{ family: string name: string }>() expectTypeOf().toEqualTypeOf<{ family: string name: string }>() expectTypeOf().toEqualTypeOf<{ family: string name: string }>() expectTypeOf().toEqualTypeOf< Record> & { family: Cookie name: Cookie } >() return body }, { body: t.Object({ name: t.String() }), headers: t.Object({ name: t.String() }), query: t.Object({ name: t.String() }), params: t.Object({ name: t.String() }), response: t.Object({ name: t.String() }), cookie: t.Object({ name: t.String() }) } ) const parent = new Elysia().use(local).post( '/family/:family/:name', ({ body, query, params, headers, cookie }) => { expectTypeOf().toEqualTypeOf<{ name: string }>() expectTypeOf().toEqualTypeOf<{ name: string }>() expectTypeOf().toEqualTypeOf<{ name: string }>() expectTypeOf().toEqualTypeOf<{ name: string }>() expectTypeOf().toEqualTypeOf< Record> & { name: Cookie } >() return body }, { body: t.Object({ name: t.String() }), headers: t.Object({ name: t.String() }), query: t.Object({ name: t.String() }), params: t.Object({ name: t.String() }), response: t.Object({ name: t.String() }), cookie: t.Object({ name: t.String() }) } ) const app = new Elysia().use(parent).post( '/:family/:name', ({ body, query, params, headers, cookie }) => { expectTypeOf().toEqualTypeOf<{ name: string }>() expectTypeOf().toEqualTypeOf<{ name: string }>() expectTypeOf().toEqualTypeOf<{ name: string }>() expectTypeOf().toEqualTypeOf<{ name: string }>() expectTypeOf().toEqualTypeOf< Record> & { name: Cookie } >() return body }, { body: t.Object({ name: t.String() }), headers: t.Object({ name: t.String() }), query: t.Object({ name: t.String() }), params: t.Object({ name: t.String() }), response: t.Object({ name: t.String() }), cookie: t.Object({ name: t.String() }) } ) } } // handle every schema type on scoped { // ? handle local { const local = new Elysia() .guard({ schema: 'standalone', as: 'scoped', body: t.Object({ family: t.String() }), headers: t.Object({ family: t.String() }), query: t.Object({ family: t.String() }), params: t.Object({ family: t.String() }), response: t.Object({ family: t.String() }), cookie: t.Object({ family: t.String() }) }) .post( '/:family', ({ body, query, params, headers, cookie }) => { expectTypeOf().toEqualTypeOf<{ family: string name: string }>() expectTypeOf().toEqualTypeOf<{ family: string name: string }>() expectTypeOf().toEqualTypeOf<{ family: string name: string }>() expectTypeOf().toEqualTypeOf<{ family: string name: string }>() expectTypeOf().toEqualTypeOf< Record> & { family: Cookie name: Cookie } >() return body }, { body: t.Object({ name: t.String() }), headers: t.Object({ name: t.String() }), query: t.Object({ name: t.String() }), params: t.Object({ name: t.String() }), response: t.Object({ name: t.String() }), cookie: t.Object({ name: t.String() }) } ) const parent = new Elysia().use(local).post( '/family/:family/:name', ({ body, query, params, headers, cookie }) => { expectTypeOf().toEqualTypeOf<{ family: string name: string }>() expectTypeOf().toEqualTypeOf<{ family: string name: string }>() expectTypeOf().toEqualTypeOf<{ family: string name: string }>() expectTypeOf().toEqualTypeOf<{ family: string name: string }>() expectTypeOf().toEqualTypeOf< Record> & { family: Cookie name: Cookie } >() return body }, { body: t.Object({ name: t.String() }), headers: t.Object({ name: t.String() }), query: t.Object({ name: t.String() }), params: t.Object({ name: t.String() }), response: t.Object({ name: t.String() }), cookie: t.Object({ name: t.String() }) } ) const app = new Elysia().use(parent).post( '/:family/:name', ({ body, query, params, headers, cookie }) => { expectTypeOf().toEqualTypeOf<{ name: string }>() expectTypeOf().toEqualTypeOf<{ name: string }>() expectTypeOf().toEqualTypeOf<{ name: string }>() expectTypeOf().toEqualTypeOf<{ name: string }>() expectTypeOf().toEqualTypeOf< Record> & { name: Cookie } >() return body }, { body: t.Object({ name: t.String() }), headers: t.Object({ name: t.String() }), query: t.Object({ name: t.String() }), params: t.Object({ name: t.String() }), response: t.Object({ name: t.String() }), cookie: t.Object({ name: t.String() }) } ) } } // handle every schema type on global { // ? handle local { const local = new Elysia() .guard({ schema: 'standalone', as: 'global', body: t.Object({ family: t.String() }), headers: t.Object({ family: t.String() }), query: t.Object({ family: t.String() }), params: t.Object({ family: t.String() }), response: t.Object({ family: t.String() }), cookie: t.Object({ family: t.String() }) }) .post( '/:family', ({ body, query, params, headers, cookie }) => { expectTypeOf().toEqualTypeOf<{ family: string name: string }>() expectTypeOf().toEqualTypeOf<{ family: string name: string }>() expectTypeOf().toEqualTypeOf<{ family: string name: string }>() expectTypeOf().toEqualTypeOf<{ family: string name: string }>() expectTypeOf().toEqualTypeOf< Record> & { family: Cookie name: Cookie } >() return body }, { body: t.Object({ name: t.String() }), headers: t.Object({ name: t.String() }), query: t.Object({ name: t.String() }), params: t.Object({ name: t.String() }), response: t.Object({ name: t.String() }), cookie: t.Object({ name: t.String() }) } ) const parent = new Elysia().use(local).post( '/family/:family/:name', ({ body, query, params, headers, cookie }) => { expectTypeOf().toEqualTypeOf<{ family: string name: string }>() expectTypeOf().toEqualTypeOf<{ family: string name: string }>() expectTypeOf().toEqualTypeOf<{ family: string name: string }>() expectTypeOf().toEqualTypeOf<{ family: string name: string }>() expectTypeOf().toEqualTypeOf< Record> & { family: Cookie name: Cookie } >() return body }, { body: t.Object({ name: t.String() }), headers: t.Object({ name: t.String() }), query: t.Object({ name: t.String() }), params: t.Object({ name: t.String() }), response: t.Object({ name: t.String() }), cookie: t.Object({ name: t.String() }) } ) const app = new Elysia().use(parent).post( '/:family/:name', ({ body, query, params, headers, cookie }) => { expectTypeOf().toEqualTypeOf<{ family: string name: string }>() expectTypeOf().toEqualTypeOf<{ family: string name: string }>() expectTypeOf().toEqualTypeOf<{ family: string name: string }>() expectTypeOf().toEqualTypeOf<{ family: string name: string }>() expectTypeOf().toEqualTypeOf< Record> & { family: Cookie name: Cookie } >() return body }, { body: t.Object({ name: t.String() }), headers: t.Object({ name: t.String() }), query: t.Object({ name: t.String() }), params: t.Object({ name: t.String() }), response: t.Object({ name: t.String() }), cookie: t.Object({ name: t.String() }) } ) } } ================================================ FILE: test/types/standard-schema/index.ts ================================================ /* eslint-disable @typescript-eslint/no-unused-vars */ import { Cookie, Elysia, t } from '../../../src' import z from 'zod' import { expectTypeOf } from 'expect-type' // ? handle standard schema { new Elysia().post( '/:name', ({ params, params: { name }, body, query, headers, cookie, status }) => { expectTypeOf().toEqualTypeOf<{ name: 'fouco' | 'lilith' }>() expectTypeOf().toEqualTypeOf<{ name: 'fouco' | 'lilith' }>() expectTypeOf().toEqualTypeOf<{ name: 'fouco' | 'lilith' }>() expectTypeOf().toEqualTypeOf<{ name: 'fouco' | 'lilith' }>() expectTypeOf().toEqualTypeOf< Record> & { name: Cookie<'fouco' | 'lilith'> } >() // @ts-expect-error status(404, 'fouco') // @ts-expect-error status(418, 'lilith') return name === 'lilith' ? status(404, 'lilith') : status(418, name as any) }, { body: z.object({ name: z.literal('fouco').or(z.literal('lilith')) }), query: z.object({ name: z.literal('fouco').or(z.literal('lilith')) }), params: z.object({ name: z.literal('fouco').or(z.literal('lilith')) }), headers: z.object({ name: z.literal('fouco').or(z.literal('lilith')) }), cookie: z.object({ name: z.literal('fouco').or(z.literal('lilith')) }), response: { 404: z.literal('lilith'), 418: z.literal('fouco') } } ) } // ? handle standard schema single response { new Elysia() .get('/lilith', () => 'lilith' as const, { response: z.literal('lilith') }) .get('/lilith', 'lilith', { response: z.literal('lilith') }) // @ts-expect-error .get('/lilith', () => 'a' as const, { response: z.literal('lilith') }) } // ? handle standard schema from reference { new Elysia() .model({ body: z.object({ name: z.literal('fouco').or(z.literal('lilith')) }), query: z.object({ name: z.literal('fouco').or(z.literal('lilith')) }), params: z.object({ name: z.literal('fouco').or(z.literal('lilith')) }), headers: z.object({ name: z.literal('fouco').or(z.literal('lilith')) }), cookie: z.object({ name: z.literal('fouco').or(z.literal('lilith')) }), 'response.404': z.literal('lilith'), 'response.418': z.literal('fouco') }) .post( '/:name', ({ params, params: { name }, body, query, headers, cookie, status }) => { expectTypeOf().toEqualTypeOf<{ name: 'fouco' | 'lilith' }>() expectTypeOf().toEqualTypeOf<{ name: 'fouco' | 'lilith' }>() expectTypeOf().toEqualTypeOf<{ name: 'fouco' | 'lilith' }>() expectTypeOf().toEqualTypeOf<{ name: 'fouco' | 'lilith' }>() expectTypeOf().toEqualTypeOf< Record> & { name: Cookie<'fouco' | 'lilith'> } >() // @ts-expect-error status(404, 'fouco') // @ts-expect-error status(418, 'lilith') return name === 'lilith' ? status(404, 'lilith') : status(418, name as any) }, { body: 'body', query: 'query', params: 'params', headers: 'headers', cookie: 'cookie', response: { 404: 'response.404', 418: 'response.418' } } ) } // ? handle standard schema from guard { new Elysia() .guard({ body: z.object({ name: z.literal('fouco').or(z.literal('lilith')) }), query: z.object({ name: z.literal('fouco').or(z.literal('lilith')) }), params: z.object({ name: z.literal('fouco').or(z.literal('lilith')) }), headers: z.object({ name: z.literal('fouco').or(z.literal('lilith')) }), cookie: z.object({ name: z.literal('fouco').or(z.literal('lilith')) }), response: { 404: z.literal('lilith'), 418: z.literal('fouco') } }) .post( '/:name', ({ params, params: { name }, body, query, headers, cookie, status }) => { expectTypeOf().toEqualTypeOf<{ name: 'fouco' | 'lilith' }>() expectTypeOf().toEqualTypeOf<{ name: 'fouco' | 'lilith' }>() expectTypeOf().toEqualTypeOf<{ name: 'fouco' | 'lilith' }>() expectTypeOf().toEqualTypeOf<{ name: 'fouco' | 'lilith' }>() expectTypeOf().toEqualTypeOf< Record> & { name: Cookie<'fouco' | 'lilith'> } >() // @ts-expect-error status(404, 'fouco') // @ts-expect-error status(418, 'lilith') return name === 'lilith' ? status(404, 'lilith') : status(418, name as any) } ) } // ? merge standard schema response status from guard { new Elysia() .guard({ response: { 418: z.literal('fouco') } }) .get('/lilith', () => 'lilith' as const, { response: z.literal('lilith') }) .get('/lilith', 'lilith', { response: z.literal('lilith') }) // @ts-expect-error .get('/lilith', () => 'focou' as const, { response: z.literal('lilith') }) .get('/fouco', ({ status }) => status(418, 'fouco'), { response: z.literal('lilith') }) // @ts-expect-error .get('/fouco', ({ status }) => status(418, 'lilith'), { response: z.literal('lilith') }) } // ? merge standalone standard schema { new Elysia() .guard({ schema: 'standalone', body: z.object({ name: z.literal('fouco').or(z.literal('lilith')) }), query: z.object({ name: z.literal('fouco').or(z.literal('lilith')) }), params: z.object({ name: z.literal('fouco').or(z.literal('lilith')) }), headers: z.object({ name: z.literal('fouco').or(z.literal('lilith')) }), cookie: z.object({ name: z.literal('fouco').or(z.literal('lilith')) }), response: { 404: z.object({ name: z.literal('lilith') }), 418: z.object({ name: z.literal('fouco') }) } }) .post( '/:name', ({ params, params: { name }, body, query, headers, cookie, status }) => { expectTypeOf().toEqualTypeOf<{ name: 'fouco' | 'lilith' q: 'fouco' | 'lilith' }>() expectTypeOf().toEqualTypeOf<{ name: 'fouco' | 'lilith' q: 'fouco' | 'lilith' }>() expectTypeOf().toEqualTypeOf<{ name: 'fouco' | 'lilith' q: 'fouco' | 'lilith' }>() expectTypeOf().toEqualTypeOf<{ name: 'fouco' | 'lilith' q: 'fouco' | 'lilith' }>() expectTypeOf().toEqualTypeOf< Record> & { name: Cookie<'fouco' | 'lilith'> q: Cookie<'fouco' | 'lilith'> } >() status(404, { // @ts-expect-error name: 'fouco', // @ts-expect-error q: 'fouco' }) status(418, { // @ts-expect-error name: 'lilith', // @ts-expect-error q: 'lilith' }) return name === 'lilith' ? status(404, { name, q: 'lilith' }) : status(418, { name, q: 'fouco' }) }, { body: t.Object({ q: t.UnionEnum(['lilith', 'fouco']) }), query: t.Object({ q: t.UnionEnum(['lilith', 'fouco']) }), params: t.Object({ q: t.UnionEnum(['lilith', 'fouco']) }), headers: t.Object({ q: t.UnionEnum(['lilith', 'fouco']) }), cookie: t.Object({ q: t.UnionEnum(['lilith', 'fouco']) }), response: { 404: t.Object({ q: t.Literal('lilith') }), 418: t.Object({ q: t.Literal('fouco') }) } } ) } // ? merge standalone standard schema from plugin { const plugin = new Elysia().guard({ as: 'scoped', schema: 'standalone', body: z.object({ name: z.literal('fouco').or(z.literal('lilith')) }), query: z.object({ name: z.literal('fouco').or(z.literal('lilith')) }), params: z.object({ name: z.literal('fouco').or(z.literal('lilith')) }), headers: z.object({ name: z.literal('fouco').or(z.literal('lilith')) }), cookie: z.object({ name: z.literal('fouco').or(z.literal('lilith')) }), response: { 404: z.object({ name: z.literal('lilith') }), 418: z.object({ name: z.literal('fouco') }) } }) new Elysia().use(plugin).post( '/:name', ({ params, params: { name }, body, query, headers, cookie, status }) => { expectTypeOf().toEqualTypeOf<{ name: 'fouco' | 'lilith' q: 'fouco' | 'lilith' }>() expectTypeOf().toEqualTypeOf<{ name: 'fouco' | 'lilith' q: 'fouco' | 'lilith' }>() expectTypeOf().toEqualTypeOf<{ name: 'fouco' | 'lilith' q: 'fouco' | 'lilith' }>() expectTypeOf().toEqualTypeOf<{ name: 'fouco' | 'lilith' q: 'fouco' | 'lilith' }>() expectTypeOf().toEqualTypeOf< Record> & { q: Cookie<'fouco' | 'lilith'> name: Cookie<'fouco' | 'lilith'> } >() status(404, { // @ts-expect-error name: 'fouco', // @ts-expect-error q: 'fouco' }) status(418, { // @ts-expect-error name: 'lilith', // @ts-expect-error q: 'lilith' }) return name === 'lilith' ? status(404, { name, q: 'lilith' }) : status(418, { name, q: 'fouco' }) }, { body: t.Object({ q: t.UnionEnum(['lilith', 'fouco']) }), query: t.Object({ q: t.UnionEnum(['lilith', 'fouco']) }), params: t.Object({ q: t.UnionEnum(['lilith', 'fouco']) }), headers: t.Object({ q: t.UnionEnum(['lilith', 'fouco']) }), cookie: t.Object({ q: t.UnionEnum(['lilith', 'fouco']) }), response: { 404: t.Object({ q: t.Literal('lilith') }), 418: t.Object({ q: t.Literal('fouco') }) } } ) } ================================================ FILE: test/types/type-system.ts ================================================ /* eslint-disable @typescript-eslint/no-unused-vars */ import { t, Elysia, form, file } from '../../src' import { expectTypeOf } from 'expect-type' // ? ArrayString { new Elysia().post( '/', ({ body }) => { expectTypeOf().toEqualTypeOf([] as string[]) }, { body: t.ArrayString(t.String()) } ) } // ? Form { new Elysia() .get( '/', () => form({ name: 'Misono Mika', file: file('example/kyuukurarin.mp4') }), { response: t.Form({ name: t.String(), file: t.File() }) } ) .get( '/', // @ts-expect-error () => form({ file: 'a' }), { response: t.Form({ name: t.String(), file: t.File() }) } ) } // Files { new Elysia().get( '/', ({ body }) => { expectTypeOf().toEqualTypeOf<{ images: File[] }>() }, { body: t.Object({ images: t.Files({ maxSize: '4m', type: 'image' }) }) } ) } // use StaticDecode to unwrap type parameter { function addTwo(num: number) { return num + 2 } new Elysia().get('', async ({ query: { foo } }) => addTwo(foo), { query: t.Object({ foo: t .Transform(t.String()) .Decode((x) => 12) .Encode((x) => x.toString()) }) }) } // handle Elysia.Ref { const Model = new Elysia().model({ hello: t.Number() }) new Elysia().use(Model).get( '', async ({ body }) => { expectTypeOf().toEqualTypeOf() }, { body: Model.Ref('hello') } ) } // Transform Tuple to Files[] { new Elysia().get( '/test', () => { return form({ files: [file('test.png'), file('test.png')], text: 'hello' }) }, { response: t.Form({ files: t.Files(), text: t.String() }) } ) } ================================================ FILE: test/types/utils.ts ================================================ import { t, getSchemaValidator } from '../../src' import { expectTypeOf } from 'expect-type' // schema validator { const schema = t.Object({ id: t.Number(), name: t.String() }) const validator = getSchemaValidator(schema) const result = validator.safeParse({ id: 1, name: 'test' }) if (result.success) { expectTypeOf(result.data).toEqualTypeOf<{ id: number; name: string }>() } } ================================================ FILE: test/units/class-to-object.test.ts ================================================ // import { describe, it, expect } from 'bun:test' // import { classToObject } from '../../src/utils' // describe('extractPropertiesAndGetters', () => { // class TestClass { // public normalProperty: string = 'normal' // private _computedProperty: number = 42 // get computedProperty(): number { // return this._computedProperty // } // public method(): string { // return 'method' // } // } // it('should extract properties and getters, omitting methods', () => { // const instance = new TestClass() // const result = classToObject(instance) // // Check that normal property is copied // expect(result.normalProperty).toBe('normal') // // Check that getter is included // expect(result.computedProperty).toBe(42) // // Check that method is not included // // @ts-ignore // expect(result.method).toBeUndefined() // // Check that private property is not included // expect(result).not.toHaveProperty('_computedProperty') // // Check the structure of the result object // expect(Object.keys(result)).toEqual([ // 'normalProperty', // 'computedProperty' // ]) // }) // it('should handle objects with no getters', () => { // const obj = { a: 1, b: 2 } // const result = classToObject(obj) // expect(result).toEqual({ a: 1, b: 2 }) // }) // it('should handle empty objects', () => { // const obj = {} // const result = classToObject(obj) // expect(result).toEqual({}) // }) // it('should handle circular references', () => { // const obj: any = { a: 1 } // obj.self = obj // obj.nested = { b: 2, parent: obj } // const result = classToObject(obj) // expect(result.a).toBe(1) // expect(result.self).toBe(result) // expect(result.nested.b).toBe(2) // expect(result.nested.parent).toBe(result) // }) // }) ================================================ FILE: test/units/deduplicate-checksum.test.ts ================================================ import { describe, expect, it } from 'bun:test' import { deduplicateChecksum, checksum } from '../../src' describe('Deduplicate Checksum', () => { it('work', () => { const a = ['a', 'b', 'c', 'a'].map((x) => ({ checksum: checksum(x), fn: () => x })) deduplicateChecksum(a) expect(a).toHaveLength(3) }) }) ================================================ FILE: test/units/get-schema-validator.test.ts ================================================ import { describe, it, expect } from 'bun:test' import { Elysia, t, getSchemaValidator } from '../../src' import { z } from 'zod' describe('getSchemaValidator', () => { it('handle TypeBox as sub type', () => { const validator = getSchemaValidator( z.object({ name: z.string() }), { validators: [ t.Object({ age: t.Number() }) ] } ) expect( validator.Check({ name: 'Elysia', age: 1 }) ).toEqual({ value: { name: 'Elysia', age: 1 } }) }) }) ================================================ FILE: test/units/has-ref.test.ts ================================================ import { t } from '../../src' import { describe, expect, it } from 'bun:test' import { hasRef } from '../../src/schema' describe('has Ref', () => { it('return true if object property has ref', () => { expect( hasRef( t.Object({ a: t.String(), b: t.Ref('b'), c: t.Number() }) ) ).toBe(true) }) it('return false if object property does not have ref', () => { expect( hasRef( t.Object({ a: t.Number(), b: t.String(), c: t.Number() }) ) ).toBe(false) }) it('return true if array property contains ref', () => { expect( hasRef( t.Object({ a: t.String(), b: t.Ref('b'), c: t.Number() }) ) ).toBe(true) }) it('should return false if array does not contains ref', () => { expect( hasRef( t.Array( t.Object({ a: t.Number(), b: t.String(), c: t.Number() }) ) ) ).toBe(false) }) it('return true if nested object property has ref', () => { expect( hasRef( t.Object({ a: t.String(), b: t.Object({ a: t.String(), b: t.Array(t.Ref('b')), c: t.Number() }), c: t.Number() }) ) ).toBe(true) }) it('return true if ref is inside array', () => { expect(hasRef(t.Ref('b'))).toBe(true) }) it('return true if ref root', () => { expect(hasRef(t.Ref('b'))).toBe(true) }) it('return true if nested object property is inside Union', () => { expect( hasRef( t.Object({ a: t.String(), b: t.Union([t.String(), t.Array(t.Ref('b'))]) }) ) ).toBe(true) }) it('return true if nested object property is inside Intersect', () => { expect( hasRef( t.Object({ a: t.String(), b: t.Intersect([t.String(), t.Array(t.Ref('b'))]) }) ) ).toBe(true) }) it('return true if Ref is inside Union', () => { expect( hasRef( t.Union([ t.Object({ foo: t.String() }), t.Object({ field: t.Ref('b') }) ]) ) ).toEqual(true) }) it('return true if Ref is inside Intersect', () => { expect( hasRef( t.Intersect([ t.Object({ foo: t.String() }), t.Object({ field: t.Ref('b') }) ]) ) ).toEqual(true) }) it('return true if Ref is inside Transform', () => { expect( hasRef( t .Transform(t.Object({ id: t.Ref('b') })) .Decode((value) => value.id) .Encode((value) => ({ id: value })) ) ).toBe(true) }) it('return true if Ref is the root', () => { expect(hasRef(t.Ref('b'))).toBe(true) }) }) ================================================ FILE: test/units/has-transform.test.ts ================================================ import { t } from '../../src' import { describe, expect, it } from 'bun:test' import { hasTransform } from '../../src/schema' describe('has transform', () => { it('return true if object property has Transform', () => { expect( hasTransform( t.Object({ a: t.String(), b: t.Numeric(), c: t.Number() }) ) ).toBe(true) }) it('return false if object property does not have Transform', () => { expect( hasTransform( t.Object({ a: t.Number(), b: t.String(), c: t.Number() }) ) ).toBe(false) }) it('return true if array property contains Transform', () => { expect( hasTransform( t.Object({ a: t.String(), b: t.Numeric(), c: t.Number() }) ) ).toBe(true) }) it('should return false if array does not contains Transform', () => { expect( hasTransform( t.Array( t.Object({ a: t.Number(), b: t.String(), c: t.Number() }) ) ) ).toBe(false) }) it('return true if nested object property has Transform', () => { expect( hasTransform( t.Object({ a: t.String(), b: t.Object({ a: t.String(), b: t.Array(t.Numeric()), c: t.Number() }), c: t.Number() }) ) ).toBe(true) }) it('return true if Transform is inside array', () => { expect(hasTransform(t.Numeric())).toBe(true) }) it('return true if Transform root', () => { expect(hasTransform(t.Numeric())).toBe(true) }) it('return true if nested object property is inside Union', () => { expect( hasTransform( t.Object({ a: t.String(), b: t.Union([t.String(), t.Array(t.Numeric())]) }) ) ).toBe(true) }) it('return true if nested object property is inside Intersect', () => { expect( hasTransform( t.Object({ a: t.String(), b: t.Intersect([t.String(), t.Array(t.Numeric())]) }) ) ).toBe(true) }) it('return true if Transform is inside Intersect', () => { expect( hasTransform( t.Intersect([ t.Object({ foo: t.String() }), t.Object({ field: t .Transform(t.String()) .Decode((decoded) => ({ decoded })) .Encode((v) => v.decoded) }) ]) ) ).toEqual(true) }) it('return true if Transform is inside Union', () => { expect( hasTransform( t.Union([ t.Object({ foo: t.String() }), t.Object({ field: t .Transform(t.String()) .Decode((decoded) => ({ decoded })) .Encode((v) => v.decoded) }) ]) ) ).toEqual(true) }) it('return true when Transform is the root', () => { expect( hasTransform( t .Transform(t.Object({ id: t.String() })) .Decode((value) => value.id) .Encode((value) => ({ id: value })) ) ).toBe(true) }) }) ================================================ FILE: test/units/merge-deep.test.ts ================================================ import { describe, expect, it } from 'bun:test' import { Elysia } from '../../src' import { mergeDeep } from '../../src/utils' import { req } from '../utils' describe('mergeDeep', () => { it('merge empty object', () => { const result = mergeDeep({}, {}) expect(result).toEqual({}) }) it('merge non-overlapping key', () => { const result = mergeDeep({ key1: 'value1' }, { key2: 'value2' }) expect(result).toEqual({ key1: 'value1', key2: 'value2' }) }) it('merge overlapping key', () => { const result = mergeDeep( { name: 'Eula', city: 'Mondstadt' }, { name: 'Amber', affiliation: 'Knight' } ) expect(result).toEqual({ name: 'Amber', city: 'Mondstadt', affiliation: 'Knight' }) }) it('maintain overlapping class', () => { class Test { readonly name = 'test' public foo() { return this.name } } const target = { key1: Test } const source = { key2: Test } const result = mergeDeep(target, source) expect(result.key1).toBe(Test) }) it('maintain overlapping class in instance', async () => { class DbConnection { health() { return 'ok' } getUsers() { return [] } } const dbPlugin = new Elysia({ name: 'db' }).decorate( 'db', new DbConnection() ) const userRoutes = new Elysia({ prefix: '/user' }) .use(dbPlugin) .get('', ({ db }) => db.getUsers()) const app = new Elysia() .use(dbPlugin) .use(userRoutes) .get('/health', ({ db }) => db.health()) const response = await app.handle(req('/health')).then((x) => x.text()) expect(response).toBe('ok') }) it('handle freezed object', () => { new Elysia() .decorate('db', Object.freeze({ hello: 'world' })) .guard({}, (app) => app) }) it('handle circular references', () => { const a: { x: number toB?: typeof b } = { x: 1 } const b: { y: number toA?: typeof a } = { y: 2 } a.toB = b b.toA = a const target = {} const source = { prop: a } const result = mergeDeep(target, source) expect(result.prop.x).toBe(1) expect(result.prop.toB?.y).toBe(2) }) it('handle shared references in different branches', () => { const shared = { value: 123 } const target = { x: {}, y: {} } const source = { x: shared, y: shared } const result = mergeDeep(target, source) expect(result.x.value).toBe(123) expect(result.y.value).toBe(123) }) it('deduplicate plugin with circular decorators', async () => { const a: { x: number toB?: typeof b } = { x: 1 } const b: { y: number toA?: typeof a } = { y: 2 } a.toB = b b.toA = a const complex = { a } const Plugin = new Elysia({ name: 'Plugin', seed: 'seed' }) .decorate('dep', complex) .as('scoped') const ModuleA = new Elysia({ name: 'ModuleA' }) .use(Plugin) .get('/moda/a', ({ dep }) => dep.a.x) .get('/moda/b', ({ dep }) => dep.a.toB?.y) const ModuleB = new Elysia({ name: 'ModuleB' }) .use(Plugin) .get('/modb/a', ({ dep }) => dep.a.x) .get('/modb/b', ({ dep }) => dep.a.toB?.y) const app = new Elysia().use(ModuleA).use(ModuleB) const resA = await app.handle(req('/moda/a')).then((x) => x.text()) const resB = await app.handle(req('/modb/a')).then((x) => x.text()) const resC = await app.handle(req('/moda/b')).then((x) => x.text()) const resD = await app.handle(req('/modb/b')).then((x) => x.text()) expect(resA).toBe('1') expect(resB).toBe('1') expect(resC).toBe('2') expect(resD).toBe('2') }) }) ================================================ FILE: test/units/merge-object-schemas.test.ts ================================================ import { t } from '../../src' import { describe, expect, it } from 'bun:test' import { mergeObjectSchemas } from '../../src/schema' describe('mergeDeep', () => { it('merge object', () => { const result = mergeObjectSchemas([ t.Object({ name: t.String() }), t.Object({ age: t.Number() }) ]) expect(result).toEqual({ schema: t.Object({ name: t.String(), age: t.Number() }), notObjects: [] }) }) it('handle additionalProperties true', () => { const result = mergeObjectSchemas([ t.Object( { name: t.String() }, { additionalProperties: true } ), t.Object({ age: t.Number() }) ]) expect(result).toEqual({ schema: t.Object( { name: t.String(), age: t.Number() }, { additionalProperties: true } ), notObjects: [] }) }) it('handle additionalProperties false', () => { const result = mergeObjectSchemas([ t.Object( { name: t.String() }, { additionalProperties: false } ), t.Object({ age: t.Number() }) ]) expect(result).toEqual({ schema: t.Object( { name: t.String(), age: t.Number() }, { additionalProperties: false } ), notObjects: [] }) }) it('prefers additionalProperties: false over true', () => { const result = mergeObjectSchemas([ t.Object( { name: t.String() }, { additionalProperties: true } ), t.Object( { age: t.Number() }, { additionalProperties: false } ) ]) expect(result).toEqual({ schema: t.Object( { name: t.String(), age: t.Number() }, { additionalProperties: false } ), notObjects: [] }) }) it('handle non object', () => { const result = mergeObjectSchemas([ t.Object( { name: t.String() }, { additionalProperties: false } ), t.Object({ age: t.Number() }), t.String() ]) expect(result).toEqual({ schema: t.Object( { name: t.String(), age: t.Number() }, { additionalProperties: false } ), notObjects: [t.String()] }) }) it('handle single object schema', () => { const result = mergeObjectSchemas([ t.Object( { name: t.String() }, { additionalProperties: false } ) ]) expect(result).toEqual({ schema: t.Object( { name: t.String() }, { additionalProperties: false } ), notObjects: [] }) }) it('handle single non object schema', () => { const result = mergeObjectSchemas([t.String()]) expect(result).toEqual({ schema: undefined, notObjects: [t.String()] }) }) it('handle multiple object schemas', () => { const result = mergeObjectSchemas([ t.Object( { name: t.String() }, { additionalProperties: false } ), t.Object({ age: t.Number() }), t.Object({ email: t.String() }), t.Object({ address: t.String() }) ]) expect(result).toEqual({ schema: t.Object( { name: t.String(), age: t.Number(), email: t.String(), address: t.String() }, { additionalProperties: false } ), notObjects: [] }) }) }) ================================================ FILE: test/units/numeric.test.ts ================================================ import { describe, expect, it } from 'bun:test' import { isNumericString } from '../../src/utils' describe('Numeric string', () => { it('valid string', async () => { expect(isNumericString('69')).toBe(true) expect(isNumericString('69.420')).toBe(true) expect(isNumericString('00093281')).toBe(true) expect(isNumericString(Number.MAX_SAFE_INTEGER.toString())).toBe(true) }) it('invalid string', async () => { expect(isNumericString('pizza')).toBe(false) expect(isNumericString('69,420')).toBe(false) expect(isNumericString('0O093281')).toBe(false) expect(isNumericString(crypto.randomUUID())).toBe(false) expect(isNumericString('9007199254740995')).toBe(false) expect(isNumericString('123456789012345678')).toBe(false) expect(isNumericString('123123.999999999999')).toBe(false) }) it('invalid on empty', async () => { expect(isNumericString('')).toBe(false) expect(isNumericString(' ')).toBe(false) expect(isNumericString(' ')).toBe(false) }) it('start with number', async () => { expect(isNumericString('6AAAA')).toBe(false) }) }) ================================================ FILE: test/units/replace-schema-type.test.ts ================================================ import { describe, it, expect } from 'bun:test' import type { TSchema } from '@sinclair/typebox' import { Elysia, t } from '../../src' import { replaceSchemaTypeFromManyOptions as replaceSchemaType, revertObjAndArrStr, coerceFormData } from '../../src/replace-schema' import { req } from '../utils' describe('Replace Schema Type', () => { it('replace primitive', async () => { expect( replaceSchemaType(t.String(), { from: t.String(), to: () => t.Number() }) ).toMatchObject(t.Number()) }) it('replace object properties', async () => { expect( replaceSchemaType( t.Object({ id: t.Number(), name: t.String() }), { from: t.Number(), to: () => t.Numeric() } ) ).toMatchObject( t.Object({ id: t.Numeric(), name: t.String() }) ) }) it('replace object properties in nullable', async () => { expect( replaceSchemaType( t.Nullable( t.Object({ id: t.Number(), name: t.String() }) ), { from: t.Number(), to: () => t.Numeric() } ) ).toMatchObject( t.Nullable( t.Object({ id: t.Numeric(), name: t.String() }) ) ) }) it('replace object properties in Union', async () => { expect( replaceSchemaType( t.Union([ t.String(), t.Object({ id: t.Number(), name: t.String() }) ]), { from: t.Number(), to: () => t.Numeric() } ) ).toMatchObject( t.Union([ t.String(), t.Object({ id: t.Numeric(), name: t.String() }) ]) ) }) it('maintain descriptive properties', async () => { expect( replaceSchemaType( t.Object({ id: t.Number({ default: 1, title: 'hello' }), name: t.String() }), { from: t.Number(), to: (options) => t.Numeric(options) } ) ).toMatchObject( t.Object({ id: t.Numeric({ default: 1, title: 'hello' }), name: t.String() }) ) }) it('accept multiple replacement', async () => { expect( replaceSchemaType( t.Object({ id: t.Number(), isAdmin: t.Boolean() }), [ { from: t.Number(), to: () => t.Numeric() }, { from: t.Boolean(), to: () => t.BooleanString() } ] ) ).toMatchObject( t.Object({ id: t.Numeric(), isAdmin: t.BooleanString() }) ) }) it('replace excludeRoot (match ObjectString)', () => { expect( replaceSchemaType( t.Object({ obj: t.Object({ id: t.String() }) }), { from: t.Object({}), to: (schema) => t.ObjectString(schema.properties), excludeRoot: true, untilObjectFound: false } ) ).toMatchObject( t.Object({ obj: t.ObjectString({ id: t.String() }) }) ) }) it('replace replace ArrayString', () => { expect( replaceSchemaType( t.Object({ arr: t.Array(t.String()) }), { from: t.Object({}), to: () => t.ObjectString({}), excludeRoot: true } ) ).toMatchObject( t.Object({ arr: t.Array(t.String()) }) ) }) it('replace re-calculate transform', async () => { const app = new Elysia().get('/', ({ query }) => query, { query: t.Object({ pagination: t.Object({ pageIndex: t.Number(), pageLimit: t.Number() }) }) }) const status = await app .handle(req('/?pagination={"pageIndex":1}')) .then((x) => x.status) expect(status).toBe(422) }) it('replace item in Array', () => { expect( replaceSchemaType( t.Object({ arr: t.Array(t.Number()) }), { from: t.Number(), to: () => t.Numeric(), excludeRoot: true } ) ).toMatchObject( t.Object({ arr: t.Array(t.Numeric()) }) ) }) describe('Basic Transformation', () => { it('should transform Object to ObjectString', () => { expect( replaceSchemaType( t.Object({ name: t.String() }), { from: t.Object({}), to: (s) => t.ObjectString(s.properties || {}, s) } ) ).toMatchObject({ elysiaMeta: 'ObjectString' }) }) it('should transform Array to ArrayString', () => { expect( replaceSchemaType(t.Array(t.String()), { from: t.Array(t.Any()), to: (s) => t.ArrayString(s.items || t.Any(), s) }) ).toMatchObject({ elysiaMeta: 'ArrayString' }) }) it('should preserve properties after transformation', () => { expect( replaceSchemaType( t.Object({ name: t.String(), age: t.Number() }), { from: t.Object({}), to: (s) => t.ObjectString(s.properties || {}, s) } ) ).toMatchObject( t.ObjectString({ name: t.String(), age: t.Number() }) ) }) }) describe('excludeRoot Option', () => { it('should NOT transform root when excludeRoot is true', () => { const result = replaceSchemaType( t.Object({ metadata: t.Object({ category: t.String() }) }), { from: t.Object({}), to: (s) => t.ObjectString(s.properties || {}, s), excludeRoot: true } ) expect(result).toMatchObject({ type: 'object' }) expect(result.elysiaMeta).toBeUndefined() expect(result.properties.metadata).toMatchObject({ elysiaMeta: 'ObjectString' }) }) it('should transform root when excludeRoot is false', () => { expect( replaceSchemaType( t.Object({ name: t.String() }), { from: t.Object({}), to: (s) => t.ObjectString(s.properties || {}, s), excludeRoot: false } ) ).toMatchObject({ elysiaMeta: 'ObjectString' }) }) }) describe('onlyFirst Option', () => { it('should stop traversal after first match', () => { const result = replaceSchemaType( t.Object({ level1: t.Object({ level2: t.Object({ level3: t.String() }) }) }), { from: t.Object({}), to: (s) => t.ObjectString(s.properties || {}, s), onlyFirst: 'object', excludeRoot: true } ) expect(result.properties.level1).toMatchObject({ elysiaMeta: 'ObjectString' }) const level1ObjBranch = result.properties.level1.anyOf.find( (x: TSchema) => x.type === 'object' ) expect(level1ObjBranch.properties.level2).toMatchObject({ type: 'object' }) expect(level1ObjBranch.properties.level2.elysiaMeta).toBeUndefined() }) it('should transform all siblings at same level', () => { const result = replaceSchemaType( t.Object({ obj1: t.Object({ a: t.String() }), obj2: t.Object({ b: t.String() }), str: t.String() }), { from: t.Object({}), to: (s) => t.ObjectString(s.properties || {}, s), onlyFirst: 'object', excludeRoot: true } ) expect(result.properties.obj1).toMatchObject({ elysiaMeta: 'ObjectString' }) expect(result.properties.obj2).toMatchObject({ elysiaMeta: 'ObjectString' }) expect(result.properties.str).toMatchObject({ type: 'string' }) }) }) describe('rootOnly Option', () => { it('should only transform root, not children', () => { const result = replaceSchemaType( t.Object({ nested: t.Object({ deep: t.String() }) }), { from: t.Object({}), to: (s) => t.ObjectString(s.properties || {}, s), rootOnly: true } ) expect(result).toMatchObject({ elysiaMeta: 'ObjectString' }) const objBranch = result.anyOf.find( (x: TSchema) => x.type === 'object' ) expect(objBranch.properties.nested).toMatchObject({ type: 'object' }) expect(objBranch.properties.nested.elysiaMeta).toBeUndefined() }) it('should not transform if root does not match', () => { expect( replaceSchemaType(t.String(), { from: t.Object({}), to: (s) => t.ObjectString(s.properties || {}, s), rootOnly: true }) ).toMatchObject({ type: 'string' }) }) }) describe('Double-wrapping Protection', () => { it('should NOT double-wrap ObjectString', () => { const result = replaceSchemaType( t.Object({ metadata: t.ObjectString({ category: t.String() }) }), { from: t.Object({}), to: (s) => t.ObjectString(s.properties || {}, s), excludeRoot: true } ) expect(result.properties.metadata).toMatchObject({ elysiaMeta: 'ObjectString' }) const anyOf = result.properties.metadata.anyOf const objBranch = anyOf.find((x: TSchema) => x.type === 'object') expect(objBranch.elysiaMeta).toBeUndefined() expect(objBranch.anyOf).toBeUndefined() }) it('should NOT double-wrap ArrayString', () => { const result = replaceSchemaType( t.Object({ items: t.ArrayString(t.String()) }), { from: t.Array(t.Any()), to: (s) => t.ArrayString(s.items || t.Any(), s), excludeRoot: true } ) expect(result.properties.items).toMatchObject({ elysiaMeta: 'ArrayString' }) const anyOf = result.properties.items.anyOf const arrBranch = anyOf.find((x: TSchema) => x.type === 'array') expect(arrBranch.elysiaMeta).toBeUndefined() }) }) describe('Bottom-up Traversal', () => { it('should transform children before parents', () => { const result = replaceSchemaType( t.Object({ level1: t.Object({ level2: t.Object({ level3: t.String() }) }) }), { from: t.Object({}), to: (s) => t.ObjectString(s.properties || {}, s), excludeRoot: true } ) expect(result.properties.level1).toMatchObject({ elysiaMeta: 'ObjectString' }) const level1ObjBranch = result.properties.level1.anyOf.find( (x: TSchema) => x.type === 'object' ) expect(level1ObjBranch.properties.level2).toMatchObject({ elysiaMeta: 'ObjectString' }) }) }) describe('Array of Options', () => { it('should apply multiple transformations in order', () => { const result = replaceSchemaType( t.Object({ metadata: t.Object({ category: t.String() }), tags: t.Array(t.String()) }), [ { from: t.Object({}), to: (s) => t.ObjectString(s.properties || {}, s), excludeRoot: true }, { from: t.Array(t.Any()), to: (s) => t.ArrayString(s.items || t.Any(), s), excludeRoot: true } ] ) expect(result.properties.metadata).toMatchObject({ elysiaMeta: 'ObjectString' }) expect(result.properties.tags).toMatchObject({ elysiaMeta: 'ArrayString' }) }) }) describe('Composition Types', () => { it('should traverse anyOf branches', () => { const result = replaceSchemaType( { anyOf: [ t.Object({ a: t.String() }), t.Object({ b: t.Number() }) ] } as any, { from: t.Object({}), to: (s) => t.ObjectString(s.properties || {}, s) } ) expect(result.anyOf[0]).toMatchObject({ elysiaMeta: 'ObjectString' }) expect(result.anyOf[1]).toMatchObject({ elysiaMeta: 'ObjectString' }) }) it('should traverse oneOf branches', () => { const result = replaceSchemaType( { oneOf: [t.Object({ type: t.String() }), t.Array(t.String())] } as any, { from: t.Object({}), to: (s) => t.ObjectString(s.properties || {}, s) } ) expect(result.oneOf[0]).toMatchObject({ elysiaMeta: 'ObjectString' }) expect(result.oneOf[1]).toMatchObject({ type: 'array' }) }) }) describe('Reverse Transformation Helpers', () => { it('should extract plain Object from ObjectString', () => { const objectString = t.ObjectString({ name: t.String(), age: t.Number() }) const result = revertObjAndArrStr(objectString) expect(result).toMatchObject({ type: 'object' }) expect(result.elysiaMeta).toBeUndefined() expect(result.anyOf).toBeUndefined() expect(result.properties).toMatchObject({ name: { type: 'string' }, age: { type: 'number' } }) }) it('should return unchanged if not ObjectString', () => { const plainObject = t.Object({ name: t.String() }) const result = revertObjAndArrStr(plainObject) expect(result).toBe(plainObject) }) it('should extract plain Array from ArrayString', () => { const arrayString = t.ArrayString(t.String()) const result = revertObjAndArrStr(arrayString) expect(result).toMatchObject({ type: 'array' }) expect(result.elysiaMeta).toBeUndefined() expect(result.anyOf).toBeUndefined() expect(result.items).toMatchObject({ type: 'string' }) }) it('should return unchanged if not ArrayString', () => { const plainArray = t.Array(t.String()) const result = revertObjAndArrStr(plainArray) expect(result).toBe(plainArray) }) it('should transform ObjectString back to Object', () => { const result = replaceSchemaType( t.Object({ metadata: t.ObjectString({ category: t.String() }) }), { from: t.ObjectString({}), to: (s) => revertObjAndArrStr(s), excludeRoot: true } ) expect(result.properties.metadata).toMatchObject({ type: 'object' }) expect(result.properties.metadata.elysiaMeta).toBeUndefined() expect(result.properties.metadata.anyOf).toBeUndefined() expect(result.properties.metadata.properties.category).toMatchObject( { type: 'string' } ) }) it('should transform ArrayString back to Array', () => { const result = replaceSchemaType( t.Object({ tags: t.ArrayString(t.String()) }), { from: t.ArrayString(t.Any()), to: (s) => revertObjAndArrStr(s), excludeRoot: true } ) expect(result.properties.tags).toMatchObject({ type: 'array' }) expect(result.properties.tags.elysiaMeta).toBeUndefined() expect(result.properties.tags.anyOf).toBeUndefined() }) }) describe('coerceFormData', () => { it('should convert first-level Object to ObjectString (excluding root)', () => { const result = replaceSchemaType( t.Object({ user: t.Object({ name: t.String(), age: t.Number() }) }), coerceFormData() ) // Root should remain plain Object expect(result).toMatchObject({ type: 'object' }) expect(result.elysiaMeta).toBeUndefined() // First-level nested object should be converted to ObjectString expect(result.properties.user).toMatchObject({ elysiaMeta: 'ObjectString' }) }) it('should NOT convert deeper nested Objects', () => { const result = replaceSchemaType( t.Object({ level1: t.Object({ level2: t.Object({ level3: t.Object({ value: t.String() }) }) }) }), coerceFormData() ) // level1 should be ObjectString expect(result.properties.level1).toMatchObject({ elysiaMeta: 'ObjectString' }) // level2 should remain plain Object (not converted) const level1ObjBranch = result.properties.level1.anyOf.find( (x: TSchema) => x.type === 'object' ) expect(level1ObjBranch.properties.level2).toMatchObject({ type: 'object' }) expect(level1ObjBranch.properties.level2.elysiaMeta).toBeUndefined() // level3 should also remain plain Object expect(level1ObjBranch.properties.level2.properties.level3).toMatchObject({ type: 'object' }) expect(level1ObjBranch.properties.level2.properties.level3.elysiaMeta).toBeUndefined() }) it('should convert first-level Array to ArrayString', () => { const result = replaceSchemaType( t.Object({ tags: t.Array(t.String()) }), coerceFormData() ) // tags should be converted to ArrayString expect(result.properties.tags).toMatchObject({ elysiaMeta: 'ArrayString' }) }) it('should NOT convert deeper nested Arrays', () => { const result = replaceSchemaType( t.Object({ level1: t.Array( t.Array( t.Array(t.String()) ) ) }), coerceFormData() ) // First-level array should be ArrayString expect(result.properties.level1).toMatchObject({ elysiaMeta: 'ArrayString' }) // Second-level array should remain plain Array const level1ArrBranch = result.properties.level1.anyOf.find( (x: TSchema) => x.type === 'array' ) expect(level1ArrBranch.items).toMatchObject({ type: 'array' }) expect(level1ArrBranch.items.elysiaMeta).toBeUndefined() // Third-level array should also remain plain Array expect(level1ArrBranch.items.items).toMatchObject({ type: 'array' }) expect(level1ArrBranch.items.items.elysiaMeta).toBeUndefined() }) it('should handle Object with File and nested Object', () => { const result = replaceSchemaType( t.Object({ avatar: t.File(), metadata: t.Object({ tags: t.Array(t.String()), settings: t.Object({ theme: t.String() }) }) }), coerceFormData() ) // Root should remain Object expect(result.type).toBe('object') expect(result.elysiaMeta).toBeUndefined() // File should remain as File expect(result.properties.avatar).toMatchObject({ type: 'string', format: 'binary' }) // First-level metadata should be ObjectString expect(result.properties.metadata).toMatchObject({ elysiaMeta: 'ObjectString' }) // Nested tags array should remain plain Array (not converted) const metadataObjBranch = result.properties.metadata.anyOf.find( (x: TSchema) => x.type === 'object' ) expect(metadataObjBranch.properties.tags).toMatchObject({ type: 'array' }) expect(metadataObjBranch.properties.tags.elysiaMeta).toBeUndefined() // Nested settings object should remain plain Object (not converted) expect(metadataObjBranch.properties.settings).toMatchObject({ type: 'object' }) expect(metadataObjBranch.properties.settings.elysiaMeta).toBeUndefined() }) it('should handle Object with Files (array) and nested structures', () => { const result = replaceSchemaType( t.Object({ images: t.Files(), data: t.Object({ items: t.Array( t.Object({ name: t.String() }) ) }) }), coerceFormData() ) // Files should remain as Files expect(result.properties.images).toMatchObject({ type: 'array', items: { type: 'string', format: 'binary' }, elysiaMeta: 'Files' }) // First-level data should be ObjectString expect(result.properties.data).toMatchObject({ elysiaMeta: 'ObjectString' }) // Nested items array should remain plain Array const dataObjBranch = result.properties.data.anyOf.find( (x: TSchema) => x.type === 'object' ) expect(dataObjBranch.properties.items).toMatchObject({ type: 'array' }) expect(dataObjBranch.properties.items.elysiaMeta).toBeUndefined() // Array items (objects) should remain plain Objects expect(dataObjBranch.properties.items.items).toMatchObject({ type: 'object' }) expect(dataObjBranch.properties.items.items.elysiaMeta).toBeUndefined() }) it('should convert all first-level siblings', () => { const result = replaceSchemaType( t.Object({ obj1: t.Object({ a: t.String() }), obj2: t.Object({ b: t.Number() }), arr1: t.Array(t.String()), arr2: t.Array(t.Number()), file: t.File(), str: t.String() }), coerceFormData() ) // All first-level objects should be ObjectString expect(result.properties.obj1).toMatchObject({ elysiaMeta: 'ObjectString' }) expect(result.properties.obj2).toMatchObject({ elysiaMeta: 'ObjectString' }) // All first-level arrays should be ArrayString expect(result.properties.arr1).toMatchObject({ elysiaMeta: 'ArrayString' }) expect(result.properties.arr2).toMatchObject({ elysiaMeta: 'ArrayString' }) // Other types should remain unchanged expect(result.properties.file).toMatchObject({ type: 'string', format: 'binary' }) expect(result.properties.str).toMatchObject({ type: 'string' }) }) it('should handle mixed nested structures correctly', () => { const result = replaceSchemaType( t.Object({ upload: t.File(), config: t.Object({ nested: t.Object({ deep: t.Array( t.Object({ value: t.String() }) ) }) }) }), coerceFormData() ) // config should be ObjectString expect(result.properties.config).toMatchObject({ elysiaMeta: 'ObjectString' }) const configObjBranch = result.properties.config.anyOf.find( (x: TSchema) => x.type === 'object' ) // nested should remain plain Object expect(configObjBranch.properties.nested).toMatchObject({ type: 'object' }) expect(configObjBranch.properties.nested.elysiaMeta).toBeUndefined() // deep array should remain plain Array expect(configObjBranch.properties.nested.properties.deep).toMatchObject({ type: 'array' }) expect(configObjBranch.properties.nested.properties.deep.elysiaMeta).toBeUndefined() // Array items should remain plain Objects expect(configObjBranch.properties.nested.properties.deep.items).toMatchObject({ type: 'object' }) expect(configObjBranch.properties.nested.properties.deep.items.elysiaMeta).toBeUndefined() }) }) }) ================================================ FILE: test/utils.d.ts ================================================ export declare const req: (path: string, options?: RequestInit) => import("undici-types").Request; type MaybeArray = T | T[]; export declare const upload: (path: string, fields: Record>) => { request: import("undici-types").Request; size: number; }; export declare const post: (path: string, body?: Record) => import("undici-types").Request; export declare const delay: (delay: number) => Promise; export {}; ================================================ FILE: test/utils.ts ================================================ export const req = (path: string, options?: RequestInit) => new Request(`http://localhost${path}`, options) type MaybeArray = T | T[] export const upload = ( path: string, fields: Record< string, MaybeArray< | (string & {}) | 'aris-yuzu.jpg' | 'midori.png' | 'millenium.jpg' | 'fake.jpg' | 'kozeki-ui.webp' > > ) => { const body = new FormData() let size = 0 for (const [key, value] of Object.entries(fields)) { if (Array.isArray(value)) value.forEach((value) => { const file = Bun.file(`./test/images/${value}`) size += file.size body.append(key, file) }) else if (value.includes('.')) { const file = Bun.file(`./test/images/${value}`) size += file.size body.append(key, file) } else body.append(key, value) } return { request: new Request(`http://localhost${path}`, { method: 'POST', body }), size } } export const post = (path: string, body?: string | Record) => typeof body === 'string' ? new Request(`http://localhost${path}`, { method: 'POST', headers: { 'Content-Type': 'text/plain', 'Content-Length': String(Buffer.byteLength(body)) }, body }) : new Request(`http://localhost${path}`, { method: 'POST', headers: body ? { 'Content-Type': 'application/json', 'Content-Length': String( Buffer.byteLength(JSON.stringify(body)) ) } : {}, body: body ? JSON.stringify(body) : body }) export const delay = (delay: number) => new Promise((resolve) => setTimeout(resolve, delay)) ================================================ FILE: test/validator/body.test.ts ================================================ import { Elysia, t, ValidationError } from '../../src' import { describe, expect, it } from 'bun:test' import { post, upload } from '../utils' describe('Body Validator', () => { it('skip body parsing if body is empty but headers is present', async () => { const app = new Elysia().post('/', ({ body }) => 'ok') const response = await app.handle( new Request('http://localhost', { method: 'POST', headers: { 'content-type': 'application/json' } }) ) expect(response.status).toBe(200) }) it('validate single', async () => { const app = new Elysia().post('/', ({ body: { name } }) => name, { body: t.Object({ name: t.String() }) }) const res = await app.handle( post('/', { name: 'sucrose' }) ) expect(await res.text()).toBe('sucrose') expect(res.status).toBe(200) }) it('validate multiple', async () => { const app = new Elysia().post('/', ({ body }) => body, { body: t.Object({ name: t.String(), job: t.String(), trait: t.String() }) }) const res = await app.handle( post('/', { name: 'sucrose', job: 'alchemist', trait: 'dog' }) ) expect(await res.json()).toEqual({ name: 'sucrose', job: 'alchemist', trait: 'dog' }) expect(res.status).toBe(200) }) it('parse without reference', async () => { const app = new Elysia().post('/', () => '', { body: t.Object({ name: t.String(), job: t.String(), trait: t.String() }) }) const res = await app.handle( post('/', { name: 'sucrose', job: 'alchemist', trait: 'dog' }) ) expect(res.status).toBe(200) }) it('validate optional', async () => { const app = new Elysia().post('/', ({ body }) => body, { body: t.Object({ name: t.String(), job: t.String(), trait: t.Optional(t.String()) }) }) const res = await app.handle( post('/', { name: 'sucrose', job: 'alchemist' }) ) expect(await res.json()).toEqual({ name: 'sucrose', job: 'alchemist' }) expect(res.status).toBe(200) }) it('parse single numeric', async () => { const app = new Elysia().post('/', ({ body }) => body, { body: t.Object({ name: t.String(), job: t.String(), trait: t.Optional(t.String()), age: t.Numeric() }) }) const res = await app.handle( post('/', { name: 'sucrose', job: 'alchemist', age: '16' }) ) expect(await res.json()).toEqual({ name: 'sucrose', job: 'alchemist', age: 16 }) expect(res.status).toBe(200) }) it('parse multiple numeric', async () => { const app = new Elysia().post('/', ({ body }) => body, { body: t.Object({ name: t.String(), job: t.String(), trait: t.Optional(t.String()), age: t.Numeric(), rank: t.Numeric() }) }) const res = await app.handle( post('/', { name: 'sucrose', job: 'alchemist', age: '16', rank: '4' }) ) expect(await res.json()).toEqual({ name: 'sucrose', job: 'alchemist', age: 16, rank: 4 }) expect(res.status).toBe(200) }) it('parse single integer', async () => { const app = new Elysia().post('/', ({ body }) => body, { body: t.Object({ name: t.String(), job: t.String(), trait: t.Optional(t.String()), age: t.Integer() }) }) const res = await app.handle( post('/', { name: 'sucrose', job: 'alchemist', age: '16' }) ) expect(await res.json()).toEqual({ name: 'sucrose', job: 'alchemist', age: 16 }) expect(res.status).toBe(200) }) it('parse multiple integers', async () => { const app = new Elysia().post('/', ({ body }) => body, { body: t.Object({ name: t.String(), job: t.String(), trait: t.Optional(t.String()), age: t.Integer(), rank: t.Integer() }) }) const res = await app.handle( post('/', { name: 'sucrose', job: 'alchemist', age: '16', rank: '4' }) ) expect(await res.json()).toEqual({ name: 'sucrose', job: 'alchemist', age: 16, rank: 4 }) expect(res.status).toBe(200) }) it('rejects malformed integer from array object', async () => { const app = new Elysia().post('/', ({ body }) => body, { body: t.Array( t.Object({ name: t.String(), job: t.String(), trait: t.Optional(t.String()), age: t.Integer(), rank: t.Integer() }) ) }) const res = await app.handle( post('/', [ { name: 'sucrose', job: 'alchemist', age: 16.4, rank: 4 } ]) ) expect(res.status).toBe(422) }) it('rejects malformed integer directly in array', async () => { const app = new Elysia().post('/', ({ body }) => body, { body: t.Array(t.Integer()) }) const res = await app.handle(post('/', [1, 2, 3, 4.2])) expect(res.status).toBe(422) }) it('validate empty body', async () => { const app = new Elysia().post('/', ({ body }) => body, { body: t.Union([ t.Undefined(), t.Object({ name: t.String(), job: t.String(), trait: t.Optional(t.String()) }) ]) }) const res = await app.handle( new Request('http://localhost/', { method: 'POST' }) ) expect(res.status).toBe(200) expect(await res.text()).toBe('') }) it('validate empty body with partial', async () => { const app = new Elysia().post('/', ({ body }) => body, { body: t.Union([ t.Undefined(), t.Object({ name: t.String(), job: t.String(), trait: t.Optional(t.String()), age: t.Numeric(), rank: t.Numeric() }) ]) }) const res = await app.handle( new Request('http://localhost/', { method: 'POST' }) ) expect(res.status).toBe(200) expect(await res.text()).toEqual('') }) it('normalize by default', async () => { const app = new Elysia().post('/', ({ body }) => body, { body: t.Object({ name: t.String() }) }) const res = await app .handle( post('/', { name: 'sucrose', job: 'alchemist' }) ) .then((x) => x.json()) expect(res).toEqual({ name: 'sucrose' }) }) it('strictly validate if not normalize', async () => { const app = new Elysia({ normalize: false }).post( '/', ({ body }) => body, { body: t.Object({ name: t.String() }) } ) const res = await app.handle( post('/', { name: 'sucrose', job: 'alchemist' }) ) expect(res.status).toBe(422) }) it('validate maybe empty body', async () => { const app = new Elysia().post('/', ({ body }) => body, { body: t.MaybeEmpty( t.Object({ name: t.String(), job: t.String(), trait: t.Optional(t.String()) }) ) }) const res = await app.handle( new Request('http://localhost/', { method: 'POST' }) ) expect(res.status).toBe(200) expect(await res.text()).toBe('') }) it('validate record', async () => { const app = new Elysia().post('/', ({ body: { name } }) => name, { body: t.Record(t.String(), t.String()) }) const res = await app.handle( post('/', { name: 'sucrose' }) ) expect(await res.text()).toBe('sucrose') expect(res.status).toBe(200) }) it('validate record inside object', async () => { const app = new Elysia().post( '/', ({ body: { name, friends } }) => `${name} ~ ${Object.keys(friends).join(' + ')}`, { body: t.Object({ name: t.String(), friends: t.Record(t.String(), t.String()) }) } ) const res = await app.handle( post('/', { name: 'sucrose', friends: { amber: 'wizard', lisa: 'librarian' } }) ) expect(await res.text()).toBe('sucrose ~ amber + lisa') expect(res.status).toBe(200) }) it('validate optional primitive', async () => { const app = new Elysia().post('/', ({ body }) => body ?? 'sucrose', { body: t.Optional(t.String()) }) const [valid, invalid] = await Promise.all([ app.handle( new Request('http://localhost/', { method: 'POST', headers: { 'Content-Type': 'text/plain' }, body: 'sucrose' }) ), app.handle( new Request('http://localhost/', { method: 'POST' }) ) ]) expect(await valid.text()).toBe('sucrose') expect(valid.status).toBe(200) expect(await invalid.text()).toBe('sucrose') expect(invalid.status).toBe(200) }) it('validate optional object', async () => { const app = new Elysia().post( '/', ({ body }) => body?.name ?? 'sucrose', { body: t.Optional( t.Object({ name: t.String() }) ) } ) const [valid, invalid] = await Promise.all([ app.handle( post('/', { name: 'sucrose' }) ), app.handle( new Request('http://localhost/', { method: 'POST' }) ) ]) expect(await valid.text()).toBe('sucrose') expect(valid.status).toBe(200) expect(await invalid.text()).toBe('sucrose') expect(invalid.status).toBe(200) }) it('create default object body', async () => { const app = new Elysia().post('/', ({ body }) => body, { body: t.Object({ username: t.String(), password: t.String(), email: t.Optional(t.String({ format: 'email' })), isSuperuser: t.Boolean({ default: false }) }) }) const value = await app .handle( post('/', { username: 'nagisa', password: 'hifumi_daisuki', email: 'kirifuji_nagisa@trinity.school' }) ) .then((x) => x.json()) expect(value).toEqual({ username: 'nagisa', password: 'hifumi_daisuki', email: 'kirifuji_nagisa@trinity.school', isSuperuser: false }) }) it('create default string body', async () => { const app = new Elysia().post('/', ({ body }) => body, { body: t.String({ default: 'hifumi_daisuki' }) }) const value = await app.handle(post('/')).then((x) => x.text()) expect(value).toBe('hifumi_daisuki') }) it('create default boolean body', async () => { const app = new Elysia().post('/', ({ body }) => typeof body, { body: t.Boolean({ default: true }) }) const value = await app.handle(post('/')).then((x) => x.text()) expect(value).toBe('boolean') }) it('create default number body', async () => { const app = new Elysia().post('/', ({ body }) => typeof body, { body: t.Number({ default: 1 }) }) const value = await app.handle(post('/')).then((x) => x.text()) expect(value).toBe('number') }) it('create default numeric body', async () => { const app = new Elysia().post('/', ({ body }) => typeof body, { body: t.Numeric({ default: 1 }) }) const value = await app.handle(post('/')).then((x) => x.text()) expect(value).toBe('number') }) it('coerce number to numeric', async () => { const app = new Elysia().post('/', ({ body }) => typeof body, { body: t.Number() }) const response = await app.handle( new Request('http://localhost/', { method: 'POST', headers: { 'Content-Type': 'text/plain' }, body: '1' }) ) expect(response.status).toBe(200) }) it("don't coerce number object to numeric", async () => { const app = new Elysia().post('/', ({ body: { id } }) => typeof id, { body: t.Object({ id: t.Number() }) }) const response = await app.handle( post('/', { id: '1' }) ) expect(response.status).toBe(422) }) it('coerce string to boolean', async () => { const app = new Elysia().post('/', ({ body }) => typeof body, { body: t.Boolean() }) const response = await app.handle( new Request('http://localhost/', { method: 'POST', headers: { 'Content-Type': 'text/plain' }, body: 'true' }) ) expect(response.status).toBe(200) }) it("don't coerce string object to boolean", async () => { const app = new Elysia().post('/', ({ body: { id } }) => typeof id, { body: t.Object({ id: t.Boolean() }) }) const response = await app.handle( post('/', { id: 'true' }) ) expect(response.status).toBe(422) }) it('handle optional at root', async () => { const app = new Elysia().post('/', ({ body }) => body, { body: t.Optional( t.Object({ id: t.Numeric() }) ) }) const res = await Promise.all([ app.handle(post('/')).then((x) => x.json()), app .handle( post('/', { id: 1 }) ) .then((x) => x.json()) ]) expect(res).toEqual([{}, { id: 1 }]) }) it('parse query body with array', async () => { const app = new Elysia().post('/', ({ body }) => body) const res = await app.handle( new Request('https://e.ly', { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, body: `tea_party=nagisa&tea_party=mika&tea_party=seia` }) ) expect(await res.json()).toEqual({ tea_party: ['nagisa', 'mika', 'seia'] }) expect(res.status).toBe(200) }) it('validate references', async () => { const job = t.Object( { name: t.String() }, { $id: 'job' } ) const person = t.Object({ name: t.String(), job: t.Ref(job) }) const app = new Elysia() .model({ job, person }) .post('/', ({ body: { name, job } }) => `${name} - ${job.name}`, { body: person }) const res = await app.handle( post('/', { name: 'sucrose', job: { name: 'alchemist' } }) ) expect(await res.text()).toBe('sucrose - alchemist') expect(res.status).toBe(200) }) it('handle file upload', async () => { const app = new Elysia().post( '/single', ({ body: { file } }) => file.size, { body: t.Object({ file: t.File() }) } ) const { request, size } = upload('/single', { file: 'millenium.jpg' }) const response = await app.handle(request).then((r) => r.text()) expect(+response).toBe(size) }) it('handle file upload using model reference', async () => { const app = new Elysia() .model({ a: t.Object({ message: t.String(), image: t.Optional(t.Files()) }) }) .post('/', ({ body }) => 'ok', { body: 'a' }) const { request } = upload('/', { message: 'Hello, world!' }) const status = await app.handle(request).then((r) => r.status) expect(status).toBe(200) }) it('handle file prefix', async () => { const app = new Elysia() .post('/pass1', ({ body: { file } }) => file.size, { body: t.Object({ file: t.File({ type: 'image/*' }) }) }) .post('/pass2', ({ body: { file } }) => file.size, { body: t.Object({ file: t.File({ type: ['application/*', 'image/*'] }) }) }) .post('/fail', ({ body: { file } }) => file.size, { body: t.Object({ file: t.File({ type: 'application/*' }) }) }) { const { request, size } = upload('/pass1', { file: 'millenium.jpg' }) const response = await app.handle(request).then((r) => r.text()) expect(+response).toBe(size) } { const { request, size } = upload('/pass2', { file: 'millenium.jpg' }) const response = await app.handle(request).then((r) => r.text()) // expect(+response).toBe(size) } { const { request } = upload('/fail', { file: 'millenium.jpg' }) const status = await app.handle(request).then((r) => r.status) expect(status).toBe(422) } }) it('handle file type', async () => { const app = new Elysia() .post('/pass1', ({ body: { file } }) => file.size, { body: t.Object({ file: t.File({ type: 'image/jpeg' }) }) }) .post('/pass2', ({ body: { file } }) => file.size, { body: t.Object({ file: t.File({ type: ['image/png', 'image/jpeg'] }) }) }) .post('/fail', ({ body: { file } }) => file.size, { body: t.Object({ file: t.File({ type: 'image/png' }) }) }) { const { request, size } = upload('/pass1', { file: 'millenium.jpg' }) const response = await app.handle(request).then((r) => r.text()) expect(+response).toBe(size) } { const { request, size } = upload('/pass2', { file: 'millenium.jpg' }) const response = await app.handle(request).then((r) => r.text()) expect(+response).toBe(size) } { const { request } = upload('/fail', { file: 'millenium.jpg' }) const status = await app.handle(request).then((r) => r.status) expect(status).toBe(422) } }) it('validate actual file', async () => { const app = new Elysia().post( '/upload', ({ body: { file } }) => file.size, { body: t.Object({ file: t.File({ type: 'image' }) }) } ) { const { request, size } = upload('/upload', { file: 'millenium.jpg' }) const response = await app.handle(request).then((r) => r.text()) expect(+response).toBe(size) } { const { request, size } = upload('/upload', { file: 'fake.jpg' }) const status = await app.handle(request).then((r) => r.status) expect(status).toBe(422) } }) it('validate actual file with multiple type', async () => { const app = new Elysia().post( '/upload', ({ body: { file } }) => file.size, { body: t.Object({ file: t.File({ type: ['image/png', 'image/jpeg'] }) }) } ) { const { request, size } = upload('/upload', { file: 'millenium.jpg' }) const response = await app.handle(request).then((r) => r.text()) expect(+response).toBe(size) } { const { request, size } = upload('/upload', { file: 'fake.jpg' }) const status = await app.handle(request).then((r) => r.status) expect(status).toBe(422) } { const { request, size } = upload('/upload', { file: 'kozeki-ui.webp' }) const status = await app.handle(request).then((r) => r.status) expect(status).toBe(422) } }) it('validate actual file type union', async () => { const app = new Elysia().post('/', ({ body }) => 'ok', { body: t.Union([ t.Object({ hello: t.String(), file: t.File({ type: 'image' }) }), t.Object({ world: t.String(), image: t.File({ type: 'image' }) }), t.Object({ donQuixote: t.String() }) ]) }) // case 1 pass { const { request, size } = upload('/', { hello: 'ok', file: 'millenium.jpg' }) const status = await app.handle(request).then((r) => r.status) expect(status).toBe(200) } // case 1 fail { const { request, size } = upload('/', { hello: 'ok', file: 'fake.jpg' }) const status = await app.handle(request).then((r) => r.status) expect(status).toBe(422) } // case 2 pass { const { request, size } = upload('/', { world: 'ok', image: 'millenium.jpg' }) const status = await app.handle(request).then((r) => r.status) expect(status).toBe(200) } // case 2 fail { const { request, size } = upload('/', { world: 'ok', image: 'fake.jpg' }) const status = await app.handle(request).then((r) => r.status) expect(status).toBe(422) } // case 3 fail { const { request, size } = upload('/', { donQuixote: 'Limbus Company!' }) const status = await app.handle(request).then((r) => r.status) expect(status).toBe(200) } }) it('validate actual file type union with multiple file type', async () => { const app = new Elysia().post('/', ({ body }) => 'ok', { body: t.Union([ t.Object({ hello: t.String(), file: t.File({ type: 'image' }) }), t.Object({ world: t.String(), image: t.File({ type: ['image/png', 'image/jpeg'] }) }), t.Object({ donQuixote: t.String() }) ]) }) // case 1 pass { const { request, size } = upload('/', { hello: 'ok', file: 'millenium.jpg' }) const status = await app.handle(request).then((r) => r.status) expect(status).toBe(200) } // case 1 fail { const { request, size } = upload('/', { hello: 'ok', file: 'fake.jpg' }) const status = await app.handle(request).then((r) => r.status) expect(status).toBe(422) } // case 2 pass { const { request, size } = upload('/', { world: 'ok', image: 'millenium.jpg' }) const status = await app.handle(request).then((r) => r.status) expect(status).toBe(200) } // case 2 fail by fake image { const { request, size } = upload('/', { world: 'ok', image: 'fake.jpg' }) const status = await app.handle(request).then((r) => r.status) expect(status).toBe(422) } // case 2 fail by incorrect image type { const { request, size } = upload('/', { world: 'ok', image: 'kozeki-ui.webp' }) const status = await app.handle(request).then((r) => r.status) expect(status).toBe(422) } // case 3 fail { const { request, size } = upload('/', { donQuixote: 'Limbus Company!' }) const status = await app.handle(request).then((r) => r.status) expect(status).toBe(200) } }) it('validate actual files', async () => { const app = new Elysia().post('/', () => 'ok', { body: t.Object({ file: t.Files({ type: 'image' }) }) }) // case 1 fail: contains fake image { const body = new FormData() body.append('file', Bun.file('test/images/fake.jpg')) body.append('file', Bun.file('test/images/kozeki-ui.webp')) const response = await app.handle( new Request('http://localhost/', { method: 'POST', body }) ) expect(response.status).toBe(422) } // case 2 pass: all valid images { const body = new FormData() body.append('file', Bun.file('test/images/millenium.jpg')) body.append('file', Bun.file('test/images/kozeki-ui.webp')) const response = await app.handle( new Request('http://localhost/', { method: 'POST', body }) ) expect(response.status).toBe(200) } }) it('handle body using Transform with Intersect ', async () => { const app = new Elysia().post('/test', ({ body }) => body, { body: t.Intersect([ t.Object({ foo: t.String() }), t.Object({ field: t .Transform(t.String()) .Decode((decoded) => ({ decoded })) .Encode((v) => v.decoded) }) ]) }) const response = await app .handle( new Request('http://localhost/test', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ field: 'bar', foo: 'test' }) }) ) .then((x) => x.json()) expect(response).toEqual({ field: { decoded: 'bar' }, foo: 'test' }) }) it('right rejects missed field with model', async () => { const model = new Elysia().model( 'user', t.Object({ username: t.String(), age: t.Integer() }) ) const app = new Elysia().use(model).post('/', ({ body }) => body, { body: 'user' }) const res = await app.handle( post('/', { name: 'sucrose' }) ) expect(res.status).toBe(422) }) it('handle coerce TransformDecodeError', async () => { let err: Error | undefined const app = new Elysia() .post('/', ({ body }) => body, { body: t.Object({ year: t.Numeric({ minimum: 1900, maximum: 2160 }) }), error({ code, error }) { switch (code) { case 'VALIDATION': err = error } } }) .listen(0) await app.handle( post('/', { year: '3000' }) ) expect(err instanceof ValidationError).toBe(true) }) it('handle nested file upload with dot notation', async () => { const app = new Elysia().post( '/', ({ body }) => ({ userName: body.user.name, fileSize: body.user.avatar.size }), { body: t.Object({ user: t.Object({ name: t.String(), avatar: t.File() }) }) } ) const formData = new FormData() formData.append('user.name', 'John') formData.append('user.avatar', Bun.file('test/images/millenium.jpg')) const response = await app.handle( new Request('http://localhost/', { method: 'POST', body: formData }) ) const result = await response.json() expect(response.status).toBe(200) expect(result).toMatchObject({ userName: 'John', fileSize: expect.any(Number) }) }) it('handle nested files upload with dot notation', async () => { const app = new Elysia().post( '/', ({ body }) => ({ productName: body.product.name, fileSizes: body.product.images.map((f) => f.size) }), { body: t.Object({ product: t.Object({ name: t.String(), images: t.Files() }) }) } ) const formData = new FormData() formData.append('product.name', 'Chair') formData.append('product.images', Bun.file('test/images/millenium.jpg')) formData.append('product.images', Bun.file('test/images/aris-yuzu.jpg')) const response = await app.handle( new Request('http://localhost/', { method: 'POST', body: formData }) ) const result = await response.json() expect(response.status).toBe(200) expect(result).toMatchObject({ productName: 'Chair', fileSizes: expect.arrayContaining([ expect.any(Number), expect.any(Number) ]) }) }) it('handle deeply nested file upload', async () => { const app = new Elysia().post( '/', ({ body }) => ({ bio: body.user.profile.bio, country: body.user.profile.country, photoSize: body.user.profile.photo.size }), { body: t.Object({ user: t.Object({ profile: t.Object({ bio: t.String(), country: t.String(), photo: t.File() }) }) }) } ) const formData = new FormData() formData.append('user.profile.bio', 'Hello World') formData.append('user.profile.country', 'France') formData.append( 'user.profile.photo', Bun.file('test/images/millenium.jpg') ) const response = await app.handle( new Request('http://localhost/', { method: 'POST', body: formData }) ) const result = await response.json() expect(response.status).toBe(200) expect(result).toMatchObject({ bio: 'Hello World', country: 'France', photoSize: expect.any(Number) }) }) it('handle multiple nested files', async () => { const app = new Elysia().post( '/', ({ body }) => ({ avatarSize: body.user.avatar.size, coverSize: body.user.cover.size }), { body: t.Object({ user: t.Object({ avatar: t.File(), cover: t.File() }) }) } ) const formData = new FormData() formData.append('user.avatar', Bun.file('test/images/millenium.jpg')) formData.append('user.cover', Bun.file('test/images/kozeki-ui.webp')) const response = await app.handle( new Request('http://localhost/', { method: 'POST', body: formData }) ) const result = await response.json() expect(response.status).toBe(200) expect(result).toMatchObject({ avatarSize: expect.any(Number), coverSize: expect.any(Number) }) }) it('handle mixed nested and flat fields', async () => { const app = new Elysia().post( '/', ({ body }) => ({ flatValue: body.flat, nestedName: body.user.name, nestedFileSize: body.user.avatar.size }), { body: t.Object({ flat: t.String(), user: t.Object({ name: t.String(), avatar: t.File() }) }) } ) const formData = new FormData() formData.append('flat', 'I am flat') formData.append('user.name', 'Jane') formData.append('user.avatar', Bun.file('test/images/millenium.jpg')) const response = await app.handle( new Request('http://localhost/', { method: 'POST', body: formData }) ) const result = await response.json() expect(response.status).toBe(200) expect(result).toMatchObject({ flatValue: 'I am flat', nestedName: 'Jane', nestedFileSize: expect.any(Number) }) }) it('handle complex nested array with files', async () => { const app = new Elysia().post( '/', ({ body }) => ({ productName: body.name, createFilesCount: body.images.create.length, updateCount: body.images.update.length, images: { create: body.images.create.map((f) => f.size), update: body.images.update.map((f) => ({ id: f.id, altText: f.altText, imgSize: f.img.size })) } }), { body: t.Object({ name: t.String(), images: t.Object({ create: t.Files(), update: t.Array( t.Object({ id: t.String(), img: t.File(), altText: t.String() }) ) }) }) } ) const formData = new FormData() formData.append('name', 'Test Product') formData.append('images.create', Bun.file('test/images/millenium.jpg')) formData.append('images.create', Bun.file('test/images/kozeki-ui.webp')) formData.append('images.update[0].id', '123') formData.append( 'images.update[0].img', Bun.file('test/images/midori.png') ) formData.append('images.update[0].altText', 'an image') const response = await app.handle( new Request('http://localhost/', { method: 'POST', body: formData }) ) const result = await response.json() expect(response.status).toBe(200) expect(result).toMatchObject({ productName: 'Test Product', createFilesCount: 2, updateCount: 1, images: { create: [expect.any(Number), expect.any(Number)], update: [ { id: '123', altText: 'an image', imgSize: expect.any(Number) } ] } }) }) it('handle dot notation for standard schema with array and nested file', async () => { const { z } = await import('zod') const app = new Elysia().post( '/', ({ body }) => ({ updateCount: body.images.update.length, updates: body.images.update.map((item) => ({ id: item.id, altText: item.altText, imgSize: item.img.size })) }), { body: z.object({ images: z.object({ update: z.array( z.object({ id: z.string(), img: z.file(), altText: z.string() }) ) }) }) } ) const formData = new FormData() formData.append( 'images.update[0]', JSON.stringify({ id: '123', altText: 'an image' }) ) formData.append( 'images.update[0].img', Bun.file('test/images/midori.png') ) const response = await app.handle( new Request('http://localhost/', { method: 'POST', body: formData }) ) const result = await response.json() expect(response.status).toBe(200) expect(result).toMatchObject({ updateCount: 1, updates: [ { id: '123', altText: 'an image', imgSize: expect.any(Number) } ] }) }) it('handle mix of stringify and dot notation', async () => { const app = new Elysia().post( '/', ({ body }) => ({ productName: body.name, metadata: body.metadata, createFilesCount: body.images.create.length, updateCount: body.images.update.length, images: { create: body.images.create.map((f) => f.size), update: body.images.update.map((f) => ({ id: f.id, altText: f.altText, imgSize: f.img.size })) } }), { body: t.Object({ name: t.String(), metadata: t.Object({ description: t.String(), price: t.Number(), inStock: t.Boolean(), tags: t.Array(t.String()), category: t.String() }), images: t.Object({ create: t.Files(), update: t.Array( t.Object({ id: t.String(), img: t.File(), altText: t.String() }) ) }) }) } ) const formData = new FormData() formData.append('name', 'Test Product') formData.append( 'metadata', JSON.stringify({ description: 'A high-quality product', price: 29.99, inStock: true, tags: ['electronics', 'featured', 'sale'], category: 'gadgets' }) ) formData.append('images.create', Bun.file('test/images/millenium.jpg')) formData.append('images.create', Bun.file('test/images/kozeki-ui.webp')) formData.append( 'images.update[0]', JSON.stringify({ id: '123', altText: 'an image' }) ) formData.append( 'images.update[0].img', Bun.file('test/images/midori.png') ) const response = await app.handle( new Request('http://localhost/', { method: 'POST', body: formData }) ) const result = await response.json() expect(response.status).toBe(200) expect(result).toMatchObject({ productName: 'Test Product', createFilesCount: 2, updateCount: 1, metadata: { description: 'A high-quality product', price: 29.99, inStock: true, tags: ['electronics', 'featured', 'sale'], category: 'gadgets' }, images: { create: [expect.any(Number), expect.any(Number)], update: [ { id: '123', altText: 'an image', imgSize: expect.any(Number) } ] } }) }) it('should parse sub-array correctly', async () => { const app = new Elysia().post('/', ({ body }) => body, { body: t.Object({ imagesOps: t.Object({ options: t.Array( t.Object({ id: t.String(), value: t.String() }) ) }) }) }) const formData = new FormData() formData.append( 'imagesOps.options', JSON.stringify([{ id: 'test-id', value: 'test-value' }]) ) const response = await app.handle( new Request('http://localhost/', { method: 'POST', body: formData }) ) expect(response.status).toBe(200) const result = (await response.json()) as any expect(result.imagesOps.options).toEqual([ { id: 'test-id', value: 'test-value' } ]) }) it('prevent prototype pollution with __proto__ in nested multipart', async () => { const app = new Elysia().post('/', ({ body }) => body) const formData = new FormData() formData.append('user.name', 'John') formData.append('__proto__.isAdmin', 'true') formData.append('user.__proto__.isAdmin', 'true') const response = await app.handle( new Request('http://localhost/', { method: 'POST', body: formData }) ) const result = await response.json() expect(response.status).toBe(200) expect(result).toMatchObject({ user: { name: 'John' } }) // Check that Object.prototype wasn't polluted const testObj = {} expect('isAdmin' in testObj).toBe(false) expect('isAdmin' in {}).toBe(false) }) it('prevent prototype pollution with constructor in nested multipart', async () => { const app = new Elysia().post('/', ({ body }) => body) const formData = new FormData() formData.append('user.name', 'John') formData.append('constructor.prototype.isAdmin', 'true') formData.append('user.constructor', 'bad') const response = await app.handle( new Request('http://localhost/', { method: 'POST', body: formData }) ) const result = await response.json() expect(response.status).toBe(200) expect(result).toMatchObject({ user: { name: 'John' } }) // Check that Object.prototype wasn't polluted expect('isAdmin' in {}).toBe(false) }) it('prevent prototype pollution in array notation', async () => { const app = new Elysia().post('/', ({ body }) => body) const formData = new FormData() formData.append('items[0].name', 'Item 1') formData.append('items[__proto__].isAdmin', 'true') formData.append('__proto__[0]', 'bad') const response = await app.handle( new Request('http://localhost/', { method: 'POST', body: formData }) ) const result = await response.json() expect(response.status).toBe(200) expect(result).toMatchObject({ items: [{ name: 'Item 1' }] }) // Check that Object.prototype wasn't polluted const testObj = {} expect('isAdmin' in testObj).toBe(false) }) }) ================================================ FILE: test/validator/cookie.test.ts ================================================ import { describe, expect, it } from 'bun:test' import { Elysia, t } from '../../src' import { req } from '../utils' describe('Cookie Validation', () => { it('validate required cookie', async () => { const app = new Elysia().get( '/', ({ cookie: { session } }) => session.value, { cookie: t.Cookie({ session: t.String() }) } ) const [valid, invalid] = await Promise.all([ app.handle(req('/', { headers: { Cookie: 'session=value' } })), app.handle(req('/')) ]) expect(valid.status).toBe(200) expect(await valid.text()).toBe('value') expect(invalid.status).toBe(422) }) it('validate optional cookie', async () => { const app = new Elysia().get( '/', ({ cookie: { session } }) => session.value ?? 'empty', { cookie: t.Cookie({ session: t.Optional(t.String()) }) } ) const [withCookie, withoutCookie] = await Promise.all([ app.handle(req('/', { headers: { Cookie: 'session=value' } })), app.handle(req('/')) ]) expect(withCookie.status).toBe(200) expect(await withCookie.text()).toBe('value') expect(withoutCookie.status).toBe(200) expect(await withoutCookie.text()).toBe('empty') }) it('validate cookie type - numeric', async () => { const app = new Elysia().get( '/', ({ cookie: { count } }) => count.value, { cookie: t.Cookie({ count: t.Numeric() }) } ) const [valid, invalid] = await Promise.all([ app.handle(req('/', { headers: { Cookie: 'count=42' } })), app.handle(req('/', { headers: { Cookie: 'count=invalid' } })) ]) expect(valid.status).toBe(200) expect(await valid.text()).toBe('42') expect(invalid.status).toBe(422) }) it('validate cookie type - boolean', async () => { const app = new Elysia().get( '/', ({ cookie: { active } }) => active.value, { cookie: t.Cookie({ active: t.BooleanString() }) } ) const [validTrue, validFalse, invalid] = await Promise.all([ app.handle(req('/', { headers: { Cookie: 'active=true' } })), app.handle(req('/', { headers: { Cookie: 'active=false' } })), app.handle(req('/', { headers: { Cookie: 'active=maybe' } })) ]) expect(validTrue.status).toBe(200) expect(await validTrue.text()).toBe('true') expect(validFalse.status).toBe(200) expect(await validFalse.text()).toBe('false') expect(invalid.status).toBe(422) }) it('validate cookie with object schema', async () => { const app = new Elysia().get( '/', ({ cookie: { profile } }) => profile.value.name, { cookie: t.Cookie({ profile: t.Object({ name: t.String(), age: t.Numeric() }) }) } ) const valid = await app.handle( req('/', { headers: { Cookie: 'profile=' + encodeURIComponent( JSON.stringify({ name: 'Himari', age: 16 }) ) } }) ) const invalid = await app.handle( req('/', { headers: { Cookie: 'profile=' + encodeURIComponent(JSON.stringify({ name: 'Himari' })) } }) ) expect(valid.status).toBe(200) expect(await valid.text()).toBe('Himari') expect(invalid.status).toBe(422) }) it('validate multiple cookies', async () => { const app = new Elysia().get( '/', ({ cookie: { session, userId } }) => `${session.value}:${userId.value}`, { cookie: t.Cookie({ session: t.String(), userId: t.Numeric() }) } ) const [valid, missingSession, missingUserId, invalidUserId] = await Promise.all([ app.handle( req('/', { headers: { Cookie: 'session=abc123; userId=42' } }) ), app.handle( req('/', { headers: { Cookie: 'userId=42' } }) ), app.handle( req('/', { headers: { Cookie: 'session=abc123' } }) ), app.handle( req('/', { headers: { Cookie: 'session=abc123; userId=invalid' } }) ) ]) expect(valid.status).toBe(200) expect(await valid.text()).toBe('abc123:42') expect(missingSession.status).toBe(422) expect(missingUserId.status).toBe(422) expect(invalidUserId.status).toBe(422) }) it('validate cookie with string constraints', async () => { const app = new Elysia().get( '/', ({ cookie: { token } }) => token.value, { cookie: t.Cookie({ token: t.String({ minLength: 10, maxLength: 50 }) }) } ) const [valid, tooShort, tooLong] = await Promise.all([ app.handle( req('/', { headers: { Cookie: 'token=validtoken123' } }) ), app.handle( req('/', { headers: { Cookie: 'token=short' } }) ), app.handle( req('/', { headers: { Cookie: 'token=' + 'a'.repeat(51) } }) ) ]) expect(valid.status).toBe(200) expect(tooShort.status).toBe(422) expect(tooLong.status).toBe(422) }) it('validate cookie with numeric constraints', async () => { const app = new Elysia().get('/', ({ cookie: { age } }) => age.value, { cookie: t.Cookie({ age: t.Numeric({ minimum: 0, maximum: 120 }) }) }) const [valid, tooLow, tooHigh] = await Promise.all([ app.handle( req('/', { headers: { Cookie: 'age=25' } }) ), app.handle( req('/', { headers: { Cookie: 'age=-1' } }) ), app.handle( req('/', { headers: { Cookie: 'age=150' } }) ) ]) expect(valid.status).toBe(200) expect(await valid.text()).toBe('25') expect(tooLow.status).toBe(422) expect(tooHigh.status).toBe(422) }) it('validate cookie with pattern', async () => { const app = new Elysia().get( '/', ({ cookie: { email } }) => email.value, { cookie: t.Cookie({ email: t.String({ format: 'email' }) }) } ) const [valid, invalid] = await Promise.all([ app.handle( req('/', { headers: { Cookie: 'email=user@example.com' } }) ), app.handle( req('/', { headers: { Cookie: 'email=notanemail' } }) ) ]) expect(valid.status).toBe(200) expect(await valid.text()).toBe('user@example.com') expect(invalid.status).toBe(422) }) it('validate cookie with transform', async () => { const app = new Elysia().get( '/', ({ cookie: { timestamp } }) => timestamp.value, { cookie: t.Cookie({ timestamp: t .Transform(t.String()) .Decode((value) => new Date(value)) .Encode((value) => value.toISOString()) }) } ) const date = new Date('2024-01-01T00:00:00.000Z') const response = await app.handle( req('/', { headers: { Cookie: `timestamp=${date.toISOString()}` } }) ) expect(response.status).toBe(200) }) it('validate optional cookie with isOptional check', async () => { const app = new Elysia().get( '/', ({ cookie }) => { const keys = Object.keys(cookie) return keys.length > 0 ? 'has cookies' : 'no cookies' }, { cookie: t.Optional( t.Cookie({ session: t.Optional(t.String()) }) ) } ) const [withCookie, withoutCookie] = await Promise.all([ app.handle( req('/', { headers: { Cookie: 'session=value' } }) ), app.handle(req('/')) ]) expect(withCookie.status).toBe(200) expect(withoutCookie.status).toBe(200) }) it('validate cookie with array type', async () => { const app = new Elysia().get( '/', ({ cookie: { tags } }) => tags.value.join(','), { cookie: t.Cookie({ tags: t.Array(t.String()) }) } ) const response = await app.handle( req('/', { headers: { Cookie: 'tags=' + encodeURIComponent( JSON.stringify(['tag1', 'tag2', 'tag3']) ) } }) ) expect(response.status).toBe(200) expect(await response.text()).toBe('tag1,tag2,tag3') }) it('validate cookie with union type', async () => { const app = new Elysia().get( '/', ({ cookie: { value } }) => String(value.value), { cookie: t.Cookie({ value: t.Union([t.String(), t.Numeric()]) }) } ) const [stringValue, numericValue, invalid] = await Promise.all([ app.handle( req('/', { headers: { Cookie: 'value=text' } }) ), app.handle( req('/', { headers: { Cookie: 'value=123' } }) ), app.handle( req('/', { headers: { Cookie: 'value=' + encodeURIComponent(JSON.stringify({ obj: true })) } }) ) ]) expect(stringValue.status).toBe(200) expect(numericValue.status).toBe(200) expect(invalid.status).toBe(422) }) it('inherits cookie validation on guard', async () => { const app = new Elysia() .guard({ cookie: t.Cookie({ session: t.String() }) }) .get('/', ({ cookie: { session } }) => session.value) .get( '/profile', ({ cookie: { session } }) => `Profile: ${session.value}` ) const [validRoot, validProfile, invalid] = await Promise.all([ app.handle( req('/', { headers: { Cookie: 'session=abc123' } }) ), app.handle( req('/profile', { headers: { Cookie: 'session=abc123' } }) ), app.handle(req('/')) ]) expect(validRoot.status).toBe(200) expect(await validRoot.text()).toBe('abc123') expect(validProfile.status).toBe(200) expect(await validProfile.text()).toBe('Profile: abc123') expect(invalid.status).toBe(422) }) it('merge cookie config from app', async () => { const app = new Elysia({ cookie: { httpOnly: true, secure: true } }).get('/', ({ cookie: { session } }) => session.value ?? 'empty', { cookie: t.Cookie({ session: t.Optional(t.String()) }) }) const response = await app.handle( req('/', { headers: { Cookie: 'session=test' } }) ) expect(response.status).toBe(200) expect(await response.text()).toBe('test') }) it('validate empty cookie object when optional', async () => { const app = new Elysia().get( '/', ({ cookie }) => Object.keys(cookie).length === 0 ? 'empty' : 'not empty', { cookie: t.Optional( t.Cookie({ session: t.Optional(t.String()) }) ) } ) const response = await app.handle(req('/')) expect(response.status).toBe(200) expect(await response.text()).toBe('empty') }) it('expires setter compares timestamps not Date objects', async () => { const app = new Elysia().get('/', ({ cookie: { session }, set }) => { // Test 1: Setting expires with same timestamp should not update const date1 = new Date('2025-12-31T23:59:59.000Z') const date2 = new Date('2025-12-31T23:59:59.000Z') session.value = 'test' session.expires = date1 // Get reference to jar before setting with same timestamp const jarBefore = set.cookie session.expires = date2 // Same timestamp, should not update const jarAfter = set.cookie // Verify jar wasn't recreated (same reference) expect(jarBefore).toBe(jarAfter) expect(session.expires?.getTime()).toBe(date1.getTime()) // Test 2: Setting expires with different timestamp should update const date3 = new Date('2026-01-01T00:00:00.000Z') session.expires = date3 expect(session.expires?.getTime()).toBe(date3.getTime()) // Test 3: Both undefined should not update session.expires = undefined const jarBeforeUndefined = set.cookie session.expires = undefined const jarAfterUndefined = set.cookie expect(jarBeforeUndefined).toBe(jarAfterUndefined) return 'ok' }) const response = await app.handle(req('/')) expect(response.status).toBe(200) expect(await response.text()).toBe('ok') }) it('parse cookie with secrets into object when available', async () => { const challengeModel = t.Object({ nonce: t.String(), issued: t.Number(), bits: t.Number() }) const issued = Date.now() const app = new Elysia({ cookie: { secrets: 'a', sign: ['challenge'] } }) .get( '/set', ({ cookie: { challenge } }) => { challenge.value = { nonce: 'hello', bits: 19, issued } }, { cookie: t.Cookie({ challenge: t.Optional(challengeModel) }) } ) .get( '/get', ({ cookie: { challenge } }) => { return { type: typeof challenge, value: challenge.value } }, { cookie: t.Cookie({ challenge: challengeModel }) } ) const cookie = await app .handle(req('/set')) .then((x) => x.headers.get('set-cookie')) const challenge = cookie!.match(/challenge=([^;]*)/)![1] const response = await app .handle( req('/get', { headers: { cookie: `challenge=${challenge}` } }) ) .then((x) => x.json()) expect(response).toEqual({ type: 'object', value: { nonce: 'hello', bits: 19, issued } }) }) it('handle graceful cookie transition from non signed to signed', async () => { const challengeModel = t.Object({ nonce: t.String(), issued: t.Number(), bits: t.Number() }) const issued = Date.now() const app = new Elysia({ cookie: { secrets: ['a', null], sign: 'challenge' } }).get( '/', ({ cookie: { challenge } }) => { challenge.value = { nonce: 'hello', bits: 19, issued } return challenge.value }, { cookie: t.Cookie({ challenge: t.Optional(challengeModel) }) } ) const first = await app.handle( req('/', { headers: { cookie: `challenge=${JSON.stringify({ nonce: 'hello', bits: 19, issued: 1770750432990 })}` } }) ) expect(first.status).toBe(200) expect(await first.json()).toEqual({ nonce: 'hello', bits: 19, issued }) const cookie = first.headers.get('set-cookie') const challenge = cookie!.match(/challenge=([^;]*)/)![1] // contains signature expect(challenge).toInclude('.') const second = await app.handle( req('/', { headers: { cookie: `challenge=${challenge}` } }) ) expect(second.status).toBe(200) }) it('handle prototype pollution', () => { const app = new Elysia().get('/profile', ({ cookie }) => { const proto = Object.getPrototypeOf(cookie) const protoIsClean = proto === Object.prototype || proto === null return { hasPhantomValue: 'value' in cookie, prototypeIsClean: protoIsClean, prototype: proto, enumeratedKeys: (() => { const keys: string[] = [] for (const k in cookie) keys.push(k) return keys })() } }) expect( app .handle( new Request('http://localhost/profile', { headers: { cookie: 'a=hi;__proto__=%7B%22injected%22%3A%22polluted%22%7D' } }) ) .then((x) => x.json()) ).resolves.toEqual({ hasPhantomValue: false, prototypeIsClean: true, prototype: null, enumeratedKeys: ['a'] }) }) it('transform cookie value', async () => { let innerValue: any = null const app = new Elysia().get( '/', ({ cookie: { thing } }) => { innerValue = thing.value return thing.value }, { cookie: t.Object({ thing: t.Number() }) } ) const value = await app .handle( new Request('http://localhost:3000/', { headers: { cookie: 'thing=9' } }) ) .then((response) => response.json()) expect(value).toBe(9) expect(typeof value).toBe('number') expect(innerValue).toBe(9) expect(typeof innerValue).toBe('number') }) }) ================================================ FILE: test/validator/encode.test.ts ================================================ import { Elysia, t } from '../../src' import { describe, expect, it } from 'bun:test' import { req } from '../utils' describe('Encode response', () => { it('handle default status', async () => { const app = new Elysia({ encodeSchema: true }).get( '/', () => ({ id: 'hello world' }), { response: t.Object({ id: t .Transform(t.String()) .Decode((v) => v) .Encode(() => 'encoded') }) } ) const response = await app.handle(req('/')).then((x) => x.json()) expect(response).toEqual({ id: 'encoded' }) }) it('handle default named status', async () => { const app = new Elysia({ encodeSchema: true }).get( '/:id', ({ status, params: { id } }) => status(id as any, { id: 'hello world' }), { params: t.Object({ id: t.Number() }), response: { 200: t.Object({ id: t .Transform(t.String()) .Decode((v) => v) .Encode(() => 'encoded 200') }), 418: t.Object({ id: t .Transform(t.String()) .Decode((v) => v) .Encode(() => 'encoded 418') }) } } ) const response = await Promise.all([ app.handle(req('/200')).then((x) => x.json()), app.handle(req('/418')).then((x) => x.json()) ]) expect(response[0]).toEqual({ id: 'encoded 200' }) expect(response[1]).toEqual({ id: 'encoded 418' }) }) it('Encode before type check', async () => { const dto = t.Object({ value: t .Transform(t.String()) .Decode((value) => parseFloat(value)) .Encode((value) => value.toString()) }) let bodyType = '' const elysia = new Elysia({ experimental: { encodeSchema: true //open the flag! } }).post( '/', ({ body }) => { bodyType = typeof body.value return body }, { body: dto, response: dto } ) const response = await elysia .handle( new Request('http://localhost:3000/', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ value: '1.1' }) }) ) .then((res) => res) expect(bodyType).toBe('number') expect(response.status).toBe(200) expect(await response.json()).toEqual({ value: '1.1' }) }) }) ================================================ FILE: test/validator/exact-mirror.test.ts ================================================ import { Elysia, t } from '../../src' import { describe, expect, it } from 'bun:test' import { post, req } from '../utils' describe('Exact Mirror', () => { it('normalize when t.Transform is provided', async () => { const app = new Elysia({ normalize: 'exactMirror' }).get('/', () => ({ count: 2, name: 'foo', extra: 1 }), { response: t.Object( { name: t.String(), count: t.Optional(t.Integer()) }, { additionalProperties: false } ) }) }) it('leave incorrect union field as-is', async () => { const app = new Elysia().post( '/test', ({ body }) => { console.log({ body }) return 'Hello Elysia' }, { body: t.Object({ foo: t.Optional( t.Nullable( t.Number({ // 'foo' but be either number, optional or nullable error: 'Must be a number' }) ) ) }) } ) const response = await app.handle( post('/test', { foo: 'asd' }) ) expect(response.status).toEqual(422) }) it('normalize array response', async () => { const app = new Elysia().get( '/', () => { return { messages: [ { message: 'Hello, world!', shouldBeRemoved: true } ] } }, { response: { 200: t.Object({ messages: t.Array( t.Object({ message: t.String() }) ) }) } } ) const response = await app.handle(req('/')).then((x) => x.json()) expect(response).toEqual({ messages: [{ message: 'Hello, world!' }] }) }) it('normalize t.Array with t.Omit(t.Union) elements', async () => { const SharedSchemaA = t.Object({ qux: t.Literal('a') }) const SharedSchemaB = t.Object({ qux: t.Literal('b') }) const SchemaA = t.Object({ foo: t.Number() }) const SchemaB = t.Object({ foo: t.Number(), baz: t.Boolean() }) const IntersectSchemaA = t.Intersect([SchemaA, SharedSchemaA]) const IntersectSchemaB = t.Intersect([SchemaB, SharedSchemaB]) const UnionSchema = t.Union([IntersectSchemaA, IntersectSchemaB]) const OmittedUnionSchema = t.Omit(UnionSchema, ['baz']) const app = new Elysia().get( '/', // @ts-ignore () => [{ bar: 'asd', baz: true, qux: 'b', foo: 1 }], { response: t.Array(OmittedUnionSchema) } ) const response = await app.handle(req('/')).then((x) => x.json()) expect(response).toEqual([{ qux: 'b', foo: 1 }]) }) it('normalize t.Omit(t.Union) response', async () => { const SchemaA = t.Object({ foo: t.Number() }) const SchemaB = t.Object({ foo: t.Number(), baz: t.Boolean() }) const UnionSchema = t.Union([SchemaA, SchemaB]) const OmittedUnionSchema = t.Omit(UnionSchema, ['baz']) const app = new Elysia().get('/', () => ({ baz: true, foo: 1 }), { response: OmittedUnionSchema }) const response = await app.handle(req('/')) expect(response.status).toBe(200) expect(await response.json()).toEqual({ foo: 1 }) }) it('normalize t.Omit(t.Union) with multiple status codes', async () => { const SchemaA = t.Object({ foo: t.Number() }) const SchemaB = t.Object({ foo: t.Number(), baz: t.Boolean() }) const UnionSchema = t.Union([SchemaA, SchemaB]) const OmittedUnionSchema = t.Omit(UnionSchema, ['baz']) const app = new Elysia().get('/', () => ({ baz: true, foo: 1 }), { response: { 200: OmittedUnionSchema } }) const response = await app.handle(req('/')) expect(response.status).toBe(200) expect(await response.json()).toEqual({ foo: 1 }) }) }) ================================================ FILE: test/validator/header.test.ts ================================================ import { Elysia, t, ValidationError } from '../../src' import { describe, expect, it } from 'bun:test' import { req } from '../utils' describe('Header Validator', () => { it('validate single', async () => { const app = new Elysia().get('/', ({ headers: { name } }) => name, { headers: t.Object({ name: t.String() }) }) const res = await app.handle( req('/', { headers: { name: 'sucrose' } }) ) expect(await res.text()).toBe('sucrose') expect(res.status).toBe(200) }) it('validate multiple', async () => { const app = new Elysia().get('/', ({ headers }) => headers, { headers: t.Object({ name: t.String(), job: t.String(), trait: t.String() }) }) const res = await app.handle( req('/', { headers: { name: 'sucrose', job: 'alchemist', trait: 'dog' } }) ) expect(await res.json()).toEqual({ name: 'sucrose', job: 'alchemist', trait: 'dog' }) expect(res.status).toBe(200) }) it('parse without reference', async () => { const app = new Elysia().get('/', () => '', { headers: t.Object({ name: t.String(), job: t.String(), trait: t.String() }) }) const res = await app.handle( req('/', { headers: { name: 'sucrose', job: 'alchemist', trait: 'dog' } }) ) expect(res.status).toBe(200) }) it('validate optional', async () => { const app = new Elysia().get('/', ({ headers }) => headers, { headers: t.Object({ name: t.String(), job: t.String(), trait: t.Optional(t.String()) }) }) const res = await app.handle( req('/', { headers: { name: 'sucrose', job: 'alchemist' } }) ) expect(await res.json()).toEqual({ name: 'sucrose', job: 'alchemist' }) expect(res.status).toBe(200) }) it('parse single numeric', async () => { const app = new Elysia().get('/', ({ headers }) => headers, { headers: t.Object({ name: t.String(), job: t.String(), trait: t.Optional(t.String()), age: t.Numeric() }) }) const res = await app.handle( req('/', { headers: { name: 'sucrose', job: 'alchemist', age: '16' } }) ) expect(await res.json()).toEqual({ name: 'sucrose', job: 'alchemist', age: 16 }) expect(res.status).toBe(200) }) it('parse multiple numeric', async () => { const app = new Elysia().get('/', ({ headers }) => headers, { headers: t.Object({ name: t.String(), job: t.String(), trait: t.Optional(t.String()), age: t.Numeric(), rank: t.Numeric() }) }) const res = await app.handle( req('/', { headers: { name: 'sucrose', job: 'alchemist', age: '16', rank: '4' } }) ) expect(await res.json()).toEqual({ name: 'sucrose', job: 'alchemist', age: 16, rank: 4 }) expect(res.status).toBe(200) }) it('parse single integer', async () => { const app = new Elysia().get('/', ({ headers }) => headers, { headers: t.Object({ limit: t.Integer() }) }) const res = await app.handle( req('/', { headers: { limit: '16' } }) ) expect(await res.json()).toEqual({ limit: 16 }) expect(res.status).toBe(200) }) it('parse multiple integers', async () => { const app = new Elysia().get('/', ({ headers }) => headers, { headers: t.Object({ limit: t.Integer(), offset: t.Integer() }) }) const res = await app.handle( req('/', { headers: { limit: '16', offset: '4' } }) ) expect(await res.json()).toEqual({ limit: 16, offset: 4 }) expect(res.status).toBe(200) }) it('validate partial', async () => { const app = new Elysia().get('/', ({ headers }) => headers, { headers: t.Partial( t.Object({ name: t.String(), job: t.String(), trait: t.Optional(t.String()) }) ) }) const res = await app.handle(req('/')) expect(res.status).toBe(200) expect(await res.json()).toEqual({}) }) it('validate numeric with partial', async () => { const app = new Elysia().get('/', ({ headers }) => headers, { headers: t.Partial( t.Object({ name: t.String(), job: t.String(), trait: t.Optional(t.String()), age: t.Numeric(), rank: t.Numeric() }) ) }) const res = await app.handle(req('/')) expect(res.status).toBe(200) expect(await res.json()).toEqual({}) }) it('validate optional object', async () => { const app = new Elysia().get( '/', ({ headers }) => headers?.name ?? 'sucrose', { headers: t.Object( { name: t.Optional(t.String()) }, { additionalProperties: true } ) } ) const [valid, invalid] = await Promise.all([ app.handle( req('/', { headers: { name: 'sucrose' } }) ), app.handle(req('/')) ]) expect(await valid.text()).toBe('sucrose') expect(valid.status).toBe(200) expect(await invalid.text()).toBe('sucrose') expect(invalid.status).toBe(200) }) it('create default string params', async () => { const app = new Elysia().get('/', ({ headers }) => headers, { headers: t.Object({ name: t.String(), faction: t.String({ default: 'tea_party' }) }) }) const value = await app .handle( req('/', { headers: { name: 'nagisa' } }) ) .then((x) => x.json()) expect(value).toEqual({ name: 'nagisa', faction: 'tea_party' }) }) it('create default number params', async () => { const app = new Elysia().get('/', ({ headers }) => headers, { headers: t.Object({ name: t.String(), rank: t.Number({ default: 1 }) }) }) const value = await app .handle( req('/', { headers: { name: 'nagisa' } }) ) .then((x) => x.json()) expect(value).toEqual({ name: 'nagisa', rank: 1 }) }) it('coerce number object to numeric', async () => { const app = new Elysia().get('/', ({ headers: { id } }) => typeof id, { headers: t.Object({ id: t.Number() }) }) const value = await app .handle( req('/', { headers: { id: '1' } }) ) .then((x) => x.text()) expect(value).toBe('number') }) it('coerce string to boolean', async () => { const app = new Elysia().get( '/', ({ headers }) => typeof headers['is-admin'], { headers: t.Object({ 'is-admin': t.Boolean() }) } ) const value = await app .handle( req('/', { headers: { 'is-admin': 'true' } }) ) .then((x) => x.text()) expect(value).toBe('boolean') }) it('handle optional at root', async () => { const app = new Elysia().get('/', ({ headers }) => headers, { headers: t.Optional( t.Object({ id: t.Numeric() }) ) }) const res = await Promise.all([ app.handle(req('/')).then((x) => x.json()), app .handle( req('/', { headers: { id: '1' } }) ) .then((x) => x.json()) ]) expect(res).toEqual([{}, { id: 1 }]) }) it('handle coerce TransformDecodeError', async () => { let err: Error | undefined const app = new Elysia() .get('/', ({ body }) => body, { headers: t.Object({ year: t.Numeric({ minimum: 1900, maximum: 2160 }) }), error({ code, error }) { switch (code) { case 'VALIDATION': err = error } } }) .listen(0) await app.handle( req('/', { headers: { year: '3000' } }) ) expect(err instanceof ValidationError).toBe(true) }) }) ================================================ FILE: test/validator/novalidate.test.ts ================================================ import { Elysia, t } from '../../src' import { describe, expect, it } from 'bun:test' import { req } from '../utils' describe('ElysiaType.NoValidate', () => { it('should bypass validation with t.NoValidate(t.String())', async () => { const app = new Elysia().get('/', () => 123 as unknown as string, { response: t.NoValidate(t.String()) }) const res = await app.handle(req('/')) expect(res.status).toBe(200) expect(await res.text()).toBe('123') }) it('should bypass validation with t.NoValidate(t.Number())', async () => { const app = new Elysia().get( '/', () => 'not-a-number' as unknown as number, { response: t.NoValidate(t.Number()) } ) const res = await app.handle(req('/')) expect(res.status).toBe(200) expect(await res.text()).toBe('not-a-number') }) it('should bypass validation with t.NoValidate(t.Boolean())', async () => { const app = new Elysia().get( '/', () => 'not-a-boolean' as unknown as boolean, { response: t.NoValidate(t.Boolean()) } ) const res = await app.handle(req('/')) expect(res.status).toBe(200) expect(await res.text()).toBe('not-a-boolean') }) it('should bypass validation with t.NoValidate(t.Object())', async () => { const app = new Elysia().get( '/', () => 'invalid-object' as unknown as { name: string }, { response: t.NoValidate(t.Object({ name: t.String() })) } ) const res = await app.handle(req('/')) expect(res.status).toBe(200) expect(await res.text()).toBe('{}') }) it('should bypass validation with t.NoValidate(t.Array())', async () => { const app = new Elysia().get( '/', () => 'not-an-array' as unknown as string[], { response: t.NoValidate(t.Array(t.String())) } ) const res = await app.handle(req('/')) expect(res.status).toBe(200) expect(await res.text()).toBe('not-an-array') }) it('should bypass validation with t.NoValidate(t.Union())', async () => { const app = new Elysia().get( '/', () => 'invalid' as unknown as string | number, { response: t.NoValidate( t.Union([ t.String({ minLength: 10 }), t.Number({ minimum: 100 }) ]) ) } ) const res = await app.handle(req('/')) expect(res.status).toBe(200) expect(await res.text()).toBe('invalid') }) it('should bypass validation with t.NoValidate(t.Date())', async () => { const app = new Elysia().get( '/', () => 'Hello Elysia' as unknown as Date, { response: t.NoValidate(t.Date()) } ) const res = await app.handle(req('/')) expect(res.status).toBe(200) expect(await res.text()).toBe('Hello Elysia') }) it('should bypass validation with t.NoValidate(t.Ref())', async () => { const app = new Elysia() .model({ score: t.Number() }) // @ts-expect-error .get('/', () => 'string instead of number!', { response: t.NoValidate(t.Ref('score')) }) const res = await app.handle(req('/')) expect(res.status).toBe(200) expect(await res.text()).toBe('string instead of number!') }) it('should work with actual Date when using t.NoValidate(t.Date())', async () => { const testDate = new Date('2025-01-01T00:00:00Z') const app = new Elysia().get('/', () => testDate, { response: t.NoValidate(t.Date()) }) const res = await app.handle(req('/')) expect(res.status).toBe(200) expect(await res.text()).toBe(testDate.toString()) }) it('should bypass validation with t.NoValidate(t.Numeric())', async () => { const app = new Elysia().get( '/', () => 'not-a-number' as unknown as number, { response: t.NoValidate(t.Numeric()) } ) const res = await app.handle(req('/')) expect(res.status).toBe(200) expect(await res.text()).toBe('not-a-number') }) it('should bypass validation with t.NoValidate(t.BooleanString())', async () => { const app = new Elysia().get( '/', () => 'invalid-boolean' as unknown as boolean, { response: t.NoValidate(t.BooleanString()) } ) const res = await app.handle(req('/')) expect(res.status).toBe(200) expect(await res.text()).toBe('invalid-boolean') }) it('should work with NoValidate in specific status codes', async () => { const app = new Elysia().get( '/', ({ set }) => { set.status = 201 return 'Hello' as unknown as Date }, { response: { 200: t.String(), 201: t.NoValidate(t.Date()) } } ) const res = await app.handle(req('/')) expect(res.status).toBe(201) expect(await res.text()).toBe('Hello') }) it('should validate normally for non-NoValidate status codes', async () => { const app = new Elysia().get( '/', ({ set }) => { set.status = 200 return 'Hello' as unknown as Date }, { response: { 200: t.Date(), 201: t.NoValidate(t.Date()) } } ) const res = await app.handle(req('/')) expect(res.status).toBe(422) }) it('should work with NoValidate on nested object properties', async () => { const app = new Elysia().get( '/', // @ts-expect-error () => ({ user: { age: '123', name: true }, timestamp: '2025-01-01T00:00:00Z' }), { response: t.NoValidate( t.Object({ user: t.Object({ name: t.String(), age: t.Number() }), timestamp: t.Date() }) ) } ) const res = await app.handle(req('/')) expect(res.status).toBe(200) expect(await res.json()).toEqual({ user: { age: '123', name: true }, timestamp: '2025-01-01T00:00:00Z' }) }) it('should validate normally when NOT using NoValidate', async () => { const app = new Elysia().get( '/', () => 'Hello Elysia' as unknown as Date, { response: t.Date() } ) const res = await app.handle(req('/')) expect(res.status).toBe(422) }) it('should validate normally with strict object schemas', async () => { const app = new Elysia() // @ts-expect-error .get('/', () => ({ name: 'John' }), { response: t.Object({ name: t.String(), age: t.Number() }) }) const res = await app.handle(req('/')) expect(res.status).toBe(422) }) it('should handle null values with NoValidate', async () => { const app = new Elysia() // @ts-expect-error .get('/', () => null, { response: t.NoValidate(t.String()) }) const res = await app.handle(req('/')) expect(res.status).toBe(200) expect(await res.text()).toBe('') }) it('should handle undefined values with NoValidate', async () => { const app = new Elysia() // @ts-expect-error .get('/', () => undefined, { response: t.NoValidate(t.String()) }) const res = await app.handle(req('/')) expect(res.status).toBe(200) expect(await res.text()).toBe('') }) it('should work with NoValidate on multiple union types', async () => { const app = new Elysia().get( '/', () => 'test' as unknown as string | number | boolean, { response: t.NoValidate( t.Union([t.String(), t.Number(), t.Boolean()]) ) } ) const res = await app.handle(req('/')) expect(res.status).toBe(200) expect(await res.text()).toBe('test') }) it('bypasses Encode when encodeSchema=true (Date)', async () => { const app = new Elysia({ encodeSchema: true }).get( '/', // @ts-expect-error () => 'Hello Elysia', { response: t.NoValidate(t.Date()) } ) const res = await app.handle(req('/')) expect(res.status).toBe(200) expect(await res.text()).toBe('Hello Elysia') }) it('bypasses Encode with NoValidate(t.Ref(Date)) when encodeSchema=true', async () => { const app = new Elysia({ encodeSchema: true }) .model({ createdAt: t.Date() }) // @ts-expect-error .get('/', () => 'Hello', { response: t.NoValidate(t.Ref('createdAt')) }) const res = await app.handle(req('/')) expect(res.status).toBe(200) expect(await res.text()).toBe('Hello') }) }) ================================================ FILE: test/validator/params.test.ts ================================================ import { Elysia, t, ValidationError } from '../../src' import { describe, expect, it } from 'bun:test' import { req } from '../utils' describe('Params Validator', () => { it('parse params without validator', async () => { const app = new Elysia().get('/id/:id', ({ params: { id } }) => id) const res = await app.handle(req('/id/617')) expect(await res.text()).toBe('617') expect(res.status).toBe(200) }) it('validate single', async () => { const app = new Elysia().get('/id/:id', ({ params: { id } }) => id, { params: t.Object({ id: t.String() }) }) const res = await app.handle(req('/id/617')) expect(await res.text()).toBe('617') expect(res.status).toBe(200) }) it('validate multiple', async () => { const app = new Elysia().get( '/id/:id/name/:name', ({ params }) => params, { params: t.Object({ id: t.String(), name: t.String() }) } ) const res = await app.handle(req('/id/617/name/Ga1ahad')) expect(await res.json()).toEqual({ id: '617', name: 'Ga1ahad' }) expect(res.status).toBe(200) }) it('parse without reference', async () => { const app = new Elysia().get('/id/:id', () => '', { params: t.Object({ id: t.String() }) }) const res = await app.handle(req('/id/617')) expect(res.status).toBe(200) }) it('parse single numeric', async () => { const app = new Elysia().get('/id/:id', ({ params }) => params, { params: t.Object({ id: t.Numeric() }) }) const res = await app.handle(req('/id/617')) expect(await res.json()).toEqual({ id: 617 }) expect(res.status).toBe(200) }) it('parse multiple numeric', async () => { const app = new Elysia().get( '/id/:id/chapter/:chapterId', ({ params }) => params, { params: t.Object({ id: t.Numeric(), chapterId: t.Numeric() }) } ) const res = await app.handle(req('/id/617/chapter/12')) expect(await res.json()).toEqual({ id: 617, chapterId: 12 }) expect(res.status).toBe(200) }) it('parse single integer', async () => { const app = new Elysia().get('/id/:id', ({ params }) => params, { params: t.Object({ id: t.Integer() }) }) const res = await app.handle(req('/id/617')) expect(await res.json()).toEqual({ id: 617 }) expect(res.status).toBe(200) }) it('parse malformed integer', async () => { const app = new Elysia().get('/id/:id', ({ params }) => params, { params: t.Object({ id: t.Integer() }) }) const res = await app.handle(req('/id/617.1234')) expect(await res.json()).toMatchObject({ type: 'validation', on: 'params', summary: "Property 'id' should be one of: 'integer', 'integer'", property: '/id', message: 'Expected union value', expected: { id: 0 }, found: { id: '617.1234' }, errors: [ { type: 62, schema: { anyOf: [ { format: 'integer', default: 0, type: 'string' }, { type: 'integer' } ] }, path: '/id', value: '617.1234', message: 'Expected union value', summary: "Property 'id' should be one of: 'integer', 'integer'" } ] }) expect(res.status).toBe(422) }) it('parse multiple integer', async () => { const app = new Elysia().get( '/id/:id/chapter/:chapterId', ({ params }) => params, { params: t.Object({ id: t.Integer(), chapterId: t.Integer() }) } ) const res = await app.handle(req('/id/617/chapter/12')) expect(await res.json()).toEqual({ id: 617, chapterId: 12 }) expect(res.status).toBe(200) }) it('create default string params', async () => { const app = new Elysia().get('/:name', ({ params }) => params, { params: t.Object({ name: t.String(), faction: t.String({ default: 'tea_party' }) }) }) const value = await app.handle(req('/nagisa')).then((x) => x.json()) expect(value).toEqual({ name: 'nagisa', faction: 'tea_party' }) }) it('create default number params', async () => { const app = new Elysia().get('/:name', ({ params }) => params, { params: t.Object({ name: t.String(), rank: t.Number({ default: 1 }) }) }) const value = await app.handle(req('/nagisa')).then((x) => x.json()) expect(value).toEqual({ name: 'nagisa', rank: 1 }) }) it('coerce number object to numeric', async () => { const app = new Elysia().get( '/id/:id', ({ params: { id } }) => typeof id, { params: t.Object({ id: t.Number() }) } ) const value = await app.handle(req('/id/1')).then((x) => x.text()) expect(value).toBe('number') }) it('coerce string object to boolean', async () => { const app = new Elysia().get( '/is-admin/:value', ({ params: { value } }) => typeof value, { params: t.Object({ value: t.Boolean() }) } ) const value = await app .handle(req('/is-admin/true')) .then((x) => x.text()) expect(value).toBe('boolean') }) describe('create default value on optional params', () => { it('parse multiple optional params', async () => { const app = new Elysia().get( '/name/:last?/:first?', ({ params: { first, last } }) => `${last}/${first}`, { params: t.Object({ first: t.String({ default: 'fubuki' }), last: t.String({ default: 'shirakami' }) }) } ) const res = await Promise.all([ app.handle(req('/name')).then((x) => x.text()), app.handle(req('/name/kurokami')).then((x) => x.text()), app.handle(req('/name/kurokami/sucorn')).then((x) => x.text()) ]) expect(res).toEqual([ 'shirakami/fubuki', 'kurokami/fubuki', 'kurokami/sucorn' ]) }) }) it('handle coerce TransformDecodeError', async () => { let err: Error | undefined const app = new Elysia() .get('/id/:id', ({ body }) => body, { params: t.Object({ year: t.Numeric({ minimum: 1900, maximum: 2160 }) }), error({ code, error }) { switch (code) { case 'VALIDATION': err = error } } }) .listen(0) await app.handle(req('/id/3000')) expect(err instanceof ValidationError).toBe(true) }) }) ================================================ FILE: test/validator/query.test.ts ================================================ import { Context, Elysia, t, ValidationError } from '../../src' import { describe, expect, it } from 'bun:test' import { req } from '../utils' describe('Query Validator', () => { it('validate single', async () => { const app = new Elysia().get('/', ({ query: { name } }) => name, { query: t.Object({ name: t.String() }) }) const res = await app.handle(req('/?name=sucrose')) expect(await res.text()).toBe('sucrose') expect(res.status).toBe(200) }) it('validate with hyphen in key', async () => { const app = new Elysia().get( '/', ({ query }) => query['character-name'], { query: t.Object({ 'character-name': t.String() }) } ) const res = await app.handle(req('/?character-name=sucrose')) expect(await res.text()).toBe('sucrose') expect(res.status).toBe(200) }) it('validate with dot in key', async () => { const app = new Elysia().get( '/', ({ query }) => query['character.name'], { query: t.Object({ 'character.name': t.String() }) } ) const res = await app.handle(req('/?character.name=sucrose')) expect(await res.text()).toBe('sucrose') expect(res.status).toBe(200) }) it('validate multiple', async () => { const app = new Elysia().get('/', ({ query }) => query, { query: t.Object({ name: t.String(), job: t.String(), trait: t.String() }) }) const res = await app.handle( req('/?name=sucrose&job=alchemist&trait=dog') ) expect(await res.json()).toEqual({ name: 'sucrose', job: 'alchemist', trait: 'dog' }) expect(res.status).toBe(200) }) it('parse without reference', async () => { const app = new Elysia().get('/', () => '', { query: t.Object({ name: t.String(), job: t.String(), trait: t.String() }) }) const res = await app.handle( req('/?name=sucrose&job=alchemist&trait=dog') ) expect(res.status).toBe(200) }) it('validate optional', async () => { const app = new Elysia().get('/', ({ query }) => query, { query: t.Object({ name: t.String(), job: t.String(), trait: t.Optional(t.String()) }) }) const res = await app.handle(req('/?name=sucrose&job=alchemist')) expect(await res.json()).toEqual({ name: 'sucrose', job: 'alchemist' }) expect(res.status).toBe(200) }) it('parse single numeric', async () => { const app = new Elysia().get('/', ({ query }) => query, { query: t.Object({ name: t.String(), job: t.String(), trait: t.Optional(t.String()), age: t.Numeric() }) }) const res = await app.handle(req('/?name=sucrose&job=alchemist&age=16')) expect(await res.json()).toEqual({ name: 'sucrose', job: 'alchemist', age: 16 }) expect(res.status).toBe(200) }) it('parse multiple numeric', async () => { const app = new Elysia().get('/', ({ query }) => query, { query: t.Object({ name: t.String(), job: t.String(), trait: t.Optional(t.String()), age: t.Numeric(), rank: t.Numeric() }) }) const res = await app.handle( req('/?name=sucrose&job=alchemist&age=16&rank=4') ) expect(await res.json()).toEqual({ name: 'sucrose', job: 'alchemist', age: 16, rank: 4 }) expect(res.status).toBe(200) }) it('parse numeric enum', async () => { enum Gender { MALE = 1, FEMALE = 2, UNKNOWN = 3 } const app = new Elysia().get('/', ({ query }) => query, { query: t.Object({ name: t.String(), gender: t.NumericEnum(Gender) }) }) const res = await app.handle( req(`/?name=sucrose&gender=${Gender.MALE}`) ) expect(await res.json()).toEqual({ name: 'sucrose', gender: Gender.MALE }) expect(res.status).toBe(200) }) it('parse single integer', async () => { const app = new Elysia().get('/', ({ query: { limit } }) => limit, { query: t.Object({ limit: t.Integer() }) }) const res = await app.handle(req('/?limit=16')) expect(res.status).toBe(200) expect(await res.text()).toBe('16') }) it('parse multiple integer', async () => { const app = new Elysia().get('/', ({ query }) => query, { query: t.Object({ limit: t.Integer(), offset: t.Integer() }) }) const res = await app.handle(req('/?limit=16&offset=0')) expect(res.status).toBe(200) expect(await res.json()).toEqual({ limit: 16, offset: 0 }) }) it('validate partial', async () => { const app = new Elysia().get('/', ({ query }) => query, { query: t.Partial( t.Object({ name: t.String(), job: t.String(), trait: t.Optional(t.String()) }) ) }) const res = await app.handle(req('/')) expect(res.status).toBe(200) expect(await res.json()).toEqual({}) }) it('parse numeric with partial', async () => { const app = new Elysia().get('/', ({ query }) => query, { query: t.Partial( t.Object({ name: t.String(), job: t.String(), trait: t.Optional(t.String()), age: t.Numeric(), rank: t.Numeric() }) ) }) const res = await app.handle(req('/')) expect(res.status).toBe(200) expect(await res.json()).toEqual({}) }) it('parse boolean string', async () => { const app = new Elysia().get('/', ({ query }) => query, { query: t.Object({ param1: t.BooleanString() }) }) const res = await app.handle(req('/?param1=true')) expect(res.status).toBe(200) expect(await res.json()).toEqual({ param1: true }) }) it('parse optional boolean string', async () => { const app = new Elysia().get('/', ({ query }) => query, { query: t.Object({ param1: t.Optional(t.BooleanString({ default: true })) }) }) const res = await app.handle(req('/')) expect(res.status).toBe(200) expect(await res.json()).toEqual({ param1: true }) }) it('parse optional boolean string with second parameter', async () => { const schema = t.Object({ registered: t.Optional(t.Boolean()), other: t.String() }) const app = new Elysia().get('/', ({ query }) => query, { query: schema }) const res = await app.handle(req('/?other=sucrose')) expect(res.status).toBe(200) expect(await res.json()).toEqual({ other: 'sucrose' }) }) it('parse optional boolean string with default value', async () => { const schema = t.Object({ registered: t.Optional(t.Boolean({ default: true })), other: t.String() }) const app = new Elysia().get('/', ({ query }) => query, { query: schema }) const res = await app.handle(req('/?other=sucrose')) expect(res.status).toBe(200) expect(await res.json()).toEqual({ other: 'sucrose', registered: true }) }) it('validate optional object', async () => { const app = new Elysia().get( '/', ({ query }) => query?.name ?? 'sucrose', { query: t.Object( { name: t.Optional(t.String()) }, { additionalProperties: true } ) } ) const [valid, invalid] = await Promise.all([ app.handle(req('/?name=sucrose')), app.handle(req('/')) ]) expect(await valid.text()).toBe('sucrose') expect(valid.status).toBe(200) expect(await invalid.text()).toBe('sucrose') expect(invalid.status).toBe(200) }) it('create default string query', async () => { const app = new Elysia().get('/', ({ query }) => query, { query: t.Object({ name: t.String(), faction: t.String({ default: 'tea_party' }) }) }) const value = await app .handle(req('/?name=nagisa')) .then((x) => x.json()) expect(value).toEqual({ name: 'nagisa', faction: 'tea_party' }) }) it('create default number query', async () => { const app = new Elysia().get('/', ({ query }) => query, { query: t.Object({ name: t.String(), rank: t.Number({ default: 1 }) }) }) const value = await app .handle(req('/?name=nagisa')) .then((x) => x.json()) expect(value).toEqual({ name: 'nagisa', rank: 1 }) }) it('handle query edge case', async () => { const checker = { check(ctx: Context, name: string, state?: string) { return typeof state !== 'undefined' } } const app = new Elysia() .derive((ctx) => { const { name } = ctx.params return { check() { const { state } = ctx.query if (!checker.check(ctx, name, state ?? ctx.query.state)) throw new Error('State mismatch') } } }) .get('/:name', ({ check }) => { check() return 'yay' }) const response = await app .handle(req('/a?state=123')) .then((x) => x.text()) expect(response).toBe('yay') }) it('parse query array', async () => { const params = new URLSearchParams() params.append('keys', '1') params.append('keys', '2') const response = await new Elysia() .get('/', ({ query }) => query, { query: t.Object({ keys: t.Array(t.String()) }) }) .handle(new Request(`http://localhost/?${params.toString()}`)) .then((res) => res.json()) expect(response).toEqual({ keys: ['1', '2'] }) }) it('parse query object', async () => { const app = new Elysia().get('/', ({ query }) => query, { query: t.Object({ role: t.Optional( t.Array( t.Object({ name: t.String() }) ) ) }) }) const response = await app .handle( req( `/?role=${JSON.stringify([ { name: 'hello' }, { name: 'world' } ])}` ) ) .then((x) => x.json()) expect(response).toEqual({ role: [{ name: 'hello' }, { name: 'world' }] }) }) it('parse optional query object', async () => { const app = new Elysia().get('/', ({ query }) => query, { query: t.Optional( t.Object({ role: t.Optional( t.Array( t.Object({ name: t.String() }) ) ) }) ) }) const response = await app .handle( req( `/?role=${JSON.stringify([ { name: 'hello' }, { name: 'world' } ])}` ) ) .then((x) => x.json()) expect(response).toEqual({ role: [{ name: 'hello' }, { name: 'world' }] }) }) it('parse array with nested object', async () => { const params = new URLSearchParams() params.append('keys', JSON.stringify({ a: 'hello' })) params.append('keys', JSON.stringify({ a: 'hi' })) const response = await new Elysia() .get('/', ({ query }) => query, { query: t.Object({ keys: t.Array( t.Object({ a: t.String() }) ) }) }) .handle(new Request(`http://localhost/?${params.toString()}`)) .then((res) => res.json()) expect(response).toEqual({ keys: [{ a: 'hello' }, { a: 'hi' }] }) }) it('parse optional array with nested object', async () => { const params = new URLSearchParams() params.append('keys', JSON.stringify({ a: 'hello' })) params.append('keys', JSON.stringify({ a: 'hi' })) const response = await new Elysia() .get('/', ({ query }) => query, { query: t.Optional( t.Object({ keys: t.Array( t.Object({ a: t.String() }) ) }) ) }) .handle(new Request(`http://localhost/?${params.toString()}`)) .then((res) => res.json()) expect(response).toEqual({ keys: [{ a: 'hello' }, { a: 'hi' }] }) }) it('parse query object array without schema', async () => { const params = new URLSearchParams() params.append('keys', JSON.stringify({ a: 'hello' })) params.append('keys', JSON.stringify({ a: 'hi' })) const response = await new Elysia() .get('/', ({ query }) => query, { query: t.Optional( t.Object({ keys: t.Array( t.Object({ a: t.String() }) ) }) ) }) .handle(new Request(`http://localhost/?${params.toString()}`)) .then((res) => res.json()) expect(response).toEqual({ keys: [{ a: 'hello' }, { a: 'hi' }] }) }) // People don't expect this // @see: https://x.com/saltyAom/status/1813236251321069918 // it('parse query array without schema', async () => { // let value: string[] | undefined // const response = await new Elysia() // .get('/', ({ query: { keys } }) => value = keys) // .handle(new Request(`http://localhost/?id=1&id=2`)) // .then((res) => res.json()) // expect(value).toEqual(['1', '2']) // }) it("don't parse query object without schema", async () => { const app = new Elysia().get('/', ({ query: { role } }) => role) const response = await app .handle(req(`/?role=${JSON.stringify({ name: 'hello' })}`)) .then((x) => x.text()) expect(response).toBe(JSON.stringify({ name: 'hello' })) }) it('parse union primitive and object', async () => { const app = new Elysia().get('/', ({ query: { ids } }) => ids, { query: t.Object({ ids: t.Union([ t.Array( t.Union([t.Object({ a: t.String() }), t.Numeric()]) ), t.Numeric() ]) }) }) const response = await app .handle(req(`/?ids=1&ids=${JSON.stringify({ a: 'b' })}`)) .then((res) => res.json()) expect(response).toEqual([1, { a: 'b' }]) }) it('coerce number object to numeric', async () => { const app = new Elysia().get('/', ({ query: { id } }) => typeof id, { query: t.Object({ id: t.Number() }) }) const value = await app.handle(req('/?id=1')).then((x) => x.text()) expect(value).toBe('number') }) it('coerce string object to boolean', async () => { const app = new Elysia().get( '/', ({ query: { isAdmin } }) => typeof isAdmin, { query: t.Object({ isAdmin: t.Boolean() }) } ) const value = await app .handle(req('/?isAdmin=true')) .then((x) => x.text()) expect(value).toBe('boolean') }) it("don't parse object automatically unless explicitly specified", async () => { let value: string | undefined const app = new Elysia().get( '/', ({ query: { pagination } }) => (value = pagination as string) ) await app.handle( req( `/?pagination=${JSON.stringify({ pageIndex: 1, pageLimit: 10 })}` ) ) expect(value).toEqual(JSON.stringify({ pageIndex: 1, pageLimit: 10 })) }) it('handle object array in single query', async () => { const app = new Elysia().get('/', ({ query }) => query, { query: t.Object({ pagination: t.Array( t.Object({ pageIndex: t.Number(), pageLimit: t.Number() }) ) }) }) const response = await app .handle( req( `/?pagination=${JSON.stringify([{ pageIndex: 1, pageLimit: 10 }])}` ) ) .then((x) => x.json()) expect(response).toEqual({ pagination: [{ pageIndex: 1, pageLimit: 10 }] }) }) it('handle merge object to array in multiple query', async () => { const app = new Elysia().get('/', ({ query }) => query, { query: t.Object({ pagination: t.Array( t.Object({ pageIndex: t.Number(), pageLimit: t.Number() }) ) }) }) const response = await app .handle( req( `/?pagination=${JSON.stringify({ pageIndex: 1, pageLimit: 10 })}&pagination=${JSON.stringify({ pageIndex: 2, pageLimit: 9 })}` ) ) .then((x) => x.json()) expect(response).toEqual({ pagination: [ { pageIndex: 1, pageLimit: 10 }, { pageIndex: 2, pageLimit: 9 } ] }) }) it('don\t coerce number in nested object', async () => { const app = new Elysia().get('/', ({ query: { user } }) => user, { query: t.Object({ user: t.Object({ id: t.Number(), name: t.String() }) }) }) const response = await app.handle( req( `?user=${JSON.stringify({ id: '2', name: 'test' })}` ) ) expect(response.status).toBe(422) }) it('handle optional at root', async () => { const app = new Elysia().get('/', ({ query }) => query, { query: t.Optional( t.Object({ id: t.Numeric() }) ) }) const res = await Promise.all([ app.handle(req('/')).then((x) => x.json()), app.handle(req('/?id=1')).then((x) => x.json()) ]) expect(res).toEqual([{}, { id: 1 }]) }) it('parse query array in multiple location correctly', async () => { const app = new Elysia().get('/', ({ query }) => query, { query: t.Object({ leading: t.String(), arr: t.Array(t.String()), trailing: t.String() }) }) const response = await app .handle(req('/?leading=foo&arr=bar&arr=baz&trailing=qux&arr=xd')) .then((x) => x.json()) expect(response).toEqual({ leading: 'foo', arr: ['bar', 'baz', 'xd'], trailing: 'qux' }) }) it('parse + in query', async () => { const api = new Elysia().get('', ({ query }) => query, { query: t.Object({ keyword: t.String() }) }) const url = new URL('http://localhost:3000/') url.searchParams.append('keyword', 'hello world') // console.log(url.href) //http://localhost:3000/?keyword=hello+world const result = await api .handle(new Request(url.href)) .then((response) => response.json()) expect(result).toEqual({ keyword: 'hello world' }) }) // https://github.com/elysiajs/elysia/issues/929 it('slice non-ASCII querystring offset correctly', async () => { const app = new Elysia().get('/', () => 'ok', { query: t.Object({ key1: t.Union([t.Array(t.String()), t.String()]) }) }) expect( await app .handle(req('/?key1=ab&key1=cd&z=が')) .then((x) => x.status) ).toEqual(200) expect( await app.handle(req('/?key1=ab&z=が')).then((x) => x.status) ).toEqual(200) expect( await app.handle(req('/?key1=ab&key1=cd&z=x')).then((x) => x.status) ).toEqual(200) expect( await app .handle(req('/?z=が&key1=ab&key1=cd')) .then((x) => x.status) ).toEqual(200) expect( await app.handle(req('/?key1=で&key1=が&z=x')).then((x) => x.status) ).toEqual(200) }) // https://github.com/elysiajs/elysia/issues/912 it('handle JavaScript date numeric offset', () => { const api = new Elysia().get('/', ({ query }) => query, { query: t.Object({ date: t.Date() }) }) api.handle(req(`/?date=${Date.now()}`)).then((x) => x.json()) }) it('handle nuqs format when specified as Array', async () => { const app = new Elysia().get('/', ({ query }) => query, { query: t.Object({ a: t.Array(t.String()) }) }) const response = await app.handle(req('/?a=a,b')).then((x) => x.json()) expect(response).toEqual({ a: ['a', 'b'] }) }) it('handle nuqs format when specified as number', async () => { const app = new Elysia().get('/', ({ query }) => query, { query: t.Object({ a: t.Array(t.Numeric()) }) }) const response = await app.handle(req('/?a=1,2')).then((x) => x.json()) expect(response).toEqual({ a: [1, 2] }) }) it('handle nuqs format when specified as boolean', async () => { const app = new Elysia().get('/', ({ query }) => query, { query: t.Object({ a: t.Array(t.BooleanString()) }) }) const response = await app .handle(req('/?a=true,false')) .then((x) => x.json()) expect(response).toEqual({ a: [true, false] }) }) // https://github.com/elysiajs/elysia/issues/1015 it('handle ref transform', async () => { const app = new Elysia() .model({ myModel: t.Object({ num: t.Number() }) }) .get( '/', ({ query: { num } }) => ({ num, type: typeof num }), { query: 'myModel' } ) const response = await app .handle(new Request('http://localhost?num=1&a=2')) .then((x) => x.json()) expect(response).toEqual({ num: 1, type: 'number' }) }) // https://github.com/elysiajs/elysia/issues/1068 it('handle ref transform with ref inside reference model', async () => { const app = new Elysia() .model({ num2: t.Numeric(), myModel: t.Object({ num: t.Numeric(), num2: t.Ref('num2') }) }) .get( '/', ({ query: { num, num2 } }) => ({ num, numType: typeof num, num2, num2Type: typeof num2 }), { query: 'myModel' } ) const response = await app .handle(new Request('http://localhost?num=1&num2=2')) .then((x) => x.json()) expect(response).toEqual({ num: 1, numType: 'number', num2: 2, num2Type: 'number' }) }) it('handle "&" inside a query value', async () => { const app = new Elysia().get( '*', ({ query, request }) => ({ query, url: { test: new URL(request.url).searchParams.get('test') } }), { query: t.Object({ test: t.String() }) } ) const url = "https://localhost/?test=Test1%20%26%20Test2'" const value = await app.handle(new Request(url)).then((x) => x.json()) expect(value).toEqual({ query: { test: "Test1 & Test2'" }, url: { test: new URL(url).searchParams.get('test') } }) }) it('handle array string correctly', async () => { const app = new Elysia({ precompile: true }).get( '/', ({ query }) => query, { query: t.Object({ status: t.Optional(t.Array(t.String())) }) } ) const response = await Promise.all([ app.handle(req('/?')).then((x) => x.json()), app.handle(req('/?status=a')).then((x) => x.json()), app.handle(req('/?status=a&status=b')).then((x) => x.json()) ]) expect(response).toEqual([ {}, { status: ['a'] }, { status: ['a', 'b'] } ]) }) it('handle Transform query', async () => { const app = new Elysia().get( '/test', ({ query: { id } }) => ({ id, type: typeof id }), { query: t.Object({ id: t .Transform(t.UnionEnum(['test', 'foo'])) .Decode((id) => ({ value: id })) .Encode((id) => id.value) }) } ) const response = await app .handle(req('/test?id=test')) .then((x) => x.json()) expect(response).toEqual({ id: { value: 'test' }, type: 'object' }) }) it('handle Date query', async () => { const app = new Elysia().get( '/', ({ query: { date } }) => date.toISOString(), { query: t.Object({ date: t.Date() }) } ) const response = await app .handle(req(`/?date=2023-04-05T12:30:00+01:00`)) .then((x) => x.text()) expect(response).toEqual('2023-04-05T11:30:00.000Z') }) it('handle coerce TransformDecodeError', async () => { let err: Error | undefined const app = new Elysia().get('/', ({ query }) => query, { query: t.Object({ year: t.Numeric({ minimum: 1900, maximum: 2160 }) }), error({ code, error }) { switch (code) { case 'VALIDATION': err = error } } }) await app.handle(req('?year=3000')) expect(err instanceof ValidationError).toBe(true) }) it('handle reference query array', async () => { const app = new Elysia() .model({ ids: t.Object({ ids: t.Array(t.Union([t.String(), t.ArrayString()])) }) }) .get('/', ({ query }) => query, { query: 'ids' }) const response = await app .handle(req('?ids=1,2,3')) .then((x) => x.json()) expect(response).toEqual({ ids: ['1', '2', '3'] }) }) it('validate url encoded query', () => { const app = new Elysia().get('/', ({ query }) => query, { query: t.Object({ test: t.Optional(t.Number()), $test: t.Optional(t.Number()) }) }) const value = app .handle(new Request('http://localhost?test=1&%24test=2')) .then((x) => x.json()) expect(value).resolves.toEqual({ test: 1, $test: 2 }) }) it("don't populate object query on failed validation", async () => { const app = new Elysia().get('/', ({ query }) => query, { query: t.Object({ filter: t.Object({ latlng: t.Object({ within: t.Object({ ne: t.Number(), sw: t.Number() }) }), zoom: t.Object({ equalTo: t.Number({ minimum: 0, maximum: 20, multipleOf: 1 }) }) }) }) }) const filter = JSON.stringify({ latlng: { within: { ne: 1, sw: 1 } }, zoom: { equalTo: 2 } }) const valid = await app.handle( new Request(`http://localhost:3000/?filter=${filter}`) ) const invalid1 = await app.handle(new Request(`http://localhost:3000`)) const invalid2 = await app.handle( new Request( `http://localhost:3000?filter=${JSON.stringify({ zoom: { equalTo: 21 } })}` ) ) expect(valid.status).toBe(200) expect(invalid1.status).toBe(422) expect(invalid2.status).toBe(422) }) it("don't populate array query on failed validation", async () => { const app = new Elysia().get('/', ({ query }) => query, { query: t.Object({ party: t.Array( t.Object({ name: t.String() }) ) }) }) const filter = JSON.stringify([ { name: 'lilith' }, { name: 'fouco' } ]) const valid = await app.handle( new Request(`http://localhost:3000/?party=${filter}`) ) const invalid1 = await app.handle(new Request(`http://localhost:3000`)) const invalid2 = await app.handle( new Request(`http://localhost:3000?filter=[]`) ) expect(valid.status).toBe(200) expect(invalid1.status).toBe(422) expect(invalid2.status).toBe(422) }) // Union schema tests it('handle query array in Union schema', async () => { const app = new Elysia({ aot: false }).get('/', ({ query }) => query, { query: t.Union([ t.Object({ ids: t.Array(t.String()) }), t.Object({ id: t.String() }) ]) }) const response = await app .handle(req('/?ids=1&ids=2')) .then((x) => x.json()) expect(response.ids).toEqual(['1', '2']) }) it('handle numeric coercion in Union schema', async () => { const app = new Elysia({ aot: false }).get('/', ({ query }) => query, { query: t.Union([ t.Object({ page: t.Numeric() }), t.Object({ cursor: t.String() }) ]) }) const response = await app .handle(req('/?page=5')) .then((x) => x.json()) expect(response.page).toBe(5) }) }) ================================================ FILE: test/validator/response-validation-nested.test.ts ================================================ import { describe, expect, it } from 'bun:test' import { Elysia, t } from '../../src' // Issue #1659: Response validation with nested schemas crashes with 500 instead of 422 // https://github.com/elysiajs/elysia/issues/1659 // // Root cause: exact-mirror's Clean() function assumes valid data structure // and throws when accessing nested properties on null values. // Fix: Wrap Clean() calls in try-catch in dynamic-handle.ts describe('Response validation nested schemas', () => { it('should return 422 for invalid nested response (aot: false)', async () => { const app = new Elysia({ aot: false }).post( '/test', // @ts-expect-error - intentionally returning invalid data to test validation () => ({ items: [ ['t1', { file: { ver: { s: '', m: null } } }], ['t2', { file: { ver: null } }] // Invalid - ver should be object ] }), { body: t.Object({}), response: t.Object({ items: t.Array( t.Tuple([ t.String(), t.Union([ t.Object({ file: t.Object({ ver: t.Object({ s: t.String(), m: t.Nullable(t.String()) }) }) }) ]) ]) ) }) } ) const res = await app.handle( new Request('http://localhost/test', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: '{}' }) ) // Should be 422 (validation error), not 500 (internal error) expect(res.status).toBe(422) const json = (await res.json()) as { type: string; errors?: unknown[] } expect(json.type).toBe('validation') expect(json.errors?.length).toBeGreaterThan(0) }) it('should return 422 for invalid nested response (aot: true)', async () => { const app = new Elysia({ aot: true }).post( '/test', // @ts-expect-error - intentionally returning invalid data to test validation () => ({ items: [ ['t1', { file: { ver: { s: '', m: null } } }], ['t2', { file: { ver: null } }] // Invalid ] }), { body: t.Object({}), response: t.Object({ items: t.Array( t.Tuple([ t.String(), t.Union([ t.Object({ file: t.Object({ ver: t.Object({ s: t.String(), m: t.Nullable(t.String()) }) }) }) ]) ]) ) }) } ) const res = await app.handle( new Request('http://localhost/test', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: '{}' }) ) expect(res.status).toBe(422) const json = (await res.json()) as { type: string; errors?: unknown[] } expect(json.type).toBe('validation') expect(json.errors?.length).toBeGreaterThan(0) }) it('should return 422 for tuple with null nested object (aot: false)', async () => { const app = new Elysia({ aot: false }).get( '/test', // @ts-expect-error - intentionally returning invalid data to test validation () => ({ data: ['id', { nested: null }] // nested should be object with 'value' }), { response: t.Object({ data: t.Tuple([ t.String(), t.Object({ nested: t.Object({ value: t.String() }) }) ]) }) } ) const res = await app.handle(new Request('http://localhost/test')) expect(res.status).toBe(422) const json = (await res.json()) as { type: string } expect(json.type).toBe('validation') }) }) ================================================ FILE: test/validator/response.test.ts ================================================ import { Elysia, t, sse } from '../../src' import { streamResponse } from '../../src/adapter/utils' import * as z from 'zod' import { describe, expect, it } from 'bun:test' import { post, req, upload } from '../utils' describe('Response Validator', () => { it('validate primitive', async () => { const app = new Elysia().get('/', () => 'sucrose', { response: t.String() }) const res = await app.handle(req('/')) expect(await res.text()).toBe('sucrose') expect(res.status).toBe(200) }) it('validate number', async () => { const app = new Elysia().get('/', () => 1, { response: t.Number() }) const res = await app.handle(req('/')) expect(await res.text()).toBe('1') expect(res.status).toBe(200) }) it('validate boolean', async () => { const app = new Elysia().get('/', () => true, { response: t.Boolean() }) const res = await app.handle(req('/')) expect(await res.text()).toBe('true') expect(res.status).toBe(200) }) it('validate literal', async () => { const app = new Elysia().get('/', () => 'A' as const, { response: t.Literal('A') }) const res = await app.handle(req('/')) expect(await res.text()).toBe('A') expect(res.status).toBe(200) }) it('validate single', async () => { const app = new Elysia().get( '/', () => ({ name: 'sucrose' }), { response: t.Object({ name: t.String() }) } ) const res = await app.handle(req('/')) expect(await res.json()).toEqual({ name: 'sucrose' }) expect(res.status).toBe(200) }) it('validate multiple', async () => { const app = new Elysia().get( '/', () => ({ name: 'sucrose', job: 'alchemist', trait: 'dog' }), { response: t.Object({ name: t.String(), job: t.String(), trait: t.String() }) } ) const res = await app.handle(req('/')) expect(await res.json()).toEqual({ name: 'sucrose', job: 'alchemist', trait: 'dog' }) expect(res.status).toBe(200) }) it('parse without reference', async () => { const app = new Elysia().get( '/', () => ({ name: 'sucrose', job: 'alchemist', trait: 'dog' }), { response: t.Object({ name: t.String(), job: t.String(), trait: t.String() }) } ) const res = await app.handle(req('/')) expect(res.status).toBe(200) }) it('validate optional', async () => { const app = new Elysia().get( '/', () => ({ name: 'sucrose', job: 'alchemist' }), { response: t.Object({ name: t.String(), job: t.String(), trait: t.Optional(t.String()) }) } ) const res = await app.handle(req('/')) expect(await res.json()).toEqual({ name: 'sucrose', job: 'alchemist' }) expect(res.status).toBe(200) }) it('allow undefined', async () => { const app = new Elysia().get('/', () => {}, { body: t.Union([ t.Undefined(), t.Object({ name: t.String(), job: t.String(), trait: t.Optional(t.String()) }) ]) }) const res = await app.handle(req('/')) expect(res.status).toBe(200) expect(await res.text()).toBe('') }) it('normalize by default', async () => { const app = new Elysia().get( '/', () => ({ name: 'sucrose', job: 'alchemist' }), { response: t.Object({ name: t.String() }) } ) const res = await app.handle(req('/')).then((x) => x.json()) expect(res).toEqual({ name: 'sucrose' }) }) it('strictly validate if not normalize', async () => { const app = new Elysia({ normalize: false }).get( '/', () => ({ name: 'sucrose', job: 'alchemist' }), { response: { 200: t.Object({ name: t.String() }) } } ) const res = await app.handle(req('/')) expect(res.status).toBe(422) }) it('handle File', async () => { const app = new Elysia().post('/', ({ body: { file } }) => file.size, { body: t.Object({ file: t.File() }) }) expect( await app .handle( upload('/', { file: 'aris-yuzu.jpg' }).request ) .then((x) => x.text()) ).toBe(Bun.file('./test/images/aris-yuzu.jpg').size + '') }) it('convert File to Files automatically', async () => { const app = new Elysia().post( '/', ({ body: { files } }) => Array.isArray(files), { body: t.Object({ files: t.Files() }) } ) expect( await app .handle( upload('/', { files: 'aris-yuzu.jpg' }).request ) .then((x) => x.text()) ).toEqual('true') expect( await app .handle( upload('/', { files: ['aris-yuzu.jpg', 'midori.png'] }).request ) .then((x) => x.text()) ).toEqual('true') }) it('validate response per status', async () => { const app = new Elysia().post( '/', ({ set, body: { status, response } }) => { set.status = status return response }, { body: t.Object({ status: t.Number(), response: t.Any() }), response: { 200: t.String(), 201: t.Number() } } ) const r200valid = await app.handle( post('/', { status: 200, response: 'String' }) ) const r200invalid = await app.handle( post('/', { status: 200, response: 1 }) ) const r201valid = await app.handle( post('/', { status: 201, response: 1 }) ) const r201invalid = await app.handle( post('/', { status: 201, response: 'String' }) ) expect(r200valid.status).toBe(200) expect(r200invalid.status).toBe(422) expect(r201valid.status).toBe(201) expect(r201invalid.status).toBe(422) }) it('validate response per status with error()', async () => { const app = new Elysia().get( '/', ({ status }) => status(418, 'I am a teapot'), { response: { 200: t.String(), 418: t.String() } } ) }) it('use inline error from handler', async () => { const app = new Elysia().get( '/', ({ status }) => status(418, 'I am a teapot'), { response: { 200: t.String(), 418: t.String() } } ) }) it('return null with schema', async () => { const app = new Elysia().get('/', () => null, { response: t.Union([ t.Null(), t.Object({ name: t.String() }) ]) }) }) it('return undefined with schema', async () => { const app = new Elysia().get('/', () => undefined, { response: t.Union([ t.Undefined(), t.Object({ name: t.String() }) ]) }) }) it('return void with schema', async () => { const app = new Elysia().get('/', () => undefined, { response: t.Union([ t.Void(), t.Object({ name: t.String() }) ]) }) }) it('return null with status based schema', async () => { const app = new Elysia().get('/', () => undefined, { response: { 200: t.Union([ t.Void(), t.Object({ name: t.String() }) ]), 418: t.String() } }) }) it('return static undefined with status based schema', async () => { const app = new Elysia().get('/', undefined as any, { response: { 200: t.Union([ t.Void(), t.Object({ name: t.String() }) ]), 418: t.String() } }) }) it('return error response with validator', async () => { const app = new Elysia() .get('/ok', () => 'ok', { response: { 200: t.String(), 418: t.Literal('Kirifuji Nagisa'), 420: t.Literal('Snoop Dogg') } }) .get( '/error', ({ status }) => status("I'm a teapot", 'Kirifuji Nagisa'), { response: { 200: t.String(), 418: t.Literal('Kirifuji Nagisa'), 420: t.Literal('Snoop Dogg') } } ) .get( '/validate-error', // @ts-ignore ({ status }) => status("I'm a teapot", 'Nagisa'), { response: { 200: t.String(), 418: t.Literal('Kirifuji Nagisa'), 420: t.Literal('Snoop Dogg') } } ) const response = await Promise.all([ app.handle(req('/ok')).then((x) => x.status), app.handle(req('/error')).then((x) => x.status), app.handle(req('/validate-error')).then((x) => x.status) ]) expect(response).toEqual([200, 418, 422]) }) it('validate nested references', async () => { const job = t.Object( { name: t.String() }, { $id: 'job' } ) const person = t.Object({ name: t.String(), job: t.Ref(job) }) const app = new Elysia().model({ job, person }).get( '/', () => ({ name: 'sucrose', job: { name: 'alchemist' } }), { response: person } ) const res = await app.handle(req('/')) expect(res.status).toBe(200) }) it('validate SSE response with generator', async () => { const app = new Elysia().get( '/', function* () { yield sse({ data: { name: 'Alice' } }) yield sse({ data: { name: 'Bob' } }) }, { response: t.Object({ data: t.Object({ name: t.String() }) }) } ) const res = await app.handle(req('/')) expect(res.status).toBe(200) expect(res.headers.get('content-type')).toBe('text/event-stream') // Verify the stream contains the expected SSE data const result = [] for await (const chunk of streamResponse(res)) { result.push(chunk) } expect(result.join('')).toContain('data: {"name":"Alice"}') expect(result.join('')).toContain('data: {"name":"Bob"}') }) it('validate async SSE response with generator', async () => { const app = new Elysia().get( '/', async function* () { yield sse({ data: { name: 'Charlie' } }) await Bun.sleep(1) yield sse({ data: { name: 'Diana' } }) }, { response: t.Object({ data: t.Object({ name: t.String() }) }) } ) const res = await app.handle(req('/')) expect(res.status).toBe(200) expect(res.headers.get('content-type')).toBe('text/event-stream') }) it('validate streaming response with generator', async () => { const app = new Elysia().get( '/', function* () { yield { message: 'first' } yield { message: 'second' } }, { response: t.Object({ message: t.String() }) } ) const res = await app.handle(req('/')) expect(res.status).toBe(200) const result = [] for await (const chunk of streamResponse(res)) { result.push(chunk) } expect(result.join('')).toContain('"message":"first"') expect(result.join('')).toContain('"message":"second"') }) it('validate SSE with Zod schema (bug report scenario)', async () => { // This test reproduces the exact bug report: // https://github.com/elysiajs/elysia/issues/1490 const Schema = z.object({ data: z.object({ name: z.string() }) }) const app = new Elysia().get( '/', function* () { yield sse({ data: { name: 'Name' } }) }, { response: Schema } ) const res = await app.handle(req('/')) // Should not throw validation error expect(res.status).toBe(200) expect(res.headers.get('content-type')).toBe('text/event-stream') // Verify the stream contains the expected SSE data const result = [] for await (const chunk of streamResponse(res)) { result.push(chunk) } expect(result.join('')).toContain('data: {"name":"Name"}') }) it('handle distinct union', () => { const app = new Elysia() .get('/health', () => ({ status: 'healthy' }) as const, { response: { 200: t.Union([ t.Object({ status: t.Literal('a'), a: t.Object({ b: t.Integer() }) }), t.Object({ status: t.Literal('healthy') }) ]) } }) .listen(3000) const status = app.handle(req('/health')).then((x) => x.status) expect(status).resolves.toBe(200) }) }) ================================================ FILE: test/validator/standalone.test.ts ================================================ import { Elysia, t } from '../../src' import { describe, it, expect } from 'bun:test' import { post } from '../utils' describe('standalone validator', () => { it('handle guard without local schema', async () => { const app = new Elysia() .guard({ schema: 'standalone', body: t.Object({ id: t.Number() }), response: t.Object({ name: t.Literal('cantarella') }) }) .post('/name/:name', ({ params: { name } }) => ({ name: name as 'cantarella' })) const correct = await app.handle( post('/name/cantarella', { id: 1 }) ) expect(correct.status).toBe(200) expect(await correct.json()).toEqual({ name: 'cantarella' }) const incorrect = await app.handle( post('/name/jinhsi', { id: 1 }) ) expect(incorrect.status).toBe(422) }) it('merge guard with local schema', async () => { const app = new Elysia() .guard({ schema: 'standalone', body: t.Object({ id: t.Number() }), response: t.Object({ name: t.Literal('cantarella') }) }) .post( '/name/:name', ({ body, params: { name } }) => ({ ...body, name: name as 'cantarella' }), { response: t.Object({ id: t.Number() }) } ) const correct = await app.handle( post('/name/cantarella', { id: 1 }) ) expect(correct.status).toBe(200) expect(await correct.json()).toEqual({ id: 1, name: 'cantarella' }) const incorrect = await app.handle( post('/name/jinhsi', { id: 1 }) ) expect(incorrect.status).toBe(422) }) it('merge multiple guard without local schema', async () => { const app = new Elysia() .guard({ schema: 'standalone', body: t.Object({ id: t.Number() }), response: t.Object({ name: t.Literal('cantarella') }) }) .post('/name/:name', ({ params: { name } }) => ({ name: name as 'cantarella' })) const correct = await app.handle( post('/name/cantarella', { id: 1 }) ) expect(correct.status).toBe(200) expect(await correct.json()).toEqual({ name: 'cantarella' }) const incorrect = await app.handle( post('/name/jinhsi', { id: 1 }) ) expect(incorrect.status).toBe(422) }) it('merge multiple guard with local schema', async () => { const app = new Elysia() .guard({ schema: 'standalone', response: t.Object({ name: t.Literal('cantarella') }) }) .guard({ schema: 'standalone', body: t.Object({ id: t.Number() }) }) .post( '/name/:name', ({ body, params: { name } }) => ({ ...body, name: name as 'cantarella' }), { response: t.Object({ id: t.Number() }) } ) const correct = await app.handle( post('/name/cantarella', { id: 1 }) ) expect(correct.status).toBe(200) expect(await correct.json()).toEqual({ id: 1, name: 'cantarella' }) const incorrect = await app.handle( post('/name/jinhsi', { id: 1 }) ) expect(incorrect.status).toBe(422) }) it('use override guard when local is not provided', async () => { const app = new Elysia() .guard({ response: t.Object( { name: t.Literal('cantarella') }, { additionalProperties: false } ) }) .guard({ schema: 'standalone', body: t.Object({ id: t.Number() }) }) .post('/name/:name', ({ params: { name } }) => ({ name: name as 'cantarella' })) const correct = await app.handle( post('/name/cantarella', { id: 1 }) ) expect(correct.status).toBe(200) expect(await correct.json()).toEqual({ name: 'cantarella' }) const incorrect = await app.handle( post('/name/jinhsi', { id: 1 }) ) expect(incorrect.status).toBe(422) }) it('override guard when local is provided', async () => { const app = new Elysia({ normalize: false }) .guard({ response: t.Object({ name: t.Literal('cantarella') }) }) .guard({ schema: 'standalone', body: t.Object({ id: t.Number() }) }) .post('/name/:name', ({ body }) => body, { response: t.Object({ id: t.Number() }) }) const correct = await app.handle( post('/name/cantarella', { id: 1 }) ) expect(correct.status).toBe(200) expect(await correct.json()).toEqual({ id: 1 }) const incorrect = await app.handle( post('/name/jinhsi', { name: 'cantarella' }) ) expect(incorrect.status).toBe(422) }) it('merge object guard with additionalProperties via mergeObjectSchemas', async () => { const app = new Elysia({ normalize: false }) .guard({ schema: 'standalone', response: t.Object({ name: t.Literal('cantarella') }) }) .guard({ schema: 'standalone', body: t.Object( { id: t.Number() }, { additionalProperties: true } ) }) .post( '/name/:name', ({ body }) => ({ ...body, name: 'cantarella' }), { response: t.Object( { id: t.Number() }, { additionalProperties: false } ) } ) const correct = await app.handle( post('/name/cantarella', { id: 1, name: 'cantarella' }) ) expect(correct.status).toBe(200) expect(await correct.json()).toEqual({ id: 1, name: 'cantarella' }) const incorrect = await app.handle( post('/name/jinhsi', { id: 1, familia: 'fisalia', a: 'b' }) ) expect(incorrect.status).toBe(422) }) it('override additionalProperties while merging guards', async () => { const app = new Elysia({ normalize: false }) .guard({ schema: 'standalone', body: t.Object( { id: t.Number() }, { additionalProperties: false } ), response: t.Object( { name: t.Literal('cantarella') }, { additionalProperties: false } ) }) .post( '/name/:name', ({ body, params: { name } }) => ({ ...body, name: name as 'cantarella' }), { body: t.Object({ name: t.Literal('cantarella') }), response: t.Object({ id: t.Number() }) } ) const correct = await app.handle( post('/name/cantarella', { id: 1, name: 'cantarella' }) ) expect(correct.status).toBe(200) expect(await correct.json()).toEqual({ id: 1, name: 'cantarella' }) const incorrect = await app.handle( post('/name/jinhsi', { id: 1, family: 'fisalia' }) ) expect(incorrect.status).toBe(422) }) it('handle local scope', async () => { const local = new Elysia() .guard({ schema: 'standalone', body: t.Object({ id: t.Number() }) }) .post( '/local', ({ body }) => ({ success: true, ...body }), { response: t.Object({ success: t.Boolean(), id: t.Number() }) } ) const app = new Elysia().use(local).post( '/main', () => ({ success: true }), { response: t.Object({ success: t.Boolean() }) } ) const correct1 = await app.handle( post('/main', { id: 1, name: 'cantarella' }) ) expect(correct1.status).toBe(200) expect(await correct1.json()).toEqual({ success: true }) const correct2 = await app.handle( post('/local', { id: 1 }) ) expect(correct2.status).toBe(200) expect(await correct2.json()).toEqual({ success: true, id: 1 }) const correct3 = await app.handle(post('/main')) expect(correct3.status).toBe(200) expect(await correct3.json()).toEqual({ success: true }) const incorrect1 = await app.handle( post('/local', { name: 'cantarella' }) ) expect(incorrect1.status).toBe(422) }) it('handle local scope', async () => { const local = new Elysia() .guard({ schema: 'standalone', body: t.Object({ id: t.Number() }) }) .post( '/local', ({ body }) => ({ success: true, ...body }), { response: t.Object({ success: t.Boolean(), id: t.Number() }) } ) const app = new Elysia().use(local).post( '/main', () => ({ success: true }), { response: t.Object({ success: t.Boolean() }) } ) const correct1 = await app.handle( post('/main', { id: 1, name: 'cantarella' }) ) expect(correct1.status).toBe(200) expect(await correct1.json()).toEqual({ success: true }) const correct2 = await app.handle( post('/local', { id: 1 }) ) expect(correct2.status).toBe(200) expect(await correct2.json()).toEqual({ success: true, id: 1 }) const correct3 = await app.handle(post('/main')) expect(correct3.status).toBe(200) expect(await correct3.json()).toEqual({ success: true }) const correct4 = await app.handle( post('/main', { id: 1, name: 'cantarella' }) ) expect(correct4.status).toBe(200) expect(await correct4.json()).toEqual({ success: true }) const incorrect1 = await app.handle( post('/local', { name: 'cantarella' }) ) expect(incorrect1.status).toBe(422) }) it('handle local scope with parent schema', async () => { const local = new Elysia() .guard({ schema: 'standalone', body: t.Object({ id: t.Number() }) }) .post( '/local', ({ body }) => ({ success: true, ...body }), { response: t.Object({ success: t.Boolean(), id: t.Number() }) } ) const app = new Elysia() .use(local) .guard({ schema: 'standalone', body: t.Object({ id: t.Number() }) }) .post( '/main', () => ({ success: true }), { response: t.Object({ success: t.Boolean() }) } ) const correct1 = await app.handle( post('/main', { id: 1, name: 'cantarella' }) ) expect(correct1.status).toBe(200) expect(await correct1.json()).toEqual({ success: true }) const correct2 = await app.handle( post('/local', { id: 1 }) ) expect(correct2.status).toBe(200) expect(await correct2.json()).toEqual({ success: true, id: 1 }) const correct3 = await app.handle( post('/main', { id: 1, name: 'cantarella' }) ) expect(correct3.status).toBe(200) expect(await correct3.json()).toEqual({ success: true }) const incorrect1 = await app.handle( post('/local', { name: 'cantarella' }) ) expect(incorrect1.status).toBe(422) const incorrect2 = await app.handle( post('/main', { name: 'cantarella' }) ) expect(incorrect2.status).toBe(422) }) it('handle scoped scope', async () => { const local = new Elysia() .guard({ schema: 'standalone', as: 'scoped', body: t.Object({ id: t.Number() }) }) .post( '/local', ({ body }) => ({ success: true, ...body }), { response: t.Object({ success: t.Boolean(), id: t.Number() }) } ) const parent = new Elysia().use(local).post( '/parent', () => ({ success: true }), { response: t.Object({ success: t.Boolean() }) } ) const app = new Elysia().use(parent).post( '/main', () => ({ success: true }), { response: t.Object({ success: t.Boolean() }) } ) const correct1 = await app.handle( post('/parent', { id: 1, name: 'cantarella' }) ) expect(correct1.status).toBe(200) expect(await correct1.json()).toEqual({ success: true }) const correct2 = await app.handle( post('/local', { id: 1 }) ) expect(correct2.status).toBe(200) expect(await correct2.json()).toEqual({ success: true, id: 1 }) const correct3 = await app.handle( post('/parent', { id: 1, name: 'cantarella' }) ) expect(correct3.status).toBe(200) expect(await correct3.json()).toEqual({ success: true }) const correct4 = await app.handle(post('/main')) expect(correct4.status).toBe(200) expect(await correct4.json()).toEqual({ success: true }) const incorrect1 = await app.handle( post('/local', { name: 'cantarella' }) ) expect(incorrect1.status).toBe(422) const incorrect2 = await app.handle( post('/parent', { name: 'cantarella' }) ) expect(incorrect2.status).toBe(422) }) it('handle global scope', async () => { const local = new Elysia() .guard({ schema: 'standalone', as: 'global', body: t.Object({ id: t.Number() }) }) .post( '/local', ({ body }) => ({ success: true, ...body }), { response: t.Object({ success: t.Boolean(), id: t.Number() }) } ) const parent = new Elysia().use(local).post( '/parent', () => ({ success: true }), { response: t.Object({ success: t.Boolean() }) } ) const app = new Elysia().use(parent).post( '/main', () => ({ success: true }), { response: t.Object({ success: t.Boolean() }) } ) const correct1 = await app.handle( post('/parent', { id: 1, name: 'cantarella' }) ) expect(correct1.status).toBe(200) expect(await correct1.json()).toEqual({ success: true }) const correct2 = await app.handle( post('/local', { id: 1 }) ) expect(correct2.status).toBe(200) expect(await correct2.json()).toEqual({ success: true, id: 1 }) const correct3 = await app.handle( post('/parent', { id: 1, name: 'cantarella' }) ) expect(correct3.status).toBe(200) expect(await correct3.json()).toEqual({ success: true }) const incorrect1 = await app.handle( post('/local', { name: 'cantarella' }) ) expect(incorrect1.status).toBe(422) const incorrect2 = await app.handle( post('/parent', { name: 'cantarella' }) ) expect(incorrect2.status).toBe(422) const incorrect3 = await app.handle(post('/main')) expect(incorrect3.status).toBe(422) }) it('handle every schema type on local scope', async () => { const app = new Elysia() .guard({ schema: 'standalone', body: t.Object({ family: t.String() }), headers: t.Object({ family: t.String() }), query: t.Object({ family: t.String() }), params: t.Object({ family: t.String() }), response: t.Object({ family: t.String() }), cookie: t.Object({ family: t.String() }) }) .post( '/:family/:name', ({ body }) => ({ ...body, name: 'cantarella' }), { body: t.Object({ name: t.String() }), headers: t.Object({ name: t.String() }), query: t.Object({ name: t.String() }), params: t.Object({ name: t.String() }), response: t.Object({ name: t.String() }), cookie: t.Object({ name: t.String() }) } ) const correct = await app.handle( new Request( 'http://localhost:3000/fisalia/cantarella?name=cantarella&family=fisalia', { method: 'POST', headers: { 'Content-Type': 'application/json', family: 'fisalia', name: 'cantarella', cookie: 'name=cantarella;family=fisalia' }, body: JSON.stringify({ family: 'fisalia', name: 'cantarella' }) } ) ) expect(correct.status).toBe(200) expect(await correct.json()).toEqual({ family: 'fisalia', name: 'cantarella' }) const incorrect = await app.handle( post('/hsi/jinhsi', { id: 1 }) ) expect(incorrect.status).toBe(422) }) it('handle every schema type on scoped scope', async () => { const local = new Elysia() .guard({ schema: 'standalone', as: 'scoped', body: t.Object({ family: t.String() }), headers: t.Object({ family: t.String() }), query: t.Object({ family: t.String() }), params: t.Object({ family: t.String() }), response: t.Object({ family: t.String() }), cookie: t.Object({ family: t.String() }) }) .post('/:family', ({ body }) => body) const app = new Elysia() .use(local) .post('/:family/:name', ({ body }) => body, { body: t.Object({ name: t.String() }), headers: t.Object({ name: t.String() }), query: t.Object({ name: t.String() }), params: t.Object({ name: t.String() }), response: t.Object({ name: t.String() }), cookie: t.Object({ name: t.String() }) }) const correct1 = await app.handle( new Request( 'http://localhost:3000/fisalia/cantarella?name=cantarella&family=fisalia', { method: 'POST', headers: { 'Content-Type': 'application/json', family: 'fisalia', name: 'cantarella', cookie: 'name=cantarella;family=fisalia' }, body: JSON.stringify({ family: 'fisalia', name: 'cantarella' }) } ) ) expect(correct1.status).toBe(200) expect(await correct1.json()).toEqual({ family: 'fisalia', name: 'cantarella' }) const correct2 = await app.handle( new Request('http://localhost:3000/fisalia?family=fisalia', { method: 'POST', headers: { 'Content-Type': 'application/json', family: 'fisalia', cookie: 'name=cantarella;family=fisalia' }, body: JSON.stringify({ family: 'fisalia' }) }) ) expect(correct2.status).toBe(200) expect(await correct2.json()).toEqual({ family: 'fisalia' }) const incorrect1 = await app.handle( post('/hsi/jinhsi', { id: 1 }) ) expect(incorrect1.status).toBe(422) const incorrect2 = await app.handle( post('/hsi', { id: 1 }) ) expect(incorrect2.status).toBe(422) }) it('handle every schema type on global scope', async () => { const local = new Elysia() .guard({ schema: 'standalone', as: 'global', body: t.Object({ family: t.String() }), headers: t.Object({ family: t.String() }), query: t.Object({ family: t.String() }), params: t.Object({ family: t.String() }), response: t.Object({ family: t.String() }), cookie: t.Object({ family: t.String() }) }) .post('/:family', ({ body }) => body) const parent = new Elysia() .use(local) .post('/family/:family/:name', ({ body }) => body, { body: t.Object({ name: t.String() }), headers: t.Object({ name: t.String() }), query: t.Object({ name: t.String() }), params: t.Object({ name: t.String() }), response: t.Object({ name: t.String() }), cookie: t.Object({ name: t.String() }) }) const app = new Elysia() .use(parent) .post('/:family/:name', ({ body }) => body, { body: t.Object({ name: t.String() }), headers: t.Object({ name: t.String() }), query: t.Object({ name: t.String() }), params: t.Object({ name: t.String() }), response: t.Object({ name: t.String() }), cookie: t.Object({ name: t.String() }) }) const correct1 = await app.handle( new Request( 'http://localhost:3000/fisalia/cantarella?name=cantarella&family=fisalia', { method: 'POST', headers: { 'Content-Type': 'application/json', family: 'fisalia', name: 'cantarella', cookie: 'name=cantarella;family=fisalia' }, body: JSON.stringify({ family: 'fisalia', name: 'cantarella' }) } ) ) expect(correct1.status).toBe(200) expect(await correct1.json()).toEqual({ family: 'fisalia', name: 'cantarella' }) const correct2 = await app.handle( new Request('http://localhost:3000/fisalia?family=fisalia', { method: 'POST', headers: { 'Content-Type': 'application/json', family: 'fisalia', cookie: 'name=cantarella;family=fisalia' }, body: JSON.stringify({ family: 'fisalia' }) }) ) expect(correct2.status).toBe(200) expect(await correct2.json()).toEqual({ family: 'fisalia' }) const correct3 = await app.handle( new Request( 'http://localhost:3000/family/fisalia/cantarella?name=cantarella&family=fisalia', { method: 'POST', headers: { 'Content-Type': 'application/json', family: 'fisalia', name: 'cantarella', cookie: 'name=cantarella;family=fisalia' }, body: JSON.stringify({ family: 'fisalia', name: 'cantarella' }) } ) ) expect(correct3.status).toBe(200) expect(await correct3.json()).toEqual({ family: 'fisalia', name: 'cantarella' }) const incorrect1 = await app.handle( post('/hsi/jinhsi', { id: 1 }) ) expect(incorrect1.status).toBe(422) const incorrect2 = await app.handle( post('/hsi', { id: 1 }) ) expect(incorrect2.status).toBe(422) const incorrect3 = await app.handle( post('/family/hsi/jinhsi', { id: 1 }) ) expect(incorrect3.status).toBe(422) }) }) ================================================ FILE: test/validator/validator.test.ts ================================================ import { Elysia, t } from '../../src' import { describe, expect, it } from 'bun:test' import { post, req } from '../utils' describe('Validator Additional Case', () => { it('validate beforeHandle', async () => { const app = new Elysia() .get('/', () => 'Mutsuki need correction 💢💢💢', { beforeHandle: () => 'Mutsuki need correction 💢💢💢', response: t.String() }) .get('/invalid', () => 1 as any, { beforeHandle() { return 1 as any }, response: t.String() }) const res = await app.handle(req('/')) const invalid = await app.handle(req('/invalid')) expect(await res.text()).toBe('Mutsuki need correction 💢💢💢') expect(res.status).toBe(200) expect(invalid.status).toBe(422) }) it('validate afterHandle', async () => { const app = new Elysia() .get('/', () => 'Mutsuki need correction 💢💢💢', { afterHandle: () => 'Mutsuki need correction 💢💢💢', response: t.String() }) .get('/invalid', () => 1 as any, { afterHandle: () => 1 as any, response: t.String() }) const res = await app.handle(req('/')) const invalid = await app.handle(req('/invalid')) expect(await res.text()).toBe('Mutsuki need correction 💢💢💢') expect(res.status).toBe(200) expect(invalid.status).toBe(422) }) it('validate beforeHandle with afterHandle', async () => { const app = new Elysia() .get('/', () => 'Mutsuki need correction 💢💢💢', { beforeHandle() {}, afterHandle() { return 'Mutsuki need correction 💢💢💢' }, response: t.String() }) .get('/invalid', () => 1 as any, { afterHandle() { return 1 as any }, response: t.String() }) const res = await app.handle(req('/')) const invalid = await app.handle(req('/invalid')) expect(await res.text()).toBe('Mutsuki need correction 💢💢💢') expect(res.status).toBe(200) expect(invalid.status).toBe(422) }) it('handle guard hook', async () => { const app = new Elysia().guard( { query: t.Object({ name: t.String() }) }, (app) => app.post('/user', ({ query: { name } }) => name, { body: t.Object({ id: t.Number(), username: t.String(), profile: t.Object({ name: t.String() }) }) }) ) const body = JSON.stringify({ id: 6, username: '', profile: { name: 'A' } }) const valid = await app.handle( new Request('http://localhost/user?name=salt', { method: 'POST', body, headers: { 'content-type': 'application/json', 'content-length': body.length.toString() } }) ) expect(await valid.text()).toBe('salt') expect(valid.status).toBe(200) const invalidQuery = await app.handle( new Request('http://localhost/user', { method: 'POST', body: JSON.stringify({ id: 6, username: '', profile: { name: 'A' } }) }) ) expect(invalidQuery.status).toBe(422) const invalidBody = await app.handle( new Request('http://localhost/user?name=salt', { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ id: 6, username: '', profile: {} }) }) ) expect(invalidBody.status).toBe(422) }) it('inherits cookie on guard', async () => { const app = new Elysia() .guard({ cookie: t.Cookie({ session: t.String() }) }) .get('/', ({ cookie: { session } }) => session.value ? session.value : 'Empty' ) const res = await Promise.all([ app.handle(req('/')), app.handle(req('/', { headers: { Cookie: 'session=value' } })) ]) expect(res[0].status).toBe(422) expect(res[1].status).toBe(200) }) it('handle record', async () => { const app = new Elysia().get('/', () => 'SAFE', { query: t.Record(t.String(), t.String()) }) const response = await app.handle(req('/?x=1')) expect(response.status).toBe(200) }) }) ================================================ FILE: test/ws/aot.test.ts ================================================ import { describe, it } from 'bun:test' import { Elysia } from '../../src' import { newWebsocket, wsOpen, wsClosed } from './utils' describe('WebSocket with AoT disabled', () => { it('should connect and close', async () => { const app = new Elysia({ aot: false }) .ws('/ws', { message() {} }) .listen(0) const ws = newWebsocket(app.server!) await wsOpen(ws) await wsClosed(ws) await app.stop(true) }) }) ================================================ FILE: test/ws/connection.test.ts ================================================ import { describe, it, expect } from 'bun:test' import { Elysia, t } from '../../src' import { newWebsocket, wsOpen, wsClose, wsClosed } from './utils' import { req } from '../utils' describe('WebSocket connection', () => { it('should connect and close', async () => { const app = new Elysia() .ws('/ws', { message() {} }) .listen(0) const ws = newWebsocket(app.server!) await wsOpen(ws) await wsClosed(ws) app.stop() }) it('should close by server', async () => { const app = new Elysia() .ws('/ws', { message(ws) { ws.close() } }) .listen(0) const ws = newWebsocket(app.server!) await wsOpen(ws) ws.send('close me!') const { wasClean, code } = await wsClose(ws) expect(wasClean).toBe(false) expect(code).toBe(1000) // going away -> https://www.rfc-editor.org/rfc/rfc6455.html#section-7.4.1 app.stop() }) it('should terminate by server', async () => { const app = new Elysia() .ws('/ws', { message(ws) { ws.terminate() } }) .listen(0) const ws = newWebsocket(app.server!) await wsOpen(ws) ws.send('close me!') const { wasClean, code } = await wsClose(ws) expect(wasClean).toBe(false) expect(code).toBe(1006) // closed abnormally -> https://www.rfc-editor.org/rfc/rfc6455.html#section-7.4.1 app.stop() }) it('should separate get from ws', async () => { const app = new Elysia() .ws('/ws', { message() {} }) .get('/ws', () => 'hi') .listen(0) const ws = newWebsocket(app.server!) await wsOpen(ws) await wsClosed(ws) const response = await app.handle(req('/ws')).then((x) => x.text()) expect(response).toBe('hi') app.stop() }) it('should separate all from ws', async () => { const app = new Elysia() .all('/ws', () => 'hi') .ws('/ws', { message() {} }) .listen(0) const ws = newWebsocket(app.server!) await wsOpen(ws) await wsClosed(ws) const response = await app.handle(req('/ws')).then((x) => x.text()) expect(response).toBe('hi') app.stop() }) it('should separate dynamic get from ws', async () => { const app = new Elysia() .ws('/ws/:id', { message() {} }) .get('/ws/:id', () => 'hi') .listen(0) const ws = newWebsocket(app.server!, '/ws/1') await wsOpen(ws) await wsClosed(ws) const response = await app.handle(req('/ws/1')).then((x) => x.text()) expect(response).toBe('hi') app.stop() }) it('should separate dynamic all from ws', async () => { const app = new Elysia() .all('/ws/:id', () => 'hi') .ws('/ws/:id', { message() {} }) .listen(0) const ws = newWebsocket(app.server!, '/ws/1') await wsOpen(ws) await wsClosed(ws) const response = await app.handle(req('/ws/1')).then((x) => x.text()) expect(response).toBe('hi') app.stop() }) it('handle derive, resolve', async () => { let sessionId: string | undefined let user: { id: '123'; name: 'Jane Doe' } | undefined const app = new Elysia() .derive(() => ({ sessionId: '123' })) .resolve(() => ({ getUser() { return { id: '123', name: 'Jane Doe' } as const } })) .ws('/ws', { open(ws) { sessionId = ws.data.sessionId user = ws.data.getUser() } }) .listen(0) const ws = newWebsocket(app.server!, '/ws') await wsOpen(ws) await wsClosed(ws) expect(sessionId).toEqual('123') expect(user).toEqual({ id: '123', name: 'Jane Doe' }) }) it('call ping/pong', async () => { let pinged = false let ponged = false const app = new Elysia() .ws('/', { ping() { pinged = true }, pong() { ponged = true }, async message(ws) { } }) .listen(0) const ws = new WebSocket(`ws://localhost:${app.server?.port}`) await new Promise((resolve) => { ws.addEventListener('open', () => { ws.ping() ws.send('df') ws.pong() resolve() }, { once: true }) }) await Bun.sleep(3) expect(pinged).toBe(true) expect(ponged).toBe(true) }) }) ================================================ FILE: test/ws/destructuring.test.ts ================================================ import { describe, it, expect } from 'bun:test' import { Elysia } from '../../src' import { newWebsocket, wsOpen, wsClosed, wsMessage } from './utils' describe('WebSocket destructuring', () => { it('should destructure', async () => { const app = new Elysia() .ws('/ws', { async open(ws) { const { subscribe, isSubscribed, publish, unsubscribe, cork, send, // close, // terminate } = ws subscribe('asdf') const subscribed = isSubscribed('asdf') publish('asdf', 'data') unsubscribe('asdf') cork(() => ws) send('Hello!' + subscribed) // malloc error on macOS // close() // terminate() } }) .listen(0) const ws = newWebsocket(app.server!) const message = wsMessage(ws) await wsOpen(ws) const { type, data } = await message expect(type).toBe('message') expect(data).toBe('Hello!true') await wsClosed(ws) app.stop() }) }) ================================================ FILE: test/ws/message.test.ts ================================================ import { describe, it, expect } from 'bun:test' import { Elysia, t } from '../../src' import { newWebsocket, wsOpen, wsMessage, wsClosed } from './utils' import z from 'zod' describe('WebSocket message', () => { it('should send & receive', async () => { const app = new Elysia() .ws('/ws', { message(ws, message) { ws.send(message) } }) .listen(0) const ws = newWebsocket(app.server!) await wsOpen(ws) const message = wsMessage(ws) ws.send('Hello!') const { type, data } = await message expect(type).toBe('message') expect(data).toBe('Hello!') await wsClosed(ws) app.stop() }) it('should respond with remoteAddress', async () => { const app = new Elysia() .ws('/ws', { message(ws) { ws.send(ws.remoteAddress) } }) .listen(0) const ws = newWebsocket(app.server!) await wsOpen(ws) const message = wsMessage(ws) ws.send('Hello!') const { type, data } = await message expect(type).toBe('message') expect(data === '::1' || data === '::ffff:127.0.0.1').toBeTruthy() await wsClosed(ws) app.stop() }) // it('should subscribe & publish', async () => { // const app = new Elysia() // .ws('/ws', { // open(ws) { // ws.subscribe('asdf') // }, // message(ws) { // ws.publish('asdf', ws.isSubscribed('asdf')) // } // }) // .listen(0) // const wsBob = newWebsocket(app.server!) // const wsAlice = newWebsocket(app.server!) // await wsOpen(wsBob) // await wsOpen(wsAlice) // const messageBob = wsMessage(wsBob) // wsAlice.send('Hello!') // const { type, data } = await messageBob // expect(type).toBe('message') // expect(data).toBe('true') // await wsClosed(wsBob) // await wsClosed(wsAlice) // app.stop() // }) it('should unsubscribe', async () => { const app = new Elysia() .ws('/ws', { open(ws) { ws.subscribe('asdf') }, message(ws, message) { if (message === 'unsubscribe') { ws.unsubscribe('asdf') } ws.send(ws.isSubscribed('asdf')) } }) .listen(0) const ws = newWebsocket(app.server!) await wsOpen(ws) const subscribedMessage = wsMessage(ws) ws.send('Hello!') const subscribed = await subscribedMessage expect(subscribed.type).toBe('message') expect(subscribed.data).toBe('true') const unsubscribedMessage = wsMessage(ws) ws.send('unsubscribe') const unsubscribed = await unsubscribedMessage expect(unsubscribed.type).toBe('message') expect(unsubscribed.data).toBe('false') await wsClosed(ws) app.stop() }) it('should validate success', async () => { const app = new Elysia() .ws('/ws', { body: t.Object({ message: t.String() }), message(ws, { message }) { ws.send(message) } }) .listen(0) const ws = newWebsocket(app.server!) await wsOpen(ws) const message = wsMessage(ws) ws.send(JSON.stringify({ message: 'Hello!' })) const { type, data } = await message expect(type).toBe('message') expect(data).toBe('Hello!') await wsClosed(ws) app.stop() }) it('should validate fail', async () => { const app = new Elysia() .ws('/ws', { body: t.Object({ message: t.String() }), message(ws, { message }) { ws.send(message) } }) .listen(0) const ws = newWebsocket(app.server!) await wsOpen(ws) const message = wsMessage(ws) ws.send('Hello!') const { type, data } = await message expect(type).toBe('message') expect(data).toInclude('Expected') await wsClosed(ws) app.stop() }) it('should validate standard schema success', async () => { const app = new Elysia() .ws('/ws', { body: z.object({ message: z.string() }), message(ws, { message }) { ws.send(message) } }) .listen(0) const ws = newWebsocket(app.server!) await wsOpen(ws) const message = wsMessage(ws) ws.send(JSON.stringify({ message: 'Hello!' })) const { type, data } = await message expect(type).toBe('message') expect(data).toBe('Hello!') await wsClosed(ws) app.stop() }) it('should validate standard schema fail', async () => { const app = new Elysia() .ws('/ws', { body: z.object({ message: z.string() }), message(ws, { message }) { ws.send(message) } }) .listen(0) const ws = newWebsocket(app.server!) await wsOpen(ws) const message = wsMessage(ws) ws.send('Hello!') const { type, data } = await message expect(type).toBe('message') expect(data).toInclude('validation') await wsClosed(ws) app.stop() }) it('should parse objects', async () => { const app = new Elysia() .ws('/ws', { message(ws, raw) { ws.send(raw) } }) .listen(0) const ws = newWebsocket(app.server!) await wsOpen(ws) const message = wsMessage(ws) ws.send(JSON.stringify({ message: 'Hello!' })) const { type, data } = await message expect(type).toBe('message') expect(data).toInclude('{"message":"Hello!"}') await wsClosed(ws) app.stop() }) it('should parse arrays', async () => { const app = new Elysia() .ws('/ws', { message(ws, raw) { ws.send(JSON.stringify(raw)) } }) .listen(0) const ws = newWebsocket(app.server!) await wsOpen(ws) const message = wsMessage(ws) ws.send(JSON.stringify([{ message: 'Hello!' }])) const { type, data } = await message expect(type).toBe('message') expect(data).toInclude('[{"message":"Hello!"}]') await wsClosed(ws) app.stop() }) it('should parse strings', async () => { const app = new Elysia() .ws('/ws', { message(ws, raw) { ws.send(JSON.stringify(raw)) } }) .listen(0) const ws = newWebsocket(app.server!) await wsOpen(ws) const message = wsMessage(ws) ws.send(JSON.stringify("Hello!")) const { type, data } = await message expect(type).toBe('message') expect(data).toInclude('"Hello!"') await wsClosed(ws) app.stop() }) it('should parse numbers', async () => { const app = new Elysia() .ws('/ws', { message(ws, raw) { ws.send(JSON.stringify(raw)) } }) .listen(0) const ws = newWebsocket(app.server!) await wsOpen(ws) const message = wsMessage(ws) ws.send(JSON.stringify(1234567890)) const { type, data } = await message expect(type).toBe('message') expect(data).toInclude('1234567890') await wsClosed(ws) app.stop() }) it('should parse true', async () => { const app = new Elysia() .ws('/ws', { message(ws, raw) { ws.send(JSON.stringify(raw)) } }) .listen(0) const ws = newWebsocket(app.server!) await wsOpen(ws) const message = wsMessage(ws) ws.send(JSON.stringify(true)) const { type, data } = await message expect(type).toBe('message') expect(data).toInclude('true') await wsClosed(ws) app.stop() }) it('should parse false', async () => { const app = new Elysia() .ws('/ws', { message(ws, raw) { ws.send(JSON.stringify(raw)) } }) .listen(0) const ws = newWebsocket(app.server!) await wsOpen(ws) const message = wsMessage(ws) ws.send(JSON.stringify(false)) const { type, data } = await message expect(type).toBe('message') expect(data).toInclude('false') await wsClosed(ws) app.stop() }) it('should parse null', async () => { const app = new Elysia() .ws('/ws', { message(ws, raw) { ws.send(JSON.stringify(raw)) } }) .listen(0) const ws = newWebsocket(app.server!) await wsOpen(ws) const message = wsMessage(ws) ws.send(JSON.stringify(null)) const { type, data } = await message expect(type).toBe('message') expect(data).toInclude('null') await wsClosed(ws) app.stop() }) it('should parse not parse /hello', async () => { const app = new Elysia() .ws('/ws', { message(ws, raw) { ws.send(JSON.stringify(raw)) } }) .listen(0) const ws = newWebsocket(app.server!) await wsOpen(ws) const message = wsMessage(ws) ws.send(JSON.stringify("/hello")) const { type, data } = await message expect(type).toBe('message') expect(data).toInclude('/hello') await wsClosed(ws) app.stop() }) it('should send from plugin', async () => { const plugin = new Elysia().ws('/ws', { message(ws, message) { ws.send(message) } }) const app = new Elysia().use(plugin).listen(0) const ws = newWebsocket(app.server!) await wsOpen(ws) const message = wsMessage(ws) ws.send('Hello!') const { type, data } = await message expect(type).toBe('message') expect(data).toBe('Hello!') await wsClosed(ws) app.stop() }) it('should be able to receive binary data', async () => { const plugin = new Elysia().ws('/ws', { message(ws, message) { ws.send(message) } }) const app = new Elysia().use(plugin).listen(0) const ws = newWebsocket(app.server!) await wsOpen(ws) const message = wsMessage(ws) ws.send(new Uint8Array(3)) const { type, data } = await message expect(type).toBe('message') // @ts-ignore expect(data).toEqual(new Uint8Array(3)) await wsClosed(ws) app.stop() }) it('should send & receive', async () => { const app = new Elysia() .ws('/ws', { message(ws, message) { ws.send(message) } }) .listen(0) const ws = newWebsocket(app.server!) await wsOpen(ws) const message = wsMessage(ws) ws.send(' ') const { type, data } = await message expect(type).toBe('message') expect(data).toBe(' ') await wsClosed(ws) app.stop() }) it('handle error', async () => { const app = new Elysia() .ws('/ws', { error() { return 'caught' }, message(ws, message) { throw new Error('A') } }) .listen(0) const ws = newWebsocket(app.server!) await wsOpen(ws) const message = wsMessage(ws) ws.send('Hello!') const { type, data } = await message expect(type).toBe('message') expect(data).toBe('caught') await wsClosed(ws) app.stop() }) it('handle error with onError', async () => { const app = new Elysia() .onError(() => { return 'caught' }) .ws('/ws', { message(ws, message) { throw new Error('A') } }) .listen(0) const ws = newWebsocket(app.server!) await wsOpen(ws) const message = wsMessage(ws) ws.send('Hello!') const { type, data } = await message expect(type).toBe('message') expect(data).toBe('caught') await wsClosed(ws) app.stop() }) it('handle validation error with onError', async () => { const app = new Elysia() .onError(() => { return 'caught' }) .ws('/ws', { body: t.Object({ name: t.String() }), message(ws, message) { return ws.send(message) } }) .listen(0) const ws = newWebsocket(app.server!) await wsOpen(ws) const message = wsMessage(ws) ws.send(JSON.stringify({ name: 123, // expecting a string })) const { type, data } = await message expect(type).toBe('message') expect(data).toBe('caught') await wsClosed(ws) app.stop() }) }) ================================================ FILE: test/ws/utils.ts ================================================ import type { Server } from 'bun' export const newWebsocket = (server: Server, path = '/ws') => new WebSocket(`ws://${server.hostname}:${server.port}${path}`, {}) export const wsOpen = (ws: WebSocket) => new Promise((resolve) => { ws.onopen = resolve }) export const wsClose = async (ws: WebSocket) => new Promise((resolve) => { ws.onclose = resolve }) export const wsClosed = async (ws: WebSocket) => { const closed = wsClose(ws) ws.close() await closed } export const wsMessage = (ws: WebSocket) => new Promise>((resolve) => { ws.onmessage = resolve }) ================================================ FILE: tsconfig.dts.json ================================================ { "compilerOptions": { "preserveSymlinks": true, /* Visit https://aka.ms/tsconfig to read more about this file */ /* Projects */ // "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */ // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */ // "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */ // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */ // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */ // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */ /* Language and Environment */ "target": "ES2021", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */ "lib": ["ESNext"], /* Specify a set of bundled library declaration files that describe the target runtime environment. */ // "jsx": "preserve", /* Specify what JSX code is generated. */ // "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */ // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */ // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */ // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */ // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */ // "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */ // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */ // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */ // "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */ /* Modules */ "module": "ES2022", /* Specify what module code is generated. */ "rootDir": "./src", /* Specify the root folder within your source files. */ "moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */ // "baseUrl": "./src", /* Specify the base directory to resolve non-relative module names. */ // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */ // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */ // "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */ // "types": ["bun-types"], /* Specify type package names to be included without being referenced in a source file. */ // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ // "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */ "resolveJsonModule": true, /* Enable importing .json files. */ // "noResolve": true, /* Disallow 'import's, 'require's or ''s from expanding the number of files TypeScript should add to a project. */ /* JavaScript Support */ // "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */ // "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */ // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */ /* Emit */ "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */ // "declarationMap": true, /* Create sourcemaps for d.ts files. */ "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */ // "sourceMap": true, /* Create source map files for emitted JavaScript files. */ // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */ "outDir": "./dist", /* Specify an output folder for all emitted files. */ // "removeComments": true, /* Disable emitting comments. */ // "noEmit": true, /* Disable emitting files from a compilation. */ // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */ // "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types. */ // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */ // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */ // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */ // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */ // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */ // "newLine": "crlf", /* Set the newline character for emitting files. */ // "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */ // "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */ // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */ // "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */ // "declarationDir": "./", /* Specify the output directory for generated declaration files. */ // "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */ /* Interop Constraints */ // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */ "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */ "esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */ // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */ "forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */ /* Type Checking */ "strict": true, /* Enable all strict type-checking options. */ // "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */ // "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */ // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */ // "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */ // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */ // "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */ // "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */ // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */ // "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */ // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */ // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */ // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */ // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */ // "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */ // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */ // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */ // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */ // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */ /* Completeness */ // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ "skipLibCheck": true, /* Skip type checking all .d.ts files. */ }, "exclude": ["node_modules", "test", "example", "dist", "build.ts"] // "include": ["src/**/*"] } ================================================ FILE: tsconfig.json ================================================ { "compilerOptions": { "resolveJsonModule": true, "preserveSymlinks": true, /* Visit https://aka.ms/tsconfig to read more about this file */ /* Projects */ // "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */ // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */ // "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */ // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */ // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */ // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */ /* Language and Environment */ "target": "ES2021", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */ "lib": ["ESNext"], /* Specify a set of bundled library declaration files that describe the target runtime environment. */ // "jsx": "preserve", /* Specify what JSX code is generated. */ // "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */ // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */ // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */ // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */ // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */ // "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */ // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */ // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */ // "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */ /* Modules */ "module": "ESNext", /* Specify what module code is generated. */ // "rootDir": "./src", /* Specify the root folder within your source files. */ "moduleResolution": "bundler", /* Specify how TypeScript looks up a file from a given module specifier. */ // "baseUrl": "./src", /* Specify the base directory to resolve non-relative module names. */ // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */ // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */ // "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */ // "types": ["bun-types"], /* Specify type package names to be included without being referenced in a source file. */ // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ // "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */ // "resolveJsonModule": true, /* Enable importing .json files. */ // "noResolve": true, /* Disallow 'import's, 'require's or ''s from expanding the number of files TypeScript should add to a project. */ /* JavaScript Support */ // "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */ // "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */ // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */ /* Emit */ "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */ // "declarationMap": true, /* Create sourcemaps for d.ts files. */ // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */ // "sourceMap": true, /* Create source map files for emitted JavaScript files. */ // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */ // "outDir": "./dist", /* Specify an output folder for all emitted files. */ // "removeComments": true, /* Disable emitting comments. */ "noEmit": true, /* Disable emitting files from a compilation. */ // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */ // "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types. */ // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */ // r"sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */ // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */ // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */ // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */ // "newLine": "crlf", /* Set the newline character for emitting files. */ // "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */ // "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */ // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */ // "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */ // "declarationDir": "./", /* Specify the output directory for generated declaration files. */ // "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */ /* Interop Constraints */ // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */ "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */ "esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */ // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */ "forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */ /* Type Checking */ "strict": true, /* Enable all strict type-checking options. */ // "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */ // "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */ // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */ // "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */ // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */ // "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */ // "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */ // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */ // "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */ // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */ // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */ // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */ // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */ // "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */ // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */ // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */ // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */ // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */ /* Completeness */ // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ "skipLibCheck": true, /* Skip type checking all .d.ts files. */ }, "exclude": ["node_modules"] // "include": ["src/**/*"] } ================================================ FILE: tsconfig.test.json ================================================ { "compilerOptions": { "preserveSymlinks": true, /* Visit https://aka.ms/tsconfig to read more about this file */ /* Projects */ "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */ // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */ // "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */ // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */ // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */ // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */ /* Language and Environment */ "target": "ES2020", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */ "lib": ["ESNext"], /* Specify a set of bundled library declaration files that describe the target runtime environment. */ // "jsx": "preserve", /* Specify what JSX code is generated. */ // "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */ // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */ // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */ // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */ // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */ // "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */ // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */ // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */ // "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */ /* Modules */ "module": "ES2022", /* Specify what module code is generated. */ // "rootDir": "./src", /* Specify the root folder within your source files. */ "moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */ // "baseUrl": "./src", /* Specify the base directory to resolve non-relative module names. */ // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */ // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */ // "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */ "types": ["@types/bun"], /* Specify type package names to be included without being referenced in a source file. */ // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ // "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */ "resolveJsonModule": true, /* Enable importing .json files. */ // "noResolve": true, /* Disallow 'import's, 'require's or ''s from expanding the number of files TypeScript should add to a project. */ /* JavaScript Support */ // "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */ // "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */ // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */ /* Emit */ // "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */ // "declarationMap": true, /* Create sourcemaps for d.ts files. */ // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */ // "sourceMap": true, /* Create source map files for emitted JavaScript files. */ // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */ // "outDir": "./dist", /* Specify an output folder for all emitted files. */ // "removeComments": true, /* Disable emitting comments. */ "noEmit": true, /* Disable emitting files from a compilation. */ // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */ // "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types. */ // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */ // r"sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */ // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */ // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */ // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */ // "newLine": "crlf", /* Set the newline character for emitting files. */ // "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */ // "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */ // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */ // "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */ // "declarationDir": "./", /* Specify the output directory for generated declaration files. */ // "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */ /* Interop Constraints */ // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */ "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */ "esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */ // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */ "forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */ /* Type Checking */ "strict": true, /* Enable all strict type-checking options. */ // "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */ // "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */ // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */ // "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */ // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */ // "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */ // "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */ // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */ // "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */ // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */ // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */ // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */ // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */ // "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */ // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */ // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */ // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */ // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */ /* Completeness */ // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ "skipLibCheck": true, /* Skip type checking all .d.ts files. */ }, "exclude": ["node_modules", "test/cloudflare/**/*"], "include": ["test/types/**/*"] }