diff --git a/.dev.vars.example b/.dev.vars.example new file mode 100644 index 0000000..5e403d9 --- /dev/null +++ b/.dev.vars.example @@ -0,0 +1,4 @@ +CLIENT_SECRET=replace-for-local-development +S3_ACCESS_KEY=your-r2-s3-access-key +S3_SECRET_ACCESS_KEY=your-r2-s3-secret-key +S3_ENDPOINT=https://your-account-id.r2.cloudflarestorage.com/files/ diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..fb06a91 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,21 @@ +name: CI + +on: + push: + pull_request: + +permissions: + contents: read + +jobs: + check: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: pnpm/action-setup@v4 + - uses: actions/setup-node@v4 + with: + node-version-file: .nvmrc + cache: pnpm + - run: pnpm install --frozen-lockfile + - run: pnpm run check diff --git a/.gitignore b/.gitignore index 10c3940..0c4e5d3 100644 --- a/.gitignore +++ b/.gitignore @@ -170,4 +170,7 @@ dist .dev.vars .wrangler/ -.secrets.json \ No newline at end of file +.secrets.json + +# Generated Worker bundle and OpenAPI document +dist/ diff --git a/.nvmrc b/.nvmrc new file mode 100644 index 0000000..a45fd52 --- /dev/null +++ b/.nvmrc @@ -0,0 +1 @@ +24 diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000..521113b --- /dev/null +++ b/.prettierignore @@ -0,0 +1,6 @@ +node_modules/ +pnpm-lock.yaml +dist/ +coverage/ +.wrangler/ +tests/contract/__snapshots__/ diff --git a/.prettierrc.json b/.prettierrc.json new file mode 100644 index 0000000..ebb4af5 --- /dev/null +++ b/.prettierrc.json @@ -0,0 +1,5 @@ +{ + "singleQuote": true, + "printWidth": 100, + "trailingComma": "all" +} diff --git a/README.md b/README.md index 493370c..4edebda 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,189 @@ # rfiles -Hash based file server on Cloudflare Workers & R2 -## Publish +Cloudflare Workers와 R2에서 실행되는 해시 기반 파일 서버입니다. Hono를 사용하며, +API 스키마와 라우트 설명으로 OpenAPI 3.1 문서를 자동 생성합니다. +`/docs`에서 Scalar로 API 문서를 확인하고 요청을 실행할 수 있습니다. -1. [Create R2 bucket](https://developers.cloudflare.com/r2/get-started/) -2. Copy `wrangler.toml.example` to `wrangler.toml` and modify values (e.g. `account_id`, `bucket_name`) -3. Run `npx wrangler publish` +## 개발 환경 + +Node.js 24를 사용합니다(`.nvmrc` 기준, 최소 22.12). +pnpm 버전은 `package.json`에 10.28.0으로 고정되어 있습니다. + +```sh +corepack enable +pnpm install --frozen-lockfile +cp .dev.vars.example .dev.vars +pnpm run dev +``` + +`.dev.vars`에 `CLIENT_SECRET`, `S3_ACCESS_KEY`, `S3_SECRET_ACCESS_KEY`, +`S3_ENDPOINT`를 설정합니다. S3 엔드포인트에는 버킷 경로와 마지막 슬래시를 포함해야 합니다. +예: `https://.r2.cloudflarestorage.com/files/` +인증 정보는 Git에 커밋하지 않습니다. 기존 `FILES_BUCKET` 바인딩, 버킷 이름, +Worker 이름, 계정, 호환성 날짜는 `wrangler.toml`에 유지되어 있습니다. + +- [Scalar API 문서](http://localhost:8787/docs) +- [자동 생성된 OpenAPI JSON](http://localhost:8787/openapi.json) + +Wrangler는 로컬에서 R2를 에뮬레이션합니다. 서명된 업로드 URL은 `S3_ENDPOINT`를 +가리키므로, 해당 URL에 업로드한 파일은 Wrangler의 로컬 버킷에 저장되지 않습니다. +자동화 테스트는 로컬 R2와 테스트용 서명 자격 증명을 사용합니다. +실제 서명 URL로 업로드하거나 운영 버킷에 접근하지 않습니다. + +## API + +| 메서드 | 경로 | 동작 | 인증 | +| ------ | ------------------------- | ---------------------------------------------------------- | ----------------- | +| GET | `/md5?return=hash` | 해시 목록의 첫 페이지 조회 | 불필요 | +| GET | `/md5?return=object` | `{ uploaded, size, md5 }` 메타데이터 목록의 첫 페이지 조회 | 불필요 | +| GET | `/md5/:hash` | 파일 바이트와 저장된 HTTP 메타데이터 다운로드 | 불필요 | +| HEAD | `/md5/:hash` | 본문 없이 파일 헤더 조회 | 불필요 | +| POST | `/md5/:hash?exists=error` | 서명된 PUT 업로드 요청 생성 | `x-client-secret` | +| DELETE | `/md5/:hash` | 파일 삭제. 파일이 없어도 204 반환 | `x-client-secret` | +| POST | `/query` | 존재하는 해시의 메타데이터 조회 | 불필요 | +| POST | `/sync` | 기존 R2 객체와 누락된 해시의 업로드 요청 반환 | `x-client-secret` | + +두 일괄 처리 엔드포인트(`/query`, `/sync`)는 `{ "md5": ["..."] }` 형식의 본문을 받으며, +문자열 항목을 최대 1000개까지 허용합니다. + +파일을 업로드하려면 먼저 `/md5/:hash`로 POST 요청을 보냅니다. +응답의 `url`에 파일 바이트를 PUT으로 전송하고, 응답에 포함된 `Content-MD5`와 +`If-Unmodified-Since` 헤더를 그대로 사용합니다. URL의 유효 기간은 600초입니다. +`exists=overwrite`는 현재 시각을 조건부 업로드 기준 시각으로 사용합니다. +그 외의 값은 기존 `error` 모드로 처리합니다. + +아래에 명시한 HTTP 응답·URL·MD5 검증·캐시 정책 변경 외에는 기존 구현의 관찰된 동작을 유지합니다. +예를 들어 `/md5`에서 반환 모드를 생략하거나 `return=md5`를 지정하면 400을 반환합니다. +이러한 동작을 변경하기 전에 [호환성 계약](docs/compatibility.md)을 확인하세요. + +## HTTP 응답 + +목록·조회·동기화·업로드 요청 정보와 오류는 `application/json`으로 반환합니다. +일반 JSON 응답에는 Hono의 `context.json()`을, 공통 오류 처리에는 표준 `Response.json()`을 사용합니다. +기존 헤더를 재현하기 위한 `lib/http.ts` 헬퍼는 제거했습니다. + +- 잘못되거나 비어 있는 JSON 본문은 `400 { "error": "bad_request" }`로 처리합니다. +- 예상하지 못한 서버 오류는 `500 { "error": "internal_server_error" }`로 반환하며, + 상세 오류는 서버 로그에만 기록합니다. +- 지원하지 않는 메서드는 Hono의 라우팅 결과에 따라 404를 반환합니다. 별도의 405·`Allow` 처리는 없습니다. +- HEAD는 Hono가 GET 핸들러로 처리한 뒤 본문을 제거합니다. 목록 HEAD도 GET과 같은 조건으로 처리하고, + 파일 HEAD는 `R2.head()`로 메타데이터만 조회합니다. +- 존재하지 않는 경로는 404, 파일 삭제는 본문 없는 204를 반환합니다. HEAD 응답에도 본문이 없습니다. + +이 응답 정책은 기존 호환성 요구에서 별도로 변경을 허용한 부분입니다. +파일 저장 키와 업로드 서명 규칙은 유지합니다. + +## URL 처리 + +Hono의 기본 라우팅과 `context.req.param()`, `context.req.query()`를 사용합니다. +별도의 라우터 호환성 보정은 없습니다. + +- 경로 끝의 슬래시를 구분합니다. `/md5/`, `/md5/:hash/`, `/query/`, `/sync/`는 404이며 리다이렉트하지 않습니다. +- 경로 파라미터는 퍼센트 디코딩 후 MD5를 검증하고 소문자로 변환합니다. 예를 들어 `%63`과 `c`는 같은 해시 문자입니다. +- 쿼리 파라미터가 반복되면 첫 값을 사용합니다. `return=hash&return=object`는 해시 목록, + `exists=overwrite&exists=error`는 덮어쓰기 모드입니다. +- 일괄 요청의 JSON 해시는 URL 디코딩하지 않습니다. + +저장된 객체를 이동하거나 수정하지 않습니다. 다만 인코딩된 경로로 요청할 때 선택되는 저장 키는 +기존과 달라질 수 있습니다. 정상 MD5 키의 기존 객체에는 해당 해시 경로로 접근할 수 있습니다. + +## MD5 검증 + +단일 파일의 경로 해시와 `/query`, `/sync`의 모든 해시는 정확히 32자리 16진수여야 합니다. +대소문자는 모두 허용하며 소문자로 통일합니다. 빈 문자열, 잘못된 길이, 기호나 공백이 포함된 +값은 `400 { "error": "bad_request" }`로 거부합니다. HEAD 오류에는 본문이 없습니다. +일괄 요청은 모든 항목을 검증한 뒤 저장소에 접근하므로 잘못된 항목이 있으면 전체가 실패합니다. +빈 배열은 허용하고 1000개 제한과 입력 순서·중복은 유지합니다. + +정상 MD5의 `objects/<소문자 해시>` 저장 키와 `Content-MD5`는 유지합니다. +기존의 비정상 해시를 허용하던 처리는 제거했습니다. 비정상 키의 기존 객체는 그대로 남지만 +단일 파일·일괄 API로 접근할 수 없습니다. 목록에는 기존 객체가 계속 표시될 수 있습니다. + +## 다운로드 캐시 + +성공한 `GET /md5/:hash` 응답은 객체에 `Cache-Control`과 `Expires`가 모두 없을 때 +다음 기본값을 추가합니다. 이 기본값은 기존 계약 보존을 위한 리팩토링 이후 합의한 응답 정책입니다. + +```http +Cache-Control: public, max-age=31536000, immutable +``` + +캐시 유효 기간은 1년이며, 해당 기간에는 일반적인 재검증을 생략할 수 있습니다. +R2에 저장된 `Cache-Control` 또는 `Expires`가 있으면 기존 정책을 그대로 사용합니다. +따라서 객체별 `no-store`, `no-cache`, 짧은 유효 기간 등을 유지할 수 있습니다. +이 기본값은 응답에만 추가하며 R2 객체나 메타데이터를 수정하지 않습니다. + +삭제는 원본 저장소에서 파일을 제거하는 동작이며, 이미 배포되거나 캐시된 파일의 접근 철회를 +의미하지 않습니다. 같은 URL의 메타데이터를 변경해도 기존 캐시에는 만료 전까지 반영되지 않을 수 있습니다. + +HEAD는 기존 저장 메타데이터만 반환하고 위 기본값을 추가하지 않습니다. +목록·조회·동기화·업로드 요청 정보·삭제·오류 응답에도 기본값을 추가하지 않습니다. +이 변경은 응답 헤더 정책이며, 별도의 Cloudflare 엣지 캐시 저장이나 무효화 로직은 추가하지 않습니다. + +## 명령어 + +| 명령어 | 용도 | +| ----------------------------- | ----------------------------------------------------- | +| `pnpm run dev` / `pnpm start` | 로컬 Wrangler 실행 | +| `pnpm test` | workerd에서 계약·통합·단위 테스트 실행 | +| `pnpm run test:watch` | 파일 변경 시 테스트 자동 재실행 | +| `pnpm run test:coverage` | `coverage/`에 커버리지 보고서 생성 | +| `pnpm run typecheck` | 애플리케이션·테스트·설정·스크립트 타입 검사 | +| `pnpm run format` | 소스 코드와 문서 포맷 정리 | +| `pnpm run openapi:generate` | `dist/openapi.json` 생성 및 검증 | +| `pnpm run build` | OpenAPI 생성 및 배포 모의 실행으로 Worker 번들 생성 | +| `pnpm run check` | 포맷·타입·테스트 커버리지·OpenAPI 검증·빌드 전체 검사 | +| `pnpm run deploy` | Wrangler로 배포 | + +CI는 푸시와 풀 리퀘스트에서 `pnpm install --frozen-lockfile`과 `pnpm run check`를 실행합니다. +생성된 파일, 로컬 비밀 값, 로컬 R2 데이터는 Git 추적에서 제외합니다. +OpenAPI는 등록된 라우트와 공통 Valibot 스키마에서 생성하므로 별도의 YAML 명세를 관리하지 않습니다. +HEAD 동작은 GET의 설명에 함께 안내합니다. 별도 HEAD 명세나 문서용 라우트는 등록하지 않습니다. +기존 Hurl은 자동화 테스트로 대체했습니다. + +## 프로젝트 구조 + +```text +src/ + index.ts Worker 진입점과 최종 오류 처리 + app.ts Hono 애플리케이션 구성 + docs.ts OpenAPI 및 Scalar 등록 + types/env.ts Worker 바인딩 타입 + middleware/auth.ts 클라이언트 시크릿 인증 + middleware/error.ts JSON 오류 응답과 서버 오류 로깅 + lib/ 해시 정규화 및 인코딩 + modules/files/ + routes.ts HTTP 핸들러와 API 동작 설명 + schemas.ts 요청·응답 스키마와 추론된 타입 + service.ts 일괄 요청 검증, 조회, 동기화 + repository.ts R2 접근, 저장 키, 메타데이터, 헤더 처리 + upload.ts AWS 서명 업로드 요청 생성 +scripts/ + generate-openapi.ts OpenAPI 문서 생성 및 검증 +tests/ + contract/ 기존 동작과 합의된 HTTP·URL 변경을 검증하는 스냅샷 + integration/ 로컬 R2 및 API 문서에 대한 Worker 통합 테스트 + unit/ 인코딩·헤더·서명의 경계 조건 테스트 + helpers/ 격리된 테스트 데이터와 요청 헬퍼 +``` + +## 배포 + +1. `wrangler.toml`의 기존 R2 버킷과 `FILES_BUCKET` 바인딩이 배포할 계정에 맞는지 확인합니다. + 기존 배포 환경에는 데이터 마이그레이션이 필요하지 않습니다. +2. 다음 명령으로 Worker의 비밀 값을 설정합니다. + + ```sh + pnpm exec wrangler secret put CLIENT_SECRET + pnpm exec wrangler secret put S3_ACCESS_KEY + pnpm exec wrangler secret put S3_SECRET_ACCESS_KEY + ``` + + `S3_ENDPOINT`는 Worker 환경 변수 또는 비밀 값으로 설정합니다. + 기존 배포 환경의 설정값은 그대로 사용할 수 있습니다. + +3. `pnpm run check`를 실행한 뒤, 배포할 준비가 되면 `pnpm run deploy`를 실행합니다. + +참고 문서: [Hono OpenAPI](https://hono.dev/examples/hono-openapi), +[Hono용 Scalar](https://scalar.com/products/api-references/integrations/hono), +[Cloudflare Vitest 통합](https://developers.cloudflare.com/workers/testing/vitest-integration/). diff --git a/docs/compatibility.md b/docs/compatibility.md new file mode 100644 index 0000000..582afa6 --- /dev/null +++ b/docs/compatibility.md @@ -0,0 +1,146 @@ +# Compatibility contract + +The baseline is commit `4376e99d1858682f27ac414ad014e53f7bf93759` on `main`. +The contract snapshots were recorded by running the original implementation in +workerd before replacing its router. They capture response status, headers, body, +R2 calls, and complete signed URLs with a fixed clock. Only the approved HTTP response, routing, and MD5 validation changes below have been applied to those +expectations. Storage calls and signed URLs remain fixed outside those routing cases; unrelated changes must +not be accepted by regenerating snapshots. Integration tests additionally exercise the real local R2 binding and +its native serialization. Neither suite touches production data. + +The old Swagger draft and Hurl files disagree with the implementation. The +running implementation is the compatibility source of truth. The new `/docs` +and `/openapi.json` routes are additive; `/` keeps its original 404. + +The HTTP response policy and download cache default below are separately +approved changes after the initial refactor. Baseline fixtures specify an +explicit cache policy; dedicated cache tests cover the new default and exclusions. + +## Approved HTTP response policy + +The legacy response helpers in `lib/http.ts` have been removed. Handlers use +Hono's `context.json()` and the error handler uses the standard `Response.json()`. +All JSON responses, including upload instructions and errors, now use +`application/json`. Hono 4.13.7 also uses this media type without a charset, so +list/query/sync Content-Type values did not need a compatibility wrapper. + +Malformed or empty JSON is a 400 `bad_request`, rather than a 500 parser error. +Unexpected failures return 500 `internal_server_error`; details are logged but +not included in responses. Authentication still runs before body parsing. + +Explicit method-not-allowed handlers are removed. Unsupported methods fall through +to the existing 404 handler, without an Allow header. Hono routes HEAD through GET +and strips the response body. List HEAD now follows list GET validation and storage +access; file HEAD continues using R2Bucket.head. HEAD and 204 responses are bodyless. + +## Approved download cache policy + +Successful `GET /md5/:hash` responses add +`Cache-Control: public, max-age=31536000, immutable` only when stored HTTP metadata +contains neither `Cache-Control` nor `Expires`. Explicit policies, including +`no-store`, `no-cache`, and shorter lifetimes, remain untouched. The default is +applied to the response only; R2 keys, bytes, metadata, and signing stay unchanged. + +Deletion removes the origin object but is not access revocation for copies +already distributed or cached. Updates to metadata at the same URL may remain +invisible to cached clients until expiry. HEAD preserves its original headers; +it does not receive the new default. No default is added to other API operations, +documentation, or error responses. This does not add an edge cache or purge logic. + +## Approved native Hono routing + +`lib/compatibility.ts` is removed. Hono uses its default strict routing, +`context.req.param('hash')`, and `context.req.query()` directly. +Trailing slashes no longer match canonical routes and return 404 without redirects. +Path parameters are percent-decoded before MD5 validation and lowercase normalization; +`%63` and `c` now select the same key. Repeated query parameters use their first +value, including `return` and `exists`. JSON bulk inputs are not URL-decoded. + +These are intentional external behavior changes. Encoded-path requests can now +select different keys and produce different upload signatures; duplicate overwrite +parameters can select a different signing condition. No stored objects are migrated. +Snapshot changes outside these cases and the approved HTTP policy require review. + +## Approved MD5 validation + +All single-file path hashes and bulk input hashes must be exactly 32 hexadecimal +characters. Uppercase is accepted and normalized to lowercase. Invalid input +returns 400 `bad_request` (bodyless for HEAD). Protected routes authenticate first. +Existing malformed keys remain stored and may appear in list responses, but cannot +be accessed through single-file or bulk operations. Response metadata schemas thus +continue allowing legacy hash strings. Valid MD5 keys and signatures are unchanged. + +## HTTP behavior + +- Routing follows the approved native Hono policy below. +- `GET /md5` requires `return=hash` or `return=object` as its first value. An omitted mode, + `return=md5`, and other inputs return 400 with `{"error":"bad_request"}`. +- List and query metadata is `{ uploaded, size, md5 }`. Dates serialize to ISO + timestamps. Responses preserve R2 ordering and input ordering respectively. +- `GET /md5/:hash` uses `R2Bucket.get`; `HEAD` uses `R2Bucket.head`. Both forward + stored HTTP metadata and set `Content-Length` and `Last-Modified`. A stored MD5 + checksum adds a base64 `Content-MD5` header. No new ETag is added. +- Missing GET objects return 404 with `{"error":"object_not_found"}`. Missing + HEAD objects return 404 with no body. HEAD responses never transmit bodies. +- Successful upload instructions, list, query, sync, and JSON errors use + `application/json`. Empty responses retain their original empty body and headers. +- Only upload POST, DELETE, and sync require `x-client-secret`. Missing or empty + values produce 401 `unauthorized`; incorrect values produce 403 `forbidden`. + Authentication precedes body parsing. Public reads remain public. +- DELETE returns 204 for both present and missing objects, with an empty body. +- Unsupported methods and unknown paths return 404 with + `{"error":"can't find endpoint"}` (bodyless for HEAD). No Allow header is added. +- No CORS middleware, redirect, or automatic request + Content-Type validation is added. + +## Bulk behavior + +`/query` and `/sync` parse JSON regardless of Content-Type and accept an `md5` +array of up to 1000 valid MD5 strings. Empty arrays, duplicates, and additional +object properties retain their historical behavior. Invalid hashes reject the whole +request before storage access; valid hashes are lowercased. +Non-array `md5` values and non-string items return 400 `bad_request`. More than +1000 items returns 413 `too_large_request` before checking item types. Malformed +or empty JSON returns 400 `bad_request`. Unexpected exceptions return 500 +`internal_server_error`, without including the error's string form. + +`/query` returns metadata only for existing files. `/sync` returns +`{ objects, uploads }`, where `objects` contains native serialized `R2Object` +values, including keys, versions, ETags, HTTP/custom metadata, and checksums. +It does not map these to `/query` metadata. Missing inputs each receive an upload +instruction, including duplicates. Operations remain sequential. + +## Storage and signing + +- Keys remain `objects/` for valid MD5 hashes. Normalization + validates exactly 32 hexadecimal characters and lowercases them. No objects + are moved, renamed, rewritten, or migrated by this refactor. +- Listing uses exactly `{ prefix: 'objects' }`, without a slash. Only the first + R2 list page is returned; the cursor is not followed or exposed. Keys matching + that prefix outside `objects/` retain the old fixed-offset hash slicing. +- Signing appends the key directly to `S3_ENDPOINT.pathname`. It does not insert + a missing slash or replace a bucket path. Existing URL query parameters remain. +- Upload requests use PUT, AWS query signing, all-header signing, and + `X-Amz-Expires=600`. Returned headers are `Content-MD5` and + `If-Unmodified-Since`. Error mode uses `Tue, 28 Sep 2021 16:00:00 GMT`; + overwrite uses the current UTC time. Only an exact first `exists=overwrite` value + activates overwrite. +- Single-file POST and bulk sync validate and lowercase hashes before checksum + conversion. Content-MD5 encodes the 16 digest bytes as Base64. Invalid input + is rejected instead of being partially parsed or converted to zero bytes. +- `wrangler.toml` retains the worker name, account ID, bucket bindings and names, + `workers_dev` setting, and compatibility date. Existing environment-variable + names remain valid. `R2_TOKEN` remains optional and unused by signing. + +## Verification limits + +Local R2 integration tests verify storage layout, bytes, metadata, query/sync, +HEAD, deletion, and authentication. Frozen signatures verify the URL and headers +produced by the old signer. They do not send PUT requests to Cloudflare's live S3 +endpoint or prove service-side conditional upload enforcement. A production +deployment or live-bucket upload is outside this refactor's local verification. + +Review changes to snapshots as changes to the external contract. Future runtime +upgrades may change native R2 serialization; review those differences explicitly +instead of blindly regenerating snapshots. JSON parser details are no longer part +of the response contract. diff --git a/hurl/basic.hurl b/hurl/basic.hurl deleted file mode 100644 index 1d62b13..0000000 --- a/hurl/basic.hurl +++ /dev/null @@ -1,2 +0,0 @@ -GET {{host}}/ -HTTP 200 \ No newline at end of file diff --git a/hurl/default.env b/hurl/default.env deleted file mode 100644 index f075b4f..0000000 --- a/hurl/default.env +++ /dev/null @@ -1,5 +0,0 @@ -host=https://rfiles.alphabeta.pw -hash=c09a30415c41bf3e39c58b7347e8ab1a - -# export HURL_client_secret= -# hurl --verbose --variables-file default.env \ No newline at end of file diff --git a/hurl/delete.hurl b/hurl/delete.hurl deleted file mode 100644 index b92e4a6..0000000 --- a/hurl/delete.hurl +++ /dev/null @@ -1,3 +0,0 @@ -DELETE {{host}}/md5/{{hash}} -X-Client-Secret: {{client_secret}} -HTTP 204 \ No newline at end of file diff --git a/hurl/download.hurl b/hurl/download.hurl deleted file mode 100644 index c966002..0000000 --- a/hurl/download.hurl +++ /dev/null @@ -1,2 +0,0 @@ -GET {{host}}/md5/{{hash}} -HTTP 200 \ No newline at end of file diff --git a/hurl/head.hurl b/hurl/head.hurl deleted file mode 100644 index 010cf57..0000000 --- a/hurl/head.hurl +++ /dev/null @@ -1,2 +0,0 @@ -HEAD {{host}}/md5/{{hash}} -HTTP 200 \ No newline at end of file diff --git a/hurl/list.hurl b/hurl/list.hurl deleted file mode 100644 index 98d0152..0000000 --- a/hurl/list.hurl +++ /dev/null @@ -1,2 +0,0 @@ -GET {{host}}/md5?return=md5 -HTTP 200 \ No newline at end of file diff --git a/hurl/query.hurl b/hurl/query.hurl deleted file mode 100644 index adedb03..0000000 --- a/hurl/query.hurl +++ /dev/null @@ -1,6 +0,0 @@ -POST {{host}}/query -{ - "md5": ["c09a30415c41bf3e39c58b7347e8ab1a"] -} - -HTTP 200 diff --git a/hurl/upload.hurl b/hurl/upload.hurl deleted file mode 100644 index 2af309d..0000000 --- a/hurl/upload.hurl +++ /dev/null @@ -1,2 +0,0 @@ -POST {{host}}/md5/{{hash}} -X-Client-Secret: {{client_secret}} \ No newline at end of file diff --git a/hurl/uploadfile b/hurl/uploadfile deleted file mode 100644 index 7d55278..0000000 Binary files a/hurl/uploadfile and /dev/null differ diff --git a/package-lock.json b/package-lock.json deleted file mode 100644 index 5dc14c6..0000000 --- a/package-lock.json +++ /dev/null @@ -1,2663 +0,0 @@ -{ - "name": "rfiles", - "version": "0.0.0", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "rfiles", - "version": "0.0.0", - "dependencies": { - "@aws-sdk/client-s3": "^3.465.0", - "@aws-sdk/s3-request-presigner": "^3.465.0", - "aws4fetch": "^1.0.17", - "itty-router": "^3.0.12" - }, - "devDependencies": { - "@cloudflare/workers-types": "^4.20230404.0", - "@types/node": "^20.10.2", - "typescript": "^5.0.4", - "wrangler": "^3.18.0" - } - }, - "node_modules/@aws-crypto/crc32": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@aws-crypto/crc32/-/crc32-3.0.0.tgz", - "integrity": "sha512-IzSgsrxUcsrejQbPVilIKy16kAT52EwB6zSaI+M3xxIhKh5+aldEyvI+z6erM7TCLB2BJsFrtHjp6/4/sr+3dA==", - "dependencies": { - "@aws-crypto/util": "^3.0.0", - "@aws-sdk/types": "^3.222.0", - "tslib": "^1.11.1" - } - }, - "node_modules/@aws-crypto/crc32/node_modules/tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" - }, - "node_modules/@aws-crypto/crc32c": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@aws-crypto/crc32c/-/crc32c-3.0.0.tgz", - "integrity": "sha512-ENNPPManmnVJ4BTXlOjAgD7URidbAznURqD0KvfREyc4o20DPYdEldU1f5cQ7Jbj0CJJSPaMIk/9ZshdB3210w==", - "dependencies": { - "@aws-crypto/util": "^3.0.0", - "@aws-sdk/types": "^3.222.0", - "tslib": "^1.11.1" - } - }, - "node_modules/@aws-crypto/crc32c/node_modules/tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" - }, - "node_modules/@aws-crypto/ie11-detection": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@aws-crypto/ie11-detection/-/ie11-detection-3.0.0.tgz", - "integrity": "sha512-341lBBkiY1DfDNKai/wXM3aujNBkXR7tq1URPQDL9wi3AUbI80NR74uF1TXHMm7po1AcnFk8iu2S2IeU/+/A+Q==", - "dependencies": { - "tslib": "^1.11.1" - } - }, - "node_modules/@aws-crypto/ie11-detection/node_modules/tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" - }, - "node_modules/@aws-crypto/sha1-browser": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@aws-crypto/sha1-browser/-/sha1-browser-3.0.0.tgz", - "integrity": "sha512-NJth5c997GLHs6nOYTzFKTbYdMNA6/1XlKVgnZoaZcQ7z7UJlOgj2JdbHE8tiYLS3fzXNCguct77SPGat2raSw==", - "dependencies": { - "@aws-crypto/ie11-detection": "^3.0.0", - "@aws-crypto/supports-web-crypto": "^3.0.0", - "@aws-crypto/util": "^3.0.0", - "@aws-sdk/types": "^3.222.0", - "@aws-sdk/util-locate-window": "^3.0.0", - "@aws-sdk/util-utf8-browser": "^3.0.0", - "tslib": "^1.11.1" - } - }, - "node_modules/@aws-crypto/sha1-browser/node_modules/tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" - }, - "node_modules/@aws-crypto/sha256-browser": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-browser/-/sha256-browser-3.0.0.tgz", - "integrity": "sha512-8VLmW2B+gjFbU5uMeqtQM6Nj0/F1bro80xQXCW6CQBWgosFWXTx77aeOF5CAIAmbOK64SdMBJdNr6J41yP5mvQ==", - "dependencies": { - "@aws-crypto/ie11-detection": "^3.0.0", - "@aws-crypto/sha256-js": "^3.0.0", - "@aws-crypto/supports-web-crypto": "^3.0.0", - "@aws-crypto/util": "^3.0.0", - "@aws-sdk/types": "^3.222.0", - "@aws-sdk/util-locate-window": "^3.0.0", - "@aws-sdk/util-utf8-browser": "^3.0.0", - "tslib": "^1.11.1" - } - }, - "node_modules/@aws-crypto/sha256-browser/node_modules/tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" - }, - "node_modules/@aws-crypto/sha256-js": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@aws-crypto/sha256-js/-/sha256-js-3.0.0.tgz", - "integrity": "sha512-PnNN7os0+yd1XvXAy23CFOmTbMaDxgxXtTKHybrJ39Y8kGzBATgBFibWJKH6BhytLI/Zyszs87xCOBNyBig6vQ==", - "dependencies": { - "@aws-crypto/util": "^3.0.0", - "@aws-sdk/types": "^3.222.0", - "tslib": "^1.11.1" - } - }, - "node_modules/@aws-crypto/sha256-js/node_modules/tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" - }, - "node_modules/@aws-crypto/supports-web-crypto": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@aws-crypto/supports-web-crypto/-/supports-web-crypto-3.0.0.tgz", - "integrity": "sha512-06hBdMwUAb2WFTuGG73LSC0wfPu93xWwo5vL2et9eymgmu3Id5vFAHBbajVWiGhPO37qcsdCap/FqXvJGJWPIg==", - "dependencies": { - "tslib": "^1.11.1" - } - }, - "node_modules/@aws-crypto/supports-web-crypto/node_modules/tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" - }, - "node_modules/@aws-crypto/util": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@aws-crypto/util/-/util-3.0.0.tgz", - "integrity": "sha512-2OJlpeJpCR48CC8r+uKVChzs9Iungj9wkZrl8Z041DWEWvyIHILYKCPNzJghKsivj+S3mLo6BVc7mBNzdxA46w==", - "dependencies": { - "@aws-sdk/types": "^3.222.0", - "@aws-sdk/util-utf8-browser": "^3.0.0", - "tslib": "^1.11.1" - } - }, - "node_modules/@aws-crypto/util/node_modules/tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" - }, - "node_modules/@aws-sdk/client-s3": { - "version": "3.465.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-s3/-/client-s3-3.465.0.tgz", - "integrity": "sha512-S2W8aUs/SR7wabyKRldl5FKtAq2gsXo3BpbKjBvuCILwNl84ooQrsOmKtcVsINRdi+q/mZvwGenqqp/98+yjdg==", - "dependencies": { - "@aws-crypto/sha1-browser": "3.0.0", - "@aws-crypto/sha256-browser": "3.0.0", - "@aws-crypto/sha256-js": "3.0.0", - "@aws-sdk/client-sts": "3.465.0", - "@aws-sdk/core": "3.465.0", - "@aws-sdk/credential-provider-node": "3.465.0", - "@aws-sdk/middleware-bucket-endpoint": "3.465.0", - "@aws-sdk/middleware-expect-continue": "3.465.0", - "@aws-sdk/middleware-flexible-checksums": "3.465.0", - "@aws-sdk/middleware-host-header": "3.465.0", - "@aws-sdk/middleware-location-constraint": "3.465.0", - "@aws-sdk/middleware-logger": "3.465.0", - "@aws-sdk/middleware-recursion-detection": "3.465.0", - "@aws-sdk/middleware-sdk-s3": "3.465.0", - "@aws-sdk/middleware-signing": "3.465.0", - "@aws-sdk/middleware-ssec": "3.465.0", - "@aws-sdk/middleware-user-agent": "3.465.0", - "@aws-sdk/region-config-resolver": "3.465.0", - "@aws-sdk/signature-v4-multi-region": "3.465.0", - "@aws-sdk/types": "3.465.0", - "@aws-sdk/util-endpoints": "3.465.0", - "@aws-sdk/util-user-agent-browser": "3.465.0", - "@aws-sdk/util-user-agent-node": "3.465.0", - "@aws-sdk/xml-builder": "3.465.0", - "@smithy/config-resolver": "^2.0.18", - "@smithy/eventstream-serde-browser": "^2.0.13", - "@smithy/eventstream-serde-config-resolver": "^2.0.13", - "@smithy/eventstream-serde-node": "^2.0.13", - "@smithy/fetch-http-handler": "^2.2.6", - "@smithy/hash-blob-browser": "^2.0.14", - "@smithy/hash-node": "^2.0.15", - "@smithy/hash-stream-node": "^2.0.15", - "@smithy/invalid-dependency": "^2.0.13", - "@smithy/md5-js": "^2.0.15", - "@smithy/middleware-content-length": "^2.0.15", - "@smithy/middleware-endpoint": "^2.2.0", - "@smithy/middleware-retry": "^2.0.20", - "@smithy/middleware-serde": "^2.0.13", - "@smithy/middleware-stack": "^2.0.7", - "@smithy/node-config-provider": "^2.1.5", - "@smithy/node-http-handler": "^2.1.9", - "@smithy/protocol-http": "^3.0.9", - "@smithy/smithy-client": "^2.1.15", - "@smithy/types": "^2.5.0", - "@smithy/url-parser": "^2.0.13", - "@smithy/util-base64": "^2.0.1", - "@smithy/util-body-length-browser": "^2.0.0", - "@smithy/util-body-length-node": "^2.1.0", - "@smithy/util-defaults-mode-browser": "^2.0.19", - "@smithy/util-defaults-mode-node": "^2.0.25", - "@smithy/util-endpoints": "^1.0.4", - "@smithy/util-retry": "^2.0.6", - "@smithy/util-stream": "^2.0.20", - "@smithy/util-utf8": "^2.0.2", - "@smithy/util-waiter": "^2.0.13", - "fast-xml-parser": "4.2.5", - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/client-sso": { - "version": "3.465.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.465.0.tgz", - "integrity": "sha512-JXDBa3Sl+LS0KEOs0PZoIjpNKEEGfeyFwdnRxi8Y1hMXNEKyJug1cI2Psqu2olpn4KeXwoP1BuITppZYdolOew==", - "dependencies": { - "@aws-crypto/sha256-browser": "3.0.0", - "@aws-crypto/sha256-js": "3.0.0", - "@aws-sdk/core": "3.465.0", - "@aws-sdk/middleware-host-header": "3.465.0", - "@aws-sdk/middleware-logger": "3.465.0", - "@aws-sdk/middleware-recursion-detection": "3.465.0", - "@aws-sdk/middleware-user-agent": "3.465.0", - "@aws-sdk/region-config-resolver": "3.465.0", - "@aws-sdk/types": "3.465.0", - "@aws-sdk/util-endpoints": "3.465.0", - "@aws-sdk/util-user-agent-browser": "3.465.0", - "@aws-sdk/util-user-agent-node": "3.465.0", - "@smithy/config-resolver": "^2.0.18", - "@smithy/fetch-http-handler": "^2.2.6", - "@smithy/hash-node": "^2.0.15", - "@smithy/invalid-dependency": "^2.0.13", - "@smithy/middleware-content-length": "^2.0.15", - "@smithy/middleware-endpoint": "^2.2.0", - "@smithy/middleware-retry": "^2.0.20", - "@smithy/middleware-serde": "^2.0.13", - "@smithy/middleware-stack": "^2.0.7", - "@smithy/node-config-provider": "^2.1.5", - "@smithy/node-http-handler": "^2.1.9", - "@smithy/protocol-http": "^3.0.9", - "@smithy/smithy-client": "^2.1.15", - "@smithy/types": "^2.5.0", - "@smithy/url-parser": "^2.0.13", - "@smithy/util-base64": "^2.0.1", - "@smithy/util-body-length-browser": "^2.0.0", - "@smithy/util-body-length-node": "^2.1.0", - "@smithy/util-defaults-mode-browser": "^2.0.19", - "@smithy/util-defaults-mode-node": "^2.0.25", - "@smithy/util-endpoints": "^1.0.4", - "@smithy/util-retry": "^2.0.6", - "@smithy/util-utf8": "^2.0.2", - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/client-sts": { - "version": "3.465.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-sts/-/client-sts-3.465.0.tgz", - "integrity": "sha512-rHi9ba6ssNbVjlWSdhi4C5newEhGhzkY9UE4KB+/Tj21zXfEP8r6uIltnQXPtun2SdA95Krh/yS1qQ4MRuzqyA==", - "dependencies": { - "@aws-crypto/sha256-browser": "3.0.0", - "@aws-crypto/sha256-js": "3.0.0", - "@aws-sdk/core": "3.465.0", - "@aws-sdk/credential-provider-node": "3.465.0", - "@aws-sdk/middleware-host-header": "3.465.0", - "@aws-sdk/middleware-logger": "3.465.0", - "@aws-sdk/middleware-recursion-detection": "3.465.0", - "@aws-sdk/middleware-sdk-sts": "3.465.0", - "@aws-sdk/middleware-signing": "3.465.0", - "@aws-sdk/middleware-user-agent": "3.465.0", - "@aws-sdk/region-config-resolver": "3.465.0", - "@aws-sdk/types": "3.465.0", - "@aws-sdk/util-endpoints": "3.465.0", - "@aws-sdk/util-user-agent-browser": "3.465.0", - "@aws-sdk/util-user-agent-node": "3.465.0", - "@smithy/config-resolver": "^2.0.18", - "@smithy/fetch-http-handler": "^2.2.6", - "@smithy/hash-node": "^2.0.15", - "@smithy/invalid-dependency": "^2.0.13", - "@smithy/middleware-content-length": "^2.0.15", - "@smithy/middleware-endpoint": "^2.2.0", - "@smithy/middleware-retry": "^2.0.20", - "@smithy/middleware-serde": "^2.0.13", - "@smithy/middleware-stack": "^2.0.7", - "@smithy/node-config-provider": "^2.1.5", - "@smithy/node-http-handler": "^2.1.9", - "@smithy/protocol-http": "^3.0.9", - "@smithy/smithy-client": "^2.1.15", - "@smithy/types": "^2.5.0", - "@smithy/url-parser": "^2.0.13", - "@smithy/util-base64": "^2.0.1", - "@smithy/util-body-length-browser": "^2.0.0", - "@smithy/util-body-length-node": "^2.1.0", - "@smithy/util-defaults-mode-browser": "^2.0.19", - "@smithy/util-defaults-mode-node": "^2.0.25", - "@smithy/util-endpoints": "^1.0.4", - "@smithy/util-retry": "^2.0.6", - "@smithy/util-utf8": "^2.0.2", - "fast-xml-parser": "4.2.5", - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/core": { - "version": "3.465.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.465.0.tgz", - "integrity": "sha512-fHSIw/Rgex3KbrEKn6ZrUc2VcsOTpdBMeyYtfmsTOLSyDDOG9k3jelOvVbCbrK5N6uEUSM8hrnySEKg94UB0cg==", - "dependencies": { - "@smithy/smithy-client": "^2.1.15", - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/credential-provider-env": { - "version": "3.465.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-env/-/credential-provider-env-3.465.0.tgz", - "integrity": "sha512-fku37AgkB9KhCuWHE6mfvbWYU0X84Df6MQ60nYH7s/PiNEhkX2cVI6X6kOKjP1MNIwRcYt+oQDvplVKdHume+A==", - "dependencies": { - "@aws-sdk/types": "3.465.0", - "@smithy/property-provider": "^2.0.0", - "@smithy/types": "^2.5.0", - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/credential-provider-ini": { - "version": "3.465.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.465.0.tgz", - "integrity": "sha512-B1MFufvdToAEMtfszilVnKer2S7P/OfMhkCizq2zuu8aU/CquRyHvKEQgWdvqunUDrFnVTc0kUZgsbBY0uPjLg==", - "dependencies": { - "@aws-sdk/credential-provider-env": "3.465.0", - "@aws-sdk/credential-provider-process": "3.465.0", - "@aws-sdk/credential-provider-sso": "3.465.0", - "@aws-sdk/credential-provider-web-identity": "3.465.0", - "@aws-sdk/types": "3.465.0", - "@smithy/credential-provider-imds": "^2.0.0", - "@smithy/property-provider": "^2.0.0", - "@smithy/shared-ini-file-loader": "^2.0.6", - "@smithy/types": "^2.5.0", - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/credential-provider-node": { - "version": "3.465.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.465.0.tgz", - "integrity": "sha512-R3VA9yJ0BvezvrDxcgPTv9VHbVPbzchLTrX5jLFSVuW/lPPYLUi/Cjtyg9C9Y7qRfoQS4fNMvSRhwO5/TF68gA==", - "dependencies": { - "@aws-sdk/credential-provider-env": "3.465.0", - "@aws-sdk/credential-provider-ini": "3.465.0", - "@aws-sdk/credential-provider-process": "3.465.0", - "@aws-sdk/credential-provider-sso": "3.465.0", - "@aws-sdk/credential-provider-web-identity": "3.465.0", - "@aws-sdk/types": "3.465.0", - "@smithy/credential-provider-imds": "^2.0.0", - "@smithy/property-provider": "^2.0.0", - "@smithy/shared-ini-file-loader": "^2.0.6", - "@smithy/types": "^2.5.0", - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/credential-provider-process": { - "version": "3.465.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-process/-/credential-provider-process-3.465.0.tgz", - "integrity": "sha512-YE6ZrRYwvb8969hWQnr4uvOJ8RU0JrNsk3vWTe/czly37ioZUEhi8jmpQp4f2mX/6U6buoFGWu5Se3VCdw2SFQ==", - "dependencies": { - "@aws-sdk/types": "3.465.0", - "@smithy/property-provider": "^2.0.0", - "@smithy/shared-ini-file-loader": "^2.0.6", - "@smithy/types": "^2.5.0", - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/credential-provider-sso": { - "version": "3.465.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.465.0.tgz", - "integrity": "sha512-tLIP/4JQIJpn8yIg6RZRQ2nmvj5i4wLZvYvY4RtaFv2JrQUkmmTfyOZJuOBrIFRwJjx0fHmFu8DJjcOhMzllIQ==", - "dependencies": { - "@aws-sdk/client-sso": "3.465.0", - "@aws-sdk/token-providers": "3.465.0", - "@aws-sdk/types": "3.465.0", - "@smithy/property-provider": "^2.0.0", - "@smithy/shared-ini-file-loader": "^2.0.6", - "@smithy/types": "^2.5.0", - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/credential-provider-web-identity": { - "version": "3.465.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.465.0.tgz", - "integrity": "sha512-B4Y75fMTZIniEU0yyqat+9NsQbYlXdqP5Y3bShkaG3pGLOHzF/xMlWuG+D3kkQ806PLYi+BgfVls4BcO+NyVcA==", - "dependencies": { - "@aws-sdk/types": "3.465.0", - "@smithy/property-provider": "^2.0.0", - "@smithy/types": "^2.5.0", - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/middleware-bucket-endpoint": { - "version": "3.465.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-bucket-endpoint/-/middleware-bucket-endpoint-3.465.0.tgz", - "integrity": "sha512-cyIR9Nwyie6giLypuLSUmZF3O5GqVRwia3Nq1B/6/Ho0LccH0/HT2x/nM8fFcnskWSNGTVZVvZzSrVYXynTtjA==", - "dependencies": { - "@aws-sdk/types": "3.465.0", - "@aws-sdk/util-arn-parser": "3.465.0", - "@smithy/node-config-provider": "^2.1.5", - "@smithy/protocol-http": "^3.0.9", - "@smithy/types": "^2.5.0", - "@smithy/util-config-provider": "^2.0.0", - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/middleware-expect-continue": { - "version": "3.465.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-expect-continue/-/middleware-expect-continue-3.465.0.tgz", - "integrity": "sha512-kthlPQDASsdtdVqKVKkJn9bHptcEpsQ6ptWeGBCYigicULvWI1fjSTeXrYczxNMVg+1Sv8xkb/bh+kUEu7mvZg==", - "dependencies": { - "@aws-sdk/types": "3.465.0", - "@smithy/protocol-http": "^3.0.9", - "@smithy/types": "^2.5.0", - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/middleware-flexible-checksums": { - "version": "3.465.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-flexible-checksums/-/middleware-flexible-checksums-3.465.0.tgz", - "integrity": "sha512-joWEWN0v1CpI4q9JlZki0AchVwLL8Les0+V+3JHVDcDgL4RQ04YUk9lMYbtldDwdyBNquKwW2+sGtIo/6ng0Tg==", - "dependencies": { - "@aws-crypto/crc32": "3.0.0", - "@aws-crypto/crc32c": "3.0.0", - "@aws-sdk/types": "3.465.0", - "@smithy/is-array-buffer": "^2.0.0", - "@smithy/protocol-http": "^3.0.9", - "@smithy/types": "^2.5.0", - "@smithy/util-utf8": "^2.0.2", - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/middleware-host-header": { - "version": "3.465.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-host-header/-/middleware-host-header-3.465.0.tgz", - "integrity": "sha512-nnGva8eplwEJqdVzcb+xF2Fwua0PpiwxMEvpnIy73gNbetbJdgFIprryMLYes00xzJEqnew+LWdpcd3YyS34ZA==", - "dependencies": { - "@aws-sdk/types": "3.465.0", - "@smithy/protocol-http": "^3.0.9", - "@smithy/types": "^2.5.0", - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/middleware-location-constraint": { - "version": "3.465.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-location-constraint/-/middleware-location-constraint-3.465.0.tgz", - "integrity": "sha512-2+mwaI/ltE2ibr5poC+E9kJRfVIv7aHpAJkLu7uvESch9cpuFuGJu6fq0/gA82eKZ/gwpBj+AaXBsDFfsDWFsw==", - "dependencies": { - "@aws-sdk/types": "3.465.0", - "@smithy/types": "^2.5.0", - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/middleware-logger": { - "version": "3.465.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-logger/-/middleware-logger-3.465.0.tgz", - "integrity": "sha512-aGMx1aSlzDDgjZ7fSxLhGD5rkyCfHwq04TSB5fQAgDBqUjj4IQXZwmNglX0sLRmArXZtDglUVESOfKvTANJTPg==", - "dependencies": { - "@aws-sdk/types": "3.465.0", - "@smithy/types": "^2.5.0", - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/middleware-recursion-detection": { - "version": "3.465.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-recursion-detection/-/middleware-recursion-detection-3.465.0.tgz", - "integrity": "sha512-ol3dlsTnryBhV5qkUvK5Yg3dRaV1NXIxYJaIkShrl8XAv4wRNcDJDmO5NYq5eVZ3zgV1nv6xIpZ//dDnnf6Z+g==", - "dependencies": { - "@aws-sdk/types": "3.465.0", - "@smithy/protocol-http": "^3.0.9", - "@smithy/types": "^2.5.0", - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/middleware-sdk-s3": { - "version": "3.465.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-s3/-/middleware-sdk-s3-3.465.0.tgz", - "integrity": "sha512-P4cpNv0EcMSSLojjqKKQjKSGZc13QJQAscUs+fcvpBg2BNR9ByxrQgXXMqQiIqr8fgAhADqN2Tp8hJk0CzfnAg==", - "dependencies": { - "@aws-sdk/types": "3.465.0", - "@aws-sdk/util-arn-parser": "3.465.0", - "@smithy/node-config-provider": "^2.1.5", - "@smithy/protocol-http": "^3.0.9", - "@smithy/signature-v4": "^2.0.0", - "@smithy/smithy-client": "^2.1.15", - "@smithy/types": "^2.5.0", - "@smithy/util-config-provider": "^2.0.0", - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/middleware-sdk-sts": { - "version": "3.465.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-sdk-sts/-/middleware-sdk-sts-3.465.0.tgz", - "integrity": "sha512-PmTM5ycUe1RLAPrQXLCR8JzKamJuKDB0aIW4rx4/skurzWsEGRI47WHggf9N7sPie41IBGUhRbXcf7sfPjvI3Q==", - "dependencies": { - "@aws-sdk/middleware-signing": "3.465.0", - "@aws-sdk/types": "3.465.0", - "@smithy/types": "^2.5.0", - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/middleware-signing": { - "version": "3.465.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-signing/-/middleware-signing-3.465.0.tgz", - "integrity": "sha512-d90KONWXSC3jA0kqJ6u8ygS4LoMg1TmSM7bPhHyibJVAEhnrlB4Aq1CWljNbbtphGpdKy5/XRM9O0/XCXWKQ4w==", - "dependencies": { - "@aws-sdk/types": "3.465.0", - "@smithy/property-provider": "^2.0.0", - "@smithy/protocol-http": "^3.0.9", - "@smithy/signature-v4": "^2.0.0", - "@smithy/types": "^2.5.0", - "@smithy/util-middleware": "^2.0.6", - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/middleware-ssec": { - "version": "3.465.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-ssec/-/middleware-ssec-3.465.0.tgz", - "integrity": "sha512-PHc1guBGp7fwoPlJkAEaHVkiYPfs93jffwsBvIevCsHcfYPv6L26/5Nk7KR+6IyuGQHpUbSC080SP1jYjOy01A==", - "dependencies": { - "@aws-sdk/types": "3.465.0", - "@smithy/types": "^2.5.0", - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/middleware-user-agent": { - "version": "3.465.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.465.0.tgz", - "integrity": "sha512-1MvIWMj2nktLOJN8Kh4jiTK28oL85fTeoXHZ+V8xYMzont6C6Y8gQPtg7ka+RotHwqWMrovfnANisnX8EzEP/Q==", - "dependencies": { - "@aws-sdk/types": "3.465.0", - "@aws-sdk/util-endpoints": "3.465.0", - "@smithy/protocol-http": "^3.0.9", - "@smithy/types": "^2.5.0", - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/region-config-resolver": { - "version": "3.465.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/region-config-resolver/-/region-config-resolver-3.465.0.tgz", - "integrity": "sha512-h0Phd2Ae873dsPSWuxqxz2yRC5NMeeWxQiJPh4j42HF8g7dZK7tMQPkYznAoA/BzSBsEX87sbr3MmigquSyUTA==", - "dependencies": { - "@smithy/node-config-provider": "^2.1.5", - "@smithy/types": "^2.5.0", - "@smithy/util-config-provider": "^2.0.0", - "@smithy/util-middleware": "^2.0.6", - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/s3-request-presigner": { - "version": "3.465.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/s3-request-presigner/-/s3-request-presigner-3.465.0.tgz", - "integrity": "sha512-wQTddmwGR+5GZR6KLXMejFuTHzuKRcbwgfTrIemYQDt6DDT4fbQGAvnIZnzpHbqnfx1bQEXquM/oB7LZrASiQQ==", - "dependencies": { - "@aws-sdk/signature-v4-multi-region": "3.465.0", - "@aws-sdk/types": "3.465.0", - "@aws-sdk/util-format-url": "3.465.0", - "@smithy/middleware-endpoint": "^2.2.0", - "@smithy/protocol-http": "^3.0.9", - "@smithy/smithy-client": "^2.1.15", - "@smithy/types": "^2.5.0", - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/signature-v4-multi-region": { - "version": "3.465.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/signature-v4-multi-region/-/signature-v4-multi-region-3.465.0.tgz", - "integrity": "sha512-p620S4YCr2CPNIdSnRvBqScAqWztjef9EwtD1MAkxTTrjNAyxSCf4apeQ2pdaWNNkJT1vSc/YKBAJ7l2SWn7rw==", - "dependencies": { - "@aws-sdk/middleware-sdk-s3": "3.465.0", - "@aws-sdk/types": "3.465.0", - "@smithy/protocol-http": "^3.0.9", - "@smithy/signature-v4": "^2.0.0", - "@smithy/types": "^2.5.0", - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/token-providers": { - "version": "3.465.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/token-providers/-/token-providers-3.465.0.tgz", - "integrity": "sha512-NaZbsyLs3whzRHGV27hrRwEdXB/tEK6tqn/aCNBy862LhVzocY1A+eYLKrnrvpraOOd2vyAuOtvvB3RMIdiL6g==", - "dependencies": { - "@aws-crypto/sha256-browser": "3.0.0", - "@aws-crypto/sha256-js": "3.0.0", - "@aws-sdk/middleware-host-header": "3.465.0", - "@aws-sdk/middleware-logger": "3.465.0", - "@aws-sdk/middleware-recursion-detection": "3.465.0", - "@aws-sdk/middleware-user-agent": "3.465.0", - "@aws-sdk/region-config-resolver": "3.465.0", - "@aws-sdk/types": "3.465.0", - "@aws-sdk/util-endpoints": "3.465.0", - "@aws-sdk/util-user-agent-browser": "3.465.0", - "@aws-sdk/util-user-agent-node": "3.465.0", - "@smithy/config-resolver": "^2.0.18", - "@smithy/fetch-http-handler": "^2.2.6", - "@smithy/hash-node": "^2.0.15", - "@smithy/invalid-dependency": "^2.0.13", - "@smithy/middleware-content-length": "^2.0.15", - "@smithy/middleware-endpoint": "^2.2.0", - "@smithy/middleware-retry": "^2.0.20", - "@smithy/middleware-serde": "^2.0.13", - "@smithy/middleware-stack": "^2.0.7", - "@smithy/node-config-provider": "^2.1.5", - "@smithy/node-http-handler": "^2.1.9", - "@smithy/property-provider": "^2.0.0", - "@smithy/protocol-http": "^3.0.9", - "@smithy/shared-ini-file-loader": "^2.0.6", - "@smithy/smithy-client": "^2.1.15", - "@smithy/types": "^2.5.0", - "@smithy/url-parser": "^2.0.13", - "@smithy/util-base64": "^2.0.1", - "@smithy/util-body-length-browser": "^2.0.0", - "@smithy/util-body-length-node": "^2.1.0", - "@smithy/util-defaults-mode-browser": "^2.0.19", - "@smithy/util-defaults-mode-node": "^2.0.25", - "@smithy/util-endpoints": "^1.0.4", - "@smithy/util-retry": "^2.0.6", - "@smithy/util-utf8": "^2.0.2", - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/types": { - "version": "3.465.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/types/-/types-3.465.0.tgz", - "integrity": "sha512-Clqu2eD50OOzwSftGpzJrIOGev/7VJhJpc02SeS4cqFgI9EVd+rnFKS/Ux0kcwjLQBMiPcCLtql3KAHApFHAIA==", - "dependencies": { - "@smithy/types": "^2.5.0", - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/util-arn-parser": { - "version": "3.465.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-arn-parser/-/util-arn-parser-3.465.0.tgz", - "integrity": "sha512-zOJ82vzDJFqBX9yZBlNeHHrul/kpx/DCoxzW5UBbZeb26kfV53QhMSoEmY8/lEbBqlqargJ/sgRC845GFhHNQw==", - "dependencies": { - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/util-endpoints": { - "version": "3.465.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-endpoints/-/util-endpoints-3.465.0.tgz", - "integrity": "sha512-lDpBN1faVw8Udg5hIo+LJaNfllbBF86PCisv628vfcggO8/EArL/v2Eos0KeqVT8yaINXCRSagwfo5TNTuW0KQ==", - "dependencies": { - "@aws-sdk/types": "3.465.0", - "@smithy/util-endpoints": "^1.0.4", - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/util-format-url": { - "version": "3.465.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-format-url/-/util-format-url-3.465.0.tgz", - "integrity": "sha512-jJmIeMFQMADPw5DfyIzqqsIyS4p+duc1fhnOK+GfEkyja1cjafUQOcvovpQ4rCRHGffjTG2LCkOdyrSRDTFzfQ==", - "dependencies": { - "@aws-sdk/types": "3.465.0", - "@smithy/querystring-builder": "^2.0.13", - "@smithy/types": "^2.5.0", - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/util-locate-window": { - "version": "3.465.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-locate-window/-/util-locate-window-3.465.0.tgz", - "integrity": "sha512-f+QNcWGswredzC1ExNAB/QzODlxwaTdXkNT5cvke2RLX8SFU5pYk6h4uCtWC0vWPELzOfMfloBrJefBzlarhsw==", - "dependencies": { - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@aws-sdk/util-user-agent-browser": { - "version": "3.465.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-browser/-/util-user-agent-browser-3.465.0.tgz", - "integrity": "sha512-RM+LjkIsmUCBJ4yQeBnkJWJTjPOPqcNaKv8bpZxatIHdvzGhXLnWLNi3qHlBsJB2mKtKRet6nAUmKmzZR1sDzA==", - "dependencies": { - "@aws-sdk/types": "3.465.0", - "@smithy/types": "^2.5.0", - "bowser": "^2.11.0", - "tslib": "^2.5.0" - } - }, - "node_modules/@aws-sdk/util-user-agent-node": { - "version": "3.465.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-user-agent-node/-/util-user-agent-node-3.465.0.tgz", - "integrity": "sha512-XsHbq7gLCiGdy6FQ7/5nGslK0ij3Iuh051djuIICvNurlds5cqKLiBe63gX3IUUwxJcrKh4xBGviQJ52KdVSeg==", - "dependencies": { - "@aws-sdk/types": "3.465.0", - "@smithy/node-config-provider": "^2.1.5", - "@smithy/types": "^2.5.0", - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - }, - "peerDependencies": { - "aws-crt": ">=1.0.0" - }, - "peerDependenciesMeta": { - "aws-crt": { - "optional": true - } - } - }, - "node_modules/@aws-sdk/util-utf8-browser": { - "version": "3.259.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/util-utf8-browser/-/util-utf8-browser-3.259.0.tgz", - "integrity": "sha512-UvFa/vR+e19XookZF8RzFZBrw2EUkQWxiBW0yYQAhvk3C+QVGl0H3ouca8LDBlBfQKXwmW3huo/59H8rwb1wJw==", - "dependencies": { - "tslib": "^2.3.1" - } - }, - "node_modules/@aws-sdk/xml-builder": { - "version": "3.465.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.465.0.tgz", - "integrity": "sha512-9TKW5ZgsReygePTnAUdvaqxr/k1HXsEz2yDnk/jTLaUeRPsd5la8fFjb6OfgYYlbEVNlxTcKzaqOdrqxpUkmyQ==", - "dependencies": { - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@cloudflare/kv-asset-handler": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/@cloudflare/kv-asset-handler/-/kv-asset-handler-0.2.0.tgz", - "integrity": "sha512-MVbXLbTcAotOPUj0pAMhVtJ+3/kFkwJqc5qNOleOZTv6QkZZABDMS21dSrSlVswEHwrpWC03e4fWytjqKvuE2A==", - "dev": true, - "dependencies": { - "mime": "^3.0.0" - } - }, - "node_modules/@cloudflare/workerd-darwin-64": { - "version": "1.20231030.0", - "resolved": "https://registry.npmjs.org/@cloudflare/workerd-darwin-64/-/workerd-darwin-64-1.20231030.0.tgz", - "integrity": "sha512-J4PQ9utPxLya9yHdMMx3AZeC5M/6FxcoYw6jo9jbDDFTy+a4Gslqf4Im9We3aeOEdPXa3tgQHVQOSelJSZLhIw==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=16" - } - }, - "node_modules/@cloudflare/workerd-darwin-arm64": { - "version": "1.20231030.0", - "resolved": "https://registry.npmjs.org/@cloudflare/workerd-darwin-arm64/-/workerd-darwin-arm64-1.20231030.0.tgz", - "integrity": "sha512-WSJJjm11Del4hSneiNB7wTXGtBXI4QMCH9l5qf4iT5PAW8cESGcCmdHtWDWDtGAAGcvmLT04KNvmum92vRKKQQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=16" - } - }, - "node_modules/@cloudflare/workerd-linux-64": { - "version": "1.20231030.0", - "resolved": "https://registry.npmjs.org/@cloudflare/workerd-linux-64/-/workerd-linux-64-1.20231030.0.tgz", - "integrity": "sha512-2HUeRTvoCC17fxE0qdBeR7J9dO8j4A8ZbdcvY8pZxdk+zERU6+N03RTbk/dQMU488PwiDvcC3zZqS4gwLfVT8g==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=16" - } - }, - "node_modules/@cloudflare/workerd-linux-arm64": { - "version": "1.20231030.0", - "resolved": "https://registry.npmjs.org/@cloudflare/workerd-linux-arm64/-/workerd-linux-arm64-1.20231030.0.tgz", - "integrity": "sha512-4/GK5zHh+9JbUI6Z5xTCM0ZmpKKHk7vu9thmHjUxtz+o8Ne9DoD7DlDvXQWgMF6XGaTubDWyp3ttn+Qv8jDFuQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=16" - } - }, - "node_modules/@cloudflare/workerd-windows-64": { - "version": "1.20231030.0", - "resolved": "https://registry.npmjs.org/@cloudflare/workerd-windows-64/-/workerd-windows-64-1.20231030.0.tgz", - "integrity": "sha512-fb/Jgj8Yqy3PO1jLhk7mTrHMkR8jklpbQFud6rL/aMAn5d6MQbaSrYOCjzkKGp0Zng8D2LIzSl+Fc0C9Sggxjg==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=16" - } - }, - "node_modules/@cloudflare/workers-types": { - "version": "4.20230404.0", - "resolved": "https://registry.npmjs.org/@cloudflare/workers-types/-/workers-types-4.20230404.0.tgz", - "integrity": "sha512-fG3oaJX1icfsGV74nhx1+AC6opvZsGqnpx6FvrcVqQaBmCNkjKNqDRFrpasXWFiOIvysBXHKQAzsAJkBZgnM+A==", - "dev": true - }, - "node_modules/@esbuild-plugins/node-globals-polyfill": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/@esbuild-plugins/node-globals-polyfill/-/node-globals-polyfill-0.2.3.tgz", - "integrity": "sha512-r3MIryXDeXDOZh7ih1l/yE9ZLORCd5e8vWg02azWRGj5SPTuoh69A2AIyn0Z31V/kHBfZ4HgWJ+OK3GTTwLmnw==", - "dev": true, - "peerDependencies": { - "esbuild": "*" - } - }, - "node_modules/@esbuild-plugins/node-modules-polyfill": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/@esbuild-plugins/node-modules-polyfill/-/node-modules-polyfill-0.2.2.tgz", - "integrity": "sha512-LXV7QsWJxRuMYvKbiznh+U1ilIop3g2TeKRzUxOG5X3YITc8JyyTa90BmLwqqv0YnX4v32CSlG+vsziZp9dMvA==", - "dev": true, - "dependencies": { - "escape-string-regexp": "^4.0.0", - "rollup-plugin-node-polyfills": "^0.2.1" - }, - "peerDependencies": { - "esbuild": "*" - } - }, - "node_modules/@esbuild/android-arm": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.17.19.tgz", - "integrity": "sha512-rIKddzqhmav7MSmoFCmDIb6e2W57geRsM94gV2l38fzhXMwq7hZoClug9USI2pFRGL06f4IOPHHpFNOkWieR8A==", - "cpu": [ - "arm" - ], - "dev": true, - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/android-arm64": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.17.19.tgz", - "integrity": "sha512-KBMWvEZooR7+kzY0BtbTQn0OAYY7CsiydT63pVEaPtVYF0hXbUaOyZog37DKxK7NF3XacBJOpYT4adIJh+avxA==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/android-x64": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.17.19.tgz", - "integrity": "sha512-uUTTc4xGNDT7YSArp/zbtmbhO0uEEK9/ETW29Wk1thYUJBz3IVnvgEiEwEa9IeLyvnpKrWK64Utw2bgUmDveww==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/darwin-arm64": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.17.19.tgz", - "integrity": "sha512-80wEoCfF/hFKM6WE1FyBHc9SfUblloAWx6FJkFWTWiCoht9Mc0ARGEM47e67W9rI09YoUxJL68WHfDRYEAvOhg==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/darwin-x64": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.17.19.tgz", - "integrity": "sha512-IJM4JJsLhRYr9xdtLytPLSH9k/oxR3boaUIYiHkAawtwNOXKE8KoU8tMvryogdcT8AU+Bflmh81Xn6Q0vTZbQw==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/freebsd-arm64": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.17.19.tgz", - "integrity": "sha512-pBwbc7DufluUeGdjSU5Si+P3SoMF5DQ/F/UmTSb8HXO80ZEAJmrykPyzo1IfNbAoaqw48YRpv8shwd1NoI0jcQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/freebsd-x64": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.17.19.tgz", - "integrity": "sha512-4lu+n8Wk0XlajEhbEffdy2xy53dpR06SlzvhGByyg36qJw6Kpfk7cp45DR/62aPH9mtJRmIyrXAS5UWBrJT6TQ==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-arm": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.17.19.tgz", - "integrity": "sha512-cdmT3KxjlOQ/gZ2cjfrQOtmhG4HJs6hhvm3mWSRDPtZ/lP5oe8FWceS10JaSJC13GBd4eH/haHnqf7hhGNLerA==", - "cpu": [ - "arm" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-arm64": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.17.19.tgz", - "integrity": "sha512-ct1Tg3WGwd3P+oZYqic+YZF4snNl2bsnMKRkb3ozHmnM0dGWuxcPTTntAF6bOP0Sp4x0PjSF+4uHQ1xvxfRKqg==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-ia32": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.17.19.tgz", - "integrity": "sha512-w4IRhSy1VbsNxHRQpeGCHEmibqdTUx61Vc38APcsRbuVgK0OPEnQ0YD39Brymn96mOx48Y2laBQGqgZ0j9w6SQ==", - "cpu": [ - "ia32" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-loong64": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.17.19.tgz", - "integrity": "sha512-2iAngUbBPMq439a+z//gE+9WBldoMp1s5GWsUSgqHLzLJ9WoZLZhpwWuym0u0u/4XmZ3gpHmzV84PonE+9IIdQ==", - "cpu": [ - "loong64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-mips64el": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.17.19.tgz", - "integrity": "sha512-LKJltc4LVdMKHsrFe4MGNPp0hqDFA1Wpt3jE1gEyM3nKUvOiO//9PheZZHfYRfYl6AwdTH4aTcXSqBerX0ml4A==", - "cpu": [ - "mips64el" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-ppc64": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.17.19.tgz", - "integrity": "sha512-/c/DGybs95WXNS8y3Ti/ytqETiW7EU44MEKuCAcpPto3YjQbyK3IQVKfF6nbghD7EcLUGl0NbiL5Rt5DMhn5tg==", - "cpu": [ - "ppc64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-riscv64": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.17.19.tgz", - "integrity": "sha512-FC3nUAWhvFoutlhAkgHf8f5HwFWUL6bYdvLc/TTuxKlvLi3+pPzdZiFKSWz/PF30TB1K19SuCxDTI5KcqASJqA==", - "cpu": [ - "riscv64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-s390x": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.17.19.tgz", - "integrity": "sha512-IbFsFbxMWLuKEbH+7sTkKzL6NJmG2vRyy6K7JJo55w+8xDk7RElYn6xvXtDW8HCfoKBFK69f3pgBJSUSQPr+4Q==", - "cpu": [ - "s390x" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-x64": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.17.19.tgz", - "integrity": "sha512-68ngA9lg2H6zkZcyp22tsVt38mlhWde8l3eJLWkyLrp4HwMUr3c1s/M2t7+kHIhvMjglIBrFpncX1SzMckomGw==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/netbsd-x64": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.17.19.tgz", - "integrity": "sha512-CwFq42rXCR8TYIjIfpXCbRX0rp1jo6cPIUPSaWwzbVI4aOfX96OXY8M6KNmtPcg7QjYeDmN+DD0Wp3LaBOLf4Q==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/openbsd-x64": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.17.19.tgz", - "integrity": "sha512-cnq5brJYrSZ2CF6c35eCmviIN3k3RczmHz8eYaVlNasVqsNY+JKohZU5MKmaOI+KkllCdzOKKdPs762VCPC20g==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/sunos-x64": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.17.19.tgz", - "integrity": "sha512-vCRT7yP3zX+bKWFeP/zdS6SqdWB8OIpaRq/mbXQxTGHnIxspRtigpkUcDMlSCOejlHowLqII7K2JKevwyRP2rg==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "sunos" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/win32-arm64": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.17.19.tgz", - "integrity": "sha512-yYx+8jwowUstVdorcMdNlzklLYhPxjniHWFKgRqH7IFlUEa0Umu3KuYplf1HUZZ422e3NU9F4LGb+4O0Kdcaag==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/win32-ia32": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.17.19.tgz", - "integrity": "sha512-eggDKanJszUtCdlVs0RB+h35wNlb5v4TWEkq4vZcmVt5u/HiDZrTXe2bWFQUez3RgNHwx/x4sk5++4NSSicKkw==", - "cpu": [ - "ia32" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/win32-x64": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.17.19.tgz", - "integrity": "sha512-lAhycmKnVOuRYNtRtatQR1LPQf2oYCkRGkSFnseDAKPl8lu5SOsK/e1sXe5a0Pc5kHIHe6P2I/ilntNv2xf3cA==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@fastify/busboy": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-2.1.0.tgz", - "integrity": "sha512-+KpH+QxZU7O4675t3mnkQKcZZg56u+K/Ct2K+N2AZYNVK8kyeo/bI18tI8aPm3tvNNRyTWfj6s5tnGNlcbQRsA==", - "dev": true, - "engines": { - "node": ">=14" - } - }, - "node_modules/@smithy/abort-controller": { - "version": "2.0.14", - "resolved": "https://registry.npmjs.org/@smithy/abort-controller/-/abort-controller-2.0.14.tgz", - "integrity": "sha512-zXtteuYLWbSXnzI3O6xq3FYvigYZFW8mdytGibfarLL2lxHto9L3ILtGVnVGmFZa7SDh62l39EnU5hesLN87Fw==", - "dependencies": { - "@smithy/types": "^2.6.0", - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@smithy/chunked-blob-reader": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@smithy/chunked-blob-reader/-/chunked-blob-reader-2.0.0.tgz", - "integrity": "sha512-k+J4GHJsMSAIQPChGBrjEmGS+WbPonCXesoqP9fynIqjn7rdOThdH8FAeCmokP9mxTYKQAKoHCLPzNlm6gh7Wg==", - "dependencies": { - "tslib": "^2.5.0" - } - }, - "node_modules/@smithy/chunked-blob-reader-native": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@smithy/chunked-blob-reader-native/-/chunked-blob-reader-native-2.0.1.tgz", - "integrity": "sha512-N2oCZRglhWKm7iMBu7S6wDzXirjAofi7tAd26cxmgibRYOBS4D3hGfmkwCpHdASZzwZDD8rluh0Rcqw1JeZDRw==", - "dependencies": { - "@smithy/util-base64": "^2.0.1", - "tslib": "^2.5.0" - } - }, - "node_modules/@smithy/config-resolver": { - "version": "2.0.19", - "resolved": "https://registry.npmjs.org/@smithy/config-resolver/-/config-resolver-2.0.19.tgz", - "integrity": "sha512-JsghnQ5zjWmjEVY8TFOulLdEOCj09SjRLugrHlkPZTIBBm7PQitCFVLThbsKPZQOP7N3ME1DU1nKUc1UaVnBog==", - "dependencies": { - "@smithy/node-config-provider": "^2.1.6", - "@smithy/types": "^2.6.0", - "@smithy/util-config-provider": "^2.0.0", - "@smithy/util-middleware": "^2.0.7", - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@smithy/credential-provider-imds": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/@smithy/credential-provider-imds/-/credential-provider-imds-2.1.2.tgz", - "integrity": "sha512-Y62jBWdoLPSYjr9fFvJf+KwTa1EunjVr6NryTEWCnwIY93OJxwV4t0qxjwdPl/XMsUkq79ppNJSEQN6Ohnhxjw==", - "dependencies": { - "@smithy/node-config-provider": "^2.1.6", - "@smithy/property-provider": "^2.0.15", - "@smithy/types": "^2.6.0", - "@smithy/url-parser": "^2.0.14", - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@smithy/eventstream-codec": { - "version": "2.0.14", - "resolved": "https://registry.npmjs.org/@smithy/eventstream-codec/-/eventstream-codec-2.0.14.tgz", - "integrity": "sha512-g/OU/MeWGfHDygoXgMWfG/Xb0QqDnAGcM9t2FRrVAhleXYRddGOEnfanR5cmHgB9ue52MJsyorqFjckzXsylaA==", - "dependencies": { - "@aws-crypto/crc32": "3.0.0", - "@smithy/types": "^2.6.0", - "@smithy/util-hex-encoding": "^2.0.0", - "tslib": "^2.5.0" - } - }, - "node_modules/@smithy/eventstream-serde-browser": { - "version": "2.0.14", - "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-browser/-/eventstream-serde-browser-2.0.14.tgz", - "integrity": "sha512-41wmYE9smDGJi1ZXp+LogH6BR7MkSsQD91wneIFISF/mupKULvoOJUkv/Nf0NMRxWlM3Bf1Vvi9FlR2oV4KU8Q==", - "dependencies": { - "@smithy/eventstream-serde-universal": "^2.0.14", - "@smithy/types": "^2.6.0", - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@smithy/eventstream-serde-config-resolver": { - "version": "2.0.14", - "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-config-resolver/-/eventstream-serde-config-resolver-2.0.14.tgz", - "integrity": "sha512-43IyRIzQ82s+5X+t/3Ood00CcWtAXQdmUIUKMed2Qg9REPk8SVIHhpm3rwewLwg+3G2Nh8NOxXlEQu6DsPUcMw==", - "dependencies": { - "@smithy/types": "^2.6.0", - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@smithy/eventstream-serde-node": { - "version": "2.0.14", - "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-node/-/eventstream-serde-node-2.0.14.tgz", - "integrity": "sha512-jVh9E2qAr6DxH5tWfCAl9HV6tI0pEQ3JVmu85JknDvYTC66djcjDdhctPV2EHuKWf2kjRiFJcMIn0eercW4THA==", - "dependencies": { - "@smithy/eventstream-serde-universal": "^2.0.14", - "@smithy/types": "^2.6.0", - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@smithy/eventstream-serde-universal": { - "version": "2.0.14", - "resolved": "https://registry.npmjs.org/@smithy/eventstream-serde-universal/-/eventstream-serde-universal-2.0.14.tgz", - "integrity": "sha512-Ie35+AISNn1NmEjn5b2SchIE49pvKp4Q74bE9ME5RULWI1MgXyGkQUajWd5E6OBSr/sqGcs+rD3IjPErXnCm9g==", - "dependencies": { - "@smithy/eventstream-codec": "^2.0.14", - "@smithy/types": "^2.6.0", - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@smithy/fetch-http-handler": { - "version": "2.2.7", - "resolved": "https://registry.npmjs.org/@smithy/fetch-http-handler/-/fetch-http-handler-2.2.7.tgz", - "integrity": "sha512-iSDBjxuH9TgrtMYAr7j5evjvkvgwLY3y+9D547uep+JNkZ1ZT+BaeU20j6I/bO/i26ilCWFImrlXTPsfQtZdIQ==", - "dependencies": { - "@smithy/protocol-http": "^3.0.10", - "@smithy/querystring-builder": "^2.0.14", - "@smithy/types": "^2.6.0", - "@smithy/util-base64": "^2.0.1", - "tslib": "^2.5.0" - } - }, - "node_modules/@smithy/hash-blob-browser": { - "version": "2.0.15", - "resolved": "https://registry.npmjs.org/@smithy/hash-blob-browser/-/hash-blob-browser-2.0.15.tgz", - "integrity": "sha512-HX/7GIyPUT/HDWVYe2HYQu0iRnSYpF4uZVNhAhZsObPRawk5Mv0PbyluBgIFI2DDCCKgL/tloCYYwycff1GtQg==", - "dependencies": { - "@smithy/chunked-blob-reader": "^2.0.0", - "@smithy/chunked-blob-reader-native": "^2.0.1", - "@smithy/types": "^2.6.0", - "tslib": "^2.5.0" - } - }, - "node_modules/@smithy/hash-node": { - "version": "2.0.16", - "resolved": "https://registry.npmjs.org/@smithy/hash-node/-/hash-node-2.0.16.tgz", - "integrity": "sha512-Wbi9A0PacMYUOwjAulQP90Wl3mQ6NDwnyrZQzFjDz+UzjXOSyQMgBrTkUBz+pVoYVlX3DUu24gWMZBcit+wOGg==", - "dependencies": { - "@smithy/types": "^2.6.0", - "@smithy/util-buffer-from": "^2.0.0", - "@smithy/util-utf8": "^2.0.2", - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@smithy/hash-stream-node": { - "version": "2.0.16", - "resolved": "https://registry.npmjs.org/@smithy/hash-stream-node/-/hash-stream-node-2.0.16.tgz", - "integrity": "sha512-4x24GFdeWos1Z49MC5sYdM1j+z32zcUr6oWM9Ggm3WudFAcRIcbG9uDQ1XgJ0Kl+ZTjpqLKniG0iuWvQb2Ud1A==", - "dependencies": { - "@smithy/types": "^2.6.0", - "@smithy/util-utf8": "^2.0.2", - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@smithy/invalid-dependency": { - "version": "2.0.14", - "resolved": "https://registry.npmjs.org/@smithy/invalid-dependency/-/invalid-dependency-2.0.14.tgz", - "integrity": "sha512-d8ohpwZo9RzTpGlAfsWtfm1SHBSU7+N4iuZ6MzR10xDTujJJWtmXYHK1uzcr7rggbpUTaWyHpPFgnf91q0EFqQ==", - "dependencies": { - "@smithy/types": "^2.6.0", - "tslib": "^2.5.0" - } - }, - "node_modules/@smithy/is-array-buffer": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@smithy/is-array-buffer/-/is-array-buffer-2.0.0.tgz", - "integrity": "sha512-z3PjFjMyZNI98JFRJi/U0nGoLWMSJlDjAW4QUX2WNZLas5C0CmVV6LJ01JI0k90l7FvpmixjWxPFmENSClQ7ug==", - "dependencies": { - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@smithy/md5-js": { - "version": "2.0.16", - "resolved": "https://registry.npmjs.org/@smithy/md5-js/-/md5-js-2.0.16.tgz", - "integrity": "sha512-YhWt9aKl+EMSNXyUTUo7I01WHf3HcCkPu/Hl2QmTNwrHT49eWaY7hptAMaERZuHFH0V5xHgPKgKZo2I93DFtgQ==", - "dependencies": { - "@smithy/types": "^2.6.0", - "@smithy/util-utf8": "^2.0.2", - "tslib": "^2.5.0" - } - }, - "node_modules/@smithy/middleware-content-length": { - "version": "2.0.16", - "resolved": "https://registry.npmjs.org/@smithy/middleware-content-length/-/middleware-content-length-2.0.16.tgz", - "integrity": "sha512-9ddDia3pp1d3XzLXKcm7QebGxLq9iwKf+J1LapvlSOhpF8EM9SjMeSrMOOFgG+2TfW5K3+qz4IAJYYm7INYCng==", - "dependencies": { - "@smithy/protocol-http": "^3.0.10", - "@smithy/types": "^2.6.0", - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@smithy/middleware-endpoint": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/@smithy/middleware-endpoint/-/middleware-endpoint-2.2.1.tgz", - "integrity": "sha512-dVDS7HNJl/wb0lpByXor6whqDbb1YlLoaoWYoelyYzLHioXOE7y/0iDwJWtDcN36/tVCw9EPBFZ3aans84jLpg==", - "dependencies": { - "@smithy/middleware-serde": "^2.0.14", - "@smithy/node-config-provider": "^2.1.6", - "@smithy/shared-ini-file-loader": "^2.2.5", - "@smithy/types": "^2.6.0", - "@smithy/url-parser": "^2.0.14", - "@smithy/util-middleware": "^2.0.7", - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@smithy/middleware-retry": { - "version": "2.0.21", - "resolved": "https://registry.npmjs.org/@smithy/middleware-retry/-/middleware-retry-2.0.21.tgz", - "integrity": "sha512-EZS1EXv1k6IJX6hyu/0yNQuPcPaXwG8SWljQHYueyRbOxmqYgoWMWPtfZj0xRRQ4YtLawQSpBgAeiJltq8/MPw==", - "dependencies": { - "@smithy/node-config-provider": "^2.1.6", - "@smithy/protocol-http": "^3.0.10", - "@smithy/service-error-classification": "^2.0.7", - "@smithy/types": "^2.6.0", - "@smithy/util-middleware": "^2.0.7", - "@smithy/util-retry": "^2.0.7", - "tslib": "^2.5.0", - "uuid": "^8.3.2" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@smithy/middleware-serde": { - "version": "2.0.14", - "resolved": "https://registry.npmjs.org/@smithy/middleware-serde/-/middleware-serde-2.0.14.tgz", - "integrity": "sha512-hFi3FqoYWDntCYA2IGY6gJ6FKjq2gye+1tfxF2HnIJB5uW8y2DhpRNBSUMoqP+qvYzRqZ6ntv4kgbG+o3pX57g==", - "dependencies": { - "@smithy/types": "^2.6.0", - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@smithy/middleware-stack": { - "version": "2.0.8", - "resolved": "https://registry.npmjs.org/@smithy/middleware-stack/-/middleware-stack-2.0.8.tgz", - "integrity": "sha512-7/N59j0zWqVEKExJcA14MrLDZ/IeN+d6nbkN8ucs+eURyaDUXWYlZrQmMOd/TyptcQv0+RDlgag/zSTTV62y/Q==", - "dependencies": { - "@smithy/types": "^2.6.0", - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@smithy/node-config-provider": { - "version": "2.1.6", - "resolved": "https://registry.npmjs.org/@smithy/node-config-provider/-/node-config-provider-2.1.6.tgz", - "integrity": "sha512-HLqTs6O78m3M3z1cPLFxddxhEPv5MkVatfPuxoVO3A+cHZanNd/H5I6btcdHy6N2CB1MJ/lihJC92h30SESsBA==", - "dependencies": { - "@smithy/property-provider": "^2.0.15", - "@smithy/shared-ini-file-loader": "^2.2.5", - "@smithy/types": "^2.6.0", - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@smithy/node-http-handler": { - "version": "2.1.10", - "resolved": "https://registry.npmjs.org/@smithy/node-http-handler/-/node-http-handler-2.1.10.tgz", - "integrity": "sha512-lkALAwtN6odygIM4nB8aHDahINM6WXXjNrZmWQAh0RSossySRT2qa31cFv0ZBuAYVWeprskRk13AFvvLmf1WLw==", - "dependencies": { - "@smithy/abort-controller": "^2.0.14", - "@smithy/protocol-http": "^3.0.10", - "@smithy/querystring-builder": "^2.0.14", - "@smithy/types": "^2.6.0", - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@smithy/property-provider": { - "version": "2.0.15", - "resolved": "https://registry.npmjs.org/@smithy/property-provider/-/property-provider-2.0.15.tgz", - "integrity": "sha512-YbRFBn8oiiC3o1Kn3a4KjGa6k47rCM9++5W9cWqYn9WnkyH+hBWgfJAckuxpyA2Hq6Ys4eFrWzXq6fqHEw7iew==", - "dependencies": { - "@smithy/types": "^2.6.0", - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@smithy/protocol-http": { - "version": "3.0.10", - "resolved": "https://registry.npmjs.org/@smithy/protocol-http/-/protocol-http-3.0.10.tgz", - "integrity": "sha512-6+tjNk7rXW7YTeGo9qwxXj/2BFpJTe37kTj3EnZCoX/nH+NP/WLA7O83fz8XhkGqsaAhLUPo/bB12vvd47nsmg==", - "dependencies": { - "@smithy/types": "^2.6.0", - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@smithy/querystring-builder": { - "version": "2.0.14", - "resolved": "https://registry.npmjs.org/@smithy/querystring-builder/-/querystring-builder-2.0.14.tgz", - "integrity": "sha512-lQ4pm9vTv9nIhl5jt6uVMPludr6syE2FyJmHsIJJuOD7QPIJnrf9HhUGf1iHh9KJ4CUv21tpOU3X6s0rB6uJ0g==", - "dependencies": { - "@smithy/types": "^2.6.0", - "@smithy/util-uri-escape": "^2.0.0", - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@smithy/querystring-parser": { - "version": "2.0.14", - "resolved": "https://registry.npmjs.org/@smithy/querystring-parser/-/querystring-parser-2.0.14.tgz", - "integrity": "sha512-+cbtXWI9tNtQjlgQg3CA+pvL3zKTAxPnG3Pj6MP89CR3vi3QMmD0SOWoq84tqZDnJCxlsusbgIXk1ngMReXo+A==", - "dependencies": { - "@smithy/types": "^2.6.0", - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@smithy/service-error-classification": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/@smithy/service-error-classification/-/service-error-classification-2.0.7.tgz", - "integrity": "sha512-LLxgW12qGz8doYto15kZ4x1rHjtXl0BnCG6T6Wb8z2DI4PT9cJfOSvzbuLzy7+5I24PAepKgFeWHRd9GYy3Z9w==", - "dependencies": { - "@smithy/types": "^2.6.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@smithy/shared-ini-file-loader": { - "version": "2.2.5", - "resolved": "https://registry.npmjs.org/@smithy/shared-ini-file-loader/-/shared-ini-file-loader-2.2.5.tgz", - "integrity": "sha512-LHA68Iu7SmNwfAVe8egmjDCy648/7iJR/fK1UnVw+iAOUJoEYhX2DLgVd5pWllqdDiRbQQzgaHLcRokM+UFR1w==", - "dependencies": { - "@smithy/types": "^2.6.0", - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@smithy/signature-v4": { - "version": "2.0.16", - "resolved": "https://registry.npmjs.org/@smithy/signature-v4/-/signature-v4-2.0.16.tgz", - "integrity": "sha512-ilLY85xS2kZZzTb83diQKYLIYALvart0KnBaKnIRnMBHAGEio5aHSlANQoxVn0VsonwmQ3CnWhnCT0sERD8uTg==", - "dependencies": { - "@smithy/eventstream-codec": "^2.0.14", - "@smithy/is-array-buffer": "^2.0.0", - "@smithy/types": "^2.6.0", - "@smithy/util-hex-encoding": "^2.0.0", - "@smithy/util-middleware": "^2.0.7", - "@smithy/util-uri-escape": "^2.0.0", - "@smithy/util-utf8": "^2.0.2", - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@smithy/smithy-client": { - "version": "2.1.16", - "resolved": "https://registry.npmjs.org/@smithy/smithy-client/-/smithy-client-2.1.16.tgz", - "integrity": "sha512-Lw67+yQSpLl4YkDLUzI2KgS8TXclXmbzSeOJUmRFS4ueT56B4pw3RZRF/SRzvgyxM/HxgkUan8oSHXCujPDafQ==", - "dependencies": { - "@smithy/middleware-stack": "^2.0.8", - "@smithy/types": "^2.6.0", - "@smithy/util-stream": "^2.0.21", - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@smithy/types": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/@smithy/types/-/types-2.6.0.tgz", - "integrity": "sha512-PgqxJq2IcdMF9iAasxcqZqqoOXBHufEfmbEUdN1pmJrJltT42b0Sc8UiYSWWzKkciIp9/mZDpzYi4qYG1qqg6g==", - "dependencies": { - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@smithy/url-parser": { - "version": "2.0.14", - "resolved": "https://registry.npmjs.org/@smithy/url-parser/-/url-parser-2.0.14.tgz", - "integrity": "sha512-kbu17Y1AFXi5lNlySdDj7ZzmvupyWKCX/0jNZ8ffquRyGdbDZb+eBh0QnWqsSmnZa/ctyWaTf7n4l/pXLExrnw==", - "dependencies": { - "@smithy/querystring-parser": "^2.0.14", - "@smithy/types": "^2.6.0", - "tslib": "^2.5.0" - } - }, - "node_modules/@smithy/util-base64": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@smithy/util-base64/-/util-base64-2.0.1.tgz", - "integrity": "sha512-DlI6XFYDMsIVN+GH9JtcRp3j02JEVuWIn/QOZisVzpIAprdsxGveFed0bjbMRCqmIFe8uetn5rxzNrBtIGrPIQ==", - "dependencies": { - "@smithy/util-buffer-from": "^2.0.0", - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@smithy/util-body-length-browser": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@smithy/util-body-length-browser/-/util-body-length-browser-2.0.0.tgz", - "integrity": "sha512-JdDuS4ircJt+FDnaQj88TzZY3+njZ6O+D3uakS32f2VNnDo3vyEuNdBOh/oFd8Df1zSZOuH1HEChk2AOYDezZg==", - "dependencies": { - "tslib": "^2.5.0" - } - }, - "node_modules/@smithy/util-body-length-node": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@smithy/util-body-length-node/-/util-body-length-node-2.1.0.tgz", - "integrity": "sha512-/li0/kj/y3fQ3vyzn36NTLGmUwAICb7Jbe/CsWCktW363gh1MOcpEcSO3mJ344Gv2dqz8YJCLQpb6hju/0qOWw==", - "dependencies": { - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@smithy/util-buffer-from": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@smithy/util-buffer-from/-/util-buffer-from-2.0.0.tgz", - "integrity": "sha512-/YNnLoHsR+4W4Vf2wL5lGv0ksg8Bmk3GEGxn2vEQt52AQaPSCuaO5PM5VM7lP1K9qHRKHwrPGktqVoAHKWHxzw==", - "dependencies": { - "@smithy/is-array-buffer": "^2.0.0", - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@smithy/util-config-provider": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@smithy/util-config-provider/-/util-config-provider-2.0.0.tgz", - "integrity": "sha512-xCQ6UapcIWKxXHEU4Mcs2s7LcFQRiU3XEluM2WcCjjBtQkUN71Tb+ydGmJFPxMUrW/GWMgQEEGipLym4XG0jZg==", - "dependencies": { - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@smithy/util-defaults-mode-browser": { - "version": "2.0.20", - "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-2.0.20.tgz", - "integrity": "sha512-QJtnbTIl0/BbEASkx1MUFf6EaoWqWW1/IM90N++8NNscePvPf77GheYfpoPis6CBQawUWq8QepTP2QUSAdrVkw==", - "dependencies": { - "@smithy/property-provider": "^2.0.15", - "@smithy/smithy-client": "^2.1.16", - "@smithy/types": "^2.6.0", - "bowser": "^2.11.0", - "tslib": "^2.5.0" - }, - "engines": { - "node": ">= 10.0.0" - } - }, - "node_modules/@smithy/util-defaults-mode-node": { - "version": "2.0.26", - "resolved": "https://registry.npmjs.org/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-2.0.26.tgz", - "integrity": "sha512-lGFPOFCHv1ql019oegYqa54BZH7HREw6EBqjDLbAr0wquMX0BDi2sg8TJ6Eq+JGLijkZbJB73m4+aK8OFAapMg==", - "dependencies": { - "@smithy/config-resolver": "^2.0.19", - "@smithy/credential-provider-imds": "^2.1.2", - "@smithy/node-config-provider": "^2.1.6", - "@smithy/property-provider": "^2.0.15", - "@smithy/smithy-client": "^2.1.16", - "@smithy/types": "^2.6.0", - "tslib": "^2.5.0" - }, - "engines": { - "node": ">= 10.0.0" - } - }, - "node_modules/@smithy/util-endpoints": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/@smithy/util-endpoints/-/util-endpoints-1.0.5.tgz", - "integrity": "sha512-K7qNuCOD5K/90MjHvHm9kJldrfm40UxWYQxNEShMFxV/lCCCRIg8R4uu1PFAxRvPxNpIdcrh1uK6I1ISjDXZJw==", - "dependencies": { - "@smithy/node-config-provider": "^2.1.6", - "@smithy/types": "^2.6.0", - "tslib": "^2.5.0" - }, - "engines": { - "node": ">= 14.0.0" - } - }, - "node_modules/@smithy/util-hex-encoding": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@smithy/util-hex-encoding/-/util-hex-encoding-2.0.0.tgz", - "integrity": "sha512-c5xY+NUnFqG6d7HFh1IFfrm3mGl29lC+vF+geHv4ToiuJCBmIfzx6IeHLg+OgRdPFKDXIw6pvi+p3CsscaMcMA==", - "dependencies": { - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@smithy/util-middleware": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/@smithy/util-middleware/-/util-middleware-2.0.7.tgz", - "integrity": "sha512-tRINOTlf1G9B0ECarFQAtTgMhpnrMPSa+5j4ZEwEawCLfTFTavk6757sxhE4RY5RMlD/I3x+DCS8ZUiR8ho9Pw==", - "dependencies": { - "@smithy/types": "^2.6.0", - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@smithy/util-retry": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/@smithy/util-retry/-/util-retry-2.0.7.tgz", - "integrity": "sha512-fIe5yARaF0+xVT1XKcrdnHKTJ1Vc4+3e3tLDjCuIcE9b6fkBzzGFY7AFiX4M+vj6yM98DrwkuZeHf7/hmtVp0Q==", - "dependencies": { - "@smithy/service-error-classification": "^2.0.7", - "@smithy/types": "^2.6.0", - "tslib": "^2.5.0" - }, - "engines": { - "node": ">= 14.0.0" - } - }, - "node_modules/@smithy/util-stream": { - "version": "2.0.21", - "resolved": "https://registry.npmjs.org/@smithy/util-stream/-/util-stream-2.0.21.tgz", - "integrity": "sha512-0BUE16d7n1x7pi1YluXJdB33jOTyBChT0j/BlOkFa9uxfg6YqXieHxjHNuCdJRARa7AZEj32LLLEPJ1fSa4inA==", - "dependencies": { - "@smithy/fetch-http-handler": "^2.2.7", - "@smithy/node-http-handler": "^2.1.10", - "@smithy/types": "^2.6.0", - "@smithy/util-base64": "^2.0.1", - "@smithy/util-buffer-from": "^2.0.0", - "@smithy/util-hex-encoding": "^2.0.0", - "@smithy/util-utf8": "^2.0.2", - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@smithy/util-uri-escape": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@smithy/util-uri-escape/-/util-uri-escape-2.0.0.tgz", - "integrity": "sha512-ebkxsqinSdEooQduuk9CbKcI+wheijxEb3utGXkCoYQkJnwTnLbH1JXGimJtUkQwNQbsbuYwG2+aFVyZf5TLaw==", - "dependencies": { - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@smithy/util-utf8": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/@smithy/util-utf8/-/util-utf8-2.0.2.tgz", - "integrity": "sha512-qOiVORSPm6Ce4/Yu6hbSgNHABLP2VMv8QOC3tTDNHHlWY19pPyc++fBTbZPtx6egPXi4HQxKDnMxVxpbtX2GoA==", - "dependencies": { - "@smithy/util-buffer-from": "^2.0.0", - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@smithy/util-waiter": { - "version": "2.0.14", - "resolved": "https://registry.npmjs.org/@smithy/util-waiter/-/util-waiter-2.0.14.tgz", - "integrity": "sha512-Q6gSz4GUNjNGhrfNg+2Mjy+7K4pEI3r82x1b/+3dSc03MQqobMiUrRVN/YK/4nHVagvBELCoXsiHAFQJNQ5BeA==", - "dependencies": { - "@smithy/abort-controller": "^2.0.14", - "@smithy/types": "^2.6.0", - "tslib": "^2.5.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@types/node": { - "version": "20.10.2", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.10.2.tgz", - "integrity": "sha512-37MXfxkb0vuIlRKHNxwCkb60PNBpR94u4efQuN4JgIAm66zfCDXGSAFCef9XUWFovX2R1ok6Z7MHhtdVXXkkIw==", - "dev": true, - "dependencies": { - "undici-types": "~5.26.4" - } - }, - "node_modules/@types/node-forge": { - "version": "1.3.10", - "resolved": "https://registry.npmjs.org/@types/node-forge/-/node-forge-1.3.10.tgz", - "integrity": "sha512-y6PJDYN4xYBxwd22l+OVH35N+1fCYWiuC3aiP2SlXVE6Lo7SS+rSx9r89hLxrP4pn6n1lBGhHJ12pj3F3Mpttw==", - "dev": true, - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/acorn": { - "version": "8.11.2", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.2.tgz", - "integrity": "sha512-nc0Axzp/0FILLEVsm4fNwLCwMttvhEI263QtVPQcbpfZZ3ts0hLsZGOpE6czNlid7CJ9MlyH8reXkpsf3YUY4w==", - "dev": true, - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/acorn-walk": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.0.tgz", - "integrity": "sha512-FS7hV565M5l1R08MXqo8odwMTB02C2UqzB17RVgu9EyuYFBqJZ3/ZY97sQD5FewVu1UyDFc1yztUDrAwT0EypA==", - "dev": true, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/anymatch": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", - "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", - "dev": true, - "dependencies": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/as-table": { - "version": "1.0.55", - "resolved": "https://registry.npmjs.org/as-table/-/as-table-1.0.55.tgz", - "integrity": "sha512-xvsWESUJn0JN421Xb9MQw6AsMHRCUknCe0Wjlxvjud80mU4E6hQf1A6NzQKcYNmYw62MfzEtXc+badstZP3JpQ==", - "dev": true, - "dependencies": { - "printable-characters": "^1.0.42" - } - }, - "node_modules/aws4fetch": { - "version": "1.0.17", - "resolved": "https://registry.npmjs.org/aws4fetch/-/aws4fetch-1.0.17.tgz", - "integrity": "sha512-4IbOvsxqxeOSxI4oA+8xEO8SzBMVlzbSTgGy/EF83rHnQ/aKtP6Sc6YV/k0oiW0mqrcxuThlbDosnvetGOuO+g==" - }, - "node_modules/binary-extensions": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", - "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/blake3-wasm": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/blake3-wasm/-/blake3-wasm-2.1.5.tgz", - "integrity": "sha512-F1+K8EbfOZE49dtoPtmxUQrpXaBIl3ICvasLh+nJta0xkz+9kF/7uet9fLnwKqhDrmj6g+6K3Tw9yQPUg2ka5g==", - "dev": true - }, - "node_modules/bowser": { - "version": "2.11.0", - "resolved": "https://registry.npmjs.org/bowser/-/bowser-2.11.0.tgz", - "integrity": "sha512-AlcaJBi/pqqJBIQ8U9Mcpc9i8Aqxn88Skv5d+xBX006BY5u8N3mGLHa5Lgppa7L/HfwgwLgZ6NYs+Ag6uUmJRA==" - }, - "node_modules/braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "dev": true, - "dependencies": { - "fill-range": "^7.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/buffer-from": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", - "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", - "dev": true - }, - "node_modules/capnp-ts": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/capnp-ts/-/capnp-ts-0.7.0.tgz", - "integrity": "sha512-XKxXAC3HVPv7r674zP0VC3RTXz+/JKhfyw94ljvF80yynK6VkTnqE3jMuN8b3dUVmmc43TjyxjW4KTsmB3c86g==", - "dev": true, - "dependencies": { - "debug": "^4.3.1", - "tslib": "^2.2.0" - } - }, - "node_modules/chokidar": { - "version": "3.5.3", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", - "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://paulmillr.com/funding/" - } - ], - "dependencies": { - "anymatch": "~3.1.2", - "braces": "~3.0.2", - "glob-parent": "~5.1.2", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.6.0" - }, - "engines": { - "node": ">= 8.10.0" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" - } - }, - "node_modules/cookie": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz", - "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==", - "dev": true, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/data-uri-to-buffer": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-2.0.2.tgz", - "integrity": "sha512-ND9qDTLc6diwj+Xe5cdAgVTbLVdXbtxTJRXRhli8Mowuaan+0EJOtdqJ0QCHNSSPyoXGx9HX2/VMnKeC34AChA==", - "dev": true - }, - "node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dev": true, - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/esbuild": { - "version": "0.17.19", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.17.19.tgz", - "integrity": "sha512-XQ0jAPFkK/u3LcVRcvVHQcTIqD6E2H1fvZMA5dQPSOWb3suUbWbfbRf94pjc0bNzRYLfIrDRQXr7X+LHIm5oHw==", - "dev": true, - "hasInstallScript": true, - "bin": { - "esbuild": "bin/esbuild" - }, - "engines": { - "node": ">=12" - }, - "optionalDependencies": { - "@esbuild/android-arm": "0.17.19", - "@esbuild/android-arm64": "0.17.19", - "@esbuild/android-x64": "0.17.19", - "@esbuild/darwin-arm64": "0.17.19", - "@esbuild/darwin-x64": "0.17.19", - "@esbuild/freebsd-arm64": "0.17.19", - "@esbuild/freebsd-x64": "0.17.19", - "@esbuild/linux-arm": "0.17.19", - "@esbuild/linux-arm64": "0.17.19", - "@esbuild/linux-ia32": "0.17.19", - "@esbuild/linux-loong64": "0.17.19", - "@esbuild/linux-mips64el": "0.17.19", - "@esbuild/linux-ppc64": "0.17.19", - "@esbuild/linux-riscv64": "0.17.19", - "@esbuild/linux-s390x": "0.17.19", - "@esbuild/linux-x64": "0.17.19", - "@esbuild/netbsd-x64": "0.17.19", - "@esbuild/openbsd-x64": "0.17.19", - "@esbuild/sunos-x64": "0.17.19", - "@esbuild/win32-arm64": "0.17.19", - "@esbuild/win32-ia32": "0.17.19", - "@esbuild/win32-x64": "0.17.19" - } - }, - "node_modules/escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/estree-walker": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-0.6.1.tgz", - "integrity": "sha512-SqmZANLWS0mnatqbSfRP5g8OXZC12Fgg1IwNtLsyHDzJizORW4khDfjPqJZsemPWBB2uqykUah5YpQ6epsqC/w==", - "dev": true - }, - "node_modules/exit-hook": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/exit-hook/-/exit-hook-2.2.1.tgz", - "integrity": "sha512-eNTPlAD67BmP31LDINZ3U7HSF8l57TxOY2PmBJ1shpCvpnxBF93mWCE8YHBnXs8qiUZJc9WDcWIeC3a2HIAMfw==", - "dev": true, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/fast-xml-parser": { - "version": "4.2.5", - "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.2.5.tgz", - "integrity": "sha512-B9/wizE4WngqQftFPmdaMYlXoJlJOYxGQOanC77fq9k8+Z0v5dDSVh+3glErdIROP//s/jgb7ZuxKfB8nVyo0g==", - "funding": [ - { - "type": "paypal", - "url": "https://paypal.me/naturalintelligence" - }, - { - "type": "github", - "url": "https://github.com/sponsors/NaturalIntelligence" - } - ], - "dependencies": { - "strnum": "^1.0.5" - }, - "bin": { - "fxparser": "src/cli/cli.js" - } - }, - "node_modules/fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", - "dev": true, - "dependencies": { - "to-regex-range": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/fsevents": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", - "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", - "dev": true, - "hasInstallScript": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/get-source": { - "version": "2.0.12", - "resolved": "https://registry.npmjs.org/get-source/-/get-source-2.0.12.tgz", - "integrity": "sha512-X5+4+iD+HoSeEED+uwrQ07BOQr0kEDFMVqqpBuI+RaZBpBpHCuXxo70bjar6f0b0u/DQJsJ7ssurpP0V60Az+w==", - "dev": true, - "dependencies": { - "data-uri-to-buffer": "^2.0.0", - "source-map": "^0.6.1" - } - }, - "node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/glob-to-regexp": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", - "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", - "dev": true - }, - "node_modules/is-binary-path": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", - "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", - "dev": true, - "dependencies": { - "binary-extensions": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-glob": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dev": true, - "dependencies": { - "is-extglob": "^2.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true, - "engines": { - "node": ">=0.12.0" - } - }, - "node_modules/itty-router": { - "version": "3.0.12", - "resolved": "https://registry.npmjs.org/itty-router/-/itty-router-3.0.12.tgz", - "integrity": "sha512-s98XTPhle6GGbaFf0kYrOD3Q8gyhnqvOqkwYijC3AmkceNKqWUp13YHg6dWmqmVv4pP7l7c94XI92I0EXVGO0w==" - }, - "node_modules/magic-string": { - "version": "0.25.9", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.9.tgz", - "integrity": "sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ==", - "dev": true, - "dependencies": { - "sourcemap-codec": "^1.4.8" - } - }, - "node_modules/mime": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-3.0.0.tgz", - "integrity": "sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==", - "dev": true, - "bin": { - "mime": "cli.js" - }, - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/miniflare": { - "version": "3.20231030.2", - "resolved": "https://registry.npmjs.org/miniflare/-/miniflare-3.20231030.2.tgz", - "integrity": "sha512-+DYdMqWlUaY4wBylIjewNu8OVsPFquYjQkxoSb2jGIMBmlKaef65Hn2Bu8sub5tQzQ8tLO0FRklmD2Upx0HCCQ==", - "dev": true, - "dependencies": { - "acorn": "^8.8.0", - "acorn-walk": "^8.2.0", - "capnp-ts": "^0.7.0", - "exit-hook": "^2.2.1", - "glob-to-regexp": "^0.4.1", - "source-map-support": "0.5.21", - "stoppable": "^1.1.0", - "undici": "^5.22.1", - "workerd": "1.20231030.0", - "ws": "^8.11.0", - "youch": "^3.2.2", - "zod": "^3.20.6" - }, - "bin": { - "miniflare": "bootstrap.js" - }, - "engines": { - "node": ">=16.13" - } - }, - "node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - }, - "node_modules/mustache": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/mustache/-/mustache-4.2.0.tgz", - "integrity": "sha512-71ippSywq5Yb7/tVYyGbkBggbU8H3u5Rz56fH60jGFgr8uHwxs+aSKeqmluIVzM0m0kB7xQjKS6qPfd0b2ZoqQ==", - "dev": true, - "bin": { - "mustache": "bin/mustache" - } - }, - "node_modules/nanoid": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.6.tgz", - "integrity": "sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "bin": { - "nanoid": "bin/nanoid.cjs" - }, - "engines": { - "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" - } - }, - "node_modules/node-forge": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.1.tgz", - "integrity": "sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==", - "dev": true, - "engines": { - "node": ">= 6.13.0" - } - }, - "node_modules/normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/path-to-regexp": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.2.1.tgz", - "integrity": "sha512-JLyh7xT1kizaEvcaXOQwOc2/Yhw6KZOvPf1S8401UyLk86CU79LN3vl7ztXGm/pZ+YjoyAJ4rxmHwbkBXJX+yw==", - "dev": true - }, - "node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true, - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/printable-characters": { - "version": "1.0.42", - "resolved": "https://registry.npmjs.org/printable-characters/-/printable-characters-1.0.42.tgz", - "integrity": "sha512-dKp+C4iXWK4vVYZmYSd0KBH5F/h1HoZRsbJ82AVKRO3PEo8L4lBS/vLwhVtpwwuYcoIsVY+1JYKR268yn480uQ==", - "dev": true - }, - "node_modules/readdirp": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", - "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", - "dev": true, - "dependencies": { - "picomatch": "^2.2.1" - }, - "engines": { - "node": ">=8.10.0" - } - }, - "node_modules/resolve.exports": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-2.0.2.tgz", - "integrity": "sha512-X2UW6Nw3n/aMgDVy+0rSqgHlv39WZAlZrXCdnbyEiKm17DSqHX4MmQMaST3FbeWR5FTuRcUwYAziZajji0Y7mg==", - "dev": true, - "engines": { - "node": ">=10" - } - }, - "node_modules/rollup-plugin-inject": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rollup-plugin-inject/-/rollup-plugin-inject-3.0.2.tgz", - "integrity": "sha512-ptg9PQwzs3orn4jkgXJ74bfs5vYz1NCZlSQMBUA0wKcGp5i5pA1AO3fOUEte8enhGUC+iapTCzEWw2jEFFUO/w==", - "deprecated": "This package has been deprecated and is no longer maintained. Please use @rollup/plugin-inject.", - "dev": true, - "dependencies": { - "estree-walker": "^0.6.1", - "magic-string": "^0.25.3", - "rollup-pluginutils": "^2.8.1" - } - }, - "node_modules/rollup-plugin-node-polyfills": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/rollup-plugin-node-polyfills/-/rollup-plugin-node-polyfills-0.2.1.tgz", - "integrity": "sha512-4kCrKPTJ6sK4/gLL/U5QzVT8cxJcofO0OU74tnB19F40cmuAKSzH5/siithxlofFEjwvw1YAhPmbvGNA6jEroA==", - "dev": true, - "dependencies": { - "rollup-plugin-inject": "^3.0.0" - } - }, - "node_modules/rollup-pluginutils": { - "version": "2.8.2", - "resolved": "https://registry.npmjs.org/rollup-pluginutils/-/rollup-pluginutils-2.8.2.tgz", - "integrity": "sha512-EEp9NhnUkwY8aif6bxgovPHMoMoNr2FulJziTndpt5H9RdwC47GSGuII9XxpSdzVGM0GWrNPHV6ie1LTNJPaLQ==", - "dev": true, - "dependencies": { - "estree-walker": "^0.6.1" - } - }, - "node_modules/selfsigned": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-2.4.1.tgz", - "integrity": "sha512-th5B4L2U+eGLq1TVh7zNRGBapioSORUeymIydxgFpwww9d2qyKvtuPU2jJuHvYAwwqi2Y596QBL3eEqcPEYL8Q==", - "dev": true, - "dependencies": { - "@types/node-forge": "^1.3.0", - "node-forge": "^1" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/source-map-support": { - "version": "0.5.21", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", - "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", - "dev": true, - "dependencies": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - } - }, - "node_modules/sourcemap-codec": { - "version": "1.4.8", - "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz", - "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==", - "deprecated": "Please use @jridgewell/sourcemap-codec instead", - "dev": true - }, - "node_modules/stacktracey": { - "version": "2.1.8", - "resolved": "https://registry.npmjs.org/stacktracey/-/stacktracey-2.1.8.tgz", - "integrity": "sha512-Kpij9riA+UNg7TnphqjH7/CzctQ/owJGNbFkfEeve4Z4uxT5+JapVLFXcsurIfN34gnTWZNJ/f7NMG0E8JDzTw==", - "dev": true, - "dependencies": { - "as-table": "^1.0.36", - "get-source": "^2.0.12" - } - }, - "node_modules/stoppable": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/stoppable/-/stoppable-1.1.0.tgz", - "integrity": "sha512-KXDYZ9dszj6bzvnEMRYvxgeTHU74QBFL54XKtP3nyMuJ81CFYtABZ3bAzL2EdFUaEwJOBOgENyFj3R7oTzDyyw==", - "dev": true, - "engines": { - "node": ">=4", - "npm": ">=6" - } - }, - "node_modules/strnum": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/strnum/-/strnum-1.0.5.tgz", - "integrity": "sha512-J8bbNyKKXl5qYcR36TIO8W3mVGVHrmmxsd5PAItGkmyzwJvybiw2IVq5nqd0i4LSNSkB/sx9VHllbfFdr9k1JA==" - }, - "node_modules/to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, - "dependencies": { - "is-number": "^7.0.0" - }, - "engines": { - "node": ">=8.0" - } - }, - "node_modules/tslib": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", - "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" - }, - "node_modules/typescript": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.0.4.tgz", - "integrity": "sha512-cW9T5W9xY37cc+jfEnaUvX91foxtHkza3Nw3wkoF4sSlKn0MONdkdEndig/qPBWXNkmplh3NzayQzCiHM4/hqw==", - "dev": true, - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=12.20" - } - }, - "node_modules/undici": { - "version": "5.28.2", - "resolved": "https://registry.npmjs.org/undici/-/undici-5.28.2.tgz", - "integrity": "sha512-wh1pHJHnUeQV5Xa8/kyQhO7WFa8M34l026L5P/+2TYiakvGy5Rdc8jWZVyG7ieht/0WgJLEd3kcU5gKx+6GC8w==", - "dev": true, - "dependencies": { - "@fastify/busboy": "^2.0.0" - }, - "engines": { - "node": ">=14.0" - } - }, - "node_modules/undici-types": { - "version": "5.26.5", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", - "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", - "dev": true - }, - "node_modules/uuid": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", - "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", - "bin": { - "uuid": "dist/bin/uuid" - } - }, - "node_modules/workerd": { - "version": "1.20231030.0", - "resolved": "https://registry.npmjs.org/workerd/-/workerd-1.20231030.0.tgz", - "integrity": "sha512-+FSW+d31f8RrjHanFf/R9A+Z0csf3OtsvzdPmAKuwuZm/5HrBv83cvG9fFeTxl7/nI6irUUXIRF9xcj/NomQzQ==", - "dev": true, - "hasInstallScript": true, - "bin": { - "workerd": "bin/workerd" - }, - "engines": { - "node": ">=16" - }, - "optionalDependencies": { - "@cloudflare/workerd-darwin-64": "1.20231030.0", - "@cloudflare/workerd-darwin-arm64": "1.20231030.0", - "@cloudflare/workerd-linux-64": "1.20231030.0", - "@cloudflare/workerd-linux-arm64": "1.20231030.0", - "@cloudflare/workerd-windows-64": "1.20231030.0" - } - }, - "node_modules/wrangler": { - "version": "3.18.0", - "resolved": "https://registry.npmjs.org/wrangler/-/wrangler-3.18.0.tgz", - "integrity": "sha512-3UrmldsD84JDBa7HRZ5ACFr1nH+ZZs+Hth37Iv/mAaW2DiQOGcrevKwn4dofmTQO2qYP01u1vtfVJxLM0mq+1w==", - "dev": true, - "dependencies": { - "@cloudflare/kv-asset-handler": "^0.2.0", - "@esbuild-plugins/node-globals-polyfill": "^0.2.3", - "@esbuild-plugins/node-modules-polyfill": "^0.2.2", - "blake3-wasm": "^2.1.5", - "chokidar": "^3.5.3", - "esbuild": "0.17.19", - "miniflare": "3.20231030.2", - "nanoid": "^3.3.3", - "path-to-regexp": "^6.2.0", - "resolve.exports": "^2.0.2", - "selfsigned": "^2.0.1", - "source-map": "0.6.1", - "source-map-support": "0.5.21", - "xxhash-wasm": "^1.0.1" - }, - "bin": { - "wrangler": "bin/wrangler.js", - "wrangler2": "bin/wrangler.js" - }, - "engines": { - "node": ">=16.17.0" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" - } - }, - "node_modules/ws": { - "version": "8.14.2", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.14.2.tgz", - "integrity": "sha512-wEBG1ftX4jcglPxgFCMJmZ2PLtSbJ2Peg6TmpJFTbe9GZYOQCDPdMYu/Tm0/bGZkw8paZnJY45J4K2PZrLYq8g==", - "dev": true, - "engines": { - "node": ">=10.0.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": ">=5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } - } - }, - "node_modules/xxhash-wasm": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/xxhash-wasm/-/xxhash-wasm-1.0.2.tgz", - "integrity": "sha512-ibF0Or+FivM9lNrg+HGJfVX8WJqgo+kCLDc4vx6xMeTce7Aj+DLttKbxxRR/gNLSAelRc1omAPlJ77N/Jem07A==", - "dev": true - }, - "node_modules/youch": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/youch/-/youch-3.3.3.tgz", - "integrity": "sha512-qSFXUk3UZBLfggAW3dJKg0BMblG5biqSF8M34E06o5CSsZtH92u9Hqmj2RzGiHDi64fhe83+4tENFP2DB6t6ZA==", - "dev": true, - "dependencies": { - "cookie": "^0.5.0", - "mustache": "^4.2.0", - "stacktracey": "^2.1.8" - } - }, - "node_modules/zod": { - "version": "3.22.4", - "resolved": "https://registry.npmjs.org/zod/-/zod-3.22.4.tgz", - "integrity": "sha512-iC+8Io04lddc+mVqQ9AZ7OQ2MrUKGN+oIQyq1vemgt46jwCwLfhq7/pwnBnNXXXZb8VTVLKwp9EDkx+ryxIWmg==", - "dev": true, - "funding": { - "url": "https://github.com/sponsors/colinhacks" - } - } - } -} diff --git a/package.json b/package.json index 13dc14e..3a03bac 100644 --- a/package.json +++ b/package.json @@ -2,20 +2,46 @@ "name": "rfiles", "version": "0.0.0", "devDependencies": { - "@cloudflare/workers-types": "^4.20230404.0", - "@types/node": "^20.10.2", - "typescript": "^5.0.4", - "wrangler": "^3.18.0" + "@apidevtools/swagger-parser": "^13.0.0", + "@cloudflare/vitest-plugin": "^1.1.4", + "@cloudflare/workers-types": "^5.20260905.1", + "@types/node": "^24.13.3", + "@vitest/coverage-istanbul": "^4.1.11", + "openapi-types": "^12.1.3", + "prettier": "^3.9.6", + "tsx": "^4.23.13", + "typescript": "^7.0.2", + "vitest": "^4.1.11", + "wrangler": "^4.129.0" }, "private": true, "scripts": { - "start": "wrangler dev", - "deploy": "wrangler publish" + "dev": "wrangler dev", + "start": "pnpm run dev", + "deploy": "wrangler deploy", + "build": "pnpm run openapi:generate && wrangler deploy --dry-run --outdir dist/worker", + "typecheck": "tsc -p tsconfig.json && tsc -p tests/tsconfig.json", + "test": "vitest run", + "test:watch": "vitest", + "test:coverage": "vitest run --coverage", + "openapi:generate": "tsx scripts/generate-openapi.ts", + "format": "prettier --write .", + "format:check": "prettier --check .", + "check": "pnpm run format:check && pnpm run typecheck && pnpm run test:coverage && pnpm run build" }, "dependencies": { - "@aws-sdk/client-s3": "^3.465.0", - "@aws-sdk/s3-request-presigner": "^3.465.0", - "aws4fetch": "^1.0.17", - "itty-router": "^3.0.12" - } + "@hono/standard-validator": "^0.2.3", + "@scalar/hono-api-reference": "^0.12.0", + "@valibot/to-json-schema": "^1.7.1", + "aws4fetch": "^1.0.20", + "hono": "^4.13.7", + "hono-openapi": "^1.3.1", + "valibot": "^1.4.2" + }, + "type": "module", + "description": "Hash based file server on Cloudflare Workers and R2", + "engines": { + "node": ">=22.12.0" + }, + "packageManager": "pnpm@10.28.0" } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml new file mode 100644 index 0000000..ea37072 --- /dev/null +++ b/pnpm-lock.yaml @@ -0,0 +1,2585 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + dependencies: + '@hono/standard-validator': + specifier: ^0.2.3 + version: 0.2.3(@standard-schema/spec@1.1.0)(hono@4.13.7) + '@scalar/hono-api-reference': + specifier: ^0.12.0 + version: 0.12.0(hono@4.13.7) + '@valibot/to-json-schema': + specifier: ^1.7.1 + version: 1.7.1(valibot@1.4.2(typescript@7.0.2)) + aws4fetch: + specifier: ^1.0.20 + version: 1.0.20 + hono: + specifier: ^4.13.7 + version: 4.13.7 + hono-openapi: + specifier: ^1.3.1 + version: 1.3.1(@hono/standard-validator@0.2.3(@standard-schema/spec@1.1.0)(hono@4.13.7))(@standard-community/standard-json@0.3.5(@standard-schema/spec@1.1.0)(@types/json-schema@7.0.15)(@valibot/to-json-schema@1.7.1(valibot@1.4.2(typescript@7.0.2)))(quansync@0.2.11)(valibot@1.4.2(typescript@7.0.2))(zod@4.4.3))(@standard-community/standard-openapi@0.2.9(@standard-community/standard-json@0.3.5(@standard-schema/spec@1.1.0)(@types/json-schema@7.0.15)(@valibot/to-json-schema@1.7.1(valibot@1.4.2(typescript@7.0.2)))(quansync@0.2.11)(valibot@1.4.2(typescript@7.0.2))(zod@4.4.3))(@standard-schema/spec@1.1.0)(openapi-types@12.1.3)(valibot@1.4.2(typescript@7.0.2))(zod@4.4.3))(@types/json-schema@7.0.15)(hono@4.13.7)(openapi-types@12.1.3) + valibot: + specifier: ^1.4.2 + version: 1.4.2(typescript@7.0.2) + devDependencies: + '@apidevtools/swagger-parser': + specifier: ^13.0.0 + version: 13.0.0(openapi-types@12.1.3) + '@cloudflare/vitest-plugin': + specifier: ^1.1.4 + version: 1.1.4(@cloudflare/workers-types@5.20260905.1)(@vitest/runner@4.1.11)(@vitest/snapshot@4.1.11)(vitest@4.1.11) + '@cloudflare/workers-types': + specifier: ^5.20260905.1 + version: 5.20260905.1 + '@types/node': + specifier: ^24.13.3 + version: 24.13.3 + '@vitest/coverage-istanbul': + specifier: ^4.1.11 + version: 4.1.11(vitest@4.1.11) + openapi-types: + specifier: ^12.1.3 + version: 12.1.3 + prettier: + specifier: ^3.9.6 + version: 3.9.6 + tsx: + specifier: ^4.23.13 + version: 4.23.13 + typescript: + specifier: ^7.0.2 + version: 7.0.2 + vitest: + specifier: ^4.1.11 + version: 4.1.11(@types/node@24.13.3)(@vitest/coverage-istanbul@4.1.11)(vite@8.2.2(@types/node@24.13.3)(esbuild@0.28.1)(tsx@4.23.13)) + wrangler: + specifier: ^4.129.0 + version: 4.129.0(@cloudflare/workers-types@5.20260905.1) + +packages: + + '@apidevtools/json-schema-ref-parser@15.3.6': + resolution: {integrity: sha512-oPFDSviRrreUmCZn09LCD4S9QQa6j7zfEwBM1pkGa/kXY0O4sXsEXn4emLP/Tt6tik2+BtgwKk2eldAeOWZ2Rw==} + engines: {node: '>=20'} + peerDependencies: + '@types/json-schema': ^7.0.15 + + '@apidevtools/openapi-schemas@2.1.0': + resolution: {integrity: sha512-Zc1AlqrJlX3SlpupFGpiLi2EbteyP7fXmUOGup6/DnkRgjP9bgMM/ag+n91rsv0U1Gpz0H3VILA/o3bW7Ua6BQ==} + engines: {node: '>=10'} + + '@apidevtools/swagger-methods@3.0.2': + resolution: {integrity: sha512-QAkD5kK2b1WfjDS/UQn/qQkbwF31uqRjPTrsCs5ZG9BQGAkjwvqGFjjPqAuzac/IYzpPtRzjCP1WrTuAIjMrXg==} + + '@apidevtools/swagger-parser@13.0.0': + resolution: {integrity: sha512-TcvnmBzJD7DBREkc/2qaxMqbKpttOaHhOFsYRYKdYAv6HGY+iNlWY2IUA209C2/NmvL9z8RBZhkrESqvGkjqmw==} + engines: {node: ^20.19.0 || >=22.12.0} + peerDependencies: + openapi-types: '>=7' + + '@babel/code-frame@7.29.7': + resolution: {integrity: sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==} + engines: {node: '>=6.9.0'} + + '@babel/compat-data@7.29.7': + resolution: {integrity: sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==} + engines: {node: '>=6.9.0'} + + '@babel/core@7.29.7': + resolution: {integrity: sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==} + engines: {node: '>=6.9.0'} + + '@babel/generator@7.29.8': + resolution: {integrity: sha512-gZbepsdh3WDtgZKWL+vTPh71LSBrm/Y4/QDZBVCcYfmeTEEuoOYwlSy+G1StfJg+/Zy550u/3TATbm7qDbbMtg==} + engines: {node: '>=6.9.0'} + + '@babel/helper-compilation-targets@7.29.7': + resolution: {integrity: sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==} + engines: {node: '>=6.9.0'} + + '@babel/helper-globals@7.29.7': + resolution: {integrity: sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==} + engines: {node: '>=6.9.0'} + + '@babel/helper-module-imports@7.29.7': + resolution: {integrity: sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==} + engines: {node: '>=6.9.0'} + + '@babel/helper-module-transforms@7.29.7': + resolution: {integrity: sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/helper-string-parser@7.29.7': + resolution: {integrity: sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-identifier@7.29.7': + resolution: {integrity: sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-option@7.29.7': + resolution: {integrity: sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==} + engines: {node: '>=6.9.0'} + + '@babel/helpers@7.29.7': + resolution: {integrity: sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==} + engines: {node: '>=6.9.0'} + + '@babel/parser@7.29.8': + resolution: {integrity: sha512-E8lTAYNB1KW+FH+VGJuZM1ioAx2E6oVlvQFRrf5P8ZZmsiJXYAD9vTFV7yyEURNzgh1dFqMZuO6tUwcARbqFCA==} + engines: {node: '>=6.0.0'} + hasBin: true + + '@babel/template@7.29.7': + resolution: {integrity: sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==} + engines: {node: '>=6.9.0'} + + '@babel/traverse@7.29.8': + resolution: {integrity: sha512-I5z7H3bf/41ktsNVLtpN0wAa336HkqIHQ5BuPLEhTkt1jVSyZpeNKIzTgEWmlxjdg81R0IgUCcaE+Ok3NvrfZg==} + engines: {node: '>=6.9.0'} + + '@babel/types@7.29.8': + resolution: {integrity: sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg==} + engines: {node: '>=6.9.0'} + + '@cloudflare/kv-asset-handler@0.5.0': + resolution: {integrity: sha512-jxQYkj8dSIzc0cD6cMMNdOc1UVjqSqu8BZdor5s8cGjW2I8BjODt/kWPVdY+u9zj3ms75Q5qaZgnxUad83+eAg==} + engines: {node: '>=22.0.0'} + + '@cloudflare/unenv-preset@2.16.1': + resolution: {integrity: sha512-ECxObrMfyTl5bhQf/lZCXwo5G6xX9IAUo+nDMKK4SZ8m4Jvvxp52vilxyySSWh2YTZz8+HQ07qGH/2rEom1vDw==} + peerDependencies: + unenv: 2.0.0-rc.24 + workerd: '>1.20260305.0 <2.0.0-0' + peerDependenciesMeta: + workerd: + optional: true + + '@cloudflare/vitest-plugin@1.1.4': + resolution: {integrity: sha512-fzZFMo8NUv1S2lMPDgZYq1jg+0FwnrkF28iYzjFuN/Z1c0Fsnp6xrjyxapLaqrTqjDO66fvKTgbHL0yV1tZnPQ==} + peerDependencies: + '@vitest/runner': ^4.1.0 + '@vitest/snapshot': ^4.1.0 + vitest: ^4.1.0 + + '@cloudflare/workerd-darwin-64@1.20260903.1': + resolution: {integrity: sha512-FG+4mGxAXhKiL/1temH42alevIkumtYXNidhTa//3yULpzux6APw5UNVocI/vCQ1yG1YOEZRfbVy8lyuipM9MQ==} + engines: {node: '>=16'} + cpu: [x64] + os: [darwin] + + '@cloudflare/workerd-darwin-arm64@1.20260903.1': + resolution: {integrity: sha512-o241VefnjG8eG+kGap5CjgV4zOT5UaAmS3OR1VZCpNj7vkXGxvp9KftKvtQgcCsIqJKaKx+7Xd7xq0L1DjkfPQ==} + engines: {node: '>=16'} + cpu: [arm64] + os: [darwin] + + '@cloudflare/workerd-linux-64@1.20260903.1': + resolution: {integrity: sha512-/VEvvtQ/XKf6HlBbg6FbvpwcpfYUcx6Fv6RkASY8DyEmUyuJ8rc7Qxil83ClRFoBzz/GY0BV94UZ6F+wers/hg==} + engines: {node: '>=16'} + cpu: [x64] + os: [linux] + + '@cloudflare/workerd-linux-arm64@1.20260903.1': + resolution: {integrity: sha512-OWhihGC6KoTXF4u2C1AonfpgXeM4/7p/1IXuALqXESmFUpLLP5gZhRzjSk/gWW+mrCZDfSrvnjifl+lRqselbA==} + engines: {node: '>=16'} + cpu: [arm64] + os: [linux] + + '@cloudflare/workerd-windows-64@1.20260903.1': + resolution: {integrity: sha512-soPMF9/aMHlHKK7M0vq5HrRRicPbnZO1F6ZZ7JWN8EltxWJU5L7CEq7CxjO878DzyPx7gYvqBFy3SVgdZKZjMQ==} + engines: {node: '>=16'} + cpu: [x64] + os: [win32] + + '@cloudflare/workers-types@5.20260905.1': + resolution: {integrity: sha512-ebDpVTgrnee245IRBTazyfjfmZas+HJehZyVQkKmUozsBN8RzvXXMI0XjjcT2rQjw+0USewfEGGLYiHO+TL0Sg==} + + '@cspotcode/source-map-support@0.8.1': + resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==} + engines: {node: '>=12'} + + '@emnapi/runtime@1.11.3': + resolution: {integrity: sha512-Xz4Tpyki7XyrpbUK1jR1AhdAdaXyhhY4lZ3neLodmhpuWfy2PAQN5B46sAiU4liOXGLkHypn/qU+jvfWSCYYLA==} + + '@esbuild/aix-ppc64@0.28.1': + resolution: {integrity: sha512-Svl7tq8k/08+p6CXPpRjQ1fKX+1odH/BQbb48fV6fj3CWHhsoIOoY87w1oHXm0qEpkIK3ZfVgp0hed3XBXzXMQ==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + + '@esbuild/android-arm64@0.28.1': + resolution: {integrity: sha512-34EGEbCIAgosYz6goLcopX6Mo7NyGv9tfwEM2/7Ce2VcVRk568iSvniGWcUXIy7wEDR1wzolcxcriFVrWYcwBg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm@0.28.1': + resolution: {integrity: sha512-0k2F129Xdio1TdJfzJ8sy1Q47vUD2NnwdhiAf7drUN1EBTfPf4hsFCtmMgu/6m8JSzsBrlmVjudMBQqOfG8usQ==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + + '@esbuild/android-x64@0.28.1': + resolution: {integrity: sha512-dbwY7ltSMDWsRatcRpCnES4F+im88OCUgGZjy52shC7GqHRE/cYlxNbB4Z4UpJswpcc4Qxd2oE/ufM0p61IKng==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + + '@esbuild/darwin-arm64@0.28.1': + resolution: {integrity: sha512-TZbWkQY7kvTAXbXUT7uVACR5cMHsDiSz9z7ZKAX/RTq/WJEk3QyRr0wZpNhBDX+/0CtdqUIJlOiodQcta6tY3Q==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-x64@0.28.1': + resolution: {integrity: sha512-zfdzgK9ACBNZLI/CyHTOx81SyNbM6YXn7rxSgX97VjyiPl9W1i4Ka4fgKECEoFCKGpvBj5qArWIGgQjOwkgskQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + + '@esbuild/freebsd-arm64@0.28.1': + resolution: {integrity: sha512-wG2EA8ENdEI0qhkSZMjfqrdY+ziCYCPMmtZjjIwOmXFjmyzEHn+UUxk5of+SYsjtfs3VpnlC7QLzSI5hY/rOAw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.28.1': + resolution: {integrity: sha512-i7dZ9vQgnvSCzi/rYCXNgtF/U+eKZNJBzu3eTQbRgHnM7tNSizLOkRFAl3qzVc/Op/u5YkHHa4pf/3DOYHthLQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + + '@esbuild/linux-arm64@0.28.1': + resolution: {integrity: sha512-yHs+0uc8+nvEAfAfxrWQKK5peSNzBc4PegcMO0EJ2hT71uA7vB8Ihg2e77R2P7SG5uYjPbHlLLmve4LLLRCf0g==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm@0.28.1': + resolution: {integrity: sha512-qVXBOHQS+d5Y722GwJzJUtOLlX7km3CraOaGormF1pDtPd2C/l1SHRPgjLunLGe51Sh5YYWKMFDyV4SxgMQYTQ==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-ia32@0.28.1': + resolution: {integrity: sha512-d1z4ZuP0ajrfz/FhGT4vv278rX8KnPPJx8i5+AtK7TYbx9Le9F1hyzurZpkEyjkGa9dUGhQow4C1NmeGvqxN2w==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-loong64@0.28.1': + resolution: {integrity: sha512-M5sRjUVZrkm1OAPR3dlOYzNmN+loZKGVi1VUQGrwuqLcbR6qeAz+famMhjASeH3YVKvZz+zT1jlh/keC3Rj/lg==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-mips64el@0.28.1': + resolution: {integrity: sha512-mRObBZeHh2OxcBFPWE/FjylkRgZdYuiTR3vaTozquCGOH14iP9oN4x4Ge81CoIDYQrXmIxpFumJBu5MtZpnQJQ==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-ppc64@0.28.1': + resolution: {integrity: sha512-slScBsMAb3GFDcdrCgLwZtPYRoH2H/youv10QiZyRjmsP48fznoveWytSgCI/R0ZcUgpc0ZhIUEx6LHts8yrfQ==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-riscv64@0.28.1': + resolution: {integrity: sha512-kw0owk1o0GFETUJyW0jc0G4Yzs0BHZn0JDZ8JRT088vjJYX777BAs1fDGxAC+q831qOs2DTC96mNsG2opdfyyQ==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-s390x@0.28.1': + resolution: {integrity: sha512-/lAIjX8aYFRByhh6L5rYtPEDRqa9de/4V/juOXcta5frjvzXO4/sqEtyytse0g3zZFuWu5cDN0MkLz2qRDD2Ag==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-x64@0.28.1': + resolution: {integrity: sha512-u/anNYF2mmVOEDwLtnQ1wOr3EZ9sTNGLWrsYGYwHWzGA3Si84IOkHXlbWTD1NB+9/1lcnweYKO54uhxZydNzfA==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + + '@esbuild/netbsd-arm64@0.28.1': + resolution: {integrity: sha512-oks0DYbLwWMmaakTsCb+zL4E+aHRVLom9IJZOAthMQEPiQmydXHkziYEsGYRx0uNV/IjEKGAV941JzH02pflqw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [netbsd] + + '@esbuild/netbsd-x64@0.28.1': + resolution: {integrity: sha512-aeL6lAnN89Hz43Mlh1G8ARasbuoYvSITDEx0tHh5b7jJnHcssqgjy9Yx430GDpmCa6OyrKoS0aNRjKundRizGg==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + + '@esbuild/openbsd-arm64@0.28.1': + resolution: {integrity: sha512-MEFJe5C3R8pwXdZ5Y21oo6m7ePiS0d9pWucn99O/wvyJZChoIQKrQDxKrGeW8F5+T0okTHesAmDeiHDTIq0V/Q==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + + '@esbuild/openbsd-x64@0.28.1': + resolution: {integrity: sha512-i/ZLIOafE0Z8cI/XANJAixoJL/uRAoS2xOA3rb0xN+KK0K177cMAsQYkzHtBrtMXAKuAc7HGgcWiZ/sRC1Nxgw==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + + '@esbuild/openharmony-arm64@0.28.1': + resolution: {integrity: sha512-ge+Z7EXFNt2BO1oAMsVpiQ8EwndV9i1xXerAeTIK7AtPs3bKFXQM7nlRxDSIUIMeueR1CNXxqztLzdNeReKBJg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openharmony] + + '@esbuild/sunos-x64@0.28.1': + resolution: {integrity: sha512-BEjgtECkL3vY+SaSQ6nzVfiALUeFxpawyp8Jmf5PtYhf1Ug40N1h/hxlhts+f1FvSvarEigdxS3BlSMI2PJLcQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + + '@esbuild/win32-arm64@0.28.1': + resolution: {integrity: sha512-lCv9eK/H6ZJWbE7bh2nw54CZ9M2nupBxJcTsdk/QQnWkdSjKGuxmmH8/GWrlT1eMmZfn4dGcCjRte397WqfQXA==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-ia32@0.28.1': + resolution: {integrity: sha512-zvb/mB2bSCoJOpoCBgYKKpX6YM6mJBlBUVUtVj41DlZJVEB6/0CKlRYxP5wWl1C1ILiCoAU5wZZ4q1P3qeS6Eg==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-x64@0.28.1': + resolution: {integrity: sha512-bm4Mowrv+GXMlpWX++EcXw/iLyd1o3+bJkC2DkWXYVvgZCqD/bSj9ctZeAMC3cIxgjRVR2Dufaiu4YPxr5gW1A==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + + '@hono/standard-validator@0.2.3': + resolution: {integrity: sha512-bp9vHu6Va6SfMHC3D4ZLBbT/woi+AZ9CRdTXQu3kLJuLh2W/Gb9UO4hijS+BQAGFXi4EGpXdetxpzwTAawSVeg==} + peerDependencies: + '@standard-schema/spec': ^1.0.0 + hono: '>=3.9.0' + + '@img/colour@1.1.0': + resolution: {integrity: sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ==} + engines: {node: '>=18'} + + '@img/sharp-darwin-arm64@0.35.2': + resolution: {integrity: sha512-eEieHsMksAW4IiO5NzauESRl2D2qz3J/kwUxUrSfV06A93eEaRfMpHXyUb1mAqrR7i8U9A0GRqE9pjn6u1Jjpg==} + engines: {node: '>=20.9.0'} + cpu: [arm64] + os: [darwin] + + '@img/sharp-darwin-x64@0.35.2': + resolution: {integrity: sha512-BaktuGPCeHJMARpodR8jK4uKiZrPAy9WrfQW0sdI37clracq8Bp01AYS3SZgi5FS/y5twa9t4+LIuuxQjqRrWw==} + engines: {node: '>=20.9.0'} + cpu: [x64] + os: [darwin] + + '@img/sharp-freebsd-wasm32@0.35.2': + resolution: {integrity: sha512-YoAxdnd8hPUkvLHd3bWY+YA8nw3xM/RyRopYucNsWHVSan8NLVM3X2volsfoRDcXdUJPg6tXahSd7HXPK7lRnw==} + engines: {node: '>=20.9.0'} + os: [freebsd] + + '@img/sharp-libvips-darwin-arm64@1.3.1': + resolution: {integrity: sha512-4V/M3roRMTYjiwZY9IOVQOE8OyeCxFAkYmyZDrZl51uOKjibm3oeEJ4WAmLxutAfzFbC9jqUiPs2gbnGflH+7g==} + cpu: [arm64] + os: [darwin] + + '@img/sharp-libvips-darwin-x64@1.3.1': + resolution: {integrity: sha512-c0/DxItpJv2+dGhgycJBBgotdqruGYDvA79drdh0MD1dFpy7JzJ/PlXwi1H4rFf0eTy8tgbI91aHDnZIceY3jQ==} + cpu: [x64] + os: [darwin] + + '@img/sharp-libvips-linux-arm64@1.3.1': + resolution: {integrity: sha512-JznefmcK9j1JKPz8AkQDh89kjojubyfOasWBPKfzMIhPwsgDy9evpE/naJTXXXmghS1iFwR8u/kTwh/I2/+GCw==} + cpu: [arm64] + os: [linux] + + '@img/sharp-libvips-linux-arm@1.3.1': + resolution: {integrity: sha512-aGGy9aWzXgHBG7HNyQPWorZthlp7+x6fDRoPAQbGO3ThcttuTyKIx3NuSHb6zb4gBNq6/yNn9f1cy9nFKS/Vmg==} + cpu: [arm] + os: [linux] + + '@img/sharp-libvips-linux-ppc64@1.3.1': + resolution: {integrity: sha512-1EkwGNCZk6iWNCMWqrvdJ+r1j0PT1zIz60CNPhYnJlK/zyeWqlsPZIe+ocBVqPF8k/Ssee/NCk+tE9Ryrko6ng==} + cpu: [ppc64] + os: [linux] + + '@img/sharp-libvips-linux-riscv64@1.3.1': + resolution: {integrity: sha512-Ilays+w2bXdnxzxtQdmXR62u8o8GYa3eL4+Gr+1KiE4xperMZUslRaVPJwwPkzlHEjGfXAfRVAa/7CYCtSqsBw==} + cpu: [riscv64] + os: [linux] + + '@img/sharp-libvips-linux-s390x@1.3.1': + resolution: {integrity: sha512-VfBwVHQTbRoj4XlpA/KLZ7ltgMpz+4WSejFzQ+GnoImjo1PtEJ59QB2qR1xQEeRPYIkNrPIm2L4cICMvz4C2ew==} + cpu: [s390x] + os: [linux] + + '@img/sharp-libvips-linux-x64@1.3.1': + resolution: {integrity: sha512-+c8ukgwU62DS54nCAjw7keOfHUkmr0B5QHEdcOqRnodF/MNXJbVI8Eopoj4B/0H8Asr65I+A4Amrn7a85/md6A==} + cpu: [x64] + os: [linux] + + '@img/sharp-libvips-linuxmusl-arm64@1.3.1': + resolution: {integrity: sha512-qlKb/pwbkAi1WMsJrYHk7CuDrd12s27U2QnRhFYUoJNrRCmkosMTttuRFat/DDB3IlDm5qE1TJgZ4JDnHX8Ldw==} + cpu: [arm64] + os: [linux] + + '@img/sharp-libvips-linuxmusl-x64@1.3.1': + resolution: {integrity: sha512-yO21HwoUVLN8Qa+/SBjQLMYwBWAVJjeGPNe+hc0OUeMeifEtJqu5a1c4HayE1nNpDih9y3/KkoltfkDodmKAlg==} + cpu: [x64] + os: [linux] + + '@img/sharp-linux-arm64@0.35.2': + resolution: {integrity: sha512-af12Pnd0ZGu2HfP8NayB0kk6eC/lrfbQE6HlR4jD+34wdJ1Vw9TF6TMn6ZvffT+WgqVsl0hRbmNvz2u/23VmwA==} + engines: {node: '>=20.9.0'} + cpu: [arm64] + os: [linux] + + '@img/sharp-linux-arm@0.35.2': + resolution: {integrity: sha512-SE4kzF2mepn6z+6E7L6lsV8FzuLL6IPQdyX8ZiwROAG/G8td+hP/m7FsFPwidtrF19gvajuC9l6TxAVcsA4S7A==} + engines: {node: '>=20.9.0'} + cpu: [arm] + os: [linux] + + '@img/sharp-linux-ppc64@0.35.2': + resolution: {integrity: sha512-hYSBm7zcNtDCozCxQHYZJiu63b/bXsgRZuOxCIBZsStMM9Vap47iFHdbX4kCvQsblPB/k+clhELpdQJHQLSHvg==} + engines: {node: '>=20.9.0'} + cpu: [ppc64] + os: [linux] + + '@img/sharp-linux-riscv64@0.35.2': + resolution: {integrity: sha512-qQt0Kc13+Hoan/Awq/qMSQw3L+RI1NCRPgD5cUJ/1WSSmIoysLOc72jlRM3E0OHN9Yr313jgeQ2T+zW+F03QFA==} + engines: {node: '>=20.9.0'} + cpu: [riscv64] + os: [linux] + + '@img/sharp-linux-s390x@0.35.2': + resolution: {integrity: sha512-E4fLLfRPzDLlEeDaTzI98OFLcv++WL5ChLLMwPoVd0CIoZQqupBSNbOisPL5am9XsbQ9T84+iiMpUvbFtkunbA==} + engines: {node: '>=20.9.0'} + cpu: [s390x] + os: [linux] + + '@img/sharp-linux-x64@0.35.2': + resolution: {integrity: sha512-gi0zFJJRLswfCZmHtJdikXPOc5u7qamSOS3NHedLqLd4W8Q0NqjdBr6TTRIgsfFjqfTsHFgdfvJ9LwqSgcHiAA==} + engines: {node: '>=20.9.0'} + cpu: [x64] + os: [linux] + + '@img/sharp-linuxmusl-arm64@0.35.2': + resolution: {integrity: sha512-siWbOW1u6HFnFLrp0waKyW7VEf7jYvcDWdrXEFa8AkdAQgEvuu5Fz8/Y70w9EeqAdwDtfU012BhEHHaDqvQNzg==} + engines: {node: '>=20.9.0'} + cpu: [arm64] + os: [linux] + + '@img/sharp-linuxmusl-x64@0.35.2': + resolution: {integrity: sha512-YBqMMcjDi4QGYiSn4vNOYBhmlC4z5AXqkOUUqI2e0AFA4urNv4ESgOgwNl3K+4etQhha0twXlzeF20bbULm9Yg==} + engines: {node: '>=20.9.0'} + cpu: [x64] + os: [linux] + + '@img/sharp-wasm32@0.35.2': + resolution: {integrity: sha512-Mrv4JQNYVQ94xH+jzZ9r+gowleN8mv2FTgKT+PI6bx5C0G8TdNYndu161pg2i7uoBwxy2ImPMHrJOM2LZef7Bw==} + engines: {node: '>=20.9.0'} + + '@img/sharp-webcontainers-wasm32@0.35.2': + resolution: {integrity: sha512-QNV27pxs9wpApEiCfvHM1RDoP1w1+2KrUWWDPEhEwg+latvOrfuhWrHWZKwdSFwU6jh3myjw/yOCRsUIuOft3g==} + engines: {node: '>=20.9.0'} + cpu: [wasm32] + + '@img/sharp-win32-arm64@0.35.2': + resolution: {integrity: sha512-BiVRYc/t6/Vl3e1hBx0hugG4oN9Pydf4fgMSpxTQJmwGUg/YoXTWHiFeRymHfCZzifxu4F4rpk/I67D0LQ20wQ==} + engines: {node: '>=20.9.0'} + cpu: [arm64] + os: [win32] + + '@img/sharp-win32-ia32@0.35.2': + resolution: {integrity: sha512-YYEhx9PImCC7T0tI8JDMi4DB9LwLCXCU5OWNYEXAxh5Q1ShKkyC6byxzoBJ3gEFDnH2lQckWuDe70G7mB2XJog==} + engines: {node: ^20.9.0} + cpu: [ia32] + os: [win32] + + '@img/sharp-win32-x64@0.35.2': + resolution: {integrity: sha512-imoOyBcoM/iiUr4J6VPpCNjPnjvP/Gks95898yB8YqoGGYmHYbOyCuNv9FMhFgtaiHFGbHW8bxKqRV6VjtXThQ==} + engines: {node: '>=20.9.0'} + cpu: [x64] + os: [win32] + + '@istanbuljs/schema@0.1.6': + resolution: {integrity: sha512-+Sg6GCR/wy1oSmQDFq4LQDAhm3ETKnorxN+y5nbLULOR3P0c14f2Wurzj3/xqPXtasLFfHd5iRFQ7AJt4KH2cw==} + engines: {node: '>=8'} + + '@jridgewell/gen-mapping@0.3.13': + resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} + + '@jridgewell/remapping@2.3.5': + resolution: {integrity: sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==} + + '@jridgewell/resolve-uri@3.1.2': + resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} + engines: {node: '>=6.0.0'} + + '@jridgewell/sourcemap-codec@1.6.0': + resolution: {integrity: sha512-T7jf+5zgsZHwNJ4lvQ7/aezbyk0nNX+zJVWpmHA7VYsEx7a7qr5Rg5IbtJFqkgze5Y2sruq1RUY8Q837Od7iFw==} + + '@jridgewell/trace-mapping@0.3.31': + resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} + + '@jridgewell/trace-mapping@0.3.9': + resolution: {integrity: sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==} + + '@oxc-project/types@0.148.0': + resolution: {integrity: sha512-Nm4s/jB+4FpFsPhWGEC4h7rzksesmtnMXomo6rCMcg/b8zLQuOziRgkCS1fxDCXOlJB/6Q8oABOZ/OP6RIPj9A==} + + '@poppinss/colors@4.1.6': + resolution: {integrity: sha512-H9xkIdFswbS8n1d6vmRd8+c10t2Qe+rZITbbDHHkQixH5+2x1FDGmi/0K+WgWiqQFKPSlIYB7jlH6Kpfn6Fleg==} + + '@poppinss/dumper@0.6.5': + resolution: {integrity: sha512-NBdYIb90J7LfOI32dOewKI1r7wnkiH6m920puQ3qHUeZkxNkQiFnXVWoE6YtFSv6QOiPPf7ys6i+HWWecDz7sw==} + + '@poppinss/exception@1.2.3': + resolution: {integrity: sha512-dCED+QRChTVatE9ibtoaxc+WkdzOSjYTKi/+uacHWIsfodVfpsueo3+DKpgU5Px8qXjgmXkSvhXvSCz3fnP9lw==} + + '@rolldown/binding-android-arm-eabi@1.2.7': + resolution: {integrity: sha512-EypzgnYCwyVY4NDHKzGmNJT5b+XaQEBniHxsMdeIQLB/tcCzZnhqrzHpZFbX9iaxx+5RiB8caATBtfvZP7zVxQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [android] + + '@rolldown/binding-android-arm64@1.2.7': + resolution: {integrity: sha512-l17HE9EweWaqJZhuUuNBN/FzM62xw+DECVnJyvMsxn8vJFAGLy5QfLDoYAcronkAN8VxKZHezDpulHDPx95vFw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [android] + + '@rolldown/binding-darwin-arm64@1.2.7': + resolution: {integrity: sha512-8ED8ELFvHXc6OCETIn4gXObPiaR6bckM/ipXtbzlPVDRMBfEGjCKgO90F9YtfdpDatVx/ZQw7aZ1vUMf/+T3Mw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [darwin] + + '@rolldown/binding-darwin-x64@1.2.7': + resolution: {integrity: sha512-/WPripjtiAIZ2tWY7ddijORT0Ujg87wxWW/qcoFVCKAWVDPhtY0xr7Dj0M3GyNGz60jGwTElhro/mkF9dT7dDQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [darwin] + + '@rolldown/binding-freebsd-x64@1.2.7': + resolution: {integrity: sha512-14DI4NcqpvbICxSnGLx3PmtDaWqRP/KGSGb6C+JLLVPeZRl6dKdHba3pGsqT3vpdTqhEYIPG0MMQ8c0xYqoJxA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [freebsd] + + '@rolldown/binding-linux-arm-gnueabihf@1.2.7': + resolution: {integrity: sha512-bxrWIRvHWQvbJwi+VIie/kDJmQxcNE6xxWwZdqF/ExVAigtHkv54WTLQPb+QsZdnFy18fg7JPfWGL0RH6vwIlQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [linux] + + '@rolldown/binding-linux-arm64-gnu@1.2.7': + resolution: {integrity: sha512-toOY2BChBZyuxU7OYX6Tn389di4IzAqPTycVcci0O7FSfBqzRB3RZn+K5Is6ANf4tmgRd/K1yZTsNTXbkXsnLg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + + '@rolldown/binding-linux-arm64-musl@1.2.7': + resolution: {integrity: sha512-lAIXTH/aiLRLxsTgQvfhjo4K1ydWIp00+V0voOr9beb/9ZmkUFrSIb03dXNFRgMNvkE6oGsF10ioQ6UsI+vS5Q==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + + '@rolldown/binding-linux-ppc64-gnu@1.2.7': + resolution: {integrity: sha512-kdnwS28Pkenp/mZMRwjXXXwxQ7pIsm+bF919LUK93BOyhcLsrVKdP2p9fxpiPNPAbNuch8ypQt0pm2P2LYCAGg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [ppc64] + os: [linux] + + '@rolldown/binding-linux-s390x-gnu@1.2.7': + resolution: {integrity: sha512-516OdsyLdr5E65paF3yBF55t8mfm9+gmtCsK3xI7XKXIT7EfRlHhxL8K/NR6Hu8BWSgF5+1w74lTL0+nxcc8Qw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [s390x] + os: [linux] + + '@rolldown/binding-linux-x64-gnu@1.2.7': + resolution: {integrity: sha512-r8/z8n7GFaYRln3xmP1Cxy0HH/HLM0uBUPkEuSVEfKGDA89M0FsZRZJRSwe/tJjRx+fpH/gjorfhB8tmEbSFLA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + + '@rolldown/binding-linux-x64-musl@1.2.7': + resolution: {integrity: sha512-pAsE8iiDxUg1xBqdhrTfg45AVDVpirjz00sblEYClGNNcMnDb+e8beQgqIAw6LvauX/APvgxUnwrgun/YYGBhw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + + '@rolldown/binding-openharmony-arm64@1.2.7': + resolution: {integrity: sha512-lTcIYmmnQQA8Or/2DatS6oSqcdLHvendjS+zLu+FwgToynWMRSmQdpM65fTANJgIS4mjbMOo5KT2lnT9SAb96w==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [openharmony] + + '@rolldown/binding-win32-arm64-msvc@1.2.7': + resolution: {integrity: sha512-e3Gu3WxbNk/UqQhxqU7YIYO+9ZBvWNz3U+h/qRFosscMFzdRPbXYSaSWgSnklv2fz1TgzBTcti2z35c/7irsHw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [win32] + + '@rolldown/binding-win32-x64-msvc@1.2.7': + resolution: {integrity: sha512-W/jg5qoRSqjsEv0+dZi4e687mcHqmVuU0P4fK6qS/xjetW2Gmc1W8j//z5nAeNcC8Ttm0hV46IjcYeuVwYhuiw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [win32] + + '@rolldown/pluginutils@1.0.1': + resolution: {integrity: sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==} + + '@scalar/client-side-rendering@0.3.10': + resolution: {integrity: sha512-FlUByzYhsas54zIvsiTJ7UgAZiVUQmErWvrQm+CKTBnIvSmIb5eWowJ4/O0bbkUa8rsOIdmCcX+HtX+EDdf0CA==} + engines: {node: '>=22'} + + '@scalar/helpers@0.11.2': + resolution: {integrity: sha512-IgHW/hIj1XAvsPIBKiOgmK87qbyDjaQQg9S/auXU4MUtvnwKKpOeIOJc0NC71FkMdr4Mok0SSVI748cmlptoXQ==} + engines: {node: '>=22'} + + '@scalar/hono-api-reference@0.12.0': + resolution: {integrity: sha512-K8WA0NTDnwCmTvh33AmkHuoolDuxEW7Jst/Am7+tcijPgFcsGE0l3O7Vk9RkNMMQT643Vy9nJDyhfnkMIWA5vQ==} + engines: {node: '>=22'} + peerDependencies: + hono: ^4.12.5 + + '@scalar/schemas@0.8.4': + resolution: {integrity: sha512-gbx/UjAswjJc+OZp/vcVRIopjqoJ62pOkrKLvVHa1MuMRoVxcPyNzW2/SQmnvdJku7C7a6PsKaPUQ6gZXGA4eg==} + engines: {node: '>=22'} + + '@scalar/types@0.18.3': + resolution: {integrity: sha512-hPLLxVt/ah4RpCVp5UrLEpnBEgTwf+RvPtRiSEty3QqfBJUfADMXHz+oWK6UAa/vALg2AWuJLCD8hRlyQ2ET9w==} + engines: {node: '>=22'} + + '@scalar/validation@0.6.3': + resolution: {integrity: sha512-j3s9XPv8Wo1EG5/naBi4XGF0uXYQ2A3QZNI0NKWgCMxRHVrDJGlZEYymcHDHY7fquRm73P8U3c8xqJqB5yad6w==} + engines: {node: '>=20'} + + '@sindresorhus/is@7.2.0': + resolution: {integrity: sha512-P1Cz1dWaFfR4IR+U13mqqiGsLFf1KbayybWwdd2vfctdV6hDpUkgCY0nKOLLTMSoRd/jJNjtbqzf13K8DCCXQw==} + engines: {node: '>=18'} + + '@speed-highlight/core@1.2.24': + resolution: {integrity: sha512-qeW2e1l78afw8VhRPfPQ1Gjj+KU5XFQ/OFV5ti6eTa9bruO7mJyZtA4vw0ofqmA3tKCkROE9xLk3VZoeRc98nw==} + + '@standard-community/standard-json@0.3.5': + resolution: {integrity: sha512-4+ZPorwDRt47i+O7RjyuaxHRK/37QY/LmgxlGrRrSTLYoFatEOzvqIc85GTlM18SFZ5E91C+v0o/M37wZPpUHA==} + peerDependencies: + '@standard-schema/spec': ^1.0.0 + '@types/json-schema': ^7.0.15 + '@valibot/to-json-schema': ^1.3.0 + arktype: ^2.1.20 + effect: ^3.16.8 + quansync: ^0.2.11 + sury: ^10.0.0 + typebox: ^1.0.17 + valibot: ^1.1.0 + zod: ^3.25.0 || ^4.0.0 + zod-to-json-schema: ^3.24.5 + peerDependenciesMeta: + '@valibot/to-json-schema': + optional: true + arktype: + optional: true + effect: + optional: true + sury: + optional: true + typebox: + optional: true + valibot: + optional: true + zod: + optional: true + zod-to-json-schema: + optional: true + + '@standard-community/standard-openapi@0.2.9': + resolution: {integrity: sha512-htj+yldvN1XncyZi4rehbf9kLbu8os2Ke/rfqoZHCMHuw34kiF3LP/yQPdA0tQ940y8nDq3Iou8R3wG+AGGyvg==} + peerDependencies: + '@standard-community/standard-json': ^0.3.5 + '@standard-schema/spec': ^1.0.0 + arktype: ^2.1.20 + effect: ^3.17.14 + openapi-types: ^12.1.3 + sury: ^10.0.0 + typebox: ^1.0.0 + valibot: ^1.1.0 + zod: ^3.25.0 || ^4.0.0 + zod-openapi: ^4 + peerDependenciesMeta: + arktype: + optional: true + effect: + optional: true + sury: + optional: true + typebox: + optional: true + valibot: + optional: true + zod: + optional: true + zod-openapi: + optional: true + + '@standard-schema/spec@1.1.0': + resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==} + + '@types/chai@5.2.3': + resolution: {integrity: sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==} + + '@types/deep-eql@4.0.2': + resolution: {integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==} + + '@types/estree@1.0.9': + resolution: {integrity: sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==} + + '@types/json-schema@7.0.15': + resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} + + '@types/node@24.13.3': + resolution: {integrity: sha512-Dh8vAsV36ig5wa9OX4pXvMc9D3Veibfw2wix0CUwYODLD8nkj9UsLjASr49nPg+2eKzxhBV+v7L8pXvT4e639Q==} + + '@typescript/typescript-aix-ppc64@7.0.2': + resolution: {integrity: sha512-MTKKkWB7p/0E9xi1d1tHtZ5PiLkGEMIq88pK2CubZjOsLtYTLqhgIgi6zepFa+9GHZ6h05NMCkQxGKiPXMxXtQ==} + engines: {node: '>=16.20.0'} + cpu: [ppc64] + os: [aix] + + '@typescript/typescript-darwin-arm64@7.0.2': + resolution: {integrity: sha512-gowzar9MwS/aRWp6f3a4KUqzRjAZjOsmGNCM6LcTgXum+dBfgsBVMN+AgvOCCbguXyick6LJhpBszxMebJ8syA==} + engines: {node: '>=16.20.0'} + cpu: [arm64] + os: [darwin] + + '@typescript/typescript-darwin-x64@7.0.2': + resolution: {integrity: sha512-SZ9xZInqApNlNGc9s0W1VSsktYSOe9cFqNOIqmN1Gs8SmkjKZYFt017G4VwPxASInODuAdbTW7sXiFUf893RgA==} + engines: {node: '>=16.20.0'} + cpu: [x64] + os: [darwin] + + '@typescript/typescript-freebsd-arm64@7.0.2': + resolution: {integrity: sha512-W5NH4y/J0plIIS5b2xvTEkU7JFxyqdMAOgf+Ilhl0vHQXKO5dZoxd+C/jEtq56c4F3wk71RB4BMRQ2XdI+bwYQ==} + engines: {node: '>=16.20.0'} + cpu: [arm64] + os: [freebsd] + + '@typescript/typescript-freebsd-x64@7.0.2': + resolution: {integrity: sha512-UMGDx5sTpzNw3WiPebH7l90IWfJggEd+egHt/q6p7/Cm3zqoV7VxkGXt+3DxPIw8CcmvAB0j3sVVfbhX+M4Tpw==} + engines: {node: '>=16.20.0'} + cpu: [x64] + os: [freebsd] + + '@typescript/typescript-linux-arm64@7.0.2': + resolution: {integrity: sha512-Qh4eU4/y3yDjnfjjyPYihMj5/ODIlmt+Bzu17OI+fiSRDW57QmU5SiN63exPRNJPKUzcc1INa1NXdrJ+MqHjUQ==} + engines: {node: '>=16.20.0'} + cpu: [arm64] + os: [linux] + + '@typescript/typescript-linux-arm@7.0.2': + resolution: {integrity: sha512-gffT3xPz9sR7j/YJExkyPntrI0P2EP9XbOyWzth2/Gs0RstK+90RBcO0ncXoXy/beYll1SXw846Nf2zdnEz0QQ==} + engines: {node: '>=16.20.0'} + cpu: [arm] + os: [linux] + + '@typescript/typescript-linux-loong64@7.0.2': + resolution: {integrity: sha512-uEHck9i8hoAzXPiYRib1O7miOnz23SxIeVl6F4LXox+qov1K35jHcEW6VHKvZI+pyvl7fZEP4MCU5LYvIq1GuQ==} + engines: {node: '>=16.20.0'} + cpu: [loong64] + os: [linux] + + '@typescript/typescript-linux-mips64el@7.0.2': + resolution: {integrity: sha512-R4KvAMnE43W5Qeqb0Ly56O3mWMWIAgsMyz36DCaycd5nbg/9kzm0liw3JocfRqyJY0KPmzFjbswozXyW0DnIYA==} + engines: {node: '>=16.20.0'} + cpu: [mips64el] + os: [linux] + + '@typescript/typescript-linux-ppc64@7.0.2': + resolution: {integrity: sha512-DORx5b3sd/4S7eayxm4FQv+A7CrkUIGRaHiwI8oiHTAI1fAPWhF4J0vAlkC8biAlHSVVwxMQ3tjZ2/DVbnQiiA==} + engines: {node: '>=16.20.0'} + cpu: [ppc64] + os: [linux] + + '@typescript/typescript-linux-riscv64@7.0.2': + resolution: {integrity: sha512-wf0jqEDOjrPRnKwYRyyJDRo11KMbvMFrU+q4zqKyChODBzvlkbhNQfKvLxQCcwTpdDaXSHZTVuh0JoCrKCUMHQ==} + engines: {node: '>=16.20.0'} + cpu: [riscv64] + os: [linux] + + '@typescript/typescript-linux-s390x@7.0.2': + resolution: {integrity: sha512-IkwJc3L7yhytWd/ewjyxNDfOmswCm9GWMJT/ue/dU4aZNbwZeYAetq42VyLmsmSjvoX7z74X6ZaYCtzAr0EuGw==} + engines: {node: '>=16.20.0'} + cpu: [s390x] + os: [linux] + + '@typescript/typescript-linux-x64@7.0.2': + resolution: {integrity: sha512-EYdf2cNg7rgCWJnxCdJ+F3V39O8ihb37eHAu1LK8oAFizgTQbPOK7zHHXbPt8rX24COqODXeI3sIf0fCXG7H/A==} + engines: {node: '>=16.20.0'} + cpu: [x64] + os: [linux] + + '@typescript/typescript-netbsd-arm64@7.0.2': + resolution: {integrity: sha512-+polYF4MF04aPpO5FTkHran9yUQDSXqy5GiSDKpsll5jy3l3+g9QLhpf39T+ePtefhXLOGrLl0QIjkQP6VnelA==} + engines: {node: '>=16.20.0'} + cpu: [arm64] + os: [netbsd] + + '@typescript/typescript-netbsd-x64@7.0.2': + resolution: {integrity: sha512-8YIT0EHM/3dq10ZOVF/A7pc/YSMtbcecct4rWtexrnSCHOPcpC2KTLXfTCR6vDpnSiY12heNb1GiN/wu+T/FyA==} + engines: {node: '>=16.20.0'} + cpu: [x64] + os: [netbsd] + + '@typescript/typescript-openbsd-arm64@7.0.2': + resolution: {integrity: sha512-APT8+ClYnuYm1u9+kgGXoMj2VzWzcymwh2gNSQVySHfkRDGOTVkoWLjCmOQSaO+PoqQ57B0flRp9SA+7GnnkzQ==} + engines: {node: '>=16.20.0'} + cpu: [arm64] + os: [openbsd] + + '@typescript/typescript-openbsd-x64@7.0.2': + resolution: {integrity: sha512-yX7s+Q0Dln0Dt9tEzZsAjXXR/+ytBM7AlglaqyeMPxQszJ1JhlJdZ6jLA+IzldHtflX81em7lDao1xXu+aRRkg==} + engines: {node: '>=16.20.0'} + cpu: [x64] + os: [openbsd] + + '@typescript/typescript-sunos-x64@7.0.2': + resolution: {integrity: sha512-dLJDGaLZ1D4HPQn62u1n8mBDkJREwMsAkCdkwd4Ieqw+x3TUyTsqY0YiBCtE6H6OzzgGk3iuZ3vFWRS+E8/d1g==} + engines: {node: '>=16.20.0'} + cpu: [x64] + os: [sunos] + + '@typescript/typescript-win32-arm64@7.0.2': + resolution: {integrity: sha512-Gyl1Vy6OsWesLzmq+EP0Fb7b4Nid5232AvcA2SFcdYreldpNtYFFofPjnt62y9hQy7VTaZp65ICJjuAQRaVcIQ==} + engines: {node: '>=16.20.0'} + cpu: [arm64] + os: [win32] + + '@typescript/typescript-win32-x64@7.0.2': + resolution: {integrity: sha512-0BQ3HkAHHlKLSp1qRvf3SUhGpGsDuhB/jgFw75guyqbxJqEaS0Cw/VFO8i2nHglJUzQCRtMMR/IBAKE3ETMC4g==} + engines: {node: '>=16.20.0'} + cpu: [x64] + os: [win32] + + '@valibot/to-json-schema@1.7.1': + resolution: {integrity: sha512-3qkmU6KXWh8GIThEAW3kuRHPQBMjWkKy+Ppz3WkUucx53DTpOa6siMn4xDGSOhlVyMrDaJTCTMLYPZVAIk1P0A==} + peerDependencies: + valibot: ^1.4.0 + + '@vitest/coverage-istanbul@4.1.11': + resolution: {integrity: sha512-pK9LJtOS534bmUwdwLpGD9qL52DeqVWSInrXFMNqrvWLbbAhuIPAlRup3UbsjFOMqCC/p3MmmX7uXV7ZTkcFfQ==} + peerDependencies: + vitest: 4.1.11 + + '@vitest/expect@4.1.11': + resolution: {integrity: sha512-VX2x5vNJXET47KAFzwERI+KRMtTTCSWTfSMKsW7JsUsXV4psq++e3DvZpuTDOpHcxytiDs6p2nhVb2tVDiiUYw==} + + '@vitest/mocker@4.1.11': + resolution: {integrity: sha512-2XJVD55d1o5AZous5CCGKS74g/riOj9odEt2bQpCVZeblHyHdnMeFl4jl0XjU21stf4mbjUkew2eXQZt65g5CQ==} + peerDependencies: + msw: ^2.4.9 + vite: ^6.0.0 || ^7.0.0 || ^8.0.0 + peerDependenciesMeta: + msw: + optional: true + vite: + optional: true + + '@vitest/pretty-format@4.1.11': + resolution: {integrity: sha512-yiZzPbGTS9Sr/JpFl8zHrcIkAofNbFV6k21vIgQN/cY/oxZeXhJv5sc/MBJ5jFKWmWs+oJHw0UXLZjmf931+Vw==} + + '@vitest/runner@4.1.11': + resolution: {integrity: sha512-LztvUgdwMNJMIkj3hQnnxiC2Xy1zNxq928W/xhjCLaNCzqTZOudjwbQf6v9IntZGPw132i2Lq2rgTRZHD3JHNw==} + + '@vitest/snapshot@4.1.11': + resolution: {integrity: sha512-pN7ikn1ON7h8ee4gIAp4AzyK+zBtJPzVbqOgu5LCEh4VaJVbPQcgYQYJIMGQPXVeJJq1fnfazis7a5pFNPahog==} + + '@vitest/spy@4.1.11': + resolution: {integrity: sha512-apNa/prQy2qCeywhnixOHPRCgGNhvg7T4Dapfl1GahLp/R+uhBm5cPyFoNVyqsNd2h1nJxL6BqqdIjiABL60YA==} + + '@vitest/utils@4.1.11': + resolution: {integrity: sha512-zTCVGpyFsGWBhllOyKlTw/vnr6D9qxsfSDyfbyZmTyjHw5N/VuvzHpHoQjm2ZJzn4RJgx5w4r7V0er69CmLgPQ==} + + ajv-draft-04@1.0.0: + resolution: {integrity: sha512-mv00Te6nmYbRp5DCwclxtt7yV/joXJPGS7nM+97GdxvuttCOfgI3K4U25zboyeX0O+myI8ERluxQe5wljMmVIw==} + peerDependencies: + ajv: ^8.5.0 + peerDependenciesMeta: + ajv: + optional: true + + ajv@8.20.0: + resolution: {integrity: sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==} + + argparse@2.0.1: + resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + + assertion-error@2.0.1: + resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} + engines: {node: '>=12'} + + aws4fetch@1.0.20: + resolution: {integrity: sha512-/djoAN709iY65ETD6LKCtyyEI04XIBP5xVvfmNxsEP0uJB5tyaGBztSryRr4HqMStr9R06PisQE7m9zDTXKu6g==} + + baseline-browser-mapping@2.11.21: + resolution: {integrity: sha512-uh8vpY/1/YyFkunIDFH/12p7/7VdPKA1hejMVEbdkEaWnUz0Hesvx5EbiU6XxjyHZIOju+ZMbQJkRh+es3/spQ==} + engines: {node: '>=6.0.0'} + hasBin: true + + blake3-wasm@2.1.5: + resolution: {integrity: sha512-F1+K8EbfOZE49dtoPtmxUQrpXaBIl3ICvasLh+nJta0xkz+9kF/7uet9fLnwKqhDrmj6g+6K3Tw9yQPUg2ka5g==} + + browserslist@4.28.9: + resolution: {integrity: sha512-EWazOblFYUvlGZcfGhPUPmYh3nikUxBVb+y9MJun5f3hBi812X+8MSQTujLBtgK3cf51fJWbWfOjyeO954d+Eg==} + engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} + hasBin: true + + call-me-maybe@1.0.2: + resolution: {integrity: sha512-HpX65o1Hnr9HH25ojC1YGs7HCQLq0GCOibSaWER0eNpgJ/Z1MZv2mTc7+xh6WOPxbRVcmgbv4hGU+uSQ/2xFZQ==} + + caniuse-lite@1.0.30001810: + resolution: {integrity: sha512-TITQPUkaz+aVk5GL6NhOdwk1aEaNTSDPsGFWrTuhKGtjTF70jL/Oht2W4c6rXUe5fu7Ie19VIahAXHIIiWWNeg==} + + chai@6.2.2: + resolution: {integrity: sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==} + engines: {node: '>=18'} + + cjs-module-lexer@1.2.3: + resolution: {integrity: sha512-0TNiGstbQmCFwt4akjjBg5pLRTSyj/PkWQ1ZoO2zntmg9yLqSRxwEa4iCfQLGjqhiqBfOJa7W/E8wfGrTDmlZQ==} + + convert-source-map@2.0.0: + resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} + + cookie@1.1.1: + resolution: {integrity: sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==} + engines: {node: '>=18'} + + debug@4.4.3: + resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + detect-libc@2.1.2: + resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} + engines: {node: '>=8'} + + electron-to-chromium@1.5.422: + resolution: {integrity: sha512-UvA/32XqrLDdZSn7Jllo1AYNcWji/G0d5M0GTViE7KoGBiMunw3a34Sb2KO4ZZyrSEhqsxFoVhWWJshdyfKqJA==} + + error-stack-parser-es@1.0.5: + resolution: {integrity: sha512-5qucVt2XcuGMcEGgWI7i+yZpmpByQ8J1lHhcL7PwqCwu9FPP3VUXzT4ltHe5i2z9dePwEHcDVOAfSnHsOlCXRA==} + + es-module-lexer@2.3.2: + resolution: {integrity: sha512-poHGpORABojJJucnV9KbOavETW8lBVnphkW77ER5/BQ5Fz7oXSoCNek7IH3vR5nRjdsEz926ibFYX8KtLQmdyw==} + + esbuild@0.28.1: + resolution: {integrity: sha512-HrJrvZv5ayxBzPfwphOoNzkzOIIlifzk0KJrGK2c8R4+LKpMtpYLQeUdjnwjWv/LZlkH2laZk+4w78pi99D4Vw==} + engines: {node: '>=18'} + hasBin: true + + escalade@3.2.0: + resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} + engines: {node: '>=6'} + + estree-walker@3.0.3: + resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} + + expect-type@1.4.0: + resolution: {integrity: sha512-KfYbmpRm0VbLjEvVa9yGwCi9GI34xvi7A/HXYWQO65CSD2u3MczUJSuwXKFIxlGsgBQizV9q5J9NHj4VG0n+pA==} + engines: {node: '>=12.0.0'} + + fast-deep-equal@3.1.3: + resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} + + fast-uri@3.1.7: + resolution: {integrity: sha512-dOvZVzjdZdz7phd9v6jCbwxrBW3fK6n8Rc0CtdmM4bumzMnxywBYhuph6J819RRw/ku+rLbelwfMunktuzVVHg==} + + fdir@6.5.0: + resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} + engines: {node: '>=12.0.0'} + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + + fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + + gensync@1.0.0-beta.2: + resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} + engines: {node: '>=6.9.0'} + + has-flag@4.0.0: + resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} + engines: {node: '>=8'} + + hono-openapi@1.3.1: + resolution: {integrity: sha512-NLVeVkhKZ3drmQNEIPac8HX8Y54uf1hJAgIM/7MfDsaeVVmB+QILWQxx5x3R3NvRHgedcbEbOCGY2uR7WQYyMw==} + peerDependencies: + '@hono/standard-validator': ^0.2.0 + '@standard-community/standard-json': ^0.3.5 + '@standard-community/standard-openapi': ^0.2.9 + '@types/json-schema': ^7.0.15 + hono: ^4.11.2 + openapi-types: ^12.1.3 + peerDependenciesMeta: + '@hono/standard-validator': + optional: true + hono: + optional: true + + hono@4.13.7: + resolution: {integrity: sha512-c8/gF9ac8Y78/agExVocyLevgR+JlpNB444Py0FSX8pJoPdYUfUzRcXtYEYGwt6l19qIlVZPN5Mfsw9jFShmQQ==} + engines: {node: '>=16.9.0'} + + html-escaper@2.0.2: + resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==} + + istanbul-lib-coverage@3.2.2: + resolution: {integrity: sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==} + engines: {node: '>=8'} + + istanbul-lib-report@3.0.1: + resolution: {integrity: sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==} + engines: {node: '>=10'} + + istanbul-reports@3.2.0: + resolution: {integrity: sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==} + engines: {node: '>=8'} + + js-tokens@4.0.0: + resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} + + js-yaml@4.3.2: + resolution: {integrity: sha512-SFNOvSJ+Dgf/9An904Yx+CgSlIPCkIpao4qo51lpee25TIRejdH3rhR4EZMGoNx3/TP3O+wzWuiTFl4sqbltzA==} + hasBin: true + + jsesc@3.1.0: + resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==} + engines: {node: '>=6'} + hasBin: true + + json-schema-traverse@1.0.0: + resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==} + + json5@2.2.3: + resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} + engines: {node: '>=6'} + hasBin: true + + kleur@4.1.5: + resolution: {integrity: sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==} + engines: {node: '>=6'} + + lightningcss-android-arm64@1.33.0: + resolution: {integrity: sha512-gEpRTalKdosp4Bb8qWtc2iOgE5SeIHlpS1up9bFq2wAyYhl1UdTObYiHe98zEM9SQvSoqQZ1IQD0JNpg3Ml5pg==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [android] + + lightningcss-darwin-arm64@1.33.0: + resolution: {integrity: sha512-Sciaz8eenNTKn9b3t7+xr0ipTp9YxKQY4npwQ3mrRuL0BAVHBLyZxofhaKBAVtzmtRZ/zTyo0/to4B1uWG/Djg==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [darwin] + + lightningcss-darwin-x64@1.33.0: + resolution: {integrity: sha512-Z5UPAxzrjlWNNyGy6i65cJzzvgJ5D3T6wMvs+gWpY9d7qRhANrxqAp6LhxIgZhWEw18RfJTGcRxjuLIBr+m8XQ==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [darwin] + + lightningcss-freebsd-x64@1.33.0: + resolution: {integrity: sha512-QQM/Ti/hQajJwCY+RiWuCZ9sdtI/XQk7nDK5vC8kkdwixezOlDgvDx7+RT+QjK6FcFT4MpsuoBnHIo/O3StRRg==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [freebsd] + + lightningcss-linux-arm-gnueabihf@1.33.0: + resolution: {integrity: sha512-N7FVBe6iS24MlM6R/4RBTxGhQheZGs7tiQ9U32UtF75NzP5Q7xWPRqLBCKxlRQRk3rY1jCIPLzx7WzOhuUIRLQ==} + engines: {node: '>= 12.0.0'} + cpu: [arm] + os: [linux] + + lightningcss-linux-arm64-gnu@1.33.0: + resolution: {integrity: sha512-j2v/itmy4HlNxlc6voKXYgBqNi0Ng2LShg4z7GufpEgs05P+2suBVyi9I6YHq5uoVFx9ETin3eCEhLVyXGQnKg==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + + lightningcss-linux-arm64-musl@1.33.0: + resolution: {integrity: sha512-yiO5ROMuYQgXbC60yjZU5CYSFZGKXL0HFATXt9mHJn1+zW55oCtMI9NfcVhYLMFDL7gV7oBPon/EmMMGg2OvtQ==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + + lightningcss-linux-x64-gnu@1.33.0: + resolution: {integrity: sha512-ar+Ju7LmcN0Jo4FpL4hpFybwNG9/3A/Br5KW2n2jyODg3MEZXaDYADdemoNS+BDNfMgKvylJLj4S5tyRActuAg==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + + lightningcss-linux-x64-musl@1.33.0: + resolution: {integrity: sha512-RYiYbkokw0trfKqqzfF55lginwEPrD3OJDfTuJzFs1MK6iFnDenaz1fqLLtX4ITG3OktJQXOeTaw1awrBAlZPw==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + + lightningcss-win32-arm64-msvc@1.33.0: + resolution: {integrity: sha512-1K+MPfLSFVpphzpdbfkhlWk6wBrTObBzS2T6db10PNOZgR9GoVsAWzwNyuhUYYbTp23j+4RrncfujZ4uAzXvwA==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [win32] + + lightningcss-win32-x64-msvc@1.33.0: + resolution: {integrity: sha512-OlEICDx/Xl0FqSp4bry8zFnCvGpig3Gl4gCquvYwHuqJKEC1+n9NgDniFvqHGmMv1ZkqDJrDqKKSykTDX+ehuA==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [win32] + + lightningcss@1.33.0: + resolution: {integrity: sha512-WkUDrojuJs0xkgGf2udWxa3yGBRxPtxUkB79i6aCZLRgc7PM8fZe9TosfPDcvEpQZbuFASnHYmRLBLUbmLOIIA==} + engines: {node: '>= 12.0.0'} + + lru-cache@5.1.1: + resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} + + magic-string@0.30.21: + resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} + + magicast@0.5.4: + resolution: {integrity: sha512-llBEhWm1SacoRwgHUoQJYtwp4PBLF4faQi5TCpIGyGs9n4y5+juI0tDgyKIfpqxckRHaHzouUEph3THklWh03w==} + + make-dir@4.0.0: + resolution: {integrity: sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==} + engines: {node: '>=10'} + + miniflare@5.20260903.0-alpha: + resolution: {integrity: sha512-VCZIFxOqFXeibRBJWwTlppqbv2lkeO10IX3QBRGK9j1QiMAfH/OSsCvbjp1MslBMhVZmy2VTRlVaVnsKnbDp6A==} + engines: {node: '>=22.0.0'} + + ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + + nanoid@3.3.18: + resolution: {integrity: sha512-DTg4MJbGMWkfi6VZFdNt2/caMbQy4Ou+Op/hJQvGEWcnVfoA1QA+xzRKAzw9jD6+GVOOeYr/mIcuDSdug6F6+w==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + + nanoid@5.1.16: + resolution: {integrity: sha512-kVrnsrJqMR8+oLJnGEmSWw9BivK5mt7H3FZatVRjrc5wGqFYuBxX1yG7+A7Gi5AefkX6t/oCkizcQgpu0cY1dQ==} + engines: {node: ^18 || >=20} + hasBin: true + + node-releases@2.0.54: + resolution: {integrity: sha512-YHs7BmmcsdAI5Ozuf8JZo6PT0mv2GIWC9vMfvUC3dp65M8hn7Ux8CPL+2oBI7juNuj9d0ndhTcznq2ODBps9cQ==} + engines: {node: '>=18'} + + obug@2.1.4: + resolution: {integrity: sha512-4a+OsYv9UktOJKE+l1A4OufDgdRF9PifWj+tJnHURo/P+WOxpG4GzUFL9qCalmWauao6ogiG+QvnCovwPoyAWA==} + engines: {node: '>=12.20.0'} + + openapi-types@12.1.3: + resolution: {integrity: sha512-N4YtSYJqghVu4iek2ZUvcN/0aqH1kRDuNqzcycDxhOUpg7GdvLa2F3DgS6yBNhInhv2r/6I0Flkn7CqL8+nIcw==} + + path-to-regexp@6.3.0: + resolution: {integrity: sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ==} + + pathe@2.0.3: + resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} + + picocolors@1.1.1: + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + + picomatch@4.0.7: + resolution: {integrity: sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==} + engines: {node: '>=12'} + + postcss@8.5.28: + resolution: {integrity: sha512-RRuzqDtt5Y9h3quz5hWhK+TPnsmVs6WwSU6LkJMeY4HstUEDuYTG8UJSdawMRzmzAtV+KEoG8N3Qg2qLy5vM/A==} + engines: {node: ^10 || ^12 || >=14} + + prettier@3.9.6: + resolution: {integrity: sha512-OpN0zzVdiaiAhxpuuj5efpIS4sY9j7bY6uR5mnj5yPzGkdkjNKSJeUThPb60Jw29QuAZgA4o+/iB49kFiaBX6g==} + engines: {node: '>=14'} + hasBin: true + + quansync@0.2.11: + resolution: {integrity: sha512-AifT7QEbW9Nri4tAwR5M/uzpBuqfZf+zwaEM/QkzEjj7NBuFD2rBuy0K3dE+8wltbezDV7JMA0WfnCPYRSYbXA==} + + require-from-string@2.0.2: + resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} + engines: {node: '>=0.10.0'} + + rolldown@1.2.7: + resolution: {integrity: sha512-g0EtLvBjTUB7jhyV0S/TCup3v/XSVl45vUIGbOGU4QPiyjTenCe4mKuFvW9fEgYmS2Fo42AUssRmNuMziXdrig==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + + semver@6.3.1: + resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} + hasBin: true + + semver@7.8.5: + resolution: {integrity: sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==} + engines: {node: '>=10'} + hasBin: true + + sharp@0.35.2: + resolution: {integrity: sha512-FVtFjtBCMiJS6yb5CX7Sop45WFMpeGw6oRKuJnXYgf/f1ms/D7LE/ZUSNxnW7rZ/dbslQWYkoqFHGPaDBtaK4w==} + engines: {node: '>=20.9.0'} + + siginfo@2.0.0: + resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} + + source-map-js@1.2.1: + resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} + engines: {node: '>=0.10.0'} + + stackback@0.0.2: + resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} + + std-env@4.2.0: + resolution: {integrity: sha512-oCUKSupKTHX53EyjDtuZQ64pjLJ6yYCtpmEw0goYxtjG9KpbRe8KAsl2tBUGU9DyMcJ0RwJ8GqJAFzMXcXW1Rw==} + + supports-color@10.2.2: + resolution: {integrity: sha512-SS+jx45GF1QjgEXQx4NJZV9ImqmO2NPz5FNsIHrsDjh2YsHnawpan7SNQ1o8NuhrbHZy9AZhIoCUiCeaW/C80g==} + engines: {node: '>=18'} + + supports-color@7.2.0: + resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} + engines: {node: '>=8'} + + tagged-tag@1.0.0: + resolution: {integrity: sha512-yEFYrVhod+hdNyx7g5Bnkkb0G6si8HJurOoOEgC8B/O0uXLHlaey/65KRv6cuWBNhBgHKAROVpc7QyYqE5gFng==} + engines: {node: '>=20'} + + tinybench@2.9.0: + resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} + + tinyexec@1.3.1: + resolution: {integrity: sha512-GCvB3aoys96IuDFBMcTB46JOR6mdMtAToqwiW8JlWhsoh1mhHi/xn9ss/Dg7N555GiJyEt2qzoG/NHCwM6h1EA==} + engines: {node: '>=18'} + + tinyglobby@0.2.17: + resolution: {integrity: sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==} + engines: {node: '>=12.0.0'} + + tinyrainbow@3.1.1: + resolution: {integrity: sha512-yau8yJdTt989Mm0Bd/236QnzEiPf2xLLTqUZRUJOo/3CB078LSwzei343DgtJVmfJKJE3TMINY1u42SQsP6mXw==} + engines: {node: '>=14.0.0'} + + tslib@2.6.2: + resolution: {integrity: sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==} + + tsx@4.23.13: + resolution: {integrity: sha512-BL5MGkRln6aDYhb0xbQlEAGw743BaZYWdbWtdJOBriYJboKgUUYCadFp2/FpBBZquBC/ezNBn7wMMPx7FDZUDw==} + engines: {node: '>=18.0.0'} + hasBin: true + + type-fest@5.9.0: + resolution: {integrity: sha512-yANm3Jr3GiJ1qgJlxGAVxTOIcEOk1rhQHamlXtnrCK7EHP4HeM9OGxtMg/W7HFdrVzw/ZWJKGVIJusVH85sLtw==} + engines: {node: '>=20'} + + typescript@7.0.2: + resolution: {integrity: sha512-8FYau96o3NKOhbjKi/qNvG/W5jhzxkbdm5sj9AbZ/5T5sWqn3hJgLfGx27sRKZWTvyzCP8dLRBTf5tBTSRVUNA==} + engines: {node: '>=16.20.0'} + hasBin: true + + undici-types@7.18.2: + resolution: {integrity: sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==} + + undici@7.29.0: + resolution: {integrity: sha512-IDxfleLmmbSskfWSUATiN1nfn2rDuvnMOqb5CWR92iIfojA0Ud+ulOAAEQ57LPr9rWmsreUyf5lwyao+7GNNVw==} + engines: {node: '>=20.18.1'} + + unenv@2.0.0-rc.24: + resolution: {integrity: sha512-i7qRCmY42zmCwnYlh9H2SvLEypEFGye5iRmEMKjcGi7zk9UquigRjFtTLz0TYqr0ZGLZhaMHl/foy1bZR+Cwlw==} + + update-browserslist-db@1.3.2: + resolution: {integrity: sha512-UQ+MSxlhRm1bzjhU+DcuXfjFO1FzNtqhK5+9Yvlp90ItDLk5vT932A0rFu619nf7RVS+Y/VeaUW1jaRDqZ8VJw==} + hasBin: true + peerDependencies: + browserslist: '>= 4.21.0' + + valibot@1.4.2: + resolution: {integrity: sha512-gjdCvJ6d3RyHAneqxMYMW9QMCwYMb3jpOO0IyHZV1bnRHFBHrX3VkIILt5XYR0WhwHiH7Mty8ovuPZ/O3gamrg==} + peerDependencies: + typescript: '>=5' + peerDependenciesMeta: + typescript: + optional: true + + vite@8.2.2: + resolution: {integrity: sha512-cFKLV/PRgAUlIRm5WjMjJ86jrftzpqcgH+Us+DS8mI3CDNiH30Whrz8uHL3+MOLPAgqbMBAqWdAHAphOAM+z/Q==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + peerDependencies: + '@types/node': ^20.19.0 || >=22.12.0 + '@vitejs/devtools': ^0.4.0 || ^0.5.0 + esbuild: ^0.27.0 || ^0.28.0 + jiti: '>=1.21.0' + less: ^4.0.0 + sass: ^1.70.0 + sass-embedded: ^1.70.0 + stylus: '>=0.54.8' + sugarss: ^5.0.0 + terser: ^5.16.0 + tsx: ^4.8.1 + yaml: ^2.4.2 + peerDependenciesMeta: + '@types/node': + optional: true + '@vitejs/devtools': + optional: true + esbuild: + optional: true + jiti: + optional: true + less: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + tsx: + optional: true + yaml: + optional: true + + vitest@4.1.11: + resolution: {integrity: sha512-fhACrNXUidIbGSBr5FlbuBkO7VWC1ZyLl0DO4CU2DrQoAPxX84Ysxs+HeGQpii5lZWV1Q4gBZTTu49mF+A6Edw==} + engines: {node: ^20.0.0 || ^22.0.0 || >=24.0.0} + hasBin: true + peerDependencies: + '@edge-runtime/vm': '*' + '@opentelemetry/api': ^1.9.0 + '@types/node': ^20.0.0 || ^22.0.0 || >=24.0.0 + '@vitest/browser-playwright': 4.1.11 + '@vitest/browser-preview': 4.1.11 + '@vitest/browser-webdriverio': 4.1.11 + '@vitest/coverage-istanbul': 4.1.11 + '@vitest/coverage-v8': 4.1.11 + '@vitest/ui': 4.1.11 + happy-dom: '*' + jsdom: '*' + vite: ^6.0.0 || ^7.0.0 || ^8.0.0 + peerDependenciesMeta: + '@edge-runtime/vm': + optional: true + '@opentelemetry/api': + optional: true + '@types/node': + optional: true + '@vitest/browser-playwright': + optional: true + '@vitest/browser-preview': + optional: true + '@vitest/browser-webdriverio': + optional: true + '@vitest/coverage-istanbul': + optional: true + '@vitest/coverage-v8': + optional: true + '@vitest/ui': + optional: true + happy-dom: + optional: true + jsdom: + optional: true + + why-is-node-running@2.3.0: + resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==} + engines: {node: '>=8'} + hasBin: true + + workerd@1.20260903.1: + resolution: {integrity: sha512-xJzt2RnCy7ulOULmZy/4JLbEPg1uisp9lVoOUsEz+UVhDsTmrSQ0rBXZMGcXuhr2HCGKs89bg8nnbbzvBSX1Ig==} + engines: {node: '>=16'} + hasBin: true + + wrangler@4.129.0: + resolution: {integrity: sha512-PGPvs9UPoFrwxT0VogpESSZGvZIctAuTK3wGsLLPHtHsSgS85kNdvtpa2d14UzG8gwLWD64XUGFPGG9tOXG9VQ==} + engines: {node: '>=22.0.0'} + hasBin: true + peerDependencies: + '@cloudflare/workers-types': ^5.20260903.1 + peerDependenciesMeta: + '@cloudflare/workers-types': + optional: true + + ws@8.21.0: + resolution: {integrity: sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g==} + engines: {node: '>=10.0.0'} + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: '>=5.0.2' + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + + yallist@3.1.1: + resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} + + youch-core@0.3.3: + resolution: {integrity: sha512-ho7XuGjLaJ2hWHoK8yFnsUGy2Y5uDpqSTq1FkHLK4/oqKtyUU1AFbOOxY4IpC9f0fTLjwYbslUz0Po5BpD1wrA==} + + youch@4.1.0-beta.10: + resolution: {integrity: sha512-rLfVLB4FgQneDr0dv1oddCVZmKjcJ6yX6mS4pU82Mq/Dt9a3cLZQ62pDBL4AUO+uVrCvtWz3ZFUL2HFAFJ/BXQ==} + + zod@4.4.3: + resolution: {integrity: sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==} + +snapshots: + + '@apidevtools/json-schema-ref-parser@15.3.6(@types/json-schema@7.0.15)': + dependencies: + '@types/json-schema': 7.0.15 + js-yaml: 4.3.2 + + '@apidevtools/openapi-schemas@2.1.0': {} + + '@apidevtools/swagger-methods@3.0.2': {} + + '@apidevtools/swagger-parser@13.0.0(openapi-types@12.1.3)': + dependencies: + '@apidevtools/json-schema-ref-parser': 15.3.6(@types/json-schema@7.0.15) + '@apidevtools/openapi-schemas': 2.1.0 + '@apidevtools/swagger-methods': 3.0.2 + '@types/json-schema': 7.0.15 + ajv: 8.20.0 + ajv-draft-04: 1.0.0(ajv@8.20.0) + call-me-maybe: 1.0.2 + openapi-types: 12.1.3 + + '@babel/code-frame@7.29.7': + dependencies: + '@babel/helper-validator-identifier': 7.29.7 + js-tokens: 4.0.0 + picocolors: 1.1.1 + + '@babel/compat-data@7.29.7': {} + + '@babel/core@7.29.7': + dependencies: + '@babel/code-frame': 7.29.7 + '@babel/generator': 7.29.8 + '@babel/helper-compilation-targets': 7.29.7 + '@babel/helper-module-transforms': 7.29.7(@babel/core@7.29.7) + '@babel/helpers': 7.29.7 + '@babel/parser': 7.29.8 + '@babel/template': 7.29.7 + '@babel/traverse': 7.29.8 + '@babel/types': 7.29.8 + '@jridgewell/remapping': 2.3.5 + convert-source-map: 2.0.0 + debug: 4.4.3 + gensync: 1.0.0-beta.2 + json5: 2.2.3 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + + '@babel/generator@7.29.8': + dependencies: + '@babel/parser': 7.29.8 + '@babel/types': 7.29.8 + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + jsesc: 3.1.0 + + '@babel/helper-compilation-targets@7.29.7': + dependencies: + '@babel/compat-data': 7.29.7 + '@babel/helper-validator-option': 7.29.7 + browserslist: 4.28.9 + lru-cache: 5.1.1 + semver: 6.3.1 + + '@babel/helper-globals@7.29.7': {} + + '@babel/helper-module-imports@7.29.7': + dependencies: + '@babel/traverse': 7.29.8 + '@babel/types': 7.29.8 + transitivePeerDependencies: + - supports-color + + '@babel/helper-module-transforms@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-module-imports': 7.29.7 + '@babel/helper-validator-identifier': 7.29.7 + '@babel/traverse': 7.29.8 + transitivePeerDependencies: + - supports-color + + '@babel/helper-string-parser@7.29.7': {} + + '@babel/helper-validator-identifier@7.29.7': {} + + '@babel/helper-validator-option@7.29.7': {} + + '@babel/helpers@7.29.7': + dependencies: + '@babel/template': 7.29.7 + '@babel/types': 7.29.8 + + '@babel/parser@7.29.8': + dependencies: + '@babel/types': 7.29.8 + + '@babel/template@7.29.7': + dependencies: + '@babel/code-frame': 7.29.7 + '@babel/parser': 7.29.8 + '@babel/types': 7.29.8 + + '@babel/traverse@7.29.8': + dependencies: + '@babel/code-frame': 7.29.7 + '@babel/generator': 7.29.8 + '@babel/helper-globals': 7.29.7 + '@babel/parser': 7.29.8 + '@babel/template': 7.29.7 + '@babel/types': 7.29.8 + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + + '@babel/types@7.29.8': + dependencies: + '@babel/helper-string-parser': 7.29.7 + '@babel/helper-validator-identifier': 7.29.7 + + '@cloudflare/kv-asset-handler@0.5.0': {} + + '@cloudflare/unenv-preset@2.16.1(unenv@2.0.0-rc.24)(workerd@1.20260903.1)': + dependencies: + unenv: 2.0.0-rc.24 + optionalDependencies: + workerd: 1.20260903.1 + + '@cloudflare/vitest-plugin@1.1.4(@cloudflare/workers-types@5.20260905.1)(@vitest/runner@4.1.11)(@vitest/snapshot@4.1.11)(vitest@4.1.11)': + dependencies: + '@vitest/runner': 4.1.11 + '@vitest/snapshot': 4.1.11 + cjs-module-lexer: 1.2.3 + esbuild: 0.28.1 + miniflare: 5.20260903.0-alpha + vitest: 4.1.11(@types/node@24.13.3)(@vitest/coverage-istanbul@4.1.11)(vite@8.2.2(@types/node@24.13.3)(esbuild@0.28.1)(tsx@4.23.13)) + wrangler: 4.129.0(@cloudflare/workers-types@5.20260905.1) + zod: 4.4.3 + transitivePeerDependencies: + - '@cloudflare/workers-types' + - bufferutil + - utf-8-validate + + '@cloudflare/workerd-darwin-64@1.20260903.1': + optional: true + + '@cloudflare/workerd-darwin-arm64@1.20260903.1': + optional: true + + '@cloudflare/workerd-linux-64@1.20260903.1': + optional: true + + '@cloudflare/workerd-linux-arm64@1.20260903.1': + optional: true + + '@cloudflare/workerd-windows-64@1.20260903.1': + optional: true + + '@cloudflare/workers-types@5.20260905.1': {} + + '@cspotcode/source-map-support@0.8.1': + dependencies: + '@jridgewell/trace-mapping': 0.3.9 + + '@emnapi/runtime@1.11.3': + dependencies: + tslib: 2.6.2 + optional: true + + '@esbuild/aix-ppc64@0.28.1': + optional: true + + '@esbuild/android-arm64@0.28.1': + optional: true + + '@esbuild/android-arm@0.28.1': + optional: true + + '@esbuild/android-x64@0.28.1': + optional: true + + '@esbuild/darwin-arm64@0.28.1': + optional: true + + '@esbuild/darwin-x64@0.28.1': + optional: true + + '@esbuild/freebsd-arm64@0.28.1': + optional: true + + '@esbuild/freebsd-x64@0.28.1': + optional: true + + '@esbuild/linux-arm64@0.28.1': + optional: true + + '@esbuild/linux-arm@0.28.1': + optional: true + + '@esbuild/linux-ia32@0.28.1': + optional: true + + '@esbuild/linux-loong64@0.28.1': + optional: true + + '@esbuild/linux-mips64el@0.28.1': + optional: true + + '@esbuild/linux-ppc64@0.28.1': + optional: true + + '@esbuild/linux-riscv64@0.28.1': + optional: true + + '@esbuild/linux-s390x@0.28.1': + optional: true + + '@esbuild/linux-x64@0.28.1': + optional: true + + '@esbuild/netbsd-arm64@0.28.1': + optional: true + + '@esbuild/netbsd-x64@0.28.1': + optional: true + + '@esbuild/openbsd-arm64@0.28.1': + optional: true + + '@esbuild/openbsd-x64@0.28.1': + optional: true + + '@esbuild/openharmony-arm64@0.28.1': + optional: true + + '@esbuild/sunos-x64@0.28.1': + optional: true + + '@esbuild/win32-arm64@0.28.1': + optional: true + + '@esbuild/win32-ia32@0.28.1': + optional: true + + '@esbuild/win32-x64@0.28.1': + optional: true + + '@hono/standard-validator@0.2.3(@standard-schema/spec@1.1.0)(hono@4.13.7)': + dependencies: + '@standard-schema/spec': 1.1.0 + hono: 4.13.7 + + '@img/colour@1.1.0': {} + + '@img/sharp-darwin-arm64@0.35.2': + optionalDependencies: + '@img/sharp-libvips-darwin-arm64': 1.3.1 + optional: true + + '@img/sharp-darwin-x64@0.35.2': + optionalDependencies: + '@img/sharp-libvips-darwin-x64': 1.3.1 + optional: true + + '@img/sharp-freebsd-wasm32@0.35.2': + dependencies: + '@img/sharp-wasm32': 0.35.2 + optional: true + + '@img/sharp-libvips-darwin-arm64@1.3.1': + optional: true + + '@img/sharp-libvips-darwin-x64@1.3.1': + optional: true + + '@img/sharp-libvips-linux-arm64@1.3.1': + optional: true + + '@img/sharp-libvips-linux-arm@1.3.1': + optional: true + + '@img/sharp-libvips-linux-ppc64@1.3.1': + optional: true + + '@img/sharp-libvips-linux-riscv64@1.3.1': + optional: true + + '@img/sharp-libvips-linux-s390x@1.3.1': + optional: true + + '@img/sharp-libvips-linux-x64@1.3.1': + optional: true + + '@img/sharp-libvips-linuxmusl-arm64@1.3.1': + optional: true + + '@img/sharp-libvips-linuxmusl-x64@1.3.1': + optional: true + + '@img/sharp-linux-arm64@0.35.2': + optionalDependencies: + '@img/sharp-libvips-linux-arm64': 1.3.1 + optional: true + + '@img/sharp-linux-arm@0.35.2': + optionalDependencies: + '@img/sharp-libvips-linux-arm': 1.3.1 + optional: true + + '@img/sharp-linux-ppc64@0.35.2': + optionalDependencies: + '@img/sharp-libvips-linux-ppc64': 1.3.1 + optional: true + + '@img/sharp-linux-riscv64@0.35.2': + optionalDependencies: + '@img/sharp-libvips-linux-riscv64': 1.3.1 + optional: true + + '@img/sharp-linux-s390x@0.35.2': + optionalDependencies: + '@img/sharp-libvips-linux-s390x': 1.3.1 + optional: true + + '@img/sharp-linux-x64@0.35.2': + optionalDependencies: + '@img/sharp-libvips-linux-x64': 1.3.1 + optional: true + + '@img/sharp-linuxmusl-arm64@0.35.2': + optionalDependencies: + '@img/sharp-libvips-linuxmusl-arm64': 1.3.1 + optional: true + + '@img/sharp-linuxmusl-x64@0.35.2': + optionalDependencies: + '@img/sharp-libvips-linuxmusl-x64': 1.3.1 + optional: true + + '@img/sharp-wasm32@0.35.2': + dependencies: + '@emnapi/runtime': 1.11.3 + optional: true + + '@img/sharp-webcontainers-wasm32@0.35.2': + dependencies: + '@img/sharp-wasm32': 0.35.2 + optional: true + + '@img/sharp-win32-arm64@0.35.2': + optional: true + + '@img/sharp-win32-ia32@0.35.2': + optional: true + + '@img/sharp-win32-x64@0.35.2': + optional: true + + '@istanbuljs/schema@0.1.6': {} + + '@jridgewell/gen-mapping@0.3.13': + dependencies: + '@jridgewell/sourcemap-codec': 1.6.0 + '@jridgewell/trace-mapping': 0.3.31 + + '@jridgewell/remapping@2.3.5': + dependencies: + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + + '@jridgewell/resolve-uri@3.1.2': {} + + '@jridgewell/sourcemap-codec@1.6.0': {} + + '@jridgewell/trace-mapping@0.3.31': + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.6.0 + + '@jridgewell/trace-mapping@0.3.9': + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.6.0 + + '@oxc-project/types@0.148.0': {} + + '@poppinss/colors@4.1.6': + dependencies: + kleur: 4.1.5 + + '@poppinss/dumper@0.6.5': + dependencies: + '@poppinss/colors': 4.1.6 + '@sindresorhus/is': 7.2.0 + supports-color: 10.2.2 + + '@poppinss/exception@1.2.3': {} + + '@rolldown/binding-android-arm-eabi@1.2.7': + optional: true + + '@rolldown/binding-android-arm64@1.2.7': + optional: true + + '@rolldown/binding-darwin-arm64@1.2.7': + optional: true + + '@rolldown/binding-darwin-x64@1.2.7': + optional: true + + '@rolldown/binding-freebsd-x64@1.2.7': + optional: true + + '@rolldown/binding-linux-arm-gnueabihf@1.2.7': + optional: true + + '@rolldown/binding-linux-arm64-gnu@1.2.7': + optional: true + + '@rolldown/binding-linux-arm64-musl@1.2.7': + optional: true + + '@rolldown/binding-linux-ppc64-gnu@1.2.7': + optional: true + + '@rolldown/binding-linux-s390x-gnu@1.2.7': + optional: true + + '@rolldown/binding-linux-x64-gnu@1.2.7': + optional: true + + '@rolldown/binding-linux-x64-musl@1.2.7': + optional: true + + '@rolldown/binding-openharmony-arm64@1.2.7': + optional: true + + '@rolldown/binding-win32-arm64-msvc@1.2.7': + optional: true + + '@rolldown/binding-win32-x64-msvc@1.2.7': + optional: true + + '@rolldown/pluginutils@1.0.1': {} + + '@scalar/client-side-rendering@0.3.10': + dependencies: + '@scalar/schemas': 0.8.4 + '@scalar/types': 0.18.3 + '@scalar/validation': 0.6.3 + + '@scalar/helpers@0.11.2': {} + + '@scalar/hono-api-reference@0.12.0(hono@4.13.7)': + dependencies: + '@scalar/client-side-rendering': 0.3.10 + hono: 4.13.7 + + '@scalar/schemas@0.8.4': + dependencies: + '@scalar/helpers': 0.11.2 + '@scalar/validation': 0.6.3 + + '@scalar/types@0.18.3': + dependencies: + '@scalar/helpers': 0.11.2 + nanoid: 5.1.16 + type-fest: 5.9.0 + zod: 4.4.3 + + '@scalar/validation@0.6.3': {} + + '@sindresorhus/is@7.2.0': {} + + '@speed-highlight/core@1.2.24': {} + + '@standard-community/standard-json@0.3.5(@standard-schema/spec@1.1.0)(@types/json-schema@7.0.15)(@valibot/to-json-schema@1.7.1(valibot@1.4.2(typescript@7.0.2)))(quansync@0.2.11)(valibot@1.4.2(typescript@7.0.2))(zod@4.4.3)': + dependencies: + '@standard-schema/spec': 1.1.0 + '@types/json-schema': 7.0.15 + quansync: 0.2.11 + optionalDependencies: + '@valibot/to-json-schema': 1.7.1(valibot@1.4.2(typescript@7.0.2)) + valibot: 1.4.2(typescript@7.0.2) + zod: 4.4.3 + + '@standard-community/standard-openapi@0.2.9(@standard-community/standard-json@0.3.5(@standard-schema/spec@1.1.0)(@types/json-schema@7.0.15)(@valibot/to-json-schema@1.7.1(valibot@1.4.2(typescript@7.0.2)))(quansync@0.2.11)(valibot@1.4.2(typescript@7.0.2))(zod@4.4.3))(@standard-schema/spec@1.1.0)(openapi-types@12.1.3)(valibot@1.4.2(typescript@7.0.2))(zod@4.4.3)': + dependencies: + '@standard-community/standard-json': 0.3.5(@standard-schema/spec@1.1.0)(@types/json-schema@7.0.15)(@valibot/to-json-schema@1.7.1(valibot@1.4.2(typescript@7.0.2)))(quansync@0.2.11)(valibot@1.4.2(typescript@7.0.2))(zod@4.4.3) + '@standard-schema/spec': 1.1.0 + openapi-types: 12.1.3 + optionalDependencies: + valibot: 1.4.2(typescript@7.0.2) + zod: 4.4.3 + + '@standard-schema/spec@1.1.0': {} + + '@types/chai@5.2.3': + dependencies: + '@types/deep-eql': 4.0.2 + assertion-error: 2.0.1 + + '@types/deep-eql@4.0.2': {} + + '@types/estree@1.0.9': {} + + '@types/json-schema@7.0.15': {} + + '@types/node@24.13.3': + dependencies: + undici-types: 7.18.2 + + '@typescript/typescript-aix-ppc64@7.0.2': + optional: true + + '@typescript/typescript-darwin-arm64@7.0.2': + optional: true + + '@typescript/typescript-darwin-x64@7.0.2': + optional: true + + '@typescript/typescript-freebsd-arm64@7.0.2': + optional: true + + '@typescript/typescript-freebsd-x64@7.0.2': + optional: true + + '@typescript/typescript-linux-arm64@7.0.2': + optional: true + + '@typescript/typescript-linux-arm@7.0.2': + optional: true + + '@typescript/typescript-linux-loong64@7.0.2': + optional: true + + '@typescript/typescript-linux-mips64el@7.0.2': + optional: true + + '@typescript/typescript-linux-ppc64@7.0.2': + optional: true + + '@typescript/typescript-linux-riscv64@7.0.2': + optional: true + + '@typescript/typescript-linux-s390x@7.0.2': + optional: true + + '@typescript/typescript-linux-x64@7.0.2': + optional: true + + '@typescript/typescript-netbsd-arm64@7.0.2': + optional: true + + '@typescript/typescript-netbsd-x64@7.0.2': + optional: true + + '@typescript/typescript-openbsd-arm64@7.0.2': + optional: true + + '@typescript/typescript-openbsd-x64@7.0.2': + optional: true + + '@typescript/typescript-sunos-x64@7.0.2': + optional: true + + '@typescript/typescript-win32-arm64@7.0.2': + optional: true + + '@typescript/typescript-win32-x64@7.0.2': + optional: true + + '@valibot/to-json-schema@1.7.1(valibot@1.4.2(typescript@7.0.2))': + dependencies: + valibot: 1.4.2(typescript@7.0.2) + + '@vitest/coverage-istanbul@4.1.11(vitest@4.1.11)': + dependencies: + '@babel/core': 7.29.7 + '@istanbuljs/schema': 0.1.6 + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + istanbul-lib-coverage: 3.2.2 + istanbul-lib-report: 3.0.1 + istanbul-reports: 3.2.0 + magicast: 0.5.4 + obug: 2.1.4 + tinyrainbow: 3.1.1 + vitest: 4.1.11(@types/node@24.13.3)(@vitest/coverage-istanbul@4.1.11)(vite@8.2.2(@types/node@24.13.3)(esbuild@0.28.1)(tsx@4.23.13)) + transitivePeerDependencies: + - supports-color + + '@vitest/expect@4.1.11': + dependencies: + '@standard-schema/spec': 1.1.0 + '@types/chai': 5.2.3 + '@vitest/spy': 4.1.11 + '@vitest/utils': 4.1.11 + chai: 6.2.2 + tinyrainbow: 3.1.1 + + '@vitest/mocker@4.1.11(vite@8.2.2(@types/node@24.13.3)(esbuild@0.28.1)(tsx@4.23.13))': + dependencies: + '@vitest/spy': 4.1.11 + estree-walker: 3.0.3 + magic-string: 0.30.21 + optionalDependencies: + vite: 8.2.2(@types/node@24.13.3)(esbuild@0.28.1)(tsx@4.23.13) + + '@vitest/pretty-format@4.1.11': + dependencies: + tinyrainbow: 3.1.1 + + '@vitest/runner@4.1.11': + dependencies: + '@vitest/utils': 4.1.11 + pathe: 2.0.3 + + '@vitest/snapshot@4.1.11': + dependencies: + '@vitest/pretty-format': 4.1.11 + '@vitest/utils': 4.1.11 + magic-string: 0.30.21 + pathe: 2.0.3 + + '@vitest/spy@4.1.11': {} + + '@vitest/utils@4.1.11': + dependencies: + '@vitest/pretty-format': 4.1.11 + convert-source-map: 2.0.0 + tinyrainbow: 3.1.1 + + ajv-draft-04@1.0.0(ajv@8.20.0): + optionalDependencies: + ajv: 8.20.0 + + ajv@8.20.0: + dependencies: + fast-deep-equal: 3.1.3 + fast-uri: 3.1.7 + json-schema-traverse: 1.0.0 + require-from-string: 2.0.2 + + argparse@2.0.1: {} + + assertion-error@2.0.1: {} + + aws4fetch@1.0.20: {} + + baseline-browser-mapping@2.11.21: {} + + blake3-wasm@2.1.5: {} + + browserslist@4.28.9: + dependencies: + baseline-browser-mapping: 2.11.21 + caniuse-lite: 1.0.30001810 + electron-to-chromium: 1.5.422 + node-releases: 2.0.54 + update-browserslist-db: 1.3.2(browserslist@4.28.9) + + call-me-maybe@1.0.2: {} + + caniuse-lite@1.0.30001810: {} + + chai@6.2.2: {} + + cjs-module-lexer@1.2.3: {} + + convert-source-map@2.0.0: {} + + cookie@1.1.1: {} + + debug@4.4.3: + dependencies: + ms: 2.1.3 + + detect-libc@2.1.2: {} + + electron-to-chromium@1.5.422: {} + + error-stack-parser-es@1.0.5: {} + + es-module-lexer@2.3.2: {} + + esbuild@0.28.1: + optionalDependencies: + '@esbuild/aix-ppc64': 0.28.1 + '@esbuild/android-arm': 0.28.1 + '@esbuild/android-arm64': 0.28.1 + '@esbuild/android-x64': 0.28.1 + '@esbuild/darwin-arm64': 0.28.1 + '@esbuild/darwin-x64': 0.28.1 + '@esbuild/freebsd-arm64': 0.28.1 + '@esbuild/freebsd-x64': 0.28.1 + '@esbuild/linux-arm': 0.28.1 + '@esbuild/linux-arm64': 0.28.1 + '@esbuild/linux-ia32': 0.28.1 + '@esbuild/linux-loong64': 0.28.1 + '@esbuild/linux-mips64el': 0.28.1 + '@esbuild/linux-ppc64': 0.28.1 + '@esbuild/linux-riscv64': 0.28.1 + '@esbuild/linux-s390x': 0.28.1 + '@esbuild/linux-x64': 0.28.1 + '@esbuild/netbsd-arm64': 0.28.1 + '@esbuild/netbsd-x64': 0.28.1 + '@esbuild/openbsd-arm64': 0.28.1 + '@esbuild/openbsd-x64': 0.28.1 + '@esbuild/openharmony-arm64': 0.28.1 + '@esbuild/sunos-x64': 0.28.1 + '@esbuild/win32-arm64': 0.28.1 + '@esbuild/win32-ia32': 0.28.1 + '@esbuild/win32-x64': 0.28.1 + + escalade@3.2.0: {} + + estree-walker@3.0.3: + dependencies: + '@types/estree': 1.0.9 + + expect-type@1.4.0: {} + + fast-deep-equal@3.1.3: {} + + fast-uri@3.1.7: {} + + fdir@6.5.0(picomatch@4.0.7): + optionalDependencies: + picomatch: 4.0.7 + + fsevents@2.3.3: + optional: true + + gensync@1.0.0-beta.2: {} + + has-flag@4.0.0: {} + + hono-openapi@1.3.1(@hono/standard-validator@0.2.3(@standard-schema/spec@1.1.0)(hono@4.13.7))(@standard-community/standard-json@0.3.5(@standard-schema/spec@1.1.0)(@types/json-schema@7.0.15)(@valibot/to-json-schema@1.7.1(valibot@1.4.2(typescript@7.0.2)))(quansync@0.2.11)(valibot@1.4.2(typescript@7.0.2))(zod@4.4.3))(@standard-community/standard-openapi@0.2.9(@standard-community/standard-json@0.3.5(@standard-schema/spec@1.1.0)(@types/json-schema@7.0.15)(@valibot/to-json-schema@1.7.1(valibot@1.4.2(typescript@7.0.2)))(quansync@0.2.11)(valibot@1.4.2(typescript@7.0.2))(zod@4.4.3))(@standard-schema/spec@1.1.0)(openapi-types@12.1.3)(valibot@1.4.2(typescript@7.0.2))(zod@4.4.3))(@types/json-schema@7.0.15)(hono@4.13.7)(openapi-types@12.1.3): + dependencies: + '@standard-community/standard-json': 0.3.5(@standard-schema/spec@1.1.0)(@types/json-schema@7.0.15)(@valibot/to-json-schema@1.7.1(valibot@1.4.2(typescript@7.0.2)))(quansync@0.2.11)(valibot@1.4.2(typescript@7.0.2))(zod@4.4.3) + '@standard-community/standard-openapi': 0.2.9(@standard-community/standard-json@0.3.5(@standard-schema/spec@1.1.0)(@types/json-schema@7.0.15)(@valibot/to-json-schema@1.7.1(valibot@1.4.2(typescript@7.0.2)))(quansync@0.2.11)(valibot@1.4.2(typescript@7.0.2))(zod@4.4.3))(@standard-schema/spec@1.1.0)(openapi-types@12.1.3)(valibot@1.4.2(typescript@7.0.2))(zod@4.4.3) + '@types/json-schema': 7.0.15 + openapi-types: 12.1.3 + optionalDependencies: + '@hono/standard-validator': 0.2.3(@standard-schema/spec@1.1.0)(hono@4.13.7) + hono: 4.13.7 + + hono@4.13.7: {} + + html-escaper@2.0.2: {} + + istanbul-lib-coverage@3.2.2: {} + + istanbul-lib-report@3.0.1: + dependencies: + istanbul-lib-coverage: 3.2.2 + make-dir: 4.0.0 + supports-color: 7.2.0 + + istanbul-reports@3.2.0: + dependencies: + html-escaper: 2.0.2 + istanbul-lib-report: 3.0.1 + + js-tokens@4.0.0: {} + + js-yaml@4.3.2: + dependencies: + argparse: 2.0.1 + + jsesc@3.1.0: {} + + json-schema-traverse@1.0.0: {} + + json5@2.2.3: {} + + kleur@4.1.5: {} + + lightningcss-android-arm64@1.33.0: + optional: true + + lightningcss-darwin-arm64@1.33.0: + optional: true + + lightningcss-darwin-x64@1.33.0: + optional: true + + lightningcss-freebsd-x64@1.33.0: + optional: true + + lightningcss-linux-arm-gnueabihf@1.33.0: + optional: true + + lightningcss-linux-arm64-gnu@1.33.0: + optional: true + + lightningcss-linux-arm64-musl@1.33.0: + optional: true + + lightningcss-linux-x64-gnu@1.33.0: + optional: true + + lightningcss-linux-x64-musl@1.33.0: + optional: true + + lightningcss-win32-arm64-msvc@1.33.0: + optional: true + + lightningcss-win32-x64-msvc@1.33.0: + optional: true + + lightningcss@1.33.0: + dependencies: + detect-libc: 2.1.2 + optionalDependencies: + lightningcss-android-arm64: 1.33.0 + lightningcss-darwin-arm64: 1.33.0 + lightningcss-darwin-x64: 1.33.0 + lightningcss-freebsd-x64: 1.33.0 + lightningcss-linux-arm-gnueabihf: 1.33.0 + lightningcss-linux-arm64-gnu: 1.33.0 + lightningcss-linux-arm64-musl: 1.33.0 + lightningcss-linux-x64-gnu: 1.33.0 + lightningcss-linux-x64-musl: 1.33.0 + lightningcss-win32-arm64-msvc: 1.33.0 + lightningcss-win32-x64-msvc: 1.33.0 + + lru-cache@5.1.1: + dependencies: + yallist: 3.1.1 + + magic-string@0.30.21: + dependencies: + '@jridgewell/sourcemap-codec': 1.6.0 + + magicast@0.5.4: + dependencies: + '@babel/parser': 7.29.8 + '@babel/types': 7.29.8 + source-map-js: 1.2.1 + + make-dir@4.0.0: + dependencies: + semver: 7.8.5 + + miniflare@5.20260903.0-alpha: + dependencies: + '@cspotcode/source-map-support': 0.8.1 + sharp: 0.35.2 + undici: 7.29.0 + workerd: 1.20260903.1 + ws: 8.21.0 + youch: 4.1.0-beta.10 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + + ms@2.1.3: {} + + nanoid@3.3.18: {} + + nanoid@5.1.16: {} + + node-releases@2.0.54: {} + + obug@2.1.4: {} + + openapi-types@12.1.3: {} + + path-to-regexp@6.3.0: {} + + pathe@2.0.3: {} + + picocolors@1.1.1: {} + + picomatch@4.0.7: {} + + postcss@8.5.28: + dependencies: + nanoid: 3.3.18 + picocolors: 1.1.1 + source-map-js: 1.2.1 + + prettier@3.9.6: {} + + quansync@0.2.11: {} + + require-from-string@2.0.2: {} + + rolldown@1.2.7: + dependencies: + '@oxc-project/types': 0.148.0 + '@rolldown/pluginutils': 1.0.1 + optionalDependencies: + '@rolldown/binding-android-arm-eabi': 1.2.7 + '@rolldown/binding-android-arm64': 1.2.7 + '@rolldown/binding-darwin-arm64': 1.2.7 + '@rolldown/binding-darwin-x64': 1.2.7 + '@rolldown/binding-freebsd-x64': 1.2.7 + '@rolldown/binding-linux-arm-gnueabihf': 1.2.7 + '@rolldown/binding-linux-arm64-gnu': 1.2.7 + '@rolldown/binding-linux-arm64-musl': 1.2.7 + '@rolldown/binding-linux-ppc64-gnu': 1.2.7 + '@rolldown/binding-linux-s390x-gnu': 1.2.7 + '@rolldown/binding-linux-x64-gnu': 1.2.7 + '@rolldown/binding-linux-x64-musl': 1.2.7 + '@rolldown/binding-openharmony-arm64': 1.2.7 + '@rolldown/binding-win32-arm64-msvc': 1.2.7 + '@rolldown/binding-win32-x64-msvc': 1.2.7 + + semver@6.3.1: {} + + semver@7.8.5: {} + + sharp@0.35.2: + dependencies: + '@img/colour': 1.1.0 + detect-libc: 2.1.2 + semver: 7.8.5 + optionalDependencies: + '@img/sharp-darwin-arm64': 0.35.2 + '@img/sharp-darwin-x64': 0.35.2 + '@img/sharp-freebsd-wasm32': 0.35.2 + '@img/sharp-libvips-darwin-arm64': 1.3.1 + '@img/sharp-libvips-darwin-x64': 1.3.1 + '@img/sharp-libvips-linux-arm': 1.3.1 + '@img/sharp-libvips-linux-arm64': 1.3.1 + '@img/sharp-libvips-linux-ppc64': 1.3.1 + '@img/sharp-libvips-linux-riscv64': 1.3.1 + '@img/sharp-libvips-linux-s390x': 1.3.1 + '@img/sharp-libvips-linux-x64': 1.3.1 + '@img/sharp-libvips-linuxmusl-arm64': 1.3.1 + '@img/sharp-libvips-linuxmusl-x64': 1.3.1 + '@img/sharp-linux-arm': 0.35.2 + '@img/sharp-linux-arm64': 0.35.2 + '@img/sharp-linux-ppc64': 0.35.2 + '@img/sharp-linux-riscv64': 0.35.2 + '@img/sharp-linux-s390x': 0.35.2 + '@img/sharp-linux-x64': 0.35.2 + '@img/sharp-linuxmusl-arm64': 0.35.2 + '@img/sharp-linuxmusl-x64': 0.35.2 + '@img/sharp-webcontainers-wasm32': 0.35.2 + '@img/sharp-win32-arm64': 0.35.2 + '@img/sharp-win32-ia32': 0.35.2 + '@img/sharp-win32-x64': 0.35.2 + + siginfo@2.0.0: {} + + source-map-js@1.2.1: {} + + stackback@0.0.2: {} + + std-env@4.2.0: {} + + supports-color@10.2.2: {} + + supports-color@7.2.0: + dependencies: + has-flag: 4.0.0 + + tagged-tag@1.0.0: {} + + tinybench@2.9.0: {} + + tinyexec@1.3.1: {} + + tinyglobby@0.2.17: + dependencies: + fdir: 6.5.0(picomatch@4.0.7) + picomatch: 4.0.7 + + tinyrainbow@3.1.1: {} + + tslib@2.6.2: + optional: true + + tsx@4.23.13: + dependencies: + esbuild: 0.28.1 + optionalDependencies: + fsevents: 2.3.3 + + type-fest@5.9.0: + dependencies: + tagged-tag: 1.0.0 + + typescript@7.0.2: + optionalDependencies: + '@typescript/typescript-aix-ppc64': 7.0.2 + '@typescript/typescript-darwin-arm64': 7.0.2 + '@typescript/typescript-darwin-x64': 7.0.2 + '@typescript/typescript-freebsd-arm64': 7.0.2 + '@typescript/typescript-freebsd-x64': 7.0.2 + '@typescript/typescript-linux-arm': 7.0.2 + '@typescript/typescript-linux-arm64': 7.0.2 + '@typescript/typescript-linux-loong64': 7.0.2 + '@typescript/typescript-linux-mips64el': 7.0.2 + '@typescript/typescript-linux-ppc64': 7.0.2 + '@typescript/typescript-linux-riscv64': 7.0.2 + '@typescript/typescript-linux-s390x': 7.0.2 + '@typescript/typescript-linux-x64': 7.0.2 + '@typescript/typescript-netbsd-arm64': 7.0.2 + '@typescript/typescript-netbsd-x64': 7.0.2 + '@typescript/typescript-openbsd-arm64': 7.0.2 + '@typescript/typescript-openbsd-x64': 7.0.2 + '@typescript/typescript-sunos-x64': 7.0.2 + '@typescript/typescript-win32-arm64': 7.0.2 + '@typescript/typescript-win32-x64': 7.0.2 + + undici-types@7.18.2: {} + + undici@7.29.0: {} + + unenv@2.0.0-rc.24: + dependencies: + pathe: 2.0.3 + + update-browserslist-db@1.3.2(browserslist@4.28.9): + dependencies: + browserslist: 4.28.9 + escalade: 3.2.0 + picocolors: 1.1.1 + + valibot@1.4.2(typescript@7.0.2): + optionalDependencies: + typescript: 7.0.2 + + vite@8.2.2(@types/node@24.13.3)(esbuild@0.28.1)(tsx@4.23.13): + dependencies: + lightningcss: 1.33.0 + picomatch: 4.0.7 + postcss: 8.5.28 + rolldown: 1.2.7 + tinyglobby: 0.2.17 + optionalDependencies: + '@types/node': 24.13.3 + esbuild: 0.28.1 + fsevents: 2.3.3 + tsx: 4.23.13 + + vitest@4.1.11(@types/node@24.13.3)(@vitest/coverage-istanbul@4.1.11)(vite@8.2.2(@types/node@24.13.3)(esbuild@0.28.1)(tsx@4.23.13)): + dependencies: + '@vitest/expect': 4.1.11 + '@vitest/mocker': 4.1.11(vite@8.2.2(@types/node@24.13.3)(esbuild@0.28.1)(tsx@4.23.13)) + '@vitest/pretty-format': 4.1.11 + '@vitest/runner': 4.1.11 + '@vitest/snapshot': 4.1.11 + '@vitest/spy': 4.1.11 + '@vitest/utils': 4.1.11 + es-module-lexer: 2.3.2 + expect-type: 1.4.0 + magic-string: 0.30.21 + obug: 2.1.4 + pathe: 2.0.3 + picomatch: 4.0.7 + std-env: 4.2.0 + tinybench: 2.9.0 + tinyexec: 1.3.1 + tinyglobby: 0.2.17 + tinyrainbow: 3.1.1 + vite: 8.2.2(@types/node@24.13.3)(esbuild@0.28.1)(tsx@4.23.13) + why-is-node-running: 2.3.0 + optionalDependencies: + '@types/node': 24.13.3 + '@vitest/coverage-istanbul': 4.1.11(vitest@4.1.11) + transitivePeerDependencies: + - msw + + why-is-node-running@2.3.0: + dependencies: + siginfo: 2.0.0 + stackback: 0.0.2 + + workerd@1.20260903.1: + optionalDependencies: + '@cloudflare/workerd-darwin-64': 1.20260903.1 + '@cloudflare/workerd-darwin-arm64': 1.20260903.1 + '@cloudflare/workerd-linux-64': 1.20260903.1 + '@cloudflare/workerd-linux-arm64': 1.20260903.1 + '@cloudflare/workerd-windows-64': 1.20260903.1 + + wrangler@4.129.0(@cloudflare/workers-types@5.20260905.1): + dependencies: + '@cloudflare/kv-asset-handler': 0.5.0 + '@cloudflare/unenv-preset': 2.16.1(unenv@2.0.0-rc.24)(workerd@1.20260903.1) + blake3-wasm: 2.1.5 + esbuild: 0.28.1 + miniflare: 5.20260903.0-alpha + path-to-regexp: 6.3.0 + unenv: 2.0.0-rc.24 + workerd: 1.20260903.1 + optionalDependencies: + '@cloudflare/workers-types': 5.20260905.1 + fsevents: 2.3.3 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + + ws@8.21.0: {} + + yallist@3.1.1: {} + + youch-core@0.3.3: + dependencies: + '@poppinss/exception': 1.2.3 + error-stack-parser-es: 1.0.5 + + youch@4.1.0-beta.10: + dependencies: + '@poppinss/colors': 4.1.6 + '@poppinss/dumper': 0.6.5 + '@speed-highlight/core': 1.2.24 + cookie: 1.1.1 + youch-core: 0.3.3 + + zod@4.4.3: {} diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml new file mode 100644 index 0000000..f798603 --- /dev/null +++ b/pnpm-workspace.yaml @@ -0,0 +1,4 @@ +onlyBuiltDependencies: + - esbuild + - fsevents + - workerd diff --git a/scripts/generate-openapi.ts b/scripts/generate-openapi.ts new file mode 100644 index 0000000..82fa0aa --- /dev/null +++ b/scripts/generate-openapi.ts @@ -0,0 +1,12 @@ +import { mkdir, writeFile } from 'node:fs/promises'; +import SwaggerParser from '@apidevtools/swagger-parser'; +import { generateSpecs } from 'hono-openapi'; +import { createApp } from '../src/app'; +import { openAPIOptions } from '../src/docs'; + +const document = await generateSpecs(createApp(), openAPIOptions); +// Validate a copy so dereferencing cannot change the generated artifact. +await SwaggerParser.validate(structuredClone(document)); +await mkdir('dist', { recursive: true }); +await writeFile('dist/openapi.json', `${JSON.stringify(document, null, 2)}\n`); +console.log('Generated and validated dist/openapi.json'); diff --git a/src/app.ts b/src/app.ts new file mode 100644 index 0000000..96c6c65 --- /dev/null +++ b/src/app.ts @@ -0,0 +1,14 @@ +import { Hono } from 'hono'; +import { registerDocs } from './docs'; +import { handleError } from './middleware/error'; +import { fileRoutes } from './modules/files/routes'; +import type { AppEnv } from './types/env'; + +export function createApp(): Hono { + const app = new Hono(); + app.onError(handleError); + app.notFound((context) => context.json({ error: "can't find endpoint" }, 404)); + app.route('/', fileRoutes); + registerDocs(app); + return app; +} diff --git a/src/auth.ts b/src/auth.ts deleted file mode 100644 index c298e2e..0000000 --- a/src/auth.ts +++ /dev/null @@ -1,15 +0,0 @@ -// middleware to prevent unauthorized client access private api - -import { IRequest } from "itty-router"; -import { CfReqContext } from "./environment"; -import { ErrorResponse } from "./responses/ErrorResponse"; - -export async function filterUnauthorized(req: IRequest, ctx: CfReqContext) { - const clientSecret = ctx.request.headers.get("x-client-secret"); - if (!clientSecret) { - return new ErrorResponse("unauthorized", 401); - } - if (clientSecret !== ctx.env.CLIENT_SECRET) { - return new ErrorResponse("forbidden", 403) - } -} \ No newline at end of file diff --git a/src/docs.ts b/src/docs.ts new file mode 100644 index 0000000..eede22e --- /dev/null +++ b/src/docs.ts @@ -0,0 +1,40 @@ +import { Scalar } from '@scalar/hono-api-reference'; +import type { Hono } from 'hono'; +import { openAPIRouteHandler, type GenerateSpecOptions } from 'hono-openapi'; +import type { AppEnv } from './types/env'; + +export const openAPIOptions: Partial = { + documentation: { + openapi: '3.1.0', + info: { + title: 'rfiles API', + version: '0.0.0', + description: + 'Hash based file storage on Cloudflare Workers and R2. Successful file downloads default to immutable caching; stored cache policies and the existing storage contract are preserved.', + }, + tags: [{ name: 'Files' }, { name: 'Bulk' }], + servers: [{ url: '/' }], + components: { + securitySchemes: { clientSecret: { type: 'apiKey', in: 'header', name: 'x-client-secret' } }, + }, + }, + exclude: ['/docs', '/openapi.json'], + excludeMethods: ['OPTIONS'], + excludeStaticFile: false, +}; + +export function registerDocs(app: Hono): void { + app.get('/openapi.json', openAPIRouteHandler(app, openAPIOptions)); + app.get( + '/docs', + Scalar((context) => ({ + url: '/openapi.json', + pageTitle: 'rfiles API', + servers: [{ url: new URL(context.req.url).origin }], + persistAuth: false, + showDeveloperTools: 'never', + agent: { disabled: true }, + cdn: 'https://cdn.jsdelivr.net/npm/@scalar/api-reference@1.67.0', + })), + ); +} diff --git a/src/environment.ts b/src/environment.ts deleted file mode 100644 index c7c8245..0000000 --- a/src/environment.ts +++ /dev/null @@ -1,14 +0,0 @@ -export interface Env { - CLIENT_SECRET: string, - R2_TOKEN: string, - S3_ACCESS_KEY: string, - S3_SECRET_ACCESS_KEY: string, - S3_ENDPOINT: string, - FILES_BUCKET: R2Bucket -} - -export interface CfReqContext { - request: Request, - env: Env, - context: ExecutionContext -} \ No newline at end of file diff --git a/src/httpUtil.ts b/src/httpUtil.ts deleted file mode 100644 index 84b05bc..0000000 --- a/src/httpUtil.ts +++ /dev/null @@ -1,5 +0,0 @@ -export function createJsonHeaders(): Headers { - const headers = new Headers(); - headers.set("Content-Type", "application/json"); - return headers; -} diff --git a/src/index.ts b/src/index.ts index 376abca..0e43b28 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,38 +1,16 @@ -/** - * Welcome to Cloudflare Workers! This is your first worker. - * - * - Run `wrangler dev src/index.ts` in your terminal to start a development server - * - Open a browser tab at http://localhost:8787/ to see your worker in action - * - Run `wrangler publish src/index.ts --name my-worker` to publish your worker - * - * Learn more at https://developers.cloudflare.com/workers/ - */ +import { createApp } from './app'; +import { handleError } from './middleware/error'; +import type { Env } from './types/env'; -import { IRequest, Router } from 'itty-router' -import bulkRoutes from './routes/bulkRoutes' -import md5Routes from './routes/md5Routes' -import { ErrorResponse } from './responses/ErrorResponse' -import { NotFoundResponse } from './responses/NotFoundResponse' -import { Env, CfReqContext } from './environment'; - - -const router = Router() - .all('*', bulkRoutes.handle) - .all('*', md5Routes.handle) - .all('*', async (req: IRequest, ctx: CfReqContext) => { - console.log("not_matching_any_routers"); - return new NotFoundResponse("can't find endpoint"); - }); - -const errorHandler = (error: any) => { - console.log("uncaughted_error"); - console.log(error?.stack ?? error); - return new ErrorResponse(error.toString(), 500); -} +const app = createApp(); export default { - fetch: (request: Request, env: Env, context: ExecutionContext): Promise => - router - .handle(request, { request, env, context }) - .catch(errorHandler) -}; \ No newline at end of file + async fetch(request: Request, env: Env, context: ExecutionContext): Promise { + try { + return await app.fetch(request, env, context); + } catch (error) { + // Handle non-Error rejections that Hono does not forward to onError. + return handleError(error); + } + }, +} satisfies ExportedHandler; diff --git a/src/lib/encoding.ts b/src/lib/encoding.ts new file mode 100644 index 0000000..cffe64b --- /dev/null +++ b/src/lib/encoding.ts @@ -0,0 +1,21 @@ +export const MD5_PATTERN = /^[0-9a-fA-F]{32}$/; + +export function normalizeHash(hash: string): string { + if (hash.length !== 32 || !MD5_PATTERN.test(hash)) throw new TypeError('Invalid MD5 hash'); + return hash.toLowerCase(); +} + +export function encodeBase64(buffer: ArrayBuffer): string { + let binary = ''; + for (const byte of new Uint8Array(buffer)) binary += String.fromCharCode(byte); + return btoa(binary); +} + +export function hexToBase64(hash: string): string { + const normalized = normalizeHash(hash); + const bytes = new Uint8Array(16); + for (let index = 0; index < bytes.length; index++) { + bytes[index] = Number.parseInt(normalized.slice(index * 2, index * 2 + 2), 16); + } + return encodeBase64(bytes.buffer); +} diff --git a/src/middleware/auth.ts b/src/middleware/auth.ts new file mode 100644 index 0000000..b1122d4 --- /dev/null +++ b/src/middleware/auth.ts @@ -0,0 +1,10 @@ +import { createMiddleware } from 'hono/factory'; +import { HTTPException } from 'hono/http-exception'; +import type { AppEnv } from '../types/env'; + +export const requireClientSecret = createMiddleware(async (context, next) => { + const secret = context.req.header('x-client-secret'); + if (!secret) throw new HTTPException(401, { message: 'unauthorized' }); + if (secret !== context.env.CLIENT_SECRET) throw new HTTPException(403, { message: 'forbidden' }); + await next(); +}); diff --git a/src/middleware/error.ts b/src/middleware/error.ts new file mode 100644 index 0000000..952bbd9 --- /dev/null +++ b/src/middleware/error.ts @@ -0,0 +1,10 @@ +import { HTTPException } from 'hono/http-exception'; + +/** Render explicit HTTP errors as JSON; keep unexpected failure details in logs. */ +export function handleError(error: unknown): Response { + if (error instanceof HTTPException) { + return Response.json({ error: error.message }, { status: error.status }); + } + console.error('uncaught_error', error instanceof Error ? error.stack : error); + return Response.json({ error: 'internal_server_error' }, { status: 500 }); +} diff --git a/src/modules/files/repository.ts b/src/modules/files/repository.ts new file mode 100644 index 0000000..8320ba4 --- /dev/null +++ b/src/modules/files/repository.ts @@ -0,0 +1,45 @@ +import { encodeBase64, normalizeHash } from '../../lib/encoding'; + +// This exact prefix (including list's lack of a slash) is part of the storage contract. +export const OBJECT_PREFIX = 'objects'; + +export function objectKey(hash: string): string { + return `${OBJECT_PREFIX}/${normalizeHash(hash)}`; +} + +export function objectHash(object: R2Object): string { + return object.key.slice(OBJECT_PREFIX.length + 1); +} + +export function objectMetadata(object: R2Object) { + return { uploaded: object.uploaded, size: object.size, md5: objectHash(object) }; +} + +export function objectHeaders(object: R2Object): Headers { + const headers = new Headers(); + object.writeHttpMetadata(headers); + headers.set('Content-Length', String(object.size)); + headers.set('Last-Modified', object.uploaded.toUTCString()); + if (object.checksums?.md5) headers.set('Content-MD5', encodeBase64(object.checksums.md5)); + return headers; +} + +export class FileRepository { + constructor(private readonly bucket: R2Bucket) {} + + async list() { + // Preserve the single page behavior; fetching further pages changes the response. + const page = await this.bucket.list({ prefix: OBJECT_PREFIX }); + return page.objects; + } + + get(hash: string) { + return this.bucket.get(objectKey(hash)); + } + head(hash: string) { + return this.bucket.head(objectKey(hash)); + } + delete(hash: string) { + return this.bucket.delete(objectKey(hash)); + } +} diff --git a/src/modules/files/routes.ts b/src/modules/files/routes.ts new file mode 100644 index 0000000..1844513 --- /dev/null +++ b/src/modules/files/routes.ts @@ -0,0 +1,262 @@ +import { Hono } from 'hono'; +import { describeRoute, resolver } from 'hono-openapi'; +import { toJsonSchema } from '@valibot/to-json-schema'; +import * as v from 'valibot'; +import type { OpenAPIV3, OpenAPIV3_1 } from 'openapi-types'; +import { HTTPException } from 'hono/http-exception'; +import { requireClientSecret } from '../../middleware/auth'; +import type { AppEnv } from '../../types/env'; +import { objectHash, objectHeaders, objectMetadata } from './repository'; +import { + bulkRequestSchema, + errorSchema, + hashListSchema, + hashSchema, + metadataSchema, + syncResponseSchema, + uploadSchema, +} from './schemas'; +import { FileService, parseBulkRequest, parseHash } from './service'; +import { createUploadInstruction } from './upload'; + +const defaultDownloadCacheControl = 'public, max-age=31536000, immutable'; +const errorContent = { 'application/json': { schema: resolver(errorSchema) } }; +const serverError = { + description: 'Unexpected server error. Internal error details are not exposed.', + content: errorContent, +}; +const authErrors = { + 401: { description: 'Missing or empty x-client-secret.', content: errorContent }, + 403: { description: 'Incorrect x-client-secret.', content: errorContent }, +}; +const security = [{ clientSecret: [] }]; +const hashParameter = { + name: 'hash', + in: 'path' as const, + required: true, + description: + '32 hexadecimal MD5 characters, case-insensitive. Hono percent-decodes the path parameter before validation.', + schema: toJsonSchema(hashSchema) as OpenAPIV3.SchemaObject, +}; +const fileHeaders = { + 'Content-Length': { description: 'File size in bytes.', schema: { type: 'integer' as const } }, + 'Last-Modified': { + description: 'R2 upload time as an HTTP date.', + schema: { type: 'string' as const }, + }, + 'Content-MD5': { + description: 'Base64 checksum, when stored by R2.', + schema: { type: 'string' as const }, + }, +}; +const bulkBody = { + required: true, + description: + 'At most 1000 MD5 hashes of 32 hexadecimal characters. Order and duplicates are retained; unknown properties are ignored. JSON is parsed regardless of Content-Type.', + content: { + 'application/json': { schema: toJsonSchema(bulkRequestSchema) as OpenAPIV3_1.SchemaObject }, + }, +}; +const bulkErrors = { + 400: { + description: 'Malformed JSON or invalid MD5 hashes.', + content: errorContent, + }, + 413: { description: 'More than 1000 hashes (checked before item types).', content: errorContent }, + 500: serverError, +}; + +export const fileRoutes = new Hono(); + +fileRoutes.get( + '/md5', + describeRoute({ + operationId: 'listFiles', + tags: ['Files'], + summary: 'List the first page of stored files', + description: + 'Lists the historical R2 prefix "objects". No pagination cursor is returned. HEAD uses the same handler and omits the response body.', + parameters: [ + { + name: 'return', + in: 'query', + required: true, + description: + 'Required for success. Missing or unsupported values (including md5) return 400. Repeated parameters use the first value.', + schema: { type: 'string', enum: ['hash', 'object'] }, + }, + ], + responses: { + 200: { + description: 'Hashes or file metadata.', + content: { + 'application/json': { + schema: resolver(v.union([hashListSchema, v.array(metadataSchema)])), + }, + }, + }, + 400: { description: 'Invalid return mode.', content: errorContent }, + 500: serverError, + }, + }), + async (context) => { + const mode = context.req.query('return'); + if (mode !== 'hash' && mode !== 'object') + throw new HTTPException(400, { message: 'bad_request' }); + const objects = await new FileService(context.env).repository.list(); + return context.json(mode === 'hash' ? objects.map(objectHash) : objects.map(objectMetadata)); + }, +); + +fileRoutes.get( + '/md5/:hash', + describeRoute({ + operationId: 'downloadFile', + tags: ['Files'], + summary: 'Download a file', + description: + 'Successful downloads default to one year of immutable caching when R2 metadata contains neither Cache-Control nor Expires. Stored cache policies take precedence. Deletion removes the origin object but does not revoke cached copies; metadata changes at the same URL may remain cached. HEAD is also supported at this URL: it uses R2.head() to read metadata only and returns no body, including on errors. HEAD returns 200 for existing files, 400 for invalid hashes, 404 for missing files, and 500 for unexpected errors. It forwards stored HTTP metadata without adding the immutable cache default.', + parameters: [hashParameter], + responses: { + 200: { + description: 'File bytes and stored HTTP metadata.', + headers: { + ...fileHeaders, + 'Cache-Control': { + description: + 'Stored object policy, or the immutable default when neither Cache-Control nor Expires is stored.', + schema: { type: 'string', example: defaultDownloadCacheControl }, + }, + }, + content: { 'application/octet-stream': { schema: { type: 'string', format: 'binary' } } }, + }, + 400: { description: 'Invalid MD5 hash.', content: errorContent }, + 404: { description: 'object_not_found.', content: errorContent }, + 500: serverError, + }, + }), + async (context) => { + const repository = new FileService(context.env).repository; + const hash = parseHash(context.req.param('hash')); + if (context.req.method === 'HEAD') { + const object = await repository.head(hash); + return object + ? new Response(null, { headers: objectHeaders(object) }) + : context.body(null, 404); + } + const object = await repository.get(hash); + if (!object) throw new HTTPException(404, { message: 'object_not_found' }); + const headers = objectHeaders(object); + // Apply only to successful downloads. Respect explicitly stored cache policies, + // including Expires, and leave HEAD headers unchanged. + if (!headers.has('Cache-Control') && !headers.has('Expires')) { + headers.set('Cache-Control', defaultDownloadCacheControl); + } + return new Response(object.body, { headers }); + }, +); +fileRoutes.post( + '/md5/:hash', + describeRoute({ + operationId: 'createUpload', + tags: ['Files'], + summary: 'Create a signed R2 upload request', + security, + description: + 'Returns a PUT URL valid for 600 seconds. Send the file bytes directly to that URL with both returned headers. The instruction is returned as JSON.', + parameters: [ + hashParameter, + { + name: 'exists', + in: 'query', + description: + 'The first value enables overwrite only when it is exactly overwrite; all other inputs mean error.', + schema: { type: 'string', default: 'error' }, + }, + ], + responses: { + 200: { + description: 'Signed PUT instruction.', + content: { 'application/json': { schema: resolver(uploadSchema) } }, + }, + 400: { description: 'Invalid MD5 hash.', content: errorContent }, + ...authErrors, + 500: serverError, + }, + }), + requireClientSecret, + async (context) => { + const hash = parseHash(context.req.param('hash')); + const mode = context.req.query('exists') === 'overwrite' ? 'overwrite' : 'error'; + return context.json(await createUploadInstruction(hash, mode, context.env)); + }, +); + +fileRoutes.delete( + '/md5/:hash', + describeRoute({ + operationId: 'deleteFile', + tags: ['Files'], + summary: 'Delete a file', + security, + parameters: [hashParameter], + responses: { + 204: { description: 'Deleted, or already absent.' }, + 400: { description: 'Invalid MD5 hash.', content: errorContent }, + ...authErrors, + 500: serverError, + }, + }), + requireClientSecret, + async (context) => { + await new FileService(context.env).repository.delete(parseHash(context.req.param('hash'))); + return context.body(null, 204); + }, +); + +fileRoutes.post( + '/query', + describeRoute({ + operationId: 'queryFiles', + tags: ['Bulk'], + summary: 'Find metadata for existing hashes', + requestBody: bulkBody, + responses: { + 200: { + description: 'Existing files, in input order; missing files omitted.', + content: { 'application/json': { schema: resolver(v.array(metadataSchema)) } }, + }, + ...bulkErrors, + }, + }), + async (context) => { + const hashes = await parseBulkRequest(context.req.raw); + return context.json(await new FileService(context.env).query(hashes)); + }, +); + +fileRoutes.post( + '/sync', + describeRoute({ + operationId: 'syncFiles', + tags: ['Bulk'], + summary: 'Find files and create upload requests for missing hashes', + security, + requestBody: bulkBody, + description: + 'objects contains native serialized R2 metadata, unlike /query. Missing inputs each produce an upload instruction; duplicates are retained.', + responses: { + 200: { + description: 'Existing R2 objects and signed upload requests.', + content: { 'application/json': { schema: resolver(syncResponseSchema) } }, + }, + ...authErrors, + ...bulkErrors, + }, + }), + requireClientSecret, + async (context) => { + const hashes = await parseBulkRequest(context.req.raw); + return context.json(await new FileService(context.env).sync(hashes)); + }, +); diff --git a/src/modules/files/schemas.ts b/src/modules/files/schemas.ts new file mode 100644 index 0000000..0f20bd3 --- /dev/null +++ b/src/modules/files/schemas.ts @@ -0,0 +1,40 @@ +import * as v from 'valibot'; +import { MD5_PATTERN } from '../../lib/encoding'; + +export const MAX_HASHES = 1000; +export const hashSchema = v.pipe(v.string(), v.length(32), v.regex(MD5_PATTERN)); +// Stored legacy objects can still appear in list and metadata responses. +export const hashListSchema = v.array(v.string()); +export const bulkRequestSchema = v.object({ + md5: v.pipe(v.array(hashSchema), v.maxLength(MAX_HASHES)), +}); +export const metadataSchema = v.object({ + uploaded: v.pipe(v.string(), v.isoTimestamp()), + size: v.number(), + md5: v.string(), +}); +export const uploadSchema = v.object({ + method: v.literal('PUT'), + url: v.string(), + headers: v.object({ 'If-Unmodified-Since': v.string(), 'Content-MD5': v.string() }), +}); +export const errorSchema = v.object({ error: v.string() }); + +// /sync exposes R2Object's native JSON shape, unlike /query and return=object. +export const storedObjectSchema = v.looseObject({ + key: v.string(), + version: v.string(), + size: v.number(), + etag: v.string(), + httpEtag: v.string(), + uploaded: v.pipe(v.string(), v.isoTimestamp()), + httpMetadata: v.optional(v.record(v.string(), v.unknown())), + customMetadata: v.optional(v.record(v.string(), v.string())), + checksums: v.record(v.string(), v.unknown()), + storageClass: v.optional(v.string()), +}); +export const syncResponseSchema = v.object({ + objects: v.array(storedObjectSchema), + uploads: v.array(uploadSchema), +}); +export type UploadInstruction = v.InferOutput; diff --git a/src/modules/files/service.ts b/src/modules/files/service.ts new file mode 100644 index 0000000..af413a0 --- /dev/null +++ b/src/modules/files/service.ts @@ -0,0 +1,64 @@ +import * as v from 'valibot'; +import { HTTPException } from 'hono/http-exception'; +import { normalizeHash } from '../../lib/encoding'; +import type { Env } from '../../types/env'; +import { FileRepository, objectMetadata } from './repository'; +import { bulkRequestSchema, hashSchema, MAX_HASHES, type UploadInstruction } from './schemas'; +import { createUploadInstruction } from './upload'; + +export function parseHash(hash: string): string { + if (!v.safeParse(hashSchema, hash).success) + throw new HTTPException(400, { message: 'bad_request' }); + return normalizeHash(hash); +} + +export async function parseBulkRequest(request: Request): Promise { + let body: unknown; + try { + body = await request.json(); + } catch (cause) { + throw new HTTPException(400, { message: 'bad_request', cause }); + } + // Size errors historically take precedence over invalid items. + if ( + body && + typeof body === 'object' && + 'md5' in body && + Array.isArray(body.md5) && + body.md5.length > MAX_HASHES + ) { + throw new HTTPException(413, { message: 'too_large_request' }); + } + const result = v.safeParse(bulkRequestSchema, body); + if (!result.success) throw new HTTPException(400, { message: 'bad_request' }); + return result.output.md5.map(normalizeHash); +} + +export class FileService { + readonly repository: FileRepository; + + constructor(private readonly env: Env) { + this.repository = new FileRepository(env.FILES_BUCKET); + } + + async query(hashes: string[]) { + const objects: R2Object[] = []; + for (const hash of hashes) { + const object = await this.repository.head(hash); + if (object) objects.push(object); + } + return objects.map(objectMetadata); + } + + async sync(hashes: string[]) { + const objects: R2Object[] = []; + const uploads: UploadInstruction[] = []; + // Keep order and duplicates, including one signed instruction per missing input. + for (const hash of hashes) { + const object = await this.repository.head(hash); + if (object) objects.push(object); + else uploads.push(await createUploadInstruction(hash, 'error', this.env)); + } + return { objects, uploads }; + } +} diff --git a/src/modules/files/upload.ts b/src/modules/files/upload.ts new file mode 100644 index 0000000..2008794 --- /dev/null +++ b/src/modules/files/upload.ts @@ -0,0 +1,34 @@ +import { AwsClient } from 'aws4fetch'; +import { hexToBase64 } from '../../lib/encoding'; +import type { Env } from '../../types/env'; +import { objectKey } from './repository'; +import type { UploadInstruction } from './schemas'; + +export type ExistsMode = 'error' | 'overwrite'; + +export async function createUploadInstruction( + hash: string, + exists: ExistsMode, + env: Env, +): Promise { + // R2's earliest accepted date prevents replacing an existing object in error mode. + const unmodifiedSince = exists === 'overwrite' ? new Date() : new Date(1632844800000); + const signer = new AwsClient({ + accessKeyId: env.S3_ACCESS_KEY, + secretAccessKey: env.S3_SECRET_ACCESS_KEY, + }); + const url = new URL(env.S3_ENDPOINT); + // Do not resolve or insert a slash: existing S3_ENDPOINT concatenation is contractual. + url.pathname += objectKey(hash); + url.searchParams.set('X-Amz-Expires', '600'); + const headers = { + 'If-Unmodified-Since': unmodifiedSince.toUTCString(), + 'Content-MD5': hexToBase64(hash), + }; + const signed = await signer.sign(url, { + method: 'PUT', + headers, + aws: { signQuery: true, allHeaders: true }, + }); + return { method: 'PUT', url: signed.url, headers }; +} diff --git a/src/r2object.ts b/src/r2object.ts deleted file mode 100644 index b58d4fa..0000000 --- a/src/r2object.ts +++ /dev/null @@ -1,89 +0,0 @@ -import { AwsClient } from "aws4fetch"; -import { PutObjectCommand, S3Client } from "@aws-sdk/client-s3"; -import { getSignedUrl } from "@aws-sdk/s3-request-presigner"; -import { encodeBase64, hexToBase64, normalizeHex } from "./util"; -import { Env } from "./environment"; - -const keyPrefix = "objects" - -export function getMD5Prefix() { - return keyPrefix -} - -export function md5ToKey(hash: string): string { - hash = normalizeHex(hash); - return `${keyPrefix}/${hash}`; -} - -export function keyToMD5(key: string): string { - const sliceFrom = keyPrefix.length + 1; - return key.slice(sliceFrom); -} - -export function addObjectHeaders(object: R2Object, headers: Headers) { - headers.set("Content-Length", object.size.toString()); - headers.set("Last-Modified", object.uploaded.toUTCString()); - if (object.checksums?.md5) - headers.set("Content-MD5", encodeBase64(object.checksums.md5)); -} - -export function createObjectHeaders(object: R2Object): Headers { - const headers = new Headers(); - object.writeHttpMetadata(headers); - addObjectHeaders(object, headers); - return headers; -} - -export function objectToMD5(object: R2Object): any { - return keyToMD5(object.key); -} - -export function objectToMetadata(object: R2Object): any { - return { - "uploaded": object.uploaded, - "size": object.size, - "md5": keyToMD5(object.key) - }; -} - -export async function createUploadRequest(hash: string, exists: "error"|"overwrite", env: Env): Promise { - let unmodifiedSince: Date; - if (exists === "overwrite") { - // even the object already exists the server would accept the request only once - unmodifiedSince = new Date(); - } - else { // error - // the server only accept the request when the object does not exists - // minimum If-Unmodified-Since value of R2 - unmodifiedSince = new Date(1632844800000); - } - - const r2 = new AwsClient({ - accessKeyId: env.S3_ACCESS_KEY, - secretAccessKey: env.S3_SECRET_ACCESS_KEY - }) - - const url = new URL(env.S3_ENDPOINT); - url.pathname += md5ToKey(hash) - url.searchParams.set("X-Amz-Expires", "600"); // 10 minutes - - const headers = { - "If-Unmodified-Since": unmodifiedSince.toUTCString(), - "Content-MD5": hexToBase64(hash) - } - - const signed = await r2.sign(url, { - method: "PUT", - headers, - aws: { - signQuery: true, - allHeaders: true - } - }) - - return { - method: signed.method, - url: signed.url, - headers: headers - } -} \ No newline at end of file diff --git a/src/responses/ErrorResponse.ts b/src/responses/ErrorResponse.ts deleted file mode 100644 index 62eb974..0000000 --- a/src/responses/ErrorResponse.ts +++ /dev/null @@ -1,18 +0,0 @@ -export class ErrorResponse extends Response { - constructor(error?: string|undefined, status?: number|undefined, options?: ResponseInit|undefined) { - let body: string|null - if (typeof error === 'undefined') { - body = null - } - else { - body = JSON.stringify({ - error: error - }); - } - - if (typeof options === 'undefined') - options = {} - options.status = status - super(body, options) - } -} \ No newline at end of file diff --git a/src/responses/MethodNotAllowedResponse.ts b/src/responses/MethodNotAllowedResponse.ts deleted file mode 100644 index b384810..0000000 --- a/src/responses/MethodNotAllowedResponse.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { ErrorResponse } from "./ErrorResponse"; - -export class MethodNotAllowedResponse extends ErrorResponse { - constructor(message?: string, allowed?: Array) { - let allowHeader: Array = [] - if (typeof allowed !== 'undefined') - allowHeader = allowed - - super(message, 405, { - headers: { - Allow: allowHeader.join(','), - } - }); - } -} \ No newline at end of file diff --git a/src/responses/NotFoundResponse.ts b/src/responses/NotFoundResponse.ts deleted file mode 100644 index 8471b94..0000000 --- a/src/responses/NotFoundResponse.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { ErrorResponse } from "./ErrorResponse" - -export class NotFoundResponse extends ErrorResponse { - constructor(message?: string) { - if (typeof message === 'undefined') - super(undefined, 404) - else - super(message, 404) - } -} \ No newline at end of file diff --git a/src/routes/bulkRoutes.ts b/src/routes/bulkRoutes.ts deleted file mode 100644 index ef7204c..0000000 --- a/src/routes/bulkRoutes.ts +++ /dev/null @@ -1,76 +0,0 @@ -import { IRequest, Router } from "itty-router"; -import { Env, CfReqContext } from "../environment"; -import { ErrorResponse } from "../responses/ErrorResponse"; -import { createUploadRequest, md5ToKey, objectToMetadata } from "../r2object"; -import { createJsonHeaders } from "../httpUtil"; -import { filterUnauthorized } from "../auth"; - -const router = Router(); - -router.post('/query', async (req: IRequest, ctx: CfReqContext): Promise => { - const reqObj: any = await ctx.request.json(); - const reqQuery = parseQueryRequest(reqObj) - if (reqQuery instanceof ErrorResponse) - return reqQuery - - const objects: Array = [] - for (let reqHash of reqQuery) { - const key = md5ToKey(reqHash) - const fileObj = await ctx.env.FILES_BUCKET.head(key) - if (fileObj) { - objects.push(fileObj) - } - } - - return new Response( - JSON.stringify(objects.map(objectToMetadata)), { - headers: createJsonHeaders() - }) -}) - -router.post('/sync', filterUnauthorized, async (req: IRequest, ctx: CfReqContext): Promise => { - const reqObj: any = await ctx.request.json(); - const reqQuery = parseQueryRequest(reqObj) - if (reqQuery instanceof ErrorResponse) - return reqQuery - - const objects: Array = [] - const uploads: Array = [] - for (let reqHash of reqQuery) { - const key = md5ToKey(reqHash) - const fileObj = await ctx.env.FILES_BUCKET.head(key) - if (fileObj) { - objects.push(fileObj) - } - else { - const uploadRequest = await createUploadRequest(reqHash, "error", ctx.env); - uploads.push(uploadRequest) - } - } - - return new Response( - JSON.stringify({ objects, uploads }), { - headers: createJsonHeaders() - }) -}); - -function parseQueryRequest(req: any): Array | ErrorResponse { - if (!req) - return new ErrorResponse("bad_request", 400) - - const reqMd5 = req.md5; - if (!Array.isArray(reqMd5)) - return new ErrorResponse("bad_request", 400) - if (reqMd5.length > 1000) - return new ErrorResponse("too_large_request", 413) - - const items: Array = [] - for (let item of reqMd5) { - if (typeof item !== "string") - return new ErrorResponse("bad_request", 400) - items.push(item) - } - return items -} - -export default router; \ No newline at end of file diff --git a/src/routes/md5Routes.ts b/src/routes/md5Routes.ts deleted file mode 100644 index 67a77b9..0000000 --- a/src/routes/md5Routes.ts +++ /dev/null @@ -1,95 +0,0 @@ -import { IRequest, Router } from "itty-router"; -import { CfReqContext } from "../environment"; -import { MethodNotAllowedResponse } from "../responses/MethodNotAllowedResponse"; -import { ErrorResponse } from "../responses/ErrorResponse"; -import { NotFoundResponse } from "../responses/NotFoundResponse"; -import { filterUnauthorized } from "../auth"; -import { createObjectHeaders, createUploadRequest, getMD5Prefix, md5ToKey, objectToMD5, objectToMetadata } from "../r2object"; -import { normalizeHex } from "../util"; -import { createJsonHeaders } from "../httpUtil"; - -const router = Router(); - -// list -router.get('/md5', async (req: IRequest, ctx: CfReqContext): Promise => { - let objectMapper: (object: R2Object) => any; - const returnMode = req.query.return ?? "md5"; - if (returnMode === "hash") { - objectMapper = objectToMD5; - } - else if (returnMode === "object") { - objectMapper = objectToMetadata; - } - else { - return new ErrorResponse("bad_request", 400); - } - - const objects = await ctx.env.FILES_BUCKET.list({ - prefix: getMD5Prefix() - }); - - return new Response( - JSON.stringify(objects.objects.map(objectMapper)), { - headers: createJsonHeaders() - }); -}) - -router.all('/md5', async (req: IRequest, ctx: CfReqContext): Promise => { - return new MethodNotAllowedResponse( - "method_not_allowed", ["GET"]) -}) - -// download -router.get('/md5/:hash', async (req: IRequest, ctx: CfReqContext): Promise => { - const key = md5ToKey(req.params.hash) - const object = await ctx.env.FILES_BUCKET.get(key); - if (object === null) { - return new NotFoundResponse("object_not_found"); - } - - const headers = createObjectHeaders(object); - return new Response(object.body, { - headers - }); -}) - -// upload -router.post('/md5/:hash', filterUnauthorized, async (req: IRequest, ctx: CfReqContext): Promise => { - const hash = normalizeHex(req.params.hash); - const exists = req.query.exists === "overwrite" ? "overwrite" : "error"; - const uploadRequest = await createUploadRequest(hash, exists, ctx.env) - return new Response(JSON.stringify(uploadRequest)); -}) - -// delete -router.delete('/md5/:hash', filterUnauthorized, async (req: IRequest, ctx: CfReqContext): Promise => { - const key = md5ToKey(req.params.hash); - await ctx.env.FILES_BUCKET.delete(key) - return new Response(null, { - status: 204 - }); -}) - -// head -router.all('/md5/:hash', async (req: IRequest, ctx: CfReqContext): Promise => { - if (req.method === 'HEAD') { - const key = md5ToKey(req.params.hash); - const object = await ctx.env.FILES_BUCKET.head(key); - if (object) { - return new Response(null, { - status: 200, - headers: createObjectHeaders(object) - }); - } - else { - return new NotFoundResponse(); - } - } - else { - return new MethodNotAllowedResponse( - "method_not_allowed", - ['GET', 'PUT', 'DELETE', 'HEAD']) - } -}) - -export default router; \ No newline at end of file diff --git a/src/types/env.ts b/src/types/env.ts new file mode 100644 index 0000000..ae733a3 --- /dev/null +++ b/src/types/env.ts @@ -0,0 +1,11 @@ +export interface Env { + CLIENT_SECRET: string; + /** Retained for existing deployments; signing uses the S3 credentials below. */ + R2_TOKEN?: string; + S3_ACCESS_KEY: string; + S3_SECRET_ACCESS_KEY: string; + S3_ENDPOINT: string; + FILES_BUCKET: R2Bucket; +} + +export type AppEnv = { Bindings: Env }; diff --git a/src/util.ts b/src/util.ts deleted file mode 100644 index f7c6be9..0000000 --- a/src/util.ts +++ /dev/null @@ -1,45 +0,0 @@ -const byteToHex: Array = []; - -for (let n = 0; n <= 0xff; ++n) -{ - const hexOctet = n.toString(16).padStart(2, "0"); - byteToHex.push(hexOctet); -} - -export function hex(arrayBuffer: ArrayBuffer) -{ - const buff = new Uint8Array(arrayBuffer); - const hexOctets = new Array(buff.length); - - for (let i = 0; i < buff.length; ++i) - hexOctets[i] = byteToHex[buff[i]]; - - return hexOctets.join(""); -} - -export function normalizeHex(hash: string): string { - return hash - .toLowerCase() - .split("") - .filter(ch => ('0' <= ch && ch <= 'f')) - .join(""); -} - -export function encodeBase64(arrayBuffer: ArrayBuffer): string { - var binary = ''; - var bytes = new Uint8Array(arrayBuffer); - var len = bytes.byteLength; - for (var i = 0; i < len; i++) { - binary += String.fromCharCode( bytes[ i ] ); - } - return btoa( binary ); -} - -export function hexToBase64(hexstring: string): string { - const matched = hexstring.match(/\w{2}/g) - if (!matched) - return "" - return btoa(matched.map(a => { - return String.fromCharCode(parseInt(a, 16)); - }).join("")); -} \ No newline at end of file diff --git a/swagger/draft.yaml b/swagger/draft.yaml deleted file mode 100644 index a24b630..0000000 --- a/swagger/draft.yaml +++ /dev/null @@ -1,331 +0,0 @@ -openapi: 3.0.3 -info: - title: rfiles - description: rfiles api draft - contact: - email: ksi123456ab@gmail.com - version: 0.0.1 -tags: - - name: objects - description: files -paths: - /objects: - get: - tags: - - objects - summary: List all objects - description: List all objects the server stores - parameters: - - name: return - in: query - description: return mode - schema: - type: string - default: md5 - enum: - - md5 - - object - responses: - '200': - description: object list - /objects/{hash}: - head: - tags: - - objects - summary: Get metadata - description: Get metadata of the file by its hash - parameters: - - name: hash - in: path - description: checksum of file - required: true - schema: - type: string - responses: - '200': - description: File found - '404': - description: Not found - content: - application/json: - schema: - type: string - get: - tags: - - objects - summary: Download a file - description: Download a file by its hash - parameters: - - name: hash - in: path - description: checksum of file - required: true - schema: - type: string - responses: - '200': - description: File Found - content: - application/octet-stream: - schema: - type: string - format: binary - '404': - description: Not found - put: - tags: - - objects - summary: Upload a file - description: Update a file by its hash - parameters: - - name: hash - in: path - description: checksum of file - required: true - schema: - type: string - - name: overwrite - in: query - description: Behavior if file already exists - required: false - schema: - type: string - default: error - enum: - - error - - overwrite - - skip - requestBody: - content: - application/octet-stream: - schema: - type: string - format: binary - required: true - responses: - '204': - description: Successful operation (no content) - '400': - description: 'bad request or "mismatch-hash"' - security: - - petstore_auth: - - write:pets - - read:pets - delete: - tags: - - objects - summary: Delete a file - description: Delete a file by its hash - parameters: - - name: hash - in: path - description: checksum of file - required: true - schema: - type: string - responses: - '204': - description: Successful operation -components: - schemas: - Order: - type: object - properties: - id: - type: integer - format: int64 - example: 10 - petId: - type: integer - format: int64 - example: 198772 - quantity: - type: integer - format: int32 - example: 7 - shipDate: - type: string - format: date-time - status: - type: string - description: Order Status - example: approved - enum: - - placed - - approved - - delivered - complete: - type: boolean - xml: - name: order - Customer: - type: object - properties: - id: - type: integer - format: int64 - example: 100000 - username: - type: string - example: fehguy - address: - type: array - xml: - name: addresses - wrapped: true - items: - $ref: '#/components/schemas/Address' - xml: - name: customer - Address: - type: object - properties: - street: - type: string - example: 437 Lytton - city: - type: string - example: Palo Alto - state: - type: string - example: CA - zip: - type: string - example: '94301' - xml: - name: address - Category: - type: object - properties: - id: - type: integer - format: int64 - example: 1 - name: - type: string - example: Dogs - xml: - name: category - User: - type: object - properties: - id: - type: integer - format: int64 - example: 10 - username: - type: string - example: theUser - firstName: - type: string - example: John - lastName: - type: string - example: James - email: - type: string - example: john@email.com - password: - type: string - example: '12345' - phone: - type: string - example: '12345' - userStatus: - type: integer - description: User Status - format: int32 - example: 1 - xml: - name: user - Tag: - type: object - properties: - id: - type: integer - format: int64 - name: - type: string - xml: - name: tag - Pet: - required: - - name - - photoUrls - type: object - properties: - id: - type: integer - format: int64 - example: 10 - name: - type: string - example: doggie - category: - $ref: '#/components/schemas/Category' - photoUrls: - type: array - xml: - wrapped: true - items: - type: string - xml: - name: photoUrl - tags: - type: array - xml: - wrapped: true - items: - $ref: '#/components/schemas/Tag' - status: - type: string - description: pet status in the store - enum: - - available - - pending - - sold - xml: - name: pet - ApiResponse: - type: object - properties: - code: - type: integer - format: int32 - type: - type: string - message: - type: string - xml: - name: '##default' - requestBodies: - Pet: - description: Pet object that needs to be added to the store - content: - application/json: - schema: - $ref: '#/components/schemas/Pet' - application/xml: - schema: - $ref: '#/components/schemas/Pet' - UserArray: - description: List of user object - content: - application/json: - schema: - type: array - items: - $ref: '#/components/schemas/User' - securitySchemes: - petstore_auth: - type: oauth2 - flows: - implicit: - authorizationUrl: https://petstore3.swagger.io/oauth/authorize - scopes: - write:pets: modify pets in your account - read:pets: read your pets - api_key: - type: apiKey - name: api_key - in: header \ No newline at end of file diff --git a/tests/contract/__snapshots__/legacy.test.ts.snap b/tests/contract/__snapshots__/legacy.test.ts.snap new file mode 100644 index 0000000..2e79692 --- /dev/null +++ b/tests/contract/__snapshots__/legacy.test.ts.snap @@ -0,0 +1,1761 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`legacy HTTP and storage contract > '/query array body' 1`] = ` +{ + "body": "{"error":"bad_request"}", + "headers": { + "content-type": "application/json", + }, + "status": 400, + "storage": { + "delete": [], + "get": [], + "head": [], + "list": [], + }, +} +`; + +exports[`legacy HTTP and storage contract > '/query empty body' 1`] = ` +{ + "body": "{"error":"bad_request"}", + "headers": { + "content-type": "application/json", + }, + "status": 400, + "storage": { + "delete": [], + "get": [], + "head": [], + "list": [], + }, +} +`; + +exports[`legacy HTTP and storage contract > '/query malformed body' 1`] = ` +{ + "body": "{"error":"bad_request"}", + "headers": { + "content-type": "application/json", + }, + "status": 400, + "storage": { + "delete": [], + "get": [], + "head": [], + "list": [], + }, +} +`; + +exports[`legacy HTTP and storage contract > '/query missing body' 1`] = ` +{ + "body": "{"error":"bad_request"}", + "headers": { + "content-type": "application/json", + }, + "status": 400, + "storage": { + "delete": [], + "get": [], + "head": [], + "list": [], + }, +} +`; + +exports[`legacy HTTP and storage contract > '/query nonstring body' 1`] = ` +{ + "body": "{"error":"bad_request"}", + "headers": { + "content-type": "application/json", + }, + "status": 400, + "storage": { + "delete": [], + "get": [], + "head": [], + "list": [], + }, +} +`; + +exports[`legacy HTTP and storage contract > '/query null body' 1`] = ` +{ + "body": "{"error":"bad_request"}", + "headers": { + "content-type": "application/json", + }, + "status": 400, + "storage": { + "delete": [], + "get": [], + "head": [], + "list": [], + }, +} +`; + +exports[`legacy HTTP and storage contract > '/query scalar body' 1`] = ` +{ + "body": "{"error":"bad_request"}", + "headers": { + "content-type": "application/json", + }, + "status": 400, + "storage": { + "delete": [], + "get": [], + "head": [], + "list": [], + }, +} +`; + +exports[`legacy HTTP and storage contract > '/query string body' 1`] = ` +{ + "body": "{"error":"bad_request"}", + "headers": { + "content-type": "application/json", + }, + "status": 400, + "storage": { + "delete": [], + "get": [], + "head": [], + "list": [], + }, +} +`; + +exports[`legacy HTTP and storage contract > '/sync array body' 1`] = ` +{ + "body": "{"error":"bad_request"}", + "headers": { + "content-type": "application/json", + }, + "status": 400, + "storage": { + "delete": [], + "get": [], + "head": [], + "list": [], + }, +} +`; + +exports[`legacy HTTP and storage contract > '/sync empty body' 1`] = ` +{ + "body": "{"error":"bad_request"}", + "headers": { + "content-type": "application/json", + }, + "status": 400, + "storage": { + "delete": [], + "get": [], + "head": [], + "list": [], + }, +} +`; + +exports[`legacy HTTP and storage contract > '/sync malformed body' 1`] = ` +{ + "body": "{"error":"bad_request"}", + "headers": { + "content-type": "application/json", + }, + "status": 400, + "storage": { + "delete": [], + "get": [], + "head": [], + "list": [], + }, +} +`; + +exports[`legacy HTTP and storage contract > '/sync missing body' 1`] = ` +{ + "body": "{"error":"bad_request"}", + "headers": { + "content-type": "application/json", + }, + "status": 400, + "storage": { + "delete": [], + "get": [], + "head": [], + "list": [], + }, +} +`; + +exports[`legacy HTTP and storage contract > '/sync nonstring body' 1`] = ` +{ + "body": "{"error":"bad_request"}", + "headers": { + "content-type": "application/json", + }, + "status": 400, + "storage": { + "delete": [], + "get": [], + "head": [], + "list": [], + }, +} +`; + +exports[`legacy HTTP and storage contract > '/sync null body' 1`] = ` +{ + "body": "{"error":"bad_request"}", + "headers": { + "content-type": "application/json", + }, + "status": 400, + "storage": { + "delete": [], + "get": [], + "head": [], + "list": [], + }, +} +`; + +exports[`legacy HTTP and storage contract > '/sync scalar body' 1`] = ` +{ + "body": "{"error":"bad_request"}", + "headers": { + "content-type": "application/json", + }, + "status": 400, + "storage": { + "delete": [], + "get": [], + "head": [], + "list": [], + }, +} +`; + +exports[`legacy HTTP and storage contract > '/sync string body' 1`] = ` +{ + "body": "{"error":"bad_request"}", + "headers": { + "content-type": "application/json", + }, + "status": 400, + "storage": { + "delete": [], + "get": [], + "head": [], + "list": [], + }, +} +`; + +exports[`legacy HTTP and storage contract > 'DELETE /md5 method matrix' 1`] = ` +{ + "body": "{"error":"can't find endpoint"}", + "headers": { + "content-type": "application/json", + }, + "status": 404, + "storage": { + "delete": [], + "get": [], + "head": [], + "list": [], + }, +} +`; + +exports[`legacy HTTP and storage contract > 'DELETE /md5/c09a30415c41bf3e39c58b734…' 1`] = ` +{ + "body": "", + "headers": {}, + "status": 204, + "storage": { + "delete": [ + [ + "objects/c09a30415c41bf3e39c58b7347e8ab1a", + ], + ], + "get": [], + "head": [], + "list": [], + }, +} +`; + +exports[`legacy HTTP and storage contract > 'DELETE /query method matrix' 1`] = ` +{ + "body": "{"error":"can't find endpoint"}", + "headers": { + "content-type": "application/json", + }, + "status": 404, + "storage": { + "delete": [], + "get": [], + "head": [], + "list": [], + }, +} +`; + +exports[`legacy HTTP and storage contract > 'DELETE /sync method matrix' 1`] = ` +{ + "body": "{"error":"can't find endpoint"}", + "headers": { + "content-type": "application/json", + }, + "status": 404, + "storage": { + "delete": [], + "get": [], + "head": [], + "list": [], + }, +} +`; + +exports[`legacy HTTP and storage contract > 'DELETE /unknown method matrix' 1`] = ` +{ + "body": "{"error":"can't find endpoint"}", + "headers": { + "content-type": "application/json", + }, + "status": 404, + "storage": { + "delete": [], + "get": [], + "head": [], + "list": [], + }, +} +`; + +exports[`legacy HTTP and storage contract > 'GET /md5 method matrix' 1`] = ` +{ + "body": "{"error":"bad_request"}", + "headers": { + "content-type": "application/json", + }, + "status": 400, + "storage": { + "delete": [], + "get": [], + "head": [], + "list": [], + }, +} +`; + +exports[`legacy HTTP and storage contract > 'GET /md5/c09a30415c41bf3e39c58b7347e8…' 1`] = ` +{ + "body": "test", + "headers": { + "cache-control": "public, max-age=60", + "content-length": "4", + "content-md5": "wJowQVxBvz45xYtzR+irGg==", + "content-type": "application/octet-stream", + "last-modified": "Wed, 01 Nov 2023 01:02:03 GMT", + }, + "status": 200, + "storage": { + "delete": [], + "get": [ + [ + "objects/c09a30415c41bf3e39c58b7347e8ab1a", + ], + ], + "head": [], + "list": [], + }, +} +`; + +exports[`legacy HTTP and storage contract > 'GET /query method matrix' 1`] = ` +{ + "body": "{"error":"can't find endpoint"}", + "headers": { + "content-type": "application/json", + }, + "status": 404, + "storage": { + "delete": [], + "get": [], + "head": [], + "list": [], + }, +} +`; + +exports[`legacy HTTP and storage contract > 'GET /sync method matrix' 1`] = ` +{ + "body": "{"error":"can't find endpoint"}", + "headers": { + "content-type": "application/json", + }, + "status": 404, + "storage": { + "delete": [], + "get": [], + "head": [], + "list": [], + }, +} +`; + +exports[`legacy HTTP and storage contract > 'GET /unknown method matrix' 1`] = ` +{ + "body": "{"error":"can't find endpoint"}", + "headers": { + "content-type": "application/json", + }, + "status": 404, + "storage": { + "delete": [], + "get": [], + "head": [], + "list": [], + }, +} +`; + +exports[`legacy HTTP and storage contract > 'HEAD /md5 method matrix' 1`] = ` +{ + "body": "", + "headers": { + "content-type": "application/json", + }, + "status": 400, + "storage": { + "delete": [], + "get": [], + "head": [], + "list": [], + }, +} +`; + +exports[`legacy HTTP and storage contract > 'HEAD /md5/c09a30415c41bf3e39c58b7347e…' 1`] = ` +{ + "body": "", + "headers": { + "cache-control": "public, max-age=60", + "content-length": "4", + "content-md5": "wJowQVxBvz45xYtzR+irGg==", + "content-type": "application/octet-stream", + "last-modified": "Wed, 01 Nov 2023 01:02:03 GMT", + }, + "status": 200, + "storage": { + "delete": [], + "get": [], + "head": [ + [ + "objects/c09a30415c41bf3e39c58b7347e8ab1a", + ], + ], + "list": [], + }, +} +`; + +exports[`legacy HTTP and storage contract > 'HEAD /query method matrix' 1`] = ` +{ + "body": "", + "headers": { + "content-type": "application/json", + }, + "status": 404, + "storage": { + "delete": [], + "get": [], + "head": [], + "list": [], + }, +} +`; + +exports[`legacy HTTP and storage contract > 'HEAD /sync method matrix' 1`] = ` +{ + "body": "", + "headers": { + "content-type": "application/json", + }, + "status": 404, + "storage": { + "delete": [], + "get": [], + "head": [], + "list": [], + }, +} +`; + +exports[`legacy HTTP and storage contract > 'HEAD /unknown method matrix' 1`] = ` +{ + "body": "", + "headers": { + "content-type": "application/json", + }, + "status": 404, + "storage": { + "delete": [], + "get": [], + "head": [], + "list": [], + }, +} +`; + +exports[`legacy HTTP and storage contract > 'HEAD' 1`] = ` +{ + "body": "", + "headers": { + "cache-control": "public, max-age=60", + "content-length": "4", + "content-md5": "wJowQVxBvz45xYtzR+irGg==", + "content-type": "application/octet-stream", + "last-modified": "Wed, 01 Nov 2023 01:02:03 GMT", + }, + "status": 200, + "storage": { + "delete": [], + "get": [], + "head": [ + [ + "objects/c09a30415c41bf3e39c58b7347e8ab1a", + ], + ], + "list": [], + }, +} +`; + +exports[`legacy HTTP and storage contract > 'OPTIONS /md5 method matrix' 1`] = ` +{ + "body": "{"error":"can't find endpoint"}", + "headers": { + "content-type": "application/json", + }, + "status": 404, + "storage": { + "delete": [], + "get": [], + "head": [], + "list": [], + }, +} +`; + +exports[`legacy HTTP and storage contract > 'OPTIONS /md5/c09a30415c41bf3e39c58b73…' 1`] = ` +{ + "body": "{"error":"can't find endpoint"}", + "headers": { + "content-type": "application/json", + }, + "status": 404, + "storage": { + "delete": [], + "get": [], + "head": [], + "list": [], + }, +} +`; + +exports[`legacy HTTP and storage contract > 'OPTIONS /query method matrix' 1`] = ` +{ + "body": "{"error":"can't find endpoint"}", + "headers": { + "content-type": "application/json", + }, + "status": 404, + "storage": { + "delete": [], + "get": [], + "head": [], + "list": [], + }, +} +`; + +exports[`legacy HTTP and storage contract > 'OPTIONS /sync method matrix' 1`] = ` +{ + "body": "{"error":"can't find endpoint"}", + "headers": { + "content-type": "application/json", + }, + "status": 404, + "storage": { + "delete": [], + "get": [], + "head": [], + "list": [], + }, +} +`; + +exports[`legacy HTTP and storage contract > 'OPTIONS /unknown method matrix' 1`] = ` +{ + "body": "{"error":"can't find endpoint"}", + "headers": { + "content-type": "application/json", + }, + "status": 404, + "storage": { + "delete": [], + "get": [], + "head": [], + "list": [], + }, +} +`; + +exports[`legacy HTTP and storage contract > 'PATCH /md5 method matrix' 1`] = ` +{ + "body": "{"error":"can't find endpoint"}", + "headers": { + "content-type": "application/json", + }, + "status": 404, + "storage": { + "delete": [], + "get": [], + "head": [], + "list": [], + }, +} +`; + +exports[`legacy HTTP and storage contract > 'PATCH /md5/c09a30415c41bf3e39c58b7347…' 1`] = ` +{ + "body": "{"error":"can't find endpoint"}", + "headers": { + "content-type": "application/json", + }, + "status": 404, + "storage": { + "delete": [], + "get": [], + "head": [], + "list": [], + }, +} +`; + +exports[`legacy HTTP and storage contract > 'PATCH /query method matrix' 1`] = ` +{ + "body": "{"error":"can't find endpoint"}", + "headers": { + "content-type": "application/json", + }, + "status": 404, + "storage": { + "delete": [], + "get": [], + "head": [], + "list": [], + }, +} +`; + +exports[`legacy HTTP and storage contract > 'PATCH /sync method matrix' 1`] = ` +{ + "body": "{"error":"can't find endpoint"}", + "headers": { + "content-type": "application/json", + }, + "status": 404, + "storage": { + "delete": [], + "get": [], + "head": [], + "list": [], + }, +} +`; + +exports[`legacy HTTP and storage contract > 'PATCH /unknown method matrix' 1`] = ` +{ + "body": "{"error":"can't find endpoint"}", + "headers": { + "content-type": "application/json", + }, + "status": 404, + "storage": { + "delete": [], + "get": [], + "head": [], + "list": [], + }, +} +`; + +exports[`legacy HTTP and storage contract > 'POST /md5 method matrix' 1`] = ` +{ + "body": "{"error":"can't find endpoint"}", + "headers": { + "content-type": "application/json", + }, + "status": 404, + "storage": { + "delete": [], + "get": [], + "head": [], + "list": [], + }, +} +`; + +exports[`legacy HTTP and storage contract > 'POST /md5/c09a30415c41bf3e39c58b7347e…' 1`] = ` +{ + "body": "{"method":"PUT","url":"https://test-account.r2.cloudflarestorage.com/files/objects/c09a30415c41bf3e39c58b7347e8ab1a?X-Amz-Expires=600&X-Amz-Date=20240102T030405Z&X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=test-access-key%2F20240102%2Fauto%2Fs3%2Faws4_request&X-Amz-SignedHeaders=content-md5%3Bhost%3Bif-unmodified-since&X-Amz-Signature=86c47f7128fbce963f859c55e1977e8d446537a858237474e9c6c5298ea9a787","headers":{"If-Unmodified-Since":"Tue, 28 Sep 2021 16:00:00 GMT","Content-MD5":"wJowQVxBvz45xYtzR+irGg=="}}", + "headers": { + "content-type": "application/json", + }, + "status": 200, + "storage": { + "delete": [], + "get": [], + "head": [], + "list": [], + }, +} +`; + +exports[`legacy HTTP and storage contract > 'POST /query method matrix' 1`] = ` +{ + "body": "{"error":"bad_request"}", + "headers": { + "content-type": "application/json", + }, + "status": 400, + "storage": { + "delete": [], + "get": [], + "head": [], + "list": [], + }, +} +`; + +exports[`legacy HTTP and storage contract > 'POST /sync method matrix' 1`] = ` +{ + "body": "{"error":"bad_request"}", + "headers": { + "content-type": "application/json", + }, + "status": 400, + "storage": { + "delete": [], + "get": [], + "head": [], + "list": [], + }, +} +`; + +exports[`legacy HTTP and storage contract > 'POST /unknown method matrix' 1`] = ` +{ + "body": "{"error":"can't find endpoint"}", + "headers": { + "content-type": "application/json", + }, + "status": 404, + "storage": { + "delete": [], + "get": [], + "head": [], + "list": [], + }, +} +`; + +exports[`legacy HTTP and storage contract > 'PUT /md5 method matrix' 1`] = ` +{ + "body": "{"error":"can't find endpoint"}", + "headers": { + "content-type": "application/json", + }, + "status": 404, + "storage": { + "delete": [], + "get": [], + "head": [], + "list": [], + }, +} +`; + +exports[`legacy HTTP and storage contract > 'PUT /md5/c09a30415c41bf3e39c58b7347e8…' 1`] = ` +{ + "body": "{"error":"can't find endpoint"}", + "headers": { + "content-type": "application/json", + }, + "status": 404, + "storage": { + "delete": [], + "get": [], + "head": [], + "list": [], + }, +} +`; + +exports[`legacy HTTP and storage contract > 'PUT /query method matrix' 1`] = ` +{ + "body": "{"error":"can't find endpoint"}", + "headers": { + "content-type": "application/json", + }, + "status": 404, + "storage": { + "delete": [], + "get": [], + "head": [], + "list": [], + }, +} +`; + +exports[`legacy HTTP and storage contract > 'PUT /sync method matrix' 1`] = ` +{ + "body": "{"error":"can't find endpoint"}", + "headers": { + "content-type": "application/json", + }, + "status": 404, + "storage": { + "delete": [], + "get": [], + "head": [], + "list": [], + }, +} +`; + +exports[`legacy HTTP and storage contract > 'PUT /unknown method matrix' 1`] = ` +{ + "body": "{"error":"can't find endpoint"}", + "headers": { + "content-type": "application/json", + }, + "status": 404, + "storage": { + "delete": [], + "get": [], + "head": [], + "list": [], + }, +} +`; + +exports[`legacy HTTP and storage contract > 'delete forbidden' 1`] = ` +{ + "body": "{"error":"forbidden"}", + "headers": { + "content-type": "application/json", + }, + "status": 403, + "storage": { + "delete": [], + "get": [], + "head": [], + "list": [], + }, +} +`; + +exports[`legacy HTTP and storage contract > 'delete missing object' 1`] = ` +{ + "body": "", + "headers": {}, + "status": 204, + "storage": { + "delete": [ + [ + "objects/d41d8cd98f00b204e9800998ecf8427e", + ], + ], + "get": [], + "head": [], + "list": [], + }, +} +`; + +exports[`legacy HTTP and storage contract > 'delete unauthorized' 1`] = ` +{ + "body": "{"error":"unauthorized"}", + "headers": { + "content-type": "application/json", + }, + "status": 401, + "storage": { + "delete": [], + "get": [], + "head": [], + "list": [], + }, +} +`; + +exports[`legacy HTTP and storage contract > 'delete' 1`] = ` +{ + "body": "", + "headers": {}, + "status": 204, + "storage": { + "delete": [ + [ + "objects/c09a30415c41bf3e39c58b7347e8ab1a", + ], + ], + "get": [], + "head": [], + "list": [], + }, +} +`; + +exports[`legacy HTTP and storage contract > 'double trailing slash' 1`] = ` +{ + "body": "{"error":"can't find endpoint"}", + "headers": { + "content-type": "application/json", + }, + "status": 404, + "storage": { + "delete": [], + "get": [], + "head": [], + "list": [], + }, +} +`; + +exports[`legacy HTTP and storage contract > 'download double trailing slash' 1`] = ` +{ + "body": "{"error":"can't find endpoint"}", + "headers": { + "content-type": "application/json", + }, + "status": 404, + "storage": { + "delete": [], + "get": [], + "head": [], + "list": [], + }, +} +`; + +exports[`legacy HTTP and storage contract > 'download trailing slash' 1`] = ` +{ + "body": "{"error":"can't find endpoint"}", + "headers": { + "content-type": "application/json", + }, + "status": 404, + "storage": { + "delete": [], + "get": [], + "head": [], + "list": [], + }, +} +`; + +exports[`legacy HTTP and storage contract > 'download' 1`] = ` +{ + "body": "test", + "headers": { + "cache-control": "public, max-age=60", + "content-length": "4", + "content-md5": "wJowQVxBvz45xYtzR+irGg==", + "content-type": "application/octet-stream", + "last-modified": "Wed, 01 Nov 2023 01:02:03 GMT", + }, + "status": 200, + "storage": { + "delete": [], + "get": [ + [ + "objects/c09a30415c41bf3e39c58b7347e8ab1a", + ], + ], + "head": [], + "list": [], + }, +} +`; + +exports[`legacy HTTP and storage contract > 'duplicate return mode' 1`] = ` +{ + "body": "["c09a30415c41bf3e39c58b7347e8ab1a","old/abc"]", + "headers": { + "content-type": "application/json", + }, + "status": 200, + "storage": { + "delete": [], + "get": [], + "head": [], + "list": [ + [ + { + "prefix": "objects", + }, + ], + ], + }, +} +`; + +exports[`legacy HTTP and storage contract > 'encoded query parameter' 1`] = ` +{ + "body": "["c09a30415c41bf3e39c58b7347e8ab1a","old/abc"]", + "headers": { + "content-type": "application/json", + }, + "status": 200, + "storage": { + "delete": [], + "get": [], + "head": [], + "list": [ + [ + { + "prefix": "objects", + }, + ], + ], + }, +} +`; + +exports[`legacy HTTP and storage contract > 'legacy punctuation normalization' 1`] = ` +{ + "body": "{"error":"bad_request"}", + "headers": { + "content-type": "application/json", + }, + "status": 400, + "storage": { + "delete": [], + "get": [], + "head": [], + "list": [], + }, +} +`; + +exports[`legacy HTTP and storage contract > 'list HEAD' 1`] = ` +{ + "body": "", + "headers": { + "content-type": "application/json", + }, + "status": 200, + "storage": { + "delete": [], + "get": [], + "head": [], + "list": [ + [ + { + "prefix": "objects", + }, + ], + ], + }, +} +`; + +exports[`legacy HTTP and storage contract > 'list hashes' 1`] = ` +{ + "body": "["c09a30415c41bf3e39c58b7347e8ab1a","old/abc"]", + "headers": { + "content-type": "application/json", + }, + "status": 200, + "storage": { + "delete": [], + "get": [], + "head": [], + "list": [ + [ + { + "prefix": "objects", + }, + ], + ], + }, +} +`; + +exports[`legacy HTTP and storage contract > 'list metadata' 1`] = ` +{ + "body": "[{"uploaded":"2023-11-01T01:02:03.000Z","size":4,"md5":"c09a30415c41bf3e39c58b7347e8ab1a"},{"uploaded":"2023-11-01T01:02:03.000Z","size":4,"md5":"old/abc"}]", + "headers": { + "content-type": "application/json", + }, + "status": 200, + "storage": { + "delete": [], + "get": [], + "head": [], + "list": [ + [ + { + "prefix": "objects", + }, + ], + ], + }, +} +`; + +exports[`legacy HTTP and storage contract > 'list wrong method' 1`] = ` +{ + "body": "{"error":"can't find endpoint"}", + "headers": { + "content-type": "application/json", + }, + "status": 404, + "storage": { + "delete": [], + "get": [], + "head": [], + "list": [], + }, +} +`; + +exports[`legacy HTTP and storage contract > 'malformed percent encoding' 1`] = ` +{ + "body": "{"error":"bad_request"}", + "headers": { + "content-type": "application/json", + }, + "status": 400, + "storage": { + "delete": [], + "get": [], + "head": [], + "list": [], + }, +} +`; + +exports[`legacy HTTP and storage contract > 'missing HEAD' 1`] = ` +{ + "body": "", + "headers": {}, + "status": 404, + "storage": { + "delete": [], + "get": [], + "head": [ + [ + "objects/d41d8cd98f00b204e9800998ecf8427e", + ], + ], + "list": [], + }, +} +`; + +exports[`legacy HTTP and storage contract > 'missing download' 1`] = ` +{ + "body": "{"error":"object_not_found"}", + "headers": { + "content-type": "application/json", + }, + "status": 404, + "storage": { + "delete": [], + "get": [ + [ + "objects/d41d8cd98f00b204e9800998ecf8427e", + ], + ], + "head": [], + "list": [], + }, +} +`; + +exports[`legacy HTTP and storage contract > 'missing return mode' 1`] = ` +{ + "body": "{"error":"bad_request"}", + "headers": { + "content-type": "application/json", + }, + "status": 400, + "storage": { + "delete": [], + "get": [], + "head": [], + "list": [], + }, +} +`; + +exports[`legacy HTTP and storage contract > 'object OPTIONS remains unsupported' 1`] = ` +{ + "body": "{"error":"can't find endpoint"}", + "headers": { + "content-type": "application/json", + }, + "status": 404, + "storage": { + "delete": [], + "get": [], + "head": [], + "list": [], + }, +} +`; + +exports[`legacy HTTP and storage contract > 'object PATCH remains unsupported' 1`] = ` +{ + "body": "{"error":"can't find endpoint"}", + "headers": { + "content-type": "application/json", + }, + "status": 404, + "storage": { + "delete": [], + "get": [], + "head": [], + "list": [], + }, +} +`; + +exports[`legacy HTTP and storage contract > 'object PUT remains unsupported' 1`] = ` +{ + "body": "{"error":"can't find endpoint"}", + "headers": { + "content-type": "application/json", + }, + "status": 404, + "storage": { + "delete": [], + "get": [], + "head": [], + "list": [], + }, +} +`; + +exports[`legacy HTTP and storage contract > 'old documented md5 mode is rejected' 1`] = ` +{ + "body": "{"error":"bad_request"}", + "headers": { + "content-type": "application/json", + }, + "status": 400, + "storage": { + "delete": [], + "get": [], + "head": [], + "list": [], + }, +} +`; + +exports[`legacy HTTP and storage contract > 'percent encoded hash is decoded' 1`] = ` +{ + "body": "test", + "headers": { + "cache-control": "public, max-age=60", + "content-length": "4", + "content-md5": "wJowQVxBvz45xYtzR+irGg==", + "content-type": "application/octet-stream", + "last-modified": "Wed, 01 Nov 2023 01:02:03 GMT", + }, + "status": 200, + "storage": { + "delete": [], + "get": [ + [ + "objects/c09a30415c41bf3e39c58b7347e8ab1a", + ], + ], + "head": [], + "list": [], + }, +} +`; + +exports[`legacy HTTP and storage contract > 'query accepts unrelated fields' 1`] = ` +{ + "body": "[]", + "headers": { + "content-type": "application/json", + }, + "status": 200, + "storage": { + "delete": [], + "get": [], + "head": [], + "list": [], + }, +} +`; + +exports[`legacy HTTP and storage contract > 'query empty list' 1`] = ` +{ + "body": "[]", + "headers": { + "content-type": "application/json", + }, + "status": 200, + "storage": { + "delete": [], + "get": [], + "head": [], + "list": [], + }, +} +`; + +exports[`legacy HTTP and storage contract > 'query ignores Content-Type' 1`] = ` +{ + "body": "[{"uploaded":"2023-11-01T01:02:03.000Z","size":4,"md5":"c09a30415c41bf3e39c58b7347e8ab1a"}]", + "headers": { + "content-type": "application/json", + }, + "status": 200, + "storage": { + "delete": [], + "get": [], + "head": [ + [ + "objects/c09a30415c41bf3e39c58b7347e8ab1a", + ], + ], + "list": [], + }, +} +`; + +exports[`legacy HTTP and storage contract > 'query includes duplicates and omits m…' 1`] = ` +{ + "body": "[{"uploaded":"2023-11-01T01:02:03.000Z","size":4,"md5":"c09a30415c41bf3e39c58b7347e8ab1a"},{"uploaded":"2023-11-01T01:02:03.000Z","size":4,"md5":"c09a30415c41bf3e39c58b7347e8ab1a"}]", + "headers": { + "content-type": "application/json", + }, + "status": 200, + "storage": { + "delete": [], + "get": [], + "head": [ + [ + "objects/c09a30415c41bf3e39c58b7347e8ab1a", + ], + [ + "objects/d41d8cd98f00b204e9800998ecf8427e", + ], + [ + "objects/c09a30415c41bf3e39c58b7347e8ab1a", + ], + ], + "list": [], + }, +} +`; + +exports[`legacy HTTP and storage contract > 'query too large before invalid items' 1`] = ` +{ + "body": "{"error":"too_large_request"}", + "headers": { + "content-type": "application/json", + }, + "status": 413, + "storage": { + "delete": [], + "get": [], + "head": [], + "list": [], + }, +} +`; + +exports[`legacy HTTP and storage contract > 'query trailing slash' 1`] = ` +{ + "body": "{"error":"can't find endpoint"}", + "headers": { + "content-type": "application/json", + }, + "status": 404, + "storage": { + "delete": [], + "get": [], + "head": [], + "list": [], + }, +} +`; + +exports[`legacy HTTP and storage contract > 'sync empty list' 1`] = ` +{ + "body": "{"objects":[],"uploads":[]}", + "headers": { + "content-type": "application/json", + }, + "status": 200, + "storage": { + "delete": [], + "get": [], + "head": [], + "list": [], + }, +} +`; + +exports[`legacy HTTP and storage contract > 'sync forbidden before parsing' 1`] = ` +{ + "body": "{"error":"forbidden"}", + "headers": { + "content-type": "application/json", + }, + "status": 403, + "storage": { + "delete": [], + "get": [], + "head": [], + "list": [], + }, +} +`; + +exports[`legacy HTTP and storage contract > 'sync permissive checksum uses origina…' 1`] = ` +{ + "body": "{"error":"bad_request"}", + "headers": { + "content-type": "application/json", + }, + "status": 400, + "storage": { + "delete": [], + "get": [], + "head": [], + "list": [], + }, +} +`; + +exports[`legacy HTTP and storage contract > 'sync raw R2 metadata and upload signa…' 1`] = ` +{ + "body": "{"objects":[{"key":"objects/c09a30415c41bf3e39c58b7347e8ab1a","version":"fixture-version","size":4,"etag":"c09a30415c41bf3e39c58b7347e8ab1a","httpEtag":"\\"c09a30415c41bf3e39c58b7347e8ab1a\\"","uploaded":"2023-11-01T01:02:03.000Z","httpMetadata":{"contentType":"application/octet-stream","cacheControl":"public, max-age=60"},"customMetadata":{"originalName":"uploadfile"},"checksums":{"md5":{}},"storageClass":"Standard"}],"uploads":[{"method":"PUT","url":"https://test-account.r2.cloudflarestorage.com/files/objects/d41d8cd98f00b204e9800998ecf8427e?X-Amz-Expires=600&X-Amz-Date=20240102T030405Z&X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=test-access-key%2F20240102%2Fauto%2Fs3%2Faws4_request&X-Amz-SignedHeaders=content-md5%3Bhost%3Bif-unmodified-since&X-Amz-Signature=2a805a7482d4c2a7e8068eb3429dc5c60f26c3d1b6e351ae18a0452b094ed0b6","headers":{"If-Unmodified-Since":"Tue, 28 Sep 2021 16:00:00 GMT","Content-MD5":"1B2M2Y8AsgTpgAmY7PhCfg=="}},{"method":"PUT","url":"https://test-account.r2.cloudflarestorage.com/files/objects/d41d8cd98f00b204e9800998ecf8427e?X-Amz-Expires=600&X-Amz-Date=20240102T030405Z&X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=test-access-key%2F20240102%2Fauto%2Fs3%2Faws4_request&X-Amz-SignedHeaders=content-md5%3Bhost%3Bif-unmodified-since&X-Amz-Signature=2a805a7482d4c2a7e8068eb3429dc5c60f26c3d1b6e351ae18a0452b094ed0b6","headers":{"If-Unmodified-Since":"Tue, 28 Sep 2021 16:00:00 GMT","Content-MD5":"1B2M2Y8AsgTpgAmY7PhCfg=="}}]}", + "headers": { + "content-type": "application/json", + }, + "status": 200, + "storage": { + "delete": [], + "get": [], + "head": [ + [ + "objects/c09a30415c41bf3e39c58b7347e8ab1a", + ], + [ + "objects/d41d8cd98f00b204e9800998ecf8427e", + ], + [ + "objects/d41d8cd98f00b204e9800998ecf8427e", + ], + ], + "list": [], + }, +} +`; + +exports[`legacy HTTP and storage contract > 'sync unauthorized before parsing' 1`] = ` +{ + "body": "{"error":"unauthorized"}", + "headers": { + "content-type": "application/json", + }, + "status": 401, + "storage": { + "delete": [], + "get": [], + "head": [], + "list": [], + }, +} +`; + +exports[`legacy HTTP and storage contract > 'sync uppercase checksum uses original…' 1`] = ` +{ + "body": "{"objects":[],"uploads":[{"method":"PUT","url":"https://test-account.r2.cloudflarestorage.com/files/objects/d41d8cd98f00b204e9800998ecf8427e?X-Amz-Expires=600&X-Amz-Date=20240102T030405Z&X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=test-access-key%2F20240102%2Fauto%2Fs3%2Faws4_request&X-Amz-SignedHeaders=content-md5%3Bhost%3Bif-unmodified-since&X-Amz-Signature=2a805a7482d4c2a7e8068eb3429dc5c60f26c3d1b6e351ae18a0452b094ed0b6","headers":{"If-Unmodified-Since":"Tue, 28 Sep 2021 16:00:00 GMT","Content-MD5":"1B2M2Y8AsgTpgAmY7PhCfg=="}}]}", + "headers": { + "content-type": "application/json", + }, + "status": 200, + "storage": { + "delete": [], + "get": [], + "head": [ + [ + "objects/d41d8cd98f00b204e9800998ecf8427e", + ], + ], + "list": [], + }, +} +`; + +exports[`legacy HTTP and storage contract > 'trailing slash' 1`] = ` +{ + "body": "{"error":"can't find endpoint"}", + "headers": { + "content-type": "application/json", + }, + "status": 404, + "storage": { + "delete": [], + "get": [], + "head": [], + "list": [], + }, +} +`; + +exports[`legacy HTTP and storage contract > 'unknown bulk method' 1`] = ` +{ + "body": "{"error":"can't find endpoint"}", + "headers": { + "content-type": "application/json", + }, + "status": 404, + "storage": { + "delete": [], + "get": [], + "head": [], + "list": [], + }, +} +`; + +exports[`legacy HTTP and storage contract > 'unknown endpoint' 1`] = ` +{ + "body": "{"error":"can't find endpoint"}", + "headers": { + "content-type": "application/json", + }, + "status": 404, + "storage": { + "delete": [], + "get": [], + "head": [], + "list": [], + }, +} +`; + +exports[`legacy HTTP and storage contract > 'unknown nested endpoint' 1`] = ` +{ + "body": "{"error":"can't find endpoint"}", + "headers": { + "content-type": "application/json", + }, + "status": 404, + "storage": { + "delete": [], + "get": [], + "head": [], + "list": [], + }, +} +`; + +exports[`legacy HTTP and storage contract > 'upload duplicate exists: first wins' 1`] = ` +{ + "body": "{"method":"PUT","url":"https://test-account.r2.cloudflarestorage.com/files/objects/c09a30415c41bf3e39c58b7347e8ab1a?X-Amz-Expires=600&X-Amz-Date=20240102T030405Z&X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=test-access-key%2F20240102%2Fauto%2Fs3%2Faws4_request&X-Amz-SignedHeaders=content-md5%3Bhost%3Bif-unmodified-since&X-Amz-Signature=83ed8bcff7fb6e06be602e67d9983cf17eb7925c942c0ecc4fd4bbc9e02dce15","headers":{"If-Unmodified-Since":"Tue, 02 Jan 2024 03:04:05 GMT","Content-MD5":"wJowQVxBvz45xYtzR+irGg=="}}", + "headers": { + "content-type": "application/json", + }, + "status": 200, + "storage": { + "delete": [], + "get": [], + "head": [], + "list": [], + }, +} +`; + +exports[`legacy HTTP and storage contract > 'upload empty secret' 1`] = ` +{ + "body": "{"error":"unauthorized"}", + "headers": { + "content-type": "application/json", + }, + "status": 401, + "storage": { + "delete": [], + "get": [], + "head": [], + "list": [], + }, +} +`; + +exports[`legacy HTTP and storage contract > 'upload forbidden' 1`] = ` +{ + "body": "{"error":"forbidden"}", + "headers": { + "content-type": "application/json", + }, + "status": 403, + "storage": { + "delete": [], + "get": [], + "head": [], + "list": [], + }, +} +`; + +exports[`legacy HTTP and storage contract > 'upload overwrite signature' 1`] = ` +{ + "body": "{"method":"PUT","url":"https://test-account.r2.cloudflarestorage.com/files/objects/c09a30415c41bf3e39c58b7347e8ab1a?X-Amz-Expires=600&X-Amz-Date=20240102T030405Z&X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=test-access-key%2F20240102%2Fauto%2Fs3%2Faws4_request&X-Amz-SignedHeaders=content-md5%3Bhost%3Bif-unmodified-since&X-Amz-Signature=83ed8bcff7fb6e06be602e67d9983cf17eb7925c942c0ecc4fd4bbc9e02dce15","headers":{"If-Unmodified-Since":"Tue, 02 Jan 2024 03:04:05 GMT","Content-MD5":"wJowQVxBvz45xYtzR+irGg=="}}", + "headers": { + "content-type": "application/json", + }, + "status": 200, + "storage": { + "delete": [], + "get": [], + "head": [], + "list": [], + }, +} +`; + +exports[`legacy HTTP and storage contract > 'upload permissive hash' 1`] = ` +{ + "body": "{"error":"bad_request"}", + "headers": { + "content-type": "application/json", + }, + "status": 400, + "storage": { + "delete": [], + "get": [], + "head": [], + "list": [], + }, +} +`; + +exports[`legacy HTTP and storage contract > 'upload signature' 1`] = ` +{ + "body": "{"method":"PUT","url":"https://test-account.r2.cloudflarestorage.com/files/objects/c09a30415c41bf3e39c58b7347e8ab1a?X-Amz-Expires=600&X-Amz-Date=20240102T030405Z&X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=test-access-key%2F20240102%2Fauto%2Fs3%2Faws4_request&X-Amz-SignedHeaders=content-md5%3Bhost%3Bif-unmodified-since&X-Amz-Signature=86c47f7128fbce963f859c55e1977e8d446537a858237474e9c6c5298ea9a787","headers":{"If-Unmodified-Since":"Tue, 28 Sep 2021 16:00:00 GMT","Content-MD5":"wJowQVxBvz45xYtzR+irGg=="}}", + "headers": { + "content-type": "application/json", + }, + "status": 200, + "storage": { + "delete": [], + "get": [], + "head": [], + "list": [], + }, +} +`; + +exports[`legacy HTTP and storage contract > 'upload unauthorized' 1`] = ` +{ + "body": "{"error":"unauthorized"}", + "headers": { + "content-type": "application/json", + }, + "status": 401, + "storage": { + "delete": [], + "get": [], + "head": [], + "list": [], + }, +} +`; + +exports[`legacy HTTP and storage contract > 'upload unknown exists means error' 1`] = ` +{ + "body": "{"method":"PUT","url":"https://test-account.r2.cloudflarestorage.com/files/objects/c09a30415c41bf3e39c58b7347e8ab1a?X-Amz-Expires=600&X-Amz-Date=20240102T030405Z&X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=test-access-key%2F20240102%2Fauto%2Fs3%2Faws4_request&X-Amz-SignedHeaders=content-md5%3Bhost%3Bif-unmodified-since&X-Amz-Signature=86c47f7128fbce963f859c55e1977e8d446537a858237474e9c6c5298ea9a787","headers":{"If-Unmodified-Since":"Tue, 28 Sep 2021 16:00:00 GMT","Content-MD5":"wJowQVxBvz45xYtzR+irGg=="}}", + "headers": { + "content-type": "application/json", + }, + "status": 200, + "storage": { + "delete": [], + "get": [], + "head": [], + "list": [], + }, +} +`; + +exports[`legacy HTTP and storage contract > 'upload uppercase signature' 1`] = ` +{ + "body": "{"method":"PUT","url":"https://test-account.r2.cloudflarestorage.com/files/objects/c09a30415c41bf3e39c58b7347e8ab1a?X-Amz-Expires=600&X-Amz-Date=20240102T030405Z&X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=test-access-key%2F20240102%2Fauto%2Fs3%2Faws4_request&X-Amz-SignedHeaders=content-md5%3Bhost%3Bif-unmodified-since&X-Amz-Signature=86c47f7128fbce963f859c55e1977e8d446537a858237474e9c6c5298ea9a787","headers":{"If-Unmodified-Since":"Tue, 28 Sep 2021 16:00:00 GMT","Content-MD5":"wJowQVxBvz45xYtzR+irGg=="}}", + "headers": { + "content-type": "application/json", + }, + "status": 200, + "storage": { + "delete": [], + "get": [], + "head": [], + "list": [], + }, +} +`; + +exports[`legacy HTTP and storage contract > 'uppercase hash' 1`] = ` +{ + "body": "test", + "headers": { + "cache-control": "public, max-age=60", + "content-length": "4", + "content-md5": "wJowQVxBvz45xYtzR+irGg==", + "content-type": "application/octet-stream", + "last-modified": "Wed, 01 Nov 2023 01:02:03 GMT", + }, + "status": 200, + "storage": { + "delete": [], + "get": [ + [ + "objects/c09a30415c41bf3e39c58b7347e8ab1a", + ], + ], + "head": [], + "list": [], + }, +} +`; + +exports[`legacy HTTP and storage contract > preserves unexpected storage error responses 1`] = ` +{ + "body": "{"error":"internal_server_error"}", + "headers": { + "content-type": "application/json", + }, + "status": 500, +} +`; diff --git a/tests/contract/legacy.test.ts b/tests/contract/legacy.test.ts new file mode 100644 index 0000000..980da7e --- /dev/null +++ b/tests/contract/legacy.test.ts @@ -0,0 +1,272 @@ +import { createExecutionContext, waitOnExecutionContext } from 'cloudflare:test'; +import { env } from 'cloudflare:workers'; +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; +import worker from '../../src/index'; +import { createBucket, HASH, OTHER_HASH } from '../helpers/bucket'; + +// Recorded against main at 4376e99d1858682f27ac414ad014e53f7bf93759 BEFORE +// the Hono migration, then adjusted only for the approved HTTP response, routing, and MD5 validation changes +// documented in docs/compatibility.md. Other storage calls and signed URLs stay fixed. +interface Scenario { + name: string; + path: string; + method?: string; + body?: string; + headers?: Record; +} +const auth = { 'x-client-secret': 'test-secret' }; +const scenarios: Scenario[] = [ + { name: 'unknown endpoint', path: '/' }, + { name: 'unknown nested endpoint', path: '/md5/a/b' }, + { name: 'unknown bulk method', path: '/query', method: 'GET' }, + { name: 'missing return mode', path: '/md5' }, + { name: 'old documented md5 mode is rejected', path: '/md5?return=md5' }, + { name: 'list hashes', path: '/md5?return=hash' }, + { name: 'list metadata', path: '/md5?return=object' }, + { name: 'trailing slash', path: '/md5/?return=hash' }, + { name: 'double trailing slash', path: '/md5//?return=hash' }, + { name: 'duplicate return mode', path: '/md5?return=hash&return=object' }, + { name: 'encoded query parameter', path: '/md5?ret%75rn=h%61sh' }, + { name: 'list wrong method', path: '/md5', method: 'POST' }, + { name: 'list HEAD', path: '/md5?return=hash', method: 'HEAD' }, + { name: 'download', path: `/md5/${HASH}` }, + { name: 'uppercase hash', path: `/md5/${HASH.toUpperCase()}` }, + { name: 'download trailing slash', path: `/md5/${HASH}/` }, + { name: 'download double trailing slash', path: `/md5/${HASH}//` }, + { name: 'missing download', path: `/md5/${OTHER_HASH}` }, + { name: 'HEAD', path: `/md5/${HASH}`, method: 'HEAD' }, + { name: 'missing HEAD', path: `/md5/${OTHER_HASH}`, method: 'HEAD' }, + { name: 'legacy punctuation normalization', path: '/md5/G:A-Z_09%2F' }, + { name: 'percent encoded hash is decoded', path: `/md5/%63${HASH.slice(1)}` }, + { name: 'malformed percent encoding', path: '/md5/%ZZ' }, + { name: 'object PUT remains unsupported', path: `/md5/${HASH}`, method: 'PUT' }, + { name: 'object OPTIONS remains unsupported', path: `/md5/${HASH}`, method: 'OPTIONS' }, + { name: 'object PATCH remains unsupported', path: `/md5/${HASH}`, method: 'PATCH' }, + { name: 'upload unauthorized', path: `/md5/${HASH}`, method: 'POST' }, + { + name: 'upload empty secret', + path: `/md5/${HASH}`, + method: 'POST', + headers: { 'x-client-secret': '' }, + }, + { + name: 'upload forbidden', + path: `/md5/${HASH}`, + method: 'POST', + headers: { 'x-client-secret': 'wrong' }, + }, + { name: 'upload signature', path: `/md5/${HASH}`, method: 'POST', headers: auth }, + { + name: 'upload uppercase signature', + path: `/md5/${HASH.toUpperCase()}`, + method: 'POST', + headers: auth, + }, + { + name: 'upload overwrite signature', + path: `/md5/${HASH}?exists=overwrite`, + method: 'POST', + headers: auth, + }, + { + name: 'upload unknown exists means error', + path: `/md5/${HASH}?exists=skip`, + method: 'POST', + headers: auth, + }, + { + name: 'upload duplicate exists: first wins', + path: `/md5/${HASH}?exists=overwrite&exists=overwrite`, + method: 'POST', + headers: auth, + }, + { name: 'upload permissive hash', path: '/md5/not-a-hash', method: 'POST', headers: auth }, + { name: 'delete', path: `/md5/${HASH}`, method: 'DELETE', headers: auth }, + { name: 'delete missing object', path: `/md5/${OTHER_HASH}`, method: 'DELETE', headers: auth }, + { name: 'delete unauthorized', path: `/md5/${HASH}`, method: 'DELETE' }, + { + name: 'delete forbidden', + path: `/md5/${HASH}`, + method: 'DELETE', + headers: { 'x-client-secret': 'wrong' }, + }, + { + name: 'query includes duplicates and omits missing', + path: '/query', + method: 'POST', + body: JSON.stringify({ md5: [HASH, OTHER_HASH, HASH] }), + }, + { + name: 'query trailing slash', + path: '/query/', + method: 'POST', + body: JSON.stringify({ md5: [HASH] }), + }, + { name: 'query empty list', path: '/query', method: 'POST', body: '{"md5":[]}' }, + { + name: 'query accepts unrelated fields', + path: '/query', + method: 'POST', + body: '{"md5":[],"extra":1}', + }, + { + name: 'query ignores Content-Type', + path: '/query', + method: 'POST', + body: JSON.stringify({ md5: [HASH] }), + headers: { 'content-type': 'text/plain' }, + }, + { + name: 'query too large before invalid items', + path: '/query', + method: 'POST', + body: JSON.stringify({ md5: Array(1001).fill(null) }), + }, + { name: 'sync unauthorized before parsing', path: '/sync', method: 'POST', body: 'invalid' }, + { + name: 'sync forbidden before parsing', + path: '/sync', + method: 'POST', + body: 'invalid', + headers: { 'x-client-secret': 'wrong' }, + }, + { + name: 'sync raw R2 metadata and upload signature', + path: '/sync', + method: 'POST', + body: JSON.stringify({ md5: [HASH, OTHER_HASH, OTHER_HASH] }), + headers: auth, + }, + { + name: 'sync uppercase checksum uses original input', + path: '/sync', + method: 'POST', + body: JSON.stringify({ md5: [OTHER_HASH.toUpperCase()] }), + headers: auth, + }, + { + name: 'sync permissive checksum uses original input', + path: '/sync', + method: 'POST', + body: '{"md5":["AB-CD","g!",""]}', + headers: auth, + }, + { name: 'sync empty list', path: '/sync', method: 'POST', body: '{"md5":[]}', headers: auth }, +]; +for (const path of ['/query', '/sync', '/md5', `/md5/${HASH}`, '/unknown']) { + for (const method of ['GET', 'HEAD', 'POST', 'PUT', 'DELETE', 'PATCH', 'OPTIONS']) { + scenarios.push({ name: `${method} ${path} method matrix`, path, method, headers: auth }); + } +} +for (const path of ['/query', '/sync']) { + for (const [name, body] of Object.entries({ + null: 'null', + array: '[]', + string: '"string"', + missing: '{}', + scalar: '{"md5":"abc"}', + nonstring: '{"md5":["a",3]}', + malformed: '{', + empty: '', + })) { + scenarios.push({ name: `${path} ${name} body`, path, method: 'POST', body, headers: auth }); + } +} + +beforeEach(() => { + vi.useFakeTimers({ toFake: ['Date'] }); + vi.setSystemTime(new Date('2024-01-02T03:04:05.000Z')); + vi.spyOn(console, 'error').mockImplementation(() => {}); +}); +afterEach(() => vi.useRealTimers()); + +describe('legacy HTTP and storage contract', () => { + it.each(scenarios)('$name', async ({ path, method = 'GET', body, headers }) => { + const bucket = createBucket(); + const context = createExecutionContext(); + const request = new Request(`https://files.example${path}`, { method, body, headers }); + const response = await worker.fetch(request, { ...env, FILES_BUCKET: bucket.binding }, context); + await waitOnExecutionContext(context); + const text = new TextDecoder().decode(await response.arrayBuffer()); + // HEAD bodies are suppressed by the HTTP runtime, even if a handler creates one. + expect({ + status: response.status, + headers: Object.fromEntries(response.headers), + body: method === 'HEAD' ? '' : text, + storage: bucket.calls(), + }).toMatchSnapshot(); + }); + + it('accepts exactly 1000 items without changing their order or keys', async () => { + const bucket = createBucket(); + const context = createExecutionContext(); + const response = await worker.fetch( + new Request('https://files.example/query', { + method: 'POST', + body: JSON.stringify({ md5: Array(1000).fill(OTHER_HASH) }), + }), + { ...env, FILES_BUCKET: bucket.binding }, + context, + ); + await waitOnExecutionContext(context); + expect(response.status).toBe(200); + expect(await response.json()).toEqual([]); + expect(bucket.calls().head).toEqual(Array(1000).fill([`objects/${OTHER_HASH}`])); + }); + + it('returns a generic JSON error for string rejections from storage', async () => { + const bucket = createBucket(); + bucket.head.mockRejectedValueOnce('R2 unavailable'); + const context = createExecutionContext(); + const response = await worker.fetch( + new Request('https://files.example/query', { + method: 'POST', + body: JSON.stringify({ md5: [HASH] }), + }), + { ...env, FILES_BUCKET: bucket.binding }, + context, + ); + await waitOnExecutionContext(context); + expect(response.status).toBe(500); + expect(response.headers.get('content-type')).toBe('application/json'); + expect(await response.text()).toBe('{"error":"internal_server_error"}'); + }); + + it('preserves unexpected storage error responses', async () => { + const bucket = createBucket(); + bucket.head.mockRejectedValueOnce(new Error('R2 unavailable')); + const context = createExecutionContext(); + const response = await worker.fetch( + new Request('https://files.example/query', { + method: 'POST', + body: JSON.stringify({ md5: [HASH] }), + }), + { ...env, FILES_BUCKET: bucket.binding }, + context, + ); + await waitOnExecutionContext(context); + expect({ + status: response.status, + headers: Object.fromEntries(response.headers), + body: await response.text(), + }).toMatchSnapshot(); + }); + + it('does not misclassify a storage SyntaxError as a malformed client request', async () => { + const bucket = createBucket(); + bucket.head.mockRejectedValueOnce(new SyntaxError('internal storage detail')); + const context = createExecutionContext(); + const response = await worker.fetch( + new Request('https://files.example/query', { + method: 'POST', + body: JSON.stringify({ md5: [HASH] }), + }), + { ...env, FILES_BUCKET: bucket.binding }, + context, + ); + await waitOnExecutionContext(context); + expect(response.status).toBe(500); + expect(response.headers.get('content-type')).toBe('application/json'); + expect(await response.json()).toEqual({ error: 'internal_server_error' }); + }); +}); diff --git a/tests/env.d.ts b/tests/env.d.ts new file mode 100644 index 0000000..3fcaf4f --- /dev/null +++ b/tests/env.d.ts @@ -0,0 +1,10 @@ +import type { Env as WorkerEnv } from '../src/types/env'; + +declare global { + namespace Cloudflare { + interface Env extends WorkerEnv {} + interface GlobalProps { + mainModule: typeof import('../src/index'); + } + } +} diff --git a/tests/helpers/bucket.ts b/tests/helpers/bucket.ts new file mode 100644 index 0000000..a0871c6 --- /dev/null +++ b/tests/helpers/bucket.ts @@ -0,0 +1,57 @@ +import { vi } from 'vitest'; + +export const HASH = 'c09a30415c41bf3e39c58b7347e8ab1a'; +export const OTHER_HASH = 'd41d8cd98f00b204e9800998ecf8427e'; +export const UPLOADED = new Date('2023-11-01T01:02:03.000Z'); + +export function storedObject(key = `objects/${HASH}`, checksum = true) { + return { + key, + version: 'fixture-version', + size: 4, + etag: HASH, + httpEtag: `"${HASH}"`, + uploaded: UPLOADED, + httpMetadata: { contentType: 'application/octet-stream', cacheControl: 'public, max-age=60' }, + customMetadata: { originalName: 'uploadfile' }, + checksums: checksum + ? { md5: new Uint8Array(HASH.match(/../g)!.map((pair) => parseInt(pair, 16))).buffer } + : {}, + storageClass: 'Standard', + writeHttpMetadata(headers: Headers) { + headers.set('Content-Type', 'application/octet-stream'); + headers.set('Cache-Control', 'public, max-age=60'); + }, + }; +} + +export function createBucket() { + const objects = new Map([ + [`objects/${HASH}`, storedObject()], + // The old prefix is "objects", without a slash. Keep this edge case observable. + ['objects-old/abc', storedObject('objects-old/abc', false)], + ]); + const head = vi.fn(async (key: string) => objects.get(key) ?? null); + const get = vi.fn(async (key: string) => { + const object = objects.get(key); + return object ? { ...object, body: new Response('test').body } : null; + }); + const list = vi.fn(async () => ({ + objects: [...objects.values()], + truncated: true, + cursor: 'not-followed', + })); + const remove = vi.fn(async (key: string) => { + objects.delete(key); + }); + return { + binding: { head, get, list, delete: remove } as unknown as R2Bucket, + calls: () => ({ + head: head.mock.calls, + get: get.mock.calls, + list: list.mock.calls, + delete: remove.mock.calls, + }), + head, + }; +} diff --git a/tests/helpers/request.ts b/tests/helpers/request.ts new file mode 100644 index 0000000..2fece6c --- /dev/null +++ b/tests/helpers/request.ts @@ -0,0 +1,7 @@ +import { exports } from 'cloudflare:workers'; + +export function request(path: string, init?: RequestInit): Promise { + return exports.default.fetch(`https://files.example${path}`, init); +} + +export const auth = { 'x-client-secret': 'test-secret' }; diff --git a/tests/integration/cache.test.ts b/tests/integration/cache.test.ts new file mode 100644 index 0000000..16d6096 --- /dev/null +++ b/tests/integration/cache.test.ts @@ -0,0 +1,89 @@ +import { env } from 'cloudflare:workers'; +import { reset } from 'cloudflare:test'; +import { beforeEach, describe, expect, it } from 'vitest'; +import { HASH, OTHER_HASH } from '../helpers/bucket'; +import { auth, request } from '../helpers/request'; + +const key = `objects/${HASH}`; + +beforeEach(async () => { + await reset(); + await env.FILES_BUCKET.put(key, 'test', { httpMetadata: { contentType: 'text/plain' } }); +}); + +describe('download cache policy', () => { + it('adds a one-year immutable default without changing bytes or R2 metadata', async () => { + const before = JSON.stringify(await env.FILES_BUCKET.head(key)); + const response = await request(`/md5/${HASH}`); + expect(response.status).toBe(200); + expect(response.headers.get('cache-control')).toBe('public, max-age=31536000, immutable'); + expect(response.headers.get('content-type')).toBe('text/plain'); + expect(await response.text()).toBe('test'); + expect(JSON.stringify(await env.FILES_BUCKET.head(key))).toBe(before); + + const head = await request(`/md5/${HASH}`, { method: 'HEAD' }); + expect(head.status).toBe(200); + expect(head.headers.has('cache-control')).toBe(false); + expect(await head.text()).toBe(''); + }); + + it.each(['private, no-store', 'no-cache', 'public, max-age=600', ''])( + 'preserves an explicit Cache-Control value: %s', + async (cacheControl) => { + await env.FILES_BUCKET.put(key, 'test', { httpMetadata: { cacheControl } }); + for (const method of ['GET', 'HEAD']) { + const response = await request(`/md5/${HASH}`, { method }); + expect(response.status).toBe(200); + // The HTTP transport may omit an explicitly empty header. + expect(response.headers.get('cache-control') ?? '').toBe(cacheControl); + await response.arrayBuffer(); + } + }, + ); + + it('preserves Expires without adding a conflicting freshness lifetime', async () => { + const cacheExpiry = new Date('2030-01-01T00:00:00Z'); + await env.FILES_BUCKET.put(key, 'test', { httpMetadata: { cacheExpiry } }); + const response = await request(`/md5/${HASH}`); + expect(response.status).toBe(200); + expect(response.headers.get('expires')).toBe(cacheExpiry.toUTCString()); + expect(response.headers.has('cache-control')).toBe(false); + await response.arrayBuffer(); + }); + + it('preserves origin deletion and adds no long-lived policy to subsequent misses', async () => { + const download = await request(`/md5/${HASH}`); + expect(download.headers.get('cache-control')).toBe('public, max-age=31536000, immutable'); + await download.arrayBuffer(); + const deleted = await request(`/md5/${HASH}`, { method: 'DELETE', headers: auth }); + expect(deleted.status).toBe(204); + expect(deleted.headers.has('cache-control')).toBe(false); + expect(await env.FILES_BUCKET.head(key)).toBeNull(); + for (const method of ['GET', 'HEAD']) { + const response = await request(`/md5/${HASH}`, { method }); + expect(response.status).toBe(404); + expect(response.headers.has('cache-control')).toBe(false); + await response.arrayBuffer(); + } + }); + + it.each([ + { path: '/md5?return=hash', method: 'GET', status: 200 }, + { path: '/md5?return=object', method: 'GET', status: 200 }, + { path: '/query', method: 'POST', body: '{"md5":[]}', status: 200 }, + { path: '/sync', method: 'POST', body: '{"md5":[]}', headers: auth, status: 200 }, + { path: `/md5/${HASH}`, method: 'POST', headers: auth, status: 200 }, + { path: `/md5/${HASH}`, method: 'POST', status: 401 }, + { path: `/md5/${HASH}`, method: 'PUT', status: 404 }, + { path: `/md5/${OTHER_HASH}`, method: 'GET', status: 404 }, + { path: '/unknown', method: 'GET', status: 404 }, + { path: '/query', method: 'POST', body: '{}', status: 400 }, + { path: '/openapi.json', method: 'GET', status: 200 }, + { path: '/docs', method: 'GET', status: 200 }, + ])('does not add the policy to $method $path ($status)', async ({ path, status, ...init }) => { + const response = await request(path, init); + expect(response.status).toBe(status); + expect(response.headers.has('cache-control')).toBe(false); + await response.arrayBuffer(); + }); +}); diff --git a/tests/integration/docs.test.ts b/tests/integration/docs.test.ts new file mode 100644 index 0000000..448a523 --- /dev/null +++ b/tests/integration/docs.test.ts @@ -0,0 +1,125 @@ +import { describe, expect, it } from 'vitest'; +import type { OpenAPIV3_1 } from 'openapi-types'; +import { createApp } from '../../src/app'; +import { request } from '../helpers/request'; + +async function document() { + const response = await request('/openapi.json'); + expect(response.status).toBe(200); + expect(response.headers.get('content-type')).toContain('application/json'); + return response.json(); +} + +describe('generated OpenAPI and Scalar', () => { + it('discovers registered operations and describes HEAD alongside GET', async () => { + const spec = await document(); + expect(spec.openapi).toBe('3.1.0'); + expect(createApp().routes.some((route) => route.method === 'HEAD')).toBe(false); + expect(spec.paths!['/md5/{hash}']!.get!.description).toContain('HEAD is also supported'); + expect(spec.paths!['/md5/{hash}']!.get!.description).toContain( + 'returns no body, including on errors', + ); + expect( + Object.fromEntries( + Object.entries(spec.paths!).map(([path, operations]) => [ + path, + Object.keys(operations!).sort(), + ]), + ), + ).toEqual({ + '/md5': ['get'], + '/md5/{hash}': ['delete', 'get', 'post'], + '/query': ['post'], + '/sync': ['post'], + }); + expect(spec.components!.securitySchemes!.clientSecret).toEqual({ + type: 'apiKey', + name: 'x-client-secret', + in: 'header', + }); + for (const [path, method] of [ + ['/md5/{hash}', 'post'], + ['/md5/{hash}', 'delete'], + ['/sync', 'post'], + ] as const) { + expect(spec.paths![path]![method]!.security).toEqual([{ clientSecret: [] }]); + } + expect(spec.paths!['/query']!.post!.security).toBeUndefined(); + expect(spec.paths!['/md5/{hash}']!.get!.security).toBeUndefined(); + }); + + it('documents JSON media types, validation limit, and distinct response shapes', async () => { + const spec = await document(); + const query = spec.paths!['/query']!.post!; + expect(query.requestBody).toMatchObject({ + content: { + 'application/json': { + schema: { + required: ['md5'], + properties: { md5: { type: 'array', maxItems: 1000, items: { type: 'string' } } }, + }, + }, + }, + }); + expect(query.responses!['200']).toMatchObject({ + content: { + 'application/json': { + schema: { type: 'array', items: { required: ['uploaded', 'size', 'md5'] } }, + }, + }, + }); + expect(query.responses!['500']).toMatchObject({ + content: { 'application/json': { schema: { properties: { error: { type: 'string' } } } } }, + }); + expect(spec.paths!['/md5/{hash}']!.post!.responses!['200']).toMatchObject({ + content: { 'application/json': { schema: { required: ['method', 'url', 'headers'] } } }, + }); + expect(spec.paths!['/sync']!.post!.responses!['200']).toMatchObject({ + content: { + 'application/json': { + schema: { + properties: { + objects: { + items: { properties: { key: { type: 'string' }, httpEtag: { type: 'string' } } }, + }, + }, + }, + }, + }, + }); + }); + + it('serves Scalar linked to the generated specification without embedding credentials', async () => { + const response = await request('/docs'); + expect(response.status).toBe(200); + expect(response.headers.get('content-type')).toContain('text/html'); + const html = await response.text(); + expect(html).toContain('/openapi.json'); + expect(html).toContain('@scalar/api-reference'); + expect(html).toContain('rfiles API'); + expect(html).toContain('https://files.example'); + expect(html).not.toContain('test-secret'); + expect(html).not.toContain('test-access-key'); + }); + + it('returns the same document on repeated requests', async () => { + expect(await document()).toEqual(await document()); + }); + + it('documents the immutable default only on successful file downloads', async () => { + const spec = await document(); + expect(spec.paths!['/md5/{hash}']!.get!.responses!['200']).toMatchObject({ + headers: { + 'Cache-Control': { + schema: { type: 'string', example: 'public, max-age=31536000, immutable' }, + }, + }, + }); + expect(spec.paths!['/md5/{hash}']!.get!.description).toContain( + 'without adding the immutable cache default', + ); + expect(spec.paths!['/md5/{hash}']!.get!.responses!['404']).not.toHaveProperty( + 'headers.Cache-Control', + ); + }); +}); diff --git a/tests/integration/files.test.ts b/tests/integration/files.test.ts new file mode 100644 index 0000000..832089c --- /dev/null +++ b/tests/integration/files.test.ts @@ -0,0 +1,159 @@ +import { env } from 'cloudflare:workers'; +import { reset } from 'cloudflare:test'; +import { beforeEach, describe, expect, it } from 'vitest'; +import { HASH, OTHER_HASH } from '../helpers/bucket'; +import { auth, request } from '../helpers/request'; + +const key = `objects/${HASH}`; +const body = new Uint8Array([0, 127, 128, 255]); + +beforeEach(async () => { + await reset(); + const md5 = await crypto.subtle.digest('MD5', body); + await env.FILES_BUCKET.put(key, body, { + md5, + httpMetadata: { + contentType: 'image/png', + contentDisposition: 'attachment; filename="fixture.png"', + contentLanguage: 'ko', + cacheControl: 'public, max-age=600', + }, + customMetadata: { originalName: 'fixture.png', owner: 'test' }, + }); +}); + +describe('Worker with a real local R2 binding', () => { + it('streams existing bytes and all stored HTTP metadata without rewriting the object', async () => { + const before = await env.FILES_BUCKET.head(key); + const response = await request(`/md5/${HASH.toUpperCase()}`); + expect(response.status).toBe(200); + expect(new Uint8Array(await response.arrayBuffer())).toEqual(body); + expect(Object.fromEntries(response.headers)).toEqual({ + 'content-type': 'image/png', + 'content-length': '4', + 'content-disposition': 'attachment; filename="fixture.png"', + 'content-language': 'ko', + 'cache-control': 'public, max-age=600', + 'content-md5': btoa(String.fromCharCode(...new Uint8Array(before!.checksums.md5!))), + 'last-modified': before!.uploaded.toUTCString(), + }); + expect(JSON.stringify(await env.FILES_BUCKET.head(key))).toBe(JSON.stringify(before)); + }); + + it('returns identical file headers for HEAD and no body', async () => { + const download = await request(`/md5/${HASH}`); + await download.arrayBuffer(); + const head = await request(`/md5/${HASH}`, { method: 'HEAD' }); + expect(head.status).toBe(200); + expect(Object.fromEntries(head.headers)).toEqual(Object.fromEntries(download.headers)); + expect((await head.arrayBuffer()).byteLength).toBe(0); + }); + + it('returns bodyless HEAD responses for missing files and the list', async () => { + const missing = await request(`/md5/${OTHER_HASH}`, { method: 'HEAD' }); + expect(missing.status).toBe(404); + expect(await missing.text()).toBe(''); + const list = await request('/md5?return=hash', { method: 'HEAD' }); + expect(list.status).toBe(200); + expect(list.headers.has('allow')).toBe(false); + expect(await list.text()).toBe(''); + }); + + it('queries metadata in input order, keeping duplicates and ignoring unknown hashes', async () => { + const stored = await env.FILES_BUCKET.head(key); + const response = await request('/query', { + method: 'POST', + body: JSON.stringify({ md5: [HASH, OTHER_HASH, HASH.toUpperCase()] }), + }); + const metadata = { uploaded: stored!.uploaded.toISOString(), size: 4, md5: HASH }; + expect(response.headers.get('content-type')).toBe('application/json'); + expect(await response.json()).toEqual([metadata, metadata]); + }); + + it('preserves native R2 serialization in sync and signs only missing entries', async () => { + const stored = await env.FILES_BUCKET.head(key); + const response = await request('/sync', { + method: 'POST', + headers: auth, + body: JSON.stringify({ md5: [HASH, OTHER_HASH] }), + }); + expect(response.status).toBe(200); + const result = await response.json<{ + objects: unknown[]; + uploads: { method: string; url: string; headers: Record }[]; + }>(); + expect(result.objects).toEqual([JSON.parse(JSON.stringify(stored))]); + expect(result.uploads).toHaveLength(1); + expect(result.uploads[0].method).toBe('PUT'); + expect(result.uploads[0].headers).toEqual({ + 'Content-MD5': '1B2M2Y8AsgTpgAmY7PhCfg==', + 'If-Unmodified-Since': 'Tue, 28 Sep 2021 16:00:00 GMT', + }); + const url = new URL(result.uploads[0].url); + expect(url.origin).toBe('https://test-account.r2.cloudflarestorage.com'); + expect(url.pathname).toBe(`/files/objects/${OTHER_HASH}`); + expect(url.searchParams.get('X-Amz-Expires')).toBe('600'); + expect(url.searchParams.get('X-Amz-SignedHeaders')).toBe( + 'content-md5;host;if-unmodified-since', + ); + expect(await env.FILES_BUCKET.head(`objects/${OTHER_HASH}`)).toBeNull(); + }); + + it('lists the original prefix, including keys without an objects/ separator', async () => { + await env.FILES_BUCKET.put('objects-old/extra', 'extra'); + await env.FILES_BUCKET.put('unrelated/file', 'unrelated'); + const response = await request('/md5?return=hash'); + expect(await response.json()).toEqual(['old/extra', HASH]); + const metadata = await request('/md5?return=object'); + expect(await metadata.json()).toEqual([ + { + uploaded: (await env.FILES_BUCKET.head('objects-old/extra'))!.uploaded.toISOString(), + size: 5, + md5: 'old/extra', + }, + { uploaded: (await env.FILES_BUCKET.head(key))!.uploaded.toISOString(), size: 4, md5: HASH }, + ]); + }); + + it('deletes only the normalized key and is idempotent', async () => { + await env.FILES_BUCKET.put(`unrelated/${HASH}`, 'other'); + for (let i = 0; i < 2; i++) { + const response = await request(`/md5/${HASH.toUpperCase()}`, { + method: 'DELETE', + headers: auth, + }); + expect(response.status).toBe(204); + expect(await response.text()).toBe(''); + } + expect(await env.FILES_BUCKET.head(key)).toBeNull(); + expect(await env.FILES_BUCKET.head(`unrelated/${HASH}`)).not.toBeNull(); + }); + + it('does not mutate storage when authentication fails', async () => { + const before = JSON.stringify(await env.FILES_BUCKET.head(key)); + for (const method of ['POST', 'DELETE']) { + const unauthorized = await request(`/md5/${HASH}`, { method }); + expect(unauthorized.status).toBe(401); + const forbidden = await request(`/md5/${HASH}`, { + method, + headers: { 'x-client-secret': 'wrong' }, + }); + expect(forbidden.status).toBe(403); + } + expect(JSON.stringify(await env.FILES_BUCKET.head(key))).toBe(before); + }); + + it('issues an upload without storing bytes or changing metadata', async () => { + const before = JSON.stringify(await env.FILES_BUCKET.head(key)); + const response = await request(`/md5/${HASH}?exists=overwrite`, { + method: 'POST', + headers: auth, + body: 'ignored', + }); + expect(response.status).toBe(200); + expect(response.headers.get('content-type')).toBe('application/json'); + const instruction = await response.json<{ url: string }>(); + expect(new URL(instruction.url).pathname).toBe(`/files/${key}`); + expect(JSON.stringify(await env.FILES_BUCKET.head(key))).toBe(before); + }); +}); diff --git a/tests/integration/routing.test.ts b/tests/integration/routing.test.ts new file mode 100644 index 0000000..485f3b6 --- /dev/null +++ b/tests/integration/routing.test.ts @@ -0,0 +1,72 @@ +import { env } from 'cloudflare:workers'; +import { reset } from 'cloudflare:test'; +import { beforeEach, describe, expect, it } from 'vitest'; +import { HASH } from '../helpers/bucket'; +import { auth, request } from '../helpers/request'; + +const encodedHash = `%63${HASH.slice(1)}`; + +beforeEach(async () => { + await reset(); + await env.FILES_BUCKET.put(`objects/${HASH}`, 'test'); +}); + +describe('native Hono URL handling', () => { + it.each([ + ['/md5/?return=hash', 'GET'], + [`/md5/${HASH}/`, 'GET'], + [`/md5/${HASH}/`, 'DELETE'], + ['/query/', 'POST'], + ['/sync/', 'POST'], + ])('rejects trailing slashes: %s %s', async (path, method) => { + const response = await request(path, { method, headers: auth }); + expect(response.status).toBe(404); + expect(response.headers.has('location')).toBe(false); + await response.text(); + expect(await env.FILES_BUCKET.head(`objects/${HASH}`)).not.toBeNull(); + }); + + it('uses decoded hashes for reads, upload instructions, and deletion', async () => { + for (const method of ['GET', 'HEAD']) { + const response = await request(`/md5/${encodedHash}`, { method }); + expect(response.status).toBe(200); + expect(await response.text()).toBe(method === 'GET' ? 'test' : ''); + } + const upload = await request(`/md5/${encodedHash}`, { method: 'POST', headers: auth }); + const instruction = await upload.json<{ url: string }>(); + expect(new URL(instruction.url).pathname).toBe(`/files/objects/${HASH}`); + const deletion = await request(`/md5/${encodedHash}`, { method: 'DELETE', headers: auth }); + expect(deletion.status).toBe(204); + expect(await env.FILES_BUCKET.head(`objects/${HASH}`)).toBeNull(); + }); + + it('decodes path parameters once and leaves bulk JSON hashes undecoded', async () => { + const doubleEncoded = await request(`/md5/%2563${HASH.slice(1)}`); + expect(doubleEncoded.status).toBe(400); + await doubleEncoded.text(); + const bulk = await request('/query', { + method: 'POST', + body: JSON.stringify({ md5: [encodedHash, HASH] }), + }); + expect(bulk.status).toBe(400); + expect(await bulk.json()).toEqual({ error: 'bad_request' }); + }); + + it('uses the first repeated query value', async () => { + const hashes = await request('/md5?return=hash&return=object'); + expect(await hashes.json()).toEqual([HASH]); + const invalid = await request('/md5?return=invalid&return=hash'); + expect(invalid.status).toBe(400); + await invalid.text(); + for (const [query, overwrite] of [ + ['exists=overwrite&exists=error', true], + ['exists=error&exists=overwrite', false], + ] as const) { + const response = await request(`/md5/${HASH}?${query}`, { method: 'POST', headers: auth }); + const instruction = await response.json<{ headers: Record }>(); + expect(instruction.headers['If-Unmodified-Since'] !== 'Tue, 28 Sep 2021 16:00:00 GMT').toBe( + overwrite, + ); + } + }); +}); diff --git a/tests/integration/validation.test.ts b/tests/integration/validation.test.ts new file mode 100644 index 0000000..a093a59 --- /dev/null +++ b/tests/integration/validation.test.ts @@ -0,0 +1,60 @@ +import { env } from 'cloudflare:workers'; +import { createExecutionContext, waitOnExecutionContext } from 'cloudflare:test'; +import { describe, expect, it } from 'vitest'; +import worker from '../../src/index'; +import { createBucket, HASH } from '../helpers/bucket'; +import { auth, request } from '../helpers/request'; + +describe('MD5 validation', () => { + it.each(['GET', 'HEAD', 'POST', 'DELETE'])( + 'rejects invalid %s hashes before R2 access', + async (method) => { + const bucket = createBucket(); + const context = createExecutionContext(); + const response = await worker.fetch( + new Request('https://files.example/md5/not-a-hash', { method, headers: auth }), + { ...env, FILES_BUCKET: bucket.binding }, + context, + ); + await waitOnExecutionContext(context); + expect(response.status).toBe(400); + expect(bucket.calls()).toEqual({ get: [], head: [], list: [], delete: [] }); + await response.arrayBuffer(); + if (method === 'HEAD') { + const head = await request('/md5/not-a-hash', { method }); + expect(head.status).toBe(400); + expect(await head.text()).toBe(''); + } + }, + ); + + it.each(['/query', '/sync'])( + 'validates the entire %s request before storage access', + async (path) => { + for (const invalid of [ + '', + 'g'.repeat(32), + '0'.repeat(31), + '0'.repeat(33), + ` ${HASH}`, + `${HASH}\n`, + ]) { + const bucket = createBucket(); + const context = createExecutionContext(); + const response = await worker.fetch( + new Request(`https://files.example${path}`, { + method: 'POST', + headers: auth, + body: JSON.stringify({ md5: [HASH, invalid] }), + }), + { ...env, FILES_BUCKET: bucket.binding }, + context, + ); + await waitOnExecutionContext(context); + expect(response.status).toBe(400); + expect(await response.json()).toEqual({ error: 'bad_request' }); + expect(bucket.calls()).toEqual({ get: [], head: [], list: [], delete: [] }); + } + }, + ); +}); diff --git a/tests/tsconfig.json b/tests/tsconfig.json new file mode 100644 index 0000000..cd11edc --- /dev/null +++ b/tests/tsconfig.json @@ -0,0 +1,7 @@ +{ + "extends": "../tsconfig.json", + "compilerOptions": { + "types": ["@cloudflare/workers-types", "@cloudflare/vitest-plugin/types", "node"] + }, + "include": ["./**/*.ts", "../src/**/*.ts", "../vitest.config.ts", "../scripts/**/*.ts"] +} diff --git a/tests/unit/storage.test.ts b/tests/unit/storage.test.ts new file mode 100644 index 0000000..f662c46 --- /dev/null +++ b/tests/unit/storage.test.ts @@ -0,0 +1,48 @@ +import { env } from 'cloudflare:workers'; +import { afterEach, describe, expect, it, vi } from 'vitest'; +import { normalizeHash, hexToBase64, encodeBase64 } from '../../src/lib/encoding'; +import { objectHeaders, objectKey } from '../../src/modules/files/repository'; +import { createUploadInstruction } from '../../src/modules/files/upload'; +import { HASH, storedObject } from '../helpers/bucket'; + +afterEach(() => vi.useRealTimers()); + +describe('persistent key and signing behavior', () => { + it('normalizes valid MD5 hashes and preserves their keys and checksums', () => { + expect(normalizeHash(HASH.toUpperCase())).toBe(HASH); + expect(objectKey(HASH.toUpperCase())).toBe(`objects/${HASH}`); + expect(hexToBase64(HASH)).toBe('wJowQVxBvz45xYtzR+irGg=='); + expect(hexToBase64(HASH.toUpperCase())).toBe(hexToBase64(HASH)); + expect(encodeBase64(new Uint8Array([0, 127, 128, 255]).buffer)).toBe('AH+A/w=='); + }); + + it.each(['', 'a', 'gg', 'AB-CD', '0'.repeat(31), '0'.repeat(33), '_'.repeat(32)])( + 'rejects invalid MD5 input: %s', + (hash) => { + expect(() => normalizeHash(hash)).toThrow('Invalid MD5 hash'); + expect(() => hexToBase64(hash)).toThrow('Invalid MD5 hash'); + }, + ); + + it('does not invent an MD5 header when R2 has no checksum', () => { + const headers = objectHeaders(storedObject(`objects/${HASH}`, false) as unknown as R2Object); + expect(headers.has('content-md5')).toBe(false); + expect(headers.get('content-length')).toBe('4'); + expect(headers.get('cache-control')).toBe('public, max-age=60'); + }); + + it('retains endpoint concatenation and query parameters', async () => { + vi.useFakeTimers({ toFake: ['Date'] }); + vi.setSystemTime(new Date('2024-01-02T03:04:05Z')); + const instruction = await createUploadInstruction(HASH, 'error', { + ...env, + S3_ENDPOINT: 'https://test-account.r2.cloudflarestorage.com/files?custom=1', + }); + const url = new URL(instruction.url); + expect(url.pathname).toBe(`/filesobjects/${HASH}`); + expect(url.searchParams.get('custom')).toBe('1'); + expect(url.searchParams.get('X-Amz-Date')).toBe('20240102T030405Z'); + expect(url.searchParams.get('X-Amz-Expires')).toBe('600'); + expect(instruction.headers['If-Unmodified-Since']).toBe('Tue, 28 Sep 2021 16:00:00 GMT'); + }); +}); diff --git a/tsconfig.json b/tsconfig.json index cbea026..f0ca9f1 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,105 +1,17 @@ { - "compilerOptions": { - /* Visit https://aka.ms/tsconfig.json to read more about this file */ - - /* Projects */ - // "incremental": true, /* Enable incremental compilation */ - // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */ - // "tsBuildInfoFile": "./", /* Specify the folder for .tsbuildinfo incremental compilation files. */ - // "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": [ - "es2021" - ] /* Specify a set of bundled library declaration files that describe the target runtime environment. */, - "jsx": "react" /* 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. */ - - /* Modules */ - "module": "es2022" /* Specify what module code is generated. */, - // "rootDir": "./", /* Specify the root folder within your source files. */ - "moduleResolution": "node" /* Specify how TypeScript looks up a file from a given module specifier. */, - // "baseUrl": "./", /* 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": [ - "@cloudflare/workers-types" - ] /* Specify type package names to be included without being referenced in a source file. */, - // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ - "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": false /* 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": "./", /* 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, /* Type catch clause variables as 'unknown' instead of 'any'. */ - // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */ - // "noUnusedLocals": true, /* Enable error reporting when a 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, /* Include 'undefined' in index signature results */ - // "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. */ - } + "compilerOptions": { + "target": "ES2022", + "lib": ["ES2022"], + "module": "ESNext", + "moduleResolution": "Bundler", + "types": ["@cloudflare/workers-types"], + "strict": true, + "noEmit": true, + "isolatedModules": true, + "verbatimModuleSyntax": true, + "resolveJsonModule": true, + "forceConsistentCasingInFileNames": true, + "skipLibCheck": true + }, + "include": ["src/**/*.ts"] } diff --git a/vitest.config.ts b/vitest.config.ts new file mode 100644 index 0000000..06e9a46 --- /dev/null +++ b/vitest.config.ts @@ -0,0 +1,29 @@ +import { cloudflareTest } from '@cloudflare/vitest-plugin'; +import { defineConfig } from 'vitest/config'; + +export default defineConfig({ + plugins: [ + cloudflareTest({ + wrangler: { configPath: './wrangler.toml' }, + miniflare: { + bindings: { + CLIENT_SECRET: 'test-secret', + S3_ACCESS_KEY: 'test-access-key', + S3_SECRET_ACCESS_KEY: 'test-secret-key', + S3_ENDPOINT: 'https://test-account.r2.cloudflarestorage.com/files/', + }, + }, + }), + ], + test: { + include: ['tests/**/*.test.ts'], + restoreMocks: true, + coverage: { + provider: 'istanbul', + include: ['src/**/*.ts'], + exclude: ['src/types/**'], + reporter: ['text', 'html', 'lcov'], + thresholds: { statements: 95, branches: 95, functions: 95, lines: 95 }, + }, + }, +}); diff --git a/wrangler.toml b/wrangler.toml index 09085a7..bd869a5 100644 --- a/wrangler.toml +++ b/wrangler.toml @@ -4,6 +4,15 @@ compatibility_date = "2023-05-02" account_id = "5af6495dfb1eac16b73762f2ffe27095" workers_dev = false +preview_urls = false + +routes = [ + { pattern = "rfiles.alphabeta.pw", custom_domain = true }, + { pattern = "rfiles.snowfrost.kr", custom_domain = true }, +] + +[observability.logs] +enabled = true [[r2_buckets]] binding = "FILES_BUCKET"